@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,41 @@
1
+ import type { ContextUsageEvent, ContextUsageLedgerOptions } from "../context-usage.js";
2
+ /** Public storage and sampling ceilings shared by every feedback producer. */
3
+ export declare const CONTEXT_USAGE_LIMITS: {
4
+ readonly max_serving_rows: 256;
5
+ readonly max_bytes: 262144;
6
+ readonly max_event_bytes: 32768;
7
+ readonly retained_byte_fraction: 0.5;
8
+ readonly max_events: 2048;
9
+ readonly retention_days: 30;
10
+ };
11
+ /** Measured persistence work for one lock-protected feedback write. */
12
+ export interface ContextUsageWriteReceipt {
13
+ /** Bytes written to the append or replacement file; excludes lock metadata. */
14
+ written_bytes: number;
15
+ /** Physical ledger size at the end of the protected write. */
16
+ ledger_bytes: number;
17
+ /** Whether this write replaced the ledger with a retained suffix. */
18
+ compacted: boolean;
19
+ /** Wall-clock time waiting to acquire the cross-process ledger lock. */
20
+ lock_wait_ms: number;
21
+ }
22
+ /** Named validation failure emitted by context-usage SDK inputs. */
23
+ export declare class ContextUsageValidationError extends TypeError {
24
+ /** Stable code for package and CLI hosts. */
25
+ readonly code = "context_usage_validation_failed";
26
+ /** Reject invalid feedback controls without exposing ledger contents. */
27
+ constructor(message: string);
28
+ }
29
+ /** Normalize one timestamp so filtering and serialization share the same clock. */
30
+ export declare function resolveNow(options: ContextUsageLedgerOptions): {
31
+ iso: string;
32
+ ms: number;
33
+ };
34
+ /** Read only a bounded file suffix, dropping a partial first JSONL row. */
35
+ export declare function readEvents(options: ContextUsageLedgerOptions): Promise<ContextUsageEvent[]>;
36
+ /**
37
+ * Append once, or compact atomically to a half-full suffix at the high-water mark.
38
+ * Requires trusted, stable workspace directory entries and ancestors; the lock
39
+ * coordinates cooperating writers and cannot constrain hostile path replacement.
40
+ */
41
+ export declare function appendEvents(options: ContextUsageLedgerOptions, events: readonly ContextUsageEvent[], deliveryServeId?: string): Promise<ContextUsageWriteReceipt>;
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @module sdk/context/usage-ledger
3
+ *
4
+ * Bounds derived feedback reads and writes in bytes. High-water compaction
5
+ * retains a half-full suffix under the same lock as append, leaving headroom
6
+ * for subsequent serves without rewriting on every context request.
7
+ */
8
+
9
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f3af7f8f-a462-5eb4-af08-c64c3c327e62")}catch(e){}}();
10
+ import { randomUUID } from "node:crypto";
11
+ import { constants } from "node:fs";
12
+ import { lstat, mkdir, open, rename, rm } from "node:fs/promises";
13
+ import path from "node:path";
14
+ import { isFileMissingError } from "../../core/fs/fs-utils.js";
15
+ import { acquireLock } from "../../core/lock/lock.js";
16
+ /** Public storage and sampling ceilings shared by every feedback producer. */
17
+ export const CONTEXT_USAGE_LIMITS = {
18
+ max_serving_rows: 256,
19
+ max_bytes: 262_144,
20
+ max_event_bytes: 32_768,
21
+ retained_byte_fraction: 0.5,
22
+ max_events: 2_048,
23
+ retention_days: 30,
24
+ };
25
+ /** Named validation failure emitted by context-usage SDK inputs. */
26
+ export class ContextUsageValidationError extends TypeError {
27
+ /** Stable code for package and CLI hosts. */
28
+ code = "context_usage_validation_failed";
29
+ /** Reject invalid feedback controls without exposing ledger contents. */
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = "ContextUsageValidationError";
33
+ }
34
+ }
35
+ /** Normalize one timestamp so filtering and serialization share the same clock. */
36
+ export function resolveNow(options) {
37
+ const ms = Date.parse(options.now ?? new Date().toISOString());
38
+ if (!Number.isFinite(ms))
39
+ throw new ContextUsageValidationError("Context usage now must be a valid timestamp");
40
+ return { iso: new Date(ms).toISOString(), ms };
41
+ }
42
+ /** Resolve checked byte, count and time bounds before touching storage. */
43
+ function bounds(options) {
44
+ const maxBytes = options.maxBytes ?? CONTEXT_USAGE_LIMITS.max_bytes;
45
+ const maxEvents = options.maxEvents ?? CONTEXT_USAGE_LIMITS.max_events;
46
+ const retentionDays = options.retentionDays ?? CONTEXT_USAGE_LIMITS.retention_days;
47
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1024 || maxBytes > CONTEXT_USAGE_LIMITS.max_bytes ||
48
+ !Number.isSafeInteger(maxEvents) || maxEvents < 1 || !Number.isFinite(retentionDays) || retentionDays <= 0) {
49
+ throw new ContextUsageValidationError("Context usage retention requires positive maxEvents and retentionDays, and maxBytes from 1024 through 262144");
50
+ }
51
+ return { maxBytes, maxEvents, cutoff: resolveNow(options).ms - retentionDays * 86_400_000 };
52
+ }
53
+ /** Open only a private regular ledger, refusing redirected runtime directories and file links. */
54
+ async function openLedger(target, flags) {
55
+ if (!(await lstat(path.dirname(target))).isDirectory()) {
56
+ throw new ContextUsageValidationError("Context usage runtime must be a real directory");
57
+ }
58
+ try {
59
+ if ((await lstat(target)).isSymbolicLink()) {
60
+ throw new ContextUsageValidationError("Context usage ledger must not be a symbolic link");
61
+ }
62
+ }
63
+ catch (error) {
64
+ if (!isFileMissingError(error))
65
+ throw error;
66
+ }
67
+ // Unsupported platform flags contribute zero to the numeric bit mask. The
68
+ // lstat check also rejects existing links where no-follow is unavailable.
69
+ const handle = await open(target, flags | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600);
70
+ try {
71
+ const metadata = await handle.stat();
72
+ if (!metadata.isFile() || metadata.nlink !== 1) {
73
+ throw new ContextUsageValidationError("Context usage ledger must be an unshared regular file");
74
+ }
75
+ return handle;
76
+ }
77
+ catch (error) {
78
+ await handle.close();
79
+ throw error;
80
+ }
81
+ }
82
+ /** Read only a bounded file suffix, dropping a partial first JSONL row. */
83
+ export async function readEvents(options) {
84
+ const policy = bounds(options);
85
+ try {
86
+ const handle = await openLedger(path.join(options.pmRoot, "runtime", "context-usage.jsonl"), constants.O_RDONLY);
87
+ try {
88
+ const size = (await handle.stat()).size;
89
+ const start = Math.max(0, size - policy.maxBytes);
90
+ const buffer = Buffer.alloc(Math.min(size, policy.maxBytes));
91
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, start);
92
+ const text = buffer.subarray(0, bytesRead).toString("utf8");
93
+ const lines = (start > 0 ? (text.includes("\n") ? text.slice(text.indexOf("\n") + 1) : "") : text).split("\n");
94
+ return lines.flatMap((line) => {
95
+ try {
96
+ const event = JSON.parse(line);
97
+ return event && Date.parse(event.at) >= policy.cutoff ? [event] : [];
98
+ }
99
+ catch {
100
+ return [];
101
+ }
102
+ }).slice(-policy.maxEvents);
103
+ }
104
+ finally {
105
+ await handle.close();
106
+ }
107
+ }
108
+ catch {
109
+ return [];
110
+ }
111
+ }
112
+ /** Retain a contiguous newest suffix within both byte and event-count bounds. */
113
+ function retainedText(events, byteLimit, maxEvents) {
114
+ const lines = [];
115
+ let bytes = 0;
116
+ for (let index = events.length - 1; index >= 0 && lines.length < maxEvents; index -= 1) {
117
+ const line = `${JSON.stringify(events[index])}\n`;
118
+ bytes += Buffer.byteLength(line);
119
+ if (bytes > byteLimit)
120
+ break;
121
+ lines.push(line);
122
+ }
123
+ return lines.reverse().join("");
124
+ }
125
+ /**
126
+ * Append once, or compact atomically to a half-full suffix at the high-water mark.
127
+ * Requires trusted, stable workspace directory entries and ancestors; the lock
128
+ * coordinates cooperating writers and cannot constrain hostile path replacement.
129
+ */
130
+ export async function appendEvents(options, events, deliveryServeId) {
131
+ const policy = bounds(options);
132
+ const target = path.join(options.pmRoot, "runtime", "context-usage.jsonl");
133
+ const retainedBytes = Math.floor(policy.maxBytes * CONTEXT_USAGE_LIMITS.retained_byte_fraction);
134
+ const eventLimit = Math.min(CONTEXT_USAGE_LIMITS.max_event_bytes, retainedBytes);
135
+ if (events.some((event) => Buffer.byteLength(`${JSON.stringify(event)}\n`) > eventLimit)) {
136
+ throw new ContextUsageValidationError("Context usage event exceeds the declared byte ceiling");
137
+ }
138
+ const current = events.filter((event) => Date.parse(event.at) >= policy.cutoff);
139
+ const appended = retainedText(current, retainedBytes, policy.maxEvents);
140
+ const lockStarted = performance.now();
141
+ const releaseLock = await acquireLock(options.pmRoot, "context-usage-ledger", 30, `context-usage:${process.pid}`, false, false, 3_000);
142
+ const lockWaitMs = performance.now() - lockStarted;
143
+ try {
144
+ await mkdir(path.dirname(target), { recursive: true });
145
+ let handle = await openLedger(target, constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND);
146
+ try {
147
+ const size = (await handle.stat()).size;
148
+ const previous = deliveryServeId === undefined ? undefined : await readEvents(options);
149
+ if (previous?.some((event) => event.kind === "delivery" && event.serve_id === deliveryServeId)) {
150
+ return { written_bytes: 0, ledger_bytes: size, compacted: false, lock_wait_ms: lockWaitMs };
151
+ }
152
+ const customRetention = options.maxEvents !== undefined || options.retentionDays !== undefined;
153
+ if (!customRetention && size + Buffer.byteLength(appended) <= policy.maxBytes) {
154
+ await handle.writeFile(appended, "utf8");
155
+ return { written_bytes: Buffer.byteLength(appended), ledger_bytes: size + Buffer.byteLength(appended), compacted: false, lock_wait_ms: lockWaitMs };
156
+ }
157
+ const retained = retainedText([...(previous ?? await readEvents(options)), ...current], retainedBytes, policy.maxEvents);
158
+ const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
159
+ const replacement = await open(temporary, "wx", 0o600);
160
+ try {
161
+ try {
162
+ await replacement.writeFile(retained, "utf8");
163
+ }
164
+ finally {
165
+ await replacement.close();
166
+ }
167
+ // Windows cannot replace the destination while our append handle is open.
168
+ await handle.close();
169
+ handle = undefined;
170
+ await rename(temporary, target);
171
+ return { written_bytes: Buffer.byteLength(retained), ledger_bytes: Buffer.byteLength(retained), compacted: true, lock_wait_ms: lockWaitMs };
172
+ }
173
+ finally {
174
+ await rm(temporary, { force: true });
175
+ }
176
+ }
177
+ finally {
178
+ await handle?.close();
179
+ }
180
+ }
181
+ finally {
182
+ await releaseLock();
183
+ }
184
+ }
185
+ //# sourceMappingURL=usage-ledger.js.map
186
+ //# debugId=f3af7f8f-a462-5eb4-af08-c64c3c327e62
@@ -1,3 +1,5 @@
1
+ import { type ContextUsageWriteReceipt } from "./context/usage-ledger.js";
2
+ export { ContextUsageValidationError, CONTEXT_USAGE_LIMITS, type ContextUsageWriteReceipt } from "./context/usage-ledger.js";
1
3
  import type { ContextRelevanceSurface } from "./context-relevance.js";
2
4
  /** One propensity row disclosed when context or next serves a candidate. */
3
5
  export interface ContextUsageServingRow {
@@ -18,6 +20,10 @@ export interface ContextUsageServingReceipt {
18
20
  surface: ContextRelevanceSurface;
19
21
  /** Propensity-complete ranked rows assembled before egress. */
20
22
  rows: readonly ContextUsageServingRow[];
23
+ /** Persisted sample identities; absent on legacy unsampled receipts. */
24
+ recorded_item_ids?: readonly string[];
25
+ /** Measured bounded persistence; absent on legacy receipts. */
26
+ storage?: ContextUsageWriteReceipt;
21
27
  }
22
28
  /** Append-only serving or subsequent-touch event. */
23
29
  export type ContextUsageEvent = {
@@ -29,6 +35,10 @@ export type ContextUsageEvent = {
29
35
  surface: ContextRelevanceSurface;
30
36
  profile: string;
31
37
  rows: ContextUsageServingRow[];
38
+ /** Full candidate population before deterministic prefix sampling. */
39
+ candidate_count?: number;
40
+ /** Candidate rows excluded from this bounded feedback sample. */
41
+ omitted_row_count?: number;
32
42
  result_omitted?: boolean;
33
43
  packed_item_ids?: string[];
34
44
  /** Legacy pre-egress field ignored by the v2 affinity fold. */
@@ -51,12 +61,18 @@ export type ContextUsageEvent = {
51
61
  };
52
62
  /** Runtime controls for the bounded derived ledger. */
53
63
  export interface ContextUsageLedgerOptions {
54
- /** Tracker root containing the runtime directory. */
64
+ /**
65
+ * Tracker root containing the runtime directory. Its directory entries and
66
+ * ancestors must remain controlled by trusted cooperating processes during
67
+ * ledger operations; hostile concurrent path replacement is unsupported.
68
+ */
55
69
  pmRoot: string;
56
70
  /** Disable all reads and writes with zero filesystem work. */
57
71
  enabled?: boolean;
58
72
  /** Maximum retained event rows. */
59
73
  maxEvents?: number;
74
+ /** Physical byte ceiling; compaction retains at most half to provide headroom. */
75
+ maxBytes?: number;
60
76
  /** Retention horizon in days. */
61
77
  retentionDays?: number;
62
78
  /** Deterministic timestamp override. */
@@ -90,14 +106,7 @@ export declare function finalizeContextUsageDelivery(options: {
90
106
  /** Final projected response returned or rendered to the caller. */
91
107
  result: unknown;
92
108
  }): Promise<boolean>;
93
- /** Named validation failure emitted by context-usage SDK inputs. */
94
- export declare class ContextUsageValidationError extends TypeError {
95
- /** Stable machine-readable error code for package and CLI hosts. */
96
- readonly code = "context_usage_validation_failed";
97
- /** Creates a typed validation error without exposing derived ledger contents. */
98
- constructor(message: string);
99
- }
100
- /** Records a propensity-complete context/next serving event. */
109
+ /** Records a bounded prefix of ranked context candidates with explicit sampling counts. */
101
110
  export declare function recordContextUsageServing(options: ContextUsageLedgerOptions & {
102
111
  author: string;
103
112
  surface: ContextRelevanceSurface;
@@ -4,18 +4,16 @@
4
4
  * Privacy-minimal, derived context-usage feedback primitives. The JSONL ledger
5
5
  * contains only item ids, timestamps, authors, ranks, profiles, and command
6
6
  * intents; it never enters item history and is safe to delete or rebuild.
7
+ * Persistence requires trusted, stable workspace directory entries and
8
+ * ancestors. For a workspace writable by an untrusted concurrent actor, set
9
+ * PM_CONTEXT_USAGE_DISABLED=1 or use an isolated copy owned by the caller.
7
10
  */
8
11
 
9
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0c0d2e38-0964-5055-b2f5-6ba7602b2f92")}catch(e){}}();
12
+ !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]="b1b1aa19-b1de-5140-8aea-ee1994ee9ecd")}catch(e){}}();
10
13
  import { randomUUID } from "node:crypto";
11
- import { appendFile, mkdir, readFile, rename, stat, writeFile, } from "node:fs/promises";
12
- import path from "node:path";
13
- import { acquireLock } from "../core/lock/lock.js";
14
- const DEFAULT_MAX_EVENTS = 2_048;
15
- const DEFAULT_RETENTION_DAYS = 30;
14
+ import { appendEvents, readEvents, resolveNow, ContextUsageValidationError, CONTEXT_USAGE_LIMITS, } from "./context/usage-ledger.js";
15
+ export { ContextUsageValidationError, CONTEXT_USAGE_LIMITS } from "./context/usage-ledger.js";
16
16
  const DAY_MS = 86_400_000;
17
- const DEFAULT_COMPACTION_BYTES = 262_144;
18
- const CONTEXT_USAGE_LOCK_ID = "context-usage-ledger";
19
17
  const CONTEXT_USAGE_SERVING_RECEIPT = Symbol.for("@unbrained/pm-cli/context-usage-serving-receipt");
20
18
  function asObjectRecord(value) {
21
19
  return typeof value === "object" && value !== null && !Array.isArray(value)
@@ -162,86 +160,7 @@ export async function finalizeContextUsageDelivery(options) {
162
160
  });
163
161
  return true;
164
162
  }
165
- /** Named validation failure emitted by context-usage SDK inputs. */
166
- export class ContextUsageValidationError extends TypeError {
167
- /** Stable machine-readable error code for package and CLI hosts. */
168
- code = "context_usage_validation_failed";
169
- /** Creates a typed validation error without exposing derived ledger contents. */
170
- constructor(message) {
171
- super(message);
172
- this.name = "ContextUsageValidationError";
173
- }
174
- }
175
- function ledgerPath(pmRoot) {
176
- return path.join(pmRoot, "runtime", "context-usage.jsonl");
177
- }
178
- function resolveNow(options) {
179
- const iso = options.now ?? new Date().toISOString();
180
- const ms = Date.parse(iso);
181
- if (!Number.isFinite(ms))
182
- throw new ContextUsageValidationError("Context usage now must be a valid timestamp");
183
- return { iso: new Date(ms).toISOString(), ms };
184
- }
185
- async function readEvents(options) {
186
- try {
187
- const text = await readFile(ledgerPath(options.pmRoot), "utf8");
188
- const events = text
189
- .split("\n")
190
- .filter(Boolean)
191
- .flatMap((line) => {
192
- try {
193
- return [JSON.parse(line)];
194
- }
195
- catch {
196
- return [];
197
- }
198
- });
199
- const maxEvents = options.maxEvents ?? DEFAULT_MAX_EVENTS;
200
- const retentionDays = options.retentionDays ?? DEFAULT_RETENTION_DAYS;
201
- const cutoff = resolveNow(options).ms - retentionDays * DAY_MS;
202
- return events
203
- .filter((entry) => Date.parse(entry.at) >= cutoff)
204
- .slice(-maxEvents);
205
- }
206
- catch {
207
- return [];
208
- }
209
- }
210
- async function appendEvents(options, events, deliveryServeId) {
211
- const maxEvents = options.maxEvents ?? DEFAULT_MAX_EVENTS;
212
- const retentionDays = options.retentionDays ?? DEFAULT_RETENTION_DAYS;
213
- if (!Number.isInteger(maxEvents) ||
214
- maxEvents < 1 ||
215
- !Number.isFinite(retentionDays) ||
216
- retentionDays <= 0) {
217
- throw new ContextUsageValidationError("Context usage retention requires positive maxEvents and retentionDays");
218
- }
219
- const target = ledgerPath(options.pmRoot);
220
- const runtimeDirectory = path.dirname(target);
221
- const releaseLock = await acquireLock(options.pmRoot, CONTEXT_USAGE_LOCK_ID, 30, `context-usage:${process.pid}`, false, false, 3_000);
222
- try {
223
- await mkdir(runtimeDirectory, { recursive: true });
224
- if (deliveryServeId !== undefined &&
225
- (await readEvents(options)).some((event) => event.kind === "delivery" && event.serve_id === deliveryServeId)) {
226
- return;
227
- }
228
- await appendFile(target, events.map((event) => `${JSON.stringify(event)}\n`).join(""), "utf8");
229
- const customBounds = options.maxEvents !== undefined || options.retentionDays !== undefined;
230
- if (!customBounds && (await stat(target)).size <= DEFAULT_COMPACTION_BYTES)
231
- return;
232
- const retained = await readEvents(options);
233
- const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
234
- await writeFile(temporary, `${retained.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf8");
235
- await rename(temporary, target);
236
- }
237
- finally {
238
- await releaseLock();
239
- }
240
- }
241
- async function appendEvent(options, event) {
242
- await appendEvents(options, [event]);
243
- }
244
- /** Records a propensity-complete context/next serving event. */
163
+ /** Records a bounded prefix of ranked context candidates with explicit sampling counts. */
245
164
  export async function recordContextUsageServing(options) {
246
165
  if ([
247
166
  process.env.PM_CONTEXT_USAGE_DISABLED === "1",
@@ -253,32 +172,37 @@ export async function recordContextUsageServing(options) {
253
172
  throw new ContextUsageValidationError("Context usage serving requires an author and valid ranked rows");
254
173
  }
255
174
  const normalizedRows = options.rows.map((row) => ({
256
- ...row,
257
175
  id: row.id.trim(),
176
+ rank: row.rank,
177
+ included: row.included,
258
178
  }));
259
179
  if (new Set(normalizedRows.map((row) => row.id)).size !== normalizedRows.length) {
260
180
  throw new ContextUsageValidationError("Context usage serving requires unique normalized row ids");
261
181
  }
182
+ const recordedRows = normalizedRows.slice(0, CONTEXT_USAGE_LIMITS.max_serving_rows);
262
183
  const receipt = {
263
184
  serve_id: randomUUID(),
264
185
  author: options.author.trim(),
265
186
  surface: options.surface,
266
187
  rows: normalizedRows,
188
+ recorded_item_ids: recordedRows.map((row) => row.id),
267
189
  };
268
- await appendEvent(options, {
269
- kind: "serve",
270
- schema_version: 2,
271
- serve_id: receipt.serve_id,
272
- at: resolveNow(options).iso,
273
- author: options.author.trim(),
274
- surface: options.surface,
275
- profile: options.profile.trim() || "balanced",
276
- rows: receipt.rows.map((row) => ({ ...row })),
277
- result_omitted: false,
278
- packed_item_ids: receipt.rows
279
- .filter((row) => row.included)
280
- .map((row) => row.id),
281
- });
190
+ receipt.storage = await appendEvents(options, [{
191
+ kind: "serve",
192
+ schema_version: 2,
193
+ serve_id: receipt.serve_id,
194
+ at: resolveNow(options).iso,
195
+ author: options.author.trim(),
196
+ surface: options.surface,
197
+ profile: options.profile.trim() || "balanced",
198
+ rows: recordedRows,
199
+ candidate_count: normalizedRows.length,
200
+ omitted_row_count: normalizedRows.length - recordedRows.length,
201
+ result_omitted: false,
202
+ packed_item_ids: recordedRows
203
+ .filter((row) => row.included)
204
+ .map((row) => row.id),
205
+ }]);
282
206
  return receipt;
283
207
  }
284
208
  /** Append the exact post-egress delivery decision for one serving receipt. */
@@ -296,6 +220,7 @@ export async function recordContextUsageDelivery(options) {
296
220
  (options.resultOmitted && deliveredItemIds.length > 0)) {
297
221
  throw new ContextUsageValidationError("Context usage delivery requires emitted ranked ids and zero ids for an omitted result");
298
222
  }
223
+ const recordedIds = new Set(options.receipt.recorded_item_ids ?? options.receipt.rows.map((row) => row.id));
299
224
  const delivery = {
300
225
  kind: "delivery",
301
226
  schema_version: 2,
@@ -304,7 +229,7 @@ export async function recordContextUsageDelivery(options) {
304
229
  author: options.receipt.author,
305
230
  surface: options.receipt.surface,
306
231
  result_omitted: options.resultOmitted,
307
- delivered_item_ids: deliveredItemIds,
232
+ delivered_item_ids: deliveredItemIds.filter((id) => recordedIds.has(id)),
308
233
  };
309
234
  await appendEvents(options, [delivery], delivery.serve_id);
310
235
  }
@@ -318,13 +243,13 @@ export async function recordContextUsageTouch(options) {
318
243
  !options.intent.trim()) {
319
244
  throw new ContextUsageValidationError("Context usage touch requires author, itemId, and intent");
320
245
  }
321
- await appendEvent(options, {
322
- kind: "touch",
323
- at: resolveNow(options).iso,
324
- author: options.author.trim(),
325
- item_id: options.itemId.trim(),
326
- intent: options.intent.trim(),
327
- });
246
+ await appendEvents(options, [{
247
+ kind: "touch",
248
+ at: resolveNow(options).iso,
249
+ author: options.author.trim(),
250
+ item_id: options.itemId.trim(),
251
+ intent: options.intent.trim(),
252
+ }]);
328
253
  }
329
254
  /**
330
255
  * Records mutation outcomes as one append and one optional compaction pass.
@@ -440,4 +365,4 @@ export async function readContextUsageAffinity(options) {
440
365
  };
441
366
  }
442
367
  //# sourceMappingURL=context-usage.js.map
443
- //# debugId=0c0d2e38-0964-5055-b2f5-6ba7602b2f92
368
+ //# debugId=b1b1aa19-b1de-5140-8aea-ee1994ee9ecd
@@ -61,3 +61,10 @@ export declare function normalizeDependencySourceKind(sourceKind: string | undef
61
61
  * global seeds preserve the caller-provided id verbatim (apart from trimming).
62
62
  */
63
63
  export declare function normalizeDependencySeedId(id: string, prefix: string, sourceKind: string | undefined): string;
64
+ /**
65
+ * Canonicalize a local relationship target using the workspace prefix. Explicit
66
+ * provider locators and externally sourced identities retain case and spelling;
67
+ * only surrounding whitespace is removed. Resolution and parent policy remain
68
+ * the caller's responsibility, so dangling local ids are never guessed remote.
69
+ */
70
+ export declare function normalizeItemReference(reference: string, prefix: string, sourceKind?: string): string;
@@ -5,7 +5,7 @@
5
5
  * items outside the current workspace.
6
6
  */
7
7
 
8
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f3f3beb2-1d60-52fe-88ce-1ab3cb9dba0d")}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]="ee17fdea-aeac-55b0-8302-a8740ba8428e")}catch(e){}}();
9
9
  import { normalizeItemId } from "../core/item/id.js";
10
10
  import { isExternalDependencyReference, isExternalDependencySourceKind, } from "../core/item/dependency-reference.js";
11
11
  export { isExternalDependencyReference, isExternalDependencySourceKind, } from "../core/item/dependency-reference.js";
@@ -117,10 +117,20 @@ export function normalizeDependencySourceKind(sourceKind) {
117
117
  * global seeds preserve the caller-provided id verbatim (apart from trimming).
118
118
  */
119
119
  export function normalizeDependencySeedId(id, prefix, sourceKind) {
120
- const trimmed = id.trim();
121
- return isExternalDependencySourceKind(sourceKind)
120
+ return normalizeItemReference(id, prefix, sourceKind);
121
+ }
122
+ /**
123
+ * Canonicalize a local relationship target using the workspace prefix. Explicit
124
+ * provider locators and externally sourced identities retain case and spelling;
125
+ * only surrounding whitespace is removed. Resolution and parent policy remain
126
+ * the caller's responsibility, so dangling local ids are never guessed remote.
127
+ */
128
+ export function normalizeItemReference(reference, prefix, sourceKind) {
129
+ const trimmed = reference.trim();
130
+ return isExternalDependencySourceKind(sourceKind) ||
131
+ isExternalDependencyReference(trimmed)
122
132
  ? trimmed
123
133
  : normalizeItemId(trimmed, prefix);
124
134
  }
125
135
  //# sourceMappingURL=dependency-provenance.js.map
126
- //# debugId=f3f3beb2-1d60-52fe-88ce-1ab3cb9dba0d
136
+ //# debugId=ee17fdea-aeac-55b0-8302-a8740ba8428e
@@ -8,6 +8,8 @@ import type { GlobalOptions } from "../core/shared/command-types.js";
8
8
  import { type DuplicateCluster, type DuplicateClustersResult } from "./similarity.js";
9
9
  /** Options shared by the `pm duplicates` command and SDK action. */
10
10
  export interface DuplicatesCommandOptions {
11
+ /** Compare all pairs instead of using lossless prefix filtering. */
12
+ exhaustive?: boolean;
11
13
  /** Lifecycle statuses to include; omitted means every status. */
12
14
  status?: readonly string[];
13
15
  /** Inclusive creation timestamp lower bound. */
@@ -1,11 +1,12 @@
1
1
 
2
- !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]="6152efa9-8338-57f0-b3de-5707befed019")}catch(e){}}();
2
+ !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]="51a84d98-6b3c-55ad-81f8-81928aa6e537")}catch(e){}}();
3
3
  import { resolvePmRoot } from "../core/store/paths.js";
4
4
  import { findDuplicateClusters, } from "./similarity.js";
5
5
  /** Discover existing duplicate clusters without creating or mutating an item. */
6
6
  export async function runDuplicates(global, options = {}) {
7
7
  const result = await findDuplicateClusters({
8
8
  pmRoot: resolvePmRoot(process.cwd(), global.path),
9
+ exhaustive: options.exhaustive,
9
10
  ...(options.status === undefined
10
11
  ? {}
11
12
  : {
@@ -37,4 +38,4 @@ export async function runDuplicates(global, options = {}) {
37
38
  };
38
39
  }
39
40
  //# sourceMappingURL=duplicates.js.map
40
- //# debugId=6152efa9-8338-57f0-b3de-5707befed019
41
+ //# debugId=51a84d98-6b3c-55ad-81f8-81928aa6e537
@@ -1,27 +1,5 @@
1
- import { PmCliError } from "../../core/shared/errors.js";
2
- /** Optional stable codes used by an existing SDK surface for each fault class. */
3
- export interface HostEnvironmentFaultCodeOverrides {
4
- /** Code used for quota and storage exhaustion. */
5
- capacity?: string;
6
- /** Code used for access policy and read-only filesystems. */
7
- permission?: string;
8
- /** Code used for file-descriptor and memory exhaustion. */
9
- resource?: string;
10
- }
11
- /** Context and compatibility controls for a host-environment boundary. */
12
- export interface HostEnvironmentBoundaryOptions {
13
- /** Existing surface-specific codes that must remain compatible. */
14
- codes?: HostEnvironmentFaultCodeOverrides;
15
- /** Human explanation of why the operation needs host resources. */
16
- why?: string;
17
- /** Additional ordered recovery guidance. */
18
- nextSteps?: readonly string[];
19
- /** Copyable retry command when one is safe. */
20
- suggestedRetry?: string;
21
- }
22
- /** Return a declared host errno only when it represents recoverable environment state. */
23
- export declare function classifyHostEnvironmentFault(error: unknown): string | null;
24
- /** Translate one known host fault into a stable path-redacted SDK refusal. */
25
- export declare function translateHostEnvironmentFault(error: unknown, operation: string, options?: HostEnvironmentBoundaryOptions): PmCliError | null;
26
- /** Run one SDK filesystem stage and translate only declared host faults. */
27
- export declare function withHostEnvironmentBoundary<T>(operation: string, run: () => Promise<T>, options?: HostEnvironmentBoundaryOptions): Promise<T>;
1
+ /**
2
+ * @module sdk/host-environment-errors
3
+ * Public SDK compatibility surface for path-redacted host filesystem refusals.
4
+ */
5
+ export { classifyHostEnvironmentFault, translateHostEnvironmentFault, withHostEnvironmentBoundary, type HostEnvironmentBoundaryOptions, type HostEnvironmentFaultCodeOverrides, } from "../../core/fs/host-environment-errors.js";