@unbrained/pm-cli 2026.8.27 → 2026.8.29
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/CHANGELOG.md +49 -1
- package/dist/cli/error-guidance.js +61 -7
- package/dist/cli/register-annotations.js +58 -41
- package/dist/cli-bundle/bundle-manifest.json +142 -142
- package/dist/cli-bundle/chunks/{chunk-TNX6HC54.js → chunk-2MKLQL2E.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-SKXLJIEK.js → chunk-2UETTHY6.js} +49 -49
- package/dist/cli-bundle/chunks/{chunk-SHMDY36D.js → chunk-3TEF3HM5.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-SH6P7FXI.js → chunk-7SKMLSMX.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-7X6OZEIV.js +34 -0
- package/dist/cli-bundle/chunks/{chunk-CVBBGWW5.js → chunk-CMIEDWAY.js} +54 -50
- package/dist/cli-bundle/chunks/{chunk-3OO3W6FW.js → chunk-GNWPTEBW.js} +64 -61
- package/dist/cli-bundle/chunks/chunk-MTTKAUBX.js +2 -0
- package/dist/cli-bundle/chunks/chunk-N6OR6FAR.js +13 -0
- package/dist/cli-bundle/chunks/{chunk-R4ETAOJC.js → chunk-TGNAA66W.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-52EKTW6V.js → chunk-WS2GJQVG.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-EUWM6VII.js → register-list-query-OMKJ5ONN.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-FD4HSAVU.js → register-mutation-QBSNCOY3.js} +4 -4
- package/dist/cli-bundle/chunks/{register-operations-HRMNFEC3.js → register-operations-UC77FQZC.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-33GNICLX.js → register-setup-GLQ5SYV3.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-HC7ODMH3.js → chunk-3AVAWVZA.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-3UMBF26I.js +158 -0
- package/dist/cli-bundle/focused-chunks/{chunk-YVVZ3LQ6.js → chunk-73EDXGYE.js} +16 -15
- package/dist/cli-bundle/focused-chunks/{chunk-4BR5UU52.js → chunk-AXZ5AJAF.js} +45 -45
- package/dist/cli-bundle/focused-chunks/{chunk-MXTYGECH.js → chunk-G6PATS5E.js} +3 -3
- package/dist/cli-bundle/focused-chunks/{chunk-SUBSWYW3.js → chunk-JZYPPMXF.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-LLNTHF5X.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-LYFWQMVC.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-RKOKG43G.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-TDK3OCX3.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-EKX37ZHA.js → chunk-THEPQMLX.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-FC2AXLB5.js → chunk-XXOWRJZ5.js} +2 -2
- package/dist/cli-bundle/main.js +14 -14
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +31 -32
- 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 +34 -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 +16 -6
- package/dist/core/history/drift-scan.d.ts +2 -0
- package/dist/core/history/drift-scan.js +72 -36
- package/dist/core/history/history.d.ts +3 -3
- package/dist/core/history/history.js +5 -5
- package/dist/core/history/replay.d.ts +1 -1
- package/dist/core/history/replay.js +50 -19
- package/dist/core/item/actionability.d.ts +7 -1
- package/dist/core/item/actionability.js +44 -11
- package/dist/core/item/dependency-reference.d.ts +10 -0
- package/dist/core/item/dependency-reference.js +21 -0
- package/dist/core/item/parse.d.ts +4 -0
- package/dist/core/item/parse.js +38 -16
- package/dist/core/shared/author.js +69 -17
- package/dist/mcp/tool-definitions.js +5 -5
- package/dist/sdk/agent/command-recovery.js +3 -3
- package/dist/sdk/agent/task-transcript-contracts.d.ts +52 -0
- package/dist/sdk/agent/task-transcript-contracts.js +198 -0
- package/dist/sdk/annotations.d.ts +5 -2
- package/dist/sdk/annotations.js +66 -36
- package/dist/sdk/cli-bootstrap.d.ts +2 -8
- package/dist/sdk/cli-bootstrap.js +7 -67
- package/dist/sdk/cli-contracts/bootstrap-command-scanner.d.ts +23 -0
- package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +80 -0
- package/dist/sdk/cli-contracts/flag-contracts.js +5 -2
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +5 -5
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +15 -2
- package/dist/sdk/cli-contracts/tool-schema.d.ts +1 -1
- package/dist/sdk/cli-contracts/tool-schema.js +16 -3
- package/dist/sdk/comments.d.ts +4 -0
- package/dist/sdk/comments.js +2 -2
- package/dist/sdk/completion.js +9 -5
- package/dist/sdk/contracts.d.ts +1 -0
- package/dist/sdk/contracts.js +3 -2
- package/dist/sdk/dependency-provenance.d.ts +51 -2
- package/dist/sdk/dependency-provenance.js +91 -7
- package/dist/sdk/extension/install-sources.d.ts +8 -1
- package/dist/sdk/extension/install-sources.js +132 -39
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +74 -14
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +86 -38
- package/dist/sdk/governance/health.js +86 -22
- package/dist/sdk/governance/validate-history-drift.js +5 -2
- package/dist/sdk/graph/assembly.js +51 -22
- package/dist/sdk/history-repair.d.ts +1 -1
- package/dist/sdk/history-repair.js +20 -10
- package/dist/sdk/index.d.ts +1 -0
- package/dist/sdk/index.js +3 -2
- package/dist/sdk/learnings.d.ts +4 -0
- package/dist/sdk/learnings.js +7 -4
- package/dist/sdk/lifecycle/close.js +4 -3
- package/dist/sdk/lifecycle/create.js +43 -19
- package/dist/sdk/linked-artifacts.js +21 -31
- package/dist/sdk/merge/reconcile.js +3 -16
- package/dist/sdk/notes.d.ts +4 -0
- package/dist/sdk/notes.js +2 -2
- package/dist/sdk/query/next.d.ts +6 -0
- package/dist/sdk/query/next.js +7 -2
- package/dist/sdk/runtime-input.js +5 -2
- package/dist/sdk/runtime.d.ts +6 -6
- package/dist/sdk/runtime.js +8 -8
- package/dist/sdk/test/entry.d.ts +1 -1
- package/dist/sdk/test/entry.js +5 -9
- package/dist/sdk/test/execution.d.ts +3 -0
- package/dist/sdk/test/execution.js +54 -13
- package/docs/COMMANDS.md +4 -3
- package/docs/CONTEXT_RECOVERY_PRIMITIVES.md +67 -0
- package/docs/MERGE_SAFETY.md +23 -10
- package/docs/OUTPUT_TOKEN_ACCOUNTING.md +20 -7
- package/docs/README.md +1 -0
- package/docs/RELEASING.md +9 -1
- package/docs/SDK.md +12 -0
- package/docs/SDK_CONTEXT_INTEGRITY.md +12 -1
- package/docs/SDK_RUNTIME_BOUNDARIES.md +10 -0
- package/docs/TESTING.md +6 -2
- package/docs/agent-task-token-baseline.json +97 -11
- package/docs/agent-task-transcripts.json +211 -0
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +3 -3
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +13 -4
- package/docs/performance/cli-transport-overhead.md +10 -2
- package/marketplace.json +2 -2
- package/package.json +8 -8
- 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-codex/.codex-plugin/plugin.json +1 -1
- package/sdk/public-surface.json +297 -36
- package/dist/cli-bundle/chunks/chunk-MFNTKMTI.js +0 -13
- package/dist/cli-bundle/chunks/chunk-OS27HHBN.js +0 -35
- package/dist/cli-bundle/chunks/chunk-QTO7USTH.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-2AGZ5BRT.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-AQ5IYEZZ.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-XDPYBQCF.js +0 -156
- package/dist/cli-bundle/focused-chunks/chunk-Y5A7SJJ7.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-YHWHX6YY.js +0 -2
|
@@ -1,11 +1,11 @@
|
|
|
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]="8d85d220-dad2-5234-8819-65632275eb47")}catch(e){}}();
|
|
3
3
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
4
4
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
5
5
|
import { resolvePmRoot } from "../../core/store/paths.js";
|
|
6
6
|
import { runHistoryRepair, runHistoryRepairAll, } from "../history-repair.js";
|
|
7
7
|
import { runValidate } from "../governance/validate.js";
|
|
8
|
-
import { inspectMergeReceiptEvidence, markMergeReceiptReconciled,
|
|
8
|
+
import { inspectMergeReceiptEvidence, markMergeReceiptReconciled, summarizeMergeReceipt, } from "./receipts.js";
|
|
9
9
|
import { findGitWorkspaceRoot } from "./install.js";
|
|
10
10
|
import { mapWithFixedConcurrency } from "../extension/concurrency.js";
|
|
11
11
|
const RECEIPT_ONLY_REPAIR_CONCURRENCY = 4;
|
|
@@ -95,19 +95,6 @@ export async function runMergeReconcile(options, global) {
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
const pendingReceipts = receiptEvidence.receipts;
|
|
98
|
-
const { pendingDecisions: discardedReceipts } = partitionMergeReceipts(pendingReceipts);
|
|
99
|
-
if (!dryRun && options.force !== true && discardedReceipts.length > 0) {
|
|
100
|
-
throw new PmCliError(`Merge reconciliation would accept ${discardedReceipts.length} receipt(s) containing discarded scalar values. Review pm merge report and rerun with --force only after deciding which values to retain or re-apply.`, EXIT_CODE.CONFLICT, {
|
|
101
|
-
code: "merge_reconcile_discards_require_acceptance",
|
|
102
|
-
required: "Explicitly review every discarded field before accepting reconciliation.",
|
|
103
|
-
nextSteps: discardedReceipts.map((receipt) => `Review receipt ${receipt.id} for ${receipt.item_id}: ${receipt.decisions
|
|
104
|
-
.map((decision) => decision.field)
|
|
105
|
-
.join(", ")}.`),
|
|
106
|
-
recovery: {
|
|
107
|
-
suggested_retry: "pm merge reconcile --dry-run",
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
98
|
const receiptsByItem = new Map();
|
|
112
99
|
for (const receipt of pendingReceipts) {
|
|
113
100
|
const receipts = receiptsByItem.get(receipt.item_id) ?? [];
|
|
@@ -193,4 +180,4 @@ export async function runMergeReconcile(options, global) {
|
|
|
193
180
|
};
|
|
194
181
|
}
|
|
195
182
|
//# sourceMappingURL=reconcile.js.map
|
|
196
|
-
//# debugId=
|
|
183
|
+
//# debugId=8d85d220-dad2-5234-8819-65632275eb47
|
package/dist/sdk/notes.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface NotesCommandOptions {
|
|
|
30
30
|
includeMeta?: boolean;
|
|
31
31
|
/** Return complete note history after a mutation instead of a bounded receipt. */
|
|
32
32
|
fullHistory?: boolean;
|
|
33
|
+
/** Append only when no note has the same resolved author and text. */
|
|
34
|
+
ifAbsent?: boolean;
|
|
33
35
|
/** Value that configures or reports author for this contract. */
|
|
34
36
|
author?: string;
|
|
35
37
|
/** Human-readable explanation suitable for logs and agent-facing output. */
|
|
@@ -59,6 +61,8 @@ export interface NotesResult {
|
|
|
59
61
|
mutation_receipt?: AnnotationMutationReceipt;
|
|
60
62
|
/** Declares whether older notes were withheld from a mutation response. */
|
|
61
63
|
omission_receipt?: AnnotationOmissionReceipt;
|
|
64
|
+
/** Whether a requested mutation changed persisted state. */
|
|
65
|
+
changed?: boolean;
|
|
62
66
|
}
|
|
63
67
|
/** Implements run notes for the public runtime surface of this module. */
|
|
64
68
|
export declare function runNotes(id: string, options: NotesCommandOptions, global: GlobalOptions): Promise<NotesResult>;
|
package/dist/sdk/notes.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements the pm notes command surface and its agent-facing runtime behavior.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!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]="
|
|
7
|
+
!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]="216389a2-fb85-56a0-8ef2-9f41f1321c16")}catch(e){}}();
|
|
8
8
|
import { EXIT_CODE, PmCliError, parseLimit, stableStringify, } from "./runtime-primitives.js";
|
|
9
9
|
import { limitAnnotationEntries, parseAnnotationTextInput, resolveAnnotationInput, runAnnotationCommand, } from "./annotations.js";
|
|
10
10
|
function parseStructuredEventPayload(raw) {
|
|
@@ -137,4 +137,4 @@ export async function runNotes(id, options, global) {
|
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
//# sourceMappingURL=notes.js.map
|
|
140
|
-
//# debugId=
|
|
140
|
+
//# debugId=216389a2-fb85-56a0-8ef2-9f41f1321c16
|
package/dist/sdk/query/next.d.ts
CHANGED
|
@@ -60,6 +60,12 @@ export interface NextBlockerRef {
|
|
|
60
60
|
title: string | null;
|
|
61
61
|
/** Lifecycle state reported for status. */
|
|
62
62
|
status: ItemStatus | null;
|
|
63
|
+
/** Whether this reference must be checked outside the local tracker. */
|
|
64
|
+
external?: boolean;
|
|
65
|
+
/** Conservative timestamp from which the unresolved external block may be stale. */
|
|
66
|
+
blocked_since?: string;
|
|
67
|
+
/** External resolver identity, or null when no integration verified the reference. */
|
|
68
|
+
resolver?: string | null;
|
|
63
69
|
}
|
|
64
70
|
/**
|
|
65
71
|
* A classified actionable item on the `pm next` output: a {@link ContextFocusItem}
|
package/dist/sdk/query/next.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* re-deriving readiness from a full `pm context` snapshot every turn.
|
|
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]="9b788e0a-f4ac-5c6e-bf52-0ef0519d6925")}catch(e){}}();
|
|
13
13
|
import { collectBlockedByIds, computeActionabilityReport, } from "../actionability.js";
|
|
14
14
|
import { resolveRuntimeStatusRegistry, EXIT_CODE, PmCliError, nowIso, normalizeStatusInput, normalizeStatusForRegistry, resolvePmRoot, readSettings, resolveAuthor, } from "../runtime-primitives.js";
|
|
15
15
|
import { parseIntegerLimit } from "./parsers.js";
|
|
@@ -60,6 +60,11 @@ function toNextActionableItem(entry, statusRegistry, childrenByParent, rank) {
|
|
|
60
60
|
id: blocker.id,
|
|
61
61
|
title: blocker.title,
|
|
62
62
|
status: blocker.status,
|
|
63
|
+
...(blocker.external ? { external: true } : {}),
|
|
64
|
+
...(blocker.blocked_since
|
|
65
|
+
? { blocked_since: blocker.blocked_since }
|
|
66
|
+
: {}),
|
|
67
|
+
...(blocker.external ? { resolver: blocker.resolver ?? null } : {}),
|
|
63
68
|
})),
|
|
64
69
|
unblocks: entry.unblocks,
|
|
65
70
|
rank,
|
|
@@ -474,4 +479,4 @@ function formatNextLine(item) {
|
|
|
474
479
|
return `${rankToken}${item.id} p${item.priority} ${item.status} ${item.type} deadline:${deadlineToken}${parentToken}${unblocksToken} ${item.title}`;
|
|
475
480
|
}
|
|
476
481
|
//# sourceMappingURL=next.js.map
|
|
477
|
-
//# debugId=
|
|
482
|
+
//# debugId=9b788e0a-f4ac-5c6e-bf52-0ef0519d6925
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* primitives are shared by native action dispatchers and MCP-specific adapters.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
8
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="fde4fea5-94d6-53e2-bfbc-bff71d828c01")}catch(e){}}();
|
|
9
9
|
import { EXIT_CODE } from "../core/shared/constants.js";
|
|
10
10
|
import { PmCliError } from "../core/shared/errors.js";
|
|
11
11
|
import { asRecordClone } from "../core/shared/primitives.js";
|
|
@@ -202,6 +202,9 @@ const HOISTED_ACTION_OPTION_KEYS = {
|
|
|
202
202
|
update: ["parent", "allowMissingParent", "completedAt"],
|
|
203
203
|
copy: ["allowDuplicate"],
|
|
204
204
|
close: ["duplicateOf", "completedAt"],
|
|
205
|
+
comments: ["ifAbsent"],
|
|
206
|
+
notes: ["ifAbsent"],
|
|
207
|
+
learnings: ["ifAbsent"],
|
|
205
208
|
upgrade: [
|
|
206
209
|
"scope",
|
|
207
210
|
"dryRun",
|
|
@@ -586,4 +589,4 @@ export function updateManyOptionsFromFlat(options) {
|
|
|
586
589
|
};
|
|
587
590
|
}
|
|
588
591
|
//# sourceMappingURL=runtime-input.js.map
|
|
589
|
-
//# debugId=
|
|
592
|
+
//# debugId=fde4fea5-94d6-53e2-bfbc-bff71d828c01
|
package/dist/sdk/runtime.d.ts
CHANGED
|
@@ -157,11 +157,11 @@ export declare class PmClient {
|
|
|
157
157
|
stats<Options extends ReadOptions<StatsCommandOptions> = StatsCommandOptions>(options?: Options): ReadPromise<StatsResult, Options>;
|
|
158
158
|
/** Discover existing duplicate clusters without mutating tracker state. */
|
|
159
159
|
duplicates<Options extends ReadOptions<DuplicatesCommandOptions> = DuplicatesCommandOptions>(options?: Options): ReadPromise<DuplicatesResult, Options>;
|
|
160
|
-
/** List
|
|
160
|
+
/** List or mutate comments, including lock-scoped idempotent appends. */
|
|
161
161
|
comments<Options extends ReadOptions<CommentsCommandOptions> = CommentsCommandOptions>(id: string, options?: Options): ReadPromise<CommentsResult, Options>;
|
|
162
|
-
/** List or
|
|
162
|
+
/** List or mutate private notes, including lock-scoped idempotent appends. */
|
|
163
163
|
notes<Options extends ReadOptions<NotesCommandOptions> = NotesCommandOptions>(id: string, options?: Options): ReadPromise<NotesResult, Options>;
|
|
164
|
-
/** List or
|
|
164
|
+
/** List or mutate durable learnings, including lock-scoped idempotent appends. */
|
|
165
165
|
learnings(id: string, options?: LearningsCommandOptions): Promise<LearningsResult>;
|
|
166
166
|
/** Add, remove, clear, or list linked project files for an item. */
|
|
167
167
|
files<Options extends ReadOptions<FilesCommandOptions> = FilesCommandOptions>(id: string, options?: Options): ReadPromise<FilesResult, Options>;
|
|
@@ -373,11 +373,11 @@ export declare function aggregate<Options extends ReadOptions<AggregateOptions>
|
|
|
373
373
|
export declare function stats<Options extends ReadOptions<StatsCommandOptions> = StatsCommandOptions>(options?: Options, clientOptions?: PmClientOptions): ReadPromise<StatsResult, Options>;
|
|
374
374
|
/** Discover duplicate clusters without constructing a reusable client. */
|
|
375
375
|
export declare function duplicates<Options extends ReadOptions<DuplicatesCommandOptions> = DuplicatesCommandOptions>(options?: Options, clientOptions?: PmClientOptions): ReadPromise<DuplicatesResult, Options>;
|
|
376
|
-
/** List
|
|
376
|
+
/** List or mutate comments with optional idempotency without constructing a client. */
|
|
377
377
|
export declare function comments<Options extends ReadOptions<CommentsCommandOptions> = CommentsCommandOptions>(id: string, options?: Options, clientOptions?: PmClientOptions): ReadPromise<CommentsResult, Options>;
|
|
378
|
-
/** List or
|
|
378
|
+
/** List or mutate private notes with optional idempotency without constructing a client. */
|
|
379
379
|
export declare function notes<Options extends ReadOptions<NotesCommandOptions> = NotesCommandOptions>(id: string, options?: Options, clientOptions?: PmClientOptions): ReadPromise<NotesResult, Options>;
|
|
380
|
-
/** List or
|
|
380
|
+
/** List or mutate durable learnings with optional idempotency without constructing a client. */
|
|
381
381
|
export declare function learnings(id: string, options?: LearningsCommandOptions, clientOptions?: PmClientOptions): Promise<LearningsResult>;
|
|
382
382
|
/** Manage linked item files without constructing a reusable client. */
|
|
383
383
|
export declare function files<Options extends ReadOptions<FilesCommandOptions> = FilesCommandOptions>(id: string, options?: Options, clientOptions?: PmClientOptions): ReadPromise<FilesResult, Options>;
|
package/dist/sdk/runtime.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Defines public SDK APIs and package-author helpers for Runtime.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!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]="
|
|
7
|
+
!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]="b67ba0fe-bbca-57ab-b3be-a4a028f21de4")}catch(e){}}();
|
|
8
8
|
export { PM_GITIGNORE_END, PM_GITIGNORE_START, ensurePmGitignore, getPmGitignoreBlock, } from "./workspace.js";
|
|
9
9
|
export { SEARCH_EXTENSION_FLAG_DEFINITIONS } from "./extension-contracts.js";
|
|
10
10
|
export * from "./cli-contracts/agent-output-contracts.js";
|
|
@@ -212,15 +212,15 @@ export class PmClient {
|
|
|
212
212
|
duplicates(options = {}) {
|
|
213
213
|
return this.runTyped("duplicates", { options });
|
|
214
214
|
}
|
|
215
|
-
/** List
|
|
215
|
+
/** List or mutate comments, including lock-scoped idempotent appends. */
|
|
216
216
|
comments(id, options = {}) {
|
|
217
217
|
return this.runTyped("comments", { id, options });
|
|
218
218
|
}
|
|
219
|
-
/** List or
|
|
219
|
+
/** List or mutate private notes, including lock-scoped idempotent appends. */
|
|
220
220
|
notes(id, options = {}) {
|
|
221
221
|
return this.runTyped("notes", { id, options });
|
|
222
222
|
}
|
|
223
|
-
/** List or
|
|
223
|
+
/** List or mutate durable learnings, including lock-scoped idempotent appends. */
|
|
224
224
|
learnings(id, options = {}) {
|
|
225
225
|
return this.runTyped("learnings", { id, options });
|
|
226
226
|
}
|
|
@@ -744,15 +744,15 @@ export function stats(options = {}, clientOptions = {}) {
|
|
|
744
744
|
export function duplicates(options = {}, clientOptions = {}) {
|
|
745
745
|
return new PmClient(clientOptions).duplicates(options);
|
|
746
746
|
}
|
|
747
|
-
/** List
|
|
747
|
+
/** List or mutate comments with optional idempotency without constructing a client. */
|
|
748
748
|
export function comments(id, options = {}, clientOptions = {}) {
|
|
749
749
|
return new PmClient(clientOptions).comments(id, options);
|
|
750
750
|
}
|
|
751
|
-
/** List or
|
|
751
|
+
/** List or mutate private notes with optional idempotency without constructing a client. */
|
|
752
752
|
export function notes(id, options = {}, clientOptions = {}) {
|
|
753
753
|
return new PmClient(clientOptions).notes(id, options);
|
|
754
754
|
}
|
|
755
|
-
/** List or
|
|
755
|
+
/** List or mutate durable learnings with optional idempotency without constructing a client. */
|
|
756
756
|
export function learnings(id, options = {}, clientOptions = {}) {
|
|
757
757
|
return new PmClient(clientOptions).learnings(id, options);
|
|
758
758
|
}
|
|
@@ -1933,4 +1933,4 @@ async function loadWorkspaceExtensionRegistrations(pmRoot, settings, cwd) {
|
|
|
1933
1933
|
}
|
|
1934
1934
|
}
|
|
1935
1935
|
//# sourceMappingURL=runtime.js.map
|
|
1936
|
-
//# debugId=
|
|
1936
|
+
//# debugId=b67ba0fe-bbca-57ab-b3be-a4a028f21de4
|
package/dist/sdk/test/entry.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Public contract for structured linked test keys, shared by SDK and presentation-layer consumers. */
|
|
2
2
|
export declare const STRUCTURED_LINKED_TEST_KEYS: readonly ["command", "cmd", "path", "scope", "timeout", "timeout_seconds", "pm_context_mode", "workspace_context_mode", "env_set", "env_clear", "shared_host_safe", "assert_stdout_contains", "assert_stdout_regex", "assert_stderr_contains", "assert_stderr_regex", "assert_stdout_min_lines", "assert_json_field_equals", "assert_json_field_gte", "note"];
|
|
3
|
-
/**
|
|
3
|
+
/** Classify linked-test plaintext through the shared structured-entry grammar. */
|
|
4
4
|
export declare function looksLikeStructuredLinkedTestEntry(raw: string): boolean;
|
|
5
5
|
/** Implements normalize structured linked test entry for the public runtime surface of this module. */
|
|
6
6
|
export declare function normalizeStructuredLinkedTestEntry(kv: Record<string, string>, optionName: "--add" | "--test"): Record<string, string>;
|
package/dist/sdk/test/entry.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Implements the pm linked test entry command surface and its agent-facing runtime behavior.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!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]="
|
|
7
|
+
!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]="b29e4416-82ab-585c-9456-718e4699566d")}catch(e){}}();
|
|
8
8
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
9
9
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
10
|
+
import { looksLikeStructuredKeyValueEntry } from "../../core/item/parse.js";
|
|
10
11
|
// Keys accepted inside a structured linked-test entry. `cmd` is an alias for
|
|
11
12
|
// `command`; everything else maps 1:1 to a LinkedTest field.
|
|
12
13
|
/** Public contract for structured linked test keys, shared by SDK and presentation-layer consumers. */
|
|
@@ -32,14 +33,9 @@ export const STRUCTURED_LINKED_TEST_KEYS = [
|
|
|
32
33
|
"note",
|
|
33
34
|
];
|
|
34
35
|
const STRUCTURED_LINKED_TEST_KEY_SET = new Set(STRUCTURED_LINKED_TEST_KEYS);
|
|
35
|
-
|
|
36
|
-
const STRUCTURED_LINKED_TEST_ENTRY_PATTERN = new RegExp(`^(?:[-*+]\\s+)?(?:${STRUCTURED_LINKED_TEST_KEY_PATTERN})\\s*[:=]`, "i");
|
|
37
|
-
/** Implements looks like structured linked test entry for the public runtime surface of this module. */
|
|
36
|
+
/** Classify linked-test plaintext through the shared structured-entry grammar. */
|
|
38
37
|
export function looksLikeStructuredLinkedTestEntry(raw) {
|
|
39
|
-
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
return STRUCTURED_LINKED_TEST_ENTRY_PATTERN.test(raw);
|
|
38
|
+
return looksLikeStructuredKeyValueEntry(raw, STRUCTURED_LINKED_TEST_KEYS);
|
|
43
39
|
}
|
|
44
40
|
/** Implements normalize structured linked test entry for the public runtime surface of this module. */
|
|
45
41
|
export function normalizeStructuredLinkedTestEntry(kv, optionName) {
|
|
@@ -72,4 +68,4 @@ export function normalizeStructuredLinkedTestEntry(kv, optionName) {
|
|
|
72
68
|
return normalizedKv;
|
|
73
69
|
}
|
|
74
70
|
//# sourceMappingURL=entry.js.map
|
|
75
|
-
//# debugId=
|
|
71
|
+
//# debugId=b29e4416-82ab-585c-9456-718e4699566d
|
|
@@ -234,6 +234,8 @@ declare function readJsonPathValue(root: unknown, fieldPath: string): {
|
|
|
234
234
|
value: unknown;
|
|
235
235
|
};
|
|
236
236
|
declare function evaluateLinkedTestAssertions(linkedTest: LinkedTest, stdout: string, stderr: string): string[];
|
|
237
|
+
/** Return whether a linked command applies a runner-level test-name filter that must match at least one test. */
|
|
238
|
+
declare function commandUsesTestNameFilter(command: string): boolean;
|
|
237
239
|
interface RunLinkedTestsOptions {
|
|
238
240
|
progress?: boolean;
|
|
239
241
|
sourceRoots?: LinkedTestSandboxSourceRoots;
|
|
@@ -303,6 +305,7 @@ export declare const _testOnlyTestCommand: {
|
|
|
303
305
|
buildPmContextMismatchHint: typeof buildPmContextMismatchHint;
|
|
304
306
|
commandInvokesPmCli: typeof commandInvokesPmCli;
|
|
305
307
|
commandInvokesPmTrackerReadCommand: typeof commandInvokesPmTrackerReadCommand;
|
|
308
|
+
commandUsesTestNameFilter: typeof commandUsesTestNameFilter;
|
|
306
309
|
copyIntoSandboxIfPresent: typeof copyIntoSandboxIfPresent;
|
|
307
310
|
countLinkedTestItemFiles: typeof countLinkedTestItemFiles;
|
|
308
311
|
ensureScope: typeof ensureScope;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements the pm test command surface and its agent-facing runtime behavior.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!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]="
|
|
7
|
+
!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]="de5bc6bf-fe31-5643-a809-ee9ca52aac48")}catch(e){}}();
|
|
8
8
|
import { assertInitializedTracker } from "../environment/tracker-preflight.js";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import { cp, mkdir, mkdtemp, open, readdir, rm, symlink, } from "node:fs/promises";
|
|
@@ -817,9 +817,17 @@ async function runLinkedTestCommand(command, timeoutMs, env, progressContext, pr
|
|
|
817
817
|
try {
|
|
818
818
|
child = createLinkedTestChild(command, env, stdoutHandle.fd, stderrHandle.fd, cwd);
|
|
819
819
|
}
|
|
820
|
-
|
|
820
|
+
catch (error) {
|
|
821
821
|
await Promise.all([stdoutHandle.close(), stderrHandle.close()]);
|
|
822
|
+
throw error;
|
|
822
823
|
}
|
|
824
|
+
const childClose = waitForLinkedTestChildClose(child);
|
|
825
|
+
let spawnError;
|
|
826
|
+
/* c8 ignore next 5 -- shell spawn error callbacks are non-deterministic across platforms. */
|
|
827
|
+
child.on("error", (error) => {
|
|
828
|
+
spawnError = error.message;
|
|
829
|
+
});
|
|
830
|
+
await Promise.all([stdoutHandle.close(), stderrHandle.close()]);
|
|
823
831
|
closeLinkedTestStdin(child);
|
|
824
832
|
const timers = {
|
|
825
833
|
heartbeat: beginLinkedTestProgress(progressContext, progressMode),
|
|
@@ -828,18 +836,13 @@ async function runLinkedTestCommand(command, timeoutMs, env, progressContext, pr
|
|
|
828
836
|
};
|
|
829
837
|
const requestTermination = createLinkedTestTerminationRequester(child, timers);
|
|
830
838
|
let timedOut = false;
|
|
831
|
-
let spawnError;
|
|
832
|
-
/* c8 ignore next 5 -- shell spawn error callbacks are non-deterministic across platforms. */
|
|
833
|
-
child.on("error", (error) => {
|
|
834
|
-
spawnError = error.message;
|
|
835
|
-
});
|
|
836
839
|
/* c8 ignore next 4 -- callback scheduling timing is non-deterministic under coverage instrumentation. */
|
|
837
840
|
timers.timedOutTimer = setTimeout(() => {
|
|
838
841
|
timedOut = true;
|
|
839
842
|
void requestTermination();
|
|
840
843
|
}, timeoutMs);
|
|
841
844
|
timers.timedOutTimer.unref?.();
|
|
842
|
-
const { code, signal } = await
|
|
845
|
+
const { code, signal } = await childClose;
|
|
843
846
|
clearLinkedTestTimers(timers);
|
|
844
847
|
const [stdout, stderr] = await Promise.all([
|
|
845
848
|
readLinkedTestCapture(stdoutPath),
|
|
@@ -1285,16 +1288,45 @@ const EMPTY_LINKED_TEST_RUN_PATTERNS = [
|
|
|
1285
1288
|
{ code: "no_tests_found", regex: /\bNo tests found\b/i },
|
|
1286
1289
|
{ code: "no_matching_tests", regex: /\bNo matching tests?\b/i },
|
|
1287
1290
|
{ code: "collected_zero_items", regex: /\bcollected 0 items?\b/i },
|
|
1291
|
+
{
|
|
1292
|
+
code: "reported_zero_passes",
|
|
1293
|
+
regex: /(?:^\s*(?:#|ℹ)?\s*tests\s+0\s*$[\s\S]*^\s*(?:#|ℹ)?\s*pass\s+0\s*$|^\s*(?:#|ℹ)?\s*pass\s+0\s*$[\s\S]*^\s*(?:#|ℹ)?\s*tests\s+0\s*$)/imu,
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
code: "reported_zero_tests",
|
|
1297
|
+
regex: /^\s*(?:#|ℹ)?\s*tests\s+0\s*$/imu,
|
|
1298
|
+
},
|
|
1299
|
+
];
|
|
1300
|
+
const POSITIVE_LINKED_TEST_RUN_PATTERNS = [
|
|
1301
|
+
/^\s*(?:#|ℹ)?\s*tests\s+[1-9]\d*\s*$/imu,
|
|
1302
|
+
/^\s*(?:#|ℹ)?\s*pass\s+[1-9]\d*\s*$/imu,
|
|
1303
|
+
/\bTests?\s+[1-9]\d*\s+passed\b/iu,
|
|
1304
|
+
/\b[1-9]\d*\s+passed\b/iu,
|
|
1305
|
+
/\bTests:\s+(?:.*\b)?[1-9]\d*\s+passed\b/iu,
|
|
1288
1306
|
];
|
|
1289
1307
|
function detectEmptyLinkedTestRun(stdout, stderr) {
|
|
1290
1308
|
const combined = `${stdout}\n${stderr}`;
|
|
1291
1309
|
for (const pattern of EMPTY_LINKED_TEST_RUN_PATTERNS) {
|
|
1292
1310
|
if (pattern.regex.test(combined)) {
|
|
1293
|
-
return pattern.code;
|
|
1311
|
+
return { code: pattern.code };
|
|
1294
1312
|
}
|
|
1295
1313
|
}
|
|
1296
1314
|
return null;
|
|
1297
1315
|
}
|
|
1316
|
+
/** Return whether runner output contains a recognized positive executed-test receipt. */
|
|
1317
|
+
function hasPositiveLinkedTestRunReceipt(stdout, stderr) {
|
|
1318
|
+
const combined = `${stdout}\n${stderr}`;
|
|
1319
|
+
return POSITIVE_LINKED_TEST_RUN_PATTERNS.some((pattern) => pattern.test(combined));
|
|
1320
|
+
}
|
|
1321
|
+
/** Return whether a linked command applies a runner-level test-name filter that must match at least one test. */
|
|
1322
|
+
function commandUsesTestNameFilter(command) {
|
|
1323
|
+
const normalized = normalizeCommandForValidation(command);
|
|
1324
|
+
if (/(?:^|\s)(?:--test-name-pattern|--testnamepattern)(?:=|\s)/u.test(normalized)) {
|
|
1325
|
+
return true;
|
|
1326
|
+
}
|
|
1327
|
+
return splitNormalizedCommandSegments(normalized).some((segment) => /(?:^|\s)-t(?:=|\s)/u.test(segment) &&
|
|
1328
|
+
segmentInvokesUnsafeDirectTestRunner(segment));
|
|
1329
|
+
}
|
|
1298
1330
|
function createLinkedTestSandboxLayout(sandboxRoot) {
|
|
1299
1331
|
return {
|
|
1300
1332
|
root: sandboxRoot,
|
|
@@ -1625,8 +1657,8 @@ function buildLinkedTestEmptyRunResult(params) {
|
|
|
1625
1657
|
execution_context: params.executionContext,
|
|
1626
1658
|
stdout: params.execution.stdout,
|
|
1627
1659
|
stderr: params.execution.stderr,
|
|
1628
|
-
error: `Linked test reported an empty test run (${params.emptyRunSignal})
|
|
1629
|
-
"Update test selection
|
|
1660
|
+
error: `Linked test reported an empty test run (${params.emptyRunSignal.code}). ` +
|
|
1661
|
+
"Update the test selection so at least one test executes; unfiltered commands may opt out by omitting --fail-on-empty-test-run.",
|
|
1630
1662
|
};
|
|
1631
1663
|
}
|
|
1632
1664
|
function buildLinkedTestCommandFailureResult(params) {
|
|
@@ -1643,11 +1675,19 @@ function buildLinkedTestCommandFailureResult(params) {
|
|
|
1643
1675
|
};
|
|
1644
1676
|
}
|
|
1645
1677
|
function buildLinkedTestPassedExecutionResult(params) {
|
|
1646
|
-
if (params.options?.failOnEmptyTestRun === true
|
|
1678
|
+
if (params.options?.failOnEmptyTestRun === true ||
|
|
1679
|
+
commandUsesTestNameFilter(params.linkedTest.command ?? "")) {
|
|
1647
1680
|
const emptyRunSignal = detectEmptyLinkedTestRun(params.execution.stdout, params.execution.stderr);
|
|
1648
1681
|
if (emptyRunSignal) {
|
|
1649
1682
|
return buildLinkedTestEmptyRunResult({ ...params, emptyRunSignal });
|
|
1650
1683
|
}
|
|
1684
|
+
if (commandUsesTestNameFilter(params.linkedTest.command ?? "") &&
|
|
1685
|
+
!hasPositiveLinkedTestRunReceipt(params.execution.stdout, params.execution.stderr)) {
|
|
1686
|
+
return buildLinkedTestEmptyRunResult({
|
|
1687
|
+
...params,
|
|
1688
|
+
emptyRunSignal: { code: "missing_positive_execution_receipt" },
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1651
1691
|
}
|
|
1652
1692
|
const assertionFailures = evaluateLinkedTestAssertions(params.linkedTest, params.execution.stdout, params.execution.stderr);
|
|
1653
1693
|
if (assertionFailures.length > 0) {
|
|
@@ -2129,6 +2169,7 @@ export const _testOnlyTestCommand = {
|
|
|
2129
2169
|
buildPmContextMismatchHint,
|
|
2130
2170
|
commandInvokesPmCli,
|
|
2131
2171
|
commandInvokesPmTrackerReadCommand,
|
|
2172
|
+
commandUsesTestNameFilter,
|
|
2132
2173
|
copyIntoSandboxIfPresent,
|
|
2133
2174
|
countLinkedTestItemFiles,
|
|
2134
2175
|
ensureScope,
|
|
@@ -2160,4 +2201,4 @@ export const _testOnlyTestCommand = {
|
|
|
2160
2201
|
summarizeRunResultStatuses,
|
|
2161
2202
|
};
|
|
2162
2203
|
//# sourceMappingURL=execution.js.map
|
|
2163
|
-
//# debugId=
|
|
2204
|
+
//# debugId=de5bc6bf-fe31-5643-a809-ee9ca52aac48
|
package/docs/COMMANDS.md
CHANGED
|
@@ -735,15 +735,16 @@ explicitly overriding terminal-state or lock conflicts.
|
|
|
735
735
|
|
|
736
736
|
```bash
|
|
737
737
|
pm comments <id> "Implemented command parsing fix."
|
|
738
|
+
pm comments <id> "Implemented command parsing fix." --author agent-a --if-absent
|
|
738
739
|
printf '%s\n' '## Verification summary' '- Linux pass' '- macOS pass' | pm comments <id> --stdin
|
|
739
740
|
pm comments <id> --file docs/release-evidence.md
|
|
740
741
|
pm comments <id> --edit 2 "Corrected: the regression was in the parser, not the renderer."
|
|
741
742
|
pm comments <id> --delete 3
|
|
742
|
-
pm notes <id> --add "Keep renderer changes isolated to TOON output."
|
|
743
|
-
pm learnings <id> --add "Use runtime contracts instead of duplicating flag lists."
|
|
743
|
+
pm notes <id> --add "Keep renderer changes isolated to TOON output." --if-absent
|
|
744
|
+
pm learnings <id> --add "Use runtime contracts instead of duplicating flag lists." --if-absent
|
|
744
745
|
```
|
|
745
746
|
|
|
746
|
-
Use comments for progress and evidence, notes for implementation context, and learnings for durable future guidance. All three accept `--text` as a hidden compatibility alias for canonical `--add`; comments also retain `--body`/`--comment`, and notes retain `--note`. Choose exactly one input source (`[text]`, an add alias, `--stdin`, or `--file`) per invocation. Conflicting alias values fail before mutation. To clean up obsolete orchestration notes, `--edit <index>` rewrites the comment at a 1-based index and `--delete <index>` removes it; both record history and honor ownership rules.
|
|
747
|
+
Use comments for progress and evidence, notes for implementation context, and learnings for durable future guidance. All three accept `--text` as a hidden compatibility alias for canonical `--add`; comments also retain `--body`/`--comment`, and notes retain `--note`. Choose exactly one input source (`[text]`, an add alias, `--stdin`, or `--file`) per invocation. Conflicting alias values fail before mutation. For retrying agents, `--if-absent` makes any annotation append idempotent by the resolved author plus exact normalized stored text: the first call returns `changed: true` and `mutation_receipt.changed_count: 1`; an exact retry returns `changed: false`, the existing entry position, and `changed_count: 0` without writing item or history state. Different authors remain distinct, and omitting `--if-absent` deliberately preserves duplicate-appending compatibility. The flag is append-only and is rejected for list, edit, and delete operations. To clean up obsolete orchestration notes, `--edit <index>` rewrites the comment at a 1-based index and `--delete <index>` removes it; both record history and honor ownership rules.
|
|
747
748
|
|
|
748
749
|
## Linked Artifacts
|
|
749
750
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Context Integrity and Recovery Primitives
|
|
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)
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
## Lossless structured values
|
|
8
|
+
|
|
9
|
+
CSV-style structured metadata preserves whitespace following literal commas. An escaped comma (`\,`) is consumed as one literal comma. Prefer quoted or structured forms when a value itself contains delimiters:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pm files pm-123 --add 'path="docs/name,with-comma.md",scope=project'
|
|
13
|
+
pm update pm-123 --annotation 'key=summary,value=Alpha\, beta'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Bare comma-separated file and documentation paths are refused because the CLI cannot know whether the comma separates two paths or belongs to one filename. Repeat the option for multiple values. A linked-test command followed by a structured tuple, such as `command,scope=project`, is refused for the same reason; use `command=<value>,scope=project`.
|
|
17
|
+
|
|
18
|
+
## Linked path write receipts
|
|
19
|
+
|
|
20
|
+
`pm files --add`, `pm docs --add`, and path migrations validate the resulting paths when the mutation changes stored metadata. The result includes `validation.missing_paths`, `validation.non_file_paths`, and `validation.remote_references`. Missing local paths remain permitted as deliberate forward references, but they are visible in the write receipt instead of becoming silent durable context.
|
|
21
|
+
|
|
22
|
+
## Executed-test evidence
|
|
23
|
+
|
|
24
|
+
A linked runner command carrying a name filter must execute at least one test. An explicit zero-test runner summary is classified as `failure_category: empty_run`, even when the process exits zero. A zero-pass count alone is not an empty-run receipt when the runner reports a positive test count. Filtered commands without either positive execution evidence or an explicit zero-test signal fail closed as `missing_positive_execution_receipt`; unfiltered commands retain the explicit `--fail-on-empty-test-run` policy.
|
|
25
|
+
|
|
26
|
+
## External blockers
|
|
27
|
+
|
|
28
|
+
URL and named external locators are not dangling local item identifiers. Validation excludes them from local dangling-reference remediation, and graph assembly materializes them as external predecessor nodes. Until an integration resolves the remote state, `pm next` retains the blocker and reports:
|
|
29
|
+
|
|
30
|
+
- `external: true`
|
|
31
|
+
- `blocked_since`: the holder's conservative last-mutation timestamp
|
|
32
|
+
- `resolver: null`
|
|
33
|
+
|
|
34
|
+
This makes an unverifiable or stale dependency visible without suggesting destructive removal.
|
|
35
|
+
|
|
36
|
+
Packages can add provider-specific resolution without coupling core to GitHub, Jira, Linear, or another service. Register an `ExternalDependencyResolver` through the public SDK and dispose it when the package deactivates. The SDK normalizes lifecycle state to `open`, `closed`, or `unknown`; caps remote titles at 240 characters and evidence locators at 2,048 characters; records `checked_at` plus the registered resolver name; and treats only trusted `closed` evidence as resolved. Provider errors and unsupported locators fail closed so another registered resolver can handle the reference or the blocker remains explicitly unverifiable.
|
|
37
|
+
|
|
38
|
+
## Package-manager receipt compatibility
|
|
39
|
+
|
|
40
|
+
The npm package source accepts both documented receipt families:
|
|
41
|
+
|
|
42
|
+
- npm 11: an array of package receipt objects
|
|
43
|
+
- npm 12: an object keyed by package name
|
|
44
|
+
|
|
45
|
+
When multiple entries exist, the SDK selects the requested package by key or `name`. A well-formed but unsupported or ambiguous JSON value fails with `npm_pack_json_shape_unsupported`; filename-only fallback is used only for genuinely non-JSON legacy output.
|
|
46
|
+
|
|
47
|
+
## History and merge recovery
|
|
48
|
+
|
|
49
|
+
History hash capability 3 distinguishes the current canonical writer surface from older epoch-2 writers while retaining readers for epochs 1 and 2. An unsupported epoch is version skew, not permission to reinterpret or silently normalize history.
|
|
50
|
+
|
|
51
|
+
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 also carry the current item-hash capability, so a runtime with incompatible hash semantics rebuilds the cache instead of trusting it.
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
## Strict-create recovery
|
|
56
|
+
|
|
57
|
+
When strict governance requires a collection, the recovery bundle lists the truthful empty declaration before an example that adds metadata:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
--clear-deps, --dep, --create-mode progressive
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The same contract covers comments, docs, events, files, learnings, notes, reminders, tests, and type options. Agents should never invent an edge or evidence row merely to satisfy a required-field gate.
|
|
64
|
+
|
|
65
|
+
## Duplication gates
|
|
66
|
+
|
|
67
|
+
`pnpm lint:duplicates` runs both the repository-wide clone gate and a lower-line production-source profile. The source profile covers `src`, `packages`, and `plugins` so a compact clone reported by hosted analysis cannot hide below the broader repository threshold.
|
package/docs/MERGE_SAFETY.md
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
Tracked by [pm-wc1r](../.agents/pm/features/pm-wc1r.toon), with the integrity and concurrency fixes [pm-9q2t](../.agents/pm/issues/pm-9q2t.toon), [pm-cxyv](../.agents/pm/issues/pm-cxyv.toon), [pm-gpo7](../.agents/pm/issues/pm-gpo7.toon), [pm-m3nl](../.agents/pm/issues/pm-m3nl.toon), [pm-wwfd](../.agents/pm/issues/pm-wwfd.toon), and [pm-xdn6](../.agents/pm/issues/pm-xdn6.toon). Fresh-init fence ownership is tracked by [pm-1w3ljt](../.agents/pm/issues/pm-1w3ljt.toon); runtime-cache index governance by [pm-hous](../.agents/pm/issues/pm-hous.toon); local allocation safety by [pm-khdq](../.agents/pm/issues/pm-khdq.toon); fence-coverage completeness and drift detection by [pm-i4fx](../.agents/pm/issues/pm-i4fx.toon); package-defined item coverage by [pm-5rexki](../.agents/pm/issues/pm-5rexki.toon); non-item JSON coverage by [pm-gjicmx](../.agents/pm/issues/pm-gjicmx.toon); portable driver identity by [pm-w91mvg](../.agents/pm/issues/pm-w91mvg.toon); pending receipt validation by [pm-ysqb6n](../.agents/pm/issues/pm-ysqb6n.toon); receipt classification by [pm-jtwsct](../.agents/pm/issues/pm-jtwsct.toon); direction-independent item conflict selection by [pm-dlx7v7](../.agents/pm/issues/pm-dlx7v7.toon); corrected reconciliation guidance by [pm-lwmstb](../.agents/pm/issues/pm-lwmstb.toon); cross-branch id collision safety by [pm-pibw](../.agents/pm/issues/pm-pibw.toon); auditable merge history by [pm-9j2r3b](../.agents/pm/tasks/pm-9j2r3b.toon); durable conflict decisions by [pm-rh98vo](../.agents/pm/issues/pm-rh98vo.toon); continuous conformance by [pm-76dnfg](../.agents/pm/tasks/pm-76dnfg.toon); workspace-wide CI enforcement by [pm-pdr8t1](../.agents/pm/tasks/pm-pdr8t1.toon); post-merge reconciliation by [pm-mfkv92](../.agents/pm/issues/pm-mfkv92.toon); linked-command execution trust by [pm-ed28wi](../.agents/pm/issues/pm-ed28wi.toon); this repository's own adoption by [pm-iwsj](../.agents/pm/chores/pm-iwsj.toon).
|
|
4
4
|
|
|
5
5
|
Lossless receipt health gating is tracked by
|
|
6
|
-
[pm-baksix](../.agents/pm/issues/pm-baksix.toon)
|
|
6
|
+
[pm-baksix](../.agents/pm/issues/pm-baksix.toon), and executable remediation
|
|
7
|
+
that performs settlement is tracked by
|
|
8
|
+
[pm-r0p3at](../.agents/pm/issues/pm-r0p3at.toon).
|
|
7
9
|
|
|
8
10
|
pm stores project context as reviewable repository files. Concurrent agents can therefore use ordinary branches and worktrees, but tracker artifacts need semantic merge behavior: raw line merging cannot preserve TOON collection counts, JSON object structure, or append-only history hash chains.
|
|
9
11
|
|
|
@@ -116,7 +118,7 @@ After every branch merge that touches `.agents/pm`, run:
|
|
|
116
118
|
```bash
|
|
117
119
|
pm merge reconcile --dry-run --json
|
|
118
120
|
pm merge reconcile --message "Reconcile merged tracker histories" --json
|
|
119
|
-
# Required only
|
|
121
|
+
# Required only to accept a receipt that lacks qualifying exact hash proof:
|
|
120
122
|
pm merge reconcile --force --message "Accept reviewed merge decisions" --json
|
|
121
123
|
```
|
|
122
124
|
|
|
@@ -125,10 +127,10 @@ Lossless receipts do not become discarded-value decisions and reconcile without
|
|
|
125
127
|
`--force`, but `pm health` reports `merge_receipts_pending:<n>` and remains
|
|
126
128
|
non-green until the apply pass settles them. `pm history-repair` cannot clear
|
|
127
129
|
that receipt finding. When a drifted item also has a pending receipt, the
|
|
128
|
-
`history_drift` remediation map prioritizes `pm merge reconcile
|
|
130
|
+
`history_drift` remediation map prioritizes `pm merge reconcile` only
|
|
129
131
|
when canonical item path, changed-field, and merged-value hash evidence all
|
|
130
132
|
attribute that finding to one or more receipts loaded from the clone-local Git
|
|
131
|
-
evidence store. Every field declared by each receipt must match its current merged-value
|
|
133
|
+
evidence store or the authoritative durable receipt store. Every field declared by each receipt must match its current merged-value
|
|
132
134
|
hash, even when only a subset appears in the history reconciliation diff.
|
|
133
135
|
Disjoint valid receipts may collectively cover a multi-field reconciliation;
|
|
134
136
|
the audit and settlement then retain every individually proven receipt id.
|
|
@@ -136,22 +138,33 @@ Serialized source claims are ignored. Receipt readers validate the complete
|
|
|
136
138
|
bounded schema, safe identifiers, filename and item-path identity, timestamps,
|
|
137
139
|
and bounded decision structure before a sidecar enters health or
|
|
138
140
|
reconciliation. Reads use size-preflighted, no-follow regular-file descriptors;
|
|
139
|
-
durable decisions must retain hash-only values.
|
|
141
|
+
durable decisions must retain hash-only values. Qualifying durable-only receipts
|
|
142
|
+
are reloaded from the authoritative store and accepted only after exact canonical
|
|
143
|
+
item path, declared-field, and merged-value hash verification. Legacy receipts,
|
|
140
144
|
receipts whose declared fields disagree with their hashes, same-item tampering,
|
|
141
145
|
and drift on unrelated items fail closed to the normal `pm history-repair`
|
|
142
|
-
guidance. Health indexes
|
|
146
|
+
guidance. Health indexes authoritative evidence once by item and reconciliation
|
|
143
147
|
uses the same per-item groups with a fixed receipt-only worker pool, so committed
|
|
144
148
|
sidecars cannot amplify drift scans into unbounded parallel repair work.
|
|
149
|
+
The machine-executable remediation names the apply command because `--dry-run`
|
|
150
|
+
returns a successful preview without changing the red health state. Operators
|
|
151
|
+
may still run the documented dry-run first; agents that execute the remediation
|
|
152
|
+
field verbatim perform the settlement instead of entering a successful no-op
|
|
153
|
+
loop.
|
|
145
154
|
Apply-mode reconciliation repeats the same proof against the exact
|
|
146
155
|
item snapshot used by the audited history rewrite. The audit event and
|
|
147
156
|
settlement include only the individually proven receipt id, so one valid receipt
|
|
148
157
|
cannot authorize an untrusted same-item sibling. Failed or unproven receipts
|
|
149
158
|
remain pending unless the coordinator explicitly reviews and supplies `--force`.
|
|
150
159
|
Receipts with discarded scalar values retain the distinct
|
|
151
|
-
`merge_decisions_unreviewed:<n>` finding
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
`merge_decisions_unreviewed:<n>` finding. Matching authoritative hash proof
|
|
161
|
+
allows those receipts to settle without `--force`; without qualifying proof,
|
|
162
|
+
the apply pass refuses them until the coordinator explicitly supplies `--force`
|
|
163
|
+
after review. The `history_drift_merge_receipt` remediation may therefore emit
|
|
164
|
+
an unforced apply command that clears `merge_decisions_unreviewed` when the
|
|
165
|
+
receipt proves the exact current merged values. This prevents routine repair
|
|
166
|
+
from hiding unfinished reconciliation while avoiding redundant force for an
|
|
167
|
+
already-proven canonical snapshot.
|
|
155
168
|
It exits nonzero while either merge-critical validation check is non-green, so
|
|
156
169
|
CI and explicit post-merge hooks cannot approve unresolved receipts or drift.
|
|
157
170
|
The apply pass uses the audited history rewrite boundary to append a
|