@unbrained/pm-cli 2026.5.30 → 2026.5.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/cli/commands/calendar.js +81 -9
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/create.js +34 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/extension/install-sources.d.ts +2 -0
- package/dist/cli/commands/extension/install-sources.js +11 -4
- package/dist/cli/commands/extension/install-sources.js.map +1 -1
- package/dist/cli/commands/reindex.js +3 -3
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/search.d.ts +17 -0
- package/dist/cli/commands/search.js +40 -10
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/register-list-query.js +9 -11
- package/dist/cli/register-list-query.js.map +1 -1
- package/dist/cli/register-mutation.js +20 -22
- package/dist/cli/register-mutation.js.map +1 -1
- package/dist/cli/register-operations.js +32 -25
- package/dist/cli/register-operations.js.map +1 -1
- package/dist/cli/register-setup.js +6 -8
- package/dist/cli/register-setup.js.map +1 -1
- package/dist/cli/registration-helpers.js +6 -3
- package/dist/cli/registration-helpers.js.map +1 -1
- package/dist/cli/search-refresh.d.ts +2 -0
- package/dist/cli/search-refresh.js +13 -0
- package/dist/cli/search-refresh.js.map +1 -0
- package/dist/cli-bundle/chunks/activity-OWYSUZOY.js +17 -0
- package/dist/cli-bundle/chunks/activity-OWYSUZOY.js.map +7 -0
- package/dist/cli-bundle/chunks/aggregate-ADUOSFRO.js +209 -0
- package/dist/cli-bundle/chunks/aggregate-ADUOSFRO.js.map +7 -0
- package/dist/cli-bundle/chunks/append-JD2DETU7.js +73 -0
- package/dist/cli-bundle/chunks/append-JD2DETU7.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-3HUE7XOF.js +27 -0
- package/dist/cli-bundle/chunks/chunk-3HUE7XOF.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-3M73P3G6.js +1636 -0
- package/dist/cli-bundle/chunks/chunk-3M73P3G6.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-4AEZR5EJ.js +189 -0
- package/dist/cli-bundle/chunks/chunk-4AEZR5EJ.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-5HSEP75M.js +330 -0
- package/dist/cli-bundle/chunks/chunk-5HSEP75M.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-6HT2F4TK.js +1770 -0
- package/dist/cli-bundle/chunks/chunk-6HT2F4TK.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-6YCV7WSL.js +552 -0
- package/dist/cli-bundle/chunks/chunk-6YCV7WSL.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-7Y53ZJIQ.js +202 -0
- package/dist/cli-bundle/chunks/chunk-7Y53ZJIQ.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-A7E7PUVX.js +157 -0
- package/dist/cli-bundle/chunks/chunk-A7E7PUVX.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-AKOOMO3T.js +90 -0
- package/dist/cli-bundle/chunks/chunk-AKOOMO3T.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-BUPWJ3IJ.js +950 -0
- package/dist/cli-bundle/chunks/chunk-BUPWJ3IJ.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-CEFIVZPG.js +79 -0
- package/dist/cli-bundle/chunks/chunk-CEFIVZPG.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-D7AOMPRH.js +349 -0
- package/dist/cli-bundle/chunks/chunk-D7AOMPRH.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-EAOG77HM.js +56 -0
- package/dist/cli-bundle/chunks/chunk-EAOG77HM.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-EGUGFSNX.js +47 -0
- package/dist/cli-bundle/chunks/chunk-EGUGFSNX.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-FM26RGSU.js +56 -0
- package/dist/cli-bundle/chunks/chunk-FM26RGSU.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-FQE6XZDB.js +435 -0
- package/dist/cli-bundle/chunks/chunk-FQE6XZDB.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-G57E5D7T.js +185 -0
- package/dist/cli-bundle/chunks/chunk-G57E5D7T.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-GN7IJPCD.js +3621 -0
- package/dist/cli-bundle/chunks/chunk-GN7IJPCD.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-JFWUHHS2.js +233 -0
- package/dist/cli-bundle/chunks/chunk-JFWUHHS2.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-JTWRXG4B.js +1861 -0
- package/dist/cli-bundle/chunks/chunk-JTWRXG4B.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-JULRTHJQ.js +648 -0
- package/dist/cli-bundle/chunks/chunk-JULRTHJQ.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-PB2YU2E3.js → chunk-KBQY3YG5.js} +28 -70
- package/dist/cli-bundle/chunks/chunk-KBQY3YG5.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-L2FXEKDB.js +185 -0
- package/dist/cli-bundle/chunks/chunk-L2FXEKDB.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-LRBOHPUE.js +189 -0
- package/dist/cli-bundle/chunks/chunk-LRBOHPUE.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-LSXGH6NE.js +5153 -0
- package/dist/cli-bundle/chunks/chunk-LSXGH6NE.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-MD7K4JYF.js +1345 -0
- package/dist/cli-bundle/chunks/chunk-MD7K4JYF.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-NGN7UV5K.js +341 -0
- package/dist/cli-bundle/chunks/chunk-NGN7UV5K.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-O7IWB35H.js +18 -0
- package/dist/cli-bundle/chunks/chunk-O7IWB35H.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-OGZEG5TT.js +2929 -0
- package/dist/cli-bundle/chunks/chunk-OGZEG5TT.js.map +7 -0
- package/dist/cli-bundle/chunks/{chunk-RJONRNXN.js → chunk-P53TN6IA.js} +22 -30
- package/dist/cli-bundle/chunks/chunk-P53TN6IA.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-PVNQLCIS.js +176 -0
- package/dist/cli-bundle/chunks/chunk-PVNQLCIS.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-PXUAZUUY.js +1680 -0
- package/dist/cli-bundle/chunks/chunk-PXUAZUUY.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-QAJBIGRA.js +20 -0
- package/dist/cli-bundle/chunks/chunk-QAJBIGRA.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-SEXQN4KL.js +60 -0
- package/dist/cli-bundle/chunks/chunk-SEXQN4KL.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-U7FMKRXH.js +362 -0
- package/dist/cli-bundle/chunks/chunk-U7FMKRXH.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-VGICHR7D.js +141 -0
- package/dist/cli-bundle/chunks/chunk-VGICHR7D.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-WDPXDQFG.js +632 -0
- package/dist/cli-bundle/chunks/chunk-WDPXDQFG.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-WJ2NAC37.js +252 -0
- package/dist/cli-bundle/chunks/chunk-WJ2NAC37.js.map +7 -0
- package/dist/cli-bundle/chunks/claim-EE4TKKCV.js +133 -0
- package/dist/cli-bundle/chunks/claim-EE4TKKCV.js.map +7 -0
- package/dist/cli-bundle/chunks/close-XY4CSAYF.js +14 -0
- package/dist/cli-bundle/chunks/close-XY4CSAYF.js.map +7 -0
- package/dist/cli-bundle/chunks/comments-AAKJQ6LW.js +104 -0
- package/dist/cli-bundle/chunks/comments-AAKJQ6LW.js.map +7 -0
- package/dist/cli-bundle/chunks/config-V3FZOKLI.js +1577 -0
- package/dist/cli-bundle/chunks/config-V3FZOKLI.js.map +7 -0
- package/dist/cli-bundle/chunks/context-RZ5QPNJB.js +1458 -0
- package/dist/cli-bundle/chunks/context-RZ5QPNJB.js.map +7 -0
- package/dist/cli-bundle/chunks/contracts-YLMH33VU.js +1245 -0
- package/dist/cli-bundle/chunks/contracts-YLMH33VU.js.map +7 -0
- package/dist/cli-bundle/chunks/create-PZVHRIZQ.js +28 -0
- package/dist/cli-bundle/chunks/create-PZVHRIZQ.js.map +7 -0
- package/dist/cli-bundle/chunks/delete-RHCTEORS.js +59 -0
- package/dist/cli-bundle/chunks/delete-RHCTEORS.js.map +7 -0
- package/dist/cli-bundle/chunks/deps-MZYCMLAO.js +229 -0
- package/dist/cli-bundle/chunks/deps-MZYCMLAO.js.map +7 -0
- package/dist/cli-bundle/chunks/docs-FXXUCZYU.js +35 -0
- package/dist/cli-bundle/chunks/docs-FXXUCZYU.js.map +7 -0
- package/dist/cli-bundle/chunks/extension-B4WL7BQU.js +25 -0
- package/dist/cli-bundle/chunks/extension-B4WL7BQU.js.map +7 -0
- package/dist/cli-bundle/chunks/files-WVOX67PE.js +286 -0
- package/dist/cli-bundle/chunks/files-WVOX67PE.js.map +7 -0
- package/dist/cli-bundle/chunks/gc-JWNF66EC.js +181 -0
- package/dist/cli-bundle/chunks/gc-JWNF66EC.js.map +7 -0
- package/dist/cli-bundle/chunks/get-LTK3HOCQ.js +198 -0
- package/dist/cli-bundle/chunks/get-LTK3HOCQ.js.map +7 -0
- package/dist/cli-bundle/chunks/health-TCWL55KM.js +1233 -0
- package/dist/cli-bundle/chunks/health-TCWL55KM.js.map +7 -0
- package/dist/cli-bundle/chunks/history-A2JUDXUE.js +21 -0
- package/dist/cli-bundle/chunks/history-A2JUDXUE.js.map +7 -0
- package/dist/cli-bundle/chunks/history-redact-ED6RFCPL.js +20 -0
- package/dist/cli-bundle/chunks/history-redact-ED6RFCPL.js.map +7 -0
- package/dist/cli-bundle/chunks/history-repair-ZWSTDRH2.js +198 -0
- package/dist/cli-bundle/chunks/history-repair-ZWSTDRH2.js.map +7 -0
- package/dist/cli-bundle/chunks/init-M2WUB4YH.js +16 -0
- package/dist/cli-bundle/chunks/init-M2WUB4YH.js.map +7 -0
- package/dist/cli-bundle/chunks/learnings-NXTWE3FR.js +48 -0
- package/dist/cli-bundle/chunks/learnings-NXTWE3FR.js.map +7 -0
- package/dist/cli-bundle/chunks/list-OGWQPC26.js +20 -0
- package/dist/cli-bundle/chunks/list-OGWQPC26.js.map +7 -0
- package/dist/cli-bundle/chunks/notes-DFS5ZGOO.js +48 -0
- package/dist/cli-bundle/chunks/notes-DFS5ZGOO.js.map +7 -0
- package/dist/cli-bundle/chunks/plan-67AB2PLM.js +1175 -0
- package/dist/cli-bundle/chunks/plan-67AB2PLM.js.map +7 -0
- package/dist/cli-bundle/chunks/{register-list-query-EIVQ5FMR.js → register-list-query-UP3VRUFS.js} +24 -15
- package/dist/cli-bundle/chunks/register-list-query-UP3VRUFS.js.map +7 -0
- package/dist/cli-bundle/chunks/{register-mutation-RSZNPSGI.js → register-mutation-YWSBF545.js} +38 -28
- package/dist/cli-bundle/chunks/register-mutation-YWSBF545.js.map +7 -0
- package/dist/cli-bundle/chunks/{register-operations-APUSYDMR.js → register-operations-3BDTGSDH.js} +49 -30
- package/dist/cli-bundle/chunks/register-operations-3BDTGSDH.js.map +7 -0
- package/dist/cli-bundle/chunks/{register-setup-OZOKSMPJ.js → register-setup-V56GDRUS.js} +21 -12
- package/dist/cli-bundle/chunks/register-setup-V56GDRUS.js.map +7 -0
- package/dist/cli-bundle/chunks/restore-MB2ISME7.js +18 -0
- package/dist/cli-bundle/chunks/restore-MB2ISME7.js.map +7 -0
- package/dist/cli-bundle/chunks/schema-Q622KC46.js +119 -0
- package/dist/cli-bundle/chunks/schema-Q622KC46.js.map +7 -0
- package/dist/cli-bundle/chunks/search-HENBL456.js +1135 -0
- package/dist/cli-bundle/chunks/search-HENBL456.js.map +7 -0
- package/dist/cli-bundle/chunks/stats-N6H3YP2F.js +124 -0
- package/dist/cli-bundle/chunks/stats-N6H3YP2F.js.map +7 -0
- package/dist/cli-bundle/chunks/test-VNBDRJ4Z.js +32 -0
- package/dist/cli-bundle/chunks/test-VNBDRJ4Z.js.map +7 -0
- package/dist/cli-bundle/chunks/test-all-G2AZOHXI.js +332 -0
- package/dist/cli-bundle/chunks/test-all-G2AZOHXI.js.map +7 -0
- package/dist/cli-bundle/chunks/test-runs-GX5HTPSF.js +939 -0
- package/dist/cli-bundle/chunks/test-runs-GX5HTPSF.js.map +7 -0
- package/dist/cli-bundle/chunks/update-H6SCWNMA.js +22 -0
- package/dist/cli-bundle/chunks/update-H6SCWNMA.js.map +7 -0
- package/dist/cli-bundle/chunks/update-many-46HRSP2Q.js +676 -0
- package/dist/cli-bundle/chunks/update-many-46HRSP2Q.js.map +7 -0
- package/dist/cli-bundle/chunks/upgrade-H5HHM5UV.js +271 -0
- package/dist/cli-bundle/chunks/upgrade-H5HHM5UV.js.map +7 -0
- package/dist/cli-bundle/chunks/validate-TMCVKIGZ.js +1110 -0
- package/dist/cli-bundle/chunks/validate-TMCVKIGZ.js.map +7 -0
- package/dist/cli-bundle/main.js +146 -90
- package/dist/cli-bundle/main.js.map +4 -4
- package/dist/core/item/item-type-definition.js +6 -2
- package/dist/core/item/item-type-definition.js.map +1 -1
- package/dist/core/item/type-registry.d.ts +4 -0
- package/dist/core/item/type-registry.js +10 -2
- package/dist/core/item/type-registry.js.map +1 -1
- package/dist/core/search/background-refresh.d.ts +53 -0
- package/dist/core/search/background-refresh.js +286 -0
- package/dist/core/search/background-refresh.js.map +1 -0
- package/dist/core/search/cache.d.ts +20 -1
- package/dist/core/search/cache.js +33 -4
- package/dist/core/search/cache.js.map +1 -1
- package/dist/core/search/vector-stores.js +7 -3
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/shared/constants.d.ts +2 -2
- package/dist/core/shared/constants.js +2 -3
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/store/item-format-migration.js +25 -15
- package/dist/core/store/item-format-migration.js.map +1 -1
- package/dist/core/store/settings-validator.js +6 -2
- package/dist/core/store/settings-validator.js.map +1 -1
- package/dist/core/telemetry/runtime.d.ts +10 -0
- package/dist/core/telemetry/runtime.js +86 -2
- package/dist/core/telemetry/runtime.js.map +1 -1
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +4 -4
- package/dist/sdk/cli-contracts/tool-parameter-tables.js.map +1 -1
- package/dist/sdk/cli-contracts.js +26 -5
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/docs/RELEASING.md +1 -1
- package/package.json +1 -2
- package/dist/cli-bundle/chunks/chunk-PB2YU2E3.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-RJONRNXN.js.map +0 -7
- package/dist/cli-bundle/chunks/chunk-SW5BMMCU.js +0 -13864
- package/dist/cli-bundle/chunks/chunk-SW5BMMCU.js.map +0 -7
- package/dist/cli-bundle/chunks/commands-XJ4TJ5UN.js +0 -24225
- package/dist/cli-bundle/chunks/commands-XJ4TJ5UN.js.map +0 -7
- package/dist/cli-bundle/chunks/register-list-query-EIVQ5FMR.js.map +0 -7
- package/dist/cli-bundle/chunks/register-mutation-RSZNPSGI.js.map +0 -7
- package/dist/cli-bundle/chunks/register-operations-APUSYDMR.js.map +0 -7
- package/dist/cli-bundle/chunks/register-setup-OZOKSMPJ.js.map +0 -7
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveAuthor
|
|
3
|
+
} from "./chunk-O7IWB35H.js";
|
|
4
|
+
import {
|
|
5
|
+
readHistoryEntries
|
|
6
|
+
} from "./chunk-LRBOHPUE.js";
|
|
7
|
+
import {
|
|
8
|
+
EMPTY_REPLAY_DOCUMENT,
|
|
9
|
+
normalizeReplayPatchOps,
|
|
10
|
+
replayHash,
|
|
11
|
+
toReplayDocument
|
|
12
|
+
} from "./chunk-JFWUHHS2.js";
|
|
13
|
+
import {
|
|
14
|
+
appendHistoryEntry,
|
|
15
|
+
createHistoryEntry,
|
|
16
|
+
enforceHistoryStreamPolicyForItem,
|
|
17
|
+
locateItem,
|
|
18
|
+
readLocatedItem
|
|
19
|
+
} from "./chunk-BUPWJ3IJ.js";
|
|
20
|
+
import {
|
|
21
|
+
acquireLock
|
|
22
|
+
} from "./chunk-4AEZR5EJ.js";
|
|
23
|
+
import {
|
|
24
|
+
normalizeItemId,
|
|
25
|
+
normalizeRawItemId
|
|
26
|
+
} from "./chunk-CEFIVZPG.js";
|
|
27
|
+
import {
|
|
28
|
+
canonicalDocument,
|
|
29
|
+
serializeItemDocument
|
|
30
|
+
} from "./chunk-MD7K4JYF.js";
|
|
31
|
+
import {
|
|
32
|
+
resolveItemTypeRegistry
|
|
33
|
+
} from "./chunk-WDPXDQFG.js";
|
|
34
|
+
import {
|
|
35
|
+
nowIso
|
|
36
|
+
} from "./chunk-PVNQLCIS.js";
|
|
37
|
+
import {
|
|
38
|
+
EXIT_CODE,
|
|
39
|
+
PmCliError,
|
|
40
|
+
getActiveExtensionRegistrations,
|
|
41
|
+
getHistoryPath,
|
|
42
|
+
getItemPath,
|
|
43
|
+
getSettingsPath,
|
|
44
|
+
pathExists,
|
|
45
|
+
readFileIfExists,
|
|
46
|
+
readSettings,
|
|
47
|
+
resolvePmRoot,
|
|
48
|
+
runActiveOnWriteHooks,
|
|
49
|
+
writeFileAtomic
|
|
50
|
+
} from "./chunk-LSXGH6NE.js";
|
|
51
|
+
|
|
52
|
+
// dist/cli/commands/restore.js
|
|
53
|
+
import jsonPatch from "fast-json-patch";
|
|
54
|
+
import fs from "node:fs/promises";
|
|
55
|
+
!(function() {
|
|
56
|
+
try {
|
|
57
|
+
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
58
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "d40fa5ec-9a3f-52de-980b-b83b2e0021d1");
|
|
59
|
+
} catch (e2) {
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
function ensureReplayTarget(target, history) {
|
|
63
|
+
const trimmed = target.trim();
|
|
64
|
+
if (!trimmed) {
|
|
65
|
+
throw new PmCliError("Missing restore target. Use a timestamp or version number.", EXIT_CODE.USAGE);
|
|
66
|
+
}
|
|
67
|
+
if (/^\d+$/.test(trimmed)) {
|
|
68
|
+
const version = Number(trimmed);
|
|
69
|
+
if (!Number.isSafeInteger(version) || version < 1 || version > history.length) {
|
|
70
|
+
throw new PmCliError(`Restore version must be between 1 and ${history.length} for this item.`, EXIT_CODE.USAGE);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
kind: "version",
|
|
74
|
+
raw: trimmed,
|
|
75
|
+
historyIndex: version - 1
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const parsedTarget = Date.parse(trimmed);
|
|
79
|
+
if (!Number.isFinite(parsedTarget)) {
|
|
80
|
+
throw new PmCliError(`Invalid restore target "${target}". Use a positive version number or ISO timestamp.`, EXIT_CODE.USAGE);
|
|
81
|
+
}
|
|
82
|
+
let index = -1;
|
|
83
|
+
for (let i = 0; i < history.length; i += 1) {
|
|
84
|
+
const entryTimestamp = Date.parse(history[i].ts);
|
|
85
|
+
if (!Number.isFinite(entryTimestamp)) {
|
|
86
|
+
throw new PmCliError(`History for this item contains invalid timestamp at entry ${i + 1}.`, EXIT_CODE.GENERIC_FAILURE);
|
|
87
|
+
}
|
|
88
|
+
if (entryTimestamp <= parsedTarget) {
|
|
89
|
+
index = i;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (index < 0) {
|
|
93
|
+
throw new PmCliError(`No history entries exist at or before timestamp ${trimmed}.`, EXIT_CODE.USAGE);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
kind: "timestamp",
|
|
97
|
+
raw: trimmed,
|
|
98
|
+
historyIndex: index
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function extractPatchFailureContext(patch, error) {
|
|
102
|
+
const context = {};
|
|
103
|
+
if (error instanceof Error && error.message.trim().length > 0) {
|
|
104
|
+
context.reason = error.message.trim();
|
|
105
|
+
}
|
|
106
|
+
if (typeof error !== "object" || error === null) {
|
|
107
|
+
return context;
|
|
108
|
+
}
|
|
109
|
+
const candidate = error;
|
|
110
|
+
if (typeof candidate.index === "number" && Number.isInteger(candidate.index) && candidate.index >= 0) {
|
|
111
|
+
context.patchIndex = candidate.index;
|
|
112
|
+
}
|
|
113
|
+
const operationRecord = typeof candidate.operation === "object" && candidate.operation !== null ? candidate.operation : null;
|
|
114
|
+
if (operationRecord && typeof operationRecord.op === "string") {
|
|
115
|
+
context.op = operationRecord.op;
|
|
116
|
+
}
|
|
117
|
+
if (operationRecord && typeof operationRecord.path === "string") {
|
|
118
|
+
context.path = operationRecord.path;
|
|
119
|
+
}
|
|
120
|
+
if (operationRecord && typeof operationRecord.from === "string") {
|
|
121
|
+
context.from = operationRecord.from;
|
|
122
|
+
}
|
|
123
|
+
if ((context.op === void 0 || context.path === void 0) && context.patchIndex !== void 0) {
|
|
124
|
+
const fallback = patch[context.patchIndex];
|
|
125
|
+
if (fallback) {
|
|
126
|
+
context.op = context.op ?? fallback.op;
|
|
127
|
+
context.path = context.path ?? fallback.path;
|
|
128
|
+
context.from = context.from ?? fallback.from;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return context;
|
|
132
|
+
}
|
|
133
|
+
function applyHistoryPatch(current, patch, entryNumber, entryOp) {
|
|
134
|
+
try {
|
|
135
|
+
const normalizedPatch = normalizeReplayPatchOps(patch);
|
|
136
|
+
const applied = jsonPatch.applyPatch(structuredClone(current), normalizedPatch, true, false).newDocument;
|
|
137
|
+
if (typeof applied !== "object" || applied === null || !("metadata" in applied) || !("body" in applied) || typeof applied.body !== "string" || typeof applied.metadata !== "object" || applied.metadata === null) {
|
|
138
|
+
throw new PmCliError(`History replay produced an invalid document shape at entry ${entryNumber}.`, EXIT_CODE.GENERIC_FAILURE);
|
|
139
|
+
}
|
|
140
|
+
const replay = applied;
|
|
141
|
+
return {
|
|
142
|
+
metadata: replay.metadata,
|
|
143
|
+
body: replay.body
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (error instanceof PmCliError) {
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
const failureContext = extractPatchFailureContext(patch, error);
|
|
150
|
+
const contextTokens = [
|
|
151
|
+
`history_op=${entryOp}`,
|
|
152
|
+
failureContext.patchIndex !== void 0 ? `patch_index=${failureContext.patchIndex}` : null,
|
|
153
|
+
failureContext.op ? `op=${failureContext.op}` : null,
|
|
154
|
+
failureContext.path ? `path=${failureContext.path}` : null,
|
|
155
|
+
failureContext.from ? `from=${failureContext.from}` : null
|
|
156
|
+
].filter((token) => token !== null);
|
|
157
|
+
const reasonSuffix = failureContext.reason ? ` ${failureContext.reason}` : "";
|
|
158
|
+
throw new PmCliError(`Failed to apply history patch at entry ${entryNumber} (${contextTokens.join(", ")}).${reasonSuffix}`, EXIT_CODE.GENERIC_FAILURE);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function replayToTarget(history, targetIndex) {
|
|
162
|
+
let document = structuredClone(EMPTY_REPLAY_DOCUMENT);
|
|
163
|
+
for (let i = 0; i <= targetIndex; i += 1) {
|
|
164
|
+
const entry = history[i];
|
|
165
|
+
const beforeHash = replayHash(document);
|
|
166
|
+
if (beforeHash !== entry.before_hash) {
|
|
167
|
+
throw new PmCliError(`History hash mismatch before replay at entry ${i + 1}.`, EXIT_CODE.GENERIC_FAILURE);
|
|
168
|
+
}
|
|
169
|
+
document = applyHistoryPatch(document, entry.patch, i + 1, entry.op);
|
|
170
|
+
const afterHash = replayHash(document);
|
|
171
|
+
if (afterHash !== entry.after_hash) {
|
|
172
|
+
throw new PmCliError(`History hash mismatch after replay at entry ${i + 1}.`, EXIT_CODE.GENERIC_FAILURE);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return document;
|
|
176
|
+
}
|
|
177
|
+
function ensureMaterializedRestoreTarget(replayDocument, target) {
|
|
178
|
+
if (Object.keys(replayDocument.metadata).length > 0) {
|
|
179
|
+
return replayDocument;
|
|
180
|
+
}
|
|
181
|
+
throw new PmCliError(`Restore target ${target.raw} resolves to a deleted state; choose a version or timestamp where the item exists.`, EXIT_CODE.USAGE);
|
|
182
|
+
}
|
|
183
|
+
function replayCurrentDocument(history) {
|
|
184
|
+
const currentReplay = replayToTarget(history, history.length - 1);
|
|
185
|
+
if (Object.keys(currentReplay.metadata).length === 0) {
|
|
186
|
+
return {
|
|
187
|
+
metadata: {},
|
|
188
|
+
body: currentReplay.body
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return canonicalDocument({
|
|
192
|
+
metadata: currentReplay.metadata,
|
|
193
|
+
body: currentReplay.body
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async function resolveRestoreSubject(pmRoot, id, settings, typeToFolder) {
|
|
197
|
+
const located = await locateItem(pmRoot, id, settings.id_prefix, settings.item_format, typeToFolder);
|
|
198
|
+
if (located) {
|
|
199
|
+
const historyPath = getHistoryPath(pmRoot, located.id);
|
|
200
|
+
const historyPolicy = await enforceHistoryStreamPolicyForItem({
|
|
201
|
+
pmRoot,
|
|
202
|
+
settings,
|
|
203
|
+
itemId: located.id,
|
|
204
|
+
commandLabel: "restore"
|
|
205
|
+
});
|
|
206
|
+
return {
|
|
207
|
+
id: located.id,
|
|
208
|
+
historyPath,
|
|
209
|
+
located,
|
|
210
|
+
historyPolicyWarnings: historyPolicy.warnings
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const normalizedId = normalizeItemId(id, settings.id_prefix);
|
|
214
|
+
const rawNormalizedId = normalizeRawItemId(id);
|
|
215
|
+
const candidateIds = normalizedId === rawNormalizedId ? [normalizedId] : [normalizedId, rawNormalizedId];
|
|
216
|
+
for (const candidateId of candidateIds) {
|
|
217
|
+
const historyPath = getHistoryPath(pmRoot, candidateId);
|
|
218
|
+
if (await pathExists(historyPath)) {
|
|
219
|
+
return {
|
|
220
|
+
id: candidateId,
|
|
221
|
+
historyPath,
|
|
222
|
+
located: null,
|
|
223
|
+
historyPolicyWarnings: []
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
throw new PmCliError(`Item ${id} not found`, EXIT_CODE.NOT_FOUND);
|
|
228
|
+
}
|
|
229
|
+
function changedFields(beforeDocument, afterDocument) {
|
|
230
|
+
const beforeReplay = toReplayDocument(beforeDocument);
|
|
231
|
+
const afterReplay = toReplayDocument(afterDocument);
|
|
232
|
+
const patch = jsonPatch.compare(beforeReplay, afterReplay);
|
|
233
|
+
const fields = /* @__PURE__ */ new Set();
|
|
234
|
+
for (const op of patch) {
|
|
235
|
+
if (op.path === "/body" || op.path.startsWith("/body/")) {
|
|
236
|
+
fields.add("body");
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const segment = op.path.replace(/^\/(?:metadata|front_matter)\/?/, "").split("/")[0];
|
|
240
|
+
fields.add(segment.replaceAll("~1", "/").replaceAll("~0", "~"));
|
|
241
|
+
}
|
|
242
|
+
return Array.from(fields).sort((a, b) => a.localeCompare(b));
|
|
243
|
+
}
|
|
244
|
+
async function runRestore(id, target, options, global2) {
|
|
245
|
+
const pmRoot = resolvePmRoot(process.cwd(), global2.path);
|
|
246
|
+
if (!await pathExists(getSettingsPath(pmRoot))) {
|
|
247
|
+
throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
|
|
248
|
+
}
|
|
249
|
+
const settings = await readSettings(pmRoot);
|
|
250
|
+
const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
|
|
251
|
+
const subject = await resolveRestoreSubject(pmRoot, id, settings, typeRegistry.type_to_folder);
|
|
252
|
+
const resolvedId = subject.id;
|
|
253
|
+
const historyRawBeforeLock = await readFileIfExists(subject.historyPath);
|
|
254
|
+
const history = await readHistoryEntries(subject.historyPath, resolvedId);
|
|
255
|
+
if (history.length === 0) {
|
|
256
|
+
throw new PmCliError(`No history exists for ${resolvedId}; restore is unavailable.`, EXIT_CODE.NOT_FOUND);
|
|
257
|
+
}
|
|
258
|
+
const resolvedTarget = ensureReplayTarget(target, history);
|
|
259
|
+
const replayDocument = ensureMaterializedRestoreTarget(replayToTarget(history, resolvedTarget.historyIndex), resolvedTarget);
|
|
260
|
+
const restoredDocument = canonicalDocument({
|
|
261
|
+
metadata: replayDocument.metadata,
|
|
262
|
+
body: replayDocument.body
|
|
263
|
+
}, { schema: settings.schema });
|
|
264
|
+
if (restoredDocument.metadata.id !== resolvedId) {
|
|
265
|
+
throw new PmCliError(`Restore target resolved to item ${restoredDocument.metadata.id}, expected ${resolvedId}.`, EXIT_CODE.GENERIC_FAILURE);
|
|
266
|
+
}
|
|
267
|
+
const loadedItemBeforeLock = subject.located ? await readLocatedItem(subject.located, { schema: settings.schema }) : null;
|
|
268
|
+
const currentItemRawBeforeLock = loadedItemBeforeLock?.raw ?? null;
|
|
269
|
+
const author = resolveAuthor(options.author, settings.author_default);
|
|
270
|
+
const releaseLock = await acquireLock(pmRoot, resolvedId, settings.locks.ttl_seconds, author, Boolean(options.force), settings.governance.force_required_for_stale_lock);
|
|
271
|
+
try {
|
|
272
|
+
const historyRawUnderLock = await readFileIfExists(subject.historyPath);
|
|
273
|
+
if (historyRawUnderLock !== historyRawBeforeLock) {
|
|
274
|
+
throw new PmCliError(`History for ${resolvedId} changed while waiting for lock; retry restore.`, EXIT_CODE.CONFLICT);
|
|
275
|
+
}
|
|
276
|
+
const locatedUnderLock = await locateItem(pmRoot, resolvedId, settings.id_prefix, settings.item_format, typeRegistry.type_to_folder);
|
|
277
|
+
const loadedItemUnderLock = locatedUnderLock ? await readLocatedItem(locatedUnderLock, { schema: settings.schema }) : null;
|
|
278
|
+
if ((loadedItemUnderLock?.raw ?? null) !== currentItemRawBeforeLock) {
|
|
279
|
+
throw new PmCliError(`Item ${resolvedId} changed while waiting for lock; retry restore.`, EXIT_CODE.CONFLICT);
|
|
280
|
+
}
|
|
281
|
+
const existingItemPath = locatedUnderLock?.itemPath ?? null;
|
|
282
|
+
const itemFormat = "toon";
|
|
283
|
+
let resolvedCurrentDocument;
|
|
284
|
+
let resolvedOriginalRaw = null;
|
|
285
|
+
if (loadedItemUnderLock) {
|
|
286
|
+
resolvedCurrentDocument = loadedItemUnderLock.document;
|
|
287
|
+
resolvedOriginalRaw = loadedItemUnderLock.raw;
|
|
288
|
+
} else {
|
|
289
|
+
resolvedCurrentDocument = replayCurrentDocument(history);
|
|
290
|
+
}
|
|
291
|
+
const assigned = resolvedCurrentDocument.metadata.assignee?.trim();
|
|
292
|
+
const ownershipWarnings = [];
|
|
293
|
+
const hasOwnershipConflict = assigned && assigned !== author && !options.force;
|
|
294
|
+
if (hasOwnershipConflict) {
|
|
295
|
+
if (settings.governance.ownership_enforcement === "strict") {
|
|
296
|
+
throw new PmCliError(`Item ${resolvedId} is assigned to ${assigned}. Use --force to override.`, EXIT_CODE.CONFLICT);
|
|
297
|
+
}
|
|
298
|
+
if (settings.governance.ownership_enforcement === "warn") {
|
|
299
|
+
ownershipWarnings.push(`ownership_warning:assignee_conflict:${resolvedId}:${assigned}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const serializedRestore = serializeItemDocument(restoredDocument, { format: itemFormat, schema: settings.schema });
|
|
303
|
+
const restoredItemPath = getItemPath(pmRoot, restoredDocument.metadata.type, resolvedId, itemFormat, typeRegistry.type_to_folder);
|
|
304
|
+
await writeFileAtomic(restoredItemPath, serializedRestore);
|
|
305
|
+
if (existingItemPath && restoredItemPath !== existingItemPath) {
|
|
306
|
+
await fs.rm(existingItemPath);
|
|
307
|
+
}
|
|
308
|
+
const historyEntry = createHistoryEntry({
|
|
309
|
+
nowIso: nowIso(),
|
|
310
|
+
author,
|
|
311
|
+
op: "restore",
|
|
312
|
+
before: resolvedCurrentDocument,
|
|
313
|
+
after: restoredDocument,
|
|
314
|
+
message: options.message
|
|
315
|
+
});
|
|
316
|
+
try {
|
|
317
|
+
await appendHistoryEntry(subject.historyPath, historyEntry);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (existingItemPath && resolvedOriginalRaw !== null && restoredItemPath !== existingItemPath) {
|
|
320
|
+
await writeFileAtomic(existingItemPath, resolvedOriginalRaw);
|
|
321
|
+
await fs.rm(restoredItemPath, { force: true });
|
|
322
|
+
} else if (existingItemPath && resolvedOriginalRaw !== null) {
|
|
323
|
+
await writeFileAtomic(existingItemPath, resolvedOriginalRaw);
|
|
324
|
+
} else {
|
|
325
|
+
await fs.rm(restoredItemPath, { force: true });
|
|
326
|
+
}
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
const hookWarnings = [
|
|
330
|
+
...await runActiveOnWriteHooks({
|
|
331
|
+
path: restoredItemPath,
|
|
332
|
+
scope: "project",
|
|
333
|
+
op: "restore"
|
|
334
|
+
}),
|
|
335
|
+
...await runActiveOnWriteHooks({
|
|
336
|
+
path: subject.historyPath,
|
|
337
|
+
scope: "project",
|
|
338
|
+
op: "restore:history"
|
|
339
|
+
})
|
|
340
|
+
];
|
|
341
|
+
const targetEntry = history[resolvedTarget.historyIndex];
|
|
342
|
+
return {
|
|
343
|
+
item: restoredDocument.metadata,
|
|
344
|
+
restored_from: {
|
|
345
|
+
kind: resolvedTarget.kind,
|
|
346
|
+
target: resolvedTarget.raw,
|
|
347
|
+
history_index: resolvedTarget.historyIndex + 1,
|
|
348
|
+
entry_ts: targetEntry.ts,
|
|
349
|
+
entry_op: targetEntry.op
|
|
350
|
+
},
|
|
351
|
+
changed_fields: changedFields(resolvedCurrentDocument, restoredDocument),
|
|
352
|
+
warnings: [...subject.historyPolicyWarnings, ...ownershipWarnings, ...hookWarnings]
|
|
353
|
+
};
|
|
354
|
+
} finally {
|
|
355
|
+
await releaseLock();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export {
|
|
360
|
+
runRestore
|
|
361
|
+
};
|
|
362
|
+
//# sourceMappingURL=chunk-U7FMKRXH.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../cli/commands/restore.ts"],
|
|
4
|
+
"sourcesContent": ["import jsonPatch from \"fast-json-patch\";\nimport fs from \"node:fs/promises\";\nimport { pathExists, readFileIfExists, writeFileAtomic } from \"../../core/fs/fs-utils.js\";\nimport { appendHistoryEntry, createHistoryEntry } from \"../../core/history/history.js\";\nimport {\n EMPTY_REPLAY_DOCUMENT,\n normalizeReplayPatchOps,\n replayHash,\n toReplayDocument,\n type ReplayDocument as CanonicalReplayDocument,\n} from \"../../core/history/replay.js\";\nimport { enforceHistoryStreamPolicyForItem } from \"../../core/history/history-stream-policy.js\";\nimport { normalizeItemId, normalizeRawItemId } from \"../../core/item/id.js\";\nimport { canonicalDocument, serializeItemDocument } from \"../../core/item/item-format.js\";\nimport { resolveItemTypeRegistry } from \"../../core/item/type-registry.js\";\nimport { acquireLock } from \"../../core/lock/lock.js\";\nimport { EXIT_CODE } from \"../../core/shared/constants.js\";\nimport type { GlobalOptions } from \"../../core/shared/command-types.js\";\nimport { PmCliError } from \"../../core/shared/errors.js\";\nimport { nowIso } from \"../../core/shared/time.js\";\nimport { getActiveExtensionRegistrations, runActiveOnWriteHooks } from \"../../core/extensions/index.js\";\nimport { locateItem, readLocatedItem } from \"../../core/store/item-store.js\";\nimport { getHistoryPath, getItemPath, getSettingsPath, resolvePmRoot } from \"../../core/store/paths.js\";\nimport { readSettings } from \"../../core/store/settings.js\";\nimport { resolveAuthor } from \"../../core/shared/author.js\";\nimport type { HistoryEntry, HistoryPatchOp, ItemDocument, ItemMetadata } from \"../../types/index.js\";\nimport { readHistoryEntries } from \"./history.js\";\n\ninterface ResolvedRestoreTarget {\n kind: \"version\" | \"timestamp\";\n raw: string;\n historyIndex: number;\n}\n\ninterface ResolvedRestoreSubject {\n id: string;\n historyPath: string;\n located: Awaited<ReturnType<typeof locateItem>>;\n historyPolicyWarnings: string[];\n}\n\nexport interface RestoreCommandOptions {\n author?: string;\n message?: string;\n force?: boolean;\n}\n\nexport interface RestoreResult {\n item: ItemMetadata;\n restored_from: {\n kind: \"version\" | \"timestamp\";\n target: string;\n history_index: number;\n entry_ts: string;\n entry_op: string;\n };\n changed_fields: string[];\n warnings: string[];\n}\n\nfunction ensureReplayTarget(target: string, history: HistoryEntry[]): ResolvedRestoreTarget {\n const trimmed = target.trim();\n if (!trimmed) {\n throw new PmCliError(\"Missing restore target. Use a timestamp or version number.\", EXIT_CODE.USAGE);\n }\n\n if (/^\\d+$/.test(trimmed)) {\n const version = Number(trimmed);\n if (!Number.isSafeInteger(version) || version < 1 || version > history.length) {\n throw new PmCliError(\n `Restore version must be between 1 and ${history.length} for this item.`,\n EXIT_CODE.USAGE,\n );\n }\n return {\n kind: \"version\",\n raw: trimmed,\n historyIndex: version - 1,\n };\n }\n\n const parsedTarget = Date.parse(trimmed);\n if (!Number.isFinite(parsedTarget)) {\n throw new PmCliError(\n `Invalid restore target \"${target}\". Use a positive version number or ISO timestamp.`,\n EXIT_CODE.USAGE,\n );\n }\n\n let index = -1;\n for (let i = 0; i < history.length; i += 1) {\n const entryTimestamp = Date.parse(history[i].ts);\n if (!Number.isFinite(entryTimestamp)) {\n throw new PmCliError(\n `History for this item contains invalid timestamp at entry ${i + 1}.`,\n EXIT_CODE.GENERIC_FAILURE,\n );\n }\n if (entryTimestamp <= parsedTarget) {\n index = i;\n }\n }\n\n if (index < 0) {\n throw new PmCliError(`No history entries exist at or before timestamp ${trimmed}.`, EXIT_CODE.USAGE);\n }\n\n return {\n kind: \"timestamp\",\n raw: trimmed,\n historyIndex: index,\n };\n}\n\nfunction extractPatchFailureContext(\n patch: HistoryPatchOp[],\n error: unknown,\n): { patchIndex?: number; op?: string; path?: string; from?: string; reason?: string } {\n const context: { patchIndex?: number; op?: string; path?: string; from?: string; reason?: string } = {};\n if (error instanceof Error && error.message.trim().length > 0) {\n context.reason = error.message.trim();\n }\n if (typeof error !== \"object\" || error === null) {\n return context;\n }\n const candidate = error as {\n index?: unknown;\n operation?: unknown;\n };\n if (typeof candidate.index === \"number\" && Number.isInteger(candidate.index) && candidate.index >= 0) {\n context.patchIndex = candidate.index;\n }\n const operationRecord =\n typeof candidate.operation === \"object\" && candidate.operation !== null\n ? (candidate.operation as { op?: unknown; path?: unknown; from?: unknown })\n : null;\n if (operationRecord && typeof operationRecord.op === \"string\") {\n context.op = operationRecord.op;\n }\n if (operationRecord && typeof operationRecord.path === \"string\") {\n context.path = operationRecord.path;\n }\n if (operationRecord && typeof operationRecord.from === \"string\") {\n context.from = operationRecord.from;\n }\n if ((context.op === undefined || context.path === undefined) && context.patchIndex !== undefined) {\n const fallback = patch[context.patchIndex];\n if (fallback) {\n context.op = context.op ?? fallback.op;\n context.path = context.path ?? fallback.path;\n context.from = context.from ?? fallback.from;\n }\n }\n return context;\n}\n\nfunction applyHistoryPatch(\n current: CanonicalReplayDocument,\n patch: HistoryPatchOp[],\n entryNumber: number,\n entryOp: string,\n): CanonicalReplayDocument {\n try {\n const normalizedPatch = normalizeReplayPatchOps(patch);\n const applied = jsonPatch.applyPatch(\n structuredClone(current),\n normalizedPatch as jsonPatch.Operation[],\n true,\n false,\n ).newDocument as unknown;\n if (\n typeof applied !== \"object\" ||\n applied === null ||\n !(\"metadata\" in applied) ||\n !(\"body\" in applied) ||\n typeof (applied as { body: unknown }).body !== \"string\" ||\n typeof (applied as { metadata: unknown }).metadata !== \"object\" ||\n (applied as { metadata: unknown }).metadata === null\n ) {\n throw new PmCliError(\n `History replay produced an invalid document shape at entry ${entryNumber}.`,\n EXIT_CODE.GENERIC_FAILURE,\n );\n }\n const replay = applied as { metadata: Record<string, unknown>; body: string };\n return {\n metadata: replay.metadata,\n body: replay.body,\n };\n } catch (error: unknown) {\n if (error instanceof PmCliError) {\n throw error;\n }\n const failureContext = extractPatchFailureContext(patch, error);\n const contextTokens = [\n `history_op=${entryOp}`,\n failureContext.patchIndex !== undefined ? `patch_index=${failureContext.patchIndex}` : null,\n failureContext.op ? `op=${failureContext.op}` : null,\n failureContext.path ? `path=${failureContext.path}` : null,\n failureContext.from ? `from=${failureContext.from}` : null,\n ].filter((token): token is string => token !== null);\n const reasonSuffix = failureContext.reason ? ` ${failureContext.reason}` : \"\";\n throw new PmCliError(\n `Failed to apply history patch at entry ${entryNumber} (${contextTokens.join(\", \")}).${reasonSuffix}`,\n EXIT_CODE.GENERIC_FAILURE,\n );\n }\n}\n\nfunction replayToTarget(history: HistoryEntry[], targetIndex: number): CanonicalReplayDocument {\n let document: CanonicalReplayDocument = structuredClone(EMPTY_REPLAY_DOCUMENT);\n\n for (let i = 0; i <= targetIndex; i += 1) {\n const entry = history[i];\n const beforeHash = replayHash(document);\n if (beforeHash !== entry.before_hash) {\n throw new PmCliError(\n `History hash mismatch before replay at entry ${i + 1}.`,\n EXIT_CODE.GENERIC_FAILURE,\n );\n }\n\n document = applyHistoryPatch(document, entry.patch, i + 1, entry.op);\n\n const afterHash = replayHash(document);\n if (afterHash !== entry.after_hash) {\n throw new PmCliError(\n `History hash mismatch after replay at entry ${i + 1}.`,\n EXIT_CODE.GENERIC_FAILURE,\n );\n }\n }\n\n return document;\n}\n\nfunction ensureMaterializedRestoreTarget(\n replayDocument: CanonicalReplayDocument,\n target: ResolvedRestoreTarget,\n): CanonicalReplayDocument {\n if (Object.keys(replayDocument.metadata).length > 0) {\n return replayDocument;\n }\n throw new PmCliError(\n `Restore target ${target.raw} resolves to a deleted state; choose a version or timestamp where the item exists.`,\n EXIT_CODE.USAGE,\n );\n}\n\nfunction replayCurrentDocument(history: HistoryEntry[]): ItemDocument {\n const currentReplay = replayToTarget(history, history.length - 1);\n if (Object.keys(currentReplay.metadata).length === 0) {\n return {\n metadata: {} as ItemMetadata,\n body: currentReplay.body,\n };\n }\n return canonicalDocument({\n metadata: currentReplay.metadata as unknown as ItemMetadata,\n body: currentReplay.body,\n });\n}\n\nasync function resolveRestoreSubject(\n pmRoot: string,\n id: string,\n settings: Awaited<ReturnType<typeof readSettings>>,\n typeToFolder: Record<string, string>,\n): Promise<ResolvedRestoreSubject> {\n const located = await locateItem(pmRoot, id, settings.id_prefix, settings.item_format, typeToFolder);\n if (located) {\n const historyPath = getHistoryPath(pmRoot, located.id);\n const historyPolicy = await enforceHistoryStreamPolicyForItem({\n pmRoot,\n settings,\n itemId: located.id,\n commandLabel: \"restore\",\n });\n return {\n id: located.id,\n historyPath,\n located,\n historyPolicyWarnings: historyPolicy.warnings,\n };\n }\n\n const normalizedId = normalizeItemId(id, settings.id_prefix);\n const rawNormalizedId = normalizeRawItemId(id);\n const candidateIds = normalizedId === rawNormalizedId ? [normalizedId] : [normalizedId, rawNormalizedId];\n for (const candidateId of candidateIds) {\n const historyPath = getHistoryPath(pmRoot, candidateId);\n if (await pathExists(historyPath)) {\n return {\n id: candidateId,\n historyPath,\n located: null,\n historyPolicyWarnings: [],\n };\n }\n }\n\n throw new PmCliError(`Item ${id} not found`, EXIT_CODE.NOT_FOUND);\n}\n\nfunction changedFields(beforeDocument: ItemDocument, afterDocument: ItemDocument): string[] {\n const beforeReplay = toReplayDocument(beforeDocument);\n const afterReplay = toReplayDocument(afterDocument);\n const patch = jsonPatch.compare(beforeReplay, afterReplay) as HistoryPatchOp[];\n const fields = new Set<string>();\n\n for (const op of patch) {\n if (op.path === \"/body\" || op.path.startsWith(\"/body/\")) {\n fields.add(\"body\");\n continue;\n }\n const segment = op.path.replace(/^\\/(?:metadata|front_matter)\\/?/, \"\").split(\"/\")[0];\n fields.add(segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n }\n\n return Array.from(fields).sort((a, b) => a.localeCompare(b));\n}\n\nexport async function runRestore(\n id: string,\n target: string,\n options: RestoreCommandOptions,\n global: GlobalOptions,\n): Promise<RestoreResult> {\n const pmRoot = resolvePmRoot(process.cwd(), global.path);\n if (!(await pathExists(getSettingsPath(pmRoot)))) {\n throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);\n }\n\n const settings = await readSettings(pmRoot);\n const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());\n const subject = await resolveRestoreSubject(pmRoot, id, settings, typeRegistry.type_to_folder);\n const resolvedId = subject.id;\n const historyRawBeforeLock = await readFileIfExists(subject.historyPath);\n const history = await readHistoryEntries(subject.historyPath, resolvedId);\n if (history.length === 0) {\n throw new PmCliError(`No history exists for ${resolvedId}; restore is unavailable.`, EXIT_CODE.NOT_FOUND);\n }\n\n const resolvedTarget = ensureReplayTarget(target, history);\n const replayDocument = ensureMaterializedRestoreTarget(replayToTarget(history, resolvedTarget.historyIndex), resolvedTarget);\n const restoredDocument = canonicalDocument(\n {\n metadata: replayDocument.metadata as unknown as ItemMetadata,\n body: replayDocument.body,\n },\n { schema: settings.schema },\n );\n\n if (restoredDocument.metadata.id !== resolvedId) {\n throw new PmCliError(\n `Restore target resolved to item ${restoredDocument.metadata.id}, expected ${resolvedId}.`,\n EXIT_CODE.GENERIC_FAILURE,\n );\n }\n const loadedItemBeforeLock = subject.located ? await readLocatedItem(subject.located, { schema: settings.schema }) : null;\n const currentItemRawBeforeLock = loadedItemBeforeLock?.raw ?? null;\n\n const author = resolveAuthor(options.author, settings.author_default);\n const releaseLock = await acquireLock(\n pmRoot,\n resolvedId,\n settings.locks.ttl_seconds,\n author,\n Boolean(options.force),\n settings.governance.force_required_for_stale_lock,\n );\n\n try {\n const historyRawUnderLock = await readFileIfExists(subject.historyPath);\n if (historyRawUnderLock !== historyRawBeforeLock) {\n throw new PmCliError(\n `History for ${resolvedId} changed while waiting for lock; retry restore.`,\n EXIT_CODE.CONFLICT,\n );\n }\n const locatedUnderLock = await locateItem(\n pmRoot,\n resolvedId,\n settings.id_prefix,\n settings.item_format,\n typeRegistry.type_to_folder,\n );\n const loadedItemUnderLock = locatedUnderLock ? await readLocatedItem(locatedUnderLock, { schema: settings.schema }) : null;\n if ((loadedItemUnderLock?.raw ?? null) !== currentItemRawBeforeLock) {\n throw new PmCliError(`Item ${resolvedId} changed while waiting for lock; retry restore.`, EXIT_CODE.CONFLICT);\n }\n\n const existingItemPath = locatedUnderLock?.itemPath ?? null;\n const itemFormat = \"toon\";\n let resolvedCurrentDocument: ItemDocument;\n let resolvedOriginalRaw: string | null = null;\n if (loadedItemUnderLock) {\n resolvedCurrentDocument = loadedItemUnderLock.document;\n resolvedOriginalRaw = loadedItemUnderLock.raw;\n } else {\n resolvedCurrentDocument = replayCurrentDocument(history);\n }\n const assigned = resolvedCurrentDocument.metadata.assignee?.trim();\n const ownershipWarnings: string[] = [];\n const hasOwnershipConflict = assigned && assigned !== author && !options.force;\n if (hasOwnershipConflict) {\n if (settings.governance.ownership_enforcement === \"strict\") {\n throw new PmCliError(\n `Item ${resolvedId} is assigned to ${assigned}. Use --force to override.`,\n EXIT_CODE.CONFLICT,\n );\n }\n if (settings.governance.ownership_enforcement === \"warn\") {\n ownershipWarnings.push(`ownership_warning:assignee_conflict:${resolvedId}:${assigned}`);\n }\n }\n\n const serializedRestore = serializeItemDocument(restoredDocument, { format: itemFormat, schema: settings.schema });\n const restoredItemPath = getItemPath(\n pmRoot,\n restoredDocument.metadata.type,\n resolvedId,\n itemFormat,\n typeRegistry.type_to_folder,\n );\n await writeFileAtomic(restoredItemPath, serializedRestore);\n if (existingItemPath && restoredItemPath !== existingItemPath) {\n await fs.rm(existingItemPath);\n }\n\n const historyEntry = createHistoryEntry({\n nowIso: nowIso(),\n author,\n op: \"restore\",\n before: resolvedCurrentDocument,\n after: restoredDocument,\n message: options.message,\n });\n\n try {\n await appendHistoryEntry(subject.historyPath, historyEntry);\n } catch (error: unknown) {\n if (existingItemPath && resolvedOriginalRaw !== null && restoredItemPath !== existingItemPath) {\n await writeFileAtomic(existingItemPath, resolvedOriginalRaw);\n await fs.rm(restoredItemPath, { force: true });\n } else if (existingItemPath && resolvedOriginalRaw !== null) {\n await writeFileAtomic(existingItemPath, resolvedOriginalRaw);\n } else {\n await fs.rm(restoredItemPath, { force: true });\n }\n throw error;\n }\n const hookWarnings = [\n ...(await runActiveOnWriteHooks({\n path: restoredItemPath,\n scope: \"project\",\n op: \"restore\",\n })),\n ...(await runActiveOnWriteHooks({\n path: subject.historyPath,\n scope: \"project\",\n op: \"restore:history\",\n })),\n ];\n\n const targetEntry = history[resolvedTarget.historyIndex];\n return {\n item: restoredDocument.metadata,\n restored_from: {\n kind: resolvedTarget.kind,\n target: resolvedTarget.raw,\n history_index: resolvedTarget.historyIndex + 1,\n entry_ts: targetEntry.ts,\n entry_op: targetEntry.op,\n },\n changed_fields: changedFields(resolvedCurrentDocument, restoredDocument),\n warnings: [...subject.historyPolicyWarnings, ...ownershipWarnings, ...hookWarnings],\n };\n } finally {\n await releaseLock();\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["global"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
verifyHistoryChain
|
|
3
|
+
} from "./chunk-JFWUHHS2.js";
|
|
4
|
+
import {
|
|
5
|
+
hashDocument
|
|
6
|
+
} from "./chunk-BUPWJ3IJ.js";
|
|
7
|
+
import {
|
|
8
|
+
getHistoryPath,
|
|
9
|
+
writeFileAtomic
|
|
10
|
+
} from "./chunk-LSXGH6NE.js";
|
|
11
|
+
|
|
12
|
+
// dist/core/history/drift-scan.js
|
|
13
|
+
import fs from "fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
!(function() {
|
|
16
|
+
try {
|
|
17
|
+
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
|
|
18
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "e8559b46-0199-506f-bf98-8f92a4474215");
|
|
19
|
+
} catch (e2) {
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
var DRIFT_CACHE_VERSION = 1;
|
|
23
|
+
var DRIFT_CACHE_FILENAME = "history-drift-cache.json";
|
|
24
|
+
function getDriftCachePath(pmRoot) {
|
|
25
|
+
return path.join(pmRoot, "runtime", DRIFT_CACHE_FILENAME);
|
|
26
|
+
}
|
|
27
|
+
async function loadDriftCache(pmRoot) {
|
|
28
|
+
try {
|
|
29
|
+
const raw = await fs.readFile(getDriftCachePath(pmRoot), "utf8");
|
|
30
|
+
const parsed = JSON.parse(raw);
|
|
31
|
+
if (parsed.version !== DRIFT_CACHE_VERSION || typeof parsed.entries !== "object" || parsed.entries === null) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function isErrno(error, code) {
|
|
40
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
41
|
+
}
|
|
42
|
+
async function verifyHistoryStream(historyPath) {
|
|
43
|
+
const raw = await fs.readFile(historyPath, "utf8");
|
|
44
|
+
if (raw.trim().length === 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const entries = [];
|
|
48
|
+
let latestAfterHash = null;
|
|
49
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
if (trimmed.length === 0) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const parsed = JSON.parse(trimmed);
|
|
55
|
+
if (typeof parsed.after_hash !== "string" || parsed.after_hash.trim().length === 0) {
|
|
56
|
+
throw new Error("missing after_hash");
|
|
57
|
+
}
|
|
58
|
+
entries.push(parsed);
|
|
59
|
+
latestAfterHash = parsed.after_hash;
|
|
60
|
+
}
|
|
61
|
+
if (!latestAfterHash) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return { latestAfterHash, chainOk: verifyHistoryChain(entries).ok };
|
|
65
|
+
}
|
|
66
|
+
async function scanHistoryDrift(pmRoot, items) {
|
|
67
|
+
const missingStreams = [];
|
|
68
|
+
const unreadableStreams = [];
|
|
69
|
+
const hashMismatches = [];
|
|
70
|
+
const chainMismatches = [];
|
|
71
|
+
const cache = await loadDriftCache(pmRoot);
|
|
72
|
+
const previousEntries = cache?.entries ?? {};
|
|
73
|
+
const nextEntries = {};
|
|
74
|
+
let cacheDirty = false;
|
|
75
|
+
for (const item of items) {
|
|
76
|
+
const historyPath = getHistoryPath(pmRoot, item.id);
|
|
77
|
+
let stat;
|
|
78
|
+
try {
|
|
79
|
+
stat = await fs.stat(historyPath);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (isErrno(error, "ENOENT")) {
|
|
82
|
+
missingStreams.push(item.id);
|
|
83
|
+
} else {
|
|
84
|
+
unreadableStreams.push(item.id);
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const cached = previousEntries[item.id];
|
|
89
|
+
let verification;
|
|
90
|
+
if (cached && cached.mtime_ms === stat.mtimeMs && cached.ctime_ms === stat.ctimeMs && cached.size === stat.size) {
|
|
91
|
+
verification = { latestAfterHash: cached.latest_after_hash, chainOk: cached.chain_ok };
|
|
92
|
+
} else {
|
|
93
|
+
cacheDirty = true;
|
|
94
|
+
let result;
|
|
95
|
+
try {
|
|
96
|
+
result = await verifyHistoryStream(historyPath);
|
|
97
|
+
} catch {
|
|
98
|
+
unreadableStreams.push(item.id);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (!result) {
|
|
102
|
+
missingStreams.push(item.id);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
verification = result;
|
|
106
|
+
}
|
|
107
|
+
if (!verification.chainOk) {
|
|
108
|
+
chainMismatches.push(item.id);
|
|
109
|
+
}
|
|
110
|
+
nextEntries[item.id] = {
|
|
111
|
+
mtime_ms: stat.mtimeMs,
|
|
112
|
+
ctime_ms: stat.ctimeMs,
|
|
113
|
+
size: stat.size,
|
|
114
|
+
latest_after_hash: verification.latestAfterHash,
|
|
115
|
+
chain_ok: verification.chainOk
|
|
116
|
+
};
|
|
117
|
+
const { body, ...frontMatter } = item;
|
|
118
|
+
const currentHash = hashDocument({
|
|
119
|
+
metadata: frontMatter,
|
|
120
|
+
body
|
|
121
|
+
});
|
|
122
|
+
if (currentHash !== verification.latestAfterHash) {
|
|
123
|
+
hashMismatches.push(item.id);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (cacheDirty || Object.keys(previousEntries).length !== Object.keys(nextEntries).length) {
|
|
127
|
+
const cachePath = getDriftCachePath(pmRoot);
|
|
128
|
+
try {
|
|
129
|
+
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
130
|
+
await writeFileAtomic(cachePath, JSON.stringify({ version: DRIFT_CACHE_VERSION, entries: nextEntries }));
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const driftedItems = [.../* @__PURE__ */ new Set([...missingStreams, ...unreadableStreams, ...hashMismatches, ...chainMismatches])].sort((a, b) => a.localeCompare(b));
|
|
135
|
+
return { missingStreams, unreadableStreams, hashMismatches, chainMismatches, driftedItems };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
scanHistoryDrift
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=chunk-VGICHR7D.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../core/history/drift-scan.ts"],
|
|
4
|
+
"sourcesContent": ["import fs from \"fs/promises\";\nimport path from \"node:path\";\nimport { getHistoryPath } from \"../store/paths.js\";\nimport { writeFileAtomic } from \"../fs/fs-utils.js\";\nimport { hashDocument } from \"./history.js\";\nimport { verifyHistoryChain } from \"./replay.js\";\nimport type { HistoryEntry, ItemMetadata } from \"../../types/index.js\";\n\nexport interface DriftScanResult {\n missingStreams: string[];\n unreadableStreams: string[];\n hashMismatches: string[];\n chainMismatches: string[];\n driftedItems: string[];\n}\n\nconst DRIFT_CACHE_VERSION = 1;\nconst DRIFT_CACHE_FILENAME = \"history-drift-cache.json\";\n\ninterface DriftCacheEntry {\n mtime_ms: number;\n ctime_ms: number;\n size: number;\n latest_after_hash: string;\n chain_ok: boolean;\n}\n\ninterface DriftCacheEnvelope {\n version: number;\n entries: Record<string, DriftCacheEntry>;\n}\n\nfunction getDriftCachePath(pmRoot: string): string {\n return path.join(pmRoot, \"runtime\", DRIFT_CACHE_FILENAME);\n}\n\nasync function loadDriftCache(pmRoot: string): Promise<DriftCacheEnvelope | null> {\n try {\n const raw = await fs.readFile(getDriftCachePath(pmRoot), \"utf8\");\n const parsed = JSON.parse(raw) as DriftCacheEnvelope;\n if (parsed.version !== DRIFT_CACHE_VERSION || typeof parsed.entries !== \"object\" || parsed.entries === null) {\n return null;\n }\n return parsed;\n } catch {\n return null;\n }\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code?: string }).code === code;\n}\n\ninterface StreamVerification {\n latestAfterHash: string;\n chainOk: boolean;\n}\n\n/**\n * Read and fully verify one history stream's hash chain. Returns null for an\n * empty/missing stream (caller records it as a missing stream).\n */\nasync function verifyHistoryStream(historyPath: string): Promise<StreamVerification | null> {\n const raw = await fs.readFile(historyPath, \"utf8\");\n if (raw.trim().length === 0) {\n return null;\n }\n const entries: HistoryEntry[] = [];\n let latestAfterHash: string | null = null;\n for (const line of raw.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (trimmed.length === 0) {\n continue;\n }\n const parsed = JSON.parse(trimmed) as HistoryEntry;\n if (typeof parsed.after_hash !== \"string\" || parsed.after_hash.trim().length === 0) {\n throw new Error(\"missing after_hash\");\n }\n entries.push(parsed);\n latestAfterHash = parsed.after_hash;\n }\n /* c8 ignore start -- defensive guard for future history schema changes. */\n if (!latestAfterHash) {\n return null;\n }\n /* c8 ignore stop */\n return { latestAfterHash, chainOk: verifyHistoryChain(entries).ok };\n}\n\n/**\n * Scan every item's history stream for drift (missing/unreadable streams, broken\n * hash chains, and item/history hash mismatches).\n *\n * Full chain re-verification of a 17MB+ history tree is the dominant cost of\n * `pm health`. We cache the per-stream verification keyed by the history file's\n * mtime/size: an unchanged file has an identical chain and latest stored hash, so\n * we skip re-reading and re-hashing it. Item-side changes are still caught every\n * run because the current document hash is recomputed and compared to the cached\n * `latest_after_hash`.\n */\nexport async function scanHistoryDrift(\n pmRoot: string,\n items: Array<ItemMetadata & { body: string }>,\n): Promise<DriftScanResult> {\n const missingStreams: string[] = [];\n const unreadableStreams: string[] = [];\n const hashMismatches: string[] = [];\n const chainMismatches: string[] = [];\n\n const cache = await loadDriftCache(pmRoot);\n const previousEntries: Record<string, DriftCacheEntry> = cache?.entries ?? {};\n const nextEntries: Record<string, DriftCacheEntry> = {};\n let cacheDirty = false;\n\n for (const item of items) {\n const historyPath = getHistoryPath(pmRoot, item.id);\n\n let stat: Awaited<ReturnType<typeof fs.stat>>;\n try {\n stat = await fs.stat(historyPath);\n } catch (error: unknown) {\n if (isErrno(error, \"ENOENT\")) {\n missingStreams.push(item.id);\n } else {\n unreadableStreams.push(item.id);\n }\n continue;\n }\n\n const cached = previousEntries[item.id];\n let verification: StreamVerification;\n if (cached && cached.mtime_ms === stat.mtimeMs && cached.ctime_ms === stat.ctimeMs && cached.size === stat.size) {\n verification = { latestAfterHash: cached.latest_after_hash, chainOk: cached.chain_ok };\n } else {\n cacheDirty = true;\n let result: StreamVerification | null;\n try {\n result = await verifyHistoryStream(historyPath);\n } catch {\n unreadableStreams.push(item.id);\n continue;\n }\n if (!result) {\n missingStreams.push(item.id);\n continue;\n }\n verification = result;\n }\n\n if (!verification.chainOk) {\n chainMismatches.push(item.id);\n }\n nextEntries[item.id] = {\n mtime_ms: stat.mtimeMs,\n ctime_ms: stat.ctimeMs,\n size: stat.size,\n latest_after_hash: verification.latestAfterHash,\n chain_ok: verification.chainOk,\n };\n\n const { body, ...frontMatter } = item;\n const currentHash = hashDocument({\n metadata: frontMatter as ItemMetadata,\n body,\n });\n if (currentHash !== verification.latestAfterHash) {\n hashMismatches.push(item.id);\n }\n }\n\n if (cacheDirty || Object.keys(previousEntries).length !== Object.keys(nextEntries).length) {\n const cachePath = getDriftCachePath(pmRoot);\n try {\n await fs.mkdir(path.dirname(cachePath), { recursive: true });\n await writeFileAtomic(cachePath, JSON.stringify({ version: DRIFT_CACHE_VERSION, entries: nextEntries }));\n } catch {\n // Best-effort cache write: a failed persist must never fail a health scan.\n }\n }\n\n const driftedItems = [...new Set([...missingStreams, ...unreadableStreams, ...hashMismatches, ...chainMismatches])].sort((a, b) =>\n a.localeCompare(b),\n );\n return { missingStreams, unreadableStreams, hashMismatches, chainMismatches, driftedItems };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|