@unbrained/pm-cli 2026.8.31 → 2026.9.2
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/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +15 -13
- package/CHANGELOG.md +33 -6
- package/README.md +3 -3
- package/dist/cli/commands/merge.d.ts +1 -1
- package/dist/cli/commands/merge.js +3 -3
- package/dist/cli/register-mutation.js +8 -5
- package/dist/cli-bundle/bundle-manifest.json +129 -129
- package/dist/cli-bundle/chunks/{chunk-52JXYSDW.js → chunk-3WICXLVT.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-JIFYOJF7.js → chunk-DTGWEL23.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-SXMHPK4V.js → chunk-FZ4Z2J4F.js} +4 -4
- package/dist/cli-bundle/chunks/chunk-GOACULJW.js +13 -0
- package/dist/cli-bundle/chunks/{chunk-NW6LV3TU.js → chunk-NOPL37X4.js} +64 -64
- package/dist/cli-bundle/chunks/{chunk-OUGDA7NR.js → chunk-T6H5BZCP.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-IEFJ3ZEB.js → chunk-TAG4VC22.js} +4 -4
- package/dist/cli-bundle/chunks/{chunk-MWIN6OH2.js → chunk-UDW64J2W.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-5DH5XCJL.js → chunk-X4RCGLTX.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-QZCIBZR3.js → chunk-Y7VOMSZT.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-ZLJIFSOE.js +35 -0
- package/dist/cli-bundle/chunks/{register-list-query-7C7DKZ2U.js → register-list-query-XYNX4ASQ.js} +2 -2
- package/dist/cli-bundle/chunks/register-mutation-WFQ7MJTX.js +20 -0
- package/dist/cli-bundle/chunks/{register-operations-IIMA5OKO.js → register-operations-JVTI7JPP.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-52H36QCB.js → register-setup-KVNC5KPL.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-VJ7EFMFL.js → chunk-242AXYB2.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-7VATS5QY.js +16 -0
- package/dist/cli-bundle/focused-chunks/{chunk-FOMZ6NG5.js → chunk-MKHG3T76.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-JCOOAUHK.js → chunk-O67VDLOT.js} +48 -48
- package/dist/cli-bundle/focused-chunks/{chunk-UN34D63M.js → chunk-PF3E6Q36.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-APN4QN6R.js → chunk-RMRCQPWU.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-BPWJ4QM5.js → chunk-S4U5CN5Q.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-T2LXXSOD.js +158 -0
- package/dist/cli-bundle/main.js +2 -2
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +25 -25
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +32 -32
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +10 -10
- package/dist/core/diagnostics/remediation.js +9 -4
- package/dist/core/history/drift-scan.js +6 -6
- package/dist/core/history/history.d.ts +6 -0
- package/dist/core/history/history.js +47 -5
- package/dist/core/history/replay.d.ts +12 -1
- package/dist/core/history/replay.js +60 -39
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +78 -15
- package/dist/mcp/tool-definitions.js +56 -2
- package/dist/sdk/agent-capability-contracts.js +4 -2
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -2
- package/dist/sdk/governance/health.js +77 -11
- package/dist/sdk/governance/validate.js +4 -2
- package/dist/sdk/history-read.js +14 -6
- package/dist/sdk/history-repair.js +17 -10
- package/dist/sdk/index.d.ts +1 -0
- package/dist/sdk/index.js +3 -2
- package/dist/sdk/init-agent-guidance.js +4 -4
- package/dist/sdk/lifecycle/update.js +23 -10
- package/dist/sdk/mcp/discovery.d.ts +152 -0
- package/dist/sdk/mcp/discovery.js +495 -0
- package/dist/sdk/merge/index.d.ts +1 -1
- package/dist/sdk/merge/index.js +3 -3
- package/dist/sdk/merge/receipts.d.ts +51 -1
- package/dist/sdk/merge/receipts.js +146 -35
- package/dist/sdk/merge/three-way.d.ts +3 -1
- package/dist/sdk/merge/three-way.js +9 -7
- package/dist/sdk/runtime-extended-actions.js +4 -4
- package/docs/AGENT_GUIDE.md +11 -3
- package/docs/ARCHITECTURE.md +5 -0
- package/docs/CLAUDE_CODE_PLUGIN.md +2 -2
- package/docs/CONTEXT_RECOVERY_PRIMITIVES.md +24 -6
- package/docs/MCP_2026_07_28.md +9 -5
- package/docs/MCP_CAPABILITY_SURFACES.md +7 -1
- package/docs/MCP_REMOTE_TRANSPORT_SECURITY.md +11 -1
- package/docs/MERGE_SAFETY.md +44 -2
- package/docs/OUTPUT_TOKEN_ACCOUNTING.md +5 -3
- package/docs/PROGRESSIVE_TOOL_DISCOVERY.md +42 -0
- package/docs/README.md +1 -0
- package/docs/agent-task-token-baseline.json +157 -3
- package/docs/agent-task-transcripts.json +143 -0
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +4 -3
- package/docs/performance/cli-transport-overhead.md +8 -6
- package/marketplace.json +2 -2
- package/package.json +2 -2
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-claude/README.md +4 -3
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/sdk/public-surface.json +168 -2
- package/dist/cli-bundle/chunks/chunk-2XVV45NJ.js +0 -35
- package/dist/cli-bundle/chunks/chunk-3FCSFWZT.js +0 -13
- package/dist/cli-bundle/chunks/register-mutation-OI4BJLUX.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-BMD42PUG.js +0 -158
- package/dist/cli-bundle/focused-chunks/chunk-EUTABA4M.js +0 -16
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
1
2
|
import type { ItemMergeConflictDecision, MergePreferredSide } from "./three-way.js";
|
|
3
|
+
/** Return whether a receipt id is safe to expose in cleartext diagnostics. */
|
|
4
|
+
export declare function isSafeReceiptId(value: string): boolean;
|
|
2
5
|
/** One clone-local merge receipt with recoverable branch values. */
|
|
3
6
|
export interface MergeDecisionReceipt {
|
|
4
7
|
/** Receipt schema version. */
|
|
@@ -70,12 +73,50 @@ export interface MergeReceiptReport {
|
|
|
70
73
|
/** ISO timestamp for the report. */
|
|
71
74
|
generated_at: string;
|
|
72
75
|
}
|
|
73
|
-
/**
|
|
76
|
+
/** Loss-aware merge receipt report for integrity gates and diagnostic adapters. */
|
|
77
|
+
export interface MergeReceiptEvidenceReport {
|
|
78
|
+
/** Whether clone-local evidence resolved and every candidate was read and validated successfully. */
|
|
79
|
+
ok: boolean;
|
|
80
|
+
/** Whether clone-local evidence resolved and every discovered JSON candidate was read and validated successfully. */
|
|
81
|
+
complete: boolean;
|
|
82
|
+
/** Number of valid receipts returned. */
|
|
83
|
+
count: number;
|
|
84
|
+
/** Number of candidates rejected by bounded-file, schema, identity, or copy-consistency validation. */
|
|
85
|
+
invalid_evidence_count: number;
|
|
86
|
+
/** Bounded privacy-safe identities and reason codes for rejected evidence. */
|
|
87
|
+
invalid_evidence: MergeReceiptInvalidEvidence[];
|
|
88
|
+
/** Whether additional rejected evidence was omitted from the bounded detail list. */
|
|
89
|
+
invalid_evidence_truncated: boolean;
|
|
90
|
+
/** Whether the clone-local Git receipt directory was resolved successfully; always emitted by current implementations and optional for structural compatibility. */
|
|
91
|
+
clone_local_evidence_resolved?: boolean;
|
|
92
|
+
/** Receipts including recoverable values from the local clone only. */
|
|
93
|
+
receipts: MergeDecisionReceipt[];
|
|
94
|
+
/** ISO timestamp for the report. */
|
|
95
|
+
generated_at: string;
|
|
96
|
+
}
|
|
97
|
+
/** Loss-aware receipt inspection result that never exposes malformed file contents. */
|
|
74
98
|
export interface MergeReceiptEvidenceScan {
|
|
75
99
|
/** Valid receipts that passed bounded-file, schema, and identity validation. */
|
|
76
100
|
receipts: MergeDecisionReceipt[];
|
|
77
101
|
/** Number of JSON receipt candidates that could not be validated safely. */
|
|
78
102
|
invalid_evidence_count: number;
|
|
103
|
+
/** Bounded privacy-safe identities and reason codes for rejected evidence. */
|
|
104
|
+
invalid_evidence: MergeReceiptInvalidEvidence[];
|
|
105
|
+
/** Whether additional rejected evidence was omitted from the bounded detail list. */
|
|
106
|
+
invalid_evidence_truncated: boolean;
|
|
107
|
+
/** Whether the clone-local Git receipt directory was resolved successfully; always emitted by current implementations and optional for structural compatibility. */
|
|
108
|
+
clone_local_evidence_resolved?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/** Stable privacy-safe classification for one rejected receipt candidate or source. */
|
|
111
|
+
export interface MergeReceiptInvalidEvidence {
|
|
112
|
+
/** Evidence store that produced the rejected candidate. */
|
|
113
|
+
evidence_source: "clone_local" | "durable" | "clone_local_and_durable";
|
|
114
|
+
/** Stable failure class suitable for remediation routing and graph analytics. */
|
|
115
|
+
reason: "directory_unreadable" | "candidate_not_bounded_regular_file" | "candidate_unreadable" | "candidate_invalid_json" | "schema_or_identity_invalid" | "copy_provenance_mismatch";
|
|
116
|
+
/** Receipt identity when the bounded filename itself is a valid receipt id. */
|
|
117
|
+
receipt_id?: string;
|
|
118
|
+
/** SHA-256 locator for an unsafe or malformed candidate filename. */
|
|
119
|
+
candidate_name_hash?: string;
|
|
79
120
|
}
|
|
80
121
|
declare function prepareReceiptSettlement(params: {
|
|
81
122
|
receiptPath: string;
|
|
@@ -101,6 +142,7 @@ export declare function writeMergeReceipt(params: {
|
|
|
101
142
|
mergedFieldHashes?: Record<string, string>;
|
|
102
143
|
decisions: ItemMergeConflictDecision[];
|
|
103
144
|
}): Promise<MergeDecisionReceipt | null>;
|
|
145
|
+
declare function receiptDirectoryFailureMeansAbsent(directory: string, error: unknown, inspectAncestor?: (ancestor: string) => Promise<Pick<Stats, "isDirectory">>): Promise<boolean>;
|
|
104
146
|
/** Inspect valid receipts and count invalid evidence without returning untrusted file contents. */
|
|
105
147
|
export declare function inspectMergeReceiptEvidence(cwd: string, options?: {
|
|
106
148
|
/** Include receipts whose reconciliation state is already settled. */
|
|
@@ -134,8 +176,16 @@ export declare function runMergeReceiptReport(options: {
|
|
|
134
176
|
/** Repository directory to inspect; defaults to the process working directory. */
|
|
135
177
|
cwd?: string;
|
|
136
178
|
}): Promise<MergeReceiptReport>;
|
|
179
|
+
/** Inspect every receipt candidate and retain completeness diagnostics without exposing rejected file contents. */
|
|
180
|
+
export declare function runMergeReceiptEvidenceReport(options: {
|
|
181
|
+
/** Include receipts already represented by merge history events. */
|
|
182
|
+
includeReconciled?: boolean;
|
|
183
|
+
/** Repository directory to inspect; defaults to the process working directory. */
|
|
184
|
+
cwd?: string;
|
|
185
|
+
}): Promise<MergeReceiptEvidenceReport>;
|
|
137
186
|
/** Test-only seams for deterministic receipt-boundary fault coverage. */
|
|
138
187
|
export declare const _testOnlyMergeReceipts: {
|
|
139
188
|
prepareReceiptSettlement: typeof prepareReceiptSettlement;
|
|
189
|
+
receiptDirectoryFailureMeansAbsent: typeof receiptDirectoryFailureMeansAbsent;
|
|
140
190
|
};
|
|
141
191
|
export {};
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* value for the coordinator that performed the merge.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
10
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="df7d5bd9-8381-5961-bda1-9e8ed335afc9")}catch(e){}}();
|
|
11
11
|
import { execFile } from "node:child_process";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
|
-
import { readdir } from "node:fs/promises";
|
|
13
|
+
import { readdir, stat } from "node:fs/promises";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
|
-
import { ensureDir, isFileAbsentError, pathExists, writeFileAtomic, } from "../../core/fs/fs-utils.js";
|
|
16
|
+
import { ensureDir, isFileAbsentError, isFileMissingError, pathExists, writeFileAtomic, } from "../../core/fs/fs-utils.js";
|
|
17
17
|
import { sha256Hex, stableStringify } from "../../core/shared/serialization.js";
|
|
18
18
|
import { isRfc3339DateTime, nowIso } from "../../core/shared/time.js";
|
|
19
19
|
import { readBoundedRegularFile } from "./receipt-file-boundary.js";
|
|
@@ -24,6 +24,7 @@ const RECEIPT_FIELD_MAX = 2_048;
|
|
|
24
24
|
const RECEIPT_FILE_MAX_BYTES = 16 * 1024 * 1024;
|
|
25
25
|
const RECEIPT_VALUE_MAX_DEPTH = 64;
|
|
26
26
|
const RECEIPT_VALUE_MAX_NODES = 100_000;
|
|
27
|
+
const RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT = 100;
|
|
27
28
|
const RECEIPT_KEYS = new Set([
|
|
28
29
|
"version",
|
|
29
30
|
"id",
|
|
@@ -42,6 +43,10 @@ const RECEIPT_KEYS = new Set([
|
|
|
42
43
|
"value_availability",
|
|
43
44
|
"evidence_source",
|
|
44
45
|
]);
|
|
46
|
+
/** Return whether a receipt id is safe to expose in cleartext diagnostics. */
|
|
47
|
+
export function isSafeReceiptId(value) {
|
|
48
|
+
return RECEIPT_ID_PATTERN.test(value);
|
|
49
|
+
}
|
|
45
50
|
const RECEIPT_DECISION_KEYS = new Set([
|
|
46
51
|
"field",
|
|
47
52
|
"base",
|
|
@@ -163,7 +168,7 @@ function hasValidReceiptIdentity(value) {
|
|
|
163
168
|
return !hasOnlyKeys(value, RECEIPT_KEYS) || value.version !== 1
|
|
164
169
|
? false
|
|
165
170
|
: typeof value.id === "string" &&
|
|
166
|
-
|
|
171
|
+
isSafeReceiptId(value.id) &&
|
|
167
172
|
typeof value.item_id === "string" &&
|
|
168
173
|
RECEIPT_ITEM_ID_PATTERN.test(value.item_id) &&
|
|
169
174
|
isSafeReceiptItemPath(value.item_path, value.item_id);
|
|
@@ -367,59 +372,120 @@ export async function writeMergeReceipt(params) {
|
|
|
367
372
|
return receipt;
|
|
368
373
|
}
|
|
369
374
|
async function readReceiptsFromDirectory(directory, evidenceSource) {
|
|
370
|
-
if (!(await pathExists(directory))) {
|
|
371
|
-
return { receipts: [], invalid_evidence_count: 0 };
|
|
372
|
-
}
|
|
373
375
|
let names;
|
|
374
376
|
try {
|
|
375
377
|
names = await readdir(directory);
|
|
376
378
|
}
|
|
377
|
-
catch {
|
|
378
|
-
|
|
379
|
+
catch (error) {
|
|
380
|
+
const absent = await receiptDirectoryFailureMeansAbsent(directory, error);
|
|
381
|
+
return {
|
|
382
|
+
receipts: [],
|
|
383
|
+
invalid_evidence_count: absent ? 0 : 1,
|
|
384
|
+
invalid_evidence: absent
|
|
385
|
+
? []
|
|
386
|
+
: [{ evidence_source: evidenceSource, reason: "directory_unreadable" }],
|
|
387
|
+
invalid_evidence_truncated: false,
|
|
388
|
+
};
|
|
379
389
|
}
|
|
380
390
|
const receipts = [];
|
|
381
391
|
let invalidEvidenceCount = 0;
|
|
392
|
+
const invalidEvidence = [];
|
|
393
|
+
const recordInvalidEvidence = (name, reason) => {
|
|
394
|
+
invalidEvidenceCount += 1;
|
|
395
|
+
if (invalidEvidence.length >= RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT)
|
|
396
|
+
return;
|
|
397
|
+
const receiptId = name.slice(0, -".json".length);
|
|
398
|
+
invalidEvidence.push({
|
|
399
|
+
evidence_source: evidenceSource,
|
|
400
|
+
reason,
|
|
401
|
+
...(isSafeReceiptId(receiptId)
|
|
402
|
+
? { receipt_id: receiptId }
|
|
403
|
+
: { candidate_name_hash: sha256Hex(name) }),
|
|
404
|
+
});
|
|
405
|
+
};
|
|
382
406
|
for (const name of names.sort((left, right) => left.localeCompare(right))) {
|
|
383
407
|
if (!name.endsWith(".json"))
|
|
384
408
|
continue;
|
|
409
|
+
const candidate = await inspectReceiptCandidate(directory, name, evidenceSource);
|
|
410
|
+
if (candidate.receipt !== undefined) {
|
|
411
|
+
receipts.push(candidate.receipt);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
recordInvalidEvidence(name, candidate.reason);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
receipts,
|
|
419
|
+
invalid_evidence_count: invalidEvidenceCount,
|
|
420
|
+
invalid_evidence: invalidEvidence,
|
|
421
|
+
invalid_evidence_truncated: invalidEvidenceCount > invalidEvidence.length,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
async function receiptDirectoryFailureMeansAbsent(directory, error, inspectAncestor = stat) {
|
|
425
|
+
if (!isFileMissingError(error))
|
|
426
|
+
return false;
|
|
427
|
+
let ancestor = directory;
|
|
428
|
+
while (true) {
|
|
385
429
|
try {
|
|
386
|
-
|
|
387
|
-
const raw = await readBoundedRegularReceiptFile(receiptPath);
|
|
388
|
-
if (raw === null) {
|
|
389
|
-
invalidEvidenceCount += 1;
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
const parsed = JSON.parse(raw);
|
|
393
|
-
if (!isMergeDecisionReceipt(parsed, evidenceSource) ||
|
|
394
|
-
name !== receiptFileName(parsed.id)) {
|
|
395
|
-
invalidEvidenceCount += 1;
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
const { preferred: legacyPreference, evidence_source: _serializedEvidenceSource, ...receiptWithoutRuntimeKeys } = parsed;
|
|
399
|
-
const normalizedReceipt = {
|
|
400
|
-
...receiptWithoutRuntimeKeys,
|
|
401
|
-
requested_preference: parsed.requested_preference ?? legacyPreference ?? "ours",
|
|
402
|
-
conflict_resolution: parsed.conflict_resolution ?? "preferred_side",
|
|
403
|
-
evidence_source: evidenceSource,
|
|
404
|
-
};
|
|
405
|
-
receipts.push(normalizedReceipt);
|
|
430
|
+
return (await inspectAncestor(ancestor)).isDirectory();
|
|
406
431
|
}
|
|
407
|
-
catch {
|
|
408
|
-
|
|
432
|
+
catch (ancestorError) {
|
|
433
|
+
if (!isFileMissingError(ancestorError))
|
|
434
|
+
return false;
|
|
435
|
+
const parent = path.dirname(ancestor);
|
|
436
|
+
if (parent === ancestor)
|
|
437
|
+
return true;
|
|
438
|
+
ancestor = parent;
|
|
409
439
|
}
|
|
410
440
|
}
|
|
411
|
-
|
|
441
|
+
}
|
|
442
|
+
async function inspectReceiptCandidate(directory, name, evidenceSource) {
|
|
443
|
+
let raw;
|
|
444
|
+
try {
|
|
445
|
+
raw = await readBoundedRegularReceiptFile(path.join(directory, name));
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return { reason: "candidate_unreadable" };
|
|
449
|
+
}
|
|
450
|
+
if (raw === null)
|
|
451
|
+
return { reason: "candidate_not_bounded_regular_file" };
|
|
452
|
+
let parsed;
|
|
453
|
+
try {
|
|
454
|
+
parsed = JSON.parse(raw);
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return { reason: "candidate_invalid_json" };
|
|
458
|
+
}
|
|
459
|
+
if (!isMergeDecisionReceipt(parsed, evidenceSource) ||
|
|
460
|
+
name !== receiptFileName(parsed.id)) {
|
|
461
|
+
return { reason: "schema_or_identity_invalid" };
|
|
462
|
+
}
|
|
463
|
+
const { preferred: legacyPreference, evidence_source: _serializedEvidenceSource, ...receiptWithoutRuntimeKeys } = parsed;
|
|
464
|
+
return {
|
|
465
|
+
receipt: {
|
|
466
|
+
...receiptWithoutRuntimeKeys,
|
|
467
|
+
requested_preference: parsed.requested_preference ?? legacyPreference ?? "ours",
|
|
468
|
+
conflict_resolution: parsed.conflict_resolution ?? "preferred_side",
|
|
469
|
+
evidence_source: evidenceSource,
|
|
470
|
+
},
|
|
471
|
+
};
|
|
412
472
|
}
|
|
413
473
|
/** Inspect valid receipts and count invalid evidence without returning untrusted file contents. */
|
|
414
474
|
export async function inspectMergeReceiptEvidence(cwd, options = {}) {
|
|
415
475
|
const directory = await resolveReceiptDirectory(cwd);
|
|
416
476
|
const local = directory === null
|
|
417
|
-
? {
|
|
477
|
+
? {
|
|
478
|
+
receipts: [],
|
|
479
|
+
invalid_evidence_count: 0,
|
|
480
|
+
invalid_evidence: [],
|
|
481
|
+
invalid_evidence_truncated: false,
|
|
482
|
+
}
|
|
418
483
|
: await readReceiptsFromDirectory(directory, "clone_local");
|
|
419
484
|
const trackerRoot = options.pmRoot ?? path.join(cwd, ".agents", "pm");
|
|
420
485
|
const durable = await readReceiptsFromDirectory(durableReceiptDirectory(trackerRoot), "durable");
|
|
421
486
|
const receipts = new Map(durable.receipts.map((receipt) => [receipt.id, receipt]));
|
|
422
487
|
let divergentCopyCount = 0;
|
|
488
|
+
const divergentCopyEvidence = [];
|
|
423
489
|
for (const receipt of local.receipts) {
|
|
424
490
|
const durableCopy = receipts.get(receipt.id);
|
|
425
491
|
if (durableCopy !== undefined &&
|
|
@@ -427,6 +493,16 @@ export async function inspectMergeReceiptEvidence(cwd, options = {}) {
|
|
|
427
493
|
receiptProvenanceFingerprint(receipt)) {
|
|
428
494
|
receipts.delete(receipt.id);
|
|
429
495
|
divergentCopyCount += 1;
|
|
496
|
+
if (local.invalid_evidence.length +
|
|
497
|
+
durable.invalid_evidence.length +
|
|
498
|
+
divergentCopyEvidence.length <
|
|
499
|
+
RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT) {
|
|
500
|
+
divergentCopyEvidence.push({
|
|
501
|
+
evidence_source: "clone_local_and_durable",
|
|
502
|
+
reason: "copy_provenance_mismatch",
|
|
503
|
+
receipt_id: receipt.id,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
430
506
|
continue;
|
|
431
507
|
}
|
|
432
508
|
receipts.set(receipt.id, durableCopy === undefined
|
|
@@ -441,6 +517,18 @@ export async function inspectMergeReceiptEvidence(cwd, options = {}) {
|
|
|
441
517
|
invalid_evidence_count: local.invalid_evidence_count +
|
|
442
518
|
durable.invalid_evidence_count +
|
|
443
519
|
divergentCopyCount,
|
|
520
|
+
invalid_evidence: [
|
|
521
|
+
...local.invalid_evidence,
|
|
522
|
+
...durable.invalid_evidence,
|
|
523
|
+
...divergentCopyEvidence,
|
|
524
|
+
].slice(0, RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT),
|
|
525
|
+
invalid_evidence_truncated: local.invalid_evidence_truncated ||
|
|
526
|
+
durable.invalid_evidence_truncated ||
|
|
527
|
+
local.invalid_evidence_count +
|
|
528
|
+
durable.invalid_evidence_count +
|
|
529
|
+
divergentCopyCount >
|
|
530
|
+
RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT,
|
|
531
|
+
clone_local_evidence_resolved: directory !== null,
|
|
444
532
|
};
|
|
445
533
|
}
|
|
446
534
|
/** Read clone-local receipts with explicit reconciled/lossless classification controls. */
|
|
@@ -524,7 +612,30 @@ export async function runMergeReceiptReport(options) {
|
|
|
524
612
|
generated_at: nowIso(),
|
|
525
613
|
};
|
|
526
614
|
}
|
|
615
|
+
/** Inspect every receipt candidate and retain completeness diagnostics without exposing rejected file contents. */
|
|
616
|
+
export async function runMergeReceiptEvidenceReport(options) {
|
|
617
|
+
const evidence = await inspectMergeReceiptEvidence(options.cwd ?? process.cwd(), {
|
|
618
|
+
...options,
|
|
619
|
+
includeLossless: true,
|
|
620
|
+
});
|
|
621
|
+
const complete = evidence.clone_local_evidence_resolved === true &&
|
|
622
|
+
evidence.invalid_evidence_count === 0;
|
|
623
|
+
return {
|
|
624
|
+
ok: complete,
|
|
625
|
+
complete,
|
|
626
|
+
count: evidence.receipts.length,
|
|
627
|
+
invalid_evidence_count: evidence.invalid_evidence_count,
|
|
628
|
+
invalid_evidence: evidence.invalid_evidence,
|
|
629
|
+
invalid_evidence_truncated: evidence.invalid_evidence_truncated,
|
|
630
|
+
clone_local_evidence_resolved: evidence.clone_local_evidence_resolved,
|
|
631
|
+
receipts: evidence.receipts,
|
|
632
|
+
generated_at: nowIso(),
|
|
633
|
+
};
|
|
634
|
+
}
|
|
527
635
|
/** Test-only seams for deterministic receipt-boundary fault coverage. */
|
|
528
|
-
export const _testOnlyMergeReceipts = {
|
|
636
|
+
export const _testOnlyMergeReceipts = {
|
|
637
|
+
prepareReceiptSettlement,
|
|
638
|
+
receiptDirectoryFailureMeansAbsent,
|
|
639
|
+
};
|
|
529
640
|
//# sourceMappingURL=receipts.js.map
|
|
530
|
-
//# debugId=
|
|
641
|
+
//# debugId=df7d5bd9-8381-5961-bda1-9e8ed335afc9
|
|
@@ -28,7 +28,9 @@ export interface HistoryMergeResult {
|
|
|
28
28
|
* hash re-anchored so the resulting chain verifies again. No side's events are
|
|
29
29
|
* ever discarded: content divergence is preserved for the post-merge
|
|
30
30
|
* reconciliation pass (`pm validate` + `pm history-repair`) instead of being
|
|
31
|
-
* silently resolved by last-writer-wins.
|
|
31
|
+
* silently resolved by last-writer-wins. If either suffix cannot apply after
|
|
32
|
+
* deterministic ordering, the merge fails closed instead of publishing a
|
|
33
|
+
* hash-valid stream that omitted a branch effect.
|
|
32
34
|
*/
|
|
33
35
|
export declare function mergeHistoryStreams(baseRaw: string, oursRaw: string, theirsRaw: string): HistoryMergeResult;
|
|
34
36
|
/** Documents the relationship event stream merge result payload exchanged by command, SDK, and package integrations. */
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* definition for multi-branch agent workflows.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
12
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="7e8fcddf-09fe-5979-b23a-78915bf7f896")}catch(e){}}();
|
|
13
13
|
import { historyEntriesToRaw, reanchorHistoryEntries, } from "../../core/history/replay.js";
|
|
14
14
|
import { canonicalDocument, parseItemDocument, serializeItemDocument, } from "../../core/item/item-format.js";
|
|
15
15
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
@@ -69,7 +69,9 @@ function commonHistoryPrefixLength(ours, theirs) {
|
|
|
69
69
|
* hash re-anchored so the resulting chain verifies again. No side's events are
|
|
70
70
|
* ever discarded: content divergence is preserved for the post-merge
|
|
71
71
|
* reconciliation pass (`pm validate` + `pm history-repair`) instead of being
|
|
72
|
-
* silently resolved by last-writer-wins.
|
|
72
|
+
* silently resolved by last-writer-wins. If either suffix cannot apply after
|
|
73
|
+
* deterministic ordering, the merge fails closed instead of publishing a
|
|
74
|
+
* hash-valid stream that omitted a branch effect.
|
|
73
75
|
*/
|
|
74
76
|
export function mergeHistoryStreams(baseRaw, oursRaw, theirsRaw) {
|
|
75
77
|
parseHistoryJsonl(baseRaw, "base");
|
|
@@ -126,10 +128,10 @@ export function mergeHistoryStreams(baseRaw, oursRaw, theirsRaw) {
|
|
|
126
128
|
}
|
|
127
129
|
return historyEntryIdentity(left).localeCompare(historyEntryIdentity(right));
|
|
128
130
|
});
|
|
129
|
-
const reanchored = reanchorHistoryEntries([
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
const reanchored = reanchorHistoryEntries([...ours.slice(0, shared), ...mergedSuffix], undefined, { continuousHashSurface: true });
|
|
132
|
+
if (reanchored.skippedOps > 0) {
|
|
133
|
+
throw new PmCliError(`History union cannot preserve ${reanchored.skippedOps} patch operation(s) after deterministic suffix ordering; leave the file unresolved for manual recovery.`, EXIT_CODE.GENERIC_FAILURE);
|
|
134
|
+
}
|
|
133
135
|
return {
|
|
134
136
|
merged: historyEntriesToRaw(reanchored.entries),
|
|
135
137
|
strategy: "union_reanchor",
|
|
@@ -704,4 +706,4 @@ export function mergeJsonDocuments(baseRaw, oursRaw, theirsRaw, options = {}) {
|
|
|
704
706
|
};
|
|
705
707
|
}
|
|
706
708
|
//# sourceMappingURL=three-way.js.map
|
|
707
|
-
//# debugId=
|
|
709
|
+
//# debugId=7e8fcddf-09fe-5979-b23a-78915bf7f896
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="41227e35-6354-5208-997b-e8a1cf696bf9")}catch(e){}}();
|
|
3
3
|
import { EXIT_CODE } from "../core/shared/constants.js";
|
|
4
4
|
import { PmCliError } from "../core/shared/errors.js";
|
|
5
5
|
import { transferMutationStdinTokenPolicy } from "./runtime-primitives.js";
|
|
@@ -9,7 +9,7 @@ import { runEval } from "./eval.js";
|
|
|
9
9
|
import { runMergeDriver } from "./merge/driver.js";
|
|
10
10
|
import { runMergeInstall } from "./merge/install.js";
|
|
11
11
|
import { runMergeReconcile } from "./merge/reconcile.js";
|
|
12
|
-
import {
|
|
12
|
+
import { runMergeReceiptEvidenceReport } from "./merge/receipts.js";
|
|
13
13
|
import { listMutationEvents } from "./mutation-events.js";
|
|
14
14
|
import { parseRuntimeInteger, readRuntimeString, readRuntimeStringArray, } from "./runtime-input.js";
|
|
15
15
|
import { runEvent, runMeet, runRemind, } from "./scheduling-shortcuts.js";
|
|
@@ -87,7 +87,7 @@ export function runRuntimeMergeAction(context) {
|
|
|
87
87
|
}, context.global);
|
|
88
88
|
}
|
|
89
89
|
if (subcommand === "report") {
|
|
90
|
-
return
|
|
90
|
+
return runMergeReceiptEvidenceReport({
|
|
91
91
|
includeReconciled: input.includeReconciled === true,
|
|
92
92
|
cwd: readRuntimeString(input, "cwd"),
|
|
93
93
|
});
|
|
@@ -175,4 +175,4 @@ export function runRuntimeSchedulingAction(context) {
|
|
|
175
175
|
return runRemind(title, input, context.global);
|
|
176
176
|
}
|
|
177
177
|
//# sourceMappingURL=runtime-extended-actions.js.map
|
|
178
|
-
//# debugId=
|
|
178
|
+
//# debugId=41227e35-6354-5208-997b-e8a1cf696bf9
|
package/docs/AGENT_GUIDE.md
CHANGED
|
@@ -4,18 +4,25 @@ This guide is optimized for coding agents that need to understand and mutate rep
|
|
|
4
4
|
|
|
5
5
|
## Agent Quick Context
|
|
6
6
|
|
|
7
|
-
Run this before heavy work:
|
|
7
|
+
Run this before heavy work. The first call is the canonical cold start. Then run the request-specific duplicate and ownership checks before item mutation:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
pm context --limit 10
|
|
10
|
+
pm context --limit 10 --for orient
|
|
11
11
|
pm search "<request keywords>" --limit 10
|
|
12
12
|
pm list --status open --limit 20
|
|
13
13
|
pm list --status in_progress --limit 20
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Run the optional guidance setup commands only when needed:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
14
19
|
pm init --agent-guidance status
|
|
15
20
|
pm package install guide-shell --project
|
|
16
21
|
pm guide workflows
|
|
17
22
|
```
|
|
18
23
|
|
|
24
|
+
The completed-task replay gate selects that one-call protocol from equivalent fixed-corpus candidates: 1,055 estimated tokens versus 2,293 for the historical four-read sequence and 4,798 for `contracts --summary` plus `next`. The reviewed numbers and per-step receipts live in [the published token baseline](agent-task-token-baseline.json).
|
|
25
|
+
|
|
19
26
|
If a relevant item exists, reuse it. If not, create a parent lineage, then create and claim the child implementation item.
|
|
20
27
|
When AGENTS/CLAUDE guidance is missing, use `pm init --agent-guidance add` to inject compact workflow guardrails, or `pm init --agent-guidance skip` to persist an explicit decline.
|
|
21
28
|
|
|
@@ -26,7 +33,7 @@ Tracked documentation work: [pm-u9d0](../.agents/pm/epics/pm-u9d0.toon).
|
|
|
26
33
|
1. **Orient**
|
|
27
34
|
|
|
28
35
|
```bash
|
|
29
|
-
pm context --limit 10
|
|
36
|
+
pm context --limit 10 --for orient
|
|
30
37
|
pm search "<keywords>" --limit 10
|
|
31
38
|
pm list --status open --limit 20
|
|
32
39
|
pm list --status in_progress --limit 20
|
|
@@ -108,6 +115,7 @@ pm release <item-id>
|
|
|
108
115
|
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
109
116
|
| The single next action + why | `pm next` (concrete ready leaves first; completed-container closeout rows surface only when no leaf work is ready; `--ready-only` for the tightest output) |
|
|
110
117
|
| Next ready work in one epic | `pm next --parent <id>` |
|
|
118
|
+
| Canonical cold-start orientation | `pm context --limit 10 --for orient` (1,055 estimated tokens on the fixed 101-item replay corpus) |
|
|
111
119
|
| Next work and agenda | `pm context --limit 10` |
|
|
112
120
|
| Comprehensive whole-tracker snapshot | `pm context --depth full` (every section, no per-section row cap) |
|
|
113
121
|
| Status of one epic/subtree | `pm context --parent <id> --depth deep` |
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -56,9 +56,12 @@ src/
|
|
|
56
56
|
shared/
|
|
57
57
|
mcp/
|
|
58
58
|
server.ts
|
|
59
|
+
tool-definitions.ts
|
|
59
60
|
sdk/
|
|
60
61
|
cli-contracts.ts
|
|
61
62
|
index.ts
|
|
63
|
+
mcp/
|
|
64
|
+
discovery.ts
|
|
62
65
|
types/
|
|
63
66
|
tests/
|
|
64
67
|
unit/
|
|
@@ -287,6 +290,8 @@ allowance file:
|
|
|
287
290
|
Do not add new CLI/MCP behavior by deep-importing `src/core`. Add or extend an
|
|
288
291
|
SDK primitive first, then consume that SDK surface from the presentation layer.
|
|
289
292
|
|
|
293
|
+
Progressive tool discovery follows this boundary directly: `src/sdk/mcp/discovery.ts` owns ranking, authorization filtering, budgets, cursors, omissions, cache identity, and the canonical result contract. `src/mcp/server.ts` only negotiates the extension, selects the entry catalog, and adapts compatibility text. See [Progressive Tool Discovery](PROGRESSIVE_TOOL_DISCOVERY.md).
|
|
294
|
+
|
|
290
295
|
## Telemetry Schema Negotiation
|
|
291
296
|
|
|
292
297
|
Telemetry preserves wire compatibility through an explicit client/server negotiation split:
|
|
@@ -55,7 +55,7 @@ This clones the repo, reads `.claude-plugin/marketplace.json` at the root, insta
|
|
|
55
55
|
claude mcp add --transport stdio pm-mcp -- npx -y --package=@unbrained/pm-cli@latest pm-mcp
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Gives you the
|
|
58
|
+
Gives you the 32 MCP tools without skills or slash commands.
|
|
59
59
|
|
|
60
60
|
### 3. Direct project `.mcp.json` (project-scoped MCP only)
|
|
61
61
|
|
|
@@ -117,7 +117,7 @@ pnpm smoke:claude-plugin
|
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
Verifies: plugin file structure, manifest name consistency, stateless MCP
|
|
120
|
-
discovery for `2026-07-28`,
|
|
120
|
+
discovery for `2026-07-28`, 32 tools present, full workflow (init → create →
|
|
121
121
|
claim → update → link files/docs/tests → get → context → search → validate →
|
|
122
122
|
health), and session-start hook.
|
|
123
123
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Context Integrity and Recovery Primitives
|
|
2
2
|
|
|
3
|
-
Tracker: [pm-hu92i3](../.agents/pm/issues/pm-hu92i3.toon), [pm-nc94mk](../.agents/pm/issues/pm-nc94mk.toon), [pm-xrjy8o](../.agents/pm/issues/pm-xrjy8o.toon), [pm-f60039](../.agents/pm/issues/pm-f60039.toon), [pm-2qahia](../.agents/pm/issues/pm-2qahia.toon), [pm-z1z96w](../.agents/pm/issues/pm-z1z96w.toon), [pm-yhle2e](../.agents/pm/issues/pm-yhle2e.toon), [pm-t7wl00](../.agents/pm/issues/pm-t7wl00.toon), [pm-hiqlkh](../.agents/pm/issues/pm-hiqlkh.toon), [pm-ntnv4k](../.agents/pm/issues/pm-ntnv4k.toon), [pm-sxg7wl](../.agents/pm/issues/pm-sxg7wl.toon), [pm-xspd](../.agents/pm/chores/pm-xspd.toon)
|
|
3
|
+
Tracker: [pm-dn8rwl](../.agents/pm/issues/pm-dn8rwl.toon), [pm-r97901](../.agents/pm/issues/pm-r97901.toon), [pm-hu92i3](../.agents/pm/issues/pm-hu92i3.toon), [pm-nc94mk](../.agents/pm/issues/pm-nc94mk.toon), [pm-xrjy8o](../.agents/pm/issues/pm-xrjy8o.toon), [pm-f60039](../.agents/pm/issues/pm-f60039.toon), [pm-2qahia](../.agents/pm/issues/pm-2qahia.toon), [pm-z1z96w](../.agents/pm/issues/pm-z1z96w.toon), [pm-yhle2e](../.agents/pm/issues/pm-yhle2e.toon), [pm-t7wl00](../.agents/pm/issues/pm-t7wl00.toon), [pm-hiqlkh](../.agents/pm/issues/pm-hiqlkh.toon), [pm-ntnv4k](../.agents/pm/issues/pm-ntnv4k.toon), [pm-sxg7wl](../.agents/pm/issues/pm-sxg7wl.toon), [pm-xspd](../.agents/pm/chores/pm-xspd.toon)
|
|
4
4
|
|
|
5
5
|
`project management = context management`: a successful command is useful only when the context it records and later returns is truthful, lossless, and actionable. These SDK-owned primitives are shared by the CLI and package integrations.
|
|
6
6
|
|
|
@@ -46,11 +46,29 @@ When multiple entries exist, the SDK selects the requested package by key or `na
|
|
|
46
46
|
|
|
47
47
|
## History and merge recovery
|
|
48
48
|
|
|
49
|
-
History hash capability 3 distinguishes the current canonical writer surface
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
History hash capability 3 distinguishes the current canonical writer surface
|
|
50
|
+
from older writers while retaining frozen readers for epochs 1 and 2. Two
|
|
51
|
+
writer surfaces were historically emitted under epoch 2: its earlier form
|
|
52
|
+
excluded linked-test workspace/provenance fields and test-run execution
|
|
53
|
+
receipts while normalizing dependency ids; its later form included those
|
|
54
|
+
fields. Verification recognizes both immutable forms and requires each entry's
|
|
55
|
+
before/after hashes to use one consistent form. Current-document verification
|
|
56
|
+
selects the candidate that matches the verified chain head. An unsupported
|
|
57
|
+
epoch is version skew, not permission to reinterpret or silently normalize
|
|
58
|
+
history.
|
|
59
|
+
|
|
60
|
+
Health keeps the metadata-only cache path for clean history streams. A cached hash mismatch, chain mismatch, or writer-version skew is only a candidate: health rereads canonical item sources and verifies stream content hashes before reporting corruption. The `history_drift` details expose `cache_confirmation` candidate, confirmed, and resolved-false-positive item sets, while `cache_hit_verification: metadata_then_content_hash` identifies the authoritative fallback. Drift-cache envelopes carry both their schema version and the current item-hash capability, so changing legacy canonicalization invalidates prior verdicts and a runtime with incompatible hash semantics rebuilds the cache instead of trusting it.
|
|
61
|
+
|
|
62
|
+
Merge reconciliation may consume a durable hash-only receipt without `--force` only when its canonical item path, complete declared-field set, and every merged-value hash exactly match the current item snapshot. Raw discarded values remain clone-local. Any incomplete or mismatched proof fails closed. SDK gates use `inspectMergeReceiptEvidence` or `runMergeReceiptEvidenceReport` to retain the distinction between no evidence, rejected evidence, and clone-local evidence whose Git directory could not be resolved; `clone_local_evidence_resolved=false` makes the loss-aware report incomplete. Rejected evidence carries bounded privacy-safe source, reason, and receipt-id-or-hash locators, while the exact count and truncation receipt preserve completeness under an agent token budget. Cross-platform ancestor inspection distinguishes an absent receipt store from an unreadable or non-directory root. The list-only and legacy report compatibility projections intentionally return valid receipts only. Diverged history unions also fail closed when deterministic suffix ordering would make any patch operation inapplicable, instead of publishing a rehashed stream with a skipped branch effect.
|
|
63
|
+
|
|
64
|
+
History receipt summaries are durable references, not cleanup hints. Health
|
|
65
|
+
checks each `context.merge.receipts[].receipt_id` against valid pending and
|
|
66
|
+
reconciled authoritative evidence. A missing reference is returned with its
|
|
67
|
+
item id and one-based history line; unsafe identifiers are SHA-256 locators
|
|
68
|
+
rather than echoed content. Detail is capped at 100 while the exact count and a
|
|
69
|
+
truncation receipt preserve loss awareness. Recovery means restoring the named
|
|
70
|
+
receipt from an authoritative clone or backup. Rewriting append-only history or
|
|
71
|
+
deleting sibling evidence is never an automatic remediation.
|
|
54
72
|
|
|
55
73
|
## Strict-create recovery
|
|
56
74
|
|
package/docs/MCP_2026_07_28.md
CHANGED
|
@@ -12,7 +12,9 @@ by [pm-v7e337](../.agents/pm/features/pm-v7e337.toon),
|
|
|
12
12
|
[pm-3zh9s4](../.agents/pm/features/pm-3zh9s4.toon), and
|
|
13
13
|
[pm-vzcisw](../.agents/pm/chores/pm-vzcisw.toon). Skills and Apps are tracked
|
|
14
14
|
by [pm-8nzivt](../.agents/pm/features/pm-8nzivt.toon) and
|
|
15
|
-
[pm-pznhee](../.agents/pm/features/pm-pznhee.toon).
|
|
15
|
+
[pm-pznhee](../.agents/pm/features/pm-pznhee.toon). Progressive tool discovery
|
|
16
|
+
and canonical model-facing results are tracked by
|
|
17
|
+
[pm-3g3f8z](../.agents/pm/features/pm-3g3f8z.toon).
|
|
16
18
|
|
|
17
19
|
Status: accepted. MCP `2026-07-28` is pm's canonical protocol revision.
|
|
18
20
|
|
|
@@ -39,16 +41,18 @@ capabilities, public cache policy, server identity, and bounded instructions.
|
|
|
39
41
|
No modern request reads identity, capabilities, or version from a previous
|
|
40
42
|
request.
|
|
41
43
|
|
|
44
|
+
The optional `dev.unbrained.pm/progressive-tool-discovery` extension is also request-local. Negotiated clients receive a stable five-tool entry catalog and expand it with `pm_discover`; unnegotiated clients retain the complete profile-selected list. Negotiated tool results designate `structuredContent.result` as canonical and use text content only as a pointer, while the isolated compatibility adapter retains duplicated JSON text for existing clients. The public SDK owns ranking, cursor binding, token and omission receipts, and cache invalidation. See [Progressive Tool Discovery](PROGRESSIVE_TOOL_DISCOVERY.md).
|
|
45
|
+
|
|
42
46
|
## Legacy boundary
|
|
43
47
|
|
|
44
48
|
Legacy means every revision that establishes a session with an `initialize`
|
|
45
49
|
handshake, which the canonical revision defines as `2025-11-25` and earlier.
|
|
46
50
|
The stdio adapter therefore accepts the full initialize-era set, newest first:
|
|
47
51
|
|
|
48
|
-
| Revision
|
|
49
|
-
|
|
50
|
-
| `2025-11-25` | yes
|
|
51
|
-
| `2025-06-18` | yes
|
|
52
|
+
| Revision | Accepted at `initialize` | Notes |
|
|
53
|
+
| ------------ | ------------------------ | -------------------------------------------------------------------- |
|
|
54
|
+
| `2025-11-25` | yes | Final legacy revision; offered when a client omits `protocolVersion` |
|
|
55
|
+
| `2025-06-18` | yes | Earlier legacy revision retained for existing consumers |
|
|
52
56
|
|
|
53
57
|
`initialize` answers with the revision the client requested, because a legacy
|
|
54
58
|
client has no fall-forward mechanism and treats the answered version as the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Runtime capability surfaces
|
|
2
2
|
|
|
3
|
-
Tracker: [pm-xwah](../.agents/pm/chores/pm-xwah.toon), [pm-kxci8x](../.agents/pm/tasks/pm-kxci8x.toon), [pm-mu8m](../.agents/pm/tasks/pm-mu8m.toon), [pm-9k90](../.agents/pm/features/pm-9k90.toon), [pm-m4ikkz](../.agents/pm/features/pm-m4ikkz.toon), [pm-yf07b7](../.agents/pm/features/pm-yf07b7.toon)
|
|
3
|
+
Tracker: [pm-xwah](../.agents/pm/chores/pm-xwah.toon), [pm-kxci8x](../.agents/pm/tasks/pm-kxci8x.toon), [pm-mu8m](../.agents/pm/tasks/pm-mu8m.toon), [pm-9k90](../.agents/pm/features/pm-9k90.toon), [pm-m4ikkz](../.agents/pm/features/pm-m4ikkz.toon), [pm-yf07b7](../.agents/pm/features/pm-yf07b7.toon), and [pm-3g3f8z](../.agents/pm/features/pm-3g3f8z.toon).
|
|
4
4
|
|
|
5
5
|
The SDK exports one agent capability contract for command visibility, MCP tool profiles, resources, and workflow prompts. CLI-facing generators and MCP hosts should project from these contracts instead of maintaining independent allowlists.
|
|
6
6
|
|
|
@@ -15,6 +15,12 @@ Set `PM_MCP_PROFILE` for the `pm-mcp` process:
|
|
|
15
15
|
|
|
16
16
|
An activated extension command makes `pm_run` discoverable outside a custom profile. Its action is added to the live `pm_run` action enumeration. Custom profiles remain exact allowlists.
|
|
17
17
|
|
|
18
|
+
## Progressive discovery
|
|
19
|
+
|
|
20
|
+
Modern clients may negotiate `dev.unbrained.pm/progressive-tool-discovery`. Their initial `tools/list` response is the stable five-tool entry catalog, and `pm_discover` expands authorized tools by intent, family, and tier under explicit row and token ceilings. Unnegotiated and initialize-era clients retain the full selected profile.
|
|
21
|
+
|
|
22
|
+
The SDK ranks with visible lexical, semantic, graph, permission, freshness, and usage signals, binds cursors to all ranking inputs, and emits omission recovery plus cache invalidation metadata. Negotiated tool calls make `structuredContent.result` canonical and replace duplicated JSON text with a pointer; compatibility clients retain the old representation. See [Progressive Tool Discovery](PROGRESSIVE_TOOL_DISCOVERY.md).
|
|
23
|
+
|
|
18
24
|
Package authors can declare `tier: "core" | "standard" | "full" | "internal"` on `registerCommand()` definitions. The default is `standard`; `internal` commands remain callable by native dispatch but are not advertised by normal MCP profiles.
|
|
19
25
|
|
|
20
26
|
## Workspace schema projection
|