pm-context 2026.10.21 → 2026.10.24
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 +24 -0
- package/README.md +83 -1
- package/dist/context-receipts.d.ts +9 -2
- package/dist/context-receipts.d.ts.map +1 -1
- package/dist/context-receipts.js +14 -9
- package/dist/context-receipts.js.map +1 -1
- package/dist/evidence-packing.d.ts +68 -0
- package/dist/evidence-packing.d.ts.map +1 -0
- package/dist/evidence-packing.js +146 -0
- package/dist/evidence-packing.js.map +1 -0
- package/dist/file-focus.d.ts +1 -1
- package/dist/file-focus.d.ts.map +1 -1
- package/dist/file-focus.js +7 -2
- package/dist/file-focus.js.map +1 -1
- package/dist/freshness.d.ts +82 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +299 -0
- package/dist/freshness.js.map +1 -0
- package/dist/index.d.ts +18 -72
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -100
- package/dist/index.js.map +1 -1
- package/dist/since.d.ts +105 -0
- package/dist/since.d.ts.map +1 -0
- package/dist/since.js +593 -0
- package/dist/since.js.map +1 -0
- package/dist/witnesses.d.ts +56 -0
- package/dist/witnesses.d.ts.map +1 -0
- package/dist/witnesses.js +121 -0
- package/dist/witnesses.js.map +1 -0
- package/manifest.json +2 -2
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import { packCriterionEvidence, encodeEvidencePack, readEvidencePacket, EvidencePackingError } from "./evidence-packing.js";
|
|
2
|
+
export { packCriterionEvidence, encodeEvidencePack, parseEvidencePacket, readEvidencePacket, EvidencePackingError } from "./evidence-packing.js";
|
|
3
|
+
import { createFreshnessReader, encodeFreshness, FreshnessError, parseFreshnessPacket, readFreshnessJson, recordFreshness, resumeFreshness } from "./freshness.js";
|
|
4
|
+
import { checkContextWitnesses, encodeWitnessReport, readWitnessPacket, WitnessError } from "./witnesses.js";
|
|
5
|
+
export { checkContextWitnesses, encodeWitnessReport, parseWitnessPacket, readWitnessPacket, WitnessError } from "./witnesses.js";
|
|
6
|
+
export { createFreshnessReader, encodeFreshness, FRESHNESS_LIMITS, FreshnessError, parseFreshnessPacket, readFreshnessFile, readFreshnessJson, recordFreshness, resumeFreshness } from "./freshness.js";
|
|
7
|
+
import { buildSincePack, encodeSincePack } from "./since.js";
|
|
1
8
|
import { writeFileSync } from "node:fs";
|
|
9
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
10
|
import { resolveFileFocus, matchFileFocus, rankFileFocus, renderFileFocus, readFileFocusItems } from "./file-focus.js";
|
|
3
11
|
import { createRelationshipKindRegistry, readSettings, resolveRuntimeStatusRegistry, resolveWorkspaceRelationshipKindRegistry, } from "@unbrained/pm-cli/sdk";
|
|
4
|
-
import {
|
|
5
|
-
import { buildItemContextRelevanceCandidates, defaultScoreContextCandidates, packContextCandidates, recordContextUsageServing, } from "@unbrained/pm-cli/sdk/query";
|
|
12
|
+
import { listAllComplete } from "@unbrained/pm-cli/sdk/core";
|
|
13
|
+
import { buildItemContextRelevanceCandidates, certifyCompleteListResult, PmCompleteListValidationError, defaultScoreContextCandidates, packContextCandidates, recordContextUsageServing, } from "@unbrained/pm-cli/sdk/query";
|
|
6
14
|
import { DEFAULT_REPORT_LIMIT, renderUsageReport, reportContextUsage, resolveSince } from "./context-usage.js";
|
|
7
15
|
import { readContextUsageAffinity } from "@unbrained/pm-cli/sdk/query";
|
|
8
16
|
import { recordReceiptUsage, reportContextReceipts, renderReceiptReport, serveContextReceipt } from "./context-receipts.js";
|
|
@@ -730,97 +738,23 @@ export function renderAgentHandoff(pack, options = {}) {
|
|
|
730
738
|
return output;
|
|
731
739
|
}
|
|
732
740
|
/**
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
* this package once built a context pack from 10 of 682 workspace items and
|
|
737
|
-
* reported success: pm 2026.8.14 defaulted the list to a truncated answer and
|
|
738
|
-
* nothing here checked. Any one of four independent signals means the rows in
|
|
739
|
-
* `items` are NOT the whole workspace, so this throws (never returns a partial
|
|
740
|
-
* list, never logs and continues) naming the signal that tripped plus the
|
|
741
|
-
* `count`/`total` figures:
|
|
742
|
-
*
|
|
743
|
-
* - `truncated === true` — the row list was cut short (output budget, limit);
|
|
744
|
-
* - `has_more === true` — rows exist past the returned cursor boundary;
|
|
745
|
-
* - `completeness.status !== "complete"` — items/directories were unreadable
|
|
746
|
-
* ("partial") or the corpus was never scanned ("unchecked"); either way the
|
|
747
|
-
* answer is not attested complete;
|
|
748
|
-
* - `omission_receipt.has_omissions === true` — field groups were dropped from
|
|
749
|
-
* the projection, so rows are present but degraded.
|
|
750
|
-
*
|
|
751
|
-
* Thrown errors are {@link CommandError} so the command runtime turns them into
|
|
752
|
-
* a clean nonzero exit. Paging is deliberately NOT attempted: a context pack
|
|
753
|
-
* silently built from a partial page is the failure mode this guard exists to
|
|
754
|
-
* prevent, so refusing loudly is simpler and safer.
|
|
755
|
-
*
|
|
756
|
-
* @param result - SDK list result (a result missing its receipt trips the
|
|
757
|
-
* completeness signal: an unverifiable answer is not complete).
|
|
758
|
-
* @throws {@link CommandError} naming the first tripped signal and the counts.
|
|
741
|
+
* Re-certify the full SDK envelope and require its producer's certificate.
|
|
742
|
+
* The SDK owns corpus and output invariants; this consumer also refuses a
|
|
743
|
+
* missing or inconsistent producer certificate before constructing a pack.
|
|
759
744
|
*/
|
|
760
745
|
export function assertListResultComplete(result) {
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
+ "The item list was cut short (output budget or limit); a context pack "
|
|
767
|
-
+ "built from it would silently miss items. Narrow the operation or "
|
|
768
|
-
+ "raise the output budget, then retry.");
|
|
769
|
-
}
|
|
770
|
-
if (result.has_more === true) {
|
|
771
|
-
throw new CommandError(`Refusing incomplete pm list-all result: has_more=true (${counts}). `
|
|
772
|
-
+ "Rows exist beyond the returned page; packing the page as the whole "
|
|
773
|
-
+ "workspace would silently drop them.");
|
|
774
|
-
}
|
|
775
|
-
if (result.completeness?.status !== "complete") {
|
|
776
|
-
const status = result.completeness?.status === undefined
|
|
777
|
-
? "(missing)"
|
|
778
|
-
: JSON.stringify(result.completeness.status);
|
|
779
|
-
const unreadable = `unreadable_item_count=${result.completeness?.unreadable_item_count ?? 0}`
|
|
780
|
-
+ `, unreadable_directory_count=${result.completeness?.unreadable_directory_count ?? 0}`;
|
|
781
|
-
throw new CommandError(`Refusing incomplete pm list-all result: completeness.status=${status} `
|
|
782
|
-
+ `(${unreadable}; ${counts}). The workspace corpus behind this list was `
|
|
783
|
-
+ "not attested completely read, so the rows are not the whole workspace.");
|
|
784
|
-
}
|
|
785
|
-
const omission = result.omission_receipt;
|
|
786
|
-
if (omission?.has_omissions === true) {
|
|
787
|
-
const groups = omission.omitted_field_groups ?? [];
|
|
788
|
-
throw new CommandError(`Refusing incomplete pm list-all result: omission_receipt.has_omissions=true `
|
|
789
|
-
+ `(omitted_field_groups: ${groups.length ? groups.join(", ") : "(none listed)"}; ${counts}). `
|
|
790
|
-
+ "Field groups were dropped from the projection, so the rows are present "
|
|
791
|
-
+ "but degraded; re-read without the field-omitting options.");
|
|
746
|
+
const certified = certifyCompleteListResult(result);
|
|
747
|
+
if (!isDeepStrictEqual(result.complete_list, certified.complete_list)) {
|
|
748
|
+
throw new PmCompleteListValidationError([{
|
|
749
|
+
code: "invalid_envelope", message: "Context input requires a matching complete_list certificate from listAllComplete.",
|
|
750
|
+
}]);
|
|
792
751
|
}
|
|
793
752
|
}
|
|
794
|
-
/**
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
* Replaces the previous `spawnSync("pm", ["list-all", "--json", "--include-body"])`
|
|
798
|
-
* shell-out with the typed {@link list} action from `@unbrained/pm-cli/sdk/core`.
|
|
799
|
-
* `list-all` is the SDK alias for `list` with `excludeTerminal: false`; passing
|
|
800
|
-
* `full: true` + `includeBody: true` reproduces the full-metadata-with-body
|
|
801
|
-
* projection the shell-out parsed. `noTruncate: true` deliberately overrides any
|
|
802
|
-
* truncating limit and `strictRead: true` makes unreadable corpus entries a hard
|
|
803
|
-
* SDK error, and the returned receipt is STILL verified by
|
|
804
|
-
* {@link assertListResultComplete} — the 2026.8.14 truncated-default regression
|
|
805
|
-
* is exactly the silently-partial read this package refuses to reintroduce, so
|
|
806
|
-
* the guard does not trust the request options alone. Being an in-process SDK
|
|
807
|
-
* action (no child process), there is no spawn stdout buffer to size here; the
|
|
808
|
-
* completeness receipt is the over-run guard.
|
|
809
|
-
*
|
|
810
|
-
* @param pmRoot - Tracker storage root to read.
|
|
811
|
-
* @param list - Injectable SDK list action (tests substitute a canned real
|
|
812
|
-
* result); defaults to the real {@link pmList}.
|
|
813
|
-
*/
|
|
814
|
-
export async function readPmItems(pmRoot, list = pmList) {
|
|
815
|
-
let result;
|
|
816
|
-
try {
|
|
817
|
-
result = await list({ full: true, includeBody: true, excludeTerminal: false, noTruncate: true, strictRead: true }, { pmRoot });
|
|
818
|
-
}
|
|
819
|
-
catch (err) {
|
|
820
|
-
throw new CommandError(`Could not read pm items via SDK list: ${err instanceof Error ? err.message : String(err)}`);
|
|
821
|
-
}
|
|
753
|
+
/** Read all types and lifecycle states without extension side effects, preserving SDK refusals. */
|
|
754
|
+
export async function readPmItems(pmRoot, list = listAllComplete) {
|
|
755
|
+
const result = await list({ includeBody: true }, { pmRoot, noExtensions: true });
|
|
822
756
|
assertListResultComplete(result);
|
|
823
|
-
return result.items
|
|
757
|
+
return result.items;
|
|
824
758
|
}
|
|
825
759
|
/** Per-item token estimate for one character of text (rough 4 chars/token). */
|
|
826
760
|
const TOKENS_PER_CHAR = 0.25;
|
|
@@ -936,21 +870,22 @@ function estimateProjectionCosts(item) {
|
|
|
936
870
|
* focus items are required anchors, neighbors compete by relevance rank for the
|
|
937
871
|
* remaining budget. The packer first selects under that token budget with pm's
|
|
938
872
|
* projection-degradation optimizer, then enforces the command's explicit item
|
|
939
|
-
* ceiling
|
|
873
|
+
* ceiling. The incoming focus order is authoritative (including file-overlap
|
|
874
|
+
* ranking); corpus ranks apply only to neighbors, so equal-cost focus ties keep
|
|
875
|
+
* the selection order established by buildContextPack.
|
|
940
876
|
*/
|
|
941
877
|
export function createSdkPacker(rankedFocus, rankedNeighbors) {
|
|
942
|
-
const focusRank = new Map(rankedFocus.map((item, index) => [item.id, index + 1]));
|
|
943
878
|
const neighborRank = new Map(rankedNeighbors.map((item, index) => [item.id, rankedFocus.length + index + 1]));
|
|
944
879
|
return (focus, neighbors, maxItems) => {
|
|
945
880
|
if (!(maxItems > 0))
|
|
946
881
|
return { focus, neighbors };
|
|
947
882
|
const tokenBudget = maxItems * TOKENS_PER_ITEM_SLOT;
|
|
948
883
|
const candidates = [
|
|
949
|
-
...focus.map((item) => ({
|
|
884
|
+
...focus.map((item, index) => ({
|
|
950
885
|
id: item.id,
|
|
951
886
|
item,
|
|
952
|
-
rank:
|
|
953
|
-
score: 1 / (
|
|
887
|
+
rank: index + 1,
|
|
888
|
+
score: 1 / (index + 1),
|
|
954
889
|
token_costs: estimateProjectionCosts(item),
|
|
955
890
|
required: true,
|
|
956
891
|
})),
|
|
@@ -1108,7 +1043,7 @@ async function recordPackServing(ctx, focus, neighbors) {
|
|
|
1108
1043
|
*/
|
|
1109
1044
|
async function measureContextOutput(ctx, pack, output, format, command, compress, outputPath) {
|
|
1110
1045
|
try {
|
|
1111
|
-
const measured = serveContextReceipt(ctx.pm_root, output, format === "json" ? "json" : "text", {
|
|
1046
|
+
const measured = await serveContextReceipt(ctx.pm_root, output, format === "json" ? "json" : "text", {
|
|
1112
1047
|
item_ids: [...pack.items, ...pack.neighbors].map((item) => item.id),
|
|
1113
1048
|
files: pack.links.filter((link) => link.kind === "file"),
|
|
1114
1049
|
}, {
|
|
@@ -1188,7 +1123,7 @@ function setupCommands(api) {
|
|
|
1188
1123
|
throw new CommandError("--format must be markdown, json, agent, or compact", EXIT_CODE.USAGE);
|
|
1189
1124
|
}
|
|
1190
1125
|
const selection = resolveSelectionOptions(options);
|
|
1191
|
-
const fileFocus = resolveFileFocus(options, ctx.args, ctx.repo_root ?? ctx.source_workspace_root ?? process.cwd(), ctx.pm_root);
|
|
1126
|
+
const fileFocus = resolveFileFocus(options, ctx.args, ctx.repo_root ?? ctx.source_workspace_root ?? process.cwd(), ctx.pm_root, process.cwd());
|
|
1192
1127
|
const limit = intOption(options, "limit", contextPackDefaultLimit);
|
|
1193
1128
|
const includeBody = boolOption(options, "include-body", "includeBody");
|
|
1194
1129
|
const includeClosed = boolOption(options, "include-closed", "includeClosed");
|
|
@@ -1203,8 +1138,8 @@ function setupCommands(api) {
|
|
|
1203
1138
|
const items = fileFocus ? await readFileFocusItems(ctx.pm_root) : await readPmItems(ctx.pm_root);
|
|
1204
1139
|
const rankOptions = await resolveSdkRankOptions(ctx);
|
|
1205
1140
|
const ranker = createSdkRanker(items, rankOptions);
|
|
1206
|
-
// Pre-rank the
|
|
1207
|
-
//
|
|
1141
|
+
// Pre-rank the corpus for neighbor relevance. Focus arrives in the final
|
|
1142
|
+
// selection order, including file overlap, after buildContextPack trims.
|
|
1208
1143
|
let packer;
|
|
1209
1144
|
if (maxItems) {
|
|
1210
1145
|
const rankedAll = rankContextItems(items, rankOptions);
|
|
@@ -1299,6 +1234,96 @@ function setupCommands(api) {
|
|
|
1299
1234
|
return renderedCommandResult(output);
|
|
1300
1235
|
},
|
|
1301
1236
|
});
|
|
1237
|
+
api.registerCommand({
|
|
1238
|
+
name: "context-evidence",
|
|
1239
|
+
description: "Pack explicit criterion evidence with exact coverage lost at a byte budget.",
|
|
1240
|
+
flags: [
|
|
1241
|
+
{ long: "--packet", value_name: "file", description: "JSON acceptance criteria and tracker field bindings", type: "string" },
|
|
1242
|
+
{ long: "--format", value_name: "format", description: "toon (default) or json", type: "string" },
|
|
1243
|
+
{ long: "--byte-budget", value_name: "n", description: "Complete-output UTF-8 byte ceiling (default 4000)", type: "string" },
|
|
1244
|
+
],
|
|
1245
|
+
async run(ctx) {
|
|
1246
|
+
const path = stringOption(ctx.options, "packet");
|
|
1247
|
+
const format = stringOption(ctx.options, "format") ?? (ctx.global.json ? "json" : "toon");
|
|
1248
|
+
if (!path || (format !== "json" && format !== "toon") || ctx.args.length)
|
|
1249
|
+
throw new EvidencePackingError("INVALID_ARGUMENT", "Supply --packet JSON with --format json or toon; no positional arguments are accepted.");
|
|
1250
|
+
const pack = await packCriterionEvidence(readEvidencePacket(path), createFreshnessReader(ctx.pm_root, "."), {
|
|
1251
|
+
format, byteBudget: Number(stringOption(ctx.options, "byte-budget", "byteBudget") ?? "4000"),
|
|
1252
|
+
});
|
|
1253
|
+
return renderedCommandResult(encodeEvidencePack(pack, format));
|
|
1254
|
+
},
|
|
1255
|
+
});
|
|
1256
|
+
api.registerCommand({
|
|
1257
|
+
name: "context-witnesses",
|
|
1258
|
+
description: "Find the smallest cited contradiction among declared packet assertions without editing sources.",
|
|
1259
|
+
flags: [
|
|
1260
|
+
{ long: "--packet", value_name: "file", description: "JSON packet of cited co-current claims", type: "string" },
|
|
1261
|
+
{ long: "--format", value_name: "format", description: "toon (default) or json", type: "string" },
|
|
1262
|
+
{ long: "--byte-budget", value_name: "n", description: "Complete-output UTF-8 byte ceiling (default 4000)", type: "string" },
|
|
1263
|
+
],
|
|
1264
|
+
async run(ctx) {
|
|
1265
|
+
const path = stringOption(ctx.options, "packet");
|
|
1266
|
+
const format = stringOption(ctx.options, "format") ?? (ctx.global.json ? "json" : "toon");
|
|
1267
|
+
if (!path || (format !== "json" && format !== "toon") || ctx.args.length)
|
|
1268
|
+
throw new WitnessError("INVALID_ARGUMENT", "Supply --packet JSON with --format json or toon; no positional arguments are accepted.");
|
|
1269
|
+
const budget = Number(stringOption(ctx.options, "byte-budget", "byteBudget") ?? "4000");
|
|
1270
|
+
const report = checkContextWitnesses(readWitnessPacket(path));
|
|
1271
|
+
return renderedCommandResult(encodeWitnessReport(report, format, budget));
|
|
1272
|
+
},
|
|
1273
|
+
});
|
|
1274
|
+
for (const action of ["record", "resume"]) {
|
|
1275
|
+
api.registerCommand({
|
|
1276
|
+
name: `context-freshness ${action}`,
|
|
1277
|
+
description: action === "record" ? "Record source digests behind declared packet assumptions." : "Return only assumptions invalidated by later source changes.",
|
|
1278
|
+
flags: [
|
|
1279
|
+
{ long: "--packet", value_name: "file", description: "JSON packet with section content and source dependencies", type: "string" },
|
|
1280
|
+
{ long: "--manifest", value_name: "file", description: "Original JSON digest manifest (resume only)", type: "string" },
|
|
1281
|
+
{ long: "--evidence-root", value_name: "directory", description: "Root for relative evidence files (default: source workspace)", type: "string" },
|
|
1282
|
+
{ long: "--format", value_name: "format", description: "Record: json; resume: toon (default) or json", type: "string" },
|
|
1283
|
+
{ long: "--byte-budget", value_name: "n", description: "Output UTF-8 byte ceiling (default: record 262144; resume 4000)", type: "string" },
|
|
1284
|
+
],
|
|
1285
|
+
async run(ctx) {
|
|
1286
|
+
const path = stringOption(ctx.options, "packet");
|
|
1287
|
+
const manifestPath = stringOption(ctx.options, "manifest");
|
|
1288
|
+
const format = stringOption(ctx.options, "format") ?? (ctx.global.json || action === "record" ? "json" : "toon");
|
|
1289
|
+
if (!path || (action === "resume" ? !manifestPath : Boolean(manifestPath)) || (format !== "json" && format !== "toon") || (action === "record" && format !== "json")) {
|
|
1290
|
+
throw new FreshnessError("INVALID_ARGUMENT", "Supply --packet JSON; resume also requires --manifest JSON. Record uses json; resume accepts --format json or toon.");
|
|
1291
|
+
}
|
|
1292
|
+
const budget = Number(stringOption(ctx.options, "byte-budget", "byteBudget") ?? (action === "record" ? "262144" : "4000"));
|
|
1293
|
+
if (!Number.isSafeInteger(budget) || budget < 512)
|
|
1294
|
+
throw new FreshnessError("INVALID_ARGUMENT", "Use an integer --byte-budget of at least 512.");
|
|
1295
|
+
const packet = parseFreshnessPacket(readFreshnessJson(path));
|
|
1296
|
+
const read = createFreshnessReader(ctx.pm_root, stringOption(ctx.options, "evidence-root", "evidenceRoot") ?? ctx.source_workspace_root ?? ctx.repo_root ?? process.cwd());
|
|
1297
|
+
if (action === "record")
|
|
1298
|
+
return renderedCommandResult(encodeFreshness(await recordFreshness(packet, read), "json", Math.min(budget, 262144)));
|
|
1299
|
+
const report = await resumeFreshness(packet, readFreshnessJson(manifestPath), read);
|
|
1300
|
+
const output = encodeFreshness(report, format, budget);
|
|
1301
|
+
if (report.code === "SOURCE_UNAVAILABLE")
|
|
1302
|
+
process.exitCode = EXIT_CODE.USAGE;
|
|
1303
|
+
return renderedCommandResult(output);
|
|
1304
|
+
},
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
api.registerCommand({
|
|
1308
|
+
name: "context-pack since",
|
|
1309
|
+
description: "Resume with ranked changes, substantive excerpts, claims and an opaque cursor.",
|
|
1310
|
+
flags: [
|
|
1311
|
+
{ long: "--cursor", value_name: "token", description: "Resume a retained delta checkpoint", type: "string" },
|
|
1312
|
+
{ long: "--from", value_name: "timestamp", description: "Start from an inclusive ISO timestamp", type: "string" },
|
|
1313
|
+
{ long: "--token-budget", value_name: "n", description: "Complete JSON byte-token upper bound (default: 4000)", type: "string" },
|
|
1314
|
+
],
|
|
1315
|
+
async run(ctx) {
|
|
1316
|
+
const settings = await readSettings(ctx.pm_root);
|
|
1317
|
+
const registry = resolveRuntimeStatusRegistry(settings.schema);
|
|
1318
|
+
const pack = await buildSincePack(ctx.pm_root, await readPmItems(ctx.pm_root), new Set(registry.terminal_statuses), {
|
|
1319
|
+
cursor: stringOption(ctx.options, "cursor"),
|
|
1320
|
+
from: stringOption(ctx.options, "from"),
|
|
1321
|
+
author: ctx.global.author ?? process.env.PM_AUTHOR ?? settings.author_default,
|
|
1322
|
+
tokenBudget: Number(stringOption(ctx.options, "token-budget", "tokenBudget") ?? "4000"),
|
|
1323
|
+
});
|
|
1324
|
+
return renderedCommandResult(encodeSincePack(pack));
|
|
1325
|
+
},
|
|
1326
|
+
});
|
|
1302
1327
|
api.registerCommand({
|
|
1303
1328
|
name: "context-handoff",
|
|
1304
1329
|
description: "Generate an agent-ready handoff with concise defaults.",
|
|
@@ -1460,7 +1485,7 @@ function setupCommands(api) {
|
|
|
1460
1485
|
if (!receiptId)
|
|
1461
1486
|
throw new CommandError("Usage evidence requires --receipt", EXIT_CODE.USAGE);
|
|
1462
1487
|
try {
|
|
1463
|
-
recordReceiptUsage(ctx.pm_root, { receipt_id: receiptId, session, citations, touched_items: touchedItems, edited_files: editedFiles });
|
|
1488
|
+
await recordReceiptUsage(ctx.pm_root, { receipt_id: receiptId, session, citations, touched_items: touchedItems, edited_files: editedFiles });
|
|
1464
1489
|
}
|
|
1465
1490
|
catch (error) {
|
|
1466
1491
|
throw new CommandError(error instanceof Error ? error.message : String(error), EXIT_CODE.USAGE);
|
|
@@ -1501,13 +1526,13 @@ function setupCommands(api) {
|
|
|
1501
1526
|
/** Register context commands and renderers with checked host behavior and package metadata. */
|
|
1502
1527
|
export default {
|
|
1503
1528
|
name: "pm-context",
|
|
1504
|
-
version: "2026.10.
|
|
1529
|
+
version: "2026.10.24",
|
|
1505
1530
|
description: "Generate deterministic pm context packs for agent handoffs, reviews, and status briefs",
|
|
1506
1531
|
activate(api) {
|
|
1507
1532
|
setupCommands(api);
|
|
1508
1533
|
if (typeof api.registerRenderer === "function") {
|
|
1509
1534
|
const rendererOwnership = {
|
|
1510
|
-
commands: ["context-pack", "context-handoff", "context-usage"],
|
|
1535
|
+
commands: ["context-pack", "context-pack since", "context-handoff", "context-usage", "context-freshness record", "context-freshness resume", "context-witnesses", "context-evidence"],
|
|
1511
1536
|
resultDiscriminator: isRenderedCommandResult,
|
|
1512
1537
|
};
|
|
1513
1538
|
api.registerRenderer("toon", renderCommandResult, rendererOwnership);
|