@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.
Files changed (98) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +30 -0
  3. package/dist/cli-bundle/bundle-manifest.json +125 -125
  4. package/dist/cli-bundle/chunks/{chunk-E73FDIWT.js → chunk-2MKLQL2E.js} +2 -2
  5. package/dist/cli-bundle/chunks/{chunk-NTXZHRKA.js → chunk-2UETTHY6.js} +46 -46
  6. package/dist/cli-bundle/chunks/{chunk-FEVBFFCQ.js → chunk-3TEF3HM5.js} +2 -2
  7. package/dist/cli-bundle/chunks/{chunk-BY2FQ2NI.js → chunk-7SKMLSMX.js} +2 -2
  8. package/dist/cli-bundle/chunks/chunk-7X6OZEIV.js +34 -0
  9. package/dist/cli-bundle/chunks/{chunk-M7OXRQE3.js → chunk-CMIEDWAY.js} +50 -50
  10. package/dist/cli-bundle/chunks/{chunk-XWEQGHHG.js → chunk-GNWPTEBW.js} +64 -61
  11. package/dist/cli-bundle/chunks/{chunk-X2RROGZE.js → chunk-MTTKAUBX.js} +2 -2
  12. package/dist/cli-bundle/chunks/chunk-N6OR6FAR.js +13 -0
  13. package/dist/cli-bundle/chunks/{chunk-NBCBFVZI.js → chunk-TGNAA66W.js} +2 -2
  14. package/dist/cli-bundle/chunks/{chunk-QE6WQXFO.js → chunk-WS2GJQVG.js} +2 -2
  15. package/dist/cli-bundle/chunks/{register-list-query-J35ZPQQ5.js → register-list-query-OMKJ5ONN.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-mutation-J6XJJOGU.js → register-mutation-QBSNCOY3.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-operations-AE3JEMFT.js → register-operations-UC77FQZC.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-setup-OQERLLWE.js → register-setup-GLQ5SYV3.js} +2 -2
  19. package/dist/cli-bundle/focused-chunks/{chunk-HC7ODMH3.js → chunk-3AVAWVZA.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/chunk-3UMBF26I.js +158 -0
  21. package/dist/cli-bundle/focused-chunks/{chunk-YVVZ3LQ6.js → chunk-73EDXGYE.js} +16 -15
  22. package/dist/cli-bundle/focused-chunks/chunk-AXZ5AJAF.js +50 -0
  23. package/dist/cli-bundle/focused-chunks/{chunk-AHAM2HAU.js → chunk-G6PATS5E.js} +3 -3
  24. package/dist/cli-bundle/focused-chunks/chunk-RKOKG43G.js +2 -0
  25. package/dist/cli-bundle/focused-chunks/chunk-TDK3OCX3.js +2 -0
  26. package/dist/cli-bundle/focused-chunks/{chunk-FC2AXLB5.js → chunk-XXOWRJZ5.js} +2 -2
  27. package/dist/cli-bundle/main.js +2 -2
  28. package/dist/cli-bundle/sdk-contracts.js +1 -1
  29. package/dist/cli-bundle/sdk-core.js +31 -32
  30. package/dist/cli-bundle/sdk-governance.js +1 -1
  31. package/dist/cli-bundle/sdk-graph.js +1 -1
  32. package/dist/cli-bundle/sdk-merge.js +34 -32
  33. package/dist/cli-bundle/sdk-query.js +1 -1
  34. package/dist/cli-bundle/sdk-runtime.js +1 -1
  35. package/dist/cli-bundle/sdk-testing.js +1 -1
  36. package/dist/cli-bundle/sdk.js +8 -8
  37. package/dist/core/diagnostics/remediation.js +16 -6
  38. package/dist/core/history/drift-scan.d.ts +2 -0
  39. package/dist/core/history/drift-scan.js +72 -36
  40. package/dist/core/history/history.d.ts +3 -3
  41. package/dist/core/history/history.js +5 -5
  42. package/dist/core/history/replay.d.ts +1 -1
  43. package/dist/core/history/replay.js +50 -19
  44. package/dist/core/item/actionability.d.ts +7 -1
  45. package/dist/core/item/actionability.js +44 -11
  46. package/dist/core/item/dependency-reference.d.ts +10 -0
  47. package/dist/core/item/dependency-reference.js +21 -0
  48. package/dist/core/item/parse.d.ts +4 -0
  49. package/dist/core/item/parse.js +38 -16
  50. package/dist/core/shared/author.js +69 -17
  51. package/dist/sdk/dependency-provenance.d.ts +51 -2
  52. package/dist/sdk/dependency-provenance.js +91 -7
  53. package/dist/sdk/extension/install-sources.d.ts +1 -7
  54. package/dist/sdk/extension/install-sources.js +94 -33
  55. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +50 -14
  56. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +62 -26
  57. package/dist/sdk/governance/health.js +86 -22
  58. package/dist/sdk/governance/validate-history-drift.js +5 -2
  59. package/dist/sdk/graph/assembly.js +51 -22
  60. package/dist/sdk/history-repair.d.ts +1 -1
  61. package/dist/sdk/history-repair.js +20 -10
  62. package/dist/sdk/lifecycle/create.js +43 -19
  63. package/dist/sdk/linked-artifacts.js +21 -31
  64. package/dist/sdk/merge/reconcile.js +3 -16
  65. package/dist/sdk/query/next.d.ts +6 -0
  66. package/dist/sdk/query/next.js +7 -2
  67. package/dist/sdk/test/entry.d.ts +1 -1
  68. package/dist/sdk/test/entry.js +5 -9
  69. package/dist/sdk/test/execution.d.ts +3 -0
  70. package/dist/sdk/test/execution.js +54 -13
  71. package/docs/CONTEXT_RECOVERY_PRIMITIVES.md +67 -0
  72. package/docs/MERGE_SAFETY.md +23 -10
  73. package/docs/README.md +1 -0
  74. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +10 -4
  75. package/marketplace.json +2 -2
  76. package/package.json +8 -8
  77. package/packages/pm-beads/package.json +1 -1
  78. package/packages/pm-calendar/package.json +1 -1
  79. package/packages/pm-command-kit/package.json +1 -1
  80. package/packages/pm-digital-twin/package.json +1 -1
  81. package/packages/pm-governance-audit/package.json +1 -1
  82. package/packages/pm-guide-shell/package.json +1 -1
  83. package/packages/pm-kanban/package.json +1 -1
  84. package/packages/pm-lifecycle-hooks/package.json +1 -1
  85. package/packages/pm-linked-test-adapters/package.json +1 -1
  86. package/packages/pm-search-advanced/package.json +1 -1
  87. package/packages/pm-templates/package.json +1 -1
  88. package/packages/pm-todos/package.json +1 -1
  89. package/packages/pm-vcs/package.json +1 -1
  90. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  91. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  92. package/sdk/public-surface.json +100 -11
  93. package/dist/cli-bundle/chunks/chunk-TIQ6AMH2.js +0 -13
  94. package/dist/cli-bundle/chunks/chunk-XRVVYRRO.js +0 -35
  95. package/dist/cli-bundle/focused-chunks/chunk-4ZDRZYYJ.js +0 -50
  96. package/dist/cli-bundle/focused-chunks/chunk-XDPYBQCF.js +0 -156
  97. package/dist/cli-bundle/focused-chunks/chunk-Y5A7SJJ7.js +0 -2
  98. package/dist/cli-bundle/focused-chunks/chunk-YJLDHJOD.js +0 -2
@@ -4,7 +4,7 @@
4
4
  * Defines item parsing, formatting, and lifecycle helpers for Parse.
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]="2252cfb9-37ea-57f1-b7ea-2b0c0f87ecb2")}catch(e){}}();
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]="80965b57-856c-5fef-a372-0c9f3766ada5")}catch(e){}}();
8
8
  import { PmCliError } from "../shared/errors.js";
9
9
  import { EXIT_CODE } from "../shared/constants.js";
10
10
  const STDIN_TOKEN = "-";
@@ -30,6 +30,16 @@ const CONTINUABLE_VALUE_KEYS = new Set([
30
30
  "value",
31
31
  "why_now",
32
32
  ]);
33
+ /** Classify plaintext as structured key/value input using an explicit accepted-key vocabulary. */
34
+ export function looksLikeStructuredKeyValueEntry(raw, keys) {
35
+ if (raw.startsWith("```") || raw.includes("\n")) {
36
+ return true;
37
+ }
38
+ const keyPattern = keys
39
+ .map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
40
+ .join("|");
41
+ return new RegExp(`^(?:[-*+]\\s+)?(?:${keyPattern})\\s*[:=]`, "i").test(raw);
42
+ }
33
43
  /** Implements parse tags for the public runtime surface of this module. */
34
44
  export function parseTags(raw) {
35
45
  const trimmed = raw.trim();
@@ -188,16 +198,16 @@ function splitCsvSegments(raw) {
188
198
  const segments = [];
189
199
  let current = "";
190
200
  let inQuotes = false;
191
- let escaped = false;
192
- for (const char of raw) {
193
- if (escaped) {
194
- current += char;
195
- escaped = false;
201
+ for (let index = 0; index < raw.length; index += 1) {
202
+ const char = raw[index];
203
+ if (char === "\\" && raw[index + 1] === ",") {
204
+ current += ",";
205
+ index += 1;
196
206
  continue;
197
207
  }
198
- if (char === "\\") {
199
- current += char;
200
- escaped = true;
208
+ if (char === "\\" && raw[index + 1] !== undefined) {
209
+ current += `${char}${raw[index + 1]}`;
210
+ index += 1;
201
211
  continue;
202
212
  }
203
213
  if (char === '"') {
@@ -206,14 +216,14 @@ function splitCsvSegments(raw) {
206
216
  continue;
207
217
  }
208
218
  if (char === "," && !inQuotes) {
209
- segments.push(current.trim());
219
+ segments.push(current);
210
220
  current = "";
211
221
  continue;
212
222
  }
213
223
  current += char;
214
224
  }
215
225
  if (current.trim()) {
216
- segments.push(current.trim());
226
+ segments.push(current);
217
227
  }
218
228
  return segments;
219
229
  }
@@ -235,8 +245,8 @@ function unquoteValue(value) {
235
245
  function findKeyValueDelimiter(segment) {
236
246
  const equalsIndex = segment.indexOf("=");
237
247
  const colonIndex = segment.indexOf(":");
238
- const hasEquals = equalsIndex > 0;
239
- const hasColon = colonIndex > 0;
248
+ const hasEquals = equalsIndex >= 0;
249
+ const hasColon = colonIndex >= 0;
240
250
  if (hasEquals && hasColon) {
241
251
  return Math.min(equalsIndex, colonIndex);
242
252
  }
@@ -324,15 +334,18 @@ export function parseCsvKv(raw, optionName) {
324
334
  const segments = splitCsvSegments(trimmed);
325
335
  for (const segment of segments) {
326
336
  const delimiterIndex = findKeyValueDelimiter(segment);
327
- if (delimiterIndex > 0) {
337
+ if (delimiterIndex >= 0) {
328
338
  const key = segment.slice(0, delimiterIndex).trim();
339
+ if (!key) {
340
+ throw new PmCliError(buildInvalidKvMessage(raw, optionName), EXIT_CODE.USAGE);
341
+ }
329
342
  const value = unquoteValue(segment.slice(delimiterIndex + 1).trim());
330
343
  result[key] = value;
331
344
  activeKey = key;
332
345
  continue;
333
346
  }
334
347
  if (activeKey && CONTINUABLE_VALUE_KEYS.has(activeKey.toLowerCase())) {
335
- result[activeKey] = `${result[activeKey]},${segment.trim()}`;
348
+ result[activeKey] = `${result[activeKey]},${segment}`;
336
349
  continue;
337
350
  }
338
351
  throw new PmCliError(buildInvalidKvMessage(raw, optionName), EXIT_CODE.USAGE);
@@ -358,6 +371,15 @@ export function looksLikeGenericKeyValueEntry(raw) {
358
371
  }
359
372
  return GENERIC_LEADING_KV_KEY_PATTERN.test(trimmed);
360
373
  }
374
+ /** Refuse a comma-bearing bare value whose meaning depends on guessing whether the comma separates values or belongs to one value. */
375
+ export function assertNoAmbiguousBareCommaEntry(raw, optionName, noun, structured) {
376
+ if (structured || !raw.includes(","))
377
+ return;
378
+ throw new PmCliError(`${optionName} received an ambiguous comma-bearing bare ${noun}. Repeat ${optionName} for multiple values, or quote or escape literal commas in a structured key=<value> entry.`, EXIT_CODE.USAGE, {
379
+ code: "bare_comma_entry_ambiguous",
380
+ required: `Use one ${optionName} per bare ${noun}, or quote or escape literal commas in a structured key=<value> entry.`,
381
+ });
382
+ }
361
383
  /**
362
384
  * Reject any key in a parsed CSV/markdown key/value map that is not part of the
363
385
  * caller's allowed-key contract, mirroring the strict `test --add` behavior
@@ -579,4 +601,4 @@ export const _testOnly = {
579
601
  parseMarkdownKeyValueLines,
580
602
  };
581
603
  //# sourceMappingURL=parse.js.map
582
- //# debugId=2252cfb9-37ea-57f1-b7ea-2b0c0f87ecb2
604
+ //# debugId=80965b57-856c-5fef-a372-0c9f3766ada5
@@ -4,7 +4,7 @@
4
4
  * Provides shared primitives and utilities for Author.
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]="8d89d381-5c6e-5f96-ba03-298fc6db9bba")}catch(e){}}();
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]="cf550961-0040-5e04-9d8d-16e316027e43")}catch(e){}}();
8
8
  import { AsyncLocalStorage } from "node:async_hooks";
9
9
  import { createHash } from "node:crypto";
10
10
  import fs from "node:fs";
@@ -312,20 +312,50 @@ const MAX_PROVENANCE_PROBE_BYTES = 1_048_576;
312
312
  const MAX_CODEX_PROVENANCE_PROBE_BYTES = 4_194_304;
313
313
  const codexProvenanceSnapshotCache = new Map();
314
314
  const EMPTY_CODEX_PROVENANCE_SNAPSHOT = Object.freeze({});
315
- function parseClaudeProvenanceLine(line, dimension) {
315
+ const claudeProvenanceSnapshotCache = new Map();
316
+ const EMPTY_CLAUDE_PROVENANCE_SNAPSHOT = Object.freeze({});
317
+ function asProvenanceRecord(value) {
318
+ return typeof value === "object" && value !== null && !Array.isArray(value)
319
+ ? value
320
+ : undefined;
321
+ }
322
+ function parseClaudeProvenanceLine(line) {
316
323
  let parsed;
317
324
  try {
318
325
  parsed = JSON.parse(line);
319
326
  }
320
327
  catch {
321
- return undefined;
328
+ return EMPTY_CLAUDE_PROVENANCE_SNAPSHOT;
322
329
  }
323
- const record = parsed;
324
- const message = typeof record.message === "object" && record.message !== null
325
- ? record.message
326
- : undefined;
327
- const value = dimension === "model" ? message?.model : record.version;
328
- return nonBlank(value)?.slice(0, 256);
330
+ const record = asProvenanceRecord(parsed);
331
+ if (!record)
332
+ return EMPTY_CLAUDE_PROVENANCE_SNAPSHOT;
333
+ const queue = [
334
+ { value: record, depth: 0 },
335
+ ];
336
+ let visited = 0;
337
+ let model;
338
+ let version;
339
+ while (queue.length > 0 && visited < 64) {
340
+ const current = queue.shift();
341
+ visited += 1;
342
+ model ??= nonBlank(current.value.model)?.slice(0, 256);
343
+ version ??= nonBlank(current.value.version)?.slice(0, 256);
344
+ if (model && version)
345
+ break;
346
+ if (current.depth >= 4)
347
+ continue;
348
+ for (const value of Object.values(current.value)) {
349
+ const nested = asProvenanceRecord(value);
350
+ if (nested) {
351
+ queue.push({
352
+ value: nested,
353
+ depth: current.depth + 1,
354
+ });
355
+ }
356
+ }
357
+ }
358
+ return Object.freeze({ model, version });
329
359
  }
330
360
  function readClaudeSessionProvenance(dimension, signals, env) {
331
361
  const session = nonBlank(env.CLAUDE_CODE_SESSION_ID);
@@ -337,18 +367,40 @@ function readClaudeSessionProvenance(dimension, signals, env) {
337
367
  try {
338
368
  const file = fs.openSync(sessionPath, "r");
339
369
  try {
340
- const size = fs.fstatSync(file).size;
341
- const tailLength = Math.min(size, MAX_PROVENANCE_PROBE_BYTES);
370
+ const stats = fs.fstatSync(file);
371
+ const cacheKey = `${sessionPath}\u0000${stats.size}\u0000${stats.mtimeMs}`;
372
+ const cached = claudeProvenanceSnapshotCache.get(cacheKey);
373
+ if (cached)
374
+ return cached[dimension];
375
+ const probeLength = Math.min(stats.size, MAX_PROVENANCE_PROBE_BYTES);
376
+ const headLength = Math.min(probeLength, Math.ceil(probeLength / 2));
377
+ const tailLength = Math.min(probeLength - headLength, stats.size - headLength);
378
+ const head = Buffer.alloc(headLength);
379
+ fs.readSync(file, head, 0, headLength, 0);
342
380
  const tail = Buffer.alloc(tailLength);
343
- fs.readSync(file, tail, 0, tailLength, Math.max(0, size - tailLength));
344
- const lines = tail.toString("utf8").split("\n").reverse().slice(0, 4_096);
381
+ if (tailLength > 0) {
382
+ fs.readSync(file, tail, 0, tailLength, stats.size - tailLength);
383
+ }
384
+ const snapshot = {};
385
+ const lines = stats.size <= probeLength
386
+ ? Buffer.concat([head, tail]).toString("utf8").split("\n")
387
+ : [
388
+ ...head.toString("utf8").split("\n").slice(0, 4_096),
389
+ ...tail.toString("utf8").split("\n").slice(-4_096),
390
+ ];
345
391
  for (const line of lines) {
346
392
  if (line.length === 0 || line.length > 262_144)
347
393
  continue;
348
- const value = parseClaudeProvenanceLine(line, dimension);
349
- if (value)
350
- return value;
394
+ const parsed = parseClaudeProvenanceLine(line);
395
+ snapshot.model ??= parsed.model;
396
+ snapshot.version ??= parsed.version;
397
+ if (snapshot.model && snapshot.version)
398
+ break;
351
399
  }
400
+ const frozen = Object.freeze(snapshot);
401
+ claudeProvenanceSnapshotCache.clear();
402
+ claudeProvenanceSnapshotCache.set(cacheKey, frozen);
403
+ return frozen[dimension];
352
404
  }
353
405
  finally {
354
406
  fs.closeSync(file);
@@ -1030,4 +1082,4 @@ export function resolveAuthor(candidate, fallback) {
1030
1082
  return identity.author;
1031
1083
  }
1032
1084
  //# sourceMappingURL=author.js.map
1033
- //# debugId=8d89d381-5c6e-5f96-ba03-298fc6db9bba
1085
+ //# debugId=cf550961-0040-5e04-9d8d-16e316027e43
@@ -1,9 +1,58 @@
1
+ export { isExternalDependencyReference, isExternalDependencySourceKind, } from "../core/item/dependency-reference.js";
1
2
  /** Provenance value declaring that a dependency id belongs to another workspace. */
2
3
  export declare const EXTERNAL_DEPENDENCY_SOURCE_KIND = "global";
3
4
  /** Human-facing alias accepted wherever external dependency provenance is parsed. */
4
5
  export declare const EXTERNAL_DEPENDENCY_SOURCE_KIND_ALIAS = "external";
5
- /** Return whether a dependency provenance value identifies an external workspace target. */
6
- export declare function isExternalDependencySourceKind(sourceKind: string | undefined): boolean;
6
+ /** Stable external lifecycle values understood without coupling core to a provider. */
7
+ export declare const EXTERNAL_DEPENDENCY_RESOLUTION_STATUSES: readonly ["open", "closed", "unknown"];
8
+ /** External lifecycle value returned by a registered dependency resolver. */
9
+ export type ExternalDependencyResolutionStatus = (typeof EXTERNAL_DEPENDENCY_RESOLUTION_STATUSES)[number];
10
+ /** Provider-owned external dependency data before the SDK applies output bounds. */
11
+ export interface ExternalDependencyResolverResult {
12
+ /** Provider lifecycle state normalized to the universal external status vocabulary. */
13
+ status: ExternalDependencyResolutionStatus;
14
+ /** Optional bounded human context for the remote dependency. */
15
+ title?: string;
16
+ /** Optional canonical provider URL or locator used as evidence. */
17
+ source?: string;
18
+ /** Optional provider observation timestamp; invalid timestamps are replaced by SDK time. */
19
+ checkedAt?: string;
20
+ }
21
+ /** Package-provided resolver contract for one or more external dependency locator families. */
22
+ export interface ExternalDependencyResolver {
23
+ /** Stable resolver identity surfaced in agent-facing evidence. */
24
+ name: string;
25
+ /** Return whether this resolver owns the supplied external locator. */
26
+ supports(reference: string): boolean;
27
+ /** Resolve the locator without exposing provider credentials or unbounded response data. */
28
+ resolve(reference: string): Promise<ExternalDependencyResolverResult | null>;
29
+ }
30
+ /** Bounded provider-neutral evidence returned by the public resolver registry. */
31
+ export interface ExternalDependencyResolution {
32
+ /** Original normalized dependency locator. */
33
+ id: string;
34
+ /** Provider lifecycle state. */
35
+ status: ExternalDependencyResolutionStatus;
36
+ /** Whether trusted provider evidence proves the blocker terminal. */
37
+ resolved: boolean;
38
+ /** Bounded remote title, or null when the provider did not supply one. */
39
+ title: string | null;
40
+ /** Bounded evidence locator. */
41
+ source: string;
42
+ /** Observation timestamp supplied by the provider or SDK clock. */
43
+ checked_at: string;
44
+ /** Registered resolver identity that produced this evidence. */
45
+ resolver: string;
46
+ }
47
+ /** Deterministic clock override for external resolver acceptance and tests. */
48
+ export interface ExternalDependencyResolutionOptions {
49
+ /** Return the timestamp used when provider evidence omits or corrupts checkedAt. */
50
+ now?: () => string;
51
+ }
52
+ /** Register a package-owned external dependency resolver and return its idempotent disposer. */
53
+ export declare function registerExternalDependencyResolver(resolver: ExternalDependencyResolver): () => void;
54
+ /** Resolve one external locator through registered package providers with bounded provider-neutral evidence. */
55
+ export declare function resolveExternalDependencyReference(reference: string, options?: ExternalDependencyResolutionOptions): Promise<ExternalDependencyResolution | null>;
7
56
  /** Canonicalize external provenance while preserving other named source kinds. */
8
57
  export declare function normalizeDependencySourceKind(sourceKind: string | undefined): string | undefined;
9
58
  /**
@@ -5,17 +5,101 @@
5
5
  * items outside the current workspace.
6
6
  */
7
7
 
8
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1b2f1ac3-82b3-58a2-9f40-2dd95ab1b2bc")}catch(e){}}();
8
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f3f3beb2-1d60-52fe-88ce-1ab3cb9dba0d")}catch(e){}}();
9
9
  import { normalizeItemId } from "../core/item/id.js";
10
+ import { isExternalDependencyReference, isExternalDependencySourceKind, } from "../core/item/dependency-reference.js";
11
+ export { isExternalDependencyReference, isExternalDependencySourceKind, } from "../core/item/dependency-reference.js";
10
12
  /** Provenance value declaring that a dependency id belongs to another workspace. */
11
13
  export const EXTERNAL_DEPENDENCY_SOURCE_KIND = "global";
12
14
  /** Human-facing alias accepted wherever external dependency provenance is parsed. */
13
15
  export const EXTERNAL_DEPENDENCY_SOURCE_KIND_ALIAS = "external";
14
- /** Return whether a dependency provenance value identifies an external workspace target. */
15
- export function isExternalDependencySourceKind(sourceKind) {
16
- const normalized = sourceKind?.trim().toLowerCase();
17
- return (normalized === EXTERNAL_DEPENDENCY_SOURCE_KIND ||
18
- normalized === EXTERNAL_DEPENDENCY_SOURCE_KIND_ALIAS);
16
+ /** Stable external lifecycle values understood without coupling core to a provider. */
17
+ export const EXTERNAL_DEPENDENCY_RESOLUTION_STATUSES = [
18
+ "open",
19
+ "closed",
20
+ "unknown",
21
+ ];
22
+ const externalDependencyResolvers = new Map();
23
+ const MAX_EXTERNAL_TITLE_LENGTH = 240;
24
+ const MAX_EXTERNAL_SOURCE_LENGTH = 2_048;
25
+ /** Normalize and cap provider strings before they reach agent-facing output. */
26
+ function boundedExternalText(value, fallback, maximumLength) {
27
+ return (value?.trim() || fallback).slice(0, maximumLength);
28
+ }
29
+ /** Reject provider payloads that violate the runtime resolver-result contract. */
30
+ function isExternalDependencyResolverResult(value) {
31
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
32
+ return false;
33
+ }
34
+ const candidate = value;
35
+ return (typeof candidate.status === "string" &&
36
+ (candidate.title == null || typeof candidate.title === "string") &&
37
+ (candidate.source == null || typeof candidate.source === "string") &&
38
+ (candidate.checkedAt == null || typeof candidate.checkedAt === "string"));
39
+ }
40
+ /** Register a package-owned external dependency resolver and return its idempotent disposer. */
41
+ export function registerExternalDependencyResolver(resolver) {
42
+ const name = resolver.name.trim();
43
+ if (name.length === 0) {
44
+ throw new Error("External dependency resolver name must not be empty");
45
+ }
46
+ if (externalDependencyResolvers.has(name)) {
47
+ throw new Error(`External dependency resolver already registered: ${name}`);
48
+ }
49
+ const registeredResolver = {
50
+ name,
51
+ supports: (reference) => resolver.supports(reference),
52
+ resolve: (reference) => resolver.resolve(reference),
53
+ };
54
+ externalDependencyResolvers.set(name, registeredResolver);
55
+ return () => {
56
+ if (externalDependencyResolvers.get(name) === registeredResolver) {
57
+ externalDependencyResolvers.delete(name);
58
+ }
59
+ };
60
+ }
61
+ /** Resolve one external locator through registered package providers with bounded provider-neutral evidence. */
62
+ export async function resolveExternalDependencyReference(reference, options = {}) {
63
+ const id = reference.trim();
64
+ if (!isExternalDependencyReference(id)) {
65
+ return null;
66
+ }
67
+ const resolvers = [...externalDependencyResolvers.values()].filter((resolver) => {
68
+ try {
69
+ return resolver.supports(id);
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ });
75
+ const outcomes = await Promise.allSettled(resolvers.map((resolver) => Promise.resolve().then(() => resolver.resolve(id))));
76
+ for (const [index, outcome] of outcomes.entries()) {
77
+ if (outcome.status !== "fulfilled" ||
78
+ !isExternalDependencyResolverResult(outcome.value)) {
79
+ continue;
80
+ }
81
+ const resolver = resolvers[index];
82
+ const candidate = outcome.value;
83
+ const status = EXTERNAL_DEPENDENCY_RESOLUTION_STATUSES.includes(candidate.status)
84
+ ? candidate.status
85
+ : "unknown";
86
+ const title = boundedExternalText(candidate.title, "", MAX_EXTERNAL_TITLE_LENGTH);
87
+ const source = boundedExternalText(candidate.source, id, MAX_EXTERNAL_SOURCE_LENGTH);
88
+ const observedAtMs = Date.parse(candidate.checkedAt?.trim() ?? "");
89
+ const checkedAt = Number.isFinite(observedAtMs)
90
+ ? new Date(observedAtMs).toISOString()
91
+ : (options.now?.() ?? new Date().toISOString());
92
+ return {
93
+ id,
94
+ status,
95
+ resolved: status === "closed",
96
+ title: title.length > 0 ? title : null,
97
+ source,
98
+ checked_at: checkedAt,
99
+ resolver: resolver.name,
100
+ };
101
+ }
102
+ return null;
19
103
  }
20
104
  /** Canonicalize external provenance while preserving other named source kinds. */
21
105
  export function normalizeDependencySourceKind(sourceKind) {
@@ -39,4 +123,4 @@ export function normalizeDependencySeedId(id, prefix, sourceKind) {
39
123
  : normalizeItemId(trimmed, prefix);
40
124
  }
41
125
  //# sourceMappingURL=dependency-provenance.js.map
42
- //# debugId=1b2f1ac3-82b3-58a2-9f40-2dd95ab1b2bc
126
+ //# debugId=f3f3beb2-1d60-52fe-88ce-1ab3cb9dba0d
@@ -107,13 +107,7 @@ export declare function wrapNpmPackResolutionError(spec: string, error: unknown)
107
107
  declare function resolveNpmPackSpec(spec: string): Promise<string>;
108
108
  /** Implements normalize npm local file alias spec for the public runtime surface of this module. */
109
109
  export declare function normalizeNpmLocalFileAliasSpec(spec: string, cwd?: string): string;
110
- /**
111
- * Parse npm pack output and bind the reported artifact to its isolated target.
112
- *
113
- * Supports current JSON metadata and the legacy filename-only form while
114
- * refusing absolute or traversing results before the filesystem is consulted.
115
- */
116
- declare function parsePackedNpmPackage(stdout: string, packDirectory: string): {
110
+ declare function parsePackedNpmPackage(stdout: string, packDirectory: string, expectedPackage?: string): {
117
111
  tarball: string;
118
112
  package?: string;
119
113
  version?: string;
@@ -4,7 +4,7 @@
4
4
  * Implements extension package-management support for Install Sources.
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]="404342eb-68ab-5b7d-9a5a-ade1c8e98546")}catch(e){}}();
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]="05f56edb-c08d-570c-b8b5-641fc694331e")}catch(e){}}();
8
8
  import { execFile } from "node:child_process";
9
9
  import fs from "node:fs/promises";
10
10
  import os from "node:os";
@@ -237,6 +237,14 @@ function npmPackageNameFromSpec(spec) {
237
237
  const unscoped = withoutAlias.match(/^([^/@\s]+)/);
238
238
  return unscoped?.[1] ?? withoutAlias;
239
239
  }
240
+ /** Return whether an npm spec is a complete local-file alias, including unchanged UNC targets. */
241
+ function isNpmLocalFileAliasSpec(spec) {
242
+ const marker = "@file:";
243
+ const markerIndex = spec.lastIndexOf(marker);
244
+ return (markerIndex > 0 &&
245
+ spec.slice(0, markerIndex).trim().length > 0 &&
246
+ spec.slice(markerIndex + marker.length).trim().length > 0);
247
+ }
240
248
  /** Implements check whether npm not found error for the public runtime surface of this module. */
241
249
  export function isNpmNotFoundError(error) {
242
250
  const message = error instanceof Error ? error.message : String(error);
@@ -358,14 +366,11 @@ async function resolveNpmPackSpec(spec) {
358
366
  export function normalizeNpmLocalFileAliasSpec(spec, cwd = process.cwd()) {
359
367
  const marker = "@file:";
360
368
  const markerIndex = spec.lastIndexOf(marker);
361
- if (markerIndex <= 0) {
369
+ if (!isNpmLocalFileAliasSpec(spec)) {
362
370
  return spec;
363
371
  }
364
372
  const packageName = spec.slice(0, markerIndex);
365
373
  const target = spec.slice(markerIndex + marker.length);
366
- if (packageName.trim().length === 0 || target.trim().length === 0) {
367
- return spec;
368
- }
369
374
  // `file://host/share` (exactly two leading slashes) is a UNC / network spec,
370
375
  // not a local path — leave it for npm to resolve.
371
376
  if (target.startsWith("//") && !target.startsWith("///")) {
@@ -403,29 +408,7 @@ export function normalizeNpmLocalFileAliasSpec(spec, cwd = process.cwd()) {
403
408
  * Supports current JSON metadata and the legacy filename-only form while
404
409
  * refusing absolute or traversing results before the filesystem is consulted.
405
410
  */
406
- function parsePackedNpmPackage(stdout, packDirectory) {
407
- let parsed;
408
- try {
409
- parsed = JSON.parse(stdout);
410
- }
411
- catch {
412
- // Fall back to the last stdout line for older npm output.
413
- }
414
- const first = Array.isArray(parsed)
415
- ? parsed[0]
416
- : undefined;
417
- const filename = first &&
418
- typeof first.filename === "string" &&
419
- first.filename.trim().length > 0
420
- ? first.filename
421
- : stdout
422
- .split(/\r?\n/)
423
- .map((line) => line.trim())
424
- .filter((line) => line.length > 0)
425
- .at(-1);
426
- if (!filename) {
427
- throw new PmCliError("npm pack did not report a tarball filename.", EXIT_CODE.GENERIC_FAILURE);
428
- }
411
+ function resolvePackedTarball(packDirectory, filename) {
429
412
  const tarball = path.resolve(packDirectory, filename);
430
413
  const relativeTarball = path.relative(packDirectory, tarball);
431
414
  if (relativeTarball.length === 0 ||
@@ -438,10 +421,86 @@ function parsePackedNpmPackage(stdout, packDirectory) {
438
421
  why: "pm will not read a package-manager artifact outside the isolated directory created for this install.",
439
422
  });
440
423
  }
424
+ return tarball;
425
+ }
426
+ function decodePackedNpmCandidate(value, key) {
427
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
428
+ return undefined;
429
+ }
430
+ const record = value;
431
+ if (typeof record.filename !== "string" ||
432
+ record.filename.trim().length === 0) {
433
+ return undefined;
434
+ }
435
+ return {
436
+ ...(key === undefined ? {} : { key }),
437
+ filename: record.filename,
438
+ ...(typeof record.name === "string" ? { name: record.name } : {}),
439
+ ...(typeof record.version === "string" ? { version: record.version } : {}),
440
+ };
441
+ }
442
+ function collectPackedNpmCandidates(parsed) {
443
+ const entries = Array.isArray(parsed)
444
+ ? parsed.map((value) => ({ value }))
445
+ : typeof parsed === "object" && parsed !== null
446
+ ? Object.entries(parsed).map(([key, value]) => ({ key, value }))
447
+ : [];
448
+ return entries.flatMap(({ key, value }) => {
449
+ const candidate = decodePackedNpmCandidate(value, key);
450
+ return candidate ? [candidate] : [];
451
+ });
452
+ }
453
+ function selectPackedNpmCandidate(candidates, expectedPackage) {
454
+ const normalizedExpected = expectedPackage?.trim().toLowerCase();
455
+ const matchingCandidates = normalizedExpected
456
+ ? candidates.filter((candidate) => {
457
+ const normalizedName = candidate.name?.trim().toLowerCase();
458
+ return normalizedName !== undefined
459
+ ? normalizedName === normalizedExpected
460
+ : candidate.key?.trim().toLowerCase() === normalizedExpected;
461
+ })
462
+ : [];
463
+ if (matchingCandidates.length === 1)
464
+ return matchingCandidates[0];
465
+ if (candidates.length !== 1)
466
+ return undefined;
467
+ const soleCandidate = candidates[0];
468
+ const soleIdentity = soleCandidate.name ?? soleCandidate.key;
469
+ return normalizedExpected &&
470
+ soleIdentity !== undefined &&
471
+ soleIdentity.trim().toLowerCase() !== normalizedExpected
472
+ ? undefined
473
+ : soleCandidate;
474
+ }
475
+ function parsePackedNpmPackage(stdout, packDirectory, expectedPackage) {
476
+ let parsed;
477
+ try {
478
+ parsed = JSON.parse(stdout);
479
+ }
480
+ catch {
481
+ const filename = stdout
482
+ .split(/\r?\n/)
483
+ .map((line) => line.trim())
484
+ .filter((line) => line.length > 0)
485
+ .at(-1);
486
+ if (!filename) {
487
+ throw new PmCliError("npm pack did not report a tarball filename.", EXIT_CODE.GENERIC_FAILURE);
488
+ }
489
+ return { tarball: resolvePackedTarball(packDirectory, filename) };
490
+ }
491
+ const candidates = collectPackedNpmCandidates(parsed);
492
+ const selected = selectPackedNpmCandidate(candidates, expectedPackage);
493
+ if (!selected) {
494
+ throw new PmCliError("npm pack --json returned an unsupported or ambiguous package receipt.", EXIT_CODE.GENERIC_FAILURE, {
495
+ code: "npm_pack_json_shape_unsupported",
496
+ required: "Use an npm client that returns an array of package receipts or an object keyed by package name, with exactly one requested package entry containing filename.",
497
+ why: `Well-formed JSON contained ${candidates.length} usable package receipt(s), but pm could not select ${expectedPackage ?? "one package"} deterministically.`,
498
+ });
499
+ }
441
500
  return {
442
- tarball,
443
- package: typeof first?.name === "string" ? first.name : undefined,
444
- version: typeof first?.version === "string" ? first.version : undefined,
501
+ tarball: resolvePackedTarball(packDirectory, selected.filename),
502
+ package: selected.name ?? selected.key,
503
+ version: selected.version,
445
504
  };
446
505
  }
447
506
  async function resolveNpmSourceDirectory(source) {
@@ -486,7 +545,9 @@ async function resolveNpmSourceDirectoryWithRunner(source, npmRunner) {
486
545
  "--pack-destination",
487
546
  packDirectory,
488
547
  ]);
489
- const packed = parsePackedNpmPackage(packStdout, packDirectory);
548
+ const packed = parsePackedNpmPackage(packStdout, packDirectory, isNpmLocalFileAliasSpec(source.spec)
549
+ ? undefined
550
+ : npmPackageNameFromSpec(source.spec));
490
551
  if (!(await pathExists(packed.tarball))) {
491
552
  throw new PmCliError("npm pack did not create its reported package archive.", EXIT_CODE.GENERIC_FAILURE, {
492
553
  code: "npm_package_archive_missing",
@@ -994,4 +1055,4 @@ export const _testOnlyInstallSources = {
994
1055
  runNpmCommand,
995
1056
  };
996
1057
  //# sourceMappingURL=install-sources.js.map
997
- //# debugId=404342eb-68ab-5b7d-9a5a-ade1c8e98546
1058
+ //# debugId=05f56edb-c08d-570c-b8b5-641fc694331e