@unbrained/pm-cli 2026.6.11 → 2026.6.12

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 (180) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/cli/commands/completion.js +20 -8
  3. package/dist/cli/commands/completion.js.map +1 -1
  4. package/dist/cli/commands/create.js +6 -3
  5. package/dist/cli/commands/create.js.map +1 -1
  6. package/dist/cli/commands/docs.d.ts +2 -0
  7. package/dist/cli/commands/docs.js +2 -2
  8. package/dist/cli/commands/docs.js.map +1 -1
  9. package/dist/cli/commands/files.d.ts +2 -0
  10. package/dist/cli/commands/files.js +2 -2
  11. package/dist/cli/commands/files.js.map +1 -1
  12. package/dist/cli/commands/health.d.ts +1 -1
  13. package/dist/cli/commands/health.js +77 -2
  14. package/dist/cli/commands/health.js.map +1 -1
  15. package/dist/cli/commands/history-repair.d.ts +36 -0
  16. package/dist/cli/commands/history-repair.js +68 -3
  17. package/dist/cli/commands/history-repair.js.map +1 -1
  18. package/dist/cli/commands/index.d.ts +1 -1
  19. package/dist/cli/commands/index.js +3 -3
  20. package/dist/cli/commands/index.js.map +1 -1
  21. package/dist/cli/commands/linked-artifacts.d.ts +18 -0
  22. package/dist/cli/commands/linked-artifacts.js +26 -3
  23. package/dist/cli/commands/linked-artifacts.js.map +1 -1
  24. package/dist/cli/commands/plan.d.ts +6 -0
  25. package/dist/cli/commands/plan.js +93 -32
  26. package/dist/cli/commands/plan.js.map +1 -1
  27. package/dist/cli/commands/validate.d.ts +20 -0
  28. package/dist/cli/commands/validate.js +292 -28
  29. package/dist/cli/commands/validate.js.map +1 -1
  30. package/dist/cli/help-content.js +7 -3
  31. package/dist/cli/help-content.js.map +1 -1
  32. package/dist/cli/register-mutation.js +47 -16
  33. package/dist/cli/register-mutation.js.map +1 -1
  34. package/dist/cli/register-operations.js +10 -2
  35. package/dist/cli/register-operations.js.map +1 -1
  36. package/dist/cli/register-setup.js +16 -16
  37. package/dist/cli/register-setup.js.map +1 -1
  38. package/dist/cli-bundle/chunks/{chunk-P2PSODAQ.js → chunk-35P4GT6E.js} +110 -9
  39. package/dist/cli-bundle/chunks/chunk-35P4GT6E.js.map +7 -0
  40. package/dist/cli-bundle/chunks/{chunk-B65UUCGQ.js → chunk-37CYVF73.js} +2 -2
  41. package/dist/cli-bundle/chunks/{chunk-J3GD64J2.js → chunk-7AMK6DOW.js} +4 -4
  42. package/dist/cli-bundle/chunks/{chunk-J3GD64J2.js.map → chunk-7AMK6DOW.js.map} +1 -1
  43. package/dist/cli-bundle/chunks/{chunk-IKOH24GZ.js → chunk-AX4GEVQC.js} +3 -3
  44. package/dist/cli-bundle/chunks/{chunk-IKOH24GZ.js.map → chunk-AX4GEVQC.js.map} +2 -2
  45. package/dist/cli-bundle/chunks/chunk-C6SAP27O.js +187 -0
  46. package/dist/cli-bundle/chunks/chunk-C6SAP27O.js.map +1 -0
  47. package/dist/cli-bundle/chunks/chunk-EKS6O2IR.js +217 -0
  48. package/dist/cli-bundle/chunks/chunk-EKS6O2IR.js.map +1 -0
  49. package/dist/cli-bundle/chunks/{chunk-PSD7F7DX.js → chunk-KBPQHXWG.js} +17 -4
  50. package/dist/cli-bundle/chunks/chunk-KBPQHXWG.js.map +1 -0
  51. package/dist/cli-bundle/chunks/{chunk-3SLOMKCJ.js → chunk-L5EPZQIC.js} +2 -2
  52. package/dist/cli-bundle/chunks/chunk-NAWVUHXH.js +220 -0
  53. package/dist/cli-bundle/chunks/chunk-NAWVUHXH.js.map +7 -0
  54. package/dist/cli-bundle/chunks/{chunk-UOO6BYIA.js → chunk-PNYAAB4S.js} +21 -188
  55. package/dist/cli-bundle/chunks/chunk-PNYAAB4S.js.map +1 -0
  56. package/dist/cli-bundle/chunks/chunk-R2OUQVBK.js +190 -0
  57. package/dist/cli-bundle/chunks/chunk-R2OUQVBK.js.map +7 -0
  58. package/dist/cli-bundle/chunks/{chunk-LCSCXBGT.js → chunk-R4VWYWRN.js} +19 -193
  59. package/dist/cli-bundle/chunks/chunk-R4VWYWRN.js.map +7 -0
  60. package/dist/cli-bundle/chunks/{chunk-7RIT3OCD.js → chunk-SO55HRA5.js} +2 -2
  61. package/dist/cli-bundle/chunks/{chunk-FXRU333M.js → chunk-UIG4TW2H.js} +110 -9
  62. package/dist/cli-bundle/chunks/chunk-UIG4TW2H.js.map +1 -0
  63. package/dist/cli-bundle/chunks/{chunk-E5DUIJA4.js → chunk-WPDMBIX6.js} +16 -3
  64. package/dist/cli-bundle/chunks/chunk-WPDMBIX6.js.map +7 -0
  65. package/dist/cli-bundle/chunks/{chunk-IH4MBKK7.js → chunk-XWMF3POR.js} +2 -2
  66. package/dist/cli-bundle/chunks/{contracts-3NKWYKKU.js → contracts-2G66IGIA.js} +2 -2
  67. package/dist/cli-bundle/chunks/{contracts-MIXBWJIB.js → contracts-PJCHYSO5.js} +2 -2
  68. package/dist/cli-bundle/chunks/{create-IFA26O2I.js → create-5433DP3G.js} +2 -2
  69. package/dist/cli-bundle/chunks/{create-JVXYHYH4.js → create-VWLWS34Z.js} +2 -2
  70. package/dist/cli-bundle/chunks/{docs-5WMBVQT2.js → docs-I3BAF5WH.js} +4 -4
  71. package/dist/cli-bundle/chunks/docs-I3BAF5WH.js.map +1 -0
  72. package/dist/cli-bundle/chunks/{docs-MB53UJAJ.js → docs-MXLGZUIT.js} +3 -3
  73. package/dist/cli-bundle/chunks/docs-MXLGZUIT.js.map +7 -0
  74. package/dist/cli-bundle/chunks/{files-GVMNVLCB.js → files-FYP3MDPX.js} +3 -3
  75. package/dist/cli-bundle/chunks/files-FYP3MDPX.js.map +7 -0
  76. package/dist/cli-bundle/chunks/{files-DLB5LJEP.js → files-GUXUM2MB.js} +4 -4
  77. package/dist/cli-bundle/chunks/files-GUXUM2MB.js.map +1 -0
  78. package/dist/cli-bundle/chunks/{gc-2WEPW6D6.js → gc-FENB2ESG.js} +12 -174
  79. package/dist/cli-bundle/chunks/gc-FENB2ESG.js.map +1 -0
  80. package/dist/cli-bundle/chunks/{gc-4Z3IKHAZ.js → gc-KGQG66SA.js} +10 -179
  81. package/dist/cli-bundle/chunks/gc-KGQG66SA.js.map +7 -0
  82. package/dist/cli-bundle/chunks/{health-YO5LRWN4.js → health-7MTXAVR2.js} +71 -4
  83. package/dist/cli-bundle/chunks/health-7MTXAVR2.js.map +7 -0
  84. package/dist/cli-bundle/chunks/{health-4ZYB3IYQ.js → health-E4D7KGHD.js} +72 -5
  85. package/dist/cli-bundle/chunks/health-E4D7KGHD.js.map +1 -0
  86. package/dist/cli-bundle/chunks/{history-repair-KVPFJKOS.js → history-repair-OSQOKJ3E.js} +61 -3
  87. package/dist/cli-bundle/chunks/history-repair-OSQOKJ3E.js.map +7 -0
  88. package/dist/cli-bundle/chunks/{history-repair-RUGKD3YR.js → history-repair-SBK7P34V.js} +62 -4
  89. package/dist/cli-bundle/chunks/history-repair-SBK7P34V.js.map +1 -0
  90. package/dist/cli-bundle/chunks/{plan-VNUG3WZZ.js → plan-2YTQYWGM.js} +52 -12
  91. package/dist/cli-bundle/chunks/plan-2YTQYWGM.js.map +1 -0
  92. package/dist/cli-bundle/chunks/{plan-FQLMJRLZ.js → plan-7IVSICR6.js} +51 -11
  93. package/dist/cli-bundle/chunks/plan-7IVSICR6.js.map +7 -0
  94. package/dist/cli-bundle/chunks/{register-list-query-VHDXWEPP.js → register-list-query-4UPMHONX.js} +3 -3
  95. package/dist/cli-bundle/chunks/{register-list-query-NPE3ECJQ.js → register-list-query-7VOW2C3W.js} +3 -3
  96. package/dist/cli-bundle/chunks/{register-mutation-EAYNLGOS.js → register-mutation-FARSGYRD.js} +43 -26
  97. package/dist/cli-bundle/chunks/{register-mutation-EAYNLGOS.js.map → register-mutation-FARSGYRD.js.map} +2 -2
  98. package/dist/cli-bundle/chunks/{register-mutation-DG53FCBV.js → register-mutation-LJDNVREU.js} +44 -27
  99. package/dist/cli-bundle/chunks/{register-mutation-DG53FCBV.js.map → register-mutation-LJDNVREU.js.map} +1 -1
  100. package/dist/cli-bundle/chunks/{register-operations-AM6KTWB6.js → register-operations-FFTYAL42.js} +17 -13
  101. package/dist/cli-bundle/chunks/register-operations-FFTYAL42.js.map +1 -0
  102. package/dist/cli-bundle/chunks/{register-operations-FWFE64JG.js → register-operations-HCJKDUA6.js} +16 -12
  103. package/dist/cli-bundle/chunks/{register-operations-FWFE64JG.js.map → register-operations-HCJKDUA6.js.map} +2 -2
  104. package/dist/cli-bundle/chunks/{register-setup-IKX5524L.js → register-setup-6AVIUOCC.js} +19 -19
  105. package/dist/cli-bundle/chunks/register-setup-6AVIUOCC.js.map +1 -0
  106. package/dist/cli-bundle/chunks/{register-setup-G4QYI3RX.js → register-setup-KVNLO5OJ.js} +18 -18
  107. package/dist/cli-bundle/chunks/register-setup-KVNLO5OJ.js.map +7 -0
  108. package/dist/cli-bundle/chunks/{update-2EVXOC7V.js → update-36JM7YZT.js} +3 -3
  109. package/dist/cli-bundle/chunks/{update-5A2B6QYB.js → update-I246PCLO.js} +3 -3
  110. package/dist/cli-bundle/chunks/{update-many-V4LA3C4H.js → update-many-F4IKDWZB.js} +3 -3
  111. package/dist/cli-bundle/chunks/{update-many-TUGU5YXQ.js → update-many-NTWZ5YT3.js} +3 -3
  112. package/dist/cli-bundle/chunks/{validate-EQTT6WVX.js → validate-46KCRHL7.js} +448 -36
  113. package/dist/cli-bundle/chunks/validate-46KCRHL7.js.map +1 -0
  114. package/dist/cli-bundle/chunks/{validate-XN4I4DPL.js → validate-CRFMPTYD.js} +468 -35
  115. package/dist/cli-bundle/chunks/validate-CRFMPTYD.js.map +7 -0
  116. package/dist/cli-bundle/main.js +24 -10
  117. package/dist/cli-bundle/main.js.map +2 -2
  118. package/dist/core/diagnostics/remediation.js +19 -6
  119. package/dist/core/diagnostics/remediation.js.map +1 -1
  120. package/dist/core/lock/lock-gc.d.ts +31 -0
  121. package/dist/core/lock/lock-gc.js +120 -62
  122. package/dist/core/lock/lock-gc.js.map +1 -1
  123. package/dist/core/telemetry/consent.js +18 -3
  124. package/dist/core/telemetry/consent.js.map +1 -1
  125. package/dist/core/validate/fix-planning.d.ts +130 -0
  126. package/dist/core/validate/fix-planning.js +202 -0
  127. package/dist/core/validate/fix-planning.js.map +1 -0
  128. package/dist/core/validate/missing-by-type.d.ts +21 -0
  129. package/dist/core/validate/missing-by-type.js +39 -0
  130. package/dist/core/validate/missing-by-type.js.map +1 -0
  131. package/dist/core/validate/stale-file-classification.d.ts +41 -0
  132. package/dist/core/validate/stale-file-classification.js +73 -0
  133. package/dist/core/validate/stale-file-classification.js.map +1 -0
  134. package/dist/mcp/server.js +41 -8
  135. package/dist/mcp/server.js.map +1 -1
  136. package/dist/sdk/cli-contracts/tool-parameter-tables.js +38 -2
  137. package/dist/sdk/cli-contracts/tool-parameter-tables.js.map +1 -1
  138. package/dist/sdk/cli-contracts.d.ts +6 -1
  139. package/dist/sdk/cli-contracts.js +74 -7
  140. package/dist/sdk/cli-contracts.js.map +1 -1
  141. package/docs/AGENT_GUIDE.md +6 -1
  142. package/docs/COMMANDS.md +23 -1
  143. package/package.json +1 -1
  144. package/dist/cli-bundle/chunks/chunk-E5DUIJA4.js.map +0 -7
  145. package/dist/cli-bundle/chunks/chunk-FXRU333M.js.map +0 -1
  146. package/dist/cli-bundle/chunks/chunk-LCSCXBGT.js.map +0 -7
  147. package/dist/cli-bundle/chunks/chunk-P2PSODAQ.js.map +0 -7
  148. package/dist/cli-bundle/chunks/chunk-PSD7F7DX.js.map +0 -1
  149. package/dist/cli-bundle/chunks/chunk-UOO6BYIA.js.map +0 -1
  150. package/dist/cli-bundle/chunks/docs-5WMBVQT2.js.map +0 -1
  151. package/dist/cli-bundle/chunks/docs-MB53UJAJ.js.map +0 -7
  152. package/dist/cli-bundle/chunks/files-DLB5LJEP.js.map +0 -1
  153. package/dist/cli-bundle/chunks/files-GVMNVLCB.js.map +0 -7
  154. package/dist/cli-bundle/chunks/gc-2WEPW6D6.js.map +0 -1
  155. package/dist/cli-bundle/chunks/gc-4Z3IKHAZ.js.map +0 -7
  156. package/dist/cli-bundle/chunks/health-4ZYB3IYQ.js.map +0 -1
  157. package/dist/cli-bundle/chunks/health-YO5LRWN4.js.map +0 -7
  158. package/dist/cli-bundle/chunks/history-repair-KVPFJKOS.js.map +0 -7
  159. package/dist/cli-bundle/chunks/history-repair-RUGKD3YR.js.map +0 -1
  160. package/dist/cli-bundle/chunks/plan-FQLMJRLZ.js.map +0 -7
  161. package/dist/cli-bundle/chunks/plan-VNUG3WZZ.js.map +0 -1
  162. package/dist/cli-bundle/chunks/register-operations-AM6KTWB6.js.map +0 -1
  163. package/dist/cli-bundle/chunks/register-setup-G4QYI3RX.js.map +0 -7
  164. package/dist/cli-bundle/chunks/register-setup-IKX5524L.js.map +0 -1
  165. package/dist/cli-bundle/chunks/validate-EQTT6WVX.js.map +0 -1
  166. package/dist/cli-bundle/chunks/validate-XN4I4DPL.js.map +0 -7
  167. /package/dist/cli-bundle/chunks/{chunk-B65UUCGQ.js.map → chunk-37CYVF73.js.map} +0 -0
  168. /package/dist/cli-bundle/chunks/{chunk-3SLOMKCJ.js.map → chunk-L5EPZQIC.js.map} +0 -0
  169. /package/dist/cli-bundle/chunks/{chunk-7RIT3OCD.js.map → chunk-SO55HRA5.js.map} +0 -0
  170. /package/dist/cli-bundle/chunks/{chunk-IH4MBKK7.js.map → chunk-XWMF3POR.js.map} +0 -0
  171. /package/dist/cli-bundle/chunks/{contracts-3NKWYKKU.js.map → contracts-2G66IGIA.js.map} +0 -0
  172. /package/dist/cli-bundle/chunks/{contracts-MIXBWJIB.js.map → contracts-PJCHYSO5.js.map} +0 -0
  173. /package/dist/cli-bundle/chunks/{create-IFA26O2I.js.map → create-5433DP3G.js.map} +0 -0
  174. /package/dist/cli-bundle/chunks/{create-JVXYHYH4.js.map → create-VWLWS34Z.js.map} +0 -0
  175. /package/dist/cli-bundle/chunks/{register-list-query-VHDXWEPP.js.map → register-list-query-4UPMHONX.js.map} +0 -0
  176. /package/dist/cli-bundle/chunks/{register-list-query-NPE3ECJQ.js.map → register-list-query-7VOW2C3W.js.map} +0 -0
  177. /package/dist/cli-bundle/chunks/{update-2EVXOC7V.js.map → update-36JM7YZT.js.map} +0 -0
  178. /package/dist/cli-bundle/chunks/{update-5A2B6QYB.js.map → update-I246PCLO.js.map} +0 -0
  179. /package/dist/cli-bundle/chunks/{update-many-V4LA3C4H.js.map → update-many-F4IKDWZB.js.map} +0 -0
  180. /package/dist/cli-bundle/chunks/{update-many-TUGU5YXQ.js.map → update-many-NTWZ5YT3.js.map} +0 -0
@@ -24,7 +24,7 @@
24
24
  * intentionally excluded here (see pm-0hnu: "all non-extension checks").
25
25
  */
26
26
 
27
- !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]="0913b502-4db3-5424-9ec5-b66ccfb7fc20")}catch(e){}}();
27
+ !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]="d65fd5ea-fcaf-5004-97da-9cdcbc7b334a")}catch(e){}}();
28
28
  export const REMEDIATION_REGISTRY = Object.freeze([
29
29
  // --- pm health: directories ---
30
30
  {
@@ -127,25 +127,38 @@ export const REMEDIATION_REGISTRY = Object.freeze([
127
127
  summary: "A history stream contains Git conflict markers; resolve the markers, then re-anchor the chain.",
128
128
  },
129
129
  // --- pm health: history_drift ---
130
+ // `pm health` rewrites these commands to `pm history-repair --all` in the
131
+ // per-check remediation_map when more than one stream is drifted.
130
132
  {
131
133
  code: "history_drift_missing_stream",
132
134
  command: "pm history-repair <id>",
133
- summary: "The item has no history stream; re-anchor the chain to rebuild it.",
135
+ summary: "The item has no history stream; re-anchor the chain to rebuild it (pm history-repair --all repairs every drifted stream in one pass).",
134
136
  },
135
137
  {
136
138
  code: "history_drift_unreadable_stream",
137
139
  command: "pm history-repair <id>",
138
- summary: "The item's history stream is unreadable; re-anchor the chain.",
140
+ summary: "The item's history stream is unreadable; re-anchor the chain (pm history-repair --all repairs every drifted stream in one pass).",
139
141
  },
140
142
  {
141
143
  code: "history_drift_hash_mismatch",
142
144
  command: "pm history-repair <id>",
143
- summary: "The item's content hash no longer matches its history; re-anchor the chain.",
145
+ summary: "The item's content hash no longer matches its history; re-anchor the chain (pm history-repair --all repairs every drifted stream in one pass).",
144
146
  },
145
147
  {
146
148
  code: "history_drift_chain_mismatch",
147
149
  command: "pm history-repair <id>",
148
- summary: "The item's history chain is broken; re-anchor the chain.",
150
+ summary: "The item's history chain is broken; re-anchor the chain (pm history-repair --all repairs every drifted stream in one pass).",
151
+ },
152
+ // --- pm health: locks ---
153
+ {
154
+ code: "locks_stale_count",
155
+ command: "pm gc --scope locks",
156
+ summary: "Stale item-claim locks (embedded ttl elapsed) can block mutations; sweep them — gc retains active and unparseable locks.",
157
+ },
158
+ {
159
+ code: "locks_unreadable",
160
+ command: "pm gc --scope locks --dry-run",
161
+ summary: "Some lock files could not be read; preview the sweep and inspect the unreadable files manually — gc never deletes what it cannot read.",
149
162
  },
150
163
  // --- pm health: vectorization ---
151
164
  {
@@ -340,4 +353,4 @@ export function buildRemediationCommands(warnings) {
340
353
  return Object.values(buildRemediationMap(warnings));
341
354
  }
342
355
  //# sourceMappingURL=remediation.js.map
343
- //# debugId=0913b502-4db3-5424-9ec5-b66ccfb7fc20
356
+ //# debugId=d65fd5ea-fcaf-5004-97da-9cdcbc7b334a
@@ -1 +1 @@
1
- {"version":3,"file":"remediation.js","sources":["core/diagnostics/remediation.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Shared machine-executable remediation registry.\n *\n * `pm health` and `pm validate` both emit stable, colon-delimited warning tokens\n * (for example `history_drift_missing_stream:pm-abcd` or\n * `validate_resolution_missing_fields:3`). Agents that gate on these commands\n * previously had to hardcode the mapping from a warning code to the `pm` command\n * that fixes it. This module is the single source of truth for that mapping so\n * `pm health --json` (per-check `remediation_map`) and `pm validate --fix-hints`\n * (per-check `fix_hints`) can both surface executable remediation without any\n * duplicated, drift-prone lookup tables.\n *\n * Semantics of {@link RemediationEntry.command}: the suggested next `pm` command\n * an operator/agent should run to resolve findings with that code. Per-item\n * findings use an `<id>` placeholder (callers that have the concrete id may\n * substitute it); findings whose only fix is a manual file edit point at the\n * most useful diagnostic/repair command and describe the manual step in\n * {@link RemediationEntry.summary}.\n */\n\nexport interface RemediationEntry {\n /**\n * Stable warning-code prefix this entry resolves. A warning matches when it\n * equals this code or begins with `<code>:`. Registry codes are mutually\n * exclusive under that colon-boundary rule (no code is a `:`-delimited prefix\n * of another — e.g. `settings:id_prefix_empty` is distinct from a bare\n * `settings`, which is intentionally not registered), so `resolveRemediation`\n * uses first-match with no ordering dependency.\n */\n readonly code: string;\n /** Executable `pm` command (or imperative) that resolves the finding. May contain an `<id>` placeholder. */\n readonly command: string;\n /** One-line description of what the command does / which manual step it covers. */\n readonly summary: string;\n}\n\n/**\n * Registry of every non-extension `pm health` / `pm validate` warning code and\n * its remediation. Extension findings keep their richer, contextual\n * `details.triage.remediation` produced by the extension health triage and are\n * intentionally excluded here (see pm-0hnu: \"all non-extension checks\").\n */\nexport const REMEDIATION_REGISTRY: readonly RemediationEntry[] = Object.freeze([\n // --- pm health: directories ---\n {\n code: \"missing_directory\",\n command: \"pm init\",\n summary: \"Recreate missing tracker directories (pm init is idempotent and restores the scaffold).\",\n },\n // --- pm health: settings (read/parse) ---\n {\n code: \"settings_read_invalid_json\",\n command: \"pm config list --json\",\n summary:\n \"settings.json is not valid JSON and pm fell back to defaults; fix the syntax error in the reported path, then re-run.\",\n },\n {\n code: \"settings_read_invalid_schema\",\n command: \"pm config list --json\",\n summary:\n \"settings.json failed schema validation and pm fell back to defaults; correct the reported key in the file, then re-run.\",\n },\n {\n code: \"settings_read_merge_failed\",\n command: \"pm config list --json\",\n summary: \"Global/project settings could not be merged; reconcile the conflicting keys in the reported settings files.\",\n },\n // --- pm health: settings_values ---\n {\n code: \"settings:id_prefix_empty\",\n command: \"pm config list --json\",\n summary: 'id_prefix is empty; set a non-empty \"id_prefix\" in settings.json so generated item ids are well-formed.',\n },\n {\n code: \"settings:locks_ttl_non_positive\",\n command: \"pm config list --json\",\n summary: 'locks.ttl_seconds must be positive; set a positive \"locks.ttl_seconds\" in settings.json.',\n },\n // --- pm health: telemetry (advisory) ---\n {\n code: \"telemetry_state_invalid_json\",\n command: \"pm health --check-telemetry\",\n summary: \"Local telemetry state file is corrupt; pm recreates it on the next flush. Advisory only.\",\n },\n {\n code: \"telemetry_queue_invalid_rows\",\n command: \"pm health --check-telemetry\",\n summary: \"The telemetry queue has unparseable rows; they are skipped on the next flush. Advisory only.\",\n },\n {\n code: \"telemetry_queue_pending\",\n command: \"pm health --check-telemetry\",\n summary: \"Telemetry events are queued; they flush automatically on the next reachable command. Advisory only.\",\n },\n {\n code: \"telemetry_queue_high_retries\",\n command: \"pm telemetry flush\",\n summary:\n \"Some queued telemetry events are near retry exhaustion; flush now and verify endpoint reachability before entries are dropped.\",\n },\n {\n code: \"telemetry_endpoint_probe_failed\",\n command: \"pm health --check-telemetry\",\n summary: \"The telemetry endpoint is unreachable; events stay queued until it recovers. Advisory only.\",\n },\n {\n code: \"telemetry_endpoint_probe_http_status\",\n command: \"pm health --check-telemetry\",\n summary: \"The telemetry endpoint returned a non-success status; events stay queued until it recovers. Advisory only.\",\n },\n {\n code: \"telemetry_schema_version_behind\",\n command: \"pm health --check-telemetry\",\n summary: \"Client schema is behind server; upgrade pm-cli to emit the latest event shape.\",\n },\n // --- pm health: integrity ---\n {\n code: \"integrity_item_unreadable\",\n command: \"pm validate --check-files --verbose-diagnostics\",\n summary: \"An item file could not be read; restore or repair the file at the reported path.\",\n },\n {\n code: \"integrity_item_parse_failed\",\n command: \"pm validate --check-files --verbose-diagnostics\",\n summary: \"An item file failed to parse; fix the malformed front matter at the reported path.\",\n },\n {\n code: \"integrity_item_conflict_marker\",\n command: \"pm validate --check-files --verbose-diagnostics\",\n summary: \"An item file contains Git conflict markers; resolve the <<<<<<< / >>>>>>> markers at the reported line.\",\n },\n {\n code: \"integrity_history_unreadable\",\n command: \"pm history-repair <id>\",\n summary: \"A history stream could not be read; re-anchor the affected item's history chain.\",\n },\n {\n code: \"integrity_history_invalid_json\",\n command: \"pm history-repair <id>\",\n summary: \"A history stream contains invalid JSON; re-anchor the affected item's history chain.\",\n },\n {\n code: \"integrity_history_conflict_marker\",\n command: \"pm history-repair <id>\",\n summary: \"A history stream contains Git conflict markers; resolve the markers, then re-anchor the chain.\",\n },\n // --- pm health: history_drift ---\n {\n code: \"history_drift_missing_stream\",\n command: \"pm history-repair <id>\",\n summary: \"The item has no history stream; re-anchor the chain to rebuild it.\",\n },\n {\n code: \"history_drift_unreadable_stream\",\n command: \"pm history-repair <id>\",\n summary: \"The item's history stream is unreadable; re-anchor the chain.\",\n },\n {\n code: \"history_drift_hash_mismatch\",\n command: \"pm history-repair <id>\",\n summary: \"The item's content hash no longer matches its history; re-anchor the chain.\",\n },\n {\n code: \"history_drift_chain_mismatch\",\n command: \"pm history-repair <id>\",\n summary: \"The item's history chain is broken; re-anchor the chain.\",\n },\n // --- pm health: vectorization ---\n {\n code: \"vectorization_stale_items_remaining\",\n command: \"pm health --refresh-vectors\",\n summary: \"Some items have stale embeddings; refresh vectors so semantic search results stay current.\",\n },\n {\n code: \"vectorization_embedding_identity_changed\",\n command: \"pm reindex --mode semantic\",\n summary: \"Embedding provider/model changed since the last index; run a semantic reindex to rebuild vectors.\",\n },\n // --- pm validate: metadata ---\n // Note: validate_metadata_missing_author has no entry on purpose. `pm update\n // --author` sets the mutation/audit author (history), not the item's\n // `metadata.author` front-matter field, and there is no CLI flag that writes\n // that field — so emitting a command here would record an audit entry without\n // clearing the finding. A missing item author must be restored at the source.\n {\n code: \"validate_metadata_missing_acceptance_criteria\",\n command: 'pm update <id> --acceptance-criteria \"<criteria>\"',\n summary: \"Backfill the missing acceptance criteria on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_estimate\",\n command: 'pm update <id> --estimate \"<estimate>\"',\n summary: \"Backfill the missing estimate on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_close_reason\",\n command: 'pm update <id> --close-reason \"<reason>\"',\n summary: \"Backfill the missing close reason on the reported closed item(s).\",\n },\n {\n code: \"validate_metadata_missing_reviewer\",\n command: 'pm update <id> --reviewer \"<name>\"',\n summary: \"Backfill the missing reviewer on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_risk\",\n command: 'pm update <id> --risk \"<level>\"',\n summary: \"Backfill the missing risk on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_confidence\",\n command: 'pm update <id> --confidence \"<level>\"',\n summary: \"Backfill the missing confidence on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_sprint\",\n command: 'pm update <id> --sprint \"<sprint>\"',\n summary: \"Backfill the missing sprint on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_release\",\n command: 'pm update <id> --release \"<release>\"',\n summary: \"Backfill the missing release on the reported item(s).\",\n },\n {\n code: \"validate_metadata_custom_profile_missing_required_fields\",\n command: 'pm config set metadata-required-fields --criterion \"<field>\"',\n summary:\n \"Config-driven finding: the custom metadata profile has no required fields configured and falls back to core, so configure the fields rather than mutating items.\",\n },\n // --- pm validate: resolution ---\n {\n code: \"validate_resolution_missing_fields\",\n command: 'pm update <id> --resolution \"<how resolved>\"',\n summary: \"Backfill resolution / expected_result / actual_result on the reported closed item(s).\",\n },\n // --- pm validate: lifecycle ---\n {\n code: \"validate_lifecycle_active_closure_like_metadata\",\n command: \"pm update <id> --unset <closure-like-field>\",\n summary:\n \"Clear the closure-like field the validator flags on this active item — the detail row names it (resolution / actual-result / blocked-reason) — or close the item if it is actually done.\",\n },\n {\n code: \"validate_lifecycle_active_terminal_parent\",\n command: \"pm update <id> --parent <active-parent-id>\",\n summary: \"Reopen the terminal parent or move the active child under a non-terminal parent.\",\n },\n {\n code: \"validate_lifecycle_stale_blockers\",\n command: \"pm update <id> --unset blocked-by --unset blocked-reason\",\n summary:\n \"Resolve the stale blocker the validator flags: clear blocked-by/blocked-reason when they linger on a non-blocked item or report a resolved blocker; if instead a blocked item is missing blocker context, add the context or move it out of a blocked status.\",\n },\n {\n code: \"validate_lifecycle_dependency_cycles_error\",\n command: \"pm update <id> --dep-remove <dep-id>\",\n summary: \"Break the dependency cycle by removing one edge from the reported cycle.\",\n },\n {\n code: \"validate_lifecycle_dependency_cycles\",\n command: \"pm update <id> --dep-remove <dep-id>\",\n summary: \"Break the dependency cycle by removing one edge from the reported cycle.\",\n },\n // --- pm validate: files ---\n {\n code: \"validate_files_missing_linked_paths\",\n command: \"pm <files|docs> <id> --remove <path>\",\n summary:\n \"The check spans both item.files and item.docs under one code: restore the missing linked artifact, or unlink it with pm files (linked file) or pm docs (linked doc).\",\n },\n {\n code: \"validate_files_orphaned_paths\",\n command: \"pm <files|docs> <id> --add <path>\",\n summary:\n \"Link the orphaned artifact with pm files (file) or pm docs (doc), or remove it from the workspace.\",\n },\n {\n code: \"validate_files_tracked_all_strict_forces_pm_internals\",\n command: \"pm validate --check-files --include-pm-internals\",\n summary: \"tracked-all-strict scan flagged pm internals; re-run with --include-pm-internals or a softer --scan-mode.\",\n },\n // --- pm validate: history_drift ---\n {\n code: \"validate_history_drift_missing_streams\",\n command: \"pm history-repair <id>\",\n summary: \"Re-anchor the history chains of the items missing a history stream.\",\n },\n {\n code: \"validate_history_drift_unreadable_streams\",\n command: \"pm history-repair <id>\",\n summary: \"Re-anchor the history chains of the items with an unreadable history stream.\",\n },\n {\n code: \"validate_history_drift_hash_mismatches\",\n command: \"pm history-repair <id>\",\n summary: \"Re-anchor the history chains of the items whose content hash drifted.\",\n },\n {\n code: \"validate_history_drift_chain_mismatches\",\n command: \"pm history-repair <id>\",\n summary: \"Re-anchor the history chains of the items with a broken history chain.\",\n },\n // --- pm validate: command_references ---\n {\n code: \"validate_command_references_stale_pm_ids\",\n command: 'pm update <id> --replace-tests --test \"command=<corrected-command>\" [--test ...]',\n summary:\n \"Correct the stale pm-ID inside the item's linked test command (the check scans tests[].command, not the body). --replace-tests overwrites the entire tests list, so re-include the item's other linked tests in the same call.\",\n },\n]);\n\n/**\n * Resolve the remediation entry for a warning token, or `undefined` when no\n * entry is registered. A warning matches an entry when it equals the entry code\n * or begins with `<code>:` (the colon boundary keeps sibling codes such as\n * `validate_lifecycle_dependency_cycles` and\n * `validate_lifecycle_dependency_cycles_error` disjoint).\n *\n * First match is intentional and correct: registry codes are mutually exclusive\n * under the colon-boundary rule, so at most one entry can ever match a warning\n * and order is irrelevant. That invariant is enforced by a test (see\n * remediation-registry.spec.ts \"keeps every registry code mutually exclusive\"),\n * which is why this stays a simple linear scan rather than a longest-prefix\n * search — a longest-prefix tie-break is unreachable with disjoint codes.\n *\n * Accepts unknown input defensively (this is an exported helper SDK consumers\n * may call from untyped JS): a non-string `warning` resolves to `undefined`.\n */\nexport function resolveRemediation(warning: string): RemediationEntry | undefined {\n if (typeof warning !== \"string\") {\n return undefined;\n }\n const normalized = warning.trim();\n for (const entry of REMEDIATION_REGISTRY) {\n if (normalized === entry.code || normalized.startsWith(`${entry.code}:`)) {\n return entry;\n }\n }\n return undefined;\n}\n\n/**\n * Build a compact `remediation_map` (code -> command) for a set of warning\n * tokens. First match wins per code; unknown codes are skipped. Used by\n * `pm health --json` per-check details and as the source of deduped executable\n * commands for `pm validate --fix-hints`.\n */\nexport function buildRemediationMap(warnings: Iterable<string>): Record<string, string> {\n const map: Record<string, string> = {};\n for (const warning of warnings) {\n const entry = resolveRemediation(warning);\n if (entry !== undefined && !Object.prototype.hasOwnProperty.call(map, entry.code)) {\n map[entry.code] = entry.command;\n }\n }\n return map;\n}\n\n/**\n * Build a deduped, ordered list of executable remediation commands for a set of\n * warning tokens (one command per distinct matched code). Used by\n * `pm validate --fix-hints` for checks that do not already emit per-row\n * remediation commands.\n */\nexport function buildRemediationCommands(warnings: Iterable<string>): string[] {\n return Object.values(buildRemediationMap(warnings));\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkBH;;;;;GAKG;;;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAgC,MAAM,CAAC,MAAM,CAAC;IAC7E,iCAAiC;IACjC;QACE,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,SAAS;QAClB,OAAO,EAAE,yFAAyF;KACnG;IACD,2CAA2C;IAC3C;QACE,IAAI,EAAE,4BAA4B;QAClC,OAAO,EAAE,uBAAuB;QAChC,OAAO,EACL,uHAAuH;KAC1H;IACD;QACE,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,uBAAuB;QAChC,OAAO,EACL,yHAAyH;KAC5H;IACD;QACE,IAAI,EAAE,4BAA4B;QAClC,OAAO,EAAE,uBAAuB;QAChC,OAAO,EAAE,6GAA6G;KACvH;IACD,qCAAqC;IACrC;QACE,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,uBAAuB;QAChC,OAAO,EAAE,yGAAyG;KACnH;IACD;QACE,IAAI,EAAE,iCAAiC;QACvC,OAAO,EAAE,uBAAuB;QAChC,OAAO,EAAE,0FAA0F;KACpG;IACD,0CAA0C;IAC1C;QACE,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,0FAA0F;KACpG;IACD;QACE,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,8FAA8F;KACxG;IACD;QACE,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,qGAAqG;KAC/G;IACD;QACE,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,oBAAoB;QAC7B,OAAO,EACL,gIAAgI;KACnI;IACD;QACE,IAAI,EAAE,iCAAiC;QACvC,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,6FAA6F;KACvG;IACD;QACE,IAAI,EAAE,sCAAsC;QAC5C,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,4GAA4G;KACtH;IACD;QACE,IAAI,EAAE,iCAAiC;QACvC,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,gFAAgF;KAC1F;IACD,+BAA+B;IAC/B;QACE,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,iDAAiD;QAC1D,OAAO,EAAE,kFAAkF;KAC5F;IACD;QACE,IAAI,EAAE,6BAA6B;QACnC,OAAO,EAAE,iDAAiD;QAC1D,OAAO,EAAE,oFAAoF;KAC9F;IACD;QACE,IAAI,EAAE,gCAAgC;QACtC,OAAO,EAAE,iDAAiD;QAC1D,OAAO,EAAE,yGAAyG;KACnH;IACD;QACE,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,kFAAkF;KAC5F;IACD;QACE,IAAI,EAAE,gCAAgC;QACtC,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,sFAAsF;KAChG;IACD;QACE,IAAI,EAAE,mCAAmC;QACzC,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,gGAAgG;KAC1G;IACD,mCAAmC;IACnC;QACE,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,oEAAoE;KAC9E;IACD;QACE,IAAI,EAAE,iCAAiC;QACvC,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,+DAA+D;KACzE;IACD;QACE,IAAI,EAAE,6BAA6B;QACnC,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,6EAA6E;KACvF;IACD;QACE,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,0DAA0D;KACpE;IACD,mCAAmC;IACnC;QACE,IAAI,EAAE,qCAAqC;QAC3C,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,4FAA4F;KACtG;IACD;QACE,IAAI,EAAE,0CAA0C;QAChD,OAAO,EAAE,4BAA4B;QACrC,OAAO,EAAE,mGAAmG;KAC7G;IACD,gCAAgC;IAChC,6EAA6E;IAC7E,qEAAqE;IACrE,6EAA6E;IAC7E,8EAA8E;IAC9E,8EAA8E;IAC9E;QACE,IAAI,EAAE,+CAA+C;QACrD,OAAO,EAAE,mDAAmD;QAC5D,OAAO,EAAE,mEAAmE;KAC7E;IACD;QACE,IAAI,EAAE,oCAAoC;QAC1C,OAAO,EAAE,wCAAwC;QACjD,OAAO,EAAE,wDAAwD;KAClE;IACD;QACE,IAAI,EAAE,wCAAwC;QAC9C,OAAO,EAAE,0CAA0C;QACnD,OAAO,EAAE,mEAAmE;KAC7E;IACD;QACE,IAAI,EAAE,oCAAoC;QAC1C,OAAO,EAAE,oCAAoC;QAC7C,OAAO,EAAE,wDAAwD;KAClE;IACD;QACE,IAAI,EAAE,gCAAgC;QACtC,OAAO,EAAE,iCAAiC;QAC1C,OAAO,EAAE,oDAAoD;KAC9D;IACD;QACE,IAAI,EAAE,sCAAsC;QAC5C,OAAO,EAAE,uCAAuC;QAChD,OAAO,EAAE,0DAA0D;KACpE;IACD;QACE,IAAI,EAAE,kCAAkC;QACxC,OAAO,EAAE,oCAAoC;QAC7C,OAAO,EAAE,sDAAsD;KAChE;IACD;QACE,IAAI,EAAE,mCAAmC;QACzC,OAAO,EAAE,sCAAsC;QAC/C,OAAO,EAAE,uDAAuD;KACjE;IACD;QACE,IAAI,EAAE,0DAA0D;QAChE,OAAO,EAAE,8DAA8D;QACvE,OAAO,EACL,kKAAkK;KACrK;IACD,kCAAkC;IAClC;QACE,IAAI,EAAE,oCAAoC;QAC1C,OAAO,EAAE,8CAA8C;QACvD,OAAO,EAAE,uFAAuF;KACjG;IACD,iCAAiC;IACjC;QACE,IAAI,EAAE,iDAAiD;QACvD,OAAO,EAAE,6CAA6C;QACtD,OAAO,EACL,0LAA0L;KAC7L;IACD;QACE,IAAI,EAAE,2CAA2C;QACjD,OAAO,EAAE,4CAA4C;QACrD,OAAO,EAAE,kFAAkF;KAC5F;IACD;QACE,IAAI,EAAE,mCAAmC;QACzC,OAAO,EAAE,0DAA0D;QACnE,OAAO,EACL,+PAA+P;KAClQ;IACD;QACE,IAAI,EAAE,4CAA4C;QAClD,OAAO,EAAE,sCAAsC;QAC/C,OAAO,EAAE,0EAA0E;KACpF;IACD;QACE,IAAI,EAAE,sCAAsC;QAC5C,OAAO,EAAE,sCAAsC;QAC/C,OAAO,EAAE,0EAA0E;KACpF;IACD,6BAA6B;IAC7B;QACE,IAAI,EAAE,qCAAqC;QAC3C,OAAO,EAAE,sCAAsC;QAC/C,OAAO,EACL,sKAAsK;KACzK;IACD;QACE,IAAI,EAAE,+BAA+B;QACrC,OAAO,EAAE,mCAAmC;QAC5C,OAAO,EACL,oGAAoG;KACvG;IACD;QACE,IAAI,EAAE,uDAAuD;QAC7D,OAAO,EAAE,kDAAkD;QAC3D,OAAO,EAAE,2GAA2G;KACrH;IACD,qCAAqC;IACrC;QACE,IAAI,EAAE,wCAAwC;QAC9C,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,qEAAqE;KAC/E;IACD;QACE,IAAI,EAAE,2CAA2C;QACjD,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,8EAA8E;KACxF;IACD;QACE,IAAI,EAAE,wCAAwC;QAC9C,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,uEAAuE;KACjF;IACD;QACE,IAAI,EAAE,yCAAyC;QAC/C,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,wEAAwE;KAClF;IACD,0CAA0C;IAC1C;QACE,IAAI,EAAE,0CAA0C;QAChD,OAAO,EAAE,kFAAkF;QAC3F,OAAO,EACL,gOAAgO;KACnO;CACF,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,oBAAoB,EAAE,CAAC;QACzC,IAAI,UAAU,KAAK,KAAK,CAAC,IAAI,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACzE,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAA0B;IAC5D,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAClF,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC;QAClC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAA0B;IACjE,OAAO,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC;AACtD,CAAC","debugId":"0913b502-4db3-5424-9ec5-b66ccfb7fc20"}
1
+ {"version":3,"file":"remediation.js","sources":["core/diagnostics/remediation.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Shared machine-executable remediation registry.\n *\n * `pm health` and `pm validate` both emit stable, colon-delimited warning tokens\n * (for example `history_drift_missing_stream:pm-abcd` or\n * `validate_resolution_missing_fields:3`). Agents that gate on these commands\n * previously had to hardcode the mapping from a warning code to the `pm` command\n * that fixes it. This module is the single source of truth for that mapping so\n * `pm health --json` (per-check `remediation_map`) and `pm validate --fix-hints`\n * (per-check `fix_hints`) can both surface executable remediation without any\n * duplicated, drift-prone lookup tables.\n *\n * Semantics of {@link RemediationEntry.command}: the suggested next `pm` command\n * an operator/agent should run to resolve findings with that code. Per-item\n * findings use an `<id>` placeholder (callers that have the concrete id may\n * substitute it); findings whose only fix is a manual file edit point at the\n * most useful diagnostic/repair command and describe the manual step in\n * {@link RemediationEntry.summary}.\n */\n\nexport interface RemediationEntry {\n /**\n * Stable warning-code prefix this entry resolves. A warning matches when it\n * equals this code or begins with `<code>:`. Registry codes are mutually\n * exclusive under that colon-boundary rule (no code is a `:`-delimited prefix\n * of another — e.g. `settings:id_prefix_empty` is distinct from a bare\n * `settings`, which is intentionally not registered), so `resolveRemediation`\n * uses first-match with no ordering dependency.\n */\n readonly code: string;\n /** Executable `pm` command (or imperative) that resolves the finding. May contain an `<id>` placeholder. */\n readonly command: string;\n /** One-line description of what the command does / which manual step it covers. */\n readonly summary: string;\n}\n\n/**\n * Registry of every non-extension `pm health` / `pm validate` warning code and\n * its remediation. Extension findings keep their richer, contextual\n * `details.triage.remediation` produced by the extension health triage and are\n * intentionally excluded here (see pm-0hnu: \"all non-extension checks\").\n */\nexport const REMEDIATION_REGISTRY: readonly RemediationEntry[] = Object.freeze([\n // --- pm health: directories ---\n {\n code: \"missing_directory\",\n command: \"pm init\",\n summary: \"Recreate missing tracker directories (pm init is idempotent and restores the scaffold).\",\n },\n // --- pm health: settings (read/parse) ---\n {\n code: \"settings_read_invalid_json\",\n command: \"pm config list --json\",\n summary:\n \"settings.json is not valid JSON and pm fell back to defaults; fix the syntax error in the reported path, then re-run.\",\n },\n {\n code: \"settings_read_invalid_schema\",\n command: \"pm config list --json\",\n summary:\n \"settings.json failed schema validation and pm fell back to defaults; correct the reported key in the file, then re-run.\",\n },\n {\n code: \"settings_read_merge_failed\",\n command: \"pm config list --json\",\n summary: \"Global/project settings could not be merged; reconcile the conflicting keys in the reported settings files.\",\n },\n // --- pm health: settings_values ---\n {\n code: \"settings:id_prefix_empty\",\n command: \"pm config list --json\",\n summary: 'id_prefix is empty; set a non-empty \"id_prefix\" in settings.json so generated item ids are well-formed.',\n },\n {\n code: \"settings:locks_ttl_non_positive\",\n command: \"pm config list --json\",\n summary: 'locks.ttl_seconds must be positive; set a positive \"locks.ttl_seconds\" in settings.json.',\n },\n // --- pm health: telemetry (advisory) ---\n {\n code: \"telemetry_state_invalid_json\",\n command: \"pm health --check-telemetry\",\n summary: \"Local telemetry state file is corrupt; pm recreates it on the next flush. Advisory only.\",\n },\n {\n code: \"telemetry_queue_invalid_rows\",\n command: \"pm health --check-telemetry\",\n summary: \"The telemetry queue has unparseable rows; they are skipped on the next flush. Advisory only.\",\n },\n {\n code: \"telemetry_queue_pending\",\n command: \"pm health --check-telemetry\",\n summary: \"Telemetry events are queued; they flush automatically on the next reachable command. Advisory only.\",\n },\n {\n code: \"telemetry_queue_high_retries\",\n command: \"pm telemetry flush\",\n summary:\n \"Some queued telemetry events are near retry exhaustion; flush now and verify endpoint reachability before entries are dropped.\",\n },\n {\n code: \"telemetry_endpoint_probe_failed\",\n command: \"pm health --check-telemetry\",\n summary: \"The telemetry endpoint is unreachable; events stay queued until it recovers. Advisory only.\",\n },\n {\n code: \"telemetry_endpoint_probe_http_status\",\n command: \"pm health --check-telemetry\",\n summary: \"The telemetry endpoint returned a non-success status; events stay queued until it recovers. Advisory only.\",\n },\n {\n code: \"telemetry_schema_version_behind\",\n command: \"pm health --check-telemetry\",\n summary: \"Client schema is behind server; upgrade pm-cli to emit the latest event shape.\",\n },\n // --- pm health: integrity ---\n {\n code: \"integrity_item_unreadable\",\n command: \"pm validate --check-files --verbose-diagnostics\",\n summary: \"An item file could not be read; restore or repair the file at the reported path.\",\n },\n {\n code: \"integrity_item_parse_failed\",\n command: \"pm validate --check-files --verbose-diagnostics\",\n summary: \"An item file failed to parse; fix the malformed front matter at the reported path.\",\n },\n {\n code: \"integrity_item_conflict_marker\",\n command: \"pm validate --check-files --verbose-diagnostics\",\n summary: \"An item file contains Git conflict markers; resolve the <<<<<<< / >>>>>>> markers at the reported line.\",\n },\n {\n code: \"integrity_history_unreadable\",\n command: \"pm history-repair <id>\",\n summary: \"A history stream could not be read; re-anchor the affected item's history chain.\",\n },\n {\n code: \"integrity_history_invalid_json\",\n command: \"pm history-repair <id>\",\n summary: \"A history stream contains invalid JSON; re-anchor the affected item's history chain.\",\n },\n {\n code: \"integrity_history_conflict_marker\",\n command: \"pm history-repair <id>\",\n summary: \"A history stream contains Git conflict markers; resolve the markers, then re-anchor the chain.\",\n },\n // --- pm health: history_drift ---\n // `pm health` rewrites these commands to `pm history-repair --all` in the\n // per-check remediation_map when more than one stream is drifted.\n {\n code: \"history_drift_missing_stream\",\n command: \"pm history-repair <id>\",\n summary: \"The item has no history stream; re-anchor the chain to rebuild it (pm history-repair --all repairs every drifted stream in one pass).\",\n },\n {\n code: \"history_drift_unreadable_stream\",\n command: \"pm history-repair <id>\",\n summary: \"The item's history stream is unreadable; re-anchor the chain (pm history-repair --all repairs every drifted stream in one pass).\",\n },\n {\n code: \"history_drift_hash_mismatch\",\n command: \"pm history-repair <id>\",\n summary: \"The item's content hash no longer matches its history; re-anchor the chain (pm history-repair --all repairs every drifted stream in one pass).\",\n },\n {\n code: \"history_drift_chain_mismatch\",\n command: \"pm history-repair <id>\",\n summary: \"The item's history chain is broken; re-anchor the chain (pm history-repair --all repairs every drifted stream in one pass).\",\n },\n // --- pm health: locks ---\n {\n code: \"locks_stale_count\",\n command: \"pm gc --scope locks\",\n summary:\n \"Stale item-claim locks (embedded ttl elapsed) can block mutations; sweep them — gc retains active and unparseable locks.\",\n },\n {\n code: \"locks_unreadable\",\n command: \"pm gc --scope locks --dry-run\",\n summary:\n \"Some lock files could not be read; preview the sweep and inspect the unreadable files manually — gc never deletes what it cannot read.\",\n },\n // --- pm health: vectorization ---\n {\n code: \"vectorization_stale_items_remaining\",\n command: \"pm health --refresh-vectors\",\n summary: \"Some items have stale embeddings; refresh vectors so semantic search results stay current.\",\n },\n {\n code: \"vectorization_embedding_identity_changed\",\n command: \"pm reindex --mode semantic\",\n summary: \"Embedding provider/model changed since the last index; run a semantic reindex to rebuild vectors.\",\n },\n // --- pm validate: metadata ---\n // Note: validate_metadata_missing_author has no entry on purpose. `pm update\n // --author` sets the mutation/audit author (history), not the item's\n // `metadata.author` front-matter field, and there is no CLI flag that writes\n // that field — so emitting a command here would record an audit entry without\n // clearing the finding. A missing item author must be restored at the source.\n {\n code: \"validate_metadata_missing_acceptance_criteria\",\n command: 'pm update <id> --acceptance-criteria \"<criteria>\"',\n summary: \"Backfill the missing acceptance criteria on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_estimate\",\n command: 'pm update <id> --estimate \"<estimate>\"',\n summary: \"Backfill the missing estimate on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_close_reason\",\n command: 'pm update <id> --close-reason \"<reason>\"',\n summary: \"Backfill the missing close reason on the reported closed item(s).\",\n },\n {\n code: \"validate_metadata_missing_reviewer\",\n command: 'pm update <id> --reviewer \"<name>\"',\n summary: \"Backfill the missing reviewer on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_risk\",\n command: 'pm update <id> --risk \"<level>\"',\n summary: \"Backfill the missing risk on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_confidence\",\n command: 'pm update <id> --confidence \"<level>\"',\n summary: \"Backfill the missing confidence on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_sprint\",\n command: 'pm update <id> --sprint \"<sprint>\"',\n summary: \"Backfill the missing sprint on the reported item(s).\",\n },\n {\n code: \"validate_metadata_missing_release\",\n command: 'pm update <id> --release \"<release>\"',\n summary: \"Backfill the missing release on the reported item(s).\",\n },\n {\n code: \"validate_metadata_custom_profile_missing_required_fields\",\n command: 'pm config set metadata-required-fields --criterion \"<field>\"',\n summary:\n \"Config-driven finding: the custom metadata profile has no required fields configured and falls back to core, so configure the fields rather than mutating items.\",\n },\n // --- pm validate: resolution ---\n {\n code: \"validate_resolution_missing_fields\",\n command: 'pm update <id> --resolution \"<how resolved>\"',\n summary: \"Backfill resolution / expected_result / actual_result on the reported closed item(s).\",\n },\n // --- pm validate: lifecycle ---\n {\n code: \"validate_lifecycle_active_closure_like_metadata\",\n command: \"pm update <id> --unset <closure-like-field>\",\n summary:\n \"Clear the closure-like field the validator flags on this active item — the detail row names it (resolution / actual-result / blocked-reason) — or close the item if it is actually done.\",\n },\n {\n code: \"validate_lifecycle_active_terminal_parent\",\n command: \"pm update <id> --parent <active-parent-id>\",\n summary: \"Reopen the terminal parent or move the active child under a non-terminal parent.\",\n },\n {\n code: \"validate_lifecycle_stale_blockers\",\n command: \"pm update <id> --unset blocked-by --unset blocked-reason\",\n summary:\n \"Resolve the stale blocker the validator flags: clear blocked-by/blocked-reason when they linger on a non-blocked item or report a resolved blocker; if instead a blocked item is missing blocker context, add the context or move it out of a blocked status.\",\n },\n {\n code: \"validate_lifecycle_dependency_cycles_error\",\n command: \"pm update <id> --dep-remove <dep-id>\",\n summary: \"Break the dependency cycle by removing one edge from the reported cycle.\",\n },\n {\n code: \"validate_lifecycle_dependency_cycles\",\n command: \"pm update <id> --dep-remove <dep-id>\",\n summary: \"Break the dependency cycle by removing one edge from the reported cycle.\",\n },\n // --- pm validate: files ---\n {\n code: \"validate_files_missing_linked_paths\",\n command: \"pm <files|docs> <id> --remove <path>\",\n summary:\n \"The check spans both item.files and item.docs under one code: restore the missing linked artifact, or unlink it with pm files (linked file) or pm docs (linked doc).\",\n },\n {\n code: \"validate_files_orphaned_paths\",\n command: \"pm <files|docs> <id> --add <path>\",\n summary:\n \"Link the orphaned artifact with pm files (file) or pm docs (doc), or remove it from the workspace.\",\n },\n {\n code: \"validate_files_tracked_all_strict_forces_pm_internals\",\n command: \"pm validate --check-files --include-pm-internals\",\n summary: \"tracked-all-strict scan flagged pm internals; re-run with --include-pm-internals or a softer --scan-mode.\",\n },\n // --- pm validate: history_drift ---\n {\n code: \"validate_history_drift_missing_streams\",\n command: \"pm history-repair <id>\",\n summary: \"Re-anchor the history chains of the items missing a history stream.\",\n },\n {\n code: \"validate_history_drift_unreadable_streams\",\n command: \"pm history-repair <id>\",\n summary: \"Re-anchor the history chains of the items with an unreadable history stream.\",\n },\n {\n code: \"validate_history_drift_hash_mismatches\",\n command: \"pm history-repair <id>\",\n summary: \"Re-anchor the history chains of the items whose content hash drifted.\",\n },\n {\n code: \"validate_history_drift_chain_mismatches\",\n command: \"pm history-repair <id>\",\n summary: \"Re-anchor the history chains of the items with a broken history chain.\",\n },\n // --- pm validate: command_references ---\n {\n code: \"validate_command_references_stale_pm_ids\",\n command: 'pm update <id> --replace-tests --test \"command=<corrected-command>\" [--test ...]',\n summary:\n \"Correct the stale pm-ID inside the item's linked test command (the check scans tests[].command, not the body). --replace-tests overwrites the entire tests list, so re-include the item's other linked tests in the same call.\",\n },\n]);\n\n/**\n * Resolve the remediation entry for a warning token, or `undefined` when no\n * entry is registered. A warning matches an entry when it equals the entry code\n * or begins with `<code>:` (the colon boundary keeps sibling codes such as\n * `validate_lifecycle_dependency_cycles` and\n * `validate_lifecycle_dependency_cycles_error` disjoint).\n *\n * First match is intentional and correct: registry codes are mutually exclusive\n * under the colon-boundary rule, so at most one entry can ever match a warning\n * and order is irrelevant. That invariant is enforced by a test (see\n * remediation-registry.spec.ts \"keeps every registry code mutually exclusive\"),\n * which is why this stays a simple linear scan rather than a longest-prefix\n * search — a longest-prefix tie-break is unreachable with disjoint codes.\n *\n * Accepts unknown input defensively (this is an exported helper SDK consumers\n * may call from untyped JS): a non-string `warning` resolves to `undefined`.\n */\nexport function resolveRemediation(warning: string): RemediationEntry | undefined {\n if (typeof warning !== \"string\") {\n return undefined;\n }\n const normalized = warning.trim();\n for (const entry of REMEDIATION_REGISTRY) {\n if (normalized === entry.code || normalized.startsWith(`${entry.code}:`)) {\n return entry;\n }\n }\n return undefined;\n}\n\n/**\n * Build a compact `remediation_map` (code -> command) for a set of warning\n * tokens. First match wins per code; unknown codes are skipped. Used by\n * `pm health --json` per-check details and as the source of deduped executable\n * commands for `pm validate --fix-hints`.\n */\nexport function buildRemediationMap(warnings: Iterable<string>): Record<string, string> {\n const map: Record<string, string> = {};\n for (const warning of warnings) {\n const entry = resolveRemediation(warning);\n if (entry !== undefined && !Object.prototype.hasOwnProperty.call(map, entry.code)) {\n map[entry.code] = entry.command;\n }\n }\n return map;\n}\n\n/**\n * Build a deduped, ordered list of executable remediation commands for a set of\n * warning tokens (one command per distinct matched code). Used by\n * `pm validate --fix-hints` for checks that do not already emit per-row\n * remediation commands.\n */\nexport function buildRemediationCommands(warnings: Iterable<string>): string[] {\n return Object.values(buildRemediationMap(warnings));\n}\n"],"names":[],"mappings":"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","debugId":"d65fd5ea-fcaf-5004-97da-9cdcbc7b334a"}
@@ -43,4 +43,35 @@ export interface LockGcResult {
43
43
  /** one per scanned *.lock file, sorted by file name asc */
44
44
  entries: StaleLockScanEntry[];
45
45
  }
46
+ /**
47
+ * Fine-grained classification outcome for one readable lock file's content.
48
+ * `unparseable_json` and `invalid_timestamp` both surface as the coarse
49
+ * `reason: "unparseable"` on the scan entry (gc retains both), but stay
50
+ * distinct here so callers can emit precise warnings.
51
+ */
52
+ export type LockClassificationDetail = "active" | "expired" | "unparseable_json" | "invalid_timestamp";
53
+ export interface LockContentClassification {
54
+ detail: LockClassificationDetail;
55
+ entry: StaleLockScanEntry;
56
+ }
57
+ /**
58
+ * Pure classification of one lock file's raw content against `nowMs`. This is
59
+ * the single staleness/parse policy shared by `pm gc --scope locks` (which acts
60
+ * on it) and the `pm health` locks check (which only reports it).
61
+ */
62
+ export declare function classifyLockContent(file: string, raw: string, nowMs: number): LockContentClassification;
63
+ export interface LockHealthScan {
64
+ /** number of *.lock files examined (ghost files that vanish mid-scan are skipped) */
65
+ scanned: number;
66
+ active_lock_count: number;
67
+ stale_lock_count: number;
68
+ unreadable_lock_count: number;
69
+ /** invalid JSON, wrong shape, or an unparseable created_at timestamp */
70
+ unparseable_lock_count: number;
71
+ }
72
+ /**
73
+ * Read-only locks scan for `pm health`: classifies every lock file with the
74
+ * same policy `pm gc --scope locks` uses but never removes anything.
75
+ */
76
+ export declare function scanLockHealth(pmRoot: string, now?: number): Promise<LockHealthScan>;
46
77
  export declare function runLockGc(pmRoot: string, options: LockGcOptions): Promise<LockGcResult>;
@@ -9,7 +9,7 @@
9
9
  * Zero ttl means any lock older than 0 ms is stale. Negative ttl means always stale.
10
10
  */
11
11
 
12
- !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]="1a412027-5c73-58d4-b696-4a909dde88a8")}catch(e){}}();
12
+ !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]="925ee778-ffe1-587a-9916-bdbd16065381")}catch(e){}}();
13
13
  import fs from "node:fs/promises";
14
14
  import path from "node:path";
15
15
  function parseLock(raw) {
@@ -40,6 +40,114 @@ function isErrno(error, code) {
40
40
  "code" in error &&
41
41
  error.code === code);
42
42
  }
43
+ /**
44
+ * Pure classification of one lock file's raw content against `nowMs`. This is
45
+ * the single staleness/parse policy shared by `pm gc --scope locks` (which acts
46
+ * on it) and the `pm health` locks check (which only reports it).
47
+ */
48
+ export function classifyLockContent(file, raw, nowMs) {
49
+ const lock = parseLock(raw);
50
+ if (lock === null) {
51
+ return {
52
+ detail: "unparseable_json",
53
+ entry: {
54
+ file,
55
+ id: null,
56
+ owner: null,
57
+ created_at: null,
58
+ ttl_seconds: null,
59
+ age_seconds: null,
60
+ stale: false,
61
+ reason: "unparseable",
62
+ },
63
+ };
64
+ }
65
+ const createdAtMs = Date.parse(lock.created_at);
66
+ if (!Number.isFinite(createdAtMs)) {
67
+ return {
68
+ detail: "invalid_timestamp",
69
+ entry: {
70
+ file,
71
+ id: lock.id,
72
+ owner: lock.owner,
73
+ created_at: lock.created_at,
74
+ ttl_seconds: lock.ttl_seconds,
75
+ age_seconds: null,
76
+ stale: false,
77
+ reason: "unparseable",
78
+ },
79
+ };
80
+ }
81
+ const ageMs = nowMs - createdAtMs;
82
+ const age_seconds = Math.floor(ageMs / 1000);
83
+ const stale = ageMs > lock.ttl_seconds * 1000;
84
+ return {
85
+ detail: stale ? "expired" : "active",
86
+ entry: {
87
+ file,
88
+ id: lock.id,
89
+ owner: lock.owner,
90
+ created_at: lock.created_at,
91
+ ttl_seconds: lock.ttl_seconds,
92
+ age_seconds,
93
+ stale,
94
+ reason: stale ? "expired" : "active",
95
+ },
96
+ };
97
+ }
98
+ /**
99
+ * Read-only locks scan for `pm health`: classifies every lock file with the
100
+ * same policy `pm gc --scope locks` uses but never removes anything.
101
+ */
102
+ export async function scanLockHealth(pmRoot, now) {
103
+ const nowMs = now ?? Date.now();
104
+ const locksDir = path.join(pmRoot, "locks");
105
+ const scan = {
106
+ scanned: 0,
107
+ active_lock_count: 0,
108
+ stale_lock_count: 0,
109
+ unreadable_lock_count: 0,
110
+ unparseable_lock_count: 0,
111
+ };
112
+ let dirEntries;
113
+ try {
114
+ const rawDir = await fs.readdir(locksDir);
115
+ dirEntries = rawDir.filter((f) => f.endsWith(".lock")).sort();
116
+ }
117
+ catch (error) {
118
+ if (isErrno(error, "ENOENT")) {
119
+ return scan;
120
+ }
121
+ throw error;
122
+ }
123
+ for (const file of dirEntries) {
124
+ let raw;
125
+ try {
126
+ raw = await fs.readFile(path.join(locksDir, file), "utf8");
127
+ }
128
+ catch (readError) {
129
+ if (isErrno(readError, "ENOENT")) {
130
+ // Disappeared between readdir and readFile — treat as a ghost; skip quietly.
131
+ continue;
132
+ }
133
+ scan.scanned += 1;
134
+ scan.unreadable_lock_count += 1;
135
+ continue;
136
+ }
137
+ scan.scanned += 1;
138
+ const { detail } = classifyLockContent(file, raw, nowMs);
139
+ if (detail === "active") {
140
+ scan.active_lock_count += 1;
141
+ }
142
+ else if (detail === "expired") {
143
+ scan.stale_lock_count += 1;
144
+ }
145
+ else {
146
+ scan.unparseable_lock_count += 1;
147
+ }
148
+ }
149
+ return scan;
150
+ }
43
151
  export async function runLockGc(pmRoot, options) {
44
152
  const { dryRun, hooks } = options;
45
153
  const nowMs = options.now ?? Date.now();
@@ -96,55 +204,23 @@ export async function runLockGc(pmRoot, options) {
96
204
  });
97
205
  continue;
98
206
  }
99
- // --- parse ---
100
- const lock = parseLock(raw);
101
- if (lock === null) {
207
+ // --- classify (shared with the read-only pm health locks scan) ---
208
+ const classified = classifyLockContent(file, raw, nowMs);
209
+ if (classified.detail === "unparseable_json") {
102
210
  result.warnings.push(`lock_unparseable:${file}`);
103
211
  result.retained.push(relPath);
104
- result.entries.push({
105
- file,
106
- id: null,
107
- owner: null,
108
- created_at: null,
109
- ttl_seconds: null,
110
- age_seconds: null,
111
- stale: false,
112
- reason: "unparseable",
113
- });
212
+ result.entries.push(classified.entry);
114
213
  continue;
115
214
  }
116
- // --- validate timestamp ---
117
- const createdAtMs = Date.parse(lock.created_at);
118
- if (!Number.isFinite(createdAtMs)) {
215
+ if (classified.detail === "invalid_timestamp") {
119
216
  result.warnings.push(`lock_invalid_timestamp:${file}`);
120
217
  result.retained.push(relPath);
121
- result.entries.push({
122
- file,
123
- id: lock.id,
124
- owner: lock.owner,
125
- created_at: lock.created_at,
126
- ttl_seconds: lock.ttl_seconds,
127
- age_seconds: null,
128
- stale: false,
129
- reason: "unparseable",
130
- });
218
+ result.entries.push(classified.entry);
131
219
  continue;
132
220
  }
133
- const ageMs = nowMs - createdAtMs;
134
- const age_seconds = Math.floor(ageMs / 1000);
135
- const stale = ageMs > lock.ttl_seconds * 1000;
136
- if (!stale) {
221
+ if (classified.detail === "active") {
137
222
  result.retained.push(relPath);
138
- result.entries.push({
139
- file,
140
- id: lock.id,
141
- owner: lock.owner,
142
- created_at: lock.created_at,
143
- ttl_seconds: lock.ttl_seconds,
144
- age_seconds,
145
- stale: false,
146
- reason: "active",
147
- });
223
+ result.entries.push(classified.entry);
148
224
  continue;
149
225
  }
150
226
  // --- stale: remove or preview ---
@@ -156,16 +232,7 @@ export async function runLockGc(pmRoot, options) {
156
232
  if (!isErrno(unlinkError, "ENOENT")) {
157
233
  result.warnings.push(`lock_remove_failed:${file}`);
158
234
  result.retained.push(relPath);
159
- result.entries.push({
160
- file,
161
- id: lock.id,
162
- owner: lock.owner,
163
- created_at: lock.created_at,
164
- ttl_seconds: lock.ttl_seconds,
165
- age_seconds,
166
- stale: true,
167
- reason: "expired",
168
- });
235
+ result.entries.push(classified.entry);
169
236
  continue;
170
237
  }
171
238
  // ENOENT on unlink = already gone (race); treat as removed
@@ -177,18 +244,9 @@ export async function runLockGc(pmRoot, options) {
177
244
  }
178
245
  }
179
246
  result.removed.push(relPath);
180
- result.entries.push({
181
- file,
182
- id: lock.id,
183
- owner: lock.owner,
184
- created_at: lock.created_at,
185
- ttl_seconds: lock.ttl_seconds,
186
- age_seconds,
187
- stale: true,
188
- reason: "expired",
189
- });
247
+ result.entries.push(classified.entry);
190
248
  }
191
249
  return result;
192
250
  }
193
251
  //# sourceMappingURL=lock-gc.js.map
194
- //# debugId=1a412027-5c73-58d4-b696-4a909dde88a8
252
+ //# debugId=925ee778-ffe1-587a-9916-bdbd16065381
@@ -1 +1 @@
1
- {"version":3,"file":"lock-gc.js","sources":["core/lock/lock-gc.ts"],"sourceRoot":"/","sourcesContent":["/**\n * lock-gc.ts — Stale lock file garbage collector.\n *\n * Safety-first policy: if a lock file cannot be read or parsed, it is RETAINED\n * rather than deleted. We only remove locks whose staleness can be conclusively\n * determined from the lock's own embedded `ttl_seconds` field.\n *\n * Staleness formula: (now_ms - Date.parse(created_at)) > ttl_seconds * 1000\n * Zero ttl means any lock older than 0 ms is stale. Negative ttl means always stale.\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface StaleLockScanEntry {\n /** e.g. \"pm-abc1.lock\" */\n file: string;\n /** parsed id, or null if unparseable */\n id: string | null;\n owner: string | null;\n created_at: string | null;\n ttl_seconds: number | null;\n /** floor((now - created_at)/1000), null if unparseable */\n age_seconds: number | null;\n /** true only when classified expired */\n stale: boolean;\n reason: \"expired\" | \"active\" | \"unparseable\";\n}\n\nexport interface LockGcHooks {\n onRead?: (lockPath: string) => Promise<string[]> | string[];\n onWrite?: (lockPath: string) => Promise<string[]> | string[];\n}\n\nexport interface LockGcOptions {\n dryRun: boolean;\n /** epoch ms; defaults to Date.now() — injectable for deterministic tests */\n now?: number;\n hooks?: LockGcHooks;\n}\n\nexport interface LockGcResult {\n /** number of *.lock files examined */\n scanned: number;\n /** relative paths like \"locks/pm-abc1.lock\" (removed, or would-remove under dryRun) */\n removed: string[];\n /** relative paths retained (active OR unparseable — never delete what we can't reason about) */\n retained: string[];\n warnings: string[];\n /** one per scanned *.lock file, sorted by file name asc */\n entries: StaleLockScanEntry[];\n}\n\ninterface ParsedLock {\n id: string;\n owner: string;\n created_at: string;\n ttl_seconds: number;\n}\n\nfunction parseLock(raw: string): ParsedLock | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw) as unknown;\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n const candidate = parsed as Record<string, unknown>;\n const { id, owner, created_at, ttl_seconds } = candidate;\n if (\n typeof id !== \"string\" ||\n typeof owner !== \"string\" ||\n typeof created_at !== \"string\" ||\n typeof ttl_seconds !== \"number\" ||\n !Number.isFinite(ttl_seconds)\n ) {\n return null;\n }\n return { id, owner, created_at, ttl_seconds };\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as { code?: string }).code === code\n );\n}\n\nexport async function runLockGc(pmRoot: string, options: LockGcOptions): Promise<LockGcResult> {\n const { dryRun, hooks } = options;\n const nowMs = options.now ?? Date.now();\n const locksDir = path.join(pmRoot, \"locks\");\n\n const result: LockGcResult = {\n scanned: 0,\n removed: [],\n retained: [],\n warnings: [],\n entries: [],\n };\n\n let dirEntries: string[];\n try {\n const raw = await fs.readdir(locksDir);\n dirEntries = raw.filter((f) => f.endsWith(\".lock\")).sort();\n } catch (error: unknown) {\n if (isErrno(error, \"ENOENT\")) {\n return result;\n }\n throw error;\n }\n\n for (const file of dirEntries) {\n result.scanned += 1;\n const absPath = path.join(locksDir, file);\n const relPath = `locks/${file}`;\n\n // --- onRead hook ---\n if (hooks?.onRead) {\n const hookWarnings = await hooks.onRead(absPath);\n result.warnings.push(...hookWarnings);\n }\n\n // --- read file ---\n let raw: string;\n try {\n raw = await fs.readFile(absPath, \"utf8\");\n } catch (readError: unknown) {\n if (isErrno(readError, \"ENOENT\")) {\n // Disappeared between readdir and readFile — treat as a ghost; skip quietly.\n result.scanned -= 1;\n continue;\n }\n result.warnings.push(`lock_unreadable:${file}`);\n result.retained.push(relPath);\n result.entries.push({\n file,\n id: null,\n owner: null,\n created_at: null,\n ttl_seconds: null,\n age_seconds: null,\n stale: false,\n reason: \"unparseable\",\n });\n continue;\n }\n\n // --- parse ---\n const lock = parseLock(raw);\n if (lock === null) {\n result.warnings.push(`lock_unparseable:${file}`);\n result.retained.push(relPath);\n result.entries.push({\n file,\n id: null,\n owner: null,\n created_at: null,\n ttl_seconds: null,\n age_seconds: null,\n stale: false,\n reason: \"unparseable\",\n });\n continue;\n }\n\n // --- validate timestamp ---\n const createdAtMs = Date.parse(lock.created_at);\n if (!Number.isFinite(createdAtMs)) {\n result.warnings.push(`lock_invalid_timestamp:${file}`);\n result.retained.push(relPath);\n result.entries.push({\n file,\n id: lock.id,\n owner: lock.owner,\n created_at: lock.created_at,\n ttl_seconds: lock.ttl_seconds,\n age_seconds: null,\n stale: false,\n reason: \"unparseable\",\n });\n continue;\n }\n\n const ageMs = nowMs - createdAtMs;\n const age_seconds = Math.floor(ageMs / 1000);\n const stale = ageMs > lock.ttl_seconds * 1000;\n\n if (!stale) {\n result.retained.push(relPath);\n result.entries.push({\n file,\n id: lock.id,\n owner: lock.owner,\n created_at: lock.created_at,\n ttl_seconds: lock.ttl_seconds,\n age_seconds,\n stale: false,\n reason: \"active\",\n });\n continue;\n }\n\n // --- stale: remove or preview ---\n if (!dryRun) {\n try {\n await fs.unlink(absPath);\n } catch (unlinkError: unknown) {\n if (!isErrno(unlinkError, \"ENOENT\")) {\n result.warnings.push(`lock_remove_failed:${file}`);\n result.retained.push(relPath);\n result.entries.push({\n file,\n id: lock.id,\n owner: lock.owner,\n created_at: lock.created_at,\n ttl_seconds: lock.ttl_seconds,\n age_seconds,\n stale: true,\n reason: \"expired\",\n });\n continue;\n }\n // ENOENT on unlink = already gone (race); treat as removed\n }\n // onWrite hook after successful removal\n if (hooks?.onWrite) {\n const hookWarnings = await hooks.onWrite(absPath);\n result.warnings.push(...hookWarnings);\n }\n }\n\n result.removed.push(relPath);\n result.entries.push({\n file,\n id: lock.id,\n owner: lock.owner,\n created_at: lock.created_at,\n ttl_seconds: lock.ttl_seconds,\n age_seconds,\n stale: true,\n reason: \"expired\",\n });\n }\n\n return result;\n}\n"],"names":[],"mappings":"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","debugId":"1a412027-5c73-58d4-b696-4a909dde88a8"}
1
+ {"version":3,"file":"lock-gc.js","sources":["core/lock/lock-gc.ts"],"sourceRoot":"/","sourcesContent":["/**\n * lock-gc.ts — Stale lock file garbage collector.\n *\n * Safety-first policy: if a lock file cannot be read or parsed, it is RETAINED\n * rather than deleted. We only remove locks whose staleness can be conclusively\n * determined from the lock's own embedded `ttl_seconds` field.\n *\n * Staleness formula: (now_ms - Date.parse(created_at)) > ttl_seconds * 1000\n * Zero ttl means any lock older than 0 ms is stale. Negative ttl means always stale.\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface StaleLockScanEntry {\n /** e.g. \"pm-abc1.lock\" */\n file: string;\n /** parsed id, or null if unparseable */\n id: string | null;\n owner: string | null;\n created_at: string | null;\n ttl_seconds: number | null;\n /** floor((now - created_at)/1000), null if unparseable */\n age_seconds: number | null;\n /** true only when classified expired */\n stale: boolean;\n reason: \"expired\" | \"active\" | \"unparseable\";\n}\n\nexport interface LockGcHooks {\n onRead?: (lockPath: string) => Promise<string[]> | string[];\n onWrite?: (lockPath: string) => Promise<string[]> | string[];\n}\n\nexport interface LockGcOptions {\n dryRun: boolean;\n /** epoch ms; defaults to Date.now() — injectable for deterministic tests */\n now?: number;\n hooks?: LockGcHooks;\n}\n\nexport interface LockGcResult {\n /** number of *.lock files examined */\n scanned: number;\n /** relative paths like \"locks/pm-abc1.lock\" (removed, or would-remove under dryRun) */\n removed: string[];\n /** relative paths retained (active OR unparseable — never delete what we can't reason about) */\n retained: string[];\n warnings: string[];\n /** one per scanned *.lock file, sorted by file name asc */\n entries: StaleLockScanEntry[];\n}\n\ninterface ParsedLock {\n id: string;\n owner: string;\n created_at: string;\n ttl_seconds: number;\n}\n\nfunction parseLock(raw: string): ParsedLock | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw) as unknown;\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n const candidate = parsed as Record<string, unknown>;\n const { id, owner, created_at, ttl_seconds } = candidate;\n if (\n typeof id !== \"string\" ||\n typeof owner !== \"string\" ||\n typeof created_at !== \"string\" ||\n typeof ttl_seconds !== \"number\" ||\n !Number.isFinite(ttl_seconds)\n ) {\n return null;\n }\n return { id, owner, created_at, ttl_seconds };\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as { code?: string }).code === code\n );\n}\n\n/**\n * Fine-grained classification outcome for one readable lock file's content.\n * `unparseable_json` and `invalid_timestamp` both surface as the coarse\n * `reason: \"unparseable\"` on the scan entry (gc retains both), but stay\n * distinct here so callers can emit precise warnings.\n */\nexport type LockClassificationDetail = \"active\" | \"expired\" | \"unparseable_json\" | \"invalid_timestamp\";\n\nexport interface LockContentClassification {\n detail: LockClassificationDetail;\n entry: StaleLockScanEntry;\n}\n\n/**\n * Pure classification of one lock file's raw content against `nowMs`. This is\n * the single staleness/parse policy shared by `pm gc --scope locks` (which acts\n * on it) and the `pm health` locks check (which only reports it).\n */\nexport function classifyLockContent(file: string, raw: string, nowMs: number): LockContentClassification {\n const lock = parseLock(raw);\n if (lock === null) {\n return {\n detail: \"unparseable_json\",\n entry: {\n file,\n id: null,\n owner: null,\n created_at: null,\n ttl_seconds: null,\n age_seconds: null,\n stale: false,\n reason: \"unparseable\",\n },\n };\n }\n const createdAtMs = Date.parse(lock.created_at);\n if (!Number.isFinite(createdAtMs)) {\n return {\n detail: \"invalid_timestamp\",\n entry: {\n file,\n id: lock.id,\n owner: lock.owner,\n created_at: lock.created_at,\n ttl_seconds: lock.ttl_seconds,\n age_seconds: null,\n stale: false,\n reason: \"unparseable\",\n },\n };\n }\n const ageMs = nowMs - createdAtMs;\n const age_seconds = Math.floor(ageMs / 1000);\n const stale = ageMs > lock.ttl_seconds * 1000;\n return {\n detail: stale ? \"expired\" : \"active\",\n entry: {\n file,\n id: lock.id,\n owner: lock.owner,\n created_at: lock.created_at,\n ttl_seconds: lock.ttl_seconds,\n age_seconds,\n stale,\n reason: stale ? \"expired\" : \"active\",\n },\n };\n}\n\nexport interface LockHealthScan {\n /** number of *.lock files examined (ghost files that vanish mid-scan are skipped) */\n scanned: number;\n active_lock_count: number;\n stale_lock_count: number;\n unreadable_lock_count: number;\n /** invalid JSON, wrong shape, or an unparseable created_at timestamp */\n unparseable_lock_count: number;\n}\n\n/**\n * Read-only locks scan for `pm health`: classifies every lock file with the\n * same policy `pm gc --scope locks` uses but never removes anything.\n */\nexport async function scanLockHealth(pmRoot: string, now?: number): Promise<LockHealthScan> {\n const nowMs = now ?? Date.now();\n const locksDir = path.join(pmRoot, \"locks\");\n const scan: LockHealthScan = {\n scanned: 0,\n active_lock_count: 0,\n stale_lock_count: 0,\n unreadable_lock_count: 0,\n unparseable_lock_count: 0,\n };\n\n let dirEntries: string[];\n try {\n const rawDir = await fs.readdir(locksDir);\n dirEntries = rawDir.filter((f) => f.endsWith(\".lock\")).sort();\n } catch (error: unknown) {\n if (isErrno(error, \"ENOENT\")) {\n return scan;\n }\n throw error;\n }\n\n for (const file of dirEntries) {\n let raw: string;\n try {\n raw = await fs.readFile(path.join(locksDir, file), \"utf8\");\n } catch (readError: unknown) {\n if (isErrno(readError, \"ENOENT\")) {\n // Disappeared between readdir and readFile — treat as a ghost; skip quietly.\n continue;\n }\n scan.scanned += 1;\n scan.unreadable_lock_count += 1;\n continue;\n }\n scan.scanned += 1;\n const { detail } = classifyLockContent(file, raw, nowMs);\n if (detail === \"active\") {\n scan.active_lock_count += 1;\n } else if (detail === \"expired\") {\n scan.stale_lock_count += 1;\n } else {\n scan.unparseable_lock_count += 1;\n }\n }\n\n return scan;\n}\n\nexport async function runLockGc(pmRoot: string, options: LockGcOptions): Promise<LockGcResult> {\n const { dryRun, hooks } = options;\n const nowMs = options.now ?? Date.now();\n const locksDir = path.join(pmRoot, \"locks\");\n\n const result: LockGcResult = {\n scanned: 0,\n removed: [],\n retained: [],\n warnings: [],\n entries: [],\n };\n\n let dirEntries: string[];\n try {\n const raw = await fs.readdir(locksDir);\n dirEntries = raw.filter((f) => f.endsWith(\".lock\")).sort();\n } catch (error: unknown) {\n if (isErrno(error, \"ENOENT\")) {\n return result;\n }\n throw error;\n }\n\n for (const file of dirEntries) {\n result.scanned += 1;\n const absPath = path.join(locksDir, file);\n const relPath = `locks/${file}`;\n\n // --- onRead hook ---\n if (hooks?.onRead) {\n const hookWarnings = await hooks.onRead(absPath);\n result.warnings.push(...hookWarnings);\n }\n\n // --- read file ---\n let raw: string;\n try {\n raw = await fs.readFile(absPath, \"utf8\");\n } catch (readError: unknown) {\n if (isErrno(readError, \"ENOENT\")) {\n // Disappeared between readdir and readFile — treat as a ghost; skip quietly.\n result.scanned -= 1;\n continue;\n }\n result.warnings.push(`lock_unreadable:${file}`);\n result.retained.push(relPath);\n result.entries.push({\n file,\n id: null,\n owner: null,\n created_at: null,\n ttl_seconds: null,\n age_seconds: null,\n stale: false,\n reason: \"unparseable\",\n });\n continue;\n }\n\n // --- classify (shared with the read-only pm health locks scan) ---\n const classified = classifyLockContent(file, raw, nowMs);\n if (classified.detail === \"unparseable_json\") {\n result.warnings.push(`lock_unparseable:${file}`);\n result.retained.push(relPath);\n result.entries.push(classified.entry);\n continue;\n }\n if (classified.detail === \"invalid_timestamp\") {\n result.warnings.push(`lock_invalid_timestamp:${file}`);\n result.retained.push(relPath);\n result.entries.push(classified.entry);\n continue;\n }\n if (classified.detail === \"active\") {\n result.retained.push(relPath);\n result.entries.push(classified.entry);\n continue;\n }\n\n // --- stale: remove or preview ---\n if (!dryRun) {\n try {\n await fs.unlink(absPath);\n } catch (unlinkError: unknown) {\n if (!isErrno(unlinkError, \"ENOENT\")) {\n result.warnings.push(`lock_remove_failed:${file}`);\n result.retained.push(relPath);\n result.entries.push(classified.entry);\n continue;\n }\n // ENOENT on unlink = already gone (race); treat as removed\n }\n // onWrite hook after successful removal\n if (hooks?.onWrite) {\n const hookWarnings = await hooks.onWrite(absPath);\n result.warnings.push(...hookWarnings);\n }\n }\n\n result.removed.push(relPath);\n result.entries.push(classified.entry);\n }\n\n return result;\n}\n"],"names":[],"mappings":"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","debugId":"925ee778-ffe1-587a-9916-bdbd16065381"}
@@ -1,5 +1,5 @@
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]="c965da5d-6609-5258-ba49-a57475f7f2ef")}catch(e){}}();
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]="7804d4bf-ff46-50af-9af5-533f83c3d445")}catch(e){}}();
3
3
  import readline from "node:readline/promises";
4
4
  import { stdin as input, stdout as output } from "node:process";
5
5
  import { resolveGlobalPmRoot } from "../store/paths.js";
@@ -13,6 +13,21 @@ function parseBooleanLike(value) {
13
13
  }
14
14
  return TELEMETRY_PROMPT_DISABLE_VALUES.has(value.trim().toLowerCase());
15
15
  }
16
+ // pm-0hx2: the CI guard must fire when CI is SET to a truthy value (CI=true,
17
+ // CI=1, ...). An unset or empty CI behaves as "not CI", and the conventional
18
+ // falsy spellings ("0", "false", "no", "off") explicitly opt back out of the
19
+ // guard. This is intentionally NOT parseBooleanLike, which matches only the
20
+ // disable set and would invert the check.
21
+ function isTruthyEnvValue(value) {
22
+ if (typeof value !== "string") {
23
+ return false;
24
+ }
25
+ const normalized = value.trim().toLowerCase();
26
+ if (normalized.length === 0) {
27
+ return false;
28
+ }
29
+ return !TELEMETRY_PROMPT_DISABLE_VALUES.has(normalized);
30
+ }
16
31
  function shouldSkipTelemetryPrompt(commandPath, globalOptions) {
17
32
  if (globalOptions.json === true || globalOptions.quiet === true) {
18
33
  return true;
@@ -23,7 +38,7 @@ function shouldSkipTelemetryPrompt(commandPath, globalOptions) {
23
38
  if (parseBooleanLike(process.env[TELEMETRY_PROMPT_DISABLE_ENV])) {
24
39
  return true;
25
40
  }
26
- if (parseBooleanLike(process.env.CI)) {
41
+ if (isTruthyEnvValue(process.env.CI)) {
27
42
  return true;
28
43
  }
29
44
  const normalizedCommandPath = commandPath.trim().toLowerCase();
@@ -79,4 +94,4 @@ export async function maybeRunFirstUseTelemetryPrompt(commandPath, globalOptions
79
94
  }
80
95
  }
81
96
  //# sourceMappingURL=consent.js.map
82
- //# debugId=c965da5d-6609-5258-ba49-a57475f7f2ef
97
+ //# debugId=7804d4bf-ff46-50af-9af5-533f83c3d445
@@ -1 +1 @@
1
- {"version":3,"file":"consent.js","sources":["core/telemetry/consent.ts"],"sourceRoot":"/","sourcesContent":["import readline from \"node:readline/promises\";\nimport { stdin as input, stdout as output } from \"node:process\";\nimport type { GlobalOptions } from \"../shared/command-types.js\";\nimport { resolveGlobalPmRoot } from \"../store/paths.js\";\nimport { readSettings, writeSettings } from \"../store/settings.js\";\n\nconst TELEMETRY_PROMPT_DISABLE_ENV = \"PM_TELEMETRY_PROMPT\";\nconst TELEMETRY_PROMPT_DISABLE_VALUES = new Set([\"0\", \"false\", \"no\", \"off\"]);\nconst TELEMETRY_PROMPT_SKIPPED_COMMANDS = new Set([\"init\", \"completion\", \"completion-statuses\", \"completion-tags\", \"completion-types\"]);\n\nfunction parseBooleanLike(value: string | undefined): boolean {\n if (typeof value !== \"string\") {\n return false;\n }\n return TELEMETRY_PROMPT_DISABLE_VALUES.has(value.trim().toLowerCase());\n}\n\nfunction shouldSkipTelemetryPrompt(commandPath: string, globalOptions: GlobalOptions): boolean {\n if (globalOptions.json === true || globalOptions.quiet === true) {\n return true;\n }\n if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {\n return true;\n }\n if (parseBooleanLike(process.env[TELEMETRY_PROMPT_DISABLE_ENV])) {\n return true;\n }\n if (parseBooleanLike(process.env.CI)) {\n return true;\n }\n const normalizedCommandPath = commandPath.trim().toLowerCase();\n if (normalizedCommandPath.length === 0) {\n return true;\n }\n return TELEMETRY_PROMPT_SKIPPED_COMMANDS.has(normalizedCommandPath);\n}\n\nfunction parseTelemetryPromptAnswer(answer: string): boolean {\n const normalized = answer.trim().toLowerCase();\n if (normalized === \"n\" || normalized === \"no\") {\n return false;\n }\n return true;\n}\n\nasync function promptTelemetryConsent(currentDefault: boolean): Promise<boolean> {\n const defaultLabel = currentDefault ? \"Y/n\" : \"y/N\";\n const prompt =\n `pm telemetry helps improve reliability and usage diagnostics.\\n` +\n `It is enabled by default and can be changed anytime via:\\n` +\n `pm config global set telemetry-tracking --policy disabled\\n` +\n `Enable telemetry now? [${defaultLabel}] `;\n const rl = readline.createInterface({ input, output });\n try {\n const answer = await rl.question(prompt);\n if (answer.trim().length === 0) {\n return currentDefault;\n }\n return parseTelemetryPromptAnswer(answer);\n } finally {\n rl.close();\n }\n}\n\nexport async function maybeRunFirstUseTelemetryPrompt(commandPath: string, globalOptions: GlobalOptions): Promise<void> {\n if (shouldSkipTelemetryPrompt(commandPath, globalOptions)) {\n return;\n }\n try {\n const globalPmRoot = resolveGlobalPmRoot(process.cwd());\n const settings = await readSettings(globalPmRoot);\n if (settings.telemetry.first_run_prompt_completed) {\n return;\n }\n const enabled = await promptTelemetryConsent(settings.telemetry.enabled);\n settings.telemetry.enabled = enabled;\n settings.telemetry.first_run_prompt_completed = true;\n await writeSettings(globalPmRoot, settings, \"telemetry:first_run_prompt\");\n output.write(\n `Telemetry ${enabled ? \"enabled\" : \"disabled\"} for this CLI install. ` +\n `Update any time with pm config global set telemetry-tracking --policy ${enabled ? \"disabled\" : \"enabled\"}.\\n`,\n );\n } catch {\n // Prompt and persistence failures should never block command execution.\n }\n}\n"],"names":[],"mappings":";;AAAA,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,4BAA4B,GAAG,qBAAqB,CAAC;AAC3D,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7E,MAAM,iCAAiC,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAExI,SAAS,gBAAgB,CAAC,KAAyB;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,+BAA+B,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,yBAAyB,CAAC,WAAmB,EAAE,aAA4B;IAClF,IAAI,aAAa,CAAC,IAAI,KAAK,IAAI,IAAI,aAAa,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,qBAAqB,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/D,IAAI,qBAAqB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,iCAAiC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,0BAA0B,CAAC,MAAc;IAChD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/C,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,cAAuB;IAC3D,MAAM,YAAY,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IACpD,MAAM,MAAM,GACV,iEAAiE;QACjE,4DAA4D;QAC5D,6DAA6D;QAC7D,0BAA0B,YAAY,IAAI,CAAC;IAC7C,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,OAAO,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,+BAA+B,CAAC,WAAmB,EAAE,aAA4B;IACrG,IAAI,yBAAyB,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,CAAC;QAC1D,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;QAClD,IAAI,QAAQ,CAAC,SAAS,CAAC,0BAA0B,EAAE,CAAC;YAClD,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACzE,QAAQ,CAAC,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC;QACrC,QAAQ,CAAC,SAAS,CAAC,0BAA0B,GAAG,IAAI,CAAC;QACrD,MAAM,aAAa,CAAC,YAAY,EAAE,QAAQ,EAAE,4BAA4B,CAAC,CAAC;QAC1E,MAAM,CAAC,KAAK,CACV,aAAa,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,yBAAyB;YACpE,yEAAyE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,KAAK,CACjH,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;IAC1E,CAAC;AACH,CAAC","debugId":"c965da5d-6609-5258-ba49-a57475f7f2ef"}
1
+ {"version":3,"file":"consent.js","sources":["core/telemetry/consent.ts"],"sourceRoot":"/","sourcesContent":["import readline from \"node:readline/promises\";\nimport { stdin as input, stdout as output } from \"node:process\";\nimport type { GlobalOptions } from \"../shared/command-types.js\";\nimport { resolveGlobalPmRoot } from \"../store/paths.js\";\nimport { readSettings, writeSettings } from \"../store/settings.js\";\n\nconst TELEMETRY_PROMPT_DISABLE_ENV = \"PM_TELEMETRY_PROMPT\";\nconst TELEMETRY_PROMPT_DISABLE_VALUES = new Set([\"0\", \"false\", \"no\", \"off\"]);\nconst TELEMETRY_PROMPT_SKIPPED_COMMANDS = new Set([\"init\", \"completion\", \"completion-statuses\", \"completion-tags\", \"completion-types\"]);\n\nfunction parseBooleanLike(value: string | undefined): boolean {\n if (typeof value !== \"string\") {\n return false;\n }\n return TELEMETRY_PROMPT_DISABLE_VALUES.has(value.trim().toLowerCase());\n}\n\n// pm-0hx2: the CI guard must fire when CI is SET to a truthy value (CI=true,\n// CI=1, ...). An unset or empty CI behaves as \"not CI\", and the conventional\n// falsy spellings (\"0\", \"false\", \"no\", \"off\") explicitly opt back out of the\n// guard. This is intentionally NOT parseBooleanLike, which matches only the\n// disable set and would invert the check.\nfunction isTruthyEnvValue(value: string | undefined): boolean {\n if (typeof value !== \"string\") {\n return false;\n }\n const normalized = value.trim().toLowerCase();\n if (normalized.length === 0) {\n return false;\n }\n return !TELEMETRY_PROMPT_DISABLE_VALUES.has(normalized);\n}\n\nfunction shouldSkipTelemetryPrompt(commandPath: string, globalOptions: GlobalOptions): boolean {\n if (globalOptions.json === true || globalOptions.quiet === true) {\n return true;\n }\n if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {\n return true;\n }\n if (parseBooleanLike(process.env[TELEMETRY_PROMPT_DISABLE_ENV])) {\n return true;\n }\n if (isTruthyEnvValue(process.env.CI)) {\n return true;\n }\n const normalizedCommandPath = commandPath.trim().toLowerCase();\n if (normalizedCommandPath.length === 0) {\n return true;\n }\n return TELEMETRY_PROMPT_SKIPPED_COMMANDS.has(normalizedCommandPath);\n}\n\nfunction parseTelemetryPromptAnswer(answer: string): boolean {\n const normalized = answer.trim().toLowerCase();\n if (normalized === \"n\" || normalized === \"no\") {\n return false;\n }\n return true;\n}\n\nasync function promptTelemetryConsent(currentDefault: boolean): Promise<boolean> {\n const defaultLabel = currentDefault ? \"Y/n\" : \"y/N\";\n const prompt =\n `pm telemetry helps improve reliability and usage diagnostics.\\n` +\n `It is enabled by default and can be changed anytime via:\\n` +\n `pm config global set telemetry-tracking --policy disabled\\n` +\n `Enable telemetry now? [${defaultLabel}] `;\n const rl = readline.createInterface({ input, output });\n try {\n const answer = await rl.question(prompt);\n if (answer.trim().length === 0) {\n return currentDefault;\n }\n return parseTelemetryPromptAnswer(answer);\n } finally {\n rl.close();\n }\n}\n\nexport async function maybeRunFirstUseTelemetryPrompt(commandPath: string, globalOptions: GlobalOptions): Promise<void> {\n if (shouldSkipTelemetryPrompt(commandPath, globalOptions)) {\n return;\n }\n try {\n const globalPmRoot = resolveGlobalPmRoot(process.cwd());\n const settings = await readSettings(globalPmRoot);\n if (settings.telemetry.first_run_prompt_completed) {\n return;\n }\n const enabled = await promptTelemetryConsent(settings.telemetry.enabled);\n settings.telemetry.enabled = enabled;\n settings.telemetry.first_run_prompt_completed = true;\n await writeSettings(globalPmRoot, settings, \"telemetry:first_run_prompt\");\n output.write(\n `Telemetry ${enabled ? \"enabled\" : \"disabled\"} for this CLI install. ` +\n `Update any time with pm config global set telemetry-tracking --policy ${enabled ? \"disabled\" : \"enabled\"}.\\n`,\n );\n } catch {\n // Prompt and persistence failures should never block command execution.\n }\n}\n"],"names":[],"mappings":";;AAAA,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,4BAA4B,GAAG,qBAAqB,CAAC;AAC3D,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7E,MAAM,iCAAiC,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAExI,SAAS,gBAAgB,CAAC,KAAyB;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,+BAA+B,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,0CAA0C;AAC1C,SAAS,gBAAgB,CAAC,KAAyB;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,CAAC,+BAA+B,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,yBAAyB,CAAC,WAAmB,EAAE,aAA4B;IAClF,IAAI,aAAa,CAAC,IAAI,KAAK,IAAI,IAAI,aAAa,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,qBAAqB,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/D,IAAI,qBAAqB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,iCAAiC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,0BAA0B,CAAC,MAAc;IAChD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/C,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,cAAuB;IAC3D,MAAM,YAAY,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IACpD,MAAM,MAAM,GACV,iEAAiE;QACjE,4DAA4D;QAC5D,6DAA6D;QAC7D,0BAA0B,YAAY,IAAI,CAAC;IAC7C,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,OAAO,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,+BAA+B,CAAC,WAAmB,EAAE,aAA4B;IACrG,IAAI,yBAAyB,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,CAAC;QAC1D,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;QAClD,IAAI,QAAQ,CAAC,SAAS,CAAC,0BAA0B,EAAE,CAAC;YAClD,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACzE,QAAQ,CAAC,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC;QACrC,QAAQ,CAAC,SAAS,CAAC,0BAA0B,GAAG,IAAI,CAAC;QACrD,MAAM,aAAa,CAAC,YAAY,EAAE,QAAQ,EAAE,4BAA4B,CAAC,CAAC;QAC1E,MAAM,CAAC,KAAK,CACV,aAAa,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,yBAAyB;YACpE,yEAAyE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,KAAK,CACjH,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;IAC1E,CAAC;AACH,CAAC","debugId":"7804d4bf-ff46-50af-9af5-533f83c3d445"}