@unbrained/pm-cli 2026.8.10 → 2026.8.12

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 (243) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +153 -100
  3. package/dist/cli/commander-usage.js +113 -15
  4. package/dist/cli/error-guidance.d.ts +12 -0
  5. package/dist/cli/error-guidance.js +147 -29
  6. package/dist/cli/main.js +38 -19
  7. package/dist/cli/register-annotations.js +56 -3
  8. package/dist/cli/register-assurance.d.ts +8 -0
  9. package/dist/cli/register-assurance.js +52 -0
  10. package/dist/cli/register-files-lookup.js +14 -3
  11. package/dist/cli/register-list-query.js +10 -7
  12. package/dist/cli/register-mutation.js +30 -9
  13. package/dist/cli/register-operations.js +30 -15
  14. package/dist/cli-bundle/bundle-manifest.json +155 -155
  15. package/dist/cli-bundle/chunks/{chunk-TRWXF476.js → chunk-3PS7X6GS.js} +2 -2
  16. package/dist/cli-bundle/chunks/chunk-7KL6KMWF.js +13 -0
  17. package/dist/cli-bundle/chunks/{chunk-EBO7OUUE.js → chunk-BM5YJ2UW.js} +69 -55
  18. package/dist/cli-bundle/chunks/chunk-D55A5AEN.js +33 -0
  19. package/dist/cli-bundle/chunks/chunk-KIKDWECH.js +196 -0
  20. package/dist/cli-bundle/chunks/{chunk-ETML5C6H.js → chunk-KOQMGCTA.js} +47 -47
  21. package/dist/cli-bundle/chunks/chunk-R76342NV.js +2 -0
  22. package/dist/cli-bundle/chunks/chunk-RBTK5EZV.js +5 -0
  23. package/dist/cli-bundle/chunks/chunk-VLUQOIDT.js +2 -0
  24. package/dist/cli-bundle/chunks/chunk-WLAHPSOX.js +3 -0
  25. package/dist/cli-bundle/chunks/register-list-query-XPCJRUUD.js +10 -0
  26. package/dist/cli-bundle/chunks/register-mutation-4RJCBKRS.js +20 -0
  27. package/dist/cli-bundle/chunks/register-operations-2OGISOVR.js +2 -0
  28. package/dist/cli-bundle/chunks/{register-setup-S3KT2LTH.js → register-setup-LLHAKP2V.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/chunk-2DALMVF7.js +5 -0
  30. package/dist/cli-bundle/focused-chunks/chunk-5OQIMELN.js +155 -0
  31. package/dist/cli-bundle/focused-chunks/chunk-5TAGTDRE.js +2 -0
  32. package/dist/cli-bundle/focused-chunks/chunk-ECADZMVC.js +18 -0
  33. package/dist/cli-bundle/focused-chunks/chunk-EJGXRA6B.js +2 -0
  34. package/dist/cli-bundle/focused-chunks/chunk-FFGSZCPK.js +16 -0
  35. package/dist/cli-bundle/focused-chunks/{chunk-IW43E4XL.js → chunk-GQW4BPUT.js} +2 -2
  36. package/dist/cli-bundle/focused-chunks/chunk-ISKOBJCM.js +2 -0
  37. package/dist/cli-bundle/focused-chunks/{chunk-75774JWP.js → chunk-JAFRIWVF.js} +2 -2
  38. package/dist/cli-bundle/focused-chunks/{chunk-2COZ67XT.js → chunk-KHM2CS2C.js} +2 -2
  39. package/dist/cli-bundle/focused-chunks/{chunk-M6RPIXKC.js → chunk-KPSXVBDV.js} +2 -2
  40. package/dist/cli-bundle/focused-chunks/{chunk-34SX654S.js → chunk-KTLOKKCQ.js} +2 -2
  41. package/dist/cli-bundle/focused-chunks/chunk-LGYK3NUF.js +8 -0
  42. package/dist/cli-bundle/focused-chunks/{chunk-2EXI2DKR.js → chunk-NBLUKITQ.js} +2 -2
  43. package/dist/cli-bundle/focused-chunks/chunk-NYLOUPOB.js +2 -0
  44. package/dist/cli-bundle/focused-chunks/chunk-Q4T463SD.js +31 -0
  45. package/dist/cli-bundle/focused-chunks/chunk-QVZVY53N.js +2 -0
  46. package/dist/cli-bundle/focused-chunks/{chunk-DZGAHZZV.js → chunk-RWPAWUOT.js} +2 -2
  47. package/dist/cli-bundle/focused-chunks/chunk-UTEWXDEQ.js +17 -0
  48. package/dist/cli-bundle/focused-chunks/{chunk-7UN6BHHO.js → chunk-YIAXMC3T.js} +2 -2
  49. package/dist/cli-bundle/focused-chunks/chunk-ZMGWAYH7.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 +41 -39
  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 -2
  61. package/dist/core/extensions/reserved-host-flags.js +3 -2
  62. package/dist/core/history/drift-scan.js +13 -8
  63. package/dist/core/history/history.d.ts +8 -0
  64. package/dist/core/history/history.js +57 -7
  65. package/dist/core/history/replay.d.ts +9 -1
  66. package/dist/core/history/replay.js +48 -13
  67. package/dist/core/output/output.d.ts +2 -0
  68. package/dist/core/output/output.js +46 -14
  69. package/dist/core/session/session-state.d.ts +47 -0
  70. package/dist/core/session/session-state.js +207 -6
  71. package/dist/core/shared/author.d.ts +7 -1
  72. package/dist/core/shared/author.js +66 -46
  73. package/dist/core/shared/command-types.d.ts +2 -0
  74. package/dist/core/shared/command-types.js +2 -2
  75. package/dist/core/shared/errors.d.ts +13 -0
  76. package/dist/core/shared/errors.js +2 -2
  77. package/dist/core/store/item-metadata-cache.d.ts +1 -1
  78. package/dist/core/store/item-metadata-cache.js +14 -7
  79. package/dist/sdk/agent/refusal-reachability.d.ts +40 -0
  80. package/dist/sdk/agent/refusal-reachability.js +72 -0
  81. package/dist/sdk/agent/subcommand-recovery.d.ts +40 -0
  82. package/dist/sdk/agent/subcommand-recovery.js +84 -0
  83. package/dist/sdk/cli-bootstrap.d.ts +10 -0
  84. package/dist/sdk/cli-bootstrap.js +11 -7
  85. package/dist/sdk/cli-contracts/completeness.js +14 -3
  86. package/dist/sdk/cli-contracts/enum-contracts.d.ts +1 -1
  87. package/dist/sdk/cli-contracts/enum-contracts.js +3 -2
  88. package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
  89. package/dist/sdk/cli-contracts/flag-contracts.js +20 -2
  90. package/dist/sdk/cli-contracts/registration-helpers.d.ts +20 -3
  91. package/dist/sdk/cli-contracts/registration-helpers.js +18 -12
  92. package/dist/sdk/cli-contracts/runtime-contracts.js +4 -3
  93. package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +515 -0
  94. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +252 -0
  95. package/dist/sdk/cli-contracts/tool-option-contracts.js +3 -2
  96. package/dist/sdk/cli-contracts/tool-parameter-tables.js +99 -2
  97. package/dist/sdk/cli-contracts/tool-schema.d.ts +3 -3
  98. package/dist/sdk/cli-contracts/tool-schema.js +38 -5
  99. package/dist/sdk/cli-contracts.d.ts +1 -1
  100. package/dist/sdk/cli-contracts.js +3 -3
  101. package/dist/sdk/cli-program.js +3 -2
  102. package/dist/sdk/completion.js +23 -8
  103. package/dist/sdk/config.js +9 -3
  104. package/dist/sdk/context/semantic-session-attribution.d.ts +32 -0
  105. package/dist/sdk/context/semantic-session-attribution.js +83 -0
  106. package/dist/sdk/context-intent-contracts.js +45 -4
  107. package/dist/sdk/core-governance.d.ts +3 -1
  108. package/dist/sdk/core-governance.js +5 -3
  109. package/dist/sdk/core.d.ts +2 -1
  110. package/dist/sdk/core.js +3 -2
  111. package/dist/sdk/error-code-catalog.d.ts +13 -0
  112. package/dist/sdk/error-code-catalog.js +35 -2
  113. package/dist/sdk/extension.js +18 -17
  114. package/dist/sdk/files.d.ts +11 -0
  115. package/dist/sdk/files.js +82 -24
  116. package/dist/sdk/generated/generated-error-code-catalog-part-1.d.ts +8 -0
  117. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1585 -0
  118. package/dist/sdk/generated/generated-error-code-catalog-part-2.d.ts +8 -0
  119. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1608 -0
  120. package/dist/sdk/generated-error-code-catalog.js +6 -3121
  121. package/dist/sdk/governance/assurance-action-contracts.d.ts +7 -0
  122. package/dist/sdk/governance/assurance-action-contracts.js +5 -0
  123. package/dist/sdk/governance/assurance-action.d.ts +70 -0
  124. package/dist/sdk/governance/assurance-action.js +200 -0
  125. package/dist/sdk/governance/assurance-limits.d.ts +7 -0
  126. package/dist/sdk/governance/assurance-limits.js +11 -0
  127. package/dist/sdk/governance/assurance-mutation-error.d.ts +7 -0
  128. package/dist/sdk/governance/assurance-mutation-error.js +35 -0
  129. package/dist/sdk/governance/assurance-runtime.d.ts +20 -0
  130. package/dist/sdk/governance/assurance-runtime.js +134 -0
  131. package/dist/sdk/governance/assurance.d.ts +438 -0
  132. package/dist/sdk/governance/assurance.js +804 -0
  133. package/dist/sdk/governance/health.d.ts +3 -1
  134. package/dist/sdk/governance/health.js +21 -4
  135. package/dist/sdk/governance/validate-item-reader.js +3 -3
  136. package/dist/sdk/governance/validate.d.ts +2 -0
  137. package/dist/sdk/governance/validate.js +13 -3
  138. package/dist/sdk/graph/durable-cache.d.ts +1 -1
  139. package/dist/sdk/graph/durable-cache.js +98 -29
  140. package/dist/sdk/graph/governance.d.ts +14 -0
  141. package/dist/sdk/graph/governance.js +34 -9
  142. package/dist/sdk/graph/run.js +8 -3
  143. package/dist/sdk/index.d.ts +7 -2
  144. package/dist/sdk/index.js +9 -4
  145. package/dist/sdk/lifecycle/claim.js +26 -4
  146. package/dist/sdk/lifecycle/focus.js +13 -3
  147. package/dist/sdk/lifecycle/plan.js +8 -3
  148. package/dist/sdk/merge/driver.d.ts +2 -2
  149. package/dist/sdk/merge/driver.js +3 -3
  150. package/dist/sdk/merge/receipts.d.ts +6 -4
  151. package/dist/sdk/merge/receipts.js +7 -5
  152. package/dist/sdk/merge/three-way.d.ts +2 -2
  153. package/dist/sdk/merge/three-way.js +3 -3
  154. package/dist/sdk/output-projection.d.ts +9 -1
  155. package/dist/sdk/output-projection.js +45 -9
  156. package/dist/sdk/provenance.d.ts +38 -0
  157. package/dist/sdk/provenance.js +64 -2
  158. package/dist/sdk/query/activity.d.ts +49 -2
  159. package/dist/sdk/query/activity.js +183 -37
  160. package/dist/sdk/query/context.js +19 -8
  161. package/dist/sdk/query/get.js +19 -2
  162. package/dist/sdk/read-output-contracts.d.ts +5 -1
  163. package/dist/sdk/read-output-contracts.js +17 -2
  164. package/dist/sdk/read-output-session.d.ts +2 -0
  165. package/dist/sdk/read-output-session.js +12 -5
  166. package/dist/sdk/runtime-extended-actions.js +22 -5
  167. package/dist/sdk/runtime-primitives.d.ts +5 -3
  168. package/dist/sdk/runtime-primitives.js +7 -5
  169. package/dist/sdk/runtime-stats-options.js +3 -2
  170. package/dist/sdk/runtime.d.ts +4 -0
  171. package/dist/sdk/runtime.js +34 -34
  172. package/dist/sdk/stats.d.ts +36 -3
  173. package/dist/sdk/stats.js +55 -15
  174. package/dist/sdk/telemetry.js +7 -4
  175. package/dist/sdk/test/execution.d.ts +4 -0
  176. package/dist/sdk/test/execution.js +86 -21
  177. package/dist/sdk/traceability/runtime-files-lookup.d.ts +8 -0
  178. package/dist/sdk/traceability/runtime-files-lookup.js +33 -0
  179. package/dist/sdk/traceability/source-traceability.d.ts +102 -0
  180. package/dist/sdk/traceability/source-traceability.js +293 -0
  181. package/dist/types.d.ts +10 -2
  182. package/dist/types.js +2 -2
  183. package/docs/ASSURANCE.md +137 -0
  184. package/docs/COMMANDS.md +29 -8
  185. package/docs/CONFIGURATION.md +7 -0
  186. package/docs/DEPENDENCY_KIND_CONTRACT.md +10 -8
  187. package/docs/MERGE_SAFETY.md +3 -1
  188. package/docs/OUTPUT_PROJECTION_CONTRACTS.md +26 -13
  189. package/docs/README.md +3 -0
  190. package/docs/READ_OUTPUT_CONTRACTS.md +17 -2
  191. package/docs/RELEASING.md +9 -5
  192. package/docs/SDK.md +12 -1
  193. package/docs/SDK_AGENT_SESSION_CONTEXT.md +52 -5
  194. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +53 -0
  195. package/docs/SDK_CONTEXT_INTEGRITY.md +28 -1
  196. package/docs/SDK_EVIDENCE_TRACEABILITY.md +39 -1
  197. package/docs/SDK_RUNTIME_BOUNDARIES.md +36 -1
  198. package/docs/TESTING.md +12 -9
  199. package/docs/TRUSTWORTHY_CONTEXT_EVIDENCE.md +106 -0
  200. package/docs/generated/AGENT_COMMAND_SURFACE.md +1 -0
  201. package/docs/performance/sdk-entrypoint-import-costs.md +6 -2
  202. package/marketplace.json +2 -2
  203. package/package.json +7 -7
  204. package/packages/pm-beads/package.json +1 -1
  205. package/packages/pm-calendar/package.json +1 -1
  206. package/packages/pm-command-kit/package.json +1 -1
  207. package/packages/pm-digital-twin/package.json +1 -1
  208. package/packages/pm-governance-audit/package.json +1 -1
  209. package/packages/pm-guide-shell/package.json +1 -1
  210. package/packages/pm-kanban/package.json +1 -1
  211. package/packages/pm-lifecycle-hooks/package.json +1 -1
  212. package/packages/pm-linked-test-adapters/package.json +1 -1
  213. package/packages/pm-search-advanced/package.json +1 -1
  214. package/packages/pm-templates/extensions/templates/index.ts +9 -3
  215. package/packages/pm-templates/package.json +1 -1
  216. package/packages/pm-todos/package.json +1 -1
  217. package/packages/pm-vcs/package.json +1 -1
  218. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  219. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  220. package/sdk/public-surface.json +1030 -67
  221. package/dist/cli-bundle/chunks/chunk-2QYH4QMI.js +0 -30
  222. package/dist/cli-bundle/chunks/chunk-JUDU4RO4.js +0 -194
  223. package/dist/cli-bundle/chunks/chunk-JZSNADRR.js +0 -5
  224. package/dist/cli-bundle/chunks/chunk-MENWVGJW.js +0 -2
  225. package/dist/cli-bundle/chunks/chunk-S4EITGLA.js +0 -3
  226. package/dist/cli-bundle/chunks/chunk-TIGKWVLA.js +0 -13
  227. package/dist/cli-bundle/chunks/chunk-TSLDTJLQ.js +0 -2
  228. package/dist/cli-bundle/chunks/register-list-query-OJ6ZXKV4.js +0 -10
  229. package/dist/cli-bundle/chunks/register-mutation-DKI7QC7O.js +0 -20
  230. package/dist/cli-bundle/chunks/register-operations-P5OGMT6I.js +0 -2
  231. package/dist/cli-bundle/focused-chunks/chunk-36FTZ2NT.js +0 -2
  232. package/dist/cli-bundle/focused-chunks/chunk-3CQECMKR.js +0 -16
  233. package/dist/cli-bundle/focused-chunks/chunk-CHIBMEZR.js +0 -8
  234. package/dist/cli-bundle/focused-chunks/chunk-FF4MNAYB.js +0 -153
  235. package/dist/cli-bundle/focused-chunks/chunk-GHTN2ILR.js +0 -2
  236. package/dist/cli-bundle/focused-chunks/chunk-I4YM2I7S.js +0 -2
  237. package/dist/cli-bundle/focused-chunks/chunk-IMDTEN7K.js +0 -2
  238. package/dist/cli-bundle/focused-chunks/chunk-JMVA4HKR.js +0 -5
  239. package/dist/cli-bundle/focused-chunks/chunk-KR5RVXLQ.js +0 -31
  240. package/dist/cli-bundle/focused-chunks/chunk-KSNJG4KI.js +0 -16
  241. package/dist/cli-bundle/focused-chunks/chunk-MXYSIQ56.js +0 -18
  242. package/dist/cli-bundle/focused-chunks/chunk-RIIN5V4N.js +0 -2
  243. package/dist/cli-bundle/focused-chunks/chunk-RKWSCYYN.js +0 -2
@@ -0,0 +1,293 @@
1
+ /**
2
+ * @module sdk/source-traceability
3
+ *
4
+ * Composes linked-file evidence, bounded Git line attribution, item rationale,
5
+ * and typed relationship paths into an explainable source-to-work projection.
6
+ */
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]="b8b3f729-be32-5a77-b08f-f616ee068e0f")}catch(e){}}();
9
+ import { execFile } from "node:child_process";
10
+ import path from "node:path";
11
+ import { assembleWorkspaceRelationshipGraph } from "../graph/assembly.js";
12
+ function runGit(workspaceRoot, args) {
13
+ return new Promise((resolve, reject) => {
14
+ execFile("git", [...args], {
15
+ cwd: workspaceRoot,
16
+ encoding: "utf8",
17
+ maxBuffer: 4 * 1024 * 1024,
18
+ }, (error, stdout) => {
19
+ if (error)
20
+ reject(error);
21
+ else
22
+ resolve(stdout);
23
+ });
24
+ });
25
+ }
26
+ function validateLineRange(range) {
27
+ if (range &&
28
+ (!Number.isSafeInteger(range.start) ||
29
+ !Number.isSafeInteger(range.end) ||
30
+ range.start < 1 ||
31
+ range.end < range.start)) {
32
+ throw new RangeError("Source line range must be inclusive positive integers with end >= start.");
33
+ }
34
+ }
35
+ /** Parse an inclusive `start:end` source-line selector. */
36
+ export function parseSourceLineRange(value) {
37
+ const match = /^(\d+):(\d+)$/u.exec(value.trim());
38
+ const range = match
39
+ ? { start: Number(match[1]), end: Number(match[2]) }
40
+ : undefined;
41
+ validateLineRange(range);
42
+ if (!range) {
43
+ throw new RangeError("Source line range must use start:end.");
44
+ }
45
+ return range;
46
+ }
47
+ function parseBlameCommits(output) {
48
+ const lines = new Map();
49
+ for (const row of output.split("\n")) {
50
+ const commit = /^([0-9a-f^]{40,64}) \d+ \d+(?: \d+)?$/u.exec(row)?.[1];
51
+ if (commit)
52
+ lines.set(commit, (lines.get(commit) ?? 0) + 1);
53
+ }
54
+ return lines;
55
+ }
56
+ function parseCommitItemReferences(output) {
57
+ const fields = output.split("\0");
58
+ const references = new Map();
59
+ for (let index = 0; index + 1 < fields.length; index += 2) {
60
+ const commit = fields[index]?.trim();
61
+ if (!commit)
62
+ continue;
63
+ const ids = [
64
+ ...new Set(fields[index + 1]?.match(/\bpm-[a-z0-9][a-z0-9-]{2,63}\b/gu) ?? []),
65
+ ].sort((left, right) => left.localeCompare(right));
66
+ references.set(commit, ids);
67
+ }
68
+ return references;
69
+ }
70
+ async function readGitAttribution(params) {
71
+ const relativePath = path.relative(params.workspaceRoot, path.resolve(params.workspaceRoot, params.sourcePath));
72
+ const blame = await runGit(params.workspaceRoot, [
73
+ "blame",
74
+ "--line-porcelain",
75
+ "-L",
76
+ `${params.lineRange.start},${params.lineRange.end}`,
77
+ "--",
78
+ relativePath,
79
+ ]);
80
+ const log = await runGit(params.workspaceRoot, [
81
+ "log",
82
+ "-n",
83
+ "256",
84
+ "--format=%H%x00%B%x00",
85
+ "--",
86
+ relativePath,
87
+ ]);
88
+ return {
89
+ commitLines: parseBlameCommits(blame),
90
+ commitItems: parseCommitItemReferences(log),
91
+ available: true,
92
+ };
93
+ }
94
+ function expandDecisionPath(params) {
95
+ const tail = params.current.nodes.at(-1);
96
+ const nextDepth = params.current.kinds.length + 1;
97
+ let foundDepth;
98
+ for (const edge of params.graph.incidentEdges(tail)) {
99
+ const outgoing = edge.source === tail;
100
+ const next = outgoing ? edge.target : edge.source;
101
+ if ((params.visitedDepth.get(next) ?? Number.POSITIVE_INFINITY) < nextDepth)
102
+ continue;
103
+ params.visitedDepth.set(next, nextDepth);
104
+ const definition = params.graph.registry().require(edge.kind);
105
+ const nextPath = {
106
+ nodes: [...params.current.nodes, next],
107
+ kinds: [
108
+ ...params.current.kinds,
109
+ outgoing ? edge.kind : (definition.inverse ?? edge.kind),
110
+ ],
111
+ };
112
+ if (params.details.get(next)?.type?.toLowerCase() === "decision") {
113
+ foundDepth = nextDepth;
114
+ params.found.push(nextPath);
115
+ }
116
+ else {
117
+ params.queue.push(nextPath);
118
+ }
119
+ }
120
+ return foundDepth;
121
+ }
122
+ function collectDecisionPaths(params) {
123
+ const queue = [
124
+ { nodes: [params.itemId], kinds: [] },
125
+ ];
126
+ const visitedDepth = new Map([[params.itemId, 0]]);
127
+ const found = [];
128
+ let foundDepth;
129
+ while (queue.length > 0) {
130
+ const current = queue.shift();
131
+ const depth = current.kinds.length;
132
+ if (foundDepth !== undefined && depth >= foundDepth)
133
+ continue;
134
+ if (depth >= params.maxDepth)
135
+ continue;
136
+ foundDepth =
137
+ expandDecisionPath({
138
+ current,
139
+ graph: params.graph,
140
+ details: params.details,
141
+ visitedDepth,
142
+ queue,
143
+ found,
144
+ }) ?? foundDepth;
145
+ }
146
+ found.sort((left, right) => left.nodes.join("\0").localeCompare(right.nodes.join("\0")));
147
+ return found;
148
+ }
149
+ function shortestDecisionPath(itemId, corpus, maxDepth) {
150
+ const assembly = assembleWorkspaceRelationshipGraph(corpus);
151
+ if (!assembly.graph.hasNode(itemId)) {
152
+ return {
153
+ status: "not_found",
154
+ nodes: [],
155
+ kinds: [],
156
+ alternative_decision_ids: [],
157
+ };
158
+ }
159
+ const found = collectDecisionPaths({
160
+ itemId,
161
+ maxDepth,
162
+ graph: assembly.graph,
163
+ details: new Map(assembly.details.map((detail) => [detail.id, detail])),
164
+ });
165
+ const selected = found[0];
166
+ if (!selected) {
167
+ return {
168
+ status: "not_found",
169
+ nodes: [],
170
+ kinds: [],
171
+ alternative_decision_ids: [],
172
+ };
173
+ }
174
+ const decisionIds = [...new Set(found.map((entry) => entry.nodes.at(-1)))];
175
+ return {
176
+ status: decisionIds.length === 1 ? "found" : "ambiguous",
177
+ nodes: selected.nodes,
178
+ kinds: selected.kinds,
179
+ alternative_decision_ids: decisionIds.slice(1),
180
+ };
181
+ }
182
+ async function resolveGitAttribution(params) {
183
+ if (!params.lineRange) {
184
+ return {
185
+ commitLines: new Map(),
186
+ commitItems: new Map(),
187
+ available: true,
188
+ };
189
+ }
190
+ try {
191
+ return await readGitAttribution({
192
+ workspaceRoot: params.workspaceRoot,
193
+ sourcePath: params.paths[0],
194
+ lineRange: params.lineRange,
195
+ });
196
+ }
197
+ catch {
198
+ return {
199
+ commitLines: new Map(),
200
+ commitItems: new Map(),
201
+ available: false,
202
+ };
203
+ }
204
+ }
205
+ function sourceAmbiguities(params) {
206
+ const ambiguities = [];
207
+ if (params.hasLineRange && !params.attributionAvailable) {
208
+ ambiguities.push("git_attribution_unavailable");
209
+ }
210
+ if (params.hasLineRange && params.contributionLines === 0) {
211
+ ambiguities.push("line_attribution_unmapped");
212
+ }
213
+ if (params.decisionStatus === "ambiguous") {
214
+ ambiguities.push("multiple_governing_decisions");
215
+ }
216
+ if (params.decisionStatus === "not_found") {
217
+ ambiguities.push("governing_decision_not_found");
218
+ }
219
+ return ambiguities;
220
+ }
221
+ function mappedBlamedCommits(attribution) {
222
+ return [...attribution.commitLines.keys()].filter((commit) => (attribution.commitItems.get(commit)?.length ?? 0) > 0);
223
+ }
224
+ /** Enrich reverse linked-file candidates with bounded source rationale. */
225
+ export async function explainSourceTraceability(params) {
226
+ validateLineRange(params.lineRange);
227
+ const decisionDepth = params.decisionDepth ?? 8;
228
+ if (!Number.isSafeInteger(decisionDepth) ||
229
+ decisionDepth < 1 ||
230
+ decisionDepth > 32) {
231
+ throw new RangeError("Decision path depth must be an integer from 1 to 32.");
232
+ }
233
+ const attribution = await resolveGitAttribution(params);
234
+ const mappedCommits = mappedBlamedCommits(attribution);
235
+ const explanations = new Map();
236
+ for (const candidate of params.candidates) {
237
+ const attributedCommits = [...attribution.commitLines.entries()].filter(([commit]) => attribution.commitItems.get(commit)?.includes(candidate.item.id));
238
+ const contributionLines = attributedCommits.reduce((total, [, lines]) => total + lines, 0);
239
+ const decisionPath = shortestDecisionPath(candidate.item.id, params.corpus, decisionDepth);
240
+ const ambiguities = sourceAmbiguities({
241
+ hasLineRange: params.lineRange !== undefined,
242
+ attributionAvailable: attribution.available,
243
+ contributionLines,
244
+ decisionStatus: decisionPath.status,
245
+ });
246
+ explanations.set(candidate.item.id, {
247
+ score: contributionLines * 100 +
248
+ candidate.files.length * 10 +
249
+ Math.max(0, 9 -
250
+ Math.floor((Date.now() - Date.parse(candidate.item.updated_at)) /
251
+ (30 * 24 * 60 * 60 * 1000))),
252
+ contribution_lines: contributionLines,
253
+ evidence: [
254
+ ...candidate.files.map((file) => ({
255
+ kind: "linked_file",
256
+ reference: file.path,
257
+ })),
258
+ ...attributedCommits.map(([commit, lines]) => ({
259
+ kind: "git_commit",
260
+ reference: commit.slice(0, 12),
261
+ contribution_lines: lines,
262
+ })),
263
+ ],
264
+ rationale: {
265
+ value: candidate.item.value?.trim() || null,
266
+ why_now: candidate.item.why_now?.trim() || null,
267
+ outcome: candidate.item.outcome?.trim() || null,
268
+ objective: candidate.item.objective?.trim() || null,
269
+ },
270
+ decision_path: decisionPath,
271
+ ambiguities,
272
+ });
273
+ }
274
+ return {
275
+ explanations,
276
+ receipt: {
277
+ line_range: params.lineRange ?? null,
278
+ blamed_commit_count: attribution.commitLines.size,
279
+ mapped_commit_count: mappedCommits.length,
280
+ unmapped_commit_count: attribution.commitLines.size - mappedCommits.length,
281
+ decision_depth: decisionDepth,
282
+ },
283
+ };
284
+ }
285
+ /** Internal parser and traversal seams for deterministic boundary tests. */
286
+ export const _testOnlySourceTraceability = {
287
+ mappedBlamedCommits,
288
+ parseBlameCommits,
289
+ parseCommitItemReferences,
290
+ shortestDecisionPath,
291
+ };
292
+ //# sourceMappingURL=source-traceability.js.map
293
+ //# debugId=b8b3f729-be32-5a77-b08f-f616ee068e0f
package/dist/types.d.ts CHANGED
@@ -743,7 +743,7 @@ export interface HistoryEntry {
743
743
  /** Observed agent model identifier, retained in local history. */
744
744
  agent_model?: string;
745
745
  /** Bounded signal class that supplied `agent_model`. */
746
- agent_model_source?: "override" | "environment" | "mcp_client" | "argv" | "host" | "session" | "probe";
746
+ agent_model_source?: "override" | "environment" | "mcp_client" | "argv" | "host" | "session" | "probe" | "inferred";
747
747
  /** Privacy-safe fingerprint distinguishing concurrent harness invocations. */
748
748
  agent_instance?: string;
749
749
  /** Extensible local-only descriptive agent provenance; null means declared but unavailable. */
@@ -751,7 +751,13 @@ export interface HistoryEntry {
751
751
  /** Bounded descriptive value observed for this dimension. */
752
752
  value: string;
753
753
  /** Bounded signal class that supplied the value. */
754
- source: "override" | "environment" | "mcp_client" | "argv" | "host" | "session" | "probe";
754
+ source: "override" | "environment" | "mcp_client" | "argv" | "host" | "session" | "probe" | "inferred";
755
+ /** Confidence attached to a semantic inference. */
756
+ confidence?: "high" | "medium" | "low";
757
+ /** Versioned semantic inference rule. */
758
+ rule_version?: "v2";
759
+ /** Bounded item and lineage references supporting the inference. */
760
+ evidence?: readonly string[];
755
761
  } | null>>;
756
762
  /** Stable declared episode identity; absent on legacy or undeclared events. */
757
763
  agent_episode?: {
@@ -770,6 +776,8 @@ export interface HistoryEntry {
770
776
  before_hash: string;
771
777
  /** Value that configures or reports after hash for this contract. */
772
778
  after_hash: string;
779
+ /** Version of the item canonicalization used by before_hash and after_hash. Absent on legacy streams. */
780
+ item_hash_version?: number;
773
781
  /** Human-readable explanation suitable for logs and agent-facing output. */
774
782
  message?: string;
775
783
  /** Structured audit metadata that does not alter replayed item state. */
package/dist/types.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  /** Supported values accepted by the builtin item type contract. */
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]="2a2072c1-f4a7-579e-ac11-4c75758d76d3")}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]="8b39bc01-0325-5dc6-8492-585c98608076")}catch(e){}}();
9
9
  export const BUILTIN_ITEM_TYPE_VALUES = [
10
10
  "Epic",
11
11
  "Feature",
@@ -241,4 +241,4 @@ export const CONTEXT_SECTION_VALUES = [
241
241
  "tests",
242
242
  ];
243
243
  //# sourceMappingURL=types.js.map
244
- //# debugId=2a2072c1-f4a7-579e-ac11-4c75758d76d3
244
+ //# debugId=8b39bc01-0325-5dc6-8492-585c98608076
@@ -0,0 +1,137 @@
1
+ # Project Assurance Primitives
2
+
3
+ Tracker: [pm-2lex4r](../.agents/pm/features/pm-2lex4r.toon), [pm-lyfu7b](../.agents/pm/features/pm-lyfu7b.toon), [pm-wn6wot](../.agents/pm/features/pm-wn6wot.toon), [pm-91xeam](../.agents/pm/features/pm-91xeam.toon), [pm-py7qv2](../.agents/pm/issues/pm-py7qv2.toon), [pm-33mjrw](../.agents/pm/issues/pm-33mjrw.toon), [pm-q6n8sj](../.agents/pm/issues/pm-q6n8sj.toon)
4
+
5
+ ## Agent Quick Context
6
+
7
+ Assurance turns project policy into three reusable SDK-owned declarations:
8
+
9
+ 1. A **measurement** selects authoritative project data and produces a number or labelled set plus population, contributor, and compute-cost receipts.
10
+ 2. An **assertion** applies exactly one explicit bound, scope, lifetime, and enforcement level to a measurement. Required negative controls prove that the bound can both pass and fail.
11
+ 3. A **gate** evaluates named assertions at declared lifecycle triggers and returns one structured verdict shared by CLI, SDK, MCP, and CI callers.
12
+
13
+ Declarations live in `.agents/pm/assurance.json`. Every registry mutation and non-dry gate verdict is appended through the verified workspace history stream; never edit either file directly.
14
+
15
+ ## Why Assurance Exists
16
+
17
+ Project management is context management. A useful quality gate therefore needs more than a shell exit code: it must preserve what was measured, which population was judged, why a bound exists, who owns it, what changed the result, how expensive the evaluation was, and which immutable tree received the verdict.
18
+
19
+ The assurance SDK keeps those semantics independent from presentation. Commander and MCP only normalize inputs. Package authors and CI hosts can use the same public functions without reconstructing policy in scripts.
20
+
21
+ ## Declaration Vocabulary
22
+
23
+ Measurements support these built-in sources:
24
+
25
+ | Source | Purpose |
26
+ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | `items` | Count authoritative full item records matching status, type, tags, an exact metadata field, or a field `state` of `present`/`missing`. |
28
+ | `dependency_kind` | Count typed relationship edges such as `blocked_by` or `verifies`; accepted aliases and canonical spellings measure the same population. |
29
+ | `graph` | Select a numeric or labelled-set field from a public graph SDK result. |
30
+ | `validate` | Select a validator check status or numeric detail. |
31
+ | `health` | Select a health check status, numeric detail, or labelled set. |
32
+ | `history` | Count immutable events by operation, author, harness, or model. |
33
+ | `links` | Count items with present or missing file, test, or documentation evidence. |
34
+ | `derived` | Combine numeric measurements with deterministic arithmetic and cycle detection. |
35
+ | `provider` | Delegate a measurement to an explicitly supplied host/package resolver. |
36
+
37
+ Each measurement may declare `max_cost`. Evaluation fails closed when the total abstract compute units exceed that ceiling. Every result reports units, scanned items, scanned history rows, provider calls, duration, population size, and contributors.
38
+
39
+ An `items` source with `field` must declare exactly one predicate: `equals` (including an explicit `null`) or `state`. `state: missing` treats an absent property, `null`, an empty string, or an empty array as missing; `state: present` selects the complement. Workspace evaluation loads full item metadata, so `files`, `tests`, and `docs` selectors measure stored evidence rather than a light projection that omitted those collections.
40
+
41
+ Assertions require exactly one polarity:
42
+
43
+ - `ceiling`, `floor`, `equals`, or `zero`
44
+ - `monotone_nondecreasing` or `monotone_nonincreasing`
45
+ - `subset_of` for labelled sets
46
+
47
+ Scopes are `all`, `active`, or `filter`. A filter names another measurement whose contributors define the item population. `lifetime: hold` keeps the guarantee after its owner item becomes terminal. `lifetime: retire` retires it only after owner termination and requires `retire_reason`.
48
+
49
+ Enforcement is `block`, `warn`, or `observe`. Weakening a bound, scope, lifetime, owner, source measurement, or enforcement requires `authorization_decision` naming a terminal Decision item verified by the host. The transport verifies only that explicitly named item; it never treats unrelated workspace Decisions as authorization. Tightening does not require authorization.
50
+
51
+ ## CLI Workflow
52
+
53
+ Create a measurement:
54
+
55
+ ```bash
56
+ pm assurance put measurement active-issues \
57
+ --definition '{"id":"active-issues","source":{"kind":"items","statuses":["open","in_progress"],"types":["Issue"]},"max_cost":5000}' \
58
+ --message "Track the active issue population"
59
+ ```
60
+
61
+ Create an assertion with executable negative controls:
62
+
63
+ ```bash
64
+ pm assurance put assertion active-issues-ceiling \
65
+ --definition '{"id":"active-issues-ceiling","measurement_id":"active-issues","owner_item_id":"pm-example","scope":{"kind":"active"},"ceiling":25,"lifetime":"hold","enforcement":"block","negative_control":{"cases":[{"observed":25,"expected":"pass"},{"observed":26,"expected":"fail"}]}}'
66
+ ```
67
+
68
+ Create and evaluate a gate:
69
+
70
+ ```bash
71
+ pm assurance put gate release-readiness \
72
+ --definition '{"id":"release-readiness","assertion_ids":["active-issues-ceiling"],"triggers":["ci","pre-release"]}'
73
+
74
+ pm assurance run release-readiness --trigger ci --dry-run --json
75
+ pm assurance run release-readiness --trigger pre-release --tree "$(git rev-parse HEAD)" --json
76
+ pm assurance verdicts release-readiness --limit 20 --json
77
+ ```
78
+
79
+ Registry reads and removals use the same nouns:
80
+
81
+ ```bash
82
+ pm assurance list measurement --json
83
+ pm assurance show assertion active-issues-ceiling --json
84
+ pm assurance remove gate release-readiness
85
+ ```
86
+
87
+ Referenced measurements and assertions cannot be removed. Remove the consuming gate or assertion first.
88
+
89
+ ## SDK and MCP
90
+
91
+ The reusable client exposes the same action grammar:
92
+
93
+ ```ts
94
+ import { PmClient } from "@unbrained/pm-cli/sdk";
95
+
96
+ const pm = new PmClient({ pmRoot: ".agents/pm" });
97
+
98
+ await pm.assurance({
99
+ action: "run",
100
+ id: "release-readiness",
101
+ trigger: "ci",
102
+ dry_run: true,
103
+ });
104
+ ```
105
+
106
+ For direct host composition, use `evaluateMeasurement`, `evaluateAssuranceGate`, `createAssuranceWorkspaceContext`, and the audited declaration/verdict helpers exported from `@unbrained/pm-cli/sdk`. A host contributes provider measurements by passing stable resolver ids to `createAssuranceWorkspaceContext`; an absent resolver fails loudly. External adapters must enforce an appropriate timeout. The core evaluator bounds concurrent assertions and expression operands, and workspace history loading uses bounded concurrency; item-only callers can explicitly skip history and Git identity resolution.
107
+
108
+ Generic SDK and MCP dispatch use `action: "assurance"` with `subcommand` set to `list`, `show`, `put`, `remove`, `run`, or `verdicts`. Discover the current machine contract instead of copying parameter lists:
109
+
110
+ ```bash
111
+ pm contracts --action assurance --schema-only --json
112
+ pm contracts --command assurance --flags-only --json
113
+ ```
114
+
115
+ ## Verdict Contract
116
+
117
+ A gate emits one object containing:
118
+
119
+ - gate id, evaluated tree, trigger, timestamp, and dry-run status;
120
+ - overall `pass`, `warn`, or `block` plus stable exit code;
121
+ - every assertion's measurement, scope, population, observed value, structured bound, signed distance, enforcement, negative-control proof, cost, and contributors;
122
+ - every assertion's `measurement_definition_fingerprint`, a SHA-256 identity for the exact declaration that produced the observation;
123
+ - an aggregate compute receipt.
124
+
125
+ Dry runs never write history. Non-dry verdicts are immutable workspace audit events and remain queryable after ordinary registry changes. Verdict reads return newest entries first and default to a bounded result; use `--limit` to select up to 1,000 matching records. A blocking verdict exits non-zero; warnings and observations remain successful while preserving their failed assertion rows.
126
+
127
+ Verdicts persisted before definition fingerprints were introduced remain readable and are identifiable by the absence of `measurement_definition_fingerprint`. Registry mutations and verdict writes use the same explicit-author, configured-author, and detected-harness precedence as other SDK mutations; they do not manufacture an `unknown` author when a harness identity is available.
128
+
129
+ ## Safety and Evolution
130
+
131
+ - Use stable lowercase ids; prose belongs in descriptions and mutation messages.
132
+ - Prefer saved measurements and derived arithmetic over duplicating queries in scripts.
133
+ - Give expensive graph, health, validate, or provider measurements explicit cost ceilings.
134
+ - Keep owner items and authorization Decisions linked into the project graph.
135
+ - Treat negative controls as part of the policy, not test decoration.
136
+ - Use `hold` unless a time-bounded guarantee has an explicit retirement rationale.
137
+ - Use `--dry-run` while authoring or tightening a gate, then persist a verdict against an immutable tree.
package/docs/COMMANDS.md CHANGED
@@ -19,7 +19,9 @@ pm contracts --command <command> --flags-only --json
19
19
  - Use `pm contracts --summary --json` for the cheapest command map, then narrow with command-scoped contracts.
20
20
  - Every mutation writes history.
21
21
 
22
- Tracked documentation work: [pm-u9d0](../.agents/pm/epics/pm-u9d0.toon).
22
+ Tracked documentation work: [pm-u9d0](../.agents/pm/epics/pm-u9d0.toon),
23
+ [pm-7nqo6b](../.agents/pm/issues/pm-7nqo6b.toon), and
24
+ [pm-j1r8gl](../.agents/pm/issues/pm-j1r8gl.toon).
23
25
 
24
26
  ## Command Families
25
27
 
@@ -941,17 +943,25 @@ pm history-repair <id> --dry-run
941
943
  pm history-repair <id> --message "re-anchor legacy drift"
942
944
  pm history-repair --all --dry-run
943
945
  pm history-repair --all --message "bulk re-anchor drifted streams"
944
- pm activity --id <id> --limit 50
946
+ pm activity # item digest for the last 24 hours
947
+ pm activity --raw --id <id> --limit 50
945
948
  pm activity --full --id <id> --limit 50
946
949
  pm activity --full --unbounded
947
950
  pm restore <id> <timestamp-or-version>
948
951
  ```
949
952
 
950
953
  History is append-only. Restore appends a new restore event instead of rewriting old history.
951
- Bare `pm activity` is bounded to 20 compact rows; direct SDK calls default to
952
- five full rows. Every result reports total and omitted counts plus the applied
953
- bound. Use `--limit` for a deliberate cap, or `--unbounded` for an explicitly
954
- unlimited read; the two flags are mutually exclusive.
954
+ Bare `pm activity` is an item-centric digest for the last 24 hours, bounded to
955
+ 15 most-recently-touched items. Each row joins current `id`, `type`, `status`,
956
+ and title with the matching event count, first/last timestamps, and a bounded
957
+ operation histogram. `activity_summary` states the effective window plus event,
958
+ item, author, and operation counts, so an empty digest is distinguishable from
959
+ a bounded one. Use `--raw` for the legacy compact per-event stream, `--compact`
960
+ as its compatibility spelling, `--full` for events with patch payloads, or
961
+ `--provenance` for patch-free provenance rows. Every mode reports total and
962
+ omitted row counts plus the applied bound. Use `--limit` for a deliberate cap,
963
+ or `--unbounded` for an explicitly unlimited read; the two flags are mutually
964
+ exclusive.
955
965
  Workspace-scoped mutations to settings, schema, profiles, init state, and
956
966
  extension/package activation are recorded in
957
967
  `.agents/pm/history/_workspace.jsonl` using the same patch/hash format.
@@ -962,10 +972,21 @@ workspace auditing and is created on the first audited singleton mutation.
962
972
 
963
973
  `--diff` replays the history chain and emits, per entry, a `changes` array of `{ field, before, after }` field-level value transitions (alongside the `changed_fields` name list) — so you can see exactly what each field changed from and to without comparing snapshots. It is independent of the compact/full projection. `--field <name>` narrows the diff to a single field's transitions (implying `--diff`), answering "when did `<field>` change?" — e.g. `pm history <id> --diff --field status`.
964
974
 
965
- `pm stats` reports item and history totals plus per-type/per-status counts. Add `--storage` for aggregate history-stream metrics — `total_streams`, `total_lines`, `total_bytes`, the top streams by size (`largest_by_bytes`) and by depth (`deepest_by_lines`), and the global `oldest_entry`/`newest_entry` — to decide when to compact or redact streams and to plan storage:
975
+ `pm stats` reports item and history totals plus a screen-sized lifecycle table.
976
+ Every non-empty item type is one row with `total`, `open`, `in_progress`,
977
+ `blocked`, `draft`, `closed`, `canceled`, and `other` counts. Empty registered
978
+ types and statuses are suppressed and their combined count is retained in the
979
+ `omitted_zero_buckets` scalar. The default TOON projection is gated at no more
980
+ than 22 lines on the representative release fixture. Add `--include-empty` to
981
+ restore every zero-filled type and status for schema-governance dashboards.
982
+ Add `--storage` for aggregate history-stream metrics — `total_streams`,
983
+ `total_lines`, `total_bytes`, the top streams by size (`largest_by_bytes`) and
984
+ by depth (`deepest_by_lines`), and the global `oldest_entry`/`newest_entry` — to
985
+ decide when to compact or redact streams and to plan storage:
966
986
 
967
987
  ```bash
968
988
  pm stats
989
+ pm stats --include-empty
969
990
  pm stats --storage --json
970
991
  pm stats --metadata-coverage --json
971
992
  pm stats --field-utilization --json
@@ -973,7 +994,7 @@ pm stats --by-assignee --by-priority
973
994
  pm stats --by-tag --tag-prefix domain: --json
974
995
  ```
975
996
 
976
- For governance dashboards, `--metadata-coverage` adds a `metadata_coverage` block reporting per-field `present`/`applicable`/`percent` for `acceptance_criteria`, `estimated_minutes`, `resolution`, `tags`, and `parent` — overall and `by_type` (resolution coverage is scoped to terminal items, its only applicable population). `--field-utilization` adds a `field_utilization` block reporting `present`/`total`/`percent` for each content field (`notes`, `learnings`, `files`, `docs`, `tests`, `comments`, `deps`, `body`, `linked_command`) across all items, so under-documented content dimensions are visible at a glance and pair naturally with the `--has-*`/`--no-*` list filters for drill-down. `--by-assignee`, `--by-tag`, and `--by-priority` add a `breakdowns` block with lifecycle-bucketed rows (`open`/`in_progress`/`blocked`/`draft`/`closed`/`canceled`/`other` + `total`) per group; blank keys render an explicit `(unassigned)`/`(untagged)` label. `--by-tag` accepts `--tag-prefix` to restrict counting to a tag namespace (for example `domain:`). All of these sections are gated behind their flags so the default `pm stats` stays token-light; the per-status/per-type distributions (already in `by_status`/`by_type`) zero-fill every configured state so underutilized lifecycle states and item types are visible at a glance.
997
+ For governance dashboards, `--metadata-coverage` adds a `metadata_coverage` block reporting per-field `present`/`applicable`/`percent` for `acceptance_criteria`, `estimated_minutes`, `resolution`, `tags`, and `parent` — overall and `by_type` (resolution coverage is scoped to terminal items, its only applicable population). `--field-utilization` adds a `field_utilization` block reporting `present`/`total`/`percent` for each content field (`notes`, `learnings`, `files`, `docs`, `tests`, `comments`, `deps`, `body`, `linked_command`) across all items, so under-documented content dimensions are visible at a glance and pair naturally with the `--has-*`/`--no-*` list filters for drill-down. `--by-assignee`, `--by-tag`, and `--by-priority` add a `breakdowns` block with lifecycle-bucketed rows (`open`/`in_progress`/`blocked`/`draft`/`closed`/`canceled`/`other` + `total`) per group; blank keys render an explicit `(unassigned)`/`(untagged)` label. `--by-tag` accepts `--tag-prefix` to restrict counting to a tag namespace (for example `domain:`). All of these sections are gated behind their flags so the default `pm stats` remains readable at first glance.
977
998
  `history-redact` rewrites matching history payloads deterministically, recomputes hash chains, and appends an auditable `history_redact` marker entry when changes are applied. Its result reports only `literal_count`, `regex_count`, `total_count`, and whether the default replacement was selected; literal values, regex source text, and replacement text are never echoed in CLI, SDK, MCP, recovery, profile, or telemetry output. Treat regex text as sensitive input because it can contain the exact material being removed.
978
999
  `history-compact` rewrites long streams into a synthetic checkpoint baseline plus a retained tail (`--before` accepts a 1-based version or ISO timestamp), re-anchors hashes, verifies integrity, and appends an auditable `history_compact` marker when applied.
979
1000
  `history-compact` bulk mode (mutually exclusive with a positional `<id>`) compacts many streams in one audited pass. Select with `--ids <a,b,c>` (an explicit list — used on its own, not combined with the scan selectors below), or a scan: `--all-over <N>` (every stream with more than N entries) and/or a lifecycle filter `--closed` (terminal items only) or `--all-streams` (every stream). `--closed` and `--all-streams` are mutually exclusive. `--min-entries <N>` (default 3) skips already-compact streams; when `history.compact_policy` is enabled and `--all-over` is omitted, the policy's `max_entries` becomes the default threshold. `--before` is single-id only and is rejected in bulk mode. Each selected stream runs the same single-item compaction; one failing stream never aborts the rest — the result reports `totals` (`streams_considered`/`selected`/`items_compacted`/`items_skipped`/`items_errored`) plus one row per stream (`compacted`/`skipped` with a `skip_reason`/`errored`), and the command exits non-zero only if any stream errored.
@@ -183,6 +183,13 @@ Crash and error diagnostics are reported to Sentry only when telemetry is enable
183
183
 
184
184
  > Sentry is hard-disabled under Vitest (`VITEST` / `VITEST_WORKER_ID`), so these knobs are no-ops inside the test suite.
185
185
 
186
+ Linked commands launched by `pm test --run` default
187
+ `SENTRY_ENVIRONMENT=test`, even when the parent process is classified as
188
+ production. This keeps sandbox failures out of production diagnostics. An
189
+ explicit run-level or per-test `env_set=SENTRY_ENVIRONMENT=<name>` directive
190
+ still overrides the default when a test intentionally targets another
191
+ environment.
192
+
186
193
  ## Item Storage Format
187
194
 
188
195
  TOON is the default:
@@ -1,15 +1,15 @@
1
1
  # Dependency-kind contract
2
2
 
3
- Tracker: [pm-4020c5](../.agents/pm/issues/pm-4020c5.toon), [pm-jkbqt8](../.agents/pm/issues/pm-jkbqt8.toon)
3
+ Tracker: [pm-4020c5](../.agents/pm/issues/pm-4020c5.toon), [pm-jkbqt8](../.agents/pm/issues/pm-jkbqt8.toon), [pm-q6n8sj](../.agents/pm/issues/pm-q6n8sj.toon)
4
4
 
5
5
  Dependency rows have one canonical stored spelling per relationship meaning. Command inputs remain compatibility-friendly: hyphens normalize to underscores and the aliases below are accepted, but `pm create` and `pm update` persist the canonical kind. Existing historical rows are never rewritten implicitly.
6
6
 
7
- | Canonical kind | Accepted legacy aliases |
8
- | --- | --- |
9
- | `blocked_by` | `depends_on`, `depends-on` |
10
- | `related` | `related_to`, `related-to` |
11
- | `parent` | `child_of`, `child-of`, `epic` |
12
- | `child` | `parent_child`, `parent-child`, `task` |
7
+ | Canonical kind | Accepted legacy aliases |
8
+ | -------------- | -------------------------------------- |
9
+ | `blocked_by` | `depends_on`, `depends-on` |
10
+ | `related` | `related_to`, `related-to` |
11
+ | `parent` | `child_of`, `child-of`, `epic` |
12
+ | `child` | `parent_child`, `parent-child`, `task` |
13
13
 
14
14
  `epic` and `task` are compatibility aliases, not item types embedded in the relationship ontology. New integrations should use `parent` or `child` and express the work classification through the item `type` field.
15
15
 
@@ -26,4 +26,6 @@ Readiness, `pm next`, context blocker summaries, `pm list-blocked`, downstream `
26
26
 
27
27
  ## Legacy observability
28
28
 
29
- `pm deps` returns `legacy_alias_counts` for the workspace. `pm graph audit` returns the same field beside the canonical `profile.edges_by_kind` counts. Empty objects mean no stored alias debt. These diagnostics are read-only; terminal history remains untouched until an explicitly governed migration is requested.
29
+ `pm deps` returns `legacy_alias_counts` for the workspace. `pm graph audit` returns the same field beside canonical `profile.edges_by_kind` counts, `profile.edge_share_by_kind` composition ratios, and the `semantic_edges`/`semantic_edge_share` context-preservation census. The semantic census counts `discovered_from`, `incident_from`, `supersedes`, and `verifies` over all deduplicated directed edges. Empty objects and zero shares are explicit, not omitted. These diagnostics are read-only; terminal history remains untouched until an explicitly governed migration is requested.
30
+
31
+ Assurance `dependency_kind` measurements canonicalize both the declaration and stored row before comparing. A declaration using `related` and one using the accepted `related_to` alias therefore measure the same edge population; alias debt remains separately observable through `legacy_alias_counts`.
@@ -61,7 +61,7 @@ pm merge install --dry-run --json
61
61
  | tracker `**/*.jsonl` except the later `history/*.jsonl` override | `pm-relationship` | Covers default and package-owned custom relationship event paths, unions divergent suffixes by `eventId` (timestamp-ordered, ours-first on ties), and renumbers `sequence` consecutively so the strict-sequence store loader accepts the merged stream. |
62
62
  | root `settings.json` and nested `**/*.json` | `pm-json` | Recursively merges objects per key. Arrays compose when both branches preserve the base and add distinct entries, so independent extension installs and evaluation additions merge without weakening edit/removal conflict detection. |
63
63
 
64
- When both sides change the same item scalar differently, the driver writes the same stable value regardless of which branch Git labels ours or theirs, but exits nonzero. JSON leaf conflicts retain the explicit preferred-side policy. Git keeps either path conflicted so a human or coordinating agent must review the discarded value and explicitly `git add` the resolution.
64
+ When both sides change the same item scalar differently, the driver writes the same stable value regardless of which branch Git labels ours or theirs, but exits nonzero. Item results and receipts expose the caller's `requested_preference`; the per-decision `retained` and `discarded` values or hashes are authoritative because stable value order can retain either side. Readers normalize the legacy receipt key `preferred`, while new receipts no longer emit it. JSON leaf conflicts retain the explicit preferred-side policy. Git keeps either path conflicted so a human or coordinating agent must review the discarded value and explicitly `git add` the resolution. This correction is tracked by [pm-qckpnq](../.agents/pm/issues/pm-qckpnq.toon).
65
65
 
66
66
  The driver result's `guidance` always points unresolved conflicts to `pm merge report`. When a clone-local receipt exists, guidance includes its privacy-safe receipt and item ids for exact correlation; discarded values remain confined to the local receipt and never appear in generic logs or tracker history. Tracked by [pm-fbrz7p](../.agents/pm/issues/pm-fbrz7p.toon).
67
67
 
@@ -133,6 +133,8 @@ pm merge reconcile --dry-run --json
133
133
 
134
134
  `history-repair` records the reconciliation patch and classifies its changed fields against the final item. Append-only collection unions and deterministic reordering are reported as preserved context without a data-loss warning. Fields whose replayed values are actually removed or replaced remain loud with discarded event authors/operations and recovery guidance. Re-apply any intended losing mutation as a normal `pm update` so it remains explicit and auditable.
135
135
 
136
+ History events now declare an item-hash epoch. The current epoch preserves linked-test insertion order; unversioned streams are verified against both the legacy sorted-test canonicalization and the order-preserving canonicalization. An unknown explicit epoch is reported as `unsupported_item_hash_version` and repair refuses to guess. This keeps version incompatibility distinct from item corruption and is tracked by [pm-2htk4p](../.agents/pm/issues/pm-2htk4p.toon).
137
+
136
138
  ## Delete versus modify policy
137
139
 
138
140
  A delete on one branch and an edit on another is not safely resolvable by a generic file driver. The merged workspace must not silently resurrect the item. `storage_integrity` reports the live document plus delete-terminated history as a hard finding. The coordinator chooses one policy explicitly: