@unbrained/pm-cli 2026.9.3 → 2026.9.5
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/.agents/skills/HARNESS_COMPATIBILITY.md +1 -1
- package/.agents/skills/README.md +2 -2
- package/.agents/skills/pm-developer/SKILL.md +4 -1
- package/.agents/skills/pm-developer/references/COMMAND_PLAYBOOK.md +10 -0
- package/.agents/skills/pm-developer/references/GRAPH_AND_RELATIONSHIPS.md +3 -1
- package/.agents/skills/pm-extensions/SKILL.md +2 -2
- package/.agents/skills/pm-extensions/references/LIFECYCLE.md +3 -3
- package/.agents/skills/pm-extensions/references/TROUBLESHOOTING.md +3 -3
- package/.agents/skills/pm-user/SKILL.md +2 -0
- package/.agents/skills/pm-user/references/BACKLOG_SHAPING.md +5 -0
- package/.agents/skills/pm-user/references/WORKFLOWS.md +20 -5
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +52 -0
- package/dist/cli/commander-usage.d.ts +2 -0
- package/dist/cli/commander-usage.js +15 -12
- package/dist/cli/help-json-payload.js +12 -7
- package/dist/cli/main.js +15 -6
- package/dist/cli/register-list-query.js +4 -2
- package/dist/cli/register-mutation.js +4 -2
- package/dist/cli/register-operations.js +6 -2
- package/dist/cli-bundle/bundle-manifest.json +154 -154
- package/dist/cli-bundle/chunks/{chunk-EDPIS7EB.js → chunk-3HBDTJRK.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-66VPKFP2.js +5 -0
- package/dist/cli-bundle/chunks/{chunk-Y4PJ6HPK.js → chunk-HUAQITVW.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-DKGP3QLE.js → chunk-IBYREGSP.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-LUI4E32I.js +231 -0
- package/dist/cli-bundle/chunks/{chunk-UPPM2FOZ.js → chunk-QPZHQY2O.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-34HASX3S.js → chunk-SF6SM6U5.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-SVK5Y64D.js +40 -0
- package/dist/cli-bundle/chunks/chunk-V5GBD5KV.js +9 -0
- package/dist/cli-bundle/chunks/{chunk-LENC656S.js → chunk-WWAEGW4V.js} +67 -70
- package/dist/cli-bundle/chunks/chunk-ZRAQS3UA.js +8 -0
- package/dist/cli-bundle/chunks/{register-list-query-W2ZTOMKP.js → register-list-query-AYKGODWT.js} +3 -3
- package/dist/cli-bundle/chunks/{register-mutation-RVND5NCG.js → register-mutation-YZYG53SQ.js} +4 -4
- package/dist/cli-bundle/chunks/register-operations-KFSCC7QV.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-D3ZFHY3L.js → register-setup-UJCRVGBG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-24IG6ONG.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-M5M6SVHS.js → chunk-3ASU6WGI.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-3ITLIBKS.js +56 -0
- package/dist/cli-bundle/focused-chunks/chunk-3NQD5YDG.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-S27XMBIN.js → chunk-4OSHLNUE.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-65N6JHAN.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-5RFXUPN5.js → chunk-B2TTQGFG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-GUBM3RGY.js +20 -0
- package/dist/cli-bundle/focused-chunks/{chunk-Q45NHPF2.js → chunk-H5FOGAVD.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-JX3RCVCX.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-D7KXFBXV.js → chunk-KWWCBWFO.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-MV752SUC.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-PKIPMCFS.js +6 -0
- package/dist/cli-bundle/focused-chunks/{chunk-4HJFY4MB.js → chunk-QL66QNWG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-RJMR2JIF.js +9 -0
- package/dist/cli-bundle/focused-chunks/chunk-WIUPWIZR.js +177 -0
- package/dist/cli-bundle/focused-chunks/chunk-WOCIPMW7.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-GT7MXDLW.js → chunk-YEF5PQQ2.js} +2 -2
- package/dist/cli-bundle/main.js +22 -15
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +29 -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 +45 -37
- 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 +4 -4
- package/dist/core/extensions/extension-types.d.ts +2 -0
- package/dist/core/extensions/extension-types.js +2 -2
- package/dist/core/history/drift-scan.d.ts +5 -0
- package/dist/core/history/drift-scan.js +16 -6
- package/dist/core/history/event-classification.d.ts +1 -1
- package/dist/core/history/event-classification.js +3 -2
- package/dist/core/history/history.d.ts +37 -1
- package/dist/core/history/history.js +311 -59
- package/dist/core/history/identity.d.ts +15 -0
- package/dist/core/history/identity.js +31 -0
- package/dist/core/history/projection.js +50 -17
- package/dist/core/history/read.js +6 -3
- package/dist/core/history/replay.d.ts +1 -2
- package/dist/core/history/replay.js +70 -36
- package/dist/core/history/version-address.d.ts +13 -0
- package/dist/core/history/version-address.js +33 -0
- package/dist/core/item/actionability.d.ts +21 -1
- package/dist/core/item/actionability.js +45 -10
- package/dist/core/item/id.js +8 -4
- package/dist/core/item/item-type-definition.d.ts +2 -0
- package/dist/core/item/item-type-definition.js +9 -2
- package/dist/core/item/type-registry.d.ts +5 -1
- package/dist/core/item/type-registry.js +9 -4
- package/dist/core/lock/lock.d.ts +10 -0
- package/dist/core/lock/lock.js +42 -3
- package/dist/core/shared/author.d.ts +3 -1
- package/dist/core/shared/author.js +111 -23
- package/dist/core/shared/errors.d.ts +8 -0
- package/dist/core/shared/errors.js +2 -2
- package/dist/core/store/item-store.d.ts +14 -0
- package/dist/core/store/item-store.js +48 -10
- package/dist/core/store/settings-validator.js +3 -2
- package/dist/sdk/actionability.d.ts +1 -1
- package/dist/sdk/actionability.js +3 -3
- package/dist/sdk/cli-contracts/command-aliases.js +11 -2
- package/dist/sdk/cli-contracts/completeness.js +4 -3
- package/dist/sdk/cli-contracts/flag-contracts.js +29 -5
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +19 -12
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +14 -2
- package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
- package/dist/sdk/cli-contracts/tool-schema.js +19 -6
- package/dist/sdk/context-intent-contracts.js +3 -3
- package/dist/sdk/context-usage.js +9 -11
- package/dist/sdk/define.d.ts +1 -1
- package/dist/sdk/define.js +2 -2
- package/dist/sdk/environment/agent-environment.d.ts +296 -0
- package/dist/sdk/environment/agent-environment.js +467 -0
- package/dist/sdk/environment/project-runtime-compatibility.d.ts +19 -1
- package/dist/sdk/environment/project-runtime-compatibility.js +118 -21
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +98 -38
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +50 -2
- package/dist/sdk/governance/health.js +13 -44
- package/dist/sdk/governance/merge-receipt-history.d.ts +62 -0
- package/dist/sdk/governance/merge-receipt-history.js +207 -0
- package/dist/sdk/governance/provenance-health.js +9 -3
- package/dist/sdk/governance/validate-history-drift.js +16 -5
- package/dist/sdk/guide-topics.js +116 -2
- package/dist/sdk/history/salvage.d.ts +35 -0
- package/dist/sdk/history/salvage.js +248 -0
- package/dist/sdk/history-compact.js +41 -37
- package/dist/sdk/history-read.d.ts +3 -2
- package/dist/sdk/history-read.js +6 -3
- package/dist/sdk/history-redact.js +58 -23
- package/dist/sdk/history-repair.d.ts +5 -0
- package/dist/sdk/history-repair.js +28 -6
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +4 -2
- package/dist/sdk/lifecycle/claim.d.ts +4 -0
- package/dist/sdk/lifecycle/claim.js +9 -3
- package/dist/sdk/lifecycle/restore.d.ts +1 -0
- package/dist/sdk/lifecycle/restore.js +19 -7
- package/dist/sdk/merge/driver.d.ts +3 -1
- package/dist/sdk/merge/driver.js +9 -9
- package/dist/sdk/merge/index.d.ts +1 -1
- package/dist/sdk/merge/index.js +3 -3
- package/dist/sdk/merge/receipts.d.ts +17 -6
- package/dist/sdk/merge/receipts.js +246 -75
- package/dist/sdk/merge/reconcile.d.ts +9 -7
- package/dist/sdk/merge/reconcile.js +175 -12
- package/dist/sdk/merge/three-way.d.ts +22 -2
- package/dist/sdk/merge/three-way.js +92 -25
- package/dist/sdk/output-projection.d.ts +3 -1
- package/dist/sdk/output-projection.js +11 -3
- package/dist/sdk/public-surface.json +1 -0
- package/dist/sdk/query/context.d.ts +9 -0
- package/dist/sdk/query/context.js +33 -21
- package/dist/sdk/query/get.d.ts +2 -2
- package/dist/sdk/query/get.js +4 -3
- package/dist/sdk/query/history.d.ts +11 -0
- package/dist/sdk/query/history.js +34 -10
- package/dist/sdk/query/next.d.ts +15 -1
- package/dist/sdk/query/next.js +43 -20
- package/dist/sdk/query.d.ts +1 -0
- package/dist/sdk/query.js +3 -2
- package/dist/sdk/runtime-primitives.d.ts +3 -3
- package/dist/sdk/runtime-primitives.js +5 -5
- package/dist/types.d.ts +27 -0
- package/dist/types.js +2 -2
- package/docs/AGENT_ENVIRONMENTS.md +47 -0
- package/docs/AGENT_GUIDE.md +36 -0
- package/docs/AGENT_PROVENANCE_ADR.md +17 -2
- package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +38 -1
- package/docs/HISTORY_RECOVERY.md +146 -0
- package/docs/MERGE_SAFETY.md +119 -23
- package/docs/README.md +2 -0
- package/docs/RELEASING.md +25 -11
- package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +24 -2
- package/docs/SDK_CONTEXT_TRUTH_CONTRACTS.md +24 -1
- package/docs/SDK_EXECUTION_CONTRACTS.md +105 -0
- package/docs/SDK_RUNTIME_BOUNDARIES.md +17 -1
- package/docs/TESTING.md +5 -0
- package/docs/agent-task-token-baseline.json +17 -17
- package/docs/agent-task-transcripts.json +2 -1
- package/docs/examples/sdk-agent-environment/README.md +14 -0
- package/docs/examples/sdk-agent-environment/package.json +8 -0
- package/docs/examples/sdk-agent-environment/run-agent-environment.mjs +154 -0
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +10 -10
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +12 -3
- package/marketplace.json +2 -2
- package/package.json +5 -6
- 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/skills/pm-audit/SKILL.md +2 -2
- package/plugins/pm-claude/skills/pm-developer/SKILL.md +2 -2
- package/plugins/pm-claude/skills/pm-planner/SKILL.md +2 -2
- package/plugins/pm-claude/skills/pm-release/SKILL.md +2 -2
- package/plugins/pm-claude/skills/pm-workflow/SKILL.md +3 -3
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pm-codex/skills/pm-auditor/SKILL.md +2 -2
- package/plugins/pm-codex/skills/pm-native/SKILL.md +3 -4
- package/plugins/pm-codex/skills/pm-release/SKILL.md +2 -2
- package/scripts/finalize-build.mjs +13 -1
- package/dist/cli-bundle/chunks/chunk-AY324UJ5.js +0 -9
- package/dist/cli-bundle/chunks/chunk-E5RBBCXO.js +0 -230
- package/dist/cli-bundle/chunks/chunk-IEHHE6GA.js +0 -5
- package/dist/cli-bundle/chunks/chunk-L7B7343B.js +0 -8
- package/dist/cli-bundle/chunks/chunk-LISESA7W.js +0 -35
- package/dist/cli-bundle/chunks/register-operations-TTROIVC4.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-3ZBI35HU.js +0 -176
- package/dist/cli-bundle/focused-chunks/chunk-4RRHC3S4.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-4WR46RMG.js +0 -9
- package/dist/cli-bundle/focused-chunks/chunk-BWKHEIVF.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-GHCZ3YFR.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-KBLZRU62.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-KZ2NB3ET.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-OL5EH6I3.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-QKHCSUGJ.js +0 -51
- package/dist/cli-bundle/focused-chunks/chunk-TXE4TSHM.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-UCLO3XDU.js +0 -2
- package/sdk/public-surface.json +0 -32524
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @module core/history/identity
|
|
2
|
+
* Identifies multiple genesis records without inferring an unrecorded writer.
|
|
3
|
+
*/
|
|
4
|
+
import type { HistoryEntry } from "../../types/index.js";
|
|
5
|
+
/** Evidence of a second subject occupying one append-only identity stream. */
|
|
6
|
+
export interface HistoryIdentityDiscontinuity {
|
|
7
|
+
/** One-based physical ordinal of the original create or retained baseline. */
|
|
8
|
+
prior_genesis_index: number;
|
|
9
|
+
/** One-based physical ordinal of the later create record. */
|
|
10
|
+
repeated_create_index: number;
|
|
11
|
+
/** Observed record sequence, not an assertion about branch or writer origin. */
|
|
12
|
+
sequence: "delete_then_create" | "multiple_creates" | "checkpoint_then_create";
|
|
13
|
+
}
|
|
14
|
+
/** Find every repeated genesis, including histories that start at a checkpoint. */
|
|
15
|
+
export declare function findHistoryIdentityDiscontinuities(entries: readonly HistoryEntry[]): HistoryIdentityDiscontinuity[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Find every repeated genesis, including histories that start at a checkpoint. */
|
|
2
|
+
|
|
3
|
+
!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]="ffaea7bd-daa4-5ff5-8cd5-82ad04e2256e")}catch(e){}}();
|
|
4
|
+
export function findHistoryIdentityDiscontinuities(entries) {
|
|
5
|
+
const findings = [];
|
|
6
|
+
const checkpoint = entries[0]?.op === "history_compact_baseline";
|
|
7
|
+
let genesis = checkpoint ? 1 : undefined;
|
|
8
|
+
let deleted = false;
|
|
9
|
+
for (const [index, entry] of entries.entries()) {
|
|
10
|
+
if (entry.op === "delete")
|
|
11
|
+
deleted = true;
|
|
12
|
+
if (entry.op !== "create")
|
|
13
|
+
continue;
|
|
14
|
+
if (genesis !== undefined) {
|
|
15
|
+
findings.push({
|
|
16
|
+
prior_genesis_index: genesis,
|
|
17
|
+
repeated_create_index: index + 1,
|
|
18
|
+
sequence: deleted
|
|
19
|
+
? "delete_then_create"
|
|
20
|
+
: checkpoint
|
|
21
|
+
? "checkpoint_then_create"
|
|
22
|
+
: "multiple_creates",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
genesis ??= index + 1;
|
|
26
|
+
deleted = false;
|
|
27
|
+
}
|
|
28
|
+
return findings;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=identity.js.map
|
|
31
|
+
//# debugId=ffaea7bd-daa4-5ff5-8cd5-82ad04e2256e
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* replay kernel so read and write paths cannot disagree about a version.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
!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]="
|
|
9
|
+
!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]="2efbffc1-722c-5f3a-9ca5-e4e7a4f97729")}catch(e){}}();
|
|
10
10
|
import { EXIT_CODE } from "../shared/constants.js";
|
|
11
11
|
import { PmCliError } from "../shared/errors.js";
|
|
12
|
-
import {
|
|
12
|
+
import { historyVersionOffset } from "./version-address.js";
|
|
13
|
+
import { EMPTY_REPLAY_DOCUMENT, verifyHistoryChain, tryApplyReplayPatch, } from "./replay.js";
|
|
13
14
|
/** Find the final history entry recorded at or before a parsed timestamp. */
|
|
14
15
|
function findHistoryIndexAtTimestamp(history, parsedTarget) {
|
|
15
16
|
let historyIndex = -1;
|
|
@@ -25,7 +26,7 @@ function findHistoryIndexAtTimestamp(history, parsedTarget) {
|
|
|
25
26
|
return historyIndex;
|
|
26
27
|
}
|
|
27
28
|
/** Build structured target-range guidance once for every resolver error path. */
|
|
28
|
-
function buildHistoryTargetGuidance(history) {
|
|
29
|
+
function buildHistoryTargetGuidance(history, offset) {
|
|
29
30
|
const firstTimestamp = history[0]?.ts ?? null;
|
|
30
31
|
const lastTimestamp = history.at(-1)?.ts ?? null;
|
|
31
32
|
const guidance = {
|
|
@@ -44,9 +45,12 @@ function buildHistoryTargetGuidance(history) {
|
|
|
44
45
|
},
|
|
45
46
|
};
|
|
46
47
|
if (lastTimestamp !== null) {
|
|
47
|
-
guidance.examples = [
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
guidance.examples = [lastTimestamp];
|
|
49
|
+
if (offset !== null) {
|
|
50
|
+
guidance.examples.unshift(String(offset + 1), String(offset + history.length));
|
|
51
|
+
guidance.valid_range.first_version = offset + 1;
|
|
52
|
+
guidance.valid_range.last_version = offset + history.length;
|
|
53
|
+
}
|
|
50
54
|
}
|
|
51
55
|
return { guidance, lastTimestamp };
|
|
52
56
|
}
|
|
@@ -90,18 +94,25 @@ export function resolveHistoryTarget(target, history, options = {}) {
|
|
|
90
94
|
const trimmed = target.trim();
|
|
91
95
|
const errorSubject = options.errorSubject ?? "history";
|
|
92
96
|
const errorSubjectTitle = errorSubject === "restore" ? "Restore" : "History";
|
|
93
|
-
const { guidance, lastTimestamp } = buildHistoryTargetGuidance(history);
|
|
97
|
+
const { guidance, lastTimestamp } = buildHistoryTargetGuidance(history, historyVersionOffset(history, true));
|
|
94
98
|
if (!trimmed) {
|
|
95
99
|
throw new PmCliError(`Missing ${errorSubject} target. Use a timestamp or version number.`, EXIT_CODE.USAGE, guidance);
|
|
96
100
|
}
|
|
97
101
|
if (/^\d+$/.test(trimmed)) {
|
|
102
|
+
const offset = historyVersionOffset(history);
|
|
98
103
|
const version = Number(trimmed);
|
|
104
|
+
if (version >= 1 && version <= offset) {
|
|
105
|
+
throw new PmCliError(`History version ${version} was pruned by compaction.`, EXIT_CODE.USAGE, {
|
|
106
|
+
...guidance,
|
|
107
|
+
code: "history_version_pruned",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
99
110
|
if (!Number.isSafeInteger(version) ||
|
|
100
111
|
version < 1 ||
|
|
101
|
-
version > history.length) {
|
|
102
|
-
throw new PmCliError(`${errorSubjectTitle} version must be between 1 and ${history.length} for this item.`, EXIT_CODE.USAGE, guidance);
|
|
112
|
+
version > offset + history.length) {
|
|
113
|
+
throw new PmCliError(`${errorSubjectTitle} version must be between ${offset + 1} and ${offset + history.length} for this item.`, EXIT_CODE.USAGE, guidance);
|
|
103
114
|
}
|
|
104
|
-
return { kind: "version", raw: trimmed, historyIndex: version - 1 };
|
|
115
|
+
return { kind: "version", raw: trimmed, historyIndex: version - offset - 1 };
|
|
105
116
|
}
|
|
106
117
|
const parsedTarget = Date.parse(trimmed);
|
|
107
118
|
if (!Number.isFinite(parsedTarget)) {
|
|
@@ -137,28 +148,50 @@ export function applyHistoryPatch(current, patch, entryNumber, entryOp) {
|
|
|
137
148
|
}
|
|
138
149
|
/** Replay and hash-verify a history stream through a zero-based target entry. */
|
|
139
150
|
export function replayHistoryToTarget(history, targetIndex) {
|
|
151
|
+
const first = history[0];
|
|
152
|
+
if (first && first.op !== "create" && first.op !== "history_compact_baseline") {
|
|
153
|
+
const baseline = tryApplyReplayPatch(EMPTY_REPLAY_DOCUMENT, first.patch);
|
|
154
|
+
if (!baseline.ok || typeof baseline.document.metadata.id !== "string") {
|
|
155
|
+
throw new PmCliError("History starts without a reconstructable item baseline.", EXIT_CODE.GENERIC_FAILURE, {
|
|
156
|
+
code: "history_baseline_unavailable",
|
|
157
|
+
required: "Recover the original create entry or a complete checkpoint from version control.",
|
|
158
|
+
nextSteps: ["Run pm history <id> --full to inspect the missing baseline."],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const selected = history.slice(0, targetIndex + 1);
|
|
163
|
+
const verification = verifyHistoryChain([...selected]);
|
|
140
164
|
let document = structuredClone(EMPTY_REPLAY_DOCUMENT);
|
|
141
165
|
for (let index = 0; index <= targetIndex; index += 1) {
|
|
142
166
|
const entry = history[index];
|
|
143
167
|
if (!entry) {
|
|
144
168
|
throw new PmCliError(`History target entry ${index + 1} does not exist.`, EXIT_CODE.USAGE);
|
|
145
169
|
}
|
|
146
|
-
if (replayHash(document) !== entry.before_hash) {
|
|
147
|
-
throw new PmCliError(`History hash mismatch before replay at entry ${index + 1}.`, EXIT_CODE.GENERIC_FAILURE);
|
|
148
|
-
}
|
|
149
170
|
document = applyHistoryPatch(document, entry.patch, index + 1, entry.op);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
171
|
+
}
|
|
172
|
+
if (!verification.ok) {
|
|
173
|
+
throw new PmCliError(`History cannot be replayed: ${verification.errors.join(", ")}.`, EXIT_CODE.GENERIC_FAILURE, {
|
|
174
|
+
code: "history_replay_invalid",
|
|
175
|
+
verification_errors: verification.errors,
|
|
176
|
+
required: "Inspect the history integrity findings before attempting recovery.",
|
|
177
|
+
nextSteps: ["Run pm history <id> --verify --full."],
|
|
178
|
+
});
|
|
153
179
|
}
|
|
154
180
|
return document;
|
|
155
181
|
}
|
|
156
182
|
/** Reject a version whose reconstructed state represents a deleted item. */
|
|
157
183
|
export function ensureMaterializedHistoryTarget(replayDocument, target) {
|
|
158
184
|
if (Object.keys(replayDocument.metadata).length > 0) {
|
|
185
|
+
if (typeof replayDocument.metadata.id !== "string" || !Array.isArray(replayDocument.metadata.tags)) {
|
|
186
|
+
throw new PmCliError("History has no complete item baseline for this target.", EXIT_CODE.GENERIC_FAILURE, {
|
|
187
|
+
code: "history_baseline_unavailable",
|
|
188
|
+
required: "Recover the original create entry or a complete checkpoint from version control.",
|
|
189
|
+
nextSteps: ["Run pm history <id> --full to inspect the first recorded state."],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
159
192
|
return replayDocument;
|
|
160
193
|
}
|
|
161
194
|
throw new PmCliError(`History target ${target.raw} resolves to a deleted state; choose a version or timestamp where the item exists.`, EXIT_CODE.USAGE);
|
|
162
195
|
}
|
|
163
196
|
//# sourceMappingURL=projection.js.map
|
|
164
|
-
//# debugId=
|
|
197
|
+
//# debugId=2efbffc1-722c-5f3a-9ca5-e4e7a4f97729
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* maintenance APIs from depending on presentation-layer modules.
|
|
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]="999c126a-112a-50f6-b53e-bd36dbc5b885")}catch(e){}}();
|
|
11
11
|
import { readFileIfExists } from "../fs/fs-utils.js";
|
|
12
12
|
import { runActiveOnReadHooks } from "../extensions/index.js";
|
|
13
13
|
import { EXIT_CODE } from "../shared/constants.js";
|
|
@@ -46,10 +46,13 @@ export async function readHistoryEntries(historyPath, itemId) {
|
|
|
46
46
|
entries.push(JSON.parse(line));
|
|
47
47
|
}
|
|
48
48
|
catch {
|
|
49
|
-
throw new PmCliError(`History for ${itemId} contains invalid JSON at line ${index + 1}. Repair or restore the history stream and retry.`, EXIT_CODE.GENERIC_FAILURE
|
|
49
|
+
throw new PmCliError(`History for ${itemId} contains invalid JSON at line ${index + 1}. Repair or restore the history stream and retry.`, EXIT_CODE.GENERIC_FAILURE, {
|
|
50
|
+
code: "history_json_invalid",
|
|
51
|
+
nextSteps: [`Run pm history-repair ${itemId} --salvage-tail --dry-run to check whether only an invalid suffix is damaged.`],
|
|
52
|
+
});
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
return entries;
|
|
53
56
|
}
|
|
54
57
|
//# sourceMappingURL=read.js.map
|
|
55
|
-
//# debugId=
|
|
58
|
+
//# debugId=999c126a-112a-50f6-b53e-bd36dbc5b885
|
|
@@ -47,7 +47,6 @@ export type ReplayApplyResult = {
|
|
|
47
47
|
};
|
|
48
48
|
/** Strictly apply a history patch (front_matter->metadata normalized) to a replay document. Returns a result envelope rather than throwing so each caller can format its own error contract. */
|
|
49
49
|
export declare function tryApplyReplayPatch(current: ReplayDocument, patch: HistoryPatchOp[]): ReplayApplyResult;
|
|
50
|
-
/** Deterministically verify a history chain: each entry's before_hash must equal the prior replayed after_hash, the patch must strictly apply, and the recorded after_hash must equal the replayed result. */
|
|
51
50
|
/** Verify a chain and report the explicit or auto-detected item hash epoch. */
|
|
52
51
|
export declare function verifyHistoryChainWithVersion(entries: HistoryEntry[]): {
|
|
53
52
|
ok: boolean;
|
|
@@ -119,6 +118,6 @@ export interface ReanchorHistoryOptions {
|
|
|
119
118
|
*/
|
|
120
119
|
export declare function resolveHistoryRepairItemHashVersion(entries: HistoryEntry[]): HistoryItemHashVersion;
|
|
121
120
|
/** Re-anchor a drifted history chain: replay every entry from empty, recompute the before/after hashes, and only rewrite a patch when the original op set no longer strictly applies (legacy drift). Clean entries keep their patch verbatim so the on-disk diff stays minimal. The returned chain verifies via verifyHistoryChain. */
|
|
122
|
-
export declare function reanchorHistoryEntries(entries: HistoryEntry[], itemHashVersion?:
|
|
121
|
+
export declare function reanchorHistoryEntries(entries: HistoryEntry[], itemHashVersion?: HistoryItemHashVersion, options?: ReanchorHistoryOptions): ReanchorResult;
|
|
123
122
|
/** Implements history entries to raw for the public runtime surface of this module. */
|
|
124
123
|
export declare function historyEntriesToRaw(entries: HistoryEntry[]): string;
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* Implements append-only history and replay behavior for Replay.
|
|
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]="b0230e20-93c4-531c-9cc1-85f85c1def7a")}catch(e){}}();
|
|
8
8
|
import jsonPatch from "fast-json-patch";
|
|
9
|
+
import { findHistoryIdentityDiscontinuities } from "./identity.js";
|
|
9
10
|
import { ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
|
|
10
11
|
import { canonicalDocument } from "../item/item-format.js";
|
|
11
12
|
import { toItemRecord } from "../item/item-record.js";
|
|
12
13
|
import { orderObject, sha256Hex, stableStringify, } from "../shared/serialization.js";
|
|
13
|
-
import { CURRENT_HISTORY_ITEM_HASH_VERSION, SUPPORTED_HISTORY_ITEM_HASH_VERSIONS, hashDocumentVerificationCandidates, } from "./history.js";
|
|
14
|
+
import { CURRENT_HISTORY_ITEM_HASH_VERSION, SUPPORTED_HISTORY_ITEM_HASH_VERSIONS, hashDocumentVerificationCandidates, resealHistoryRewrite, verifyHistoryRecordHash, verifyHistoryRewriteEvidence, } from "./history.js";
|
|
14
15
|
/** Public contract for empty replay document, shared by SDK and presentation-layer consumers. */
|
|
15
16
|
export const EMPTY_REPLAY_DOCUMENT = {
|
|
16
17
|
metadata: {},
|
|
@@ -165,13 +166,37 @@ function historyEntryHashCandidates(explicitVersion, authoritativeVersion) {
|
|
|
165
166
|
? [...SUPPORTED_HISTORY_ITEM_HASH_VERSIONS]
|
|
166
167
|
: [authoritativeVersion];
|
|
167
168
|
}
|
|
168
|
-
/**
|
|
169
|
+
/** Return the first record or retained-rewrite integrity failure for one entry. */
|
|
170
|
+
function historyEntryIntegrityError(entry) {
|
|
171
|
+
const recordVerification = verifyHistoryRecordHash(entry);
|
|
172
|
+
if (!recordVerification.ok)
|
|
173
|
+
return recordVerification.error;
|
|
174
|
+
const rewriteVerification = verifyHistoryRewriteEvidence(entry);
|
|
175
|
+
return rewriteVerification.ok ? undefined : rewriteVerification.error;
|
|
176
|
+
}
|
|
177
|
+
/** Validate immutable metadata and apply one patch for chain verification. */
|
|
178
|
+
function verifyHistoryEntryInput(entry, replay) {
|
|
179
|
+
const integrityError = historyEntryIntegrityError(entry);
|
|
180
|
+
if (integrityError)
|
|
181
|
+
return { ok: false, error: integrityError };
|
|
182
|
+
const applied = tryApplyReplayPatch(replay, entry.patch);
|
|
183
|
+
return applied.ok
|
|
184
|
+
? { ok: true, document: applied.document }
|
|
185
|
+
: { ok: false, error: "patch_apply_failed" };
|
|
186
|
+
}
|
|
187
|
+
/** Refuse any entry whose current or retained immutable envelope is invalid. */
|
|
188
|
+
function assertHistoryEntryIntegrity(entry, index) {
|
|
189
|
+
const integrityError = historyEntryIntegrityError(entry);
|
|
190
|
+
if (integrityError) {
|
|
191
|
+
throw new TypeError(`${integrityError}:entry_${index + 1}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
169
194
|
/** Verify a chain and report the explicit or auto-detected item hash epoch. */
|
|
170
195
|
export function verifyHistoryChainWithVersion(entries) {
|
|
171
196
|
let replay = cloneEmptyReplayDocument();
|
|
172
197
|
let detectedVersion;
|
|
173
198
|
let authoritativeExplicitVersion;
|
|
174
|
-
const errors =
|
|
199
|
+
const errors = findHistoryIdentityDiscontinuities(entries).map((finding) => `verify_failed:duplicate_create:entry_${finding.repeated_create_index}:prior_${finding.prior_genesis_index}`);
|
|
175
200
|
for (let index = 0; index < entries.length; index += 1) {
|
|
176
201
|
const entry = entries[index];
|
|
177
202
|
const explicitVersion = entry.item_hash_version;
|
|
@@ -179,18 +204,15 @@ export function verifyHistoryChainWithVersion(entries) {
|
|
|
179
204
|
if (unsupportedVersion) {
|
|
180
205
|
errors.push(`verify_failed:unsupported_item_hash_version:${String(explicitVersion)}:entry_${index + 1}`);
|
|
181
206
|
}
|
|
182
|
-
const
|
|
183
|
-
if (!
|
|
207
|
+
const input = verifyHistoryEntryInput(entry, replay);
|
|
208
|
+
if (!input.ok) {
|
|
184
209
|
return {
|
|
185
210
|
ok: false,
|
|
186
|
-
errors: [
|
|
187
|
-
...errors,
|
|
188
|
-
`verify_failed:patch_apply_failed:entry_${index + 1}`,
|
|
189
|
-
],
|
|
211
|
+
errors: [...errors, `verify_failed:${input.error}:entry_${index + 1}`],
|
|
190
212
|
};
|
|
191
213
|
}
|
|
192
214
|
if (unsupportedVersion) {
|
|
193
|
-
replay =
|
|
215
|
+
replay = input.document;
|
|
194
216
|
detectedVersion = undefined;
|
|
195
217
|
authoritativeExplicitVersion = undefined;
|
|
196
218
|
continue;
|
|
@@ -201,7 +223,7 @@ export function verifyHistoryChainWithVersion(entries) {
|
|
|
201
223
|
const candidates = historyEntryHashCandidates(explicitVersion, authoritativeExplicitVersion);
|
|
202
224
|
const candidateMatches = candidates.map((version) => ({
|
|
203
225
|
version,
|
|
204
|
-
match: matchReplayHashCandidates(replay,
|
|
226
|
+
match: matchReplayHashCandidates(replay, input.document, version, entry),
|
|
205
227
|
}));
|
|
206
228
|
const matchingVersion = candidateMatches.find(({ match }) => match.pairIndex >= 0);
|
|
207
229
|
if (matchingVersion === undefined) {
|
|
@@ -215,7 +237,7 @@ export function verifyHistoryChainWithVersion(entries) {
|
|
|
215
237
|
};
|
|
216
238
|
}
|
|
217
239
|
const version = matchingVersion.version;
|
|
218
|
-
replay =
|
|
240
|
+
replay = input.document;
|
|
219
241
|
detectedVersion = version;
|
|
220
242
|
authoritativeExplicitVersion =
|
|
221
243
|
explicitVersion ??
|
|
@@ -304,6 +326,27 @@ export function resolveHistoryRepairItemHashVersion(entries) {
|
|
|
304
326
|
}
|
|
305
327
|
return CURRENT_HISTORY_ITEM_HASH_VERSION;
|
|
306
328
|
}
|
|
329
|
+
/** Apply one patch strictly or return its deterministic lenient repair projection. */
|
|
330
|
+
function applyReanchorPatch(replay, entry) {
|
|
331
|
+
const strict = tryApplyReplayPatch(replay, entry.patch);
|
|
332
|
+
if (strict.ok) {
|
|
333
|
+
return {
|
|
334
|
+
next: strict.document,
|
|
335
|
+
patch: entry.patch,
|
|
336
|
+
repaired: false,
|
|
337
|
+
converted: 0,
|
|
338
|
+
skipped: 0,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const lenient = lenientApplyReplayPatch(replay, entry.patch);
|
|
342
|
+
return {
|
|
343
|
+
next: lenient.document,
|
|
344
|
+
patch: jsonPatch.compare(replay, lenient.document),
|
|
345
|
+
repaired: true,
|
|
346
|
+
converted: lenient.convertedReplaceToAdd,
|
|
347
|
+
skipped: lenient.skippedOps,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
307
350
|
/** Re-anchor a drifted history chain: replay every entry from empty, recompute the before/after hashes, and only rewrite a patch when the original op set no longer strictly applies (legacy drift). Clean entries keep their patch verbatim so the on-disk diff stays minimal. The returned chain verifies via verifyHistoryChain. */
|
|
308
351
|
export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistoryRepairItemHashVersion(entries), options = {}) {
|
|
309
352
|
const unsupportedIndex = entries.findIndex((entry) => isUnsupportedExplicitHistoryItemHashVersion(entry.item_hash_version));
|
|
@@ -322,27 +365,12 @@ export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistory
|
|
|
322
365
|
entries.some((entry) => entry.item_hash_version !== undefined);
|
|
323
366
|
for (let index = 0; index < entries.length; index += 1) {
|
|
324
367
|
const entry = entries[index];
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (strict.ok) {
|
|
332
|
-
next = strict.document;
|
|
333
|
-
outPatch = entry.patch;
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
const lenient = lenientApplyReplayPatch(replay, entry.patch);
|
|
337
|
-
next = lenient.document;
|
|
338
|
-
outPatch = jsonPatch.compare(replay, next);
|
|
339
|
-
patchRepaired = true;
|
|
340
|
-
entryConverted = lenient.convertedReplaceToAdd;
|
|
341
|
-
entrySkipped = lenient.skippedOps;
|
|
342
|
-
convertedReplaceToAdd += entryConverted;
|
|
343
|
-
skippedOps += entrySkipped;
|
|
344
|
-
entriesPatchRepaired += 1;
|
|
345
|
-
}
|
|
368
|
+
assertHistoryEntryIntegrity(entry, index);
|
|
369
|
+
const patchApplication = applyReanchorPatch(replay, entry);
|
|
370
|
+
const { next, patch: outPatch, repaired: patchRepaired, converted: entryConverted, skipped: entrySkipped, } = patchApplication;
|
|
371
|
+
convertedReplaceToAdd += entryConverted;
|
|
372
|
+
skippedOps += entrySkipped;
|
|
373
|
+
entriesPatchRepaired += Number(patchRepaired);
|
|
346
374
|
const hashMatch = matchReplayHashCandidates(replay, next, itemHashVersion, entry);
|
|
347
375
|
if (semanticIndex === undefined || options.continuousHashSurface !== true) {
|
|
348
376
|
semanticIndex = [
|
|
@@ -361,7 +389,7 @@ export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistory
|
|
|
361
389
|
if (rehashed) {
|
|
362
390
|
entriesRehashed += 1;
|
|
363
391
|
}
|
|
364
|
-
|
|
392
|
+
let rewrittenEntry = {
|
|
365
393
|
...entry,
|
|
366
394
|
patch: outPatch,
|
|
367
395
|
before_hash: beforeHash,
|
|
@@ -373,6 +401,12 @@ export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistory
|
|
|
373
401
|
else {
|
|
374
402
|
delete rewrittenEntry.item_hash_version;
|
|
375
403
|
}
|
|
404
|
+
const versionChanged = rewrittenEntry.item_hash_version !== entry.item_hash_version;
|
|
405
|
+
if (rehashed || patchRepaired || versionChanged) {
|
|
406
|
+
rewrittenEntry = resealHistoryRewrite(entry, rewrittenEntry, {
|
|
407
|
+
retainOriginalPatch: patchRepaired,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
376
410
|
rewritten.push(rewrittenEntry);
|
|
377
411
|
details.push({
|
|
378
412
|
index: index + 1,
|
|
@@ -403,4 +437,4 @@ export function historyEntriesToRaw(entries) {
|
|
|
403
437
|
return `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
|
|
404
438
|
}
|
|
405
439
|
//# sourceMappingURL=replay.js.map
|
|
406
|
-
//# debugId=
|
|
440
|
+
//# debugId=b0230e20-93c4-531c-9cc1-85f85c1def7a
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/history/version-address
|
|
3
|
+
* Stable ordinal addressing for streams whose retained prefix is a checkpoint.
|
|
4
|
+
*/
|
|
5
|
+
import type { HistoryEntry } from "../../types/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Return the number added to physical one-based positions to obtain durable
|
|
8
|
+
* version addresses. Legacy checkpoints cannot prove their original ordinal;
|
|
9
|
+
* refusing them prevents silently selecting a different historical state.
|
|
10
|
+
*/
|
|
11
|
+
export declare function historyVersionOffset(history: readonly HistoryEntry[], allowUnknown: true): number | null;
|
|
12
|
+
/** Resolve a required durable offset, refusing ambiguous legacy checkpoints. */
|
|
13
|
+
export declare function historyVersionOffset(history: readonly HistoryEntry[], allowUnknown?: false): number;
|
|
@@ -0,0 +1,33 @@
|
|
|
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]="7ad4e6c0-7545-51cb-9af7-4ba1ad4c4758")}catch(e){}}();
|
|
3
|
+
import { EXIT_CODE } from "../shared/constants.js";
|
|
4
|
+
import { PmCliError } from "../shared/errors.js";
|
|
5
|
+
/** Resolve the checkpoint offset with an explicit opt-in for unknown legacy addresses. */
|
|
6
|
+
export function historyVersionOffset(history, allowUnknown = false) {
|
|
7
|
+
const first = history[0];
|
|
8
|
+
if (first?.op !== "history_compact_baseline")
|
|
9
|
+
return 0;
|
|
10
|
+
const receipt = first.context?.history_compaction;
|
|
11
|
+
const offset = typeof receipt === "object" &&
|
|
12
|
+
receipt !== null &&
|
|
13
|
+
"version_offset" in receipt
|
|
14
|
+
? receipt.version_offset
|
|
15
|
+
: undefined;
|
|
16
|
+
if (typeof offset === "number" &&
|
|
17
|
+
Number.isSafeInteger(offset) &&
|
|
18
|
+
offset >= 0 &&
|
|
19
|
+
Number.isSafeInteger(offset + history.length)) {
|
|
20
|
+
return offset;
|
|
21
|
+
}
|
|
22
|
+
if (allowUnknown)
|
|
23
|
+
return null;
|
|
24
|
+
throw new PmCliError("This compacted stream has no trustworthy version offset; numeric history addresses are unavailable.", EXIT_CODE.USAGE, {
|
|
25
|
+
code: "history_version_mapping_unavailable",
|
|
26
|
+
required: "Use a retained timestamp or recover the pre-compaction stream from version control.",
|
|
27
|
+
nextSteps: [
|
|
28
|
+
"Run pm history <id> --full to inspect the retained timestamps and checkpoint evidence.",
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=version-address.js.map
|
|
33
|
+
//# debugId=7ad4e6c0-7545-51cb-9af7-4ba1ad4c4758
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { RuntimeStatusRegistry } from "../schema/runtime-schema.js";
|
|
2
2
|
import type { ItemMetadata, ItemStatus } from "../../types/index.js";
|
|
3
|
+
import { type ItemTypeRegistry } from "./type-registry.js";
|
|
4
|
+
/** Resolve dispatch semantics once for SDK callers, honoring custom and extension type aliases before built-in defaults. */
|
|
5
|
+
export declare function resolveItemExecutionRole(type: string, registry?: ItemTypeRegistry): "agent" | "human" | "gate";
|
|
3
6
|
/** Built-in ordering kinds that must remain reachable by blocker resolution. */
|
|
4
7
|
export declare const ACTIONABILITY_ORDERING_KINDS: readonly string[];
|
|
5
8
|
/** Collects the blocker item ids declared by an item: the legacy scalar `blocked_by` field plus every `blocked_by` dependency edge. Ids are trimmed, de-duplicated, and returned in stable lexicographic order. This is the single source of truth for "what must close before this item can proceed", shared by `pm next` readiness classification and the close-time auto-unblock sweep. */
|
|
@@ -54,11 +57,28 @@ export interface ActionabilityReport {
|
|
|
54
57
|
ready: ActionableEntry[];
|
|
55
58
|
/** Active leaves with at least one open blocker — waiting to become ready. */
|
|
56
59
|
blocked: ActionableEntry[];
|
|
60
|
+
/** Active containers with unresolved descendants, including their blocker evidence. */
|
|
61
|
+
containers: ActionableEntry[];
|
|
62
|
+
/** Unblocked leaf work that requires a human decision. */
|
|
63
|
+
decisions: ActionableEntry[];
|
|
64
|
+
/** Unblocked leaf outcome gates held out of automatic dispatch. */
|
|
65
|
+
gates: ActionableEntry[];
|
|
57
66
|
/** Active candidates considered before the leaf/blocker split. */
|
|
58
67
|
active_count: number;
|
|
59
68
|
/** Active candidates skipped because they still have open descendants. */
|
|
60
69
|
container_count: number;
|
|
61
70
|
}
|
|
71
|
+
/** Explicit dispatch opt-ins; dependency and lifecycle blocking always remain enforced. */
|
|
72
|
+
export interface ActionabilitySelectionOptions {
|
|
73
|
+
/** Include human decision work in the executable selection. */
|
|
74
|
+
includeDecisions?: boolean;
|
|
75
|
+
/** Include outcome gates in the executable selection. */
|
|
76
|
+
includeGates?: boolean;
|
|
77
|
+
/** Include containers whose descendants are still unfinished. */
|
|
78
|
+
includeContainers?: boolean;
|
|
79
|
+
}
|
|
80
|
+
/** Select dispatch candidates from a classified report while retaining held-out worklists. Ownership and ranking remain caller responsibilities. */
|
|
81
|
+
export declare function selectActionableEntries(report: ActionabilityReport, statusRegistry: RuntimeStatusRegistry, options?: ActionabilitySelectionOptions, typeRegistry?: ItemTypeRegistry): Pick<ActionabilityReport, "ready" | "decisions" | "gates">;
|
|
62
82
|
/**
|
|
63
83
|
* Partitions a candidate set into ready vs blocked leaf work using the corpus to
|
|
64
84
|
* resolve blockers, descendants, and downstream dependents.
|
|
@@ -70,4 +90,4 @@ export interface ActionabilityReport {
|
|
|
70
90
|
* blockers and the non-terminal items it would unblock. Returned lists preserve
|
|
71
91
|
* the candidate order so the caller can apply its own ranking.
|
|
72
92
|
*/
|
|
73
|
-
export declare function computeActionabilityReport(candidates: ItemMetadata[], corpus: ItemMetadata[], statusRegistry: RuntimeStatusRegistry): ActionabilityReport;
|
|
93
|
+
export declare function computeActionabilityReport(candidates: ItemMetadata[], corpus: ItemMetadata[], statusRegistry: RuntimeStatusRegistry, typeRegistry?: ItemTypeRegistry): ActionabilityReport;
|
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
* `blocked_by` resolution a single source of truth.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
!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]="
|
|
13
|
+
!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]="7d98ca40-57e8-5afe-8e9d-b694da0130d1")}catch(e){}}();
|
|
14
14
|
import { isTerminalStatus, normalizeStatusForRegistry } from "./status.js";
|
|
15
|
+
import { BUILTIN_ITEM_EXECUTION_ROLES, resolveTypeDefinition } from "./type-registry.js";
|
|
15
16
|
import { isExternalDependencyReference, isExternalDependencySourceKind, } from "./dependency-reference.js";
|
|
17
|
+
/** Resolve dispatch semantics once for SDK callers, honoring custom and extension type aliases before built-in defaults. */
|
|
18
|
+
export function resolveItemExecutionRole(type, registry) {
|
|
19
|
+
const definition = registry === undefined ? undefined : resolveTypeDefinition(type, registry);
|
|
20
|
+
return definition?.execution_role ?? BUILTIN_ITEM_EXECUTION_ROLES[type.trim().toLowerCase()] ?? "agent";
|
|
21
|
+
}
|
|
16
22
|
/** Dependency kind that marks "this item is blocked by the referenced item". */
|
|
17
23
|
const BLOCKED_BY_DEPENDENCY_KIND = "blocked_by";
|
|
18
24
|
/** Reverse dependency kind that marks "this item blocks the referenced item". */
|
|
@@ -177,6 +183,24 @@ export function collectDependencyBlockedIds(corpus, statusRegistry) {
|
|
|
177
183
|
}
|
|
178
184
|
return blocked;
|
|
179
185
|
}
|
|
186
|
+
/** Select dispatch candidates from a classified report while retaining held-out worklists. Ownership and ranking remain caller responsibilities. */
|
|
187
|
+
export function selectActionableEntries(report, statusRegistry, options = {}, typeRegistry) {
|
|
188
|
+
const candidates = [...report.ready, ...report.decisions, ...report.gates];
|
|
189
|
+
if (options.includeContainers === true) {
|
|
190
|
+
candidates.push(...report.containers.filter((entry) => entry.open_blockers.length === 0 && !statusRegistry.blocked_statuses.has(normalizeStatusForRegistry(entry.item.status, statusRegistry))));
|
|
191
|
+
}
|
|
192
|
+
const selected = { ready: [], decisions: [], gates: [] };
|
|
193
|
+
for (const entry of candidates) {
|
|
194
|
+
const role = resolveItemExecutionRole(entry.item.type, typeRegistry);
|
|
195
|
+
if (role === "human" && options.includeDecisions !== true)
|
|
196
|
+
selected.decisions.push(entry);
|
|
197
|
+
else if (role === "gate" && options.includeGates !== true)
|
|
198
|
+
selected.gates.push(entry);
|
|
199
|
+
else
|
|
200
|
+
selected.ready.push(entry);
|
|
201
|
+
}
|
|
202
|
+
return selected;
|
|
203
|
+
}
|
|
180
204
|
/** Resolves the registry's active status set, falling back to just the canonical open status when a custom schema declares none — the same safety net `pm context` applies so the report never misclassifies every item as inactive. */
|
|
181
205
|
function resolveActiveStatusSet(statusRegistry) {
|
|
182
206
|
/* c8 ignore start -- fallback applies only to custom schemas that intentionally define zero active statuses */
|
|
@@ -242,7 +266,7 @@ function indexCorpus(corpus) {
|
|
|
242
266
|
* blockers and the non-terminal items it would unblock. Returned lists preserve
|
|
243
267
|
* the candidate order so the caller can apply its own ranking.
|
|
244
268
|
*/
|
|
245
|
-
export function computeActionabilityReport(candidates, corpus, statusRegistry) {
|
|
269
|
+
export function computeActionabilityReport(candidates, corpus, statusRegistry, typeRegistry) {
|
|
246
270
|
const { itemsById, childrenByParent, blockedByReverse, blockerIdsByItem } = indexCorpus(corpus);
|
|
247
271
|
const activeStatuses = new Set([
|
|
248
272
|
...resolveActiveStatusSet(statusRegistry),
|
|
@@ -255,16 +279,14 @@ export function computeActionabilityReport(candidates, corpus, statusRegistry) {
|
|
|
255
279
|
.map((entry) => normalizeItemId(entry.id)));
|
|
256
280
|
const ready = [];
|
|
257
281
|
const blocked = [];
|
|
282
|
+
const containers = [];
|
|
283
|
+
const decisions = [];
|
|
284
|
+
const gates = [];
|
|
258
285
|
let activeCount = 0;
|
|
259
|
-
let containerCount = 0;
|
|
260
286
|
for (const item of candidates) {
|
|
261
287
|
if (!activeStatuses.has(normalizeStatusForRegistry(item.status, statusRegistry)))
|
|
262
288
|
continue;
|
|
263
289
|
activeCount += 1;
|
|
264
|
-
if (hasOpenDescendant(item.id, childrenByParent, statusRegistry)) {
|
|
265
|
-
containerCount += 1;
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
290
|
const openBlockers = resolveItemBlockersWithIndex(item, itemsById, statusRegistry, blockerIdsByItem).filter((blocker) => !blocker.resolved);
|
|
269
291
|
const unblocks = (blockedByReverse.get(normalizeItemId(item.id)) ?? [])
|
|
270
292
|
.filter((dependentId) => nonTerminalIds.has(normalizeItemId(dependentId)))
|
|
@@ -274,9 +296,19 @@ export function computeActionabilityReport(candidates, corpus, statusRegistry) {
|
|
|
274
296
|
open_blockers: openBlockers,
|
|
275
297
|
unblocks,
|
|
276
298
|
};
|
|
299
|
+
if (hasOpenDescendant(item.id, childrenByParent, statusRegistry)) {
|
|
300
|
+
containers.push(entry);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
277
303
|
const lifecycleBlocked = statusRegistry.blocked_statuses.has(normalizeStatusForRegistry(item.status, statusRegistry));
|
|
278
304
|
if (openBlockers.length === 0 && !lifecycleBlocked) {
|
|
279
|
-
|
|
305
|
+
const role = resolveItemExecutionRole(item.type, typeRegistry);
|
|
306
|
+
if (role === "human")
|
|
307
|
+
decisions.push(entry);
|
|
308
|
+
else if (role === "gate")
|
|
309
|
+
gates.push(entry);
|
|
310
|
+
else
|
|
311
|
+
ready.push(entry);
|
|
280
312
|
}
|
|
281
313
|
else {
|
|
282
314
|
blocked.push(entry);
|
|
@@ -285,9 +317,12 @@ export function computeActionabilityReport(candidates, corpus, statusRegistry) {
|
|
|
285
317
|
return {
|
|
286
318
|
ready,
|
|
287
319
|
blocked,
|
|
320
|
+
containers,
|
|
321
|
+
decisions,
|
|
322
|
+
gates,
|
|
288
323
|
active_count: activeCount,
|
|
289
|
-
container_count:
|
|
324
|
+
container_count: containers.length,
|
|
290
325
|
};
|
|
291
326
|
}
|
|
292
327
|
//# sourceMappingURL=actionability.js.map
|
|
293
|
-
//# debugId=
|
|
328
|
+
//# debugId=7d98ca40-57e8-5afe-8e9d-b694da0130d1
|
package/dist/core/item/id.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Defines item parsing, formatting, and lifecycle helpers for Id.
|
|
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]="04f02467-a4ee-5616-9fbf-3df1141efccf")}catch(e){}}();
|
|
8
8
|
import crypto from "node:crypto";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { TYPE_TO_FOLDER } from "../shared/constants.js";
|
|
@@ -60,11 +60,13 @@ function randomToken(length) {
|
|
|
60
60
|
}
|
|
61
61
|
return token;
|
|
62
62
|
}
|
|
63
|
+
/** Reserve an identity when either a live item path or its retained history exists. */
|
|
63
64
|
async function idExists(pmRoot, id, typeToFolder) {
|
|
64
65
|
const checks = [...new Set(Object.values(typeToFolder))].flatMap((folder) => [
|
|
65
66
|
path.join(pmRoot, folder, `${id}.md`),
|
|
66
67
|
path.join(pmRoot, folder, `${id}.toon`),
|
|
67
68
|
]);
|
|
69
|
+
checks.push(path.join(pmRoot, "history", `${id}.jsonl`));
|
|
68
70
|
return (await Promise.all(checks.map((target) => pathExists(target)))).some(Boolean);
|
|
69
71
|
}
|
|
70
72
|
/** Bounds accepted for the configurable random id token length (`ids.token_length`). */
|
|
@@ -95,8 +97,10 @@ export async function generateItemId(pmRoot, prefix, options = {}) {
|
|
|
95
97
|
while (tokenLength <= maxTokenLength) {
|
|
96
98
|
for (let i = 0; i < 32; i += 1) {
|
|
97
99
|
const id = `${normalizePrefix(prefix)}${randomToken(tokenLength)}`;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
const occupied = await (options.probeExisting === false
|
|
101
|
+
? pathExists(path.join(pmRoot, "history", `${id}.jsonl`))
|
|
102
|
+
: idExists(pmRoot, id, options.typeToFolder ?? TYPE_TO_FOLDER));
|
|
103
|
+
if (!occupied) {
|
|
100
104
|
return id;
|
|
101
105
|
}
|
|
102
106
|
attempts += 1;
|
|
@@ -106,4 +110,4 @@ export async function generateItemId(pmRoot, prefix, options = {}) {
|
|
|
106
110
|
throw new Error(`Unable to generate unique id after ${attempts} attempts`);
|
|
107
111
|
}
|
|
108
112
|
//# sourceMappingURL=id.js.map
|
|
109
|
-
//# debugId=
|
|
113
|
+
//# debugId=04f02467-a4ee-5616-9fbf-3df1141efccf
|