@unbrained/pm-cli 2026.8.21 → 2026.8.22

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 (158) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +16 -0
  3. package/dist/cli/error-guidance.d.ts +9 -4
  4. package/dist/cli/error-guidance.js +16 -15
  5. package/dist/cli/main.d.ts +3 -0
  6. package/dist/cli/main.js +40 -7
  7. package/dist/cli-bundle/bundle-manifest.json +153 -153
  8. package/dist/cli-bundle/chunks/{chunk-BKYB45NQ.js → chunk-BWX4SK46.js} +48 -48
  9. package/dist/cli-bundle/chunks/chunk-EMWDM5FI.js +13 -0
  10. package/dist/cli-bundle/chunks/chunk-HYG6LRL4.js +200 -0
  11. package/dist/cli-bundle/chunks/{chunk-LARJJ3RK.js → chunk-MYYI3ZOA.js} +2 -2
  12. package/dist/cli-bundle/chunks/{chunk-BUNIEJNE.js → chunk-MZTZQTMS.js} +2 -2
  13. package/dist/cli-bundle/chunks/{chunk-LOVTQ52Z.js → chunk-QI2CT3IX.js} +2 -2
  14. package/dist/cli-bundle/chunks/chunk-SMWXHMVB.js +35 -0
  15. package/dist/cli-bundle/chunks/{chunk-N3DSV4CH.js → chunk-U6GQ4VMV.js} +6 -6
  16. package/dist/cli-bundle/chunks/{chunk-3AFAUHUE.js → chunk-VMMIL3SI.js} +2 -2
  17. package/dist/cli-bundle/chunks/chunk-WG4STC36.js +5 -0
  18. package/dist/cli-bundle/chunks/{chunk-JSLOFXGA.js → chunk-YBPJAOQQ.js} +2 -2
  19. package/dist/cli-bundle/chunks/{register-list-query-F57EWDEF.js → register-list-query-HZKBQNWW.js} +2 -2
  20. package/dist/cli-bundle/chunks/{register-mutation-7IY2D5KO.js → register-mutation-HTKEEGRD.js} +2 -2
  21. package/dist/cli-bundle/chunks/{register-operations-FALL3YTU.js → register-operations-6EMRWJJY.js} +2 -2
  22. package/dist/cli-bundle/chunks/{register-setup-HXU7IZZD.js → register-setup-QK4OTOXN.js} +2 -2
  23. package/dist/cli-bundle/focused-chunks/{chunk-FJ22HNRP.js → chunk-2ARQGDDW.js} +2 -2
  24. package/dist/cli-bundle/focused-chunks/{chunk-CKKQSJNN.js → chunk-2AZT5WXZ.js} +2 -2
  25. package/dist/cli-bundle/focused-chunks/{chunk-F4YEW5I2.js → chunk-2POVTNLY.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/chunk-3LKU6PTJ.js +4 -0
  27. package/dist/cli-bundle/focused-chunks/chunk-5SHSBMX2.js +2 -0
  28. package/dist/cli-bundle/focused-chunks/{chunk-FH6DYED3.js → chunk-6S2MO3Q3.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/{chunk-UZE7XUZK.js → chunk-A2BC5PQL.js} +2 -2
  30. package/dist/cli-bundle/focused-chunks/chunk-C4S4A4JJ.js +5 -0
  31. package/dist/cli-bundle/focused-chunks/{chunk-KDNG7TKQ.js → chunk-D6RBZEJJ.js} +2 -2
  32. package/dist/cli-bundle/focused-chunks/chunk-E2EWYPY6.js +16 -0
  33. package/dist/cli-bundle/focused-chunks/{chunk-4ERLO7QD.js → chunk-GMYPD4FX.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/chunk-L5LDSRO2.js +155 -0
  35. package/dist/cli-bundle/focused-chunks/{chunk-ZOSEELYJ.js → chunk-MB37AYN6.js} +2 -2
  36. package/dist/cli-bundle/focused-chunks/chunk-MCOR37QK.js +2 -0
  37. package/dist/cli-bundle/focused-chunks/{chunk-2NIY5NKG.js → chunk-PH2OXXVZ.js} +2 -2
  38. package/dist/cli-bundle/focused-chunks/{chunk-YKBKIEAJ.js → chunk-PMGJUFNQ.js} +2 -2
  39. package/dist/cli-bundle/focused-chunks/chunk-R6SR45XU.js +50 -0
  40. package/dist/cli-bundle/focused-chunks/chunk-WZE4LIPW.js +8 -0
  41. package/dist/cli-bundle/main.js +13 -13
  42. package/dist/cli-bundle/sdk-authoring.js +1 -1
  43. package/dist/cli-bundle/sdk-contracts.js +2 -2
  44. package/dist/cli-bundle/sdk-core.js +40 -40
  45. package/dist/cli-bundle/sdk-governance.js +1 -1
  46. package/dist/cli-bundle/sdk-graph.js +1 -1
  47. package/dist/cli-bundle/sdk-merge.js +32 -32
  48. package/dist/cli-bundle/sdk-query.js +1 -1
  49. package/dist/cli-bundle/sdk-runtime.js +1 -1
  50. package/dist/cli-bundle/sdk-testing.js +1 -1
  51. package/dist/cli-bundle/sdk.js +5 -5
  52. package/dist/core/extensions/extension-hook-runtime.js +22 -20
  53. package/dist/core/extensions/extension-types.d.ts +8 -1
  54. package/dist/core/extensions/extension-types.js +2 -2
  55. package/dist/core/extensions/loader.js +7 -3
  56. package/dist/core/extensions/service-ownership.d.ts +8 -0
  57. package/dist/core/extensions/service-ownership.js +20 -0
  58. package/dist/core/item/id.js +20 -5
  59. package/dist/core/item/item-format.js +27 -2
  60. package/dist/core/shared/errors.d.ts +4 -0
  61. package/dist/core/shared/errors.js +2 -2
  62. package/dist/sdk/agent/refusal-reachability.d.ts +1 -1
  63. package/dist/sdk/authoring.d.ts +1 -1
  64. package/dist/sdk/authoring.js +2 -2
  65. package/dist/sdk/cli-contracts/agent-output-contracts.d.ts +77 -0
  66. package/dist/sdk/cli-contracts/agent-output-contracts.js +324 -2
  67. package/dist/sdk/cli-contracts/commander-mutation-options.js +3 -3
  68. package/dist/sdk/cli-contracts/runtime-contracts.d.ts +3 -1
  69. package/dist/sdk/cli-contracts/runtime-contracts.js +4 -3
  70. package/dist/sdk/cli-contracts.d.ts +1 -1
  71. package/dist/sdk/cli-contracts.js +3 -3
  72. package/dist/sdk/extension/install-sources.d.ts +1 -1
  73. package/dist/sdk/extension/install-sources.js +6 -3
  74. package/dist/sdk/extension/output-ownership.d.ts +1 -0
  75. package/dist/sdk/extension/output-ownership.js +3 -2
  76. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +38 -14
  77. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -2
  78. package/dist/sdk/governance/health.d.ts +3 -0
  79. package/dist/sdk/governance/health.js +39 -7
  80. package/dist/sdk/governance/hierarchy-validation.d.ts +29 -0
  81. package/dist/sdk/governance/hierarchy-validation.js +109 -0
  82. package/dist/sdk/governance/validate.d.ts +1 -7
  83. package/dist/sdk/governance/validate.js +47 -99
  84. package/dist/sdk/graph/assembly.d.ts +6 -1
  85. package/dist/sdk/graph/assembly.js +16 -22
  86. package/dist/sdk/graph/governance-contracts.d.ts +1 -1
  87. package/dist/sdk/graph/governance-contracts.js +8 -2
  88. package/dist/sdk/graph/governance.js +46 -2
  89. package/dist/sdk/graph/hierarchy-integrity.d.ts +94 -0
  90. package/dist/sdk/graph/hierarchy-integrity.js +311 -0
  91. package/dist/sdk/graph/index.d.ts +2 -1
  92. package/dist/sdk/graph/index.js +3 -2
  93. package/dist/sdk/graph/remediation.js +32 -2
  94. package/dist/sdk/graph/run.d.ts +31 -2
  95. package/dist/sdk/graph/run.js +80 -31
  96. package/dist/sdk/graph/workspace-relationship-kind-registry.d.ts +8 -0
  97. package/dist/sdk/graph/workspace-relationship-kind-registry.js +28 -0
  98. package/dist/sdk/index.d.ts +2 -2
  99. package/dist/sdk/index.js +3 -3
  100. package/dist/sdk/item-children.js +21 -8
  101. package/dist/sdk/lifecycle/create.js +125 -96
  102. package/dist/sdk/lifecycle/hierarchy-mutation-lock.d.ts +15 -0
  103. package/dist/sdk/lifecycle/hierarchy-mutation-lock.js +25 -0
  104. package/dist/sdk/lifecycle/update.d.ts +2 -0
  105. package/dist/sdk/lifecycle/update.js +171 -83
  106. package/dist/sdk/query/complete-list.d.ts +3 -3
  107. package/dist/sdk/query/complete-list.js +46 -3
  108. package/dist/sdk/query/list.d.ts +4 -3
  109. package/dist/sdk/query/list.js +89 -49
  110. package/dist/sdk/read-output-contracts.d.ts +5 -1
  111. package/dist/sdk/read-output-contracts.js +87 -22
  112. package/dist/sdk/relationship-context.js +3 -4
  113. package/dist/sdk/relationships.js +17 -10
  114. package/dist/sdk/runtime.d.ts +3 -2
  115. package/dist/sdk/runtime.js +3 -2
  116. package/dist/sdk/test/batch.js +13 -5
  117. package/dist/sdk/test/execution.js +10 -2
  118. package/dist/types.d.ts +11 -0
  119. package/dist/types.js +2 -2
  120. package/docs/AGENT_UX_CONTRACTS.md +20 -4
  121. package/docs/COMMANDS.md +29 -14
  122. package/docs/DEPENDENCY_KIND_CONTRACT.md +31 -3
  123. package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +98 -0
  124. package/docs/EXTENSIONS.md +2 -1
  125. package/docs/README.md +1 -0
  126. package/docs/READ_OUTPUT_CONTRACTS.md +9 -2
  127. package/docs/RELATIONSHIP_GRAPH.md +6 -3
  128. package/docs/TESTING.md +15 -0
  129. package/marketplace.json +2 -2
  130. package/package.json +1 -1
  131. package/packages/pm-beads/package.json +1 -1
  132. package/packages/pm-calendar/package.json +1 -1
  133. package/packages/pm-command-kit/package.json +1 -1
  134. package/packages/pm-digital-twin/package.json +1 -1
  135. package/packages/pm-governance-audit/package.json +1 -1
  136. package/packages/pm-guide-shell/package.json +1 -1
  137. package/packages/pm-kanban/package.json +1 -1
  138. package/packages/pm-lifecycle-hooks/package.json +1 -1
  139. package/packages/pm-linked-test-adapters/package.json +1 -1
  140. package/packages/pm-search-advanced/package.json +1 -1
  141. package/packages/pm-templates/package.json +1 -1
  142. package/packages/pm-todos/package.json +1 -1
  143. package/packages/pm-vcs/package.json +1 -1
  144. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  145. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  146. package/sdk/public-surface.json +593 -35
  147. package/dist/cli-bundle/chunks/chunk-2MA6ZQVZ.js +0 -197
  148. package/dist/cli-bundle/chunks/chunk-7VHZS727.js +0 -5
  149. package/dist/cli-bundle/chunks/chunk-OCLPLFAY.js +0 -35
  150. package/dist/cli-bundle/chunks/chunk-S4IRYKBU.js +0 -13
  151. package/dist/cli-bundle/focused-chunks/chunk-7WZRLCYF.js +0 -50
  152. package/dist/cli-bundle/focused-chunks/chunk-EEFKYMDR.js +0 -16
  153. package/dist/cli-bundle/focused-chunks/chunk-F2A56DHV.js +0 -8
  154. package/dist/cli-bundle/focused-chunks/chunk-FV5ACOK7.js +0 -2
  155. package/dist/cli-bundle/focused-chunks/chunk-JEAVFOWM.js +0 -2
  156. package/dist/cli-bundle/focused-chunks/chunk-NJ63LOP6.js +0 -155
  157. package/dist/cli-bundle/focused-chunks/chunk-NXRTQ7HI.js +0 -2
  158. package/dist/cli-bundle/focused-chunks/chunk-TJ7EMJ46.js +0 -4
@@ -7,6 +7,7 @@ import { type HistoryAuthorAttributionScan } from "../author-attribution.js";
7
7
  import type { PmSettings } from "../../types/index.js";
8
8
  import { buildCapabilityContractMetadata, collectUnknownCapabilityGuidance } from "../extension/doctor.js";
9
9
  import { type StaleInProgressScan } from "./stale-work.js";
10
+ import { type HierarchyIntegrityAnalysis } from "../graph/hierarchy-integrity.js";
10
11
  type HealthStatus = "ok" | "warn";
11
12
  type MigrationRuntimeStatus = "pending" | "failed" | "applied";
12
13
  /** Documents the health check payload exchanged by command, SDK, and package integrations. */
@@ -145,6 +146,7 @@ declare function normalizeExtensionNameForMatch(value: string): string;
145
146
  declare function isExpectedUnmanagedExtension(name: string, directory: string): boolean;
146
147
  declare function isDirectory(targetPath: string): Promise<boolean>;
147
148
  declare function listItemDocumentPaths(pmRoot: string, typeToFolder: Record<string, string>): Promise<string[]>;
149
+ declare function buildHierarchyIntegrityWarnings(analysis: HierarchyIntegrityAnalysis): string[];
148
150
  declare function buildExtensionHealthTriageSummary(warnings: string[], loadFailureCount: number, activationFailureCount: number, migrationStatus: MigrationStatusSummary, managedStateWarningCount: number, managedExtensionEntriesCount: number, unmanagedLoadedExtensions: string[], unmanagedExpectedExtensions: string[], unmanagedActionRequiredExtensions: string[]): ExtensionHealthTriageSummary;
149
151
  declare function summarizeRecordList(value: unknown, limit: number): {
150
152
  count: number;
@@ -218,6 +220,7 @@ declare function projectHealthResult(result: HealthResult, options: RunHealthOpt
218
220
  export declare function runHealth(global: GlobalOptions, options?: RunHealthOptions): Promise<HealthResult>;
219
221
  /** Public contract for test only health command, shared by SDK and presentation-layer consumers. */
220
222
  export declare const _testOnlyHealthCommand: {
223
+ buildHierarchyIntegrityWarnings: typeof buildHierarchyIntegrityWarnings;
221
224
  buildExtensionHealthTriageSummary: typeof buildExtensionHealthTriageSummary;
222
225
  buildHealthFindings: typeof buildHealthFindings;
223
226
  buildCapabilityContractMetadata: typeof buildCapabilityContractMetadata;
@@ -4,11 +4,13 @@
4
4
  * Implements the pm health command surface and its agent-facing runtime behavior.
5
5
  */
6
6
 
7
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a7e3a41b-7d69-526d-aa3e-bc244ef8fe0c")}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]="f7b3ff92-1fc7-55e9-aa76-af956714998f")}catch(e){}}();
8
8
  import fs from "node:fs/promises";
9
9
  import path from "node:path";
10
10
  import { assertInitializedTracker } from "../environment/tracker-preflight.js";
11
+ import { isTerminalStatus } from "../../core/item/status.js";
11
12
  import { resolveItemTypeRegistry } from "../../core/item/type-registry.js";
13
+ import { resolveRuntimeStatusRegistry } from "../../core/schema/runtime-schema.js";
12
14
  import { isFileMissingError, readFileIfExists, } from "../../core/fs/fs-utils.js";
13
15
  import { activateExtensions, getActiveExtensionRegistrations, loadExtensions, runActiveOnReadHooks, } from "../../core/extensions/index.js";
14
16
  import { collectRegisteredItemFieldNames } from "../../core/extensions/item-fields.js";
@@ -44,6 +46,8 @@ import { scanStaleInProgressItems, } from "./stale-work.js";
44
46
  import { scanTrackedRuntimeCache } from "./tracked-runtime-cache.js";
45
47
  import { auditMergeDriverConfiguration, findGitWorkspaceRoot, } from "../merge/install.js";
46
48
  import { listMergeReceipts, partitionMergeReceipts, } from "../merge/receipts.js";
49
+ import { resolveWorkspaceRelationshipKindRegistry } from "../graph/assembly.js";
50
+ import { analyzeHierarchyIntegrity, } from "../graph/hierarchy-integrity.js";
47
51
  const PM_TELEMETRY_SOURCE_CONTEXT_SET = new Set(PM_TELEMETRY_SOURCE_CONTEXT_VALUES);
48
52
  const STALE_VECTORIZATION_SUMMARY_LIMIT = 25;
49
53
  const BRIEF_HEALTH_DETAIL_LIMIT = 8;
@@ -61,6 +65,7 @@ const TELEMETRY_SERVER_MAX_SCHEMA_VERSION_HEADERS = [
61
65
  function isAdvisoryHealthWarning(warning, requireMergeDrivers = false) {
62
66
  return (warning.startsWith("telemetry_") ||
63
67
  warning.startsWith("history_stream_over_compact_threshold:") ||
68
+ warning.startsWith("integrity_legacy_hierarchy_") ||
64
69
  warning.startsWith("stale_in_progress_items:") ||
65
70
  warning.startsWith("provenance_resolver_zero_success:") ||
66
71
  warning.startsWith("provenance_value_domain_invalid:") ||
@@ -315,10 +320,25 @@ async function scanHistoryIntegrity(pmRoot) {
315
320
  }
316
321
  return { files, unreadable, conflictMarkers, invalidJson };
317
322
  }
318
- async function buildIntegrityCheck(pmRoot, typeToFolder, schema, requireMergeDrivers) {
323
+ function buildHierarchyIntegrityWarnings(analysis) {
324
+ return [
325
+ ...analysis.cycles.map((cycle) => cycle.legacy_terminal
326
+ ? `integrity_legacy_hierarchy_cycle:${cycle.item_ids.join(",")}`
327
+ : `integrity_hierarchy_cycle:${cycle.item_ids.join(",")}`),
328
+ ...analysis.cardinality_violations.map((finding) => finding.legacy_terminal
329
+ ? `integrity_legacy_hierarchy_cardinality:${finding.child_id}`
330
+ : `integrity_hierarchy_cardinality:${finding.child_id}`),
331
+ ...analysis.divergences.map((finding) => finding.legacy_terminal
332
+ ? `integrity_legacy_hierarchy_divergence:${finding.child_id}`
333
+ : `integrity_hierarchy_divergence:${finding.child_id}`),
334
+ ];
335
+ }
336
+ async function buildIntegrityCheck(pmRoot, typeToFolder, schema, requireMergeDrivers, items) {
319
337
  const itemScan = await scanItemIntegrity(pmRoot, typeToFolder, schema);
320
338
  const historyScan = await scanHistoryIntegrity(pmRoot);
321
339
  const formatVersionScan = scanItemFormatVersions(itemScan.formatVersions);
340
+ const statusRegistry = resolveRuntimeStatusRegistry(schema);
341
+ const hierarchyIntegrity = analyzeHierarchyIntegrity(items, (status) => isTerminalStatus(status, statusRegistry), resolveWorkspaceRelationshipKindRegistry());
322
342
  const trackedRuntimeCache = await scanTrackedRuntimeCache(pmRoot);
323
343
  const gitWorkspaceRoot = await findGitWorkspaceRoot(pmRoot);
324
344
  const mergeDriverAudit = gitWorkspaceRoot === null
@@ -345,6 +365,7 @@ async function buildIntegrityCheck(pmRoot, typeToFolder, schema, requireMergeDri
345
365
  ...formatVersionScan.outdated.map((entry) => `integrity_item_outdated_format_version:${entry}`),
346
366
  /* c8 ignore stop */
347
367
  ...formatVersionScan.ahead.map((entry) => `integrity_item_ahead_format_version:${entry}`),
368
+ ...buildHierarchyIntegrityWarnings(hierarchyIntegrity),
348
369
  ...(trackedRuntimeCache.tracked_path_count > 0
349
370
  ? [
350
371
  `tracked_runtime_cache_files:${trackedRuntimeCache.tracked_path_count}`,
@@ -360,11 +381,12 @@ async function buildIntegrityCheck(pmRoot, typeToFolder, schema, requireMergeDri
360
381
  : []),
361
382
  ];
362
383
  const normalizedWarnings = [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
384
+ const actionableWarnings = normalizedWarnings.filter((warning) => !warning.startsWith("integrity_legacy_hierarchy_"));
363
385
  return {
364
386
  check: {
365
387
  name: "integrity",
366
- status: normalizedWarnings.length === 0 ? "ok" : "warn",
367
- ok: normalizedWarnings.length === 0,
388
+ status: actionableWarnings.length === 0 ? "ok" : "warn",
389
+ ok: actionableWarnings.length === 0,
368
390
  details: {
369
391
  checked_item_files: itemScan.paths.length,
370
392
  checked_history_streams: historyScan.files.length,
@@ -377,6 +399,9 @@ async function buildIntegrityCheck(pmRoot, typeToFolder, schema, requireMergeDri
377
399
  history_invalid_json: historyScan.invalidJson.length,
378
400
  item_outdated_format_version: formatVersionScan.outdated.length,
379
401
  item_ahead_format_version: formatVersionScan.ahead.length,
402
+ hierarchy_cycles: hierarchyIntegrity.cycles.length,
403
+ hierarchy_cardinality_violations: hierarchyIntegrity.cardinality_violations.length,
404
+ hierarchy_parent_divergences: hierarchyIntegrity.divergences.length,
380
405
  tracked_runtime_cache_files: trackedRuntimeCache.tracked_path_count,
381
406
  merge_driver_configuration: mergeDriverAudit === null || mergeDriverAudit.status === "ok"
382
407
  ? 0
@@ -390,6 +415,10 @@ async function buildIntegrityCheck(pmRoot, typeToFolder, schema, requireMergeDri
390
415
  item_parse_failures: itemScan.parseFailures,
391
416
  item_outdated_format_version: formatVersionScan.outdated,
392
417
  item_ahead_format_version: formatVersionScan.ahead,
418
+ hierarchy_integrity: {
419
+ ...hierarchyIntegrity,
420
+ relations: summarizeRecordList(hierarchyIntegrity.relations, BRIEF_HEALTH_DETAIL_LIMIT),
421
+ },
393
422
  history_unreadable: historyScan.unreadable,
394
423
  history_conflict_markers: historyScan.conflictMarkers,
395
424
  history_invalid_json: historyScan.invalidJson,
@@ -1832,7 +1861,9 @@ function extractHistoryDriftedCount(historyDriftCheck) {
1832
1861
  function buildHealthRemediationSources(params) {
1833
1862
  return {
1834
1863
  settings: params.normalizedSettingsReadWarnings,
1835
- directories: params.directoryState.missingDirs.map((dir) => `missing_directory:${dir}`).concat(params.directoryState.hookWarnings),
1864
+ directories: params.directoryState.missingDirs
1865
+ .map((dir) => `missing_directory:${dir}`)
1866
+ .concat(params.directoryState.hookWarnings),
1836
1867
  settings_values: params.settingWarnings,
1837
1868
  telemetry: params.telemetryCheck.warnings,
1838
1869
  extensions: params.extensionCheck.warnings,
@@ -2000,7 +2031,7 @@ export async function runHealth(global, options = {}) {
2000
2031
  const locksCheck = await buildLocksCheck(pmRoot);
2001
2032
  const integrityCheck = skipPolicy.skipIntegrity
2002
2033
  ? buildSkippedHealthCheck("integrity")
2003
- : await buildIntegrityCheck(pmRoot, typeRegistry.type_to_folder, settings.schema, options.requireMergeDrivers === true);
2034
+ : await buildIntegrityCheck(pmRoot, typeRegistry.type_to_folder, settings.schema, options.requireMergeDrivers === true, items);
2004
2035
  const historyDriftCheck = skipPolicy.skipDrift
2005
2036
  ? buildSkippedHealthCheck("history_drift")
2006
2037
  : await buildHistoryDriftCheck(pmRoot, itemsWithBody);
@@ -2082,6 +2113,7 @@ export async function runHealth(global, options = {}) {
2082
2113
  }
2083
2114
  /** Public contract for test only health command, shared by SDK and presentation-layer consumers. */
2084
2115
  export const _testOnlyHealthCommand = {
2116
+ buildHierarchyIntegrityWarnings,
2085
2117
  buildExtensionHealthTriageSummary,
2086
2118
  buildHealthFindings,
2087
2119
  buildCapabilityContractMetadata,
@@ -2110,4 +2142,4 @@ export const _testOnlyHealthCommand = {
2110
2142
  warningCode,
2111
2143
  };
2112
2144
  //# sourceMappingURL=health.js.map
2113
- //# debugId=a7e3a41b-7d69-526d-aa3e-bc244ef8fe0c
2145
+ //# debugId=f7b3ff92-1fc7-55e9-aa76-af956714998f
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @module sdk/governance/hierarchy-validation
3
+ *
4
+ * Adapts the shared hierarchy analyzer to validation-specific diagnostics and
5
+ * deterministic cycle paths without coupling the analyzer to CLI policy.
6
+ */
7
+ import { type RuntimeStatusRegistry } from "../../core/schema/runtime-schema.js";
8
+ import type { RelationshipKindRegistry } from "../relationships.js";
9
+ import type { ValidateItem } from "./validate-item-reader.js";
10
+ /** Resolve one deterministic closed walk through a known cycle component. */
11
+ export declare function resolveLifecycleDependencyCycleSamplePath(component: string[], graph: Map<string, string[]>): string[];
12
+ /** Build canonical child-to-parent adjacency across every hierarchy spelling. */
13
+ export declare function buildLifecycleParentGraph(items: ValidateItem[], relationshipRegistry?: RelationshipKindRegistry): Map<string, string[]>;
14
+ /** Project shared hierarchy analysis into the validation result contract. */
15
+ export declare function detectLifecycleParentCycles(items: ValidateItem[], statusRegistry?: RuntimeStatusRegistry, relationshipRegistry?: RelationshipKindRegistry): {
16
+ cycle_count: number;
17
+ active_cycle_count: number;
18
+ legacy_cycle_count: number;
19
+ cycle_item_ids: string[];
20
+ cycle_sample_paths: string[];
21
+ cardinality_violation_count: number;
22
+ active_cardinality_violation_count: number;
23
+ legacy_cardinality_violation_count: number;
24
+ cardinality_violation_rows: string[];
25
+ parent_divergence_count: number;
26
+ active_parent_divergence_count: number;
27
+ legacy_parent_divergence_count: number;
28
+ parent_divergence_rows: string[];
29
+ };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @module sdk/governance/hierarchy-validation
3
+ *
4
+ * Adapts the shared hierarchy analyzer to validation-specific diagnostics and
5
+ * deterministic cycle paths without coupling the analyzer to CLI policy.
6
+ */
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]="77a3c404-bbcd-5a5e-80bf-cb61c45af55d")}catch(e){}}();
9
+ import { statusIsTerminal, } from "../../core/schema/runtime-schema.js";
10
+ import { resolveWorkspaceRelationshipKindRegistry } from "../graph/assembly.js";
11
+ import { analyzeHierarchyIntegrity } from "../graph/hierarchy-integrity.js";
12
+ /** Resolve one deterministic closed walk through a known cycle component. */
13
+ /* c8 ignore start -- fallback branches are covered by lifecycle graph integration tests */
14
+ export function resolveLifecycleDependencyCycleSamplePath(component, graph) {
15
+ const start = component[0];
16
+ if (component.length === 1)
17
+ return [start, start];
18
+ const componentSet = new Set(component);
19
+ const path = [start];
20
+ const visited = new Set([start]);
21
+ const search = (current) => {
22
+ for (const next of (graph.get(current) ?? []).filter((candidate) => componentSet.has(candidate))) {
23
+ if (next === start && path.length > 1) {
24
+ path.push(start);
25
+ return true;
26
+ }
27
+ if (visited.has(next))
28
+ continue;
29
+ visited.add(next);
30
+ path.push(next);
31
+ if (search(next))
32
+ return true;
33
+ path.pop();
34
+ visited.delete(next);
35
+ }
36
+ return false;
37
+ };
38
+ return search(start) ? [...path] : [...component, start];
39
+ }
40
+ /* c8 ignore stop */
41
+ /** Resolve a scalar parent spelling to the workspace's canonical identifier. */
42
+ function resolveCanonicalScalarParent(parent, canonicalIdByLowercase) {
43
+ if (typeof parent !== "string")
44
+ return undefined;
45
+ const normalized = parent.trim().toLowerCase();
46
+ return normalized.length > 0
47
+ ? canonicalIdByLowercase.get(normalized)
48
+ : undefined;
49
+ }
50
+ /** Append dependency-backed child-to-parent adjacency for one holder. */
51
+ function appendDependencyHierarchyEdges(item, graph, canonicalIdByLowercase, relationshipRegistry) {
52
+ for (const dependency of item.dependencies ?? []) {
53
+ if (typeof dependency !== "object" ||
54
+ dependency === null ||
55
+ typeof dependency.id !== "string")
56
+ continue;
57
+ const definition = relationshipRegistry.resolve(dependency.kind);
58
+ if (!definition?.hierarchy)
59
+ continue;
60
+ const target = canonicalIdByLowercase.get(dependency.id.toLowerCase());
61
+ if (!target)
62
+ continue;
63
+ if (definition.hierarchyDirection === "source_parent")
64
+ graph.get(target).push(item.id);
65
+ else
66
+ graph.get(item.id).push(target);
67
+ }
68
+ }
69
+ /** Build canonical child-to-parent adjacency across every hierarchy spelling. */
70
+ export function buildLifecycleParentGraph(items, relationshipRegistry = resolveWorkspaceRelationshipKindRegistry()) {
71
+ const canonicalIdByLowercase = new Map(items.map((item) => [item.id.toLowerCase(), item.id]));
72
+ const graph = new Map(items.map((item) => [item.id, []]));
73
+ for (const item of [...items].sort((left, right) => left.id.localeCompare(right.id))) {
74
+ const edges = graph.get(item.id);
75
+ const canonicalParentId = resolveCanonicalScalarParent(item.parent, canonicalIdByLowercase);
76
+ if (canonicalParentId)
77
+ edges.push(canonicalParentId);
78
+ appendDependencyHierarchyEdges(item, graph, canonicalIdByLowercase, relationshipRegistry);
79
+ }
80
+ for (const [id, edges] of graph) {
81
+ graph.set(id, [...new Set(edges)].sort());
82
+ }
83
+ return graph;
84
+ }
85
+ /** Project shared hierarchy analysis into the validation result contract. */
86
+ export function detectLifecycleParentCycles(items, statusRegistry, relationshipRegistry = resolveWorkspaceRelationshipKindRegistry()) {
87
+ const hierarchy = analyzeHierarchyIntegrity(items, (status) => statusRegistry
88
+ ? statusIsTerminal(status, statusRegistry)
89
+ : status === "closed" || status === "canceled", relationshipRegistry);
90
+ const graph = buildLifecycleParentGraph(items, relationshipRegistry);
91
+ const cycleComponents = hierarchy.cycles.map((cycle) => cycle.item_ids);
92
+ return {
93
+ cycle_count: cycleComponents.length,
94
+ active_cycle_count: hierarchy.cycles.filter((cycle) => !cycle.legacy_terminal).length,
95
+ legacy_cycle_count: hierarchy.cycles.filter((cycle) => cycle.legacy_terminal).length,
96
+ cycle_item_ids: [...new Set(cycleComponents.flat())].sort(),
97
+ cycle_sample_paths: cycleComponents.map((component) => resolveLifecycleDependencyCycleSamplePath(component, graph).join("->")),
98
+ cardinality_violation_count: hierarchy.cardinality_violations.length,
99
+ active_cardinality_violation_count: hierarchy.cardinality_violations.filter((finding) => !finding.legacy_terminal).length,
100
+ legacy_cardinality_violation_count: hierarchy.cardinality_violations.filter((finding) => finding.legacy_terminal).length,
101
+ cardinality_violation_rows: hierarchy.cardinality_violations.map((finding) => `${finding.child_id}:${finding.parent_ids.join(",")}:${finding.legacy_terminal ? "legacy" : "active"}`),
102
+ parent_divergence_count: hierarchy.divergences.length,
103
+ active_parent_divergence_count: hierarchy.divergences.filter((finding) => !finding.legacy_terminal).length,
104
+ legacy_parent_divergence_count: hierarchy.divergences.filter((finding) => finding.legacy_terminal).length,
105
+ parent_divergence_rows: hierarchy.divergences.map((finding) => `${finding.child_id}:scalar=${finding.scalar_parent_id}:dependencies=${finding.dependency_parent_ids.join(",")}:${finding.legacy_terminal ? "legacy" : "active"}`),
106
+ };
107
+ }
108
+ //# sourceMappingURL=hierarchy-validation.js.map
109
+ //# debugId=77a3c404-bbcd-5a5e-80bf-cb61c45af55d
@@ -7,6 +7,7 @@ import type { ValidateMetadataProfile, ValidateMetadataRequiredField } from "../
7
7
  import { type OutputProjectionDeclaration } from "../output-projection.js";
8
8
  import { type RelationshipKindRegistry } from "../relationships.js";
9
9
  import { type ValidateItem } from "./validate-item-reader.js";
10
+ import { buildLifecycleParentGraph, detectLifecycleParentCycles, resolveLifecycleDependencyCycleSamplePath } from "./hierarchy-validation.js";
10
11
  type ValidateCheckName = "metadata" | "resolution" | "lifecycle" | "dependency_references" | "files" | "command_references" | "history_drift" | "format_version" | "storage_integrity";
11
12
  type ValidateStatus = "ok" | "warn" | "error";
12
13
  type ValidateDependencyCycleSeverity = "off" | "warn" | "error";
@@ -198,18 +199,11 @@ declare function buildDependencyReferencesCheck(items: ItemWithBody[], verboseDi
198
199
  };
199
200
  declare function extractItemIds(value: string, idPrefix?: string): string[];
200
201
  declare function findLifecycleDependencyCycleComponents(graph: Map<string, string[]>): string[][];
201
- declare function resolveLifecycleDependencyCycleSamplePath(component: string[], graph: Map<string, string[]>): string[];
202
202
  declare function detectLifecycleDependencyCycles(activeItems: ItemWithBody[], idPrefix?: string): {
203
203
  cycle_count: number;
204
204
  cycle_item_ids: string[];
205
205
  cycle_sample_paths: string[];
206
206
  };
207
- declare function buildLifecycleParentGraph(items: ItemWithBody[], relationshipRegistry?: RelationshipKindRegistry): Map<string, string[]>;
208
- declare function detectLifecycleParentCycles(items: ItemWithBody[]): {
209
- cycle_count: number;
210
- cycle_item_ids: string[];
211
- cycle_sample_paths: string[];
212
- };
213
207
  interface OrphanedPathRow {
214
208
  path: string;
215
209
  classification: OrphanedPathClassification;
@@ -4,7 +4,7 @@
4
4
  * Implements the pm validate command surface and its agent-facing runtime behavior.
5
5
  */
6
6
 
7
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1ed6b663-882a-5936-bd89-46bbbb17d806")}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]="87e3932d-26f0-5d44-ac35-5d0d7d1f4467")}catch(e){}}();
8
8
  import { assertInitializedTracker } from "../environment/tracker-preflight.js";
9
9
  import fs from "node:fs/promises";
10
10
  import { realpathSync } from "node:fs";
@@ -44,6 +44,7 @@ import { runFiles } from "../files.js";
44
44
  import { extractReferencedPmItemIdsFromCommand } from "../test/linked-command-detection.js";
45
45
  import { readValidateItems, } from "./validate-item-reader.js";
46
46
  import { buildValidateHistoryDriftCheck } from "./validate-history-drift.js";
47
+ import { buildLifecycleParentGraph, detectLifecycleParentCycles, resolveLifecycleDependencyCycleSamplePath, } from "./hierarchy-validation.js";
47
48
  const FILE_SCAN_DIRECTORIES = ["src", "tests", "docs"];
48
49
  const FILE_SCAN_ROOT_FILES = [
49
50
  "README.md",
@@ -1107,41 +1108,6 @@ function findLifecycleDependencyCycleComponents(graph) {
1107
1108
  left.join(",").localeCompare(right.join(",")));
1108
1109
  }
1109
1110
  /* c8 ignore stop */
1110
- /* c8 ignore start -- cycle sample-path fallback branches are covered by lifecycle graph integration tests */
1111
- function resolveLifecycleDependencyCycleSamplePath(component, graph) {
1112
- const start = component[0];
1113
- if (component.length === 1) {
1114
- return [start, start];
1115
- }
1116
- const componentSet = new Set(component);
1117
- const path = [start];
1118
- const visited = new Set([start]);
1119
- const search = (current) => {
1120
- const neighbors = (graph.get(current) ?? []).filter((candidate) => componentSet.has(candidate));
1121
- for (const next of neighbors) {
1122
- if (next === start && path.length > 1) {
1123
- path.push(start);
1124
- return true;
1125
- }
1126
- if (visited.has(next)) {
1127
- continue;
1128
- }
1129
- visited.add(next);
1130
- path.push(next);
1131
- if (search(next)) {
1132
- return true;
1133
- }
1134
- path.pop();
1135
- visited.delete(next);
1136
- }
1137
- return false;
1138
- };
1139
- if (search(start)) {
1140
- return [...path];
1141
- }
1142
- return [...component, start];
1143
- }
1144
- /* c8 ignore stop */
1145
1111
  function detectLifecycleDependencyCycles(activeItems, idPrefix = "pm") {
1146
1112
  const graph = buildLifecycleDependencyGraph(activeItems, idPrefix);
1147
1113
  const cycleComponents = findLifecycleDependencyCycleComponents(graph);
@@ -1153,62 +1119,6 @@ function detectLifecycleDependencyCycles(activeItems, idPrefix = "pm") {
1153
1119
  cycle_sample_paths: cycleSamplePaths,
1154
1120
  };
1155
1121
  }
1156
- // Parent (composition) cycle detection (pm-8vul / GH-280). The dependency-cycle
1157
- // path above only walks blocked_by/definition_of_ready edges across ACTIVE items;
1158
- // it never traverses item.parent, so a parent cycle (A.parent=B, B.parent=A, or
1159
- // any longer ring) goes undetected while it silently breaks `pm list --tree`,
1160
- // orphan detection, and progress rollups. We reuse the generic Tarjan SCC helper
1161
- // on a child->[parent] adjacency map. Unlike dependency cycles we scan ALL items
1162
- // (not just active ones) because a parent cycle among closed items is still
1163
- // structural corruption of the hierarchy.
1164
- function buildLifecycleParentGraph(items, relationshipRegistry = createRelationshipKindRegistry()) {
1165
- // PR #279 made parent matching case-insensitive (e.g. `parent: PM-FK49`
1166
- // resolves to `id: pm-fk49`). Resolve parent references to their canonical
1167
- // item id the same way so a casing mismatch can never silently drop a cycle
1168
- // edge and hide a parent cycle (false negative).
1169
- const canonicalIdByLowercase = new Map(items.map((item) => [item.id.toLowerCase(), item.id]));
1170
- const graph = new Map(items.map((item) => [item.id, []]));
1171
- const sortedItems = [...items].sort((left, right) => left.id.localeCompare(right.id));
1172
- sortedItems.forEach((item) => {
1173
- const edges = graph.get(item.id);
1174
- const parentId = toMeaningfulString(item.parent);
1175
- const canonicalParentId = parentId
1176
- ? canonicalIdByLowercase.get(parentId.toLowerCase())
1177
- : undefined;
1178
- if (canonicalParentId) {
1179
- edges.push(canonicalParentId);
1180
- }
1181
- (item.dependencies ?? []).forEach((dependency) => {
1182
- const definition = relationshipRegistry.resolve(dependency.kind);
1183
- if (!definition?.hierarchy)
1184
- return;
1185
- const target = canonicalIdByLowercase.get(dependency.id.toLowerCase());
1186
- if (!target)
1187
- return;
1188
- if (definition.hierarchyDirection === "source_parent") {
1189
- graph.get(target).push(item.id);
1190
- }
1191
- else {
1192
- edges.push(target);
1193
- }
1194
- });
1195
- });
1196
- graph.forEach((edges, id) => {
1197
- graph.set(id, [...new Set(edges)].sort((left, right) => left.localeCompare(right)));
1198
- });
1199
- return graph;
1200
- }
1201
- function detectLifecycleParentCycles(items) {
1202
- const graph = buildLifecycleParentGraph(items);
1203
- const cycleComponents = findLifecycleDependencyCycleComponents(graph);
1204
- const cycleItemIds = [...new Set(cycleComponents.flat())].sort((left, right) => left.localeCompare(right));
1205
- const cycleSamplePaths = cycleComponents.map((component) => resolveLifecycleDependencyCycleSamplePath(component, graph).join("->"));
1206
- return {
1207
- cycle_count: cycleComponents.length,
1208
- cycle_item_ids: cycleItemIds,
1209
- cycle_sample_paths: cycleSamplePaths,
1210
- };
1211
- }
1212
1122
  function classifyOrphanedPath(pathValue) {
1213
1123
  if (pathValue.startsWith("docs/")) {
1214
1124
  return "docs_unowned";
@@ -1435,7 +1345,10 @@ function lifecycleCycleWarningToken(prefix, severity, count) {
1435
1345
  }
1436
1346
  return `${severity === "error" ? `${prefix}_error` : prefix}:${count}`;
1437
1347
  }
1438
- function buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, dependencyCycleCount, parentCycleCount) {
1348
+ function lifecycleAdvisoryWarningToken(prefix, severity, count) {
1349
+ return count > 0 && severity !== "off" ? `${prefix}:${count}` : null;
1350
+ }
1351
+ function buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, counts) {
1439
1352
  const warnings = [];
1440
1353
  if (rows.closureLikeRows.length > 0) {
1441
1354
  warnings.push(`validate_lifecycle_active_closure_like_metadata:${rows.closureLikeRows.length}`);
@@ -1446,12 +1359,22 @@ function buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSever
1446
1359
  if (includeStaleBlockers && rows.staleBlockerRows.length > 0) {
1447
1360
  warnings.push(`validate_lifecycle_stale_blockers:${rows.staleBlockerRows.length}`);
1448
1361
  }
1449
- const dependencyWarning = lifecycleCycleWarningToken("validate_lifecycle_dependency_cycles", dependencyCycleSeverity, dependencyCycleCount);
1450
- const parentWarning = lifecycleCycleWarningToken("validate_hierarchy_parent_cycle", parentCycleSeverity, parentCycleCount);
1362
+ const dependencyWarning = lifecycleCycleWarningToken("validate_lifecycle_dependency_cycles", dependencyCycleSeverity, counts.dependencyCycles);
1363
+ const parentWarning = lifecycleCycleWarningToken("validate_hierarchy_parent_cycle", parentCycleSeverity, counts.activeHierarchyCycles);
1364
+ const cardinalityWarning = lifecycleCycleWarningToken("validate_hierarchy_cardinality_violation", parentCycleSeverity, counts.activeHierarchyCardinality);
1365
+ const divergenceWarning = lifecycleCycleWarningToken("validate_hierarchy_parent_divergence", parentCycleSeverity, counts.activeHierarchyDivergence);
1366
+ const legacyCycleWarning = lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_parent_cycle", parentCycleSeverity, counts.legacyHierarchyCycles);
1367
+ const legacyCardinalityWarning = lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_cardinality_violation", parentCycleSeverity, counts.legacyHierarchyCardinality);
1368
+ const legacyDivergenceWarning = lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_parent_divergence", parentCycleSeverity, counts.legacyHierarchyDivergence);
1451
1369
  return [
1452
1370
  ...warnings,
1453
1371
  ...(dependencyWarning ? [dependencyWarning] : []),
1454
1372
  ...(parentWarning ? [parentWarning] : []),
1373
+ ...(cardinalityWarning ? [cardinalityWarning] : []),
1374
+ ...(divergenceWarning ? [divergenceWarning] : []),
1375
+ ...(legacyCycleWarning ? [legacyCycleWarning] : []),
1376
+ ...(legacyCardinalityWarning ? [legacyCardinalityWarning] : []),
1377
+ ...(legacyDivergenceWarning ? [legacyDivergenceWarning] : []),
1455
1378
  ];
1456
1379
  }
1457
1380
  function linkedArtifactPathExceedsFilesystemLimits(artifactPath) {
@@ -1557,8 +1480,16 @@ function buildLifecycleCheck(items, includeStaleBlockers, dependencyCycleSeverit
1557
1480
  const rows = collectLifecycleScanRows(items, includeStaleBlockers, statusRegistry, lifecyclePatternPolicy);
1558
1481
  sortLifecycleScanRows(rows);
1559
1482
  const dependencyCycleDiagnostics = detectLifecycleDependencyCycles(rows.activeItems, idPrefix);
1560
- const parentCycleDiagnostics = detectLifecycleParentCycles(items);
1561
- const warnings = buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, dependencyCycleDiagnostics.cycle_count, parentCycleDiagnostics.cycle_count);
1483
+ const parentCycleDiagnostics = detectLifecycleParentCycles(items, statusRegistry);
1484
+ const warnings = buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, {
1485
+ dependencyCycles: dependencyCycleDiagnostics.cycle_count,
1486
+ activeHierarchyCycles: parentCycleDiagnostics.active_cycle_count,
1487
+ activeHierarchyCardinality: parentCycleDiagnostics.active_cardinality_violation_count,
1488
+ activeHierarchyDivergence: parentCycleDiagnostics.active_parent_divergence_count,
1489
+ legacyHierarchyCycles: parentCycleDiagnostics.legacy_cycle_count,
1490
+ legacyHierarchyCardinality: parentCycleDiagnostics.legacy_cardinality_violation_count,
1491
+ legacyHierarchyDivergence: parentCycleDiagnostics.legacy_parent_divergence_count,
1492
+ });
1562
1493
  const diagnosticLimit = verboseDiagnostics
1563
1494
  ? Number.POSITIVE_INFINITY
1564
1495
  : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
@@ -1569,9 +1500,14 @@ function buildLifecycleCheck(items, includeStaleBlockers, dependencyCycleSeverit
1569
1500
  const summarizedDependencyCycleSamplePaths = summarizeList(dependencyCycleDiagnostics.cycle_sample_paths, diagnosticLimit);
1570
1501
  const summarizedParentCycleItemIds = summarizeList(parentCycleDiagnostics.cycle_item_ids, diagnosticLimit);
1571
1502
  const summarizedParentCycleSamplePaths = summarizeList(parentCycleDiagnostics.cycle_sample_paths, diagnosticLimit);
1503
+ const summarizedHierarchyCardinalityRows = summarizeList(parentCycleDiagnostics.cardinality_violation_rows, diagnosticLimit);
1504
+ const summarizedHierarchyDivergenceRows = summarizeList(parentCycleDiagnostics.parent_divergence_rows, diagnosticLimit);
1572
1505
  const hasErrorSeverityCycle = (dependencyCycleDiagnostics.cycle_count > 0 &&
1573
1506
  dependencyCycleSeverity === "error") ||
1574
- (parentCycleDiagnostics.cycle_count > 0 && parentCycleSeverity === "error");
1507
+ ((parentCycleDiagnostics.active_cycle_count > 0 ||
1508
+ parentCycleDiagnostics.active_cardinality_violation_count > 0 ||
1509
+ parentCycleDiagnostics.active_parent_divergence_count > 0) &&
1510
+ parentCycleSeverity === "error");
1575
1511
  return {
1576
1512
  check: {
1577
1513
  name: "lifecycle",
@@ -1602,11 +1538,23 @@ function buildLifecycleCheck(items, includeStaleBlockers, dependencyCycleSeverit
1602
1538
  dependency_cycle_sample_paths_truncated: summarizedDependencyCycleSamplePaths.truncated,
1603
1539
  parent_cycle_severity_policy: parentCycleSeverity,
1604
1540
  parent_cycle_count: parentCycleDiagnostics.cycle_count,
1541
+ active_parent_cycle_count: parentCycleDiagnostics.active_cycle_count,
1542
+ legacy_parent_cycle_count: parentCycleDiagnostics.legacy_cycle_count,
1605
1543
  parent_cycle_item_count: parentCycleDiagnostics.cycle_item_ids.length,
1606
1544
  parent_cycle_item_ids: summarizedParentCycleItemIds.values,
1607
1545
  parent_cycle_item_ids_truncated: summarizedParentCycleItemIds.truncated,
1608
1546
  parent_cycle_sample_paths: summarizedParentCycleSamplePaths.values,
1609
1547
  parent_cycle_sample_paths_truncated: summarizedParentCycleSamplePaths.truncated,
1548
+ hierarchy_cardinality_violation_count: parentCycleDiagnostics.cardinality_violation_count,
1549
+ active_hierarchy_cardinality_violation_count: parentCycleDiagnostics.active_cardinality_violation_count,
1550
+ legacy_hierarchy_cardinality_violation_count: parentCycleDiagnostics.legacy_cardinality_violation_count,
1551
+ hierarchy_cardinality_violation_rows: summarizedHierarchyCardinalityRows.values,
1552
+ hierarchy_cardinality_violation_rows_truncated: summarizedHierarchyCardinalityRows.truncated,
1553
+ hierarchy_parent_divergence_count: parentCycleDiagnostics.parent_divergence_count,
1554
+ active_hierarchy_parent_divergence_count: parentCycleDiagnostics.active_parent_divergence_count,
1555
+ legacy_hierarchy_parent_divergence_count: parentCycleDiagnostics.legacy_parent_divergence_count,
1556
+ hierarchy_parent_divergence_rows: summarizedHierarchyDivergenceRows.values,
1557
+ hierarchy_parent_divergence_rows_truncated: summarizedHierarchyDivergenceRows.truncated,
1610
1558
  stale_blocker_reason_patterns: [
1611
1559
  ...lifecyclePatternPolicy.stale_blocker_reason_patterns,
1612
1560
  ],
@@ -2307,4 +2255,4 @@ export async function runValidate(options, global, services = {}) {
2307
2255
  }
2308
2256
  /* c8 ignore stop */
2309
2257
  //# sourceMappingURL=validate.js.map
2310
- //# debugId=1ed6b663-882a-5936-bd89-46bbbb17d806
2258
+ //# debugId=87e3932d-26f0-5d44-ac35-5d0d7d1f4467
@@ -11,6 +11,8 @@
11
11
  */
12
12
  import type { Dependency, ItemStatus } from "../../types/index.js";
13
13
  import { RelationshipGraph, type RelationshipKindRegistry } from "../relationships.js";
14
+ import { type HierarchyIntegrityAnalysis } from "./hierarchy-integrity.js";
15
+ export { resolveWorkspaceRelationshipKindRegistry } from "./workspace-relationship-kind-registry.js";
14
16
  /** Minimal item shape inspected by dependency-reference governance. */
15
17
  export interface DependencyReferenceHolder {
16
18
  /** Stable item identifier. */
@@ -61,7 +63,6 @@ export declare function normalizeDependencyGraphTarget(target: unknown): string
61
63
  * share this resolution so custom ordering semantics apply identically on
62
64
  * every surface.
63
65
  */
64
- export declare function resolveWorkspaceRelationshipKindRegistry(): RelationshipKindRegistry;
65
66
  /** One raw stored dependency row duplicated verbatim on a single holder. */
66
67
  export interface DuplicateDependencyRow {
67
68
  /** Item that stores the duplicated row. */
@@ -152,7 +153,11 @@ export interface WorkspaceRelationshipAssembly {
152
153
  orderingContradictions?: OrderingStorageContradiction[];
153
154
  /** Raw legacy alias rows grouped by accepted spelling before graph canonicalization. */
154
155
  legacyAliasCounts: Record<string, number>;
156
+ /** Shared hierarchy analysis retained across ordinary object spread copies. */
157
+ hierarchyIntegrity?: HierarchyIntegrityAnalysis;
155
158
  }
159
+ /** Read the hierarchy analysis paired with an assembly created by this module. */
160
+ export declare function getWorkspaceHierarchyIntegrity(assembly: WorkspaceRelationshipAssembly): HierarchyIntegrityAnalysis | undefined;
156
161
  /** Count raw dependency rows that use a registered compatibility alias instead of its canonical kind. */
157
162
  export declare function collectLegacyDependencyAliasCounts(items: readonly DependencyReferenceHolder[], registry?: RelationshipKindRegistry): Record<string, number>;
158
163
  /**