@unbrained/pm-cli 2026.8.28 → 2026.8.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +30 -0
- package/dist/cli-bundle/bundle-manifest.json +125 -125
- package/dist/cli-bundle/chunks/{chunk-E73FDIWT.js → chunk-2MKLQL2E.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-NTXZHRKA.js → chunk-2UETTHY6.js} +46 -46
- package/dist/cli-bundle/chunks/{chunk-FEVBFFCQ.js → chunk-3TEF3HM5.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-BY2FQ2NI.js → chunk-7SKMLSMX.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-7X6OZEIV.js +34 -0
- package/dist/cli-bundle/chunks/{chunk-M7OXRQE3.js → chunk-CMIEDWAY.js} +50 -50
- package/dist/cli-bundle/chunks/{chunk-XWEQGHHG.js → chunk-GNWPTEBW.js} +64 -61
- package/dist/cli-bundle/chunks/{chunk-X2RROGZE.js → chunk-MTTKAUBX.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-N6OR6FAR.js +13 -0
- package/dist/cli-bundle/chunks/{chunk-NBCBFVZI.js → chunk-TGNAA66W.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-QE6WQXFO.js → chunk-WS2GJQVG.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-J35ZPQQ5.js → register-list-query-OMKJ5ONN.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-J6XJJOGU.js → register-mutation-QBSNCOY3.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-AE3JEMFT.js → register-operations-UC77FQZC.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-OQERLLWE.js → register-setup-GLQ5SYV3.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-HC7ODMH3.js → chunk-3AVAWVZA.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-3UMBF26I.js +158 -0
- package/dist/cli-bundle/focused-chunks/{chunk-YVVZ3LQ6.js → chunk-73EDXGYE.js} +16 -15
- package/dist/cli-bundle/focused-chunks/chunk-AXZ5AJAF.js +50 -0
- package/dist/cli-bundle/focused-chunks/{chunk-AHAM2HAU.js → chunk-G6PATS5E.js} +3 -3
- package/dist/cli-bundle/focused-chunks/chunk-RKOKG43G.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-TDK3OCX3.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-FC2AXLB5.js → chunk-XXOWRJZ5.js} +2 -2
- package/dist/cli-bundle/main.js +2 -2
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +31 -32
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +34 -32
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +8 -8
- package/dist/core/diagnostics/remediation.js +16 -6
- package/dist/core/history/drift-scan.d.ts +2 -0
- package/dist/core/history/drift-scan.js +72 -36
- package/dist/core/history/history.d.ts +3 -3
- package/dist/core/history/history.js +5 -5
- package/dist/core/history/replay.d.ts +1 -1
- package/dist/core/history/replay.js +50 -19
- package/dist/core/item/actionability.d.ts +7 -1
- package/dist/core/item/actionability.js +44 -11
- package/dist/core/item/dependency-reference.d.ts +10 -0
- package/dist/core/item/dependency-reference.js +21 -0
- package/dist/core/item/parse.d.ts +4 -0
- package/dist/core/item/parse.js +38 -16
- package/dist/core/shared/author.js +69 -17
- package/dist/sdk/dependency-provenance.d.ts +51 -2
- package/dist/sdk/dependency-provenance.js +91 -7
- package/dist/sdk/extension/install-sources.d.ts +1 -7
- package/dist/sdk/extension/install-sources.js +94 -33
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +50 -14
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +62 -26
- package/dist/sdk/governance/health.js +86 -22
- package/dist/sdk/governance/validate-history-drift.js +5 -2
- package/dist/sdk/graph/assembly.js +51 -22
- package/dist/sdk/history-repair.d.ts +1 -1
- package/dist/sdk/history-repair.js +20 -10
- package/dist/sdk/lifecycle/create.js +43 -19
- package/dist/sdk/linked-artifacts.js +21 -31
- package/dist/sdk/merge/reconcile.js +3 -16
- package/dist/sdk/query/next.d.ts +6 -0
- package/dist/sdk/query/next.js +7 -2
- package/dist/sdk/test/entry.d.ts +1 -1
- package/dist/sdk/test/entry.js +5 -9
- package/dist/sdk/test/execution.d.ts +3 -0
- package/dist/sdk/test/execution.js +54 -13
- package/docs/CONTEXT_RECOVERY_PRIMITIVES.md +67 -0
- package/docs/MERGE_SAFETY.md +23 -10
- package/docs/README.md +1 -0
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +10 -4
- package/marketplace.json +2 -2
- package/package.json +8 -8
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/sdk/public-surface.json +100 -11
- package/dist/cli-bundle/chunks/chunk-TIQ6AMH2.js +0 -13
- package/dist/cli-bundle/chunks/chunk-XRVVYRRO.js +0 -35
- package/dist/cli-bundle/focused-chunks/chunk-4ZDRZYYJ.js +0 -50
- package/dist/cli-bundle/focused-chunks/chunk-XDPYBQCF.js +0 -156
- package/dist/cli-bundle/focused-chunks/chunk-Y5A7SJJ7.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-YJLDHJOD.js +0 -2
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
/** Registry of every non-extension `pm health` / `pm validate` warning code and its remediation. Extension findings keep their richer, contextual `details.triage.remediation` produced by the extension health triage and are intentionally excluded here (see pm-0hnu: "all non-extension checks"). */
|
|
21
21
|
|
|
22
|
-
!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]="
|
|
22
|
+
!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]="bb094b44-42b2-5a1c-9fc2-7a0eeaf9fbfd")}catch(e){}}();
|
|
23
23
|
export const REMEDIATION_REGISTRY = Object.freeze([
|
|
24
24
|
// --- pm health: directories ---
|
|
25
25
|
{
|
|
@@ -195,8 +195,8 @@ export const REMEDIATION_REGISTRY = Object.freeze([
|
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
code: "merge_receipts_pending",
|
|
198
|
-
command: "pm merge reconcile
|
|
199
|
-
summary: "A lossless merge receipt is still pending;
|
|
198
|
+
command: "pm merge reconcile",
|
|
199
|
+
summary: "A lossless merge receipt is still pending; run pm merge reconcile to settle it (optionally preview first with --dry-run) so a green health gate certifies receipt settlement, not only intact data.",
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
code: "merge_receipt_evidence_invalid",
|
|
@@ -231,10 +231,15 @@ export const REMEDIATION_REGISTRY = Object.freeze([
|
|
|
231
231
|
command: "pm history-repair <id>",
|
|
232
232
|
summary: "The item's history chain is broken; re-anchor the chain (pm history-repair --all repairs every drifted stream in one pass).",
|
|
233
233
|
},
|
|
234
|
+
{
|
|
235
|
+
code: "history_drift_version_skew",
|
|
236
|
+
command: "npm install -g @unbrained/pm-cli@latest",
|
|
237
|
+
summary: "A history stream declares an item hash version this runtime does not support; upgrade pm to verify the chain instead of re-anchoring it.",
|
|
238
|
+
},
|
|
234
239
|
{
|
|
235
240
|
code: "history_drift_merge_receipt",
|
|
236
|
-
command: "pm merge reconcile
|
|
237
|
-
summary: "Canonical path, changed-field, and merged-value hash evidence attributes this drift to a pending receipt;
|
|
241
|
+
command: "pm merge reconcile",
|
|
242
|
+
summary: "Canonical path, changed-field, and merged-value hash evidence attributes this drift to a pending receipt; run reconciliation (optionally preview first with --dry-run) instead of repairing history directly.",
|
|
238
243
|
},
|
|
239
244
|
// --- pm health: storage (history compaction policy) ---
|
|
240
245
|
{
|
|
@@ -426,6 +431,11 @@ export const REMEDIATION_REGISTRY = Object.freeze([
|
|
|
426
431
|
command: "pm history-repair <id>",
|
|
427
432
|
summary: "Re-anchor the history chains of the items with a broken history chain.",
|
|
428
433
|
},
|
|
434
|
+
{
|
|
435
|
+
code: "validate_history_drift_version_skews",
|
|
436
|
+
command: "npm install -g @unbrained/pm-cli@latest",
|
|
437
|
+
summary: "One or more history streams declare an item hash version this runtime does not support; upgrade pm to verify the chains instead of re-anchoring them.",
|
|
438
|
+
},
|
|
429
439
|
// --- pm validate: command_references ---
|
|
430
440
|
{
|
|
431
441
|
code: "validate_command_references_stale_pm_ids",
|
|
@@ -489,4 +499,4 @@ export function buildRemediationCommands(warnings) {
|
|
|
489
499
|
return Object.values(buildRemediationMap(warnings));
|
|
490
500
|
}
|
|
491
501
|
//# sourceMappingURL=remediation.js.map
|
|
492
|
-
//# debugId=
|
|
502
|
+
//# debugId=bb094b44-42b2-5a1c-9fc2-7a0eeaf9fbfd
|
|
@@ -9,6 +9,8 @@ export interface DriftScanResult {
|
|
|
9
9
|
hashMismatches: string[];
|
|
10
10
|
/** Value that configures or reports chain mismatches for this contract. */
|
|
11
11
|
chainMismatches: string[];
|
|
12
|
+
/** Streams written with a hash capability newer than this runtime understands. */
|
|
13
|
+
versionSkews: string[];
|
|
12
14
|
/** Value that configures or reports drifted items for this contract. */
|
|
13
15
|
driftedItems: string[];
|
|
14
16
|
/** Governed singleton files whose JSON differs from workspace-history replay. */
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="65c0fed6-315a-5930-a23f-83996d3f5081")}catch(e){}}();
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { getHistoryPath } from "../store/paths.js";
|
|
7
7
|
import { isFileMissingError, writeFileAtomic } from "../fs/fs-utils.js";
|
|
8
|
-
import { CURRENT_HISTORY_ITEM_HASH_VERSION, hashDocumentForVersion, } from "./history.js";
|
|
8
|
+
import { CURRENT_HISTORY_ITEM_HASH_VERSION, SUPPORTED_HISTORY_ITEM_HASH_VERSIONS, hashDocumentForVersion, } from "./history.js";
|
|
9
9
|
import { verifyHistoryChainWithVersion } from "./replay.js";
|
|
10
10
|
import { getWorkspaceHistoryPath, inspectWorkspaceHistoryState, WORKSPACE_HISTORY_ID, } from "./workspace-history.js";
|
|
11
|
-
const DRIFT_CACHE_VERSION =
|
|
11
|
+
const DRIFT_CACHE_VERSION = 7;
|
|
12
12
|
const DRIFT_CACHE_FILENAME = "history-drift-cache.json";
|
|
13
13
|
function getDriftCachePath(pmRoot) {
|
|
14
14
|
return path.join(pmRoot, "runtime", DRIFT_CACHE_FILENAME);
|
|
@@ -18,8 +18,17 @@ async function loadDriftCache(pmRoot) {
|
|
|
18
18
|
const raw = await fs.readFile(getDriftCachePath(pmRoot), "utf8");
|
|
19
19
|
const parsed = JSON.parse(raw);
|
|
20
20
|
if (parsed.version !== DRIFT_CACHE_VERSION ||
|
|
21
|
+
parsed.history_item_hash_version !== CURRENT_HISTORY_ITEM_HASH_VERSION ||
|
|
21
22
|
typeof parsed.entries !== "object" ||
|
|
22
|
-
parsed.entries === null
|
|
23
|
+
parsed.entries === null ||
|
|
24
|
+
Object.values(parsed.entries).some((entry) => typeof entry.chain_ok !== "boolean" ||
|
|
25
|
+
typeof entry.latest_hash_comparable !== "boolean" ||
|
|
26
|
+
(!SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(entry.item_hash_version) &&
|
|
27
|
+
entry.version_skew !== true) ||
|
|
28
|
+
(entry.chain_ok &&
|
|
29
|
+
entry.version_skew !== true &&
|
|
30
|
+
SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(entry.item_hash_version) &&
|
|
31
|
+
!entry.latest_hash_comparable))) {
|
|
23
32
|
return null;
|
|
24
33
|
}
|
|
25
34
|
return parsed;
|
|
@@ -53,6 +62,9 @@ async function scanWorkspaceHistory(pmRoot, previousEntries, nextEntries, verify
|
|
|
53
62
|
accumulator.missingStreams.splice(missingCount);
|
|
54
63
|
return resolved.cacheDirty;
|
|
55
64
|
}
|
|
65
|
+
if (resolved.verification.versionSkew) {
|
|
66
|
+
accumulator.versionSkews.push(WORKSPACE_HISTORY_ID);
|
|
67
|
+
}
|
|
56
68
|
if (!resolved.verification.chainOk) {
|
|
57
69
|
accumulator.chainMismatches.push(WORKSPACE_HISTORY_ID);
|
|
58
70
|
}
|
|
@@ -63,6 +75,8 @@ async function scanWorkspaceHistory(pmRoot, previousEntries, nextEntries, verify
|
|
|
63
75
|
content_hash: resolved.verification.contentHash,
|
|
64
76
|
latest_after_hash: resolved.verification.latestAfterHash,
|
|
65
77
|
chain_ok: resolved.verification.chainOk,
|
|
78
|
+
version_skew: resolved.verification.versionSkew,
|
|
79
|
+
latest_hash_comparable: resolved.verification.latestHashComparable,
|
|
66
80
|
item_hash_version: resolved.verification.itemHashVersion,
|
|
67
81
|
};
|
|
68
82
|
return resolved.cacheDirty;
|
|
@@ -70,10 +84,6 @@ async function scanWorkspaceHistory(pmRoot, previousEntries, nextEntries, verify
|
|
|
70
84
|
function hashContent(raw) {
|
|
71
85
|
return createHash("sha256").update(raw).digest("hex");
|
|
72
86
|
}
|
|
73
|
-
async function readHistoryContentHash(historyPath) {
|
|
74
|
-
const raw = await fs.readFile(historyPath, "utf8");
|
|
75
|
-
return hashContent(raw);
|
|
76
|
-
}
|
|
77
87
|
/** Read and fully verify one history stream's hash chain. Returns null for an empty/missing stream (caller records it as a missing stream). */
|
|
78
88
|
async function verifyHistoryStream(historyPath) {
|
|
79
89
|
const raw = await fs.readFile(historyPath, "utf8");
|
|
@@ -102,11 +112,25 @@ async function verifyHistoryStream(historyPath) {
|
|
|
102
112
|
}
|
|
103
113
|
/* c8 ignore stop */
|
|
104
114
|
const verification = verifyHistoryChainWithVersion(entries);
|
|
115
|
+
const versionSkew = entries.some((entry) => entry.item_hash_version !== undefined &&
|
|
116
|
+
!SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(entry.item_hash_version));
|
|
117
|
+
const latestEntry = entries[entries.length - 1];
|
|
118
|
+
const latestExplicitVersion = latestEntry.item_hash_version;
|
|
119
|
+
const latestExplicitVersionSupported = latestExplicitVersion !== undefined &&
|
|
120
|
+
SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(latestExplicitVersion);
|
|
121
|
+
const latestHashVersion = latestExplicitVersionSupported
|
|
122
|
+
? latestExplicitVersion
|
|
123
|
+
: verification.item_hash_version;
|
|
105
124
|
return {
|
|
106
125
|
latestAfterHash,
|
|
107
126
|
chainOk: verification.ok,
|
|
127
|
+
versionSkew,
|
|
128
|
+
latestHashComparable: latestHashVersion !== undefined &&
|
|
129
|
+
(!versionSkew ||
|
|
130
|
+
latestExplicitVersionSupported ||
|
|
131
|
+
latestExplicitVersion === undefined),
|
|
108
132
|
contentHash,
|
|
109
|
-
itemHashVersion:
|
|
133
|
+
itemHashVersion: latestHashVersion ?? CURRENT_HISTORY_ITEM_HASH_VERSION,
|
|
110
134
|
};
|
|
111
135
|
}
|
|
112
136
|
function cacheMetadataMatches(cached, stat) {
|
|
@@ -143,34 +167,31 @@ async function resolveStreamVerification(params) {
|
|
|
143
167
|
cacheDirty: true,
|
|
144
168
|
};
|
|
145
169
|
}
|
|
146
|
-
let currentContentHash;
|
|
147
170
|
if (params.verifyCacheHitByContent) {
|
|
148
|
-
|
|
149
|
-
currentContentHash = await readHistoryContentHash(params.historyPath);
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
params.accumulator.unreadableStreams.push(params.itemId);
|
|
153
|
-
return { verification: null, cacheDirty: false };
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
currentContentHash = cachedContentHash;
|
|
158
|
-
}
|
|
159
|
-
if (!params.verifyCacheHitByContent ||
|
|
160
|
-
currentContentHash === cachedContentHash) {
|
|
171
|
+
const verification = await loadFreshStreamVerification(params.itemId, params.historyPath, params.accumulator);
|
|
161
172
|
return {
|
|
162
|
-
verification
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
verification,
|
|
174
|
+
cacheDirty: verification === null ||
|
|
175
|
+
verification.contentHash !== cachedContentHash ||
|
|
176
|
+
verification.latestAfterHash !== params.cached.latest_after_hash ||
|
|
177
|
+
verification.chainOk !== params.cached.chain_ok ||
|
|
178
|
+
verification.versionSkew !== (params.cached.version_skew === true) ||
|
|
179
|
+
verification.latestHashComparable !==
|
|
180
|
+
params.cached.latest_hash_comparable ||
|
|
181
|
+
verification.itemHashVersion !== params.cached.item_hash_version,
|
|
169
182
|
};
|
|
170
183
|
}
|
|
171
184
|
return {
|
|
172
|
-
verification:
|
|
173
|
-
|
|
185
|
+
verification: {
|
|
186
|
+
latestAfterHash: params.cached.latest_after_hash,
|
|
187
|
+
chainOk: params.cached.chain_ok,
|
|
188
|
+
versionSkew: params.cached.version_skew === true,
|
|
189
|
+
latestHashComparable: params.cached.latest_hash_comparable &&
|
|
190
|
+
SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(params.cached.item_hash_version),
|
|
191
|
+
contentHash: cachedContentHash,
|
|
192
|
+
itemHashVersion: params.cached.item_hash_version,
|
|
193
|
+
},
|
|
194
|
+
cacheDirty: false,
|
|
174
195
|
};
|
|
175
196
|
}
|
|
176
197
|
/** Scan one item stream and return whether its cache entry needs persistence. */
|
|
@@ -196,8 +217,12 @@ async function scanItemHistory(pmRoot, item, cache, accumulator) {
|
|
|
196
217
|
});
|
|
197
218
|
if (!resolved.verification)
|
|
198
219
|
return resolved.cacheDirty;
|
|
199
|
-
if (
|
|
220
|
+
if (resolved.verification.versionSkew) {
|
|
221
|
+
accumulator.versionSkews.push(item.id);
|
|
222
|
+
}
|
|
223
|
+
if (!resolved.verification.chainOk) {
|
|
200
224
|
accumulator.chainMismatches.push(item.id);
|
|
225
|
+
}
|
|
201
226
|
cache.nextEntries[item.id] = {
|
|
202
227
|
mtime_ms: stat.mtimeMs,
|
|
203
228
|
ctime_ms: stat.ctimeMs,
|
|
@@ -205,9 +230,13 @@ async function scanItemHistory(pmRoot, item, cache, accumulator) {
|
|
|
205
230
|
content_hash: resolved.verification.contentHash,
|
|
206
231
|
latest_after_hash: resolved.verification.latestAfterHash,
|
|
207
232
|
chain_ok: resolved.verification.chainOk,
|
|
233
|
+
version_skew: resolved.verification.versionSkew,
|
|
234
|
+
latest_hash_comparable: resolved.verification.latestHashComparable,
|
|
208
235
|
item_hash_version: resolved.verification.itemHashVersion,
|
|
209
236
|
};
|
|
210
237
|
const { body, ...itemMetadata } = item;
|
|
238
|
+
if (!resolved.verification.latestHashComparable)
|
|
239
|
+
return resolved.cacheDirty;
|
|
211
240
|
const currentHash = hashDocumentForVersion({ metadata: itemMetadata, body }, resolved.verification.itemHashVersion);
|
|
212
241
|
if (currentHash !== resolved.verification.latestAfterHash)
|
|
213
242
|
accumulator.hashMismatches.push(item.id);
|
|
@@ -216,7 +245,8 @@ async function scanItemHistory(pmRoot, item, cache, accumulator) {
|
|
|
216
245
|
/** Compare the verified workspace stream with every governed singleton file. */
|
|
217
246
|
async function scanWorkspaceStateAgreement(pmRoot, accumulator) {
|
|
218
247
|
if (accumulator.unreadableStreams.includes(WORKSPACE_HISTORY_ID) ||
|
|
219
|
-
accumulator.chainMismatches.includes(WORKSPACE_HISTORY_ID)
|
|
248
|
+
accumulator.chainMismatches.includes(WORKSPACE_HISTORY_ID) ||
|
|
249
|
+
accumulator.versionSkews.includes(WORKSPACE_HISTORY_ID))
|
|
220
250
|
return;
|
|
221
251
|
try {
|
|
222
252
|
const agreement = await inspectWorkspaceHistoryState(pmRoot);
|
|
@@ -244,6 +274,7 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
|
|
|
244
274
|
unreadableStreams: [],
|
|
245
275
|
hashMismatches: [],
|
|
246
276
|
chainMismatches: [],
|
|
277
|
+
versionSkews: [],
|
|
247
278
|
workspaceStateMismatches: [],
|
|
248
279
|
workspaceStateMissing: [],
|
|
249
280
|
workspaceStateUnreadable: [],
|
|
@@ -272,7 +303,11 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
|
|
|
272
303
|
const cachePath = getDriftCachePath(pmRoot);
|
|
273
304
|
try {
|
|
274
305
|
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
275
|
-
await writeFileAtomic(cachePath, JSON.stringify({
|
|
306
|
+
await writeFileAtomic(cachePath, JSON.stringify({
|
|
307
|
+
version: DRIFT_CACHE_VERSION,
|
|
308
|
+
history_item_hash_version: CURRENT_HISTORY_ITEM_HASH_VERSION,
|
|
309
|
+
entries: nextEntries,
|
|
310
|
+
}));
|
|
276
311
|
}
|
|
277
312
|
catch {
|
|
278
313
|
// Best-effort cache write: a failed persist must never fail a health scan.
|
|
@@ -284,6 +319,7 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
|
|
|
284
319
|
...accumulator.unreadableStreams,
|
|
285
320
|
...accumulator.hashMismatches,
|
|
286
321
|
...accumulator.chainMismatches,
|
|
322
|
+
...accumulator.versionSkews,
|
|
287
323
|
...(accumulator.workspaceStateMismatches.length > 0 ||
|
|
288
324
|
accumulator.workspaceStateMissing.length > 0 ||
|
|
289
325
|
accumulator.workspaceStateUnreadable.length > 0
|
|
@@ -294,4 +330,4 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
|
|
|
294
330
|
return { ...accumulator, driftedItems };
|
|
295
331
|
}
|
|
296
332
|
//# sourceMappingURL=drift-scan.js.map
|
|
297
|
-
//# debugId=
|
|
333
|
+
//# debugId=65c0fed6-315a-5930-a23f-83996d3f5081
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type AuthorSource } from "../shared/author.js";
|
|
2
2
|
import type { HistoryEntry, ItemDocument } from "../../types/index.js";
|
|
3
|
-
/** Hash epoch
|
|
4
|
-
export declare const CURRENT_HISTORY_ITEM_HASH_VERSION:
|
|
3
|
+
/** Hash epoch for the current canonical item surface written into immutable history. */
|
|
4
|
+
export declare const CURRENT_HISTORY_ITEM_HASH_VERSION: 3;
|
|
5
5
|
/** Hash epochs understood by this runtime. */
|
|
6
|
-
export declare const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS: readonly [1, 2];
|
|
6
|
+
export declare const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS: readonly [1, 2, 3];
|
|
7
7
|
/** Restricts item hash versions accepted by history replay. */
|
|
8
8
|
export type HistoryItemHashVersion = (typeof SUPPORTED_HISTORY_ITEM_HASH_VERSIONS)[number];
|
|
9
9
|
declare function decodeJsonPointer(path: string): string[];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements append-only history and replay behavior for History.
|
|
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]="8581413f-e74e-5db3-86bf-5e39290de741")}catch(e){}}();
|
|
8
8
|
import jsonPatch from "fast-json-patch";
|
|
9
9
|
import { ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
|
|
10
10
|
import { runActiveServiceOverride } from "../extensions/index.js";
|
|
@@ -20,10 +20,10 @@ const EMPTY_LEGACY_HASH_DOCUMENT = {
|
|
|
20
20
|
front_matter: {},
|
|
21
21
|
body: "",
|
|
22
22
|
};
|
|
23
|
-
/** Hash epoch
|
|
24
|
-
export const CURRENT_HISTORY_ITEM_HASH_VERSION =
|
|
23
|
+
/** Hash epoch for the current canonical item surface written into immutable history. */
|
|
24
|
+
export const CURRENT_HISTORY_ITEM_HASH_VERSION = 3;
|
|
25
25
|
/** Hash epochs understood by this runtime. */
|
|
26
|
-
export const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS = [1, 2];
|
|
26
|
+
export const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS = [1, 2, 3];
|
|
27
27
|
function compareOptionalStrings(left, right) {
|
|
28
28
|
return (left ?? "").localeCompare(right ?? "");
|
|
29
29
|
}
|
|
@@ -292,4 +292,4 @@ export const _testOnly = {
|
|
|
292
292
|
isDefinedPointerPath,
|
|
293
293
|
};
|
|
294
294
|
//# sourceMappingURL=history.js.map
|
|
295
|
-
//# debugId=
|
|
295
|
+
//# debugId=8581413f-e74e-5db3-86bf-5e39290de741
|
|
@@ -108,6 +108,6 @@ export interface ReanchorResult {
|
|
|
108
108
|
*/
|
|
109
109
|
export declare function resolveHistoryRepairItemHashVersion(entries: HistoryEntry[]): HistoryItemHashVersion;
|
|
110
110
|
/** 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. */
|
|
111
|
-
export declare function reanchorHistoryEntries(entries: HistoryEntry[], itemHashVersion?: 1 | 2): ReanchorResult;
|
|
111
|
+
export declare function reanchorHistoryEntries(entries: HistoryEntry[], itemHashVersion?: 1 | 2 | 3): ReanchorResult;
|
|
112
112
|
/** Implements history entries to raw for the public runtime surface of this module. */
|
|
113
113
|
export declare function historyEntriesToRaw(entries: HistoryEntry[]): string;
|
|
@@ -4,7 +4,7 @@
|
|
|
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]="c823af51-0cfd-536b-8f85-1f3e72db1c86")}catch(e){}}();
|
|
8
8
|
import jsonPatch from "fast-json-patch";
|
|
9
9
|
import { ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
|
|
10
10
|
import { canonicalDocument } from "../item/item-format.js";
|
|
@@ -132,13 +132,18 @@ function supportedExplicitHistoryItemHashVersions(entries) {
|
|
|
132
132
|
SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(version))),
|
|
133
133
|
];
|
|
134
134
|
}
|
|
135
|
+
/** Return whether an explicit hash epoch cannot be replayed by this build. */
|
|
136
|
+
function isUnsupportedExplicitHistoryItemHashVersion(version) {
|
|
137
|
+
return (version !== undefined &&
|
|
138
|
+
!SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(version));
|
|
139
|
+
}
|
|
135
140
|
/** Select the hash epochs allowed for one entry under an authoritative stream marker. */
|
|
136
141
|
function historyEntryHashCandidates(explicitVersion, authoritativeVersion) {
|
|
137
142
|
if (explicitVersion !== undefined) {
|
|
138
143
|
return [explicitVersion];
|
|
139
144
|
}
|
|
140
145
|
return authoritativeVersion === undefined
|
|
141
|
-
? [
|
|
146
|
+
? [...SUPPORTED_HISTORY_ITEM_HASH_VERSIONS]
|
|
142
147
|
: [authoritativeVersion];
|
|
143
148
|
}
|
|
144
149
|
/** 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. */
|
|
@@ -147,23 +152,35 @@ export function verifyHistoryChainWithVersion(entries) {
|
|
|
147
152
|
let replay = cloneEmptyReplayDocument();
|
|
148
153
|
let detectedVersion;
|
|
149
154
|
let authoritativeExplicitVersion;
|
|
155
|
+
const errors = [];
|
|
150
156
|
for (let index = 0; index < entries.length; index += 1) {
|
|
151
157
|
const entry = entries[index];
|
|
152
158
|
const explicitVersion = entry.item_hash_version;
|
|
153
|
-
if (explicitVersion
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
if (isUnsupportedExplicitHistoryItemHashVersion(explicitVersion)) {
|
|
160
|
+
errors.push(`verify_failed:unsupported_item_hash_version:${String(explicitVersion)}:entry_${index + 1}`);
|
|
161
|
+
const applied = tryApplyReplayPatch(replay, entry.patch);
|
|
162
|
+
if (!applied.ok) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
errors: [
|
|
166
|
+
...errors,
|
|
167
|
+
`verify_failed:patch_apply_failed:entry_${index + 1}`,
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
replay = applied.document;
|
|
172
|
+
detectedVersion = undefined;
|
|
173
|
+
authoritativeExplicitVersion = undefined;
|
|
174
|
+
continue;
|
|
161
175
|
}
|
|
162
176
|
const applied = tryApplyReplayPatch(replay, entry.patch);
|
|
163
177
|
if (!applied.ok) {
|
|
164
178
|
return {
|
|
165
179
|
ok: false,
|
|
166
|
-
errors: [
|
|
180
|
+
errors: [
|
|
181
|
+
...errors,
|
|
182
|
+
`verify_failed:patch_apply_failed:entry_${index + 1}`,
|
|
183
|
+
],
|
|
167
184
|
};
|
|
168
185
|
}
|
|
169
186
|
// Prefer legacy epoch 1 when an unversioned entry is valid under both
|
|
@@ -174,21 +191,36 @@ export function verifyHistoryChainWithVersion(entries) {
|
|
|
174
191
|
if (beforeMatches.length === 0) {
|
|
175
192
|
return {
|
|
176
193
|
ok: false,
|
|
177
|
-
errors: [
|
|
194
|
+
errors: [
|
|
195
|
+
...errors,
|
|
196
|
+
`verify_failed:before_hash_mismatch:entry_${index + 1}`,
|
|
197
|
+
],
|
|
178
198
|
};
|
|
179
199
|
}
|
|
180
200
|
const version = beforeMatches.find((candidate) => replayHash(applied.document, candidate) === entry.after_hash);
|
|
181
201
|
if (version === undefined) {
|
|
182
202
|
return {
|
|
183
203
|
ok: false,
|
|
184
|
-
errors: [
|
|
204
|
+
errors: [
|
|
205
|
+
...errors,
|
|
206
|
+
`verify_failed:after_hash_mismatch:entry_${index + 1}`,
|
|
207
|
+
],
|
|
185
208
|
};
|
|
186
209
|
}
|
|
187
210
|
replay = applied.document;
|
|
188
211
|
detectedVersion = version;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
212
|
+
authoritativeExplicitVersion =
|
|
213
|
+
explicitVersion ??
|
|
214
|
+
authoritativeExplicitVersion;
|
|
215
|
+
}
|
|
216
|
+
if (errors.length > 0) {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
errors,
|
|
220
|
+
...(detectedVersion === undefined
|
|
221
|
+
? {}
|
|
222
|
+
: { item_hash_version: detectedVersion }),
|
|
223
|
+
};
|
|
192
224
|
}
|
|
193
225
|
return {
|
|
194
226
|
ok: true,
|
|
@@ -266,8 +298,7 @@ export function resolveHistoryRepairItemHashVersion(entries) {
|
|
|
266
298
|
}
|
|
267
299
|
/** 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. */
|
|
268
300
|
export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistoryRepairItemHashVersion(entries)) {
|
|
269
|
-
const unsupportedIndex = entries.findIndex((entry) => entry.item_hash_version
|
|
270
|
-
!SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(entry.item_hash_version));
|
|
301
|
+
const unsupportedIndex = entries.findIndex((entry) => isUnsupportedExplicitHistoryItemHashVersion(entry.item_hash_version));
|
|
271
302
|
if (unsupportedIndex >= 0) {
|
|
272
303
|
throw new TypeError(`unsupported_item_hash_version:${String(entries[unsupportedIndex]?.item_hash_version)}:entry_${unsupportedIndex + 1}`);
|
|
273
304
|
}
|
|
@@ -351,4 +382,4 @@ export function historyEntriesToRaw(entries) {
|
|
|
351
382
|
return `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
|
|
352
383
|
}
|
|
353
384
|
//# sourceMappingURL=replay.js.map
|
|
354
|
-
//# debugId=
|
|
385
|
+
//# debugId=c823af51-0cfd-536b-8f85-1f3e72db1c86
|
|
@@ -18,9 +18,15 @@ export interface ResolvedBlocker {
|
|
|
18
18
|
status: ItemStatus | null;
|
|
19
19
|
/** True when the blocker no longer gates work because the referenced item is terminal. */
|
|
20
20
|
resolved: boolean;
|
|
21
|
+
/** Whether this blocker is a cross-system locator requiring an external resolver. */
|
|
22
|
+
external?: boolean;
|
|
23
|
+
/** Last holder mutation timestamp, used as a conservative external-block staleness marker. */
|
|
24
|
+
blocked_since?: string;
|
|
25
|
+
/** Resolver identity when an integration resolved this external blocker; null means unverifiable. */
|
|
26
|
+
resolver?: string | null;
|
|
21
27
|
}
|
|
22
28
|
/** Resolves an item's blockers against a corpus index with reverse `blocks` support. */
|
|
23
|
-
export declare function resolveItemBlockers(item: Pick<ItemMetadata, "blocked_by" | "dependencies"
|
|
29
|
+
export declare function resolveItemBlockers(item: Pick<ItemMetadata, "blocked_by" | "dependencies"> & Partial<Pick<ItemMetadata, "id" | "updated_at">>, itemsById: Map<string, ItemMetadata>, statusRegistry: RuntimeStatusRegistry): ResolvedBlocker[];
|
|
24
30
|
/**
|
|
25
31
|
* Collects the normalized (trimmed, lowercased) ids of every corpus item that is
|
|
26
32
|
* blocked under the single shared edge-aware definition: a non-terminal item
|
|
@@ -10,8 +10,9 @@
|
|
|
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]="0605f770-d868-5fe9-a186-45f00d2e93b4")}catch(e){}}();
|
|
14
14
|
import { isTerminalStatus, normalizeStatusForRegistry } from "./status.js";
|
|
15
|
+
import { isExternalDependencyReference, isExternalDependencySourceKind, } from "./dependency-reference.js";
|
|
15
16
|
/** Dependency kind that marks "this item is blocked by the referenced item". */
|
|
16
17
|
const BLOCKED_BY_DEPENDENCY_KIND = "blocked_by";
|
|
17
18
|
/** Reverse dependency kind that marks "this item blocks the referenced item". */
|
|
@@ -32,22 +33,32 @@ const NO_ACTIVE_BLOCKER_SENTINEL = "no-active-blocker";
|
|
|
32
33
|
function normalizeItemId(id) {
|
|
33
34
|
return id.trim().toLowerCase();
|
|
34
35
|
}
|
|
36
|
+
/** Build a collision-safe blocker key without normalizing external identity. */
|
|
37
|
+
function blockerReferenceKey(id, external) {
|
|
38
|
+
return external ? `external:${id}` : `local:${normalizeItemId(id)}`;
|
|
39
|
+
}
|
|
35
40
|
/** 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. */
|
|
36
41
|
export function collectBlockedByIds(item) {
|
|
37
|
-
const ids = new
|
|
38
|
-
const scalar = typeof item.blocked_by === "string" ?
|
|
39
|
-
if (scalar.length > 0 &&
|
|
40
|
-
|
|
42
|
+
const ids = new Map();
|
|
43
|
+
const scalar = typeof item.blocked_by === "string" ? item.blocked_by.trim() : "";
|
|
44
|
+
if (scalar.length > 0 &&
|
|
45
|
+
normalizeItemId(scalar) !== NO_ACTIVE_BLOCKER_SENTINEL) {
|
|
46
|
+
const external = isExternalDependencyReference(scalar);
|
|
47
|
+
ids.set(blockerReferenceKey(scalar, external), external ? scalar : normalizeItemId(scalar));
|
|
41
48
|
}
|
|
42
49
|
for (const dependency of item.dependencies ?? []) {
|
|
43
|
-
const dependencyId = typeof dependency.id === "string" ?
|
|
50
|
+
const dependencyId = typeof dependency.id === "string" ? dependency.id.trim() : "";
|
|
44
51
|
if (dependency.kind === BLOCKED_BY_DEPENDENCY_KIND &&
|
|
45
52
|
dependencyId.length > 0 &&
|
|
46
|
-
dependencyId !== NO_ACTIVE_BLOCKER_SENTINEL) {
|
|
47
|
-
|
|
53
|
+
normalizeItemId(dependencyId) !== NO_ACTIVE_BLOCKER_SENTINEL) {
|
|
54
|
+
const external = isExternalDependencyReference(dependencyId) ||
|
|
55
|
+
isExternalDependencySourceKind(dependency.source_kind);
|
|
56
|
+
ids.set(blockerReferenceKey(dependencyId, external), external
|
|
57
|
+
? dependencyId
|
|
58
|
+
: normalizeItemId(dependencyId));
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
|
-
return [...ids].sort((left, right) => left.localeCompare(right));
|
|
61
|
+
return [...ids.values()].sort((left, right) => left.localeCompare(right));
|
|
51
62
|
}
|
|
52
63
|
/** Build every forward and reverse blocker set in one corpus pass. */
|
|
53
64
|
export function indexBlockedByIds(corpus) {
|
|
@@ -92,9 +103,31 @@ function resolveItemBlockersWithIndex(item, itemsById, statusRegistry, blockerId
|
|
|
92
103
|
const itemWithId = item;
|
|
93
104
|
const blockerIds = typeof itemWithId.id === "string"
|
|
94
105
|
? (blockerIdsByItem?.get(normalizeItemId(itemWithId.id)) ??
|
|
95
|
-
collectBlockedByIdsFromCorpus(itemWithId, [
|
|
106
|
+
collectBlockedByIdsFromCorpus(itemWithId, [
|
|
107
|
+
...itemsById.values(),
|
|
108
|
+
]))
|
|
96
109
|
: collectBlockedByIds(item);
|
|
110
|
+
const structuredExternalBlockerIds = new Set((item.dependencies ?? [])
|
|
111
|
+
.filter((dependency) => dependency.kind === BLOCKED_BY_DEPENDENCY_KIND &&
|
|
112
|
+
isExternalDependencySourceKind(dependency.source_kind) &&
|
|
113
|
+
typeof dependency.id === "string" &&
|
|
114
|
+
dependency.id.trim().length > 0)
|
|
115
|
+
.map((dependency) => blockerReferenceKey(dependency.id.trim(), true)));
|
|
97
116
|
return blockerIds.map((id) => {
|
|
117
|
+
if (isExternalDependencyReference(id) ||
|
|
118
|
+
structuredExternalBlockerIds.has(blockerReferenceKey(id, true))) {
|
|
119
|
+
return {
|
|
120
|
+
id,
|
|
121
|
+
title: null,
|
|
122
|
+
status: null,
|
|
123
|
+
resolved: false,
|
|
124
|
+
external: true,
|
|
125
|
+
...(typeof item.updated_at === "string"
|
|
126
|
+
? { blocked_since: item.updated_at }
|
|
127
|
+
: {}),
|
|
128
|
+
resolver: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
98
131
|
const blocker = itemsById.get(normalizeItemId(id));
|
|
99
132
|
if (!blocker) {
|
|
100
133
|
return { id, title: null, status: null, resolved: false };
|
|
@@ -257,4 +290,4 @@ export function computeActionabilityReport(candidates, corpus, statusRegistry) {
|
|
|
257
290
|
};
|
|
258
291
|
}
|
|
259
292
|
//# sourceMappingURL=actionability.js.map
|
|
260
|
-
//# debugId=
|
|
293
|
+
//# debugId=0605f770-d868-5fe9-a186-45f00d2e93b4
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/item/dependency-reference
|
|
3
|
+
*
|
|
4
|
+
* Classifies dependency locators that intentionally resolve outside the local
|
|
5
|
+
* tracker so scheduling and graph governance do not confuse them with typos.
|
|
6
|
+
*/
|
|
7
|
+
/** Return whether a dependency target is an explicit cross-system locator rather than a local pm item id. */
|
|
8
|
+
export declare function isExternalDependencyReference(target: string | undefined): boolean;
|
|
9
|
+
/** Return whether dependency provenance explicitly places a target outside the local workspace. */
|
|
10
|
+
export declare function isExternalDependencySourceKind(sourceKind: string | undefined): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/item/dependency-reference
|
|
3
|
+
*
|
|
4
|
+
* Classifies dependency locators that intentionally resolve outside the local
|
|
5
|
+
* tracker so scheduling and graph governance do not confuse them with typos.
|
|
6
|
+
*/
|
|
7
|
+
/** Return whether a dependency target is an explicit cross-system locator rather than a local pm item id. */
|
|
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]="971d929f-12be-5648-93c9-8a9878b40dde")}catch(e){}}();
|
|
10
|
+
export function isExternalDependencyReference(target) {
|
|
11
|
+
const normalized = target?.trim() ?? "";
|
|
12
|
+
return (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(normalized) ||
|
|
13
|
+
/^(?:github|gitlab|jira|linear):/iu.test(normalized));
|
|
14
|
+
}
|
|
15
|
+
/** Return whether dependency provenance explicitly places a target outside the local workspace. */
|
|
16
|
+
export function isExternalDependencySourceKind(sourceKind) {
|
|
17
|
+
const normalized = sourceKind?.trim().toLowerCase();
|
|
18
|
+
return normalized === "global" || normalized === "external";
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=dependency-reference.js.map
|
|
21
|
+
//# debugId=971d929f-12be-5648-93c9-8a9878b40dde
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Classify plaintext as structured key/value input using an explicit accepted-key vocabulary. */
|
|
2
|
+
export declare function looksLikeStructuredKeyValueEntry(raw: string, keys: readonly string[]): boolean;
|
|
1
3
|
/** Implements parse tags for the public runtime surface of this module. */
|
|
2
4
|
export declare function parseTags(raw: string): string[];
|
|
3
5
|
/** Merge repeated `--add-tags` / `--remove-tags` values into a single normalized tag list. Each entry can itself be CSV or a JSON array, mirroring the format accepted by `--tags`. Returns a deterministically sorted, deduped list. */
|
|
@@ -29,6 +31,8 @@ export declare function parseCsvKv(raw: string, optionName: string): Record<stri
|
|
|
29
31
|
* never misread as a `C=…` key/value entry.
|
|
30
32
|
*/
|
|
31
33
|
export declare function looksLikeGenericKeyValueEntry(raw: string): boolean;
|
|
34
|
+
/** Refuse a comma-bearing bare value whose meaning depends on guessing whether the comma separates values or belongs to one value. */
|
|
35
|
+
export declare function assertNoAmbiguousBareCommaEntry(raw: string, optionName: string, noun: string, structured: boolean): void;
|
|
32
36
|
/**
|
|
33
37
|
* Reject any key in a parsed CSV/markdown key/value map that is not part of the
|
|
34
38
|
* caller's allowed-key contract, mirroring the strict `test --add` behavior
|