pm-context 2026.7.25 → 2026.7.27

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.7.27 - 2026-07-27
4
+
5
+ ### Added
6
+
7
+ - Adopt sdk/query context engine and drop spawnSync shell-outs ([pm-context-lk82](https://github.com/unbraind/pm-context/blob/main/.agents/pm/features/pm-context-lk82.toon))
8
+
9
+ ### Removed
10
+
11
+ - Adopt pm-cli 2026.7.26 typed authoring contracts and remove the any-cast defineExtension shim ([pm-context-q67i](https://github.com/unbraind/pm-context/blob/main/.agents/pm/tasks/pm-context-q67i.toon))
12
+
13
+ ### Other
14
+
15
+ - Exclude generated dist output from DeepScan static analysis ([pm-context-01lr](https://github.com/unbraind/pm-context/blob/main/.agents/pm/chores/pm-context-01lr.toon))
16
+
17
+ ## 2026.7.26 - 2026-07-26
18
+
19
+ ### Other
20
+
21
+ - Enable governance duplicate-detection advisory mode and adopt pm-cli 2026.7.25 ([pm-context-x5qt](https://github.com/unbraind/pm-context/blob/main/.agents/pm/chores/pm-context-x5qt.toon))
22
+
3
23
  ## 2026.7.23 - 2026-07-23
4
24
 
5
25
  ### Fixed
package/README.md CHANGED
@@ -27,9 +27,11 @@ pm context-pack --id pm-1234 --include-deps --section focus --section blockers
27
27
  pm context-pack --id pm-1234 --max-items 10
28
28
  ```
29
29
 
30
- The command is read-only. It shells out to the active `pm` binary for
31
- workspace data, so generated packs reflect the installed CLI and package
32
- runtime rather than a stale parser.
30
+ The command reads workspace data in-process through the typed pm SDK, so packs
31
+ use the installed CLI's canonical query and relevance engines rather than a
32
+ second parser. Normal pack and handoff runs record one best-effort context-usage
33
+ serving event for the emitted items when an author is available; `--explain`
34
+ does not write to the usage ledger.
33
35
 
34
36
  ## Output
35
37
 
@@ -73,8 +75,10 @@ section is rendered in markdown/agent output when present.
73
75
  ### Max items
74
76
 
75
77
  `--max-items <n>` caps the total number of items (focus + neighbors) in the
76
- pack. Focus items take priority; neighbors are trimmed to fit the remaining
77
- budget.
78
+ pack. The SDK packer first selects candidates under a token budget with
79
+ projection degradation, preserving required focus items and relevance-ranked
80
+ neighbors; `--max-items` is then enforced as an additional hard item-count
81
+ ceiling.
78
82
 
79
83
  ## Command
80
84
 
@@ -106,16 +110,63 @@ Options:
106
110
  - `--compress` minimize output tokens (compact JSON, no blank lines)
107
111
  - `--include-deps` include per-item dependency info in the context pack
108
112
  - `--max-items <n>` maximum total items (focus + neighbors) in the pack
113
+ - `--explain` explain the exact focus and neighborhood items that the normal
114
+ selection and packing path would emit, without recording a serving event;
115
+ reported ranks and scores are relative to that emitted pack, not the workspace
109
116
  - `--section <section>` include only specific sections (repeatable):
110
117
  `summary`, `focus`, `neighborhood`, `neighbors`, `links`, `deps`,
111
118
  `blockers`, `next-actions` (alias: `actions`), `recent` (alias: `activity`),
112
119
  `refresh`
113
120
 
121
+ `pm context-usage`
122
+
123
+ Reports which items `pm context` and `pm next` served, and which of those were
124
+ actually touched afterwards — the feedback half of "project management = context
125
+ management".
126
+
127
+ pm maintains an append-only ledger at `<pm_root>/runtime/context-usage.jsonl`
128
+ (a `serve` row per ranking, a `touch` row per mutation) and folds it into the
129
+ `usage_affinity` signal of its built-in relevance model. Nothing surfaces the
130
+ ledger itself, so an agent cannot ask *what was I shown, and did I use it?*
131
+ This command answers that:
132
+
133
+ - **conversion** — share of served items followed by a same-author touch
134
+ - **waste** — served but never touched afterwards: context you paid tokens for and did not use
135
+ - **misses** — touched but never served: work the ranking failed to surface
136
+
137
+ An item counts as *served* only when pm marked it `included` — it actually made
138
+ the pack. The per-item `ranked` column counts every appearance, so
139
+ `ranked - serves` is how often the item lost to the token budget rather than to
140
+ the ranking: tune the budget, not the query. An item that was only ever ranked
141
+ and cut is never reported as waste, because the agent never saw it.
142
+
143
+ ```bash
144
+ pm context-usage # markdown brief
145
+ pm context-usage --json # raw report
146
+ pm context-usage --surface next --since 7d
147
+ pm context-usage --author agent-a --limit 50
148
+ ```
149
+
150
+ Options:
151
+
152
+ - `--author <author>` restrict to one recording author
153
+ - `--surface <context|next>` restrict serve events to one surface
154
+ - `--since <when>` drop events at or before an ISO timestamp or a day offset (`7d`, `-7d`, `7`)
155
+ - `--limit <n>` maximum per-item rows (default `20`)
156
+ - `--format <markdown|json>` output format (default `markdown`); pm's global `--json` also selects JSON
157
+
158
+ The command is strictly read-only. The ledger is pm's file, with pm's schema and
159
+ pm's pruning, and it deliberately reports only what the ledger states directly —
160
+ it does **not** compute a competing affinity score, because pm owns the decay
161
+ model and a second implementation could silently disagree with the ranking it
162
+ purports to explain.
163
+
114
164
  ## Philosophy
115
165
 
116
166
  Project management is context management. `pm-context` makes that concrete by
117
167
  turning pm's source-of-truth items into portable context that can be reviewed,
118
- sent to another agent, or attached to a pull request.
168
+ sent to another agent, or attached to a pull request — and, with
169
+ `pm context-usage`, by measuring whether that context was worth sending.
119
170
 
120
171
  ## Multi-agent merge safety
121
172
 
@@ -0,0 +1,166 @@
1
+ import type { ContextUsageEvent } from "@unbrained/pm-cli/sdk";
2
+ /** Ledger location relative to the tracker root, owned and pruned by pm itself. */
3
+ export declare const LEDGER_RELATIVE_PATH: string;
4
+ /** Default number of per-item rows rendered before the report truncates. */
5
+ export declare const DEFAULT_REPORT_LIMIT = 20;
6
+ /** Per-item rollup of every retained ledger row referencing one item. */
7
+ export interface ContextUsageItemReport {
8
+ /** Item identifier as recorded by the ledger. */
9
+ id: string;
10
+ /**
11
+ * Serve rows that actually put this item in the pack (`included: true`).
12
+ *
13
+ * Only these count as "shown", so only these can be wasted. An item pm ranked
14
+ * but cut from the pack was never in front of the agent.
15
+ */
16
+ serves: number;
17
+ /**
18
+ * Serve rows that ranked this item at all, whether or not it made the pack.
19
+ *
20
+ * `ranked - serves` is the number of times the item lost to the token budget,
21
+ * which is the signal for tuning that budget rather than the ranking.
22
+ */
23
+ ranked: number;
24
+ /** Serve rows for this item followed by a same-author touch. */
25
+ conversions: number;
26
+ /** Total touch rows for this item, whether or not a serve preceded them. */
27
+ touches: number;
28
+ /** Best (lowest) rank the item was ever served at, or null when never served. */
29
+ best_rank: number | null;
30
+ /** Timestamp of the most recent serve, or null when never served. */
31
+ last_served_at: string | null;
32
+ /** Timestamp of the most recent touch, or null when never touched. */
33
+ last_touched_at: string | null;
34
+ /** Distinct touch intents observed, sorted, e.g. `["close", "create"]`. */
35
+ intents: string[];
36
+ }
37
+ /** Decay-aware served-then-touched affinity for one author, sourced from the
38
+ * SDK context-usage store (`readContextUsageAffinity`) rather than recomputed here. */
39
+ export interface ContextUsageAffinitySummary {
40
+ /** Normalized affinity by item id. */
41
+ affinity: Record<string, number>;
42
+ /** Number of eligible served-then-touched judgments. */
43
+ positive_judgments: number;
44
+ /** Number of retained serving events inspected. */
45
+ serving_events: number;
46
+ }
47
+ /** Complete read-only view of the ledger for one filter selection. */
48
+ export interface ContextUsageReport {
49
+ /** Whether the ledger file exists; false means pm has not served or mutated yet. */
50
+ ledger_present: boolean;
51
+ /** Rows that parsed into a recognized event, after filtering. */
52
+ event_count: number;
53
+ /** Rows that could not be parsed; pm truncates on prune, so a tail row can be partial. */
54
+ malformed_line_count: number;
55
+ /** Serve events retained after filtering. */
56
+ serve_event_count: number;
57
+ /** Touch events retained after filtering. */
58
+ touch_event_count: number;
59
+ /** Distinct authors present, sorted. */
60
+ authors: string[];
61
+ /** Distinct surfaces present, sorted; `context` and/or `next`. */
62
+ surfaces: string[];
63
+ /** Earliest retained event timestamp, or null when no events matched. */
64
+ from: string | null;
65
+ /** Latest retained event timestamp, or null when no events matched. */
66
+ to: string | null;
67
+ /** Per-item rollups, ordered by serves desc, then conversions desc, then id. */
68
+ items: ContextUsageItemReport[];
69
+ /** Items served but never touched afterwards — context paid for and unused. */
70
+ waste: string[];
71
+ /** Items touched but never served — work the ranking failed to surface. */
72
+ misses: string[];
73
+ /**
74
+ * Fraction of included serve judgments followed by a same-author touch, or
75
+ * null when no judgments exist. This is serve-then-touch conversion, not pm's
76
+ * `usage_affinity`, which additionally applies recency decay.
77
+ */
78
+ conversion_rate: number | null;
79
+ /**
80
+ * Optional decay-aware affinity for the selected author, supplied by the SDK
81
+ * context-usage store when `--author` is set. Absent for unfiltered reports
82
+ * and for the pure {@link reportContextUsage} path.
83
+ */
84
+ affinity?: ContextUsageAffinitySummary;
85
+ }
86
+ /** Filters narrowing which ledger rows a report is derived from. */
87
+ export interface ContextUsageReportOptions {
88
+ /** Restrict to one recording author. */
89
+ author?: string;
90
+ /** Restrict to one serving surface; touch rows carry no surface and are kept. */
91
+ surface?: string;
92
+ /** Drop events at or before this ISO timestamp. */
93
+ since?: string;
94
+ /** Maximum per-item rows retained in {@link ContextUsageReport.items}. */
95
+ limit?: number;
96
+ }
97
+ /**
98
+ * Resolve a `--since` value into an ISO timestamp.
99
+ *
100
+ * Accepts a full ISO timestamp, or a relative day offset written as `7d`,
101
+ * `-7d`, or `7`. pm's own `--from` silently yields an empty window for an
102
+ * unsigned offset (upstream pm-cli#651); both signs are accepted here because
103
+ * "7 days ago" and "-7 days" are the same request from an agent.
104
+ *
105
+ * @param value - Raw flag text.
106
+ * @param now - Clock used to resolve relative offsets, for deterministic tests.
107
+ * @returns The resolved ISO timestamp, or null when the value is unparseable.
108
+ */
109
+ export declare function resolveSince(value: string, now?: Date): string | null;
110
+ /**
111
+ * Decode one ledger line into a recognized event.
112
+ *
113
+ * Validates the discriminant and every field the report reads, so a row written
114
+ * by a future pm version — or a partial tail row left by a prune — is reported
115
+ * as malformed rather than silently contributing zeroes to the metrics.
116
+ *
117
+ * @param line - Raw JSONL line, assumed already trimmed of surrounding space.
118
+ * @returns The decoded event, or null when the line is absent or malformed.
119
+ */
120
+ export declare function parseLedgerLine(line: string): ContextUsageEvent | null;
121
+ /**
122
+ * Read and decode every retained ledger row for a tracker.
123
+ *
124
+ * @param pmRoot - Tracker root containing the `runtime` directory.
125
+ * @returns Decoded events in file order plus the count of unparseable lines,
126
+ * and `present: false` when pm has not written a ledger yet.
127
+ */
128
+ export declare function readLedger(pmRoot: string): {
129
+ present: boolean;
130
+ events: ContextUsageEvent[];
131
+ malformed: number;
132
+ };
133
+ /**
134
+ * Roll decoded ledger events up into a usage report.
135
+ *
136
+ * A serve judgment converts when the same author touches the served item at a
137
+ * later timestamp. Only rows pm marked `included` are judged: a candidate that
138
+ * was ranked but cut from the pack was never shown, so failing to touch it is
139
+ * not waste. Touch matching uses each item's latest touch per author, which is
140
+ * sufficient because conversion asks only whether *any* later touch exists.
141
+ *
142
+ * @param events - Decoded events, in any order; ordering is derived internally.
143
+ * @param options - Author, surface, since, and limit filters.
144
+ * @returns The rolled-up report over the filtered event set.
145
+ */
146
+ export declare function buildUsageReport(events: readonly ContextUsageEvent[], options?: ContextUsageReportOptions): Omit<ContextUsageReport, "ledger_present" | "malformed_line_count">;
147
+ /**
148
+ * Read a tracker's ledger and roll it up in one call.
149
+ *
150
+ * @param pmRoot - Tracker root containing the `runtime` directory.
151
+ * @param options - Author, surface, since, and limit filters.
152
+ * @returns The complete report, with `ledger_present: false` and zeroed
153
+ * counters when pm has not written a ledger yet.
154
+ */
155
+ export declare function reportContextUsage(pmRoot: string, options?: ContextUsageReportOptions): ContextUsageReport;
156
+ /**
157
+ * Render a report as an agent-readable Markdown brief.
158
+ *
159
+ * Leads with conversion rate and the two failure lists, because those are the
160
+ * only parts an agent acts on; the per-item table is supporting detail.
161
+ *
162
+ * @param report - Report produced by {@link reportContextUsage}.
163
+ * @returns Markdown text ending in a newline.
164
+ */
165
+ export declare function renderUsageReport(report: ContextUsageReport): string;
166
+ //# sourceMappingURL=context-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-usage.d.ts","sourceRoot":"","sources":["../context-usage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,331 @@
1
+ /**
2
+ * @module context-usage
3
+ *
4
+ * Reports on pm's own context-usage ledger: which items `pm context` and
5
+ * `pm next` served, and which of those the agent subsequently touched.
6
+ *
7
+ * pm 2026.7.25 maintains an append-only ledger at
8
+ * `<pm_root>/runtime/context-usage.jsonl`, writing a `serve` row whenever
9
+ * `pm context` / `pm next` rank candidates and a `touch` row whenever a command
10
+ * mutates an item. The CLI folds that ledger into the `usage_affinity` signal of
11
+ * its built-in relevance model, but nothing surfaces the ledger itself — an
12
+ * agent cannot ask "what was I shown, and did I actually use it?".
13
+ *
14
+ * This module answers exactly that, and deliberately stops there. It derives
15
+ * only facts the ledger states directly — serve counts, touch counts, and
16
+ * serve-then-touch conversion — and does **not** compute a competing affinity
17
+ * score. pm owns the decay model; a second implementation of it here could
18
+ * silently disagree with the ranking it purports to explain.
19
+ *
20
+ * Two failure modes fall out of the conversion analysis and are the practical
21
+ * payload of the report:
22
+ *
23
+ * - **waste** — items pm served that were never touched afterwards. Every one
24
+ * is context an agent paid tokens for and did not use.
25
+ * - **misses** — items an agent touched that pm never served. Every one is work
26
+ * the ranking failed to surface.
27
+ *
28
+ * The whole surface is read-only. The ledger is pm's file, with pm's schema and
29
+ * pm's pruning; writing to it from here would corrupt the very signal being
30
+ * reported on. {@link ContextUsageEvent} is imported as a type so the parser is
31
+ * checked against the SDK's real contract while adding no runtime dependency —
32
+ * installed extensions cannot resolve `@unbrained/pm-cli` at runtime
33
+ * (upstream pm-cli#717).
34
+ */
35
+ import { existsSync, readFileSync } from "node:fs";
36
+ import { join } from "node:path";
37
+ /** Ledger location relative to the tracker root, owned and pruned by pm itself. */
38
+ export const LEDGER_RELATIVE_PATH = join("runtime", "context-usage.jsonl");
39
+ /**
40
+ * Serving surfaces recognized by the parser.
41
+ *
42
+ * Typed against the SDK union so it cannot drift into an invalid value. A row
43
+ * naming a surface pm adds later decodes as malformed, which the report states
44
+ * explicitly — preferable to admitting it and skewing conversion silently.
45
+ */
46
+ const SERVE_SURFACES = ["context", "next"];
47
+ /**
48
+ * Whether a timestamp is in canonical `Date.prototype.toISOString` form.
49
+ *
50
+ * Every window filter, report bound, "latest" wins, and conversion check in this
51
+ * module compares `at` values as strings. That is only sound for a fixed-width
52
+ * representation: `"2026-07-01T00:00:00.500Z"` sorts *before*
53
+ * `"2026-07-01T00:00:00Z"` lexically, because `.` precedes `Z`, even though it
54
+ * is the later instant. Requiring the canonical form — which is what pm writes —
55
+ * makes string order equal chronological order, so any other spelling is
56
+ * reported as malformed instead of silently skewing the metrics.
57
+ */
58
+ function isCanonicalTimestamp(value) {
59
+ const parsed = Date.parse(value);
60
+ return !Number.isNaN(parsed) && new Date(parsed).toISOString() === value;
61
+ }
62
+ /** Default number of per-item rows rendered before the report truncates. */
63
+ export const DEFAULT_REPORT_LIMIT = 20;
64
+ /**
65
+ * Resolve a `--since` value into an ISO timestamp.
66
+ *
67
+ * Accepts a full ISO timestamp, or a relative day offset written as `7d`,
68
+ * `-7d`, or `7`. pm's own `--from` silently yields an empty window for an
69
+ * unsigned offset (upstream pm-cli#651); both signs are accepted here because
70
+ * "7 days ago" and "-7 days" are the same request from an agent.
71
+ *
72
+ * @param value - Raw flag text.
73
+ * @param now - Clock used to resolve relative offsets, for deterministic tests.
74
+ * @returns The resolved ISO timestamp, or null when the value is unparseable.
75
+ */
76
+ export function resolveSince(value, now = new Date()) {
77
+ const trimmed = value.trim();
78
+ if (!trimmed)
79
+ return null;
80
+ const relative = /^-?(\d+(?:\.\d+)?)d?$/i.exec(trimmed);
81
+ if (relative)
82
+ return new Date(now.getTime() - Number(relative[1]) * 86_400_000).toISOString();
83
+ const parsed = Date.parse(trimmed);
84
+ return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
85
+ }
86
+ /**
87
+ * Decode one ledger line into a recognized event.
88
+ *
89
+ * Validates the discriminant and every field the report reads, so a row written
90
+ * by a future pm version — or a partial tail row left by a prune — is reported
91
+ * as malformed rather than silently contributing zeroes to the metrics.
92
+ *
93
+ * @param line - Raw JSONL line, assumed already trimmed of surrounding space.
94
+ * @returns The decoded event, or null when the line is absent or malformed.
95
+ */
96
+ export function parseLedgerLine(line) {
97
+ if (!line)
98
+ return null;
99
+ let parsed;
100
+ try {
101
+ parsed = JSON.parse(line);
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ if (typeof parsed !== "object" || parsed === null)
107
+ return null;
108
+ const row = parsed;
109
+ if (typeof row.at !== "string" || !isCanonicalTimestamp(row.at) || typeof row.author !== "string")
110
+ return null;
111
+ if (row.kind === "touch") {
112
+ if (typeof row.item_id !== "string" || typeof row.intent !== "string")
113
+ return null;
114
+ return { kind: "touch", at: row.at, author: row.author, item_id: row.item_id, intent: row.intent };
115
+ }
116
+ if (row.kind !== "serve")
117
+ return null;
118
+ if (typeof row.profile !== "string" || !Array.isArray(row.rows))
119
+ return null;
120
+ const surface = SERVE_SURFACES.find((candidate) => candidate === row.surface);
121
+ if (!surface)
122
+ return null;
123
+ const rows = [];
124
+ for (const candidate of row.rows) {
125
+ if (typeof candidate !== "object" || candidate === null)
126
+ return null;
127
+ const entry = candidate;
128
+ if (typeof entry.id !== "string" || typeof entry.rank !== "number" || typeof entry.included !== "boolean")
129
+ return null;
130
+ rows.push({ id: entry.id, rank: entry.rank, included: entry.included });
131
+ }
132
+ return { kind: "serve", at: row.at, author: row.author, surface, profile: row.profile, rows };
133
+ }
134
+ /**
135
+ * Read and decode every retained ledger row for a tracker.
136
+ *
137
+ * @param pmRoot - Tracker root containing the `runtime` directory.
138
+ * @returns Decoded events in file order plus the count of unparseable lines,
139
+ * and `present: false` when pm has not written a ledger yet.
140
+ */
141
+ export function readLedger(pmRoot) {
142
+ const path = join(pmRoot, LEDGER_RELATIVE_PATH);
143
+ if (!existsSync(path))
144
+ return { present: false, events: [], malformed: 0 };
145
+ const events = [];
146
+ let malformed = 0;
147
+ for (const raw of readFileSync(path, "utf-8").split("\n")) {
148
+ const line = raw.trim();
149
+ if (!line)
150
+ continue;
151
+ const event = parseLedgerLine(line);
152
+ if (event)
153
+ events.push(event);
154
+ else
155
+ malformed += 1;
156
+ }
157
+ return { present: true, events, malformed };
158
+ }
159
+ /**
160
+ * Roll decoded ledger events up into a usage report.
161
+ *
162
+ * A serve judgment converts when the same author touches the served item at a
163
+ * later timestamp. Only rows pm marked `included` are judged: a candidate that
164
+ * was ranked but cut from the pack was never shown, so failing to touch it is
165
+ * not waste. Touch matching uses each item's latest touch per author, which is
166
+ * sufficient because conversion asks only whether *any* later touch exists.
167
+ *
168
+ * @param events - Decoded events, in any order; ordering is derived internally.
169
+ * @param options - Author, surface, since, and limit filters.
170
+ * @returns The rolled-up report over the filtered event set.
171
+ */
172
+ export function buildUsageReport(events, options = {}) {
173
+ const { author, surface, since } = options;
174
+ const limit = options.limit ?? DEFAULT_REPORT_LIMIT;
175
+ const selected = events.filter((event) => {
176
+ if (author && event.author !== author)
177
+ return false;
178
+ if (since && event.at <= since)
179
+ return false;
180
+ if (surface && event.kind === "serve" && event.surface !== surface)
181
+ return false;
182
+ return true;
183
+ });
184
+ const items = new Map();
185
+ const touchTimesByAuthor = new Map();
186
+ const judgments = [];
187
+ const authors = new Set();
188
+ const surfaces = new Set();
189
+ let serveEvents = 0;
190
+ let touchEvents = 0;
191
+ let from = null;
192
+ let to = null;
193
+ const entryFor = (id) => {
194
+ const existing = items.get(id);
195
+ if (existing)
196
+ return existing;
197
+ const created = {
198
+ id,
199
+ serves: 0,
200
+ conversions: 0,
201
+ ranked: 0,
202
+ touches: 0,
203
+ best_rank: null,
204
+ last_served_at: null,
205
+ last_touched_at: null,
206
+ intents: [],
207
+ };
208
+ items.set(id, created);
209
+ return created;
210
+ };
211
+ for (const event of selected) {
212
+ authors.add(event.author);
213
+ if (from === null || event.at < from)
214
+ from = event.at;
215
+ if (to === null || event.at > to)
216
+ to = event.at;
217
+ if (event.kind === "touch") {
218
+ touchEvents += 1;
219
+ const entry = entryFor(event.item_id);
220
+ entry.touches += 1;
221
+ if (entry.last_touched_at === null || event.at > entry.last_touched_at)
222
+ entry.last_touched_at = event.at;
223
+ if (!entry.intents.includes(event.intent))
224
+ entry.intents.push(event.intent);
225
+ const key = `${event.author}\u0000${event.item_id}`;
226
+ const times = touchTimesByAuthor.get(key);
227
+ if (times)
228
+ times.push(event.at);
229
+ else
230
+ touchTimesByAuthor.set(key, [event.at]);
231
+ continue;
232
+ }
233
+ serveEvents += 1;
234
+ surfaces.add(event.surface);
235
+ for (const row of event.rows) {
236
+ const entry = entryFor(row.id);
237
+ entry.ranked += 1;
238
+ if (entry.best_rank === null || row.rank < entry.best_rank)
239
+ entry.best_rank = row.rank;
240
+ // Only an included row was actually shown, so only it can be served, wasted, or converted.
241
+ if (!row.included)
242
+ continue;
243
+ entry.serves += 1;
244
+ if (entry.last_served_at === null || event.at > entry.last_served_at)
245
+ entry.last_served_at = event.at;
246
+ judgments.push({ id: row.id, rank: row.rank, at: event.at, author: event.author });
247
+ }
248
+ }
249
+ let converted = 0;
250
+ for (const judgment of judgments) {
251
+ const times = touchTimesByAuthor.get(`${judgment.author}\u0000${judgment.id}`);
252
+ if (!times?.some((at) => at > judgment.at))
253
+ continue;
254
+ converted += 1;
255
+ entryFor(judgment.id).conversions += 1;
256
+ }
257
+ const ranked = [...items.values()].sort((a, b) => b.serves - a.serves || b.conversions - a.conversions || a.id.localeCompare(b.id));
258
+ for (const entry of ranked)
259
+ entry.intents.sort();
260
+ return {
261
+ event_count: selected.length,
262
+ serve_event_count: serveEvents,
263
+ touch_event_count: touchEvents,
264
+ authors: [...authors].sort(),
265
+ surfaces: [...surfaces].sort(),
266
+ from,
267
+ to,
268
+ items: ranked.slice(0, Math.max(0, limit)),
269
+ waste: ranked.filter((entry) => entry.serves > 0 && entry.conversions === 0).map((entry) => entry.id),
270
+ misses: ranked.filter((entry) => entry.serves === 0 && entry.touches > 0).map((entry) => entry.id),
271
+ conversion_rate: judgments.length === 0 ? null : converted / judgments.length,
272
+ };
273
+ }
274
+ /**
275
+ * Read a tracker's ledger and roll it up in one call.
276
+ *
277
+ * @param pmRoot - Tracker root containing the `runtime` directory.
278
+ * @param options - Author, surface, since, and limit filters.
279
+ * @returns The complete report, with `ledger_present: false` and zeroed
280
+ * counters when pm has not written a ledger yet.
281
+ */
282
+ export function reportContextUsage(pmRoot, options = {}) {
283
+ const { present, events, malformed } = readLedger(pmRoot);
284
+ return { ledger_present: present, malformed_line_count: malformed, ...buildUsageReport(events, options) };
285
+ }
286
+ /**
287
+ * Render a report as an agent-readable Markdown brief.
288
+ *
289
+ * Leads with conversion rate and the two failure lists, because those are the
290
+ * only parts an agent acts on; the per-item table is supporting detail.
291
+ *
292
+ * @param report - Report produced by {@link reportContextUsage}.
293
+ * @returns Markdown text ending in a newline.
294
+ */
295
+ export function renderUsageReport(report) {
296
+ const lines = ["# Context usage", ""];
297
+ if (!report.ledger_present) {
298
+ lines.push("No ledger at `runtime/context-usage.jsonl`.", "", "pm writes it on the first `pm context`, `pm next`, or item mutation.", "");
299
+ return lines.join("\n");
300
+ }
301
+ const rate = report.conversion_rate === null ? "n/a" : `${(report.conversion_rate * 100).toFixed(1)}%`;
302
+ lines.push(`- serve-then-touch conversion: **${rate}**`, `- events: ${report.event_count} (${report.serve_event_count} serve, ${report.touch_event_count} touch)`, `- window: ${report.from ?? "n/a"} .. ${report.to ?? "n/a"}`, `- authors: ${report.authors.length > 0 ? report.authors.join(", ") : "n/a"}`, `- surfaces: ${report.surfaces.length > 0 ? report.surfaces.join(", ") : "n/a"}`);
303
+ if (report.malformed_line_count > 0)
304
+ lines.push(`- malformed lines skipped: ${report.malformed_line_count}`);
305
+ lines.push("");
306
+ lines.push("## Waste (served, never touched)", "", report.waste.length > 0 ? report.waste.map((id) => `- ${id}`).join("\n") : "_none_", "", "## Misses (touched, never served)", "", report.misses.length > 0 ? report.misses.map((id) => `- ${id}`).join("\n") : "_none_", "");
307
+ if (report.items.length > 0) {
308
+ lines.push("## Items", "", "| id | serves | ranked | conversions | touches | best rank | last served | last touched | intents |", "| --- | ---: | ---: | ---: | ---: | ---: | --- | --- | --- |");
309
+ for (const entry of report.items) {
310
+ lines.push(`| ${entry.id} | ${entry.serves} | ${entry.ranked} | ${entry.conversions} | ${entry.touches} | ` +
311
+ `${entry.best_rank ?? "-"} | ${entry.last_served_at ?? "-"} | ${entry.last_touched_at ?? "-"} | ` +
312
+ `${entry.intents.join(", ") || "-"} |`);
313
+ }
314
+ lines.push("");
315
+ }
316
+ if (report.affinity) {
317
+ const entries = Object.entries(report.affinity.affinity)
318
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
319
+ lines.push("## Author affinity (SDK decayed)", "", `- positive judgments: ${report.affinity.positive_judgments}`, `- serving events: ${report.affinity.serving_events}`, "");
320
+ if (entries.length === 0) {
321
+ lines.push("_no decayed affinity yet._", "");
322
+ }
323
+ else {
324
+ for (const [id, value] of entries)
325
+ lines.push(`- ${id}: ${value.toFixed(3)}`);
326
+ lines.push("");
327
+ }
328
+ }
329
+ return lines.join("\n");
330
+ }
331
+ //# sourceMappingURL=context-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-usage.js","sourceRoot":"","sources":["../context-usage.ts"],"names":[],"mappings":"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"}