@unbrained/pm-cli 2026.9.5 → 2026.9.6

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 (185) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +32 -2
  3. package/dist/cli/main.js +10 -8
  4. package/dist/cli/register-operations.js +13 -8
  5. package/dist/cli-bundle/bundle-manifest.json +150 -158
  6. package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +9 -0
  7. package/dist/cli-bundle/chunks/{chunk-WWAEGW4V.js → chunk-BAX2BYUW.js} +66 -66
  8. package/dist/cli-bundle/chunks/{chunk-IBYREGSP.js → chunk-CKOL5U7J.js} +51 -51
  9. package/dist/cli-bundle/chunks/{chunk-ZRAQS3UA.js → chunk-JT6XF6GE.js} +2 -2
  10. package/dist/cli-bundle/chunks/{chunk-HUAQITVW.js → chunk-JX4DH4WC.js} +2 -2
  11. package/dist/cli-bundle/chunks/{chunk-QPZHQY2O.js → chunk-NPN5I67J.js} +2 -2
  12. package/dist/cli-bundle/chunks/{chunk-66VPKFP2.js → chunk-OF76PSDQ.js} +2 -2
  13. package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +232 -0
  14. package/dist/cli-bundle/chunks/{chunk-3HBDTJRK.js → chunk-RWWBOWJ5.js} +2 -2
  15. package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +40 -0
  16. package/dist/cli-bundle/chunks/{chunk-SF6SM6U5.js → chunk-WI2AYPA7.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-list-query-AYKGODWT.js → register-list-query-5JL52D33.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-mutation-YZYG53SQ.js → register-mutation-ZPG7KMVP.js} +2 -2
  19. package/dist/cli-bundle/chunks/register-operations-I5W3KMGB.js +2 -0
  20. package/dist/cli-bundle/chunks/{register-setup-UJCRVGBG.js → register-setup-H4SVAVX7.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-QL66QNWG.js → chunk-3BZEOXLT.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +2 -0
  23. package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +6 -0
  24. package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +20 -0
  25. package/dist/cli-bundle/focused-chunks/{chunk-WOCIPMW7.js → chunk-EZR3GHFE.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +56 -0
  27. package/dist/cli-bundle/focused-chunks/{chunk-3ASU6WGI.js → chunk-LIJIA4TY.js} +2 -2
  28. package/dist/cli-bundle/focused-chunks/{chunk-H5FOGAVD.js → chunk-M6PBRFSH.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/{chunk-B2TTQGFG.js → chunk-NTPY5GSO.js} +2 -2
  30. package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +2 -0
  31. package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +177 -0
  32. package/dist/cli-bundle/focused-chunks/{chunk-YEF5PQQ2.js → chunk-PEFABF7E.js} +2 -2
  33. package/dist/cli-bundle/focused-chunks/{chunk-KWWCBWFO.js → chunk-QTVCLMSZ.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/{chunk-4OSHLNUE.js → chunk-QTZRBUYA.js} +2 -2
  35. package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +12 -0
  36. package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +10 -0
  37. package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +2 -0
  38. package/dist/cli-bundle/main.js +20 -20
  39. package/dist/cli-bundle/sdk-authoring.js +1 -1
  40. package/dist/cli-bundle/sdk-contracts.js +2 -2
  41. package/dist/cli-bundle/sdk-core.js +37 -37
  42. package/dist/cli-bundle/sdk-governance.js +1 -1
  43. package/dist/cli-bundle/sdk-graph.js +1 -1
  44. package/dist/cli-bundle/sdk-merge.js +42 -42
  45. package/dist/cli-bundle/sdk-query.js +1 -1
  46. package/dist/cli-bundle/sdk-runtime.js +1 -1
  47. package/dist/cli-bundle/sdk-testing.js +1 -1
  48. package/dist/cli-bundle/sdk.js +9 -9
  49. package/dist/core/extensions/module-graph-snapshot.js +5 -4
  50. package/dist/core/fs/host-environment-errors.d.ts +27 -0
  51. package/dist/core/fs/host-environment-errors.js +142 -0
  52. package/dist/core/output/mutation-projection.js +12 -5
  53. package/dist/core/store/item-metadata-query-index.js +13 -10
  54. package/dist/core/store/item-metadata-read-work.d.ts +19 -0
  55. package/dist/core/store/item-metadata-read-work.js +44 -0
  56. package/dist/core/store/item-store.js +6 -2
  57. package/dist/core/validate/fix-planning.d.ts +1 -1
  58. package/dist/core/validate/fix-planning.js +2 -2
  59. package/dist/core/validate/missing-link-owners.d.ts +1 -1
  60. package/dist/core/validate/missing-link-owners.js +5 -3
  61. package/dist/core/validate/stale-file-classification.d.ts +6 -4
  62. package/dist/core/validate/stale-file-classification.js +20 -5
  63. package/dist/mcp/server.js +12 -5
  64. package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
  65. package/dist/sdk/cli-contracts/flag-contracts.js +7 -5
  66. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +4 -3
  67. package/dist/sdk/cli-contracts/grammar-contracts.js +11 -2
  68. package/dist/sdk/cli-contracts/runtime-contracts.js +5 -2
  69. package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +254 -5
  70. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +52 -2
  71. package/dist/sdk/cli-contracts/tool-parameter-tables.js +18 -6
  72. package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
  73. package/dist/sdk/cli-contracts/tool-schema.js +17 -5
  74. package/dist/sdk/context/usage-ledger.d.ts +41 -0
  75. package/dist/sdk/context/usage-ledger.js +186 -0
  76. package/dist/sdk/context-usage.d.ts +18 -9
  77. package/dist/sdk/context-usage.js +37 -112
  78. package/dist/sdk/dependency-provenance.d.ts +7 -0
  79. package/dist/sdk/dependency-provenance.js +14 -4
  80. package/dist/sdk/duplicates.d.ts +2 -0
  81. package/dist/sdk/duplicates.js +3 -2
  82. package/dist/sdk/environment/host-environment-errors.d.ts +5 -27
  83. package/dist/sdk/environment/host-environment-errors.js +4 -137
  84. package/dist/sdk/extension/install-runtime.js +9 -6
  85. package/dist/sdk/extension/install-snapshot.js +7 -6
  86. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +8 -8
  87. package/dist/sdk/governance/assurance-action.d.ts +3 -2
  88. package/dist/sdk/governance/assurance-action.js +23 -11
  89. package/dist/sdk/governance/assurance-runtime.d.ts +2 -0
  90. package/dist/sdk/governance/assurance-runtime.js +6 -3
  91. package/dist/sdk/governance/defect-recurrence-coverage.d.ts +93 -0
  92. package/dist/sdk/governance/defect-recurrence-coverage.js +189 -0
  93. package/dist/sdk/governance/defect-recurrence-graph.d.ts +33 -0
  94. package/dist/sdk/governance/defect-recurrence-graph.js +121 -0
  95. package/dist/sdk/governance/defect-recurrence.d.ts +8 -0
  96. package/dist/sdk/governance/defect-recurrence.js +118 -17
  97. package/dist/sdk/governance/health.d.ts +17 -2
  98. package/dist/sdk/governance/health.js +31 -10
  99. package/dist/sdk/governance/linked-file-report.d.ts +41 -0
  100. package/dist/sdk/governance/linked-file-report.js +72 -0
  101. package/dist/sdk/governance/validate.d.ts +5 -2
  102. package/dist/sdk/governance/validate.js +21 -37
  103. package/dist/sdk/governance.d.ts +2 -0
  104. package/dist/sdk/governance.js +4 -2
  105. package/dist/sdk/history-repair.d.ts +7 -0
  106. package/dist/sdk/history-repair.js +48 -13
  107. package/dist/sdk/lifecycle/close.d.ts +8 -0
  108. package/dist/sdk/lifecycle/close.js +16 -8
  109. package/dist/sdk/lifecycle/create.js +9 -12
  110. package/dist/sdk/lifecycle/update.js +12 -12
  111. package/dist/sdk/lifecycle-policy.d.ts +3 -0
  112. package/dist/sdk/lifecycle-policy.js +2 -2
  113. package/dist/sdk/merge/abandoned-receipts.d.ts +15 -0
  114. package/dist/sdk/merge/abandoned-receipts.js +47 -0
  115. package/dist/sdk/merge/driver.js +4 -2
  116. package/dist/sdk/merge/index.d.ts +1 -0
  117. package/dist/sdk/merge/index.js +2 -2
  118. package/dist/sdk/merge/receipt-operation.d.ts +15 -0
  119. package/dist/sdk/merge/receipt-operation.js +97 -0
  120. package/dist/sdk/merge/receipt-schema.d.ts +20 -0
  121. package/dist/sdk/merge/receipt-schema.js +398 -0
  122. package/dist/sdk/merge/receipts.d.ts +16 -7
  123. package/dist/sdk/merge/receipts.js +54 -340
  124. package/dist/sdk/merge/reconcile.d.ts +2 -0
  125. package/dist/sdk/merge/reconcile.js +11 -4
  126. package/dist/sdk/public-surface.json +1 -1
  127. package/dist/sdk/query/duplicate-candidates.d.ts +7 -0
  128. package/dist/sdk/query/duplicate-candidates.js +71 -0
  129. package/dist/sdk/query/get.js +7 -7
  130. package/dist/sdk/query.d.ts +2 -0
  131. package/dist/sdk/query.js +4 -2
  132. package/dist/sdk/read-output-session.js +5 -3
  133. package/dist/sdk/runtime.js +4 -2
  134. package/dist/sdk/similarity.d.ts +24 -3
  135. package/dist/sdk/similarity.js +50 -55
  136. package/docs/AGENT_GUIDE.md +23 -2
  137. package/docs/BOUNDED_CONTEXT_READS.md +185 -0
  138. package/docs/CLOSE_EVIDENCE_RECOVERY.md +35 -0
  139. package/docs/COMMANDS.md +30 -8
  140. package/docs/CONTEXT_RELEVANCE.md +16 -4
  141. package/docs/DEFECT_RECURRENCE.md +80 -6
  142. package/docs/EXTENSIONS.md +18 -19
  143. package/docs/MERGE_SAFETY.md +50 -0
  144. package/docs/OUTPUT_PROJECTION_CONTRACTS.md +66 -0
  145. package/docs/PR_REVIEW_LOOP.md +18 -3
  146. package/docs/README.md +18 -1
  147. package/docs/RELEASING.md +6 -2
  148. package/docs/SDK_LIFECYCLE.md +27 -0
  149. package/docs/SDK_RUNTIME_BOUNDARIES.md +11 -0
  150. package/docs/generated/FLAG_LEXICON_BUDGETS.md +1 -1
  151. package/marketplace.json +2 -2
  152. package/package.json +6 -3
  153. package/packages/pm-beads/package.json +1 -1
  154. package/packages/pm-calendar/package.json +1 -1
  155. package/packages/pm-command-kit/package.json +1 -1
  156. package/packages/pm-digital-twin/package.json +1 -1
  157. package/packages/pm-governance-audit/README.md +10 -1
  158. package/packages/pm-governance-audit/extensions/governance-audit/dedupe-audit.ts +50 -65
  159. package/packages/pm-governance-audit/extensions/governance-audit/index.ts +2 -2
  160. package/packages/pm-governance-audit/extensions/governance-audit/sdk.ts +1 -0
  161. package/packages/pm-governance-audit/package.json +1 -1
  162. package/packages/pm-guide-shell/package.json +1 -1
  163. package/packages/pm-kanban/package.json +1 -1
  164. package/packages/pm-lifecycle-hooks/package.json +1 -1
  165. package/packages/pm-linked-test-adapters/package.json +1 -1
  166. package/packages/pm-search-advanced/package.json +1 -1
  167. package/packages/pm-templates/package.json +1 -1
  168. package/packages/pm-todos/package.json +1 -1
  169. package/packages/pm-vcs/package.json +1 -1
  170. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  171. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  172. package/dist/cli-bundle/chunks/chunk-LUI4E32I.js +0 -231
  173. package/dist/cli-bundle/chunks/chunk-SVK5Y64D.js +0 -40
  174. package/dist/cli-bundle/chunks/chunk-V5GBD5KV.js +0 -9
  175. package/dist/cli-bundle/chunks/register-operations-KFSCC7QV.js +0 -2
  176. package/dist/cli-bundle/focused-chunks/chunk-24IG6ONG.js +0 -2
  177. package/dist/cli-bundle/focused-chunks/chunk-3ITLIBKS.js +0 -56
  178. package/dist/cli-bundle/focused-chunks/chunk-3NQD5YDG.js +0 -2
  179. package/dist/cli-bundle/focused-chunks/chunk-65N6JHAN.js +0 -2
  180. package/dist/cli-bundle/focused-chunks/chunk-GUBM3RGY.js +0 -20
  181. package/dist/cli-bundle/focused-chunks/chunk-JX3RCVCX.js +0 -2
  182. package/dist/cli-bundle/focused-chunks/chunk-MV752SUC.js +0 -12
  183. package/dist/cli-bundle/focused-chunks/chunk-PKIPMCFS.js +0 -6
  184. package/dist/cli-bundle/focused-chunks/chunk-RJMR2JIF.js +0 -9
  185. package/dist/cli-bundle/focused-chunks/chunk-WIUPWIZR.js +0 -177
@@ -0,0 +1,185 @@
1
+ # Bounded context reads and duplicate discovery
2
+
3
+ Tracked by [pm-pshhry](../.agents/pm/issues/pm-pshhry.toon),
4
+ [pm-ydshl9](../.agents/pm/issues/pm-ydshl9.toon),
5
+ [pm-bab3gb](../.agents/pm/issues/pm-bab3gb.toon),
6
+ [pm-fx80w2](../.agents/pm/issues/pm-fx80w2.toon),
7
+ [pm-gtw5zh](../.agents/pm/issues/pm-gtw5zh.toon), and
8
+ [pm-s8ybl9](../.agents/pm/issues/pm-s8ybl9.toon).
9
+
10
+ Project management is context management. Routine reads should pay for the
11
+ requested evidence, and derived feedback should not grow with every candidate
12
+ in a project's history. These primitives share SDK implementations across CLI,
13
+ MCP, and package consumers.
14
+
15
+ ## Point reads
16
+
17
+ `pm get <id>` at standard depth reads the addressed item and its own history.
18
+ It does not enumerate unrelated items to compute child counts. `--depth brief`
19
+ and `--fields id,title` also omit claim-history work and heavy output facets.
20
+ The cost still depends on the addressed record, registered type directories,
21
+ and extension activation; this is not a constant-latency guarantee for arbitrary
22
+ extensions or unbounded individual history streams.
23
+
24
+ Request hierarchy work explicitly with `--fields id,children`, `--tree`, or a
25
+ deep/full container read. These paths preserve registered hierarchy semantics,
26
+ including inverse and custom relationship kinds, rather than treating only the
27
+ scalar `parent` field as authoritative. An omitted `children` facet carries a
28
+ restore selector in the omission receipt. Explicit deep/full and child-field
29
+ projections retain a zero-count rollup for an empty container.
30
+
31
+ Caller-carried output sessions retain only portable item identities in
32
+ `next_state.seen_item_ids`. Workspace activity such as `_workspace` remains
33
+ visible in full on subsequent reads. It cannot become an invalid item reference
34
+ or make the next read reject a state produced by the previous read.
35
+
36
+ The optional SQLite projection binds each FTS document to the corresponding
37
+ `items.rowid`. Maintenance resolves the indexed item identity and deletes the
38
+ single FTS row, avoiding a scan of the unindexed FTS `id` column on every insert,
39
+ update, or deletion. Derived index version 5 rejects older projections and
40
+ rebuilds them from authoritative metadata. Real SQLite query-plan tests retain
41
+ the old predicate as a negative control and verify search results after updates
42
+ and deletions. SQLite documents the distinction between
43
+ [FTS columns and row identity](https://www.sqlite.org/fts5.html).
44
+
45
+ ## Exact duplicate candidates
46
+
47
+ ```bash
48
+ pm duplicates --status all --threshold 0.8 --json
49
+ pm duplicates --status open --exhaustive --json
50
+ ```
51
+
52
+ The default algorithm orders title tokens by corpus frequency and uses a
53
+ Jaccard prefix join. For a set of size `n` and threshold `t`, a prefix of length
54
+ `n - ceil(t*n) + 1` must intersect the similarly ordered prefix of every
55
+ qualifying set. The length filter rejects impossible token matches before
56
+ scoring. Exact normalized titles, issue codes, and empty-token matches retain
57
+ the canonical scorer's separate rules. This follows the established
58
+ [all-pairs similarity-search approach](https://research.google/pubs/scaling-up-all-pairs-similarity-search/).
59
+
60
+ Both modes retain exact recall relative to the canonical title scorer.
61
+ `cost` discloses `algorithm`, `item_count`, `possible_pairs`, `candidate_pairs`,
62
+ `scored_pairs`, `pair_limit`, and `recall_guarantee`. The one-million-candidate
63
+ safety ceiling remains enforced. Dense duplicate output or a zero threshold
64
+ can still require quadratic work; the command refuses without returning a
65
+ partial answer. Exact recall is not a claim that every semantic duplicate has
66
+ similar titles. Review the actual item evidence before merging or closing.
67
+
68
+ Packages with remote or custom stores can use the same kernel without building
69
+ a filesystem tracker:
70
+
71
+ ```ts
72
+ import { analyzeDuplicateItems } from "@unbrained/pm-cli/sdk/query";
73
+
74
+ const result = analyzeDuplicateItems([
75
+ { id: "work-a", title: "Publish research protocol", type: "Task", status: "open" },
76
+ { id: "work-b", title: "Publish research protocol", type: "Task", status: "closed" },
77
+ ]);
78
+ console.log(result.clusters, result.cost);
79
+ ```
80
+
81
+ The function preserves its input, validates unique nonempty identifiers, and
82
+ returns deterministic components and scored pair evidence. The filesystem
83
+ `findDuplicateClusters`, `PmClient.duplicates`, CLI and MCP adapters reuse it.
84
+ Record the algorithm and cost receipt in create-time duplicate-check evidence.
85
+
86
+ ## Feedback storage
87
+
88
+ Serving receipts retain the full candidate population in memory for final
89
+ delivery validation. Persisted events retain only the first 256 candidate rows,
90
+ with explicit population and omission counts. Only sampled ids observed in the
91
+ final emitted result can train affinity. This deterministic sample has no
92
+ unbiased-propensity claim. Unknown row properties are not persisted.
93
+
94
+ `CONTEXT_USAGE_LIMITS` publishes a 256 KiB physical ceiling, a 32 KiB individual
95
+ event ceiling, 2,048 retained events and a 30-day default horizon. Writers share
96
+ a cross-process lock. High-water compaction atomically replaces the ledger with
97
+ a suffix of at most half the byte ceiling, leaving append headroom. Readers
98
+ consume at most the byte ceiling even for an oversized legacy file. Custom
99
+ retention controls can tighten the limits; invalid controls fail before I/O.
100
+
101
+ Serving receipt `storage` reports actual `written_bytes`, `ledger_bytes`,
102
+ `compacted`, and `lock_wait_ms`. Written bytes count ledger data rather than lock
103
+ metadata or filesystem block allocation. The receipt is attached to results
104
+ through a non-JSON symbol and does not inflate normal CLI output. Storage is
105
+ derived and disposable; item history remains independent. Ledger access refuses
106
+ existing symbolic links, shared hard links, non-regular files, and a redirected
107
+ runtime directory. Appends use a validated descriptor with no-follow opening
108
+ where supported; replacement files are created exclusively with private
109
+ permissions. Compaction closes both data handles before replacing the ledger,
110
+ including on Windows.
111
+
112
+ Feedback persistence requires trusted, stable workspace directory entries and
113
+ ancestors. It is unsafe when an untrusted actor can concurrently replace those
114
+ entries: portable Node filesystem APIs do not pin ancestor directories through
115
+ the whole operation. The link checks reject existing redirections; the lock
116
+ coordinates cooperating SDK writers and is not a security boundary. Before
117
+ using an adversary-writable live workspace, set `PM_CONTEXT_USAGE_DISABLED=1`
118
+ to disable feedback reads and writes, or work in an isolated copy owned by the
119
+ caller. Public feedback API callers can also pass `enabled: false`.
120
+
121
+ ## Reproducible measurements
122
+
123
+ ```bash
124
+ pnpm build
125
+ node scripts/bench/context-read-costs.mjs 10000 100000 1000000
126
+ node scripts/bench/point-read-costs.mjs 100 10000 100000
127
+ ```
128
+
129
+ The script uses paired titles with common project vocabulary, compares a
130
+ 1,000-item subset against exhaustive scoring, and runs four concurrent writers
131
+ for 40 serves per tier in temporary storage. It asserts exact reference equality,
132
+ expected pair counts, reachable byte ceilings, and append headroom. Property
133
+ tests additionally vary thresholds, Unicode, punctuation, and issue identifiers.
134
+
135
+ The point-read script holds the addressed Epic constant while varying corpus
136
+ size. The public `measureItemMetadataReadWork` query SDK wrapper reports actual
137
+ item-store enumeration calls and returned metadata rows, including cache hits.
138
+ Ordinary reads must perform zero enumerations; an explicit child scan must
139
+ observe every generated item. A regression test deliberately enumerates and
140
+ discards the corpus before a normal read, proving that output shape alone cannot
141
+ satisfy this work gate. Measurements preserve operation failures, isolate
142
+ concurrent roots, include nested work, and release their observers on completion.
143
+ They cover item-store enumeration APIs rather than arbitrary extension I/O.
144
+ Fixture setup
145
+ registers all generated types, including `Story`, through the live SDK schema
146
+ and uses deterministic SDK execution for byte-reproducible workspace history.
147
+
148
+ The 2026-09-05 filesystem run observed the following timings (10 reads per
149
+ projection, including feedback writes). The explicit scan includes the cold
150
+ derived-index rebuild; these are host observations rather than latency gates.
151
+
152
+ | Corpus items | Standard p95 | Brief p95 | Fields p95 | Explicit full scan | Verified scan rows |
153
+ | ---: | ---: | ---: | ---: | ---: | ---: |
154
+ | 100 | 4.53 ms | 4.24 ms | 3.54 ms | 48 ms | 100 |
155
+ | 10,000 | 3.61 ms | 3.03 ms | 4.08 ms | 4.91 s | 10,000 |
156
+ | 100,000 | 3.69 ms | 11.00 ms | 3.11 ms | 48.59 s | 100,000 |
157
+
158
+ Measured on 2026-09-05 with Node 26.7.0; these are observations, not portable
159
+ latency guarantees. Duplicate timings exclude filesystem ingestion.
160
+
161
+ | Items | Possible pairs | Scored pairs | Analysis time | Mean bytes written per serve | Compactions / serves | Final ledger bytes |
162
+ | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
163
+ | 10,000 | 49,995,000 | 5,000 | 92 ms | 18,138 | 2 / 40 | 237,785 |
164
+ | 100,000 | 4,999,950,000 | 50,000 | 864 ms | 18,149 | 2 / 40 | 237,821 |
165
+ | 1,000,000 | 499,999,500,000 | 500,000 | 10,367 ms | 18,135 | 2 / 40 | 237,857 |
166
+
167
+ The same four-writer runs include full in-memory candidate validation and final
168
+ delivery recording. Throughput therefore still falls with candidate population.
169
+
170
+ Lock-wait percentiles use the empirical nearest rank across all 40 serving
171
+ receipts; these are four concurrent tasks sharing a cross-process lock, not a
172
+ measurement of four separate OS processes.
173
+
174
+ | Candidate rows per serve | Serves/s/workspace | Minimum wait | p50 wait | p95 wait | Maximum wait |
175
+ | ---: | ---: | ---: | ---: | ---: | ---: |
176
+ | 10,000 | 46.73 | 3.11 ms | 25.15 ms | 84.97 ms | 105.66 ms |
177
+ | 100,000 | 20.82 | 18.29 ms | 50.10 ms | 193.86 ms | 229.60 ms |
178
+ | 1,000,000 | 1.92 | 198.10 ms | 809.52 ms | 1,367.67 ms | 1,622.77 ms |
179
+
180
+ Reference recall was 100% at every tier. The million-item batch does not meet
181
+ the broader one-second project target; persistent incremental ingestion and
182
+ analysis remain necessary for that target. Full candidate validation still
183
+ costs O(candidate count) in memory even though persisted feedback is bounded.
184
+ On the live 2,638-item corpus, default discovery completed after scoring 3,136
185
+ of 3,478,203 possible pairs; the prior algorithm refused at its safety ceiling.
@@ -0,0 +1,35 @@
1
+ # Recovering missing close evidence
2
+
3
+ Tracked by [pm-ub1ott](../.agents/pm/issues/pm-ub1ott.toon).
4
+
5
+ `pm close --validate-close warn` can close an item while reporting missing
6
+ resolution, expected outcome, or actual outcome. Its SDK result and compact CLI
7
+ output now include `recovery.missing`, `suggested_retry`, and
8
+ `suggested_retry_args` when evidence is incomplete. The argument vector selects
9
+ the same tracker and uses `pm update` to add only the missing evidence fields.
10
+ Use `recovery.suggested_retry_args` as the authoritative argument vector. Replace
11
+ each `<value>` with the observed outcome before executing it; retain its tracker
12
+ selection and only the flags it supplies for missing fields. Do not add flags
13
+ for evidence already present, because `pm update` would overwrite that evidence.
14
+
15
+ For example, when only the actual outcome is missing, the recovery arguments
16
+ contain `--actual-result` alone (plus the item and tracker selection):
17
+
18
+ ```bash
19
+ pm --pm-path <tracker-root> update <item-id> \
20
+ --actual-result "Evidence update appended; original close event unchanged"
21
+ ```
22
+
23
+ An attempt to close an already terminal item also returns evidence-update
24
+ recovery when applicable. `--force` is reserved for an intentional repeated
25
+ lifecycle transition. Adding evidence with `pm update` preserves the original
26
+ close timestamp, close reason, and immutable history event, then appends a
27
+ normal update event. It does not retroactively claim that evidence existed at
28
+ the original close. Complete closes have no extra recovery payload.
29
+
30
+ The update flags for the other missing fields are `--resolution` and
31
+ `--expected-result`; include them only when the generated recovery requests them.
32
+
33
+ For new work, include all three fields in the original close command so its
34
+ immutable event contains the full evidence. Warning-mode recovery exists for
35
+ incomplete historical or interrupted workflows.
package/docs/COMMANDS.md CHANGED
@@ -173,12 +173,18 @@ Use `context` first for a compact active-work snapshot. Use `search` when the re
173
173
  Use `duplicates --status all` for a true whole-history duplicate check. The
174
174
  result echoes `filters.statuses: null` for the unrestricted corpus; named or
175
175
  custom statuses are normalized through the runtime status registry and an
176
- unknown status fails instead of returning a false-clean result.
176
+ unknown status fails instead of returning a false-clean result. The default
177
+ lossless prefix filter preserves every pair that meets the canonical similarity
178
+ threshold. `--exhaustive` compares all pairs for small-corpus verification. Both
179
+ modes disclose the algorithm, possible and scored pair counts, exact recall
180
+ guarantee, and safety ceiling; exceeding that ceiling refuses without returning
181
+ a partial result. Review clusters before closing anything: title similarity is
182
+ evidence for investigation, not proof that work is interchangeable.
177
183
  As a read-only structured surface, `duplicates` accepts the universal output
178
184
  controls, including `--output-format json`, `--lean`, projection/amount
179
185
  controls, and token accounting, while its default TOON output remains bounded.
180
186
  Tracked by [pm-gh1076](../.agents/pm/issues/pm-gh1076.toon).
181
- Use `pm get <id>` to read a single item by ID — the single-item read primitive used throughout the agent loop. It accepts `--fields <list>` and `--depth brief|standard|deep|full` for token-minimal projections, and `--tree`/`--tree-depth <n>` to include descendants. The universal `--output-include` selector can request stored collections directly, for example `pm get <id> --output-include comments,learnings,tests`; the same bare and `item.<field>` grammar works through CLI, SDK, and MCP transports. Standard/deep reads expose a normalized `schedule` facet (`deadline`, `start_at`, `end_at`, `location`, reminders, and events) when scheduling metadata exists. Container-oriented built-ins (Epic, Feature, Milestone, and Plan) plus custom types automatically expose type-agnostic child counts and continuation metadata. Standard depth keeps that rollup counts-only; `--depth deep|full` or an explicit `--fields id,children` request adds the deterministic bounded child sample. Built-in leaf reads avoid a workspace scan unless children are explicitly requested. `pm get <id> --json` returns the `body` inside the `item` object (`.item.body`); see [Full results, totals, and bodies](#full-results-totals-and-bodies). To duplicate an existing item as a starting point, `pm copy <id> --title "New title"` clones it into a fresh id with lifecycle fields reset.
187
+ Use `pm get <id>` to read a single item by ID — the single-item read primitive used throughout the agent loop. It accepts `--fields <list>` and `--depth brief|standard|deep|full` for token-minimal projections, and `--tree`/`--tree-depth <n>` to include descendants. The universal `--output-include` selector can request stored collections directly, for example `pm get <id> --output-include comments,learnings,tests`; the same bare and `item.<field>` grammar works through CLI, SDK, and MCP transports. Standard/deep reads expose a normalized `schedule` facet (`deadline`, `start_at`, `end_at`, `location`, reminders, and events) when scheduling metadata exists. Standard reads of every item type avoid a workspace child scan. For container-oriented built-ins (Epic, Feature, Milestone, and Plan) and custom types, `--depth deep|full` adds type-agnostic child counts, a bounded sample, and continuation metadata. An explicit `--fields id,children` requests that rollup for any type. See [bounded context reads](BOUNDED_CONTEXT_READS.md) for the cost contract. `pm get <id> --json` returns the `body` inside the `item` object (`.item.body`); see [Full results, totals, and bodies](#full-results-totals-and-bodies). To duplicate an existing item as a starting point, `pm copy <id> --title "New title"` clones it into a fresh id with lifecycle fields reset.
182
188
 
183
189
  When the strongest duplicate match is terminal because the same work recurred,
184
190
  reuse its lineage instead of creating or copying another item:
@@ -526,7 +532,7 @@ pm notes <id> --event-type step.completed --since 2026-07-31T00:00:00Z --include
526
532
 
527
533
  `append` mutates the scalar Markdown body and is not a concurrency-safe event log. `notes --add` stores union-merged text entries; `notes --add-json` stores a validated JSON payload plus timestamp and author in that same merge-safe collection. Structured events retain canonical JSON text for older readers and expose `format: json`, `data`, and optional `event_type` to SDK/CLI/MCP consumers. `--since`, `--event-type`, `--limit`, and `--include-meta` provide bounded reads without client-side free-text parsing.
528
534
 
529
- can make non-lifecycle metadata updates and append comments, linked files, and linked
535
+ `pm update` can make non-lifecycle metadata updates and append comments, linked files, and linked
530
536
  docs in one audited history entry. It is intentionally append-only for evidence:
531
537
  lifecycle/ownership fields, dependencies, tests, notes, learnings, reminders, events,
532
538
  and all clear/replace operations stay restricted or use their dedicated commands.
@@ -760,7 +766,7 @@ pm deps <id> --format context --direction both --kind blocked_by,parent --token-
760
766
 
761
767
  Linked files and docs keep reviews reproducible. `deps` is read-only and projects item relationships. Tree and graph formats are bounded by depth, node, edge, and estimated-token budgets (safe defaults: depth 32, 200 nodes, 400 edges, 16,000 tokens); pass `--max-depth`, `--node-limit`, `--edge-limit`, or `--token-budget` to tighten them. A bounded result carries deterministic `truncation.reasons`, effective limits, full unique-node/edge totals, and omitted counts. The estimate is calculated from the emitted JSON or TOON representation, including its accounting envelope. `--format context` returns one bounded, explainable relationship packet: a counts-first summary, per-node `role`/`via`/`reasons`, root evidence pointers, enumerated `missing_references` with active-versus-legacy classification, `meta.completeness`, exact rendered `meta.usedTokens`, and cursor continuation; `--direction`, repeatable or comma-separated `--kind`, and `--cursor` further control that traversal (see [Relationship Graph](RELATIONSHIP_GRAPH.md)). `--edge-limit` caps both returned graph edges and enumerated `missing_references`; `missing_reference_count` retains the total when rows are omitted. The standalone `--note <text>` flag annotates every link added by `--add`/`--add-glob` in the same invocation (a per-entry embedded `note=` wins); `--note` without an add is a usage error.
762
768
 
763
- Structured key/value forms reject unrecognized keys with an `Allowed keys: …` error (matching `test --add`), so a typoed key (`lable=` instead of `label=`) fails fast instead of being silently dropped: `--add`/`--file`/`--doc` accept `path,scope,note`; `--add-glob` accepts `pattern,glob,path,scope,note`; `--remove` accepts `path`; `--migrate` accepts `from,to`; create/update `--dep` accepts `id,kind,type,author,created_at,source_kind`; `--reminder` accepts `at,date,text,title`; `--event` accepts `start,date,end,duration,title,description,location,timezone,all_day` and the `recur_*` recurrence keys. Set `source_kind=global` for a dependency owned by another workspace: pm preserves its id verbatim, materializes it as an external graph endpoint, and excludes it from dangling-local-reference findings. Dependencies without that explicit provenance retain normal local `id_prefix` normalization. Bare values (`--add src/cli/main.ts`) skip key validation.
769
+ Structured key/value forms reject unrecognized keys with an `Allowed keys: …` error (matching `test --add`), so a typoed key (`lable=` instead of `label=`) fails fast instead of being silently dropped: `--add`/`--file`/`--doc` accept `path,scope,note`; `--add-glob` accepts `pattern,glob,path,scope,note`; `--remove` accepts `path`; `--migrate` accepts `from,to`; create/update `--dep` accepts `id,kind,type,author,created_at,source_kind`; `--reminder` accepts `at,date,text,title`; `--event` accepts `start,date,end,duration,title,description,location,timezone,all_day` and the `recur_*` recurrence keys. Set `source_kind=global` for a dependency owned by another workspace: pm preserves its id verbatim, materializes it as an external graph endpoint, and excludes it from dangling-local-reference findings. Explicit remote locators also retain their spelling; other dependency and parent references share local `id_prefix` normalization, including uppercase and `#` shorthand. Bare values (`--add src/cli/main.ts`) skip key validation.
764
770
 
765
771
  ## Graph Queries
766
772
 
@@ -886,7 +892,11 @@ pm gc --scope locks --dry-run
886
892
 
887
893
  Use dry-run modes before broad lifecycle or cleanup changes.
888
894
 
889
- `pm gc` accepts `--scope` values `index`, `embeddings`, `runtime`, `locks`, and `checkpoints` (comma-separated or repeatable); with no `--scope` it sweeps all of them. The `runtime` scope clears `runtime/test-runs/` and `runtime/history-drift-cache.json`; removing the drift cache forces the next `pm health` run to perform a full history-drift re-scan. The `embeddings` scope removes the keyword/semantic index artifacts (`search/embeddings.jsonl`, `search/vectorization-status.json`, `search/lancedb/`) **and** the background-refresh queue (`search/pending-refresh.json`) and its gate so a worker draining a stale queue cannot rebuild a partial index against an empty ledger; it invalidates the entire semantic index, so run `pm reindex --mode keyword` (and `--mode semantic` when enabled) afterwards. The `locks` scope removes only **expired** lock files in `locks/` — those whose own embedded `created_at + ttl_seconds` has elapsed (debris left by crashed processes). Active locks and any lock file that cannot be parsed are always retained (never deleted when staleness cannot be proven), and the result includes a `locks` summary (`scanned`/`removed`/`retained`).
895
+ `pm gc --scope` accepts `index`, `embeddings`, `runtime`, `locks`, and `checkpoints`, comma-separated or repeated. Omitting the flag selects all scopes:
896
+
897
+ - `runtime` clears `runtime/test-runs/` and `runtime/history-drift-cache.json`. The next health check rescans history drift.
898
+ - `embeddings` removes keyword/semantic artifacts (`search/embeddings.jsonl`, `search/vectorization-status.json`, `search/lancedb/`) plus `search/pending-refresh.json` and its gate, preventing a background worker from rebuilding a partial index against an empty ledger. Rebuild with keyword reindex and semantic reindex when enabled.
899
+ - `locks` removes only provably expired locks, using embedded `created_at + ttl_seconds`. Active, unreadable, or unparseable locks remain. The result reports `scanned`, `removed`, and `retained` counts.
890
900
 
891
901
  The `checkpoints` scope prunes bulk-mutation rollback checkpoints under `checkpoints/` (written by `pm update-many`/`pm close-many`) that are older than `checkpoints.retention_days` (default 14; set via `pm config <scope> set checkpoints_retention_days <n>`). Checkpoints whose `created_at` cannot be parsed are retained (safety-first, like the locks sweep), and the result includes a `checkpoints` summary (`scanned`/`removed`/`retained`/`retention_days`). Removing aged checkpoints permanently closes their `--rollback` window.
892
902
 
@@ -894,7 +904,7 @@ The `checkpoints` scope prunes bulk-mutation rollback checkpoints under `checkpo
894
904
 
895
905
  `pm validate --check-metadata` also groups missing-required-field counts per item type in `details.missing_by_type` (for example `{ "Task": { "close_reason": 3 } }`) — counts only, zero-suppressed, and limited to the active metadata profile's required fields, so remediation can be targeted by type without verbose row dumps.
896
906
 
897
- `--counts` keeps the validation envelope, check statuses, warning codes, scalar counts/totals, nested count maps, and fix summary totals while recursively omitting diagnostic and fix row arrays. It is the preferred agent projection when deciding whether drift exists; remove it only when the affected ids or remediation rows are needed. The public SDK `projectValidateCounts` helper applies the identical projection to an already-computed `ValidateResult`.
907
+ `--counts` keeps the validation envelope, check statuses, warning codes, scalar counts/totals, nested count maps, and fix summary totals while recursively omitting diagnostic and fix row arrays. False `*_truncated` markers are omitted; true markers, other false values, and zero counts remain. It is the preferred agent projection when deciding whether drift exists; remove it only when the affected ids or remediation rows are needed. The public SDK `projectValidateCounts` helper applies the identical projection to an already-computed `ValidateResult`.
898
908
 
899
909
  By default the human view caps each diagnostic `*_item_ids` list at 5 entries and sets the matching `*_truncated` flag. `--json` **never** truncates those lists (machine consumers always receive the complete arrays), and `--all-affected-ids` (equivalent to `--verbose-diagnostics`) emits the full lists in human mode too — so bulk remediation can pipe every affected id straight into `pm update-many`:
900
910
 
@@ -903,13 +913,25 @@ pm validate --check-metadata --all-affected-ids
903
913
  pm validate --check-metadata --json | jq -r '.checks[] | select(.name=="metadata") | .details.missing_acceptance_criteria_item_ids[]'
904
914
  ```
905
915
 
906
- `--auto-fix` applies the safe, deterministic subset of those remediations automatically and reports the result under a top-level `fixes` object (`planned_fixes[]`, `applied_fixes[]`, `gated_fixes[]`, `failed_fixes[]` — each row lists the item id, check, field, and the equivalent standalone `pm` command). Safe means derivable and non-destructive: a closed item missing `resolution` is backfilled from its own `close_reason` (or the `"completed"` default), and a closed item missing `close_reason` is backfilled from its existing `resolution`. Auto-fix NEVER closes, cancels, or deletes items, and every applied fix runs through the normal audited `pm update` path. Two scopes are opt-in. Structural lifecycle fixes — an active item whose parent is terminal gets reparented to its active grandparent or has its parent link cleared — are always _planned_ but only _applied_ under an explicit `--fix-scope lifecycle`. Estimate backfills — an item missing `estimated_minutes` gets a config-driven per-type default — are likewise _planned_ but only _applied_ under `--fix-scope estimates` (estimates are heuristic per-type guesses, not derived facts, so they are never auto-granted). The defaults are `Epic`/`Milestone` 2880, `Feature`/`Story` 480, `Task`/`Plan` 120, `Issue`/`Bug` 60, `Chore` 30, `Decision` 15, and a 120-minute fallback for any other type; override them per type with the `validation.estimate_defaults_by_type` setting (a `{ "<Type>": <minutes> }` map). `--fix-scope` is an exact allowlist of what `--auto-fix` may mutate (`metadata`, `resolution`, `estimates`, `lifecycle`; comma-separated or repeatable) — `--fix-scope estimates` alone applies _only_ estimate fixes; omitting the flag grants the safe field-backfill scopes (`metadata`, `resolution`) and neither estimates nor lifecycle. `--dry-run` previews the full plan without mutating anything. With `--auto-fix` and no explicit `--check-*` flags, only the fix-capable checks (metadata, resolution, lifecycle) run. The `checks` in the output always describe the pre-fix state; re-run `pm validate` to confirm convergence.
916
+ `--auto-fix` reports `planned_fixes`, `applied_fixes`, `gated_fixes`, and `failed_fixes` under `fixes`. Each row identifies the item, check, field, and equivalent standalone command:
917
+
918
+ - Safe field backfills derive a missing terminal `resolution` from `close_reason` (falling back to `"completed"`) or a missing `close_reason` from existing `resolution`. Every applied change uses the audited update path; auto-fix never closes, cancels, or deletes items.
919
+ - `--fix-scope lifecycle` explicitly permits reparenting an active item from its terminal parent to an active grandparent, or clearing that parent link. These structural fixes are otherwise planned only.
920
+ - `--fix-scope estimates` explicitly permits heuristic per-type estimates. Defaults in minutes are Epic/Milestone 2880, Feature/Story 480, Task/Plan 120, Issue/Bug 60, Chore 30, Decision 15, and fallback 120. Override `validation.estimate_defaults_by_type` with a type-to-minutes map.
921
+ - `--fix-scope` is an exact, repeatable or comma-separated allowlist: `metadata`, `resolution`, `estimates`, `lifecycle`. Omitting it grants only the safe metadata/resolution scopes; specifying only estimates grants only estimates.
922
+ - `--dry-run` previews without mutation. With no explicit check flags, auto-fix runs only metadata, resolution, and lifecycle checks. Output checks describe the pre-fix state; rerun validation to confirm convergence.
907
923
 
908
924
  > Note: acceptance-criteria gaps are intentionally **not** auto-fixed. Unlike resolution/close_reason (derivable from the item's own fields) or estimates (a config-driven type default), acceptance criteria have no deterministic source — synthesizing them from the description would fabricate content rather than derive it, violating the auto-fix safety invariant. They remain `--fix-hints`-only.
909
925
 
910
926
  `pm validate --check-lifecycle` detects dependency cycles from explicit dependency edges, scalar `blocked_by` item ids, and exact pm-id references in `definition_of_ready`. That catches logical deadlocks such as A blocked by B while B's readiness text names A; `--dependency-cycle-severity off|warn|error` still controls whether those cycles warn, error, or remain informational in details.
911
927
 
912
- `pm validate --check-files` classifies every stale linked path in `details.missing_linked_path_classifications` as either `moved` (a file with the same basename still exists in the scan — the row carries the top relink candidate, e.g. `old/path.md:moved:new/path.md`) or `deleted` (no candidate anywhere, e.g. `old/path.md:deleted`). It also reports `details.missing_linked_path_rows` — owner attribution so cleanup is evidence-based without a reverse lookup. By default these are token-efficient one-liners (`<path>:<classification> owner=<id> status=<status> field=<files|docs> title="…"`); `--verbose-file-lists` expands them to the full structured shape (`{ path, classification, items: [{ id, type, title, status, field }] }`). Orphaned existing files get the same treatment through `details.orphaned_path_classifications` and `details.orphaned_path_rows`; classifications start with `docs_unowned`, `tests_unowned`, `source_unowned`, or `unlinked_existing`, and rows include a concrete `pm docs|files <id> --add ...` hint when a likely owner is found from nearby linked paths. `--prune-missing` bulk-removes the stale links classified `deleted` from their items (link removal only — real files are never touched; `moved` links are kept so their relink candidates are not lost) and reports each removal in `fixes.applied_fixes[]` as the equivalent `pm files <id> --remove <path>` / `pm docs <id> --remove <path>` command. It honors `--dry-run` and implies `--check-files`.
928
+ `pm validate --check-files` joins stale paths, owning items, and relocation candidates in structured `details.missing_linked_path_rows` in both compact and full output:
929
+
930
+ - `moved` means same-basename candidates exist; these are suggestions requiring verification. `deleted` means none exists. `malformed` marks ambiguous missing CSV or path:note literals and never infers a relocation or prune.
931
+ - Counters distinguish unique paths from linked holder/field pairs, active holders from the closed subset, and all terminal holders. Only active missing paths cause the files warning; historical debt remains visible. Custom lifecycle roles determine the partition.
932
+ - Active paths and owners appear first. Compact output bounds both dimensions and includes total counts and truncation flags; `--verbose-file-lists` restores every row. See [Linked-file repair rows](OUTPUT_PROJECTION_CONTRACTS.md#linked-file-repair-rows) for the SDK contract.
933
+ - Existing unlinked paths retain `orphaned_path_classifications` and `orphaned_path_rows`, with `docs_unowned`, `tests_unowned`, `source_unowned`, or `unlinked_existing` classifications and concrete ownership hints where available.
934
+ - `--prune-missing` removes only links classified `deleted`, retaining moved and malformed references for review. It never removes real files, honors `--dry-run`, implies `--check-files`, and records equivalent files/docs removal commands in `fixes.applied_fixes`.
913
935
 
914
936
  The default `pm validate` run also includes a `format_version` check that compares every item's stored item metadata format version (`pm_format_version`, absent meaning the implicit baseline version `1`) against the version this pm runtime writes. `details.outdated_items` lists items that predate the current version (a future storage migration would rewrite them — advisory `warn`); `details.ahead_items` lists items written by a _newer_ pm than the one running, which is an `error` because validation cannot vouch for fields it does not understand (upgrade pm to read them safely). At the current baseline version both lists are empty and the check is `ok`. The same `pm_format_version` integrity is surfaced by `pm health` under the `integrity` check (`details.counts.item_outdated_format_version` / `item_ahead_format_version`).
915
937
 
@@ -1,6 +1,6 @@
1
1
  # Context relevance and packing
2
2
 
3
- Tracked by [pm-4k6b](../.agents/pm/features/pm-4k6b.toon), [pm-07pt16](../.agents/pm/issues/pm-07pt16.toon), [pm-wv47pf](../.agents/pm/issues/pm-wv47pf.toon), [pm-3hps](../.agents/pm/tasks/pm-3hps.toon), and [pm-801d](../.agents/pm/features/pm-801d.toon).
3
+ Tracked by [pm-bab3gb](../.agents/pm/issues/pm-bab3gb.toon), [pm-4k6b](../.agents/pm/features/pm-4k6b.toon), [pm-07pt16](../.agents/pm/issues/pm-07pt16.toon), [pm-wv47pf](../.agents/pm/issues/pm-wv47pf.toon), [pm-3hps](../.agents/pm/tasks/pm-3hps.toon), and [pm-801d](../.agents/pm/features/pm-801d.toon).
4
4
 
5
5
  `pm context` and `pm next` share one public SDK relevance pipeline. The built-in
6
6
  commands assemble authoritative item metadata, load rebuildable signal rows,
@@ -104,9 +104,13 @@ the same explanation as the ranking it serves.
104
104
  ### Delivered usage feedback
105
105
 
106
106
  Usage affinity learns from post-egress delivery rather than pre-budget packing.
107
- A versioned serve event records the full propensity rows and a correlation id;
108
- CLI and `PmClient` egress append a delivery event containing the final
109
- `result_omitted` decision and exact emitted item ids. A whole-result omission
107
+ A versioned serve event records at most 256 candidate rows in input order,
108
+ plus `candidate_count`, `omitted_row_count`, and a correlation id. The SDK
109
+ receipt keeps the complete candidate set in memory to validate final delivery.
110
+ CLI and `PmClient` egress append the final `result_omitted` decision and emitted
111
+ item ids intersected with the recorded sample. Unsampled items produce no
112
+ exposure or affinity judgment; the sample is deterministic, not an unbiased
113
+ propensity estimate. A whole-result omission
110
114
  therefore delivers zero items even when the packer selected rows earlier.
111
115
 
112
116
  Direct `runContext` and `runNext` calls record only their assembled rows on a
@@ -116,6 +120,14 @@ Legacy serve events have no correlation marker, so the affinity reader ignores
116
120
  their inclusion flags and reports them as `untrusted_serving_events` instead of
117
121
  training on unverifiable phantom serves.
118
122
 
123
+ The physical ledger is capped at 256 KiB. Appends exceeding that ceiling compact
124
+ atomically under the writer lock to a suffix of at most 128 KiB, leaving headroom
125
+ for subsequent requests. Reads consume at most 256 KiB even for oversized legacy
126
+ files, dropping a partial first row. Age and event-count limits also apply.
127
+ `CONTEXT_USAGE_LIMITS` and serving receipt `storage` expose the ceilings, actual
128
+ written bytes, retained bytes, compaction decision, and lock wait to SDK hosts.
129
+ See [bounded context reads](BOUNDED_CONTEXT_READS.md) for cost measurements.
130
+
119
131
  For corpora of at least 10,000 items, `context` automatically allocates a
120
132
  bounded fraction of its token budget to recent calendar-epoch and epic-lineage
121
133
  rollups. `search` attaches only matching rollups. The projection contains
@@ -1,16 +1,17 @@
1
1
  # Defect Recurrence and Boundary Evidence
2
2
 
3
- Tracked by [pm-1qkivy](../.agents/pm/features/pm-1qkivy.toon), [pm-rtn5h6](../.agents/pm/tasks/pm-rtn5h6.toon), [pm-0pzgit](../.agents/pm/tasks/pm-0pzgit.toon), and [pm-h8tpeh](../.agents/pm/features/pm-h8tpeh.toon).
3
+ Tracked by [pm-1qkivy](../.agents/pm/features/pm-1qkivy.toon), [pm-rtn5h6](../.agents/pm/tasks/pm-rtn5h6.toon), [pm-0pzgit](../.agents/pm/tasks/pm-0pzgit.toon), [pm-7c27ep](../.agents/pm/issues/pm-7c27ep.toon), and [pm-h8tpeh](../.agents/pm/features/pm-h8tpeh.toon).
4
4
 
5
5
  ## Agent Quick Context
6
6
 
7
7
  `pm` treats project management as context management. A resolved defect is therefore not only a closed item: it is durable context that should select the checks most likely to prevent the same failure from recurring.
8
8
 
9
- The public SDK now provides three composable contracts:
9
+ The public SDK provides four composable contracts:
10
10
 
11
11
  - a captured-boundary registry that rejects self-generated fixtures, unsafe samples, missing samples, and expired waivers;
12
12
  - a defect-evidence ratchet that requires a typed escape class plus a gate improvement or reviewed, expiring waiver on new terminal defects;
13
- - a versioned recurrence index that maps proposed files, packages, PM items, tags, and error codes to shared local and hosted checks.
13
+ - a versioned recurrence index that maps proposed files, packages, PM items, tags, and error codes to shared local and hosted checks;
14
+ - a complete-population recurrence coverage report with bounded, resumable evidence rows.
14
15
 
15
16
  The CLI and MCP use the same SDK action path. Repository policy can be replaced by a package or workspace policy without changing the analyzer.
16
17
 
@@ -63,7 +64,8 @@ Use `pm assurance risk` for an operator or agent decision. Use the pure SDK func
63
64
 
64
65
  ### TypeScript compatibility
65
66
 
66
- The exported `ASSURANCE_ACTIONS` tuple and `AssuranceActionResult` union now include `risk` and `DefectChangeRiskReport`. This is additive at runtime, but TypeScript consumers with an exhaustive action or result switch must add the new branch. The SDK surface snapshot records that source-compatibility change explicitly.
67
+ The exported `ASSURANCE_ACTIONS` tuple and `AssuranceActionResult` union include `risk`, `lineages`,
68
+ `DefectChangeRiskReport`, and `DefectRecurrenceCoverageReport`. This is additive at runtime, but TypeScript consumers with an exhaustive action or result switch must add the new branch. The SDK surface snapshot records that source-compatibility change explicitly.
67
69
 
68
70
  ## Policy Model
69
71
 
@@ -79,7 +81,69 @@ The repository example is [config/defect-recurrence-policy.json](../config/defec
79
81
 
80
82
  Repository policy validation rejects duplicate ids, absent historical examples, missing family negative controls, invalid taxonomy values, and budgets outside zero through one. Deterministic sorting makes the serialized policy merge-friendly.
81
83
 
82
- Register a family whenever a defect is recorded as a recurrence of an earlier one, meaning the new item carries a `recurs_from` edge to its predecessor. A recurrence with no family produces no local and no hosted protection, so the next instance is rediscovered by hand. Coverage of recorded recurrence lineages is not yet computed by any gate; that gap is tracked on [pm-7c27ep](../.agents/pm/issues/pm-7c27ep.toon).
84
+ Register a semantic family seed using its owner, trigger item ids, or historical
85
+ item ids. The SDK follows every recorded local `recurs_from` edge in either
86
+ direction to derive family membership for the complete connected lineage.
87
+ Cycles and deep chains do not impose a traversal cutoff. Related, parent, and
88
+ external edges never establish recurrence coverage; tags and matching file
89
+ globs select change risk but cannot certify lineage registration.
90
+
91
+ Derived members inherit the family's checks. Changes naming a member or one of
92
+ its exact project-scoped linked files select those checks even when the policy
93
+ never named that member. Incremental indexes retain sparse direct evidence so
94
+ removing or changing an edge also removes obsolete inherited matches.
95
+
96
+ ### Complete-population coverage
97
+
98
+ Tracked by [pm-7c27ep](../.agents/pm/issues/pm-7c27ep.toon).
99
+
100
+ ```ts
101
+ import { analyzeDefectRecurrenceCoverage } from "@unbrained/pm-cli/sdk/governance";
102
+
103
+ const coverage = analyzeDefectRecurrenceCoverage(policy, allStatusItems, {
104
+ limit: 25,
105
+ uncoveredOnly: true,
106
+ });
107
+ ```
108
+
109
+ Supply an authoritative complete item corpus. The workspace action loads it
110
+ with strict read semantics. Unreadable item documents or directories refuse
111
+ both workspace analyses and the repository gate with `list_source_incomplete`.
112
+ Embedding hosts can request the same policy with
113
+ `createAssuranceWorkspaceContext(pmRoot, { strict_read: true })`.
114
+ Duplicate ids are rejected. Missing local targets
115
+ remain in the population and produce `missing_recurrence_item`; an unregistered
116
+ component produces `unregistered_recurrence_item`. The complete verdict is
117
+ computed before filtering or pagination, so a covered first page cannot hide
118
+ an uncovered member later in the graph.
119
+
120
+ ```bash
121
+ lineage_request=$(jq -cn --slurpfile policy config/defect-recurrence-policy.json \
122
+ '{policy:$policy[0],limit:25,uncoveredOnly:true}')
123
+ pm assurance lineages --definition "$lineage_request" --json
124
+ ```
125
+
126
+ SDK, CLI, and MCP accept the same `definition`. Use `limit` from 1 through 100,
127
+ `cursor`, and `uncoveredOnly` inside that object. Continuations bind the policy,
128
+ complete recurrence evidence, item presence, and filter; changing page size is
129
+ allowed. `total`, `has_more`, and `next_cursor` describe the selected rows;
130
+ `population` always describes every recorded recurrence member. The CLI analysis
131
+ returns a report; the repository gate makes its `ok` predicate blocking.
132
+
133
+ The denominator includes covered, uncovered, and missing recurrence ids across
134
+ all lifecycle states. Recorded recognized `escape_class` values produce class
135
+ counts; unclassified members are never silently counted as non-escapes.
136
+ `escape_rate` is null for an empty or incompletely classified population.
137
+ `escape_rate_lower_bound` separately reports known production defects divided by
138
+ the entire population. Family labels never backfill item classifications, and
139
+ `budget_scope: "registered_families_only"` prevents policy budgets from being
140
+ mistaken for proof about an unknown population.
141
+
142
+ Registration means checks are selected, not that the defect is fixed or a
143
+ hosted check has passed. The policy includes domain seeds for all current
144
+ recorded components, while future disconnected recurrences fail the gate until
145
+ a justified family is registered. Future edges within a registered lineage
146
+ inherit protection automatically.
83
147
 
84
148
  ## Defect Evidence on PM Items
85
149
 
@@ -123,10 +187,20 @@ pnpm quality:defect-evidence
123
187
  node scripts/release/defect-evidence-gate.mjs --negative-control --json
124
188
  ```
125
189
 
126
- The first command must pass. The negative control must exit `1` after replacing a captured sample with a forbidden source and adding a terminal defect without evidence. Focused provider modes are available as `--boundary-only`, `--evidence-only`, and `--policy-only`.
190
+ The first command must pass. The negative control must exit `1` after replacing a captured sample with a forbidden source and adding a terminal defect without evidence. The policy-only negative control
191
+ adds a disconnected, unregistered recurrence pair and must also exit `1`. Focused provider modes are available as `--boundary-only`, `--evidence-only`, and `--policy-only`.
127
192
 
128
193
  `repository-defect-evidence-required` is part of the blocking `repository-static-quality` assurance composition. That makes local and CI behavior share the same provider result, assertion negative control, enforcement, and immutable verdict semantics.
129
194
 
195
+ The mandatory `gate-registry` check also compares each recurrence family's `checks.hosted` values with concrete names derived from the repository workflows. It expands static matrix axes and applies exclusions before inclusions according to [GitHub's matrix rules](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstrategymatrixinclude). Renamed or removed checks produce named findings. Runtime expressions, unnamed matrix jobs, and static expansions beyond 256 combinations cannot certify a required check name. The public SDK policy parser remains provider-neutral; this workflow binding belongs to the repository gate.
196
+
197
+ Assurance request schemas declare each family's required identity, triggers,
198
+ checks, historical IDs, budgets, and negative-control input. Runtime policy
199
+ validation additionally verifies unique family IDs and that each negative control
200
+ actually selects its family; structural JSON Schema validation alone cannot prove
201
+ those relationships. Risk reasons are sorted after direct and inherited matches
202
+ are combined, using signal, value, then matched evidence as stable ordering keys.
203
+
130
204
  ## Recovery Producer Census
131
205
 
132
206
  `censusPmRecoveryReferenceProducers` scans complete source files for static object-literal recovery fields. It ignores type literals, destructuring patterns, labels, comments, strings, templates, and regular expressions. Aliases such as `candidate_commands`, nested `fallback_candidates[].command`, `next_best_command`, `retry_command`, and `suggested_next_steps` normalize to the same six public recovery kinds used by executable reachability verification.
@@ -91,11 +91,15 @@ Package roots declare resources in `package.json` under `pm`:
91
91
  ```
92
92
 
93
93
  Installation activates `pm.extensions`. `pm.docs`, `pm.examples`, `pm.assets`, and `pm.prompts` are catalog metadata (metadata-only — they are discovered and surfaced in the catalog but not executed). Declare agent-facing prompt/slash-command markdown under `pm.prompts` and non-code assets (images, skills, fixtures) under `pm.assets`; their conventional roots are `prompts/` (also `.agents/pm/prompts/`) and `assets/` (also `.agents/pm/assets/`).
94
- `pm package init` and its compatibility spelling `pm extension init` emit the same publishable root-extension artifact (`"extensions": ["."]`): package metadata, a typed `index.ts`, a colocated `node:test` suite, a strict type-check-only `tsconfig.json`, and `typecheck`/`test` scripts. Both results report the canonical `package_name` and exact `invocation_command`; an already prefixed target such as `pm-my-workflow` stays `pm-my-workflow` instead of becoming `pm-pm-my-workflow`. The manifest `entry` points at `./index.ts` itself (ADR [pm-2c28](../.agents/pm/decisions/pm-2c28.toon) / [pm-m1uz](../.agents/pm/decisions/pm-m1uz.toon)). pm loads that `.ts` entry directly via Node's native type stripping (Node >=22.18), so there is no build step — run `npm install` (for the peer SDK and type-checking) before `pm package install`. The generated README shows how to author exported command/hook definitions with the SDK [define\* builders](../.agents/pm/decisions/pm-3mph.toon). `--capability` selects one of ten scaffold starters (`commands`, `hooks`, `search`, `importers`, `schema`, `profile`, `renderers`, `parser`, `preflight`, `services`), each keeping a runnable starter command and adding the selected registration pattern plus a colocated `node:test` suite built on the matching SDK `assertRegistered*`/`runRegistered*ForTest` helpers. These are authoring selectors, not a one-to-one manifest-capability vocabulary: the `profile` starter registers a profile but declares the supported `schema` manifest capability because `api.registerProfile` is schema-governed. The option is repeatable for shell and config composition: repeating the same capability is idempotent, while combining distinct starter shapes fails with a usage error that asks the author to select one explicit scaffold capability. The full per-capability matrix (what each starter registers, which starters also declare `schema` because flag metadata is schema-governed, and why `schema`/`profile` omit `activation.commands` so their global contributions activate conservatively for every command) lives in [SDK.md — Minimal Command Extension](./SDK.md#minimal-command-extension). Starter manifests use the same least-privilege policy metadata as pure first-party command packages: `trusted: true`, `sandbox_profile: "strict"`, and explicit `false` permissions for `fs_read`, `fs_write`, `network`, `env_read`, `env_write`, and `process_spawn`. Declarative starters import `manifest.json` in their generated test and call `assertExtensionManifestMatchesBlueprint`, so capability drift fails locally before publication. Larger packages may point at nested extension directories after declaring runtime dependencies, relaxing only the permissions they actually need, and validating with `pm package doctor`, which additionally emits the advisory `extension_schema_narrow_activation` warning when a package registers custom item types/fields yet declares narrow `activation.commands` (the schema footgun above), recommending the field be dropped so the type stays globally available.
95
- Package tests can pair `readPmPackageManifest(packageRoot)` with
96
- `assertPackageManifest(manifest, { resources: ... })` from
97
- `@unbrained/pm-cli/sdk` to prove aliases and resource paths without duplicating
98
- pm's manifest normalization logic.
94
+ `pm package init` (and compatible `pm extension init`) creates a publishable root extension (`"extensions": ["."]`):
95
+
96
+ - The artifact includes package metadata, typed `index.ts`, a colocated `node:test` suite, strict type-check-only `tsconfig.json`, and `typecheck`/`test` scripts. Results report canonical `package_name` and exact `invocation_command`; an existing `pm-` prefix is retained once.
97
+ - The manifest points to `./index.ts`, loaded by native TypeScript stripping on Node >=22.18 ([pm-2c28](../.agents/pm/decisions/pm-2c28.toon), [pm-m1uz](../.agents/pm/decisions/pm-m1uz.toon)). Run `npm install` for the peer SDK and type checking before package installation; no build step is required. The README demonstrates the SDK [define* builders](../.agents/pm/decisions/pm-3mph.toon).
98
+ - `--capability` selects `commands`, `hooks`, `search`, `importers`, `schema`, `profile`, `renderers`, `parser`, `preflight`, or `services`. Every starter keeps a runnable command and adds matching SDK `assertRegistered*`/`runRegistered*ForTest` tests. Repeating one selection is idempotent; distinct selections fail with an actionable usage error.
99
+ - Scaffold selectors and manifest capabilities differ: `profile` registers a profile under `schema`. The [capability matrix](SDK.md#minimal-command-extension) explains each registration, schema-governed flags, and why `schema`/`profile` omit narrow `activation.commands` so their contributions stay globally available.
100
+ - Starter manifests declare `trusted: true`, `sandbox_profile: "strict"`, and explicit false permissions for filesystem, network, environment, and subprocess access. Declarative tests import `manifest.json` and call `assertExtensionManifestMatchesBlueprint`, detecting capability drift before publication.
101
+ - Larger packages may use nested extension directories and declare required runtime dependencies and permissions. Validate with `pm package doctor`; its advisory `extension_schema_narrow_activation` finding recommends removing narrow activation when custom item types or fields need global availability.
102
+ Package tests can pair `readPmPackageManifest(packageRoot)` with `assertPackageManifest(manifest, { resources: ... })` from `@unbrained/pm-cli/sdk` to prove aliases and resource paths without duplicating pm's manifest normalization logic.
99
103
 
100
104
  When no package manifest is present, `pm` discovers conventional extension directories:
101
105
 
@@ -115,7 +119,11 @@ Runtime path overrides:
115
119
  - `PM_PATH`: project tracker root
116
120
  - `PM_GLOBAL_PATH`: global profile root
117
121
 
118
- Extensions are authored **and loaded** as TypeScript (ADR [pm-2c28](../.agents/pm/decisions/pm-2c28.toon) / [pm-m1uz](../.agents/pm/decisions/pm-m1uz.toon)): a minimal standalone extension has a `manifest.json` and a TypeScript `index.ts` entrypoint the loader imports directly by file URL via Node's native type stripping (Node >=22.18) — there is no compile step and no `./index.js`. Author against the SDK types with `import type { ExtensionApi }` — the type-only import is erased on load, so the `.ts` entry carries **no runtime import** of `@unbrained/pm-cli` (standalone entries are loaded outside any `node_modules`, so a runtime SDK import would fail). Type-check with `npx tsc --noEmit` after installing `typescript` and `@unbrained/pm-cli` for the type resolution.
122
+ Extensions are authored and loaded as TypeScript ([pm-2c28](../.agents/pm/decisions/pm-2c28.toon), [pm-m1uz](../.agents/pm/decisions/pm-m1uz.toon)):
123
+
124
+ - A minimal standalone extension has `manifest.json` and `index.ts`. Node >=22.18 loads that entry directly with native type stripping; no compilation or `index.js` is required.
125
+ - Use `import type { ExtensionApi }` for erased SDK types. Standalone entries outside `node_modules` need the host SDK link described below before using runtime SDK imports.
126
+ - Install `typescript` and `@unbrained/pm-cli`, then run `npx tsc --noEmit` to check authoring types.
119
127
 
120
128
  ```json
121
129
  {
@@ -279,7 +287,7 @@ Surface tokens include command handlers/overrides, parser/preflight/services/ren
279
287
 
280
288
  Some extension surfaces are intentionally single-winner: command handlers and overrides, parser overrides, preflight overrides, and format renderers. Activation is deterministic: lower manifest `priority` values load first, omitted priority defaults to `100`, equal priorities sort by package identity/path, and the last registration wins. If multiple packages register the same single-winner surface, `pm package doctor` / `pm health` report deterministic `extension_*_collision` warnings whose suffix names the winning layer/package before the displaced layer/package. `pm package describe --json` also exposes `command_ownership`: every claimant in activation order, the effective winner, collision state, and the explicit `last_activated_wins` policy. SDK hosts can build the identical table with `buildExtensionDescribeResult` and the exported `ExtensionCommandOwnership` contracts. Renderer ownership is evaluated per command: same-format renderers with disjoint `commands` lists safely coexist, while an unscoped or overlapping claim still warns; runtime `resultDiscriminator` predicates alone cannot prove static disjointness. Tracked by [pm-6mjxgq](../.agents/pm/issues/pm-6mjxgq.toon).
281
289
 
282
- For definition-based commands, validation is isolated per command: a malformed definition is recorded as `extension_command_quarantined:*` with a registration trace while valid siblings continue to activate. Unknown-command recovery reports that failure without recommending reinstallation.
290
+ For definition-based commands, a malformed definition is recorded as `extension_command_quarantined:*` with a registration trace. Activation then rolls back all registrations from that extension, preserving atomic package activation. Fix the failed definition before expecting sibling commands to load. Other valid extensions retain their registrations.
283
291
  Use the warning details to resolve the overlap:
284
292
 
285
293
  ```bash
@@ -294,12 +302,6 @@ Doctor JSON also includes `triage.collision_plan` with grouped surfaces, ranked
294
302
 
295
303
  Use the public SDK barrel. Do not deep-import from `src/core` or `dist/core`.
296
304
 
297
- ```ts
298
- import { defineExtension } from "@unbrained/pm-cli/sdk";
299
- ```
300
-
301
- Common APIs:
302
-
303
305
  - `api.extension` is a read-only identity (`name`, `layer`, `version`, `capabilities`, `pm_min_version?`, `pm_max_version?`, `source_package?`) for self-identifying logs and version gating without re-reading the manifest.
304
306
  - `api.registerCommand(definition)` adds package-owned commands.
305
307
  - `api.registerFlags(command, flags)` adds runtime command flags. A flag may declare `value_type` (canonical; the legacy `type` alias is honored only when `value_type` is absent), `list: true` to accumulate repeated/comma-joined values like core `--tags`, and a `default` applied when the flag is omitted.
@@ -331,6 +333,8 @@ pm contracts --runtime-only --schema-only --json
331
333
  pm contracts --command <command> --flags-only --json
332
334
  ```
333
335
 
336
+ `CliFlagContract.reservation_scope` tells package authors which host flags are reserved: `inherited` flags (including their aliases) cannot be redeclared by extensions; `root_only` flags belong only to the root command. The public `GLOBAL_FLAG_CONTRACTS` table from `@unbrained/pm-cli/sdk/contracts` exposes the same classification used by runtime validation. For example, an extension may define its own boolean `--explain`; root `pm --explain` keeps its host meaning. Redeclaring inherited `--json` fails validation and rolls back that package's activation. See [pm-2h8t6y](../.agents/pm/issues/pm-2h8t6y.toon).
337
+
334
338
  List-valued inline flags and `registerFlags` entries share one ordered
335
339
  accumulator across their long and short aliases. For a variadic positional that
336
340
  must receive values such as `-h` or `--json`, put the end-of-options separator
@@ -381,8 +385,6 @@ pm package reload --project
381
385
  pm package reload --project --watch
382
386
  ```
383
387
 
384
- Compatibility equivalents remain available through `pm extension ...` for existing automation.
385
-
386
388
  ## Upgrade Workflow
387
389
 
388
390
  `pm package upgrade` is the package-first update entrypoint:
@@ -443,7 +445,4 @@ Prefer `createExtensionTestHarness(module, { capabilities })` in package unit te
443
445
 
444
446
  ## Runnable Examples
445
447
 
446
- - `docs/examples/starter-extension/`
447
- - `docs/examples/policy-restricted-extension/`
448
- - `docs/examples/sdk-contract-consumer/`
449
- - `docs/examples/sdk-app-embedding/`
448
+ See the [starter](examples/starter-extension/README.md), [policy-restricted](examples/policy-restricted-extension/README.md), [SDK contract consumer](examples/sdk-contract-consumer/README.md), and [SDK application](examples/sdk-app-embedding/README.md) examples.