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 +20 -0
- package/README.md +57 -6
- package/dist/context-usage.d.ts +166 -0
- package/dist/context-usage.d.ts.map +1 -0
- package/dist/context-usage.js +331 -0
- package/dist/context-usage.js.map +1 -0
- package/dist/index.d.ts +125 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +435 -23
- package/dist/index.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +6 -5
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
|
|
31
|
-
|
|
32
|
-
|
|
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.
|
|
77
|
-
|
|
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":"AAoCA,OAAO,KAAK,EAA2B,iBAAiB,EAA0B,MAAM,uBAAuB,CAAC;AAEhH,mFAAmF;AACnF,eAAO,MAAM,oBAAoB,QAAyC,CAAC;AA2B3E,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,yEAAyE;AACzE,MAAM,WAAW,sBAAsB;IACrC,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,qEAAqE;IACrE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,sEAAsE;IACtE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2EAA2E;IAC3E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;uFACuF;AACvF,MAAM,WAAW,2BAA2B;IAC1C,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,wDAAwD;IACxD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,oFAAoF;IACpF,cAAc,EAAE,OAAO,CAAC;IACxB,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,oBAAoB,EAAE,MAAM,CAAC;IAC7B,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,uEAAuE;IACvE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,gFAAgF;IAChF,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,2BAA2B,CAAC;CACxC;AAED,oEAAoE;AACpE,MAAM,WAAW,yBAAyB;IACxC,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,GAAG,IAAI,CAOjF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CA2BtE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAa/G;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,OAAO,GAAE,yBAA8B,GACtC,IAAI,CAAC,kBAAkB,EAAE,gBAAgB,GAAG,sBAAsB,CAAC,CA6FrE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B,GAAG,kBAAkB,CAG9G;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAiEpE"}
|
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,mFAAmF;AACnF,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,cAAc,GAAuC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAE/E;;;;;;;;;;GAUG;AACH,SAAS,oBAAoB,CAAC,KAAa;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC;AAC3E,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAyGvC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,GAAG,GAAS,IAAI,IAAI,EAAE;IAChE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,QAAQ,GAAG,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,QAAQ;QAAE,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9F,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;AACtE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC/G,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACnF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IACrG,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7E,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9E,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,IAAI,GAA6B,EAAE,CAAC;IAC1C,KAAK,MAAM,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACrE,MAAM,KAAK,GAAG,SAAoC,CAAC;QACnD,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACvH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;AAChG,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IAC3E,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,GAAG,IAAI,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YACzB,SAAS,IAAI,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAoC,EACpC,OAAO,GAA8B,EAAE;IAEvC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,oBAAoB,CAAC;IACpD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QACvC,IAAI,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC;QACpD,IAAI,KAAK,IAAI,KAAK,CAAC,EAAE,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QAC7C,IAAI,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO;YAAE,OAAO,KAAK,CAAC;QACjF,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkC,CAAC;IACxD,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAoB,CAAC;IACvD,MAAM,SAAS,GAAoB,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,EAAE,GAAkB,IAAI,CAAC;IAE7B,MAAM,QAAQ,GAAG,CAAC,EAAU,EAA0B,EAAE;QACtD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,OAAO,GAA2B;YACtC,EAAE;YACF,MAAM,EAAE,CAAC;YACT,WAAW,EAAE,CAAC;YACd,MAAM,EAAE,CAAC;YACT,OAAO,EAAE,CAAC;YACV,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,IAAI;YACpB,eAAe,EAAE,IAAI;YACrB,OAAO,EAAE,EAAE;SACZ,CAAC;QACF,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACvB,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QACtD,IAAI,EAAE,KAAK,IAAI,IAAI,KAAK,CAAC,EAAE,GAAG,EAAE;YAAE,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;QAChD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,WAAW,IAAI,CAAC,CAAC;YACjB,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACtC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YACnB,IAAI,KAAK,CAAC,eAAe,KAAK,IAAI,IAAI,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,eAAe;gBAAE,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,EAAE,CAAC;YACzG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;gBAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC5E,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,MAAM,SAAS,KAAK,CAAC,OAAO,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1C,IAAI,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;;gBAC3B,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7C,SAAS;QACX,CAAC;QACD,WAAW,IAAI,CAAC,CAAC;QACjB,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC/B,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;YAClB,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,SAAS;gBAAE,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC;YACvF,2FAA2F;YAC3F,IAAI,CAAC,GAAG,CAAC,QAAQ;gBAAE,SAAS;YAC5B,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;YAClB,IAAI,KAAK,CAAC,cAAc,KAAK,IAAI,IAAI,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,cAAc;gBAAE,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,EAAE,CAAC;YACtG,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IAED,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,SAAS,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/E,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;YAAE,SAAS;QACrD,SAAS,IAAI,CAAC,CAAC;QACf,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC/C,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpF,KAAK,MAAM,KAAK,IAAI,MAAM;QAAE,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAEjD,OAAO;QACL,WAAW,EAAE,QAAQ,CAAC,MAAM;QAC5B,iBAAiB,EAAE,WAAW;QAC9B,iBAAiB,EAAE,WAAW;QAC9B,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE;QAC5B,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE;QAC9B,IAAI;QACJ,EAAE;QACF,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC1C,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,WAAW,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;QACrG,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;QAClG,eAAe,EAAE,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,MAAM;KAC9E,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,OAAO,GAA8B,EAAE;IACxF,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;AAC5G,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA0B;IAC1D,MAAM,KAAK,GAAa,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CACR,6CAA6C,EAC7C,EAAE,EACF,sEAAsE,EACtE,EAAE,CACH,CAAC;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACvG,KAAK,CAAC,IAAI,CACR,oCAAoC,IAAI,IAAI,EAC5C,aAAa,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,iBAAiB,WAAW,MAAM,CAAC,iBAAiB,SAAS,EACxG,aAAa,MAAM,CAAC,IAAI,IAAI,KAAK,OAAO,MAAM,CAAC,EAAE,IAAI,KAAK,EAAE,EAC5D,cAAc,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAC7E,eAAe,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CACjF,CAAC;IACF,IAAI,MAAM,CAAC,oBAAoB,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,8BAA8B,MAAM,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAC7G,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CACR,kCAAkC,EAClC,EAAE,EACF,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EACnF,EAAE,EACF,mCAAmC,EACnC,EAAE,EACF,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EACrF,EAAE,CACH,CAAC;IACF,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CACR,UAAU,EACV,EAAE,EACF,qGAAqG,EACrG,8DAA8D,CAC/D,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CACR,KAAK,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC,MAAM,MAAM,KAAK,CAAC,MAAM,MAAM,KAAK,CAAC,WAAW,MAAM,KAAK,CAAC,OAAO,KAAK;gBAChG,GAAG,KAAK,CAAC,SAAS,IAAI,GAAG,MAAM,KAAK,CAAC,cAAc,IAAI,GAAG,MAAM,KAAK,CAAC,eAAe,IAAI,GAAG,KAAK;gBACjG,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CACvC,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;aACrD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAK,CAAC,IAAI,CACR,kCAAkC,EAClC,EAAE,EACF,yBAAyB,MAAM,CAAC,QAAQ,CAAC,kBAAkB,EAAE,EAC7D,qBAAqB,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,EACrD,EAAE,CACH,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC;QAC/C,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,OAAO;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|