@unbrained/pm-cli 2026.9.5 → 2026.9.6

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 (185) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +32 -2
  3. package/dist/cli/main.js +10 -8
  4. package/dist/cli/register-operations.js +13 -8
  5. package/dist/cli-bundle/bundle-manifest.json +150 -158
  6. package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +9 -0
  7. package/dist/cli-bundle/chunks/{chunk-WWAEGW4V.js → chunk-BAX2BYUW.js} +66 -66
  8. package/dist/cli-bundle/chunks/{chunk-IBYREGSP.js → chunk-CKOL5U7J.js} +51 -51
  9. package/dist/cli-bundle/chunks/{chunk-ZRAQS3UA.js → chunk-JT6XF6GE.js} +2 -2
  10. package/dist/cli-bundle/chunks/{chunk-HUAQITVW.js → chunk-JX4DH4WC.js} +2 -2
  11. package/dist/cli-bundle/chunks/{chunk-QPZHQY2O.js → chunk-NPN5I67J.js} +2 -2
  12. package/dist/cli-bundle/chunks/{chunk-66VPKFP2.js → chunk-OF76PSDQ.js} +2 -2
  13. package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +232 -0
  14. package/dist/cli-bundle/chunks/{chunk-3HBDTJRK.js → chunk-RWWBOWJ5.js} +2 -2
  15. package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +40 -0
  16. package/dist/cli-bundle/chunks/{chunk-SF6SM6U5.js → chunk-WI2AYPA7.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-list-query-AYKGODWT.js → register-list-query-5JL52D33.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-mutation-YZYG53SQ.js → register-mutation-ZPG7KMVP.js} +2 -2
  19. package/dist/cli-bundle/chunks/register-operations-I5W3KMGB.js +2 -0
  20. package/dist/cli-bundle/chunks/{register-setup-UJCRVGBG.js → register-setup-H4SVAVX7.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-QL66QNWG.js → chunk-3BZEOXLT.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +2 -0
  23. package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +6 -0
  24. package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +20 -0
  25. package/dist/cli-bundle/focused-chunks/{chunk-WOCIPMW7.js → chunk-EZR3GHFE.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +56 -0
  27. package/dist/cli-bundle/focused-chunks/{chunk-3ASU6WGI.js → chunk-LIJIA4TY.js} +2 -2
  28. package/dist/cli-bundle/focused-chunks/{chunk-H5FOGAVD.js → chunk-M6PBRFSH.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/{chunk-B2TTQGFG.js → chunk-NTPY5GSO.js} +2 -2
  30. package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +2 -0
  31. package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +177 -0
  32. package/dist/cli-bundle/focused-chunks/{chunk-YEF5PQQ2.js → chunk-PEFABF7E.js} +2 -2
  33. package/dist/cli-bundle/focused-chunks/{chunk-KWWCBWFO.js → chunk-QTVCLMSZ.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/{chunk-4OSHLNUE.js → chunk-QTZRBUYA.js} +2 -2
  35. package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +12 -0
  36. package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +10 -0
  37. package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +2 -0
  38. package/dist/cli-bundle/main.js +20 -20
  39. package/dist/cli-bundle/sdk-authoring.js +1 -1
  40. package/dist/cli-bundle/sdk-contracts.js +2 -2
  41. package/dist/cli-bundle/sdk-core.js +37 -37
  42. package/dist/cli-bundle/sdk-governance.js +1 -1
  43. package/dist/cli-bundle/sdk-graph.js +1 -1
  44. package/dist/cli-bundle/sdk-merge.js +42 -42
  45. package/dist/cli-bundle/sdk-query.js +1 -1
  46. package/dist/cli-bundle/sdk-runtime.js +1 -1
  47. package/dist/cli-bundle/sdk-testing.js +1 -1
  48. package/dist/cli-bundle/sdk.js +9 -9
  49. package/dist/core/extensions/module-graph-snapshot.js +5 -4
  50. package/dist/core/fs/host-environment-errors.d.ts +27 -0
  51. package/dist/core/fs/host-environment-errors.js +142 -0
  52. package/dist/core/output/mutation-projection.js +12 -5
  53. package/dist/core/store/item-metadata-query-index.js +13 -10
  54. package/dist/core/store/item-metadata-read-work.d.ts +19 -0
  55. package/dist/core/store/item-metadata-read-work.js +44 -0
  56. package/dist/core/store/item-store.js +6 -2
  57. package/dist/core/validate/fix-planning.d.ts +1 -1
  58. package/dist/core/validate/fix-planning.js +2 -2
  59. package/dist/core/validate/missing-link-owners.d.ts +1 -1
  60. package/dist/core/validate/missing-link-owners.js +5 -3
  61. package/dist/core/validate/stale-file-classification.d.ts +6 -4
  62. package/dist/core/validate/stale-file-classification.js +20 -5
  63. package/dist/mcp/server.js +12 -5
  64. package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
  65. package/dist/sdk/cli-contracts/flag-contracts.js +7 -5
  66. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +4 -3
  67. package/dist/sdk/cli-contracts/grammar-contracts.js +11 -2
  68. package/dist/sdk/cli-contracts/runtime-contracts.js +5 -2
  69. package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +254 -5
  70. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +52 -2
  71. package/dist/sdk/cli-contracts/tool-parameter-tables.js +18 -6
  72. package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
  73. package/dist/sdk/cli-contracts/tool-schema.js +17 -5
  74. package/dist/sdk/context/usage-ledger.d.ts +41 -0
  75. package/dist/sdk/context/usage-ledger.js +186 -0
  76. package/dist/sdk/context-usage.d.ts +18 -9
  77. package/dist/sdk/context-usage.js +37 -112
  78. package/dist/sdk/dependency-provenance.d.ts +7 -0
  79. package/dist/sdk/dependency-provenance.js +14 -4
  80. package/dist/sdk/duplicates.d.ts +2 -0
  81. package/dist/sdk/duplicates.js +3 -2
  82. package/dist/sdk/environment/host-environment-errors.d.ts +5 -27
  83. package/dist/sdk/environment/host-environment-errors.js +4 -137
  84. package/dist/sdk/extension/install-runtime.js +9 -6
  85. package/dist/sdk/extension/install-snapshot.js +7 -6
  86. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +8 -8
  87. package/dist/sdk/governance/assurance-action.d.ts +3 -2
  88. package/dist/sdk/governance/assurance-action.js +23 -11
  89. package/dist/sdk/governance/assurance-runtime.d.ts +2 -0
  90. package/dist/sdk/governance/assurance-runtime.js +6 -3
  91. package/dist/sdk/governance/defect-recurrence-coverage.d.ts +93 -0
  92. package/dist/sdk/governance/defect-recurrence-coverage.js +189 -0
  93. package/dist/sdk/governance/defect-recurrence-graph.d.ts +33 -0
  94. package/dist/sdk/governance/defect-recurrence-graph.js +121 -0
  95. package/dist/sdk/governance/defect-recurrence.d.ts +8 -0
  96. package/dist/sdk/governance/defect-recurrence.js +118 -17
  97. package/dist/sdk/governance/health.d.ts +17 -2
  98. package/dist/sdk/governance/health.js +31 -10
  99. package/dist/sdk/governance/linked-file-report.d.ts +41 -0
  100. package/dist/sdk/governance/linked-file-report.js +72 -0
  101. package/dist/sdk/governance/validate.d.ts +5 -2
  102. package/dist/sdk/governance/validate.js +21 -37
  103. package/dist/sdk/governance.d.ts +2 -0
  104. package/dist/sdk/governance.js +4 -2
  105. package/dist/sdk/history-repair.d.ts +7 -0
  106. package/dist/sdk/history-repair.js +48 -13
  107. package/dist/sdk/lifecycle/close.d.ts +8 -0
  108. package/dist/sdk/lifecycle/close.js +16 -8
  109. package/dist/sdk/lifecycle/create.js +9 -12
  110. package/dist/sdk/lifecycle/update.js +12 -12
  111. package/dist/sdk/lifecycle-policy.d.ts +3 -0
  112. package/dist/sdk/lifecycle-policy.js +2 -2
  113. package/dist/sdk/merge/abandoned-receipts.d.ts +15 -0
  114. package/dist/sdk/merge/abandoned-receipts.js +47 -0
  115. package/dist/sdk/merge/driver.js +4 -2
  116. package/dist/sdk/merge/index.d.ts +1 -0
  117. package/dist/sdk/merge/index.js +2 -2
  118. package/dist/sdk/merge/receipt-operation.d.ts +15 -0
  119. package/dist/sdk/merge/receipt-operation.js +97 -0
  120. package/dist/sdk/merge/receipt-schema.d.ts +20 -0
  121. package/dist/sdk/merge/receipt-schema.js +398 -0
  122. package/dist/sdk/merge/receipts.d.ts +16 -7
  123. package/dist/sdk/merge/receipts.js +54 -340
  124. package/dist/sdk/merge/reconcile.d.ts +2 -0
  125. package/dist/sdk/merge/reconcile.js +11 -4
  126. package/dist/sdk/public-surface.json +1 -1
  127. package/dist/sdk/query/duplicate-candidates.d.ts +7 -0
  128. package/dist/sdk/query/duplicate-candidates.js +71 -0
  129. package/dist/sdk/query/get.js +7 -7
  130. package/dist/sdk/query.d.ts +2 -0
  131. package/dist/sdk/query.js +4 -2
  132. package/dist/sdk/read-output-session.js +5 -3
  133. package/dist/sdk/runtime.js +4 -2
  134. package/dist/sdk/similarity.d.ts +24 -3
  135. package/dist/sdk/similarity.js +50 -55
  136. package/docs/AGENT_GUIDE.md +23 -2
  137. package/docs/BOUNDED_CONTEXT_READS.md +185 -0
  138. package/docs/CLOSE_EVIDENCE_RECOVERY.md +35 -0
  139. package/docs/COMMANDS.md +30 -8
  140. package/docs/CONTEXT_RELEVANCE.md +16 -4
  141. package/docs/DEFECT_RECURRENCE.md +80 -6
  142. package/docs/EXTENSIONS.md +18 -19
  143. package/docs/MERGE_SAFETY.md +50 -0
  144. package/docs/OUTPUT_PROJECTION_CONTRACTS.md +66 -0
  145. package/docs/PR_REVIEW_LOOP.md +18 -3
  146. package/docs/README.md +18 -1
  147. package/docs/RELEASING.md +6 -2
  148. package/docs/SDK_LIFECYCLE.md +27 -0
  149. package/docs/SDK_RUNTIME_BOUNDARIES.md +11 -0
  150. package/docs/generated/FLAG_LEXICON_BUDGETS.md +1 -1
  151. package/marketplace.json +2 -2
  152. package/package.json +6 -3
  153. package/packages/pm-beads/package.json +1 -1
  154. package/packages/pm-calendar/package.json +1 -1
  155. package/packages/pm-command-kit/package.json +1 -1
  156. package/packages/pm-digital-twin/package.json +1 -1
  157. package/packages/pm-governance-audit/README.md +10 -1
  158. package/packages/pm-governance-audit/extensions/governance-audit/dedupe-audit.ts +50 -65
  159. package/packages/pm-governance-audit/extensions/governance-audit/index.ts +2 -2
  160. package/packages/pm-governance-audit/extensions/governance-audit/sdk.ts +1 -0
  161. package/packages/pm-governance-audit/package.json +1 -1
  162. package/packages/pm-guide-shell/package.json +1 -1
  163. package/packages/pm-kanban/package.json +1 -1
  164. package/packages/pm-lifecycle-hooks/package.json +1 -1
  165. package/packages/pm-linked-test-adapters/package.json +1 -1
  166. package/packages/pm-search-advanced/package.json +1 -1
  167. package/packages/pm-templates/package.json +1 -1
  168. package/packages/pm-todos/package.json +1 -1
  169. package/packages/pm-vcs/package.json +1 -1
  170. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  171. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  172. package/dist/cli-bundle/chunks/chunk-LUI4E32I.js +0 -231
  173. package/dist/cli-bundle/chunks/chunk-SVK5Y64D.js +0 -40
  174. package/dist/cli-bundle/chunks/chunk-V5GBD5KV.js +0 -9
  175. package/dist/cli-bundle/chunks/register-operations-KFSCC7QV.js +0 -2
  176. package/dist/cli-bundle/focused-chunks/chunk-24IG6ONG.js +0 -2
  177. package/dist/cli-bundle/focused-chunks/chunk-3ITLIBKS.js +0 -56
  178. package/dist/cli-bundle/focused-chunks/chunk-3NQD5YDG.js +0 -2
  179. package/dist/cli-bundle/focused-chunks/chunk-65N6JHAN.js +0 -2
  180. package/dist/cli-bundle/focused-chunks/chunk-GUBM3RGY.js +0 -20
  181. package/dist/cli-bundle/focused-chunks/chunk-JX3RCVCX.js +0 -2
  182. package/dist/cli-bundle/focused-chunks/chunk-MV752SUC.js +0 -12
  183. package/dist/cli-bundle/focused-chunks/chunk-PKIPMCFS.js +0 -6
  184. package/dist/cli-bundle/focused-chunks/chunk-RJMR2JIF.js +0 -9
  185. package/dist/cli-bundle/focused-chunks/chunk-WIUPWIZR.js +0 -177
@@ -7,7 +7,8 @@ import {
7
7
  EXIT_CODE,
8
8
  PmCliError,
9
9
  isTerminalStatus,
10
- normalizeStatusInput,
10
+ SETTINGS_DEFAULTS,
11
+ parseStatusFilterCsv,
11
12
  normalizeSimilarityText,
12
13
  nowIso,
13
14
  readSettings,
@@ -46,6 +47,8 @@ interface DedupeAuditPreparedCandidate {
46
47
  created_at: string;
47
48
  updated_at: string;
48
49
  normalized_title: string;
50
+ /** Lifecycle evidence captured from this call's workspace registry. */
51
+ terminal?: boolean;
49
52
  }
50
53
 
51
54
  /** Documents the dedupe audit candidate payload exchanged by command, SDK, and package integrations. */
@@ -176,8 +179,8 @@ export interface DedupeAuditResult {
176
179
  warnings?: string[];
177
180
  }
178
181
 
182
+ /** Normalize the optional strategy name against the supported audit modes. */
179
183
  const parseMode = (raw: string | undefined): DedupeAuditMode => {
180
- /** Normalize the optional strategy name against the supported audit modes. */
181
184
  const normalized = (raw ?? "title_exact").trim().toLowerCase();
182
185
  if (!DEDUPE_AUDIT_MODES.includes(normalized as DedupeAuditMode)) {
183
186
  throw new PmCliError(
@@ -188,39 +191,23 @@ const parseMode = (raw: string | undefined): DedupeAuditMode => {
188
191
  return normalized as DedupeAuditMode;
189
192
  };
190
193
 
191
- let dedupeAllowedStatuses = new Set<string>([
192
- "draft",
193
- "open",
194
- "in_progress",
195
- "blocked",
196
- "closed",
197
- "canceled",
198
- ]);
199
- let dedupeTerminalStatuses = new Set<string>(["closed", "canceled"]);
200
- let dedupeStatusRegistry: RuntimeStatusRegistry | null = null;
194
+ const defaultStatusRegistry = resolveRuntimeStatusRegistry(
195
+ SETTINGS_DEFAULTS.schema,
196
+ );
201
197
 
198
+ /** Parse package filters with the host SDK's workflow aliases and all sentinel. */
202
199
  const parseStatus = (
203
200
  raw: string | undefined,
201
+ registry: RuntimeStatusRegistry = defaultStatusRegistry,
204
202
  ): ItemStatus | "all" | undefined => {
205
- /** Normalize an optional status token against the active audit registry. */
206
- if (raw === undefined) {
207
- return undefined;
208
- }
209
- const normalized = raw.trim().toLowerCase().replaceAll("-", "_");
210
- if (normalized === "all") {
211
- return "all";
212
- }
213
- if (!dedupeAllowedStatuses.has(normalized)) {
214
- throw new PmCliError(
215
- `Status filter must be one of all|${[...dedupeAllowedStatuses].join("|")}`,
216
- EXIT_CODE.USAGE,
217
- );
218
- }
219
- return normalized as ItemStatus;
203
+ if (raw === undefined) return undefined;
204
+ return (
205
+ parseStatusFilterCsv(raw, registry, { strict: true })?.join(",") ?? "all"
206
+ );
220
207
  };
221
208
 
209
+ /** Parse an optional fuzzy-match threshold on the inclusive zero-to-one scale. */
222
210
  const parseThreshold = (raw: string | undefined): number | undefined => {
223
- /** Parse an optional fuzzy-match threshold on the inclusive zero-to-one scale. */
224
211
  if (raw === undefined) {
225
212
  return undefined;
226
213
  }
@@ -234,22 +221,15 @@ const parseThreshold = (raw: string | undefined): number | undefined => {
234
221
  return parsed;
235
222
  };
236
223
 
237
- const isTerminal = (status: ItemStatus): boolean => {
238
- /** Resolve terminality through the active schema with a built-in fallback. */
239
- if (dedupeStatusRegistry) {
240
- return isTerminalStatus(status, dedupeStatusRegistry);
241
- }
242
- const normalized = normalizeStatusInput(status) ?? status;
243
- return dedupeTerminalStatuses.has(normalized);
244
- };
245
-
224
+ /** Rank canonical candidates by lifecycle, priority, recency, and identity. */
246
225
  const compareCandidates = (
247
226
  left: DedupeAuditPreparedCandidate,
248
227
  right: DedupeAuditPreparedCandidate,
249
228
  ): number => {
250
- /** Rank canonical candidates by lifecycle, priority, recency, and identity. */
251
- const leftTerminal = isTerminal(left.status);
252
- const rightTerminal = isTerminal(right.status);
229
+ const leftTerminal =
230
+ left.terminal ?? isTerminalStatus(left.status, defaultStatusRegistry);
231
+ const rightTerminal =
232
+ right.terminal ?? isTerminalStatus(right.status, defaultStatusRegistry);
253
233
  if (leftTerminal !== rightTerminal) {
254
234
  return leftTerminal ? 1 : -1;
255
235
  }
@@ -264,10 +244,10 @@ const compareCandidates = (
264
244
  return left.id.localeCompare(right.id);
265
245
  };
266
246
 
247
+ /** Project one prepared comparison record into the public candidate shape. */
267
248
  const toCandidate = (
268
249
  candidate: DedupeAuditPreparedCandidate,
269
250
  ): DedupeAuditCandidate => {
270
- /** Project one prepared comparison record into the public candidate shape. */
271
251
  return {
272
252
  id: candidate.id,
273
253
  title: candidate.title,
@@ -280,12 +260,12 @@ const toCandidate = (
280
260
  };
281
261
  };
282
262
 
263
+ /** Build an executable close recommendation for one duplicate candidate. */
283
264
  const toMergeSuggestion = (
284
265
  duplicate: DedupeAuditPreparedCandidate,
285
266
  canonical: DedupeAuditPreparedCandidate,
286
267
  mode: DedupeAuditMode,
287
268
  ): DedupeMergeSuggestion => {
288
- /** Build an executable close recommendation for one duplicate candidate. */
289
269
  const closeReason = `Duplicate of ${canonical.id}`;
290
270
  const message = `Close ${duplicate.id} as duplicate of ${canonical.id} from pm dedupe-audit (${mode}).`;
291
271
  const escapedReason = closeReason.replaceAll('"', '\\"');
@@ -299,14 +279,15 @@ const toMergeSuggestion = (
299
279
  };
300
280
  };
301
281
 
282
+ /** Score exact normalized titles first, then fall back to token overlap. */
302
283
  const similarityScore = (
303
284
  left: DedupeAuditPreparedCandidate,
304
285
  right: DedupeAuditPreparedCandidate,
305
286
  ): number => {
306
- /** Score exact normalized titles first, then fall back to token overlap. */
307
287
  return scoreItemSimilarity(left.title, right.title).score;
308
288
  };
309
289
 
290
+ /** Visit each unordered candidate pair exactly once. */
310
291
  const forEachCandidatePair = (
311
292
  items: DedupeAuditPreparedCandidate[],
312
293
  visit: (
@@ -316,7 +297,6 @@ const forEachCandidatePair = (
316
297
  rightIndex: number,
317
298
  ) => void,
318
299
  ): void => {
319
- /** Visit each unordered candidate pair exactly once. */
320
300
  for (let leftIndex = 0; leftIndex < items.length; leftIndex += 1) {
321
301
  for (
322
302
  let rightIndex = leftIndex + 1;
@@ -328,15 +308,16 @@ const forEachCandidatePair = (
328
308
  }
329
309
  };
330
310
 
311
+ /** Select one numeric extreme while preserving a deterministic empty fallback. */
331
312
  const extremeOrDefault = (
332
313
  values: number[],
333
314
  select: (...candidates: number[]) => number,
334
315
  fallback: number,
335
316
  ): number => {
336
- /** Select one numeric extreme while preserving a deterministic empty fallback. */
337
317
  return values.length === 0 ? fallback : select(...values);
338
318
  };
339
319
 
320
+ /** Assemble one deterministic cluster and its optional similarity envelope. */
340
321
  const clusterFromMembers = (
341
322
  mode: DedupeAuditMode,
342
323
  key: string,
@@ -344,7 +325,6 @@ const clusterFromMembers = (
344
325
  matchReason: string,
345
326
  threshold?: number,
346
327
  ): DedupeAuditCluster => {
347
- /** Assemble one deterministic cluster and its optional similarity envelope. */
348
328
  const sortedMembers = [...members].sort(compareCandidates);
349
329
  const canonical = sortedMembers[0];
350
330
  const duplicates = sortedMembers.slice(1);
@@ -375,11 +355,11 @@ const clusterFromMembers = (
375
355
  return cluster;
376
356
  };
377
357
 
358
+ /** Group candidates by a caller-defined non-empty comparison key. */
378
359
  const collectGroupedCandidates = (
379
360
  items: DedupeAuditPreparedCandidate[],
380
361
  keyFor: (item: DedupeAuditPreparedCandidate) => string | undefined,
381
362
  ): Map<string, DedupeAuditPreparedCandidate[]> => {
382
- /** Group candidates by a caller-defined non-empty comparison key. */
383
363
  const groups = new Map<string, DedupeAuditPreparedCandidate[]>();
384
364
  for (const item of items) {
385
365
  const key = keyFor(item);
@@ -391,12 +371,12 @@ const collectGroupedCandidates = (
391
371
  return groups;
392
372
  };
393
373
 
374
+ /** Convert non-singleton candidate groups into deterministic clusters. */
394
375
  const clustersFromGroups = (
395
376
  groups: Map<string, DedupeAuditPreparedCandidate[]>,
396
377
  mode: DedupeAuditMode,
397
378
  matchReason: string,
398
379
  ): DedupeAuditCluster[] => {
399
- /** Convert non-singleton candidate groups into deterministic clusters. */
400
380
  return [...groups.entries()]
401
381
  .filter(([, members]) => members.length > 1)
402
382
  .map(([key, members]) =>
@@ -404,10 +384,10 @@ const clustersFromGroups = (
404
384
  );
405
385
  };
406
386
 
387
+ /** Group candidates that share the same normalized title. */
407
388
  const collectExactTitleClusters = (
408
389
  items: DedupeAuditPreparedCandidate[],
409
390
  ): DedupeAuditCluster[] => {
410
- /** Group candidates that share the same normalized title. */
411
391
  const groups = collectGroupedCandidates(items, (item) =>
412
392
  item.normalized_title.length === 0 ? undefined : item.normalized_title,
413
393
  );
@@ -418,10 +398,10 @@ const collectExactTitleClusters = (
418
398
  );
419
399
  };
420
400
 
401
+ /** Group normalized-title duplicates only when they share a parent. */
421
402
  const collectParentScopedClusters = (
422
403
  items: DedupeAuditPreparedCandidate[],
423
404
  ): DedupeAuditCluster[] => {
424
- /** Group normalized-title duplicates only when they share a parent. */
425
405
  const groups = collectGroupedCandidates(items, (item) =>
426
406
  item.parent && item.normalized_title.length > 0
427
407
  ? `${item.parent}|${item.normalized_title}`
@@ -434,8 +414,8 @@ const collectParentScopedClusters = (
434
414
  );
435
415
  };
436
416
 
417
+ /** Find and compress one disjoint-set root for fuzzy clustering. */
437
418
  const findRoot = (parents: number[], index: number): number => {
438
- /** Find and compress one disjoint-set root for fuzzy clustering. */
439
419
  let current = index;
440
420
  while (parents[current] !== current) {
441
421
  parents[current] = parents[parents[current]];
@@ -444,8 +424,8 @@ const findRoot = (parents: number[], index: number): number => {
444
424
  return current;
445
425
  };
446
426
 
427
+ /** Join two fuzzy-cluster roots under the lower deterministic index. */
447
428
  const unionRoots = (parents: number[], left: number, right: number): void => {
448
- /** Join two fuzzy-cluster roots under the lower deterministic index. */
449
429
  const leftRoot = findRoot(parents, left);
450
430
  const rightRoot = findRoot(parents, right);
451
431
  if (leftRoot === rightRoot) {
@@ -454,11 +434,11 @@ const unionRoots = (parents: number[], left: number, right: number): void => {
454
434
  parents[Math.max(leftRoot, rightRoot)] = Math.min(leftRoot, rightRoot);
455
435
  };
456
436
 
437
+ /** Build transitive fuzzy-title clusters at the requested similarity floor. */
457
438
  const collectFuzzyTitleClusters = (
458
439
  items: DedupeAuditPreparedCandidate[],
459
440
  threshold: number,
460
441
  ): DedupeAuditCluster[] => {
461
- /** Build transitive fuzzy-title clusters at the requested similarity floor. */
462
442
  const parents = items.map((_item, index) => index);
463
443
  forEachCandidatePair(items, (left, right, leftIndex, rightIndex) => {
464
444
  if (similarityScore(left, right) >= threshold) {
@@ -490,11 +470,11 @@ const collectFuzzyTitleClusters = (
490
470
  });
491
471
  };
492
472
 
473
+ /** Order larger clusters first and break ties by canonical identity. */
493
474
  const compareClusters = (
494
475
  left: DedupeAuditCluster,
495
476
  right: DedupeAuditCluster,
496
477
  ): number => {
497
- /** Order larger clusters first and break ties by canonical identity. */
498
478
  const bySize = right.cluster_size - left.cluster_size;
499
479
  if (bySize !== 0) {
500
480
  return bySize;
@@ -502,12 +482,12 @@ const compareClusters = (
502
482
  return left.canonical.id.localeCompare(right.canonical.id);
503
483
  };
504
484
 
485
+ /** Dispatch candidate collection to the selected comparison strategy. */
505
486
  const collectDedupeClusters = (
506
487
  mode: DedupeAuditMode,
507
488
  prepared: DedupeAuditPreparedCandidate[],
508
489
  fuzzyThreshold: number,
509
490
  ): DedupeAuditCluster[] => {
510
- /** Dispatch candidate collection to the selected comparison strategy. */
511
491
  if (mode === "title_exact") {
512
492
  return collectExactTitleClusters(prepared);
513
493
  }
@@ -517,10 +497,11 @@ const collectDedupeClusters = (
517
497
  return collectFuzzyTitleClusters(prepared, fuzzyThreshold);
518
498
  };
519
499
 
500
+ /** Precompute normalized fields used by every dedupe comparison strategy. */
520
501
  const toPreparedDedupeCandidate = (
521
502
  item: ListedItem,
503
+ registry: RuntimeStatusRegistry,
522
504
  ): DedupeAuditPreparedCandidate => {
523
- /** Precompute normalized fields used by every dedupe comparison strategy. */
524
505
  return {
525
506
  id: item.id,
526
507
  title: item.title,
@@ -531,6 +512,7 @@ const toPreparedDedupeCandidate = (
531
512
  created_at: item.created_at,
532
513
  updated_at: item.updated_at,
533
514
  normalized_title: normalizeSimilarityText(item.title),
515
+ terminal: isTerminalStatus(item.status, registry),
534
516
  };
535
517
  };
536
518
 
@@ -538,6 +520,7 @@ const toPreparedDedupeCandidate = (
538
520
  const toNullable = <Value>(value: Value | undefined): Value | null =>
539
521
  value ?? null;
540
522
 
523
+ /** Echo normalized audit filters in the stable public result shape. */
541
524
  const buildDedupeAuditFilters = (params: {
542
525
  mode: DedupeAuditMode;
543
526
  status: ItemStatus | "all" | undefined;
@@ -545,7 +528,6 @@ const buildDedupeAuditFilters = (params: {
545
528
  limit: number | undefined;
546
529
  fuzzyThreshold: number;
547
530
  }): DedupeAuditResult["filters"] => {
548
- /** Echo normalized audit filters in the stable public result shape. */
549
531
  return {
550
532
  mode: params.mode,
551
533
  status: toNullable(params.status),
@@ -572,24 +554,27 @@ export const runDedupeAudit = async (
572
554
  const pmRoot = resolvePmRoot(process.cwd(), global.path);
573
555
  const settings = await readSettings(pmRoot);
574
556
  const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
575
- dedupeStatusRegistry = statusRegistry;
576
- dedupeAllowedStatuses = new Set(
577
- statusRegistry.definitions.map((definition) => definition.id),
578
- );
579
- dedupeTerminalStatuses = new Set(statusRegistry.terminal_statuses);
580
557
  const mode = parseMode(options.mode);
581
- const status = parseStatus(options.status);
558
+ const status = parseStatus(options.status, statusRegistry);
582
559
  const limit = parseIntegerLimit(options.limit);
583
560
  const threshold = parseThreshold(options.threshold);
584
561
  const fuzzyThreshold = threshold ?? 0.8;
585
562
 
586
563
  const listed = await runList(
587
564
  status === "all" ? undefined : status,
588
- { ...buildListQueryFilters(options), full: true as const },
565
+ {
566
+ ...buildListQueryFilters(options),
567
+ full: true as const,
568
+ all: true,
569
+ noTruncate: true,
570
+ strictRead: true,
571
+ },
589
572
  global,
590
573
  );
591
574
 
592
- const prepared = listed.items.map((item) => toPreparedDedupeCandidate(item));
575
+ const prepared = listed.items.map((item) =>
576
+ toPreparedDedupeCandidate(item, statusRegistry),
577
+ );
593
578
 
594
579
  const clusters = collectDedupeClusters(mode, prepared, fuzzyThreshold);
595
580
 
@@ -60,14 +60,14 @@ const dedupeAuditFlags = [
60
60
  long: "--status",
61
61
  value_name: "value",
62
62
  value_type: "string",
63
- description: "Filter by status.",
63
+ description: "Filter by configured status IDs or aliases (comma-separated); all includes every lifecycle state.",
64
64
  },
65
65
  ...auditNormalizeFilterFlags,
66
66
  {
67
67
  long: "--limit",
68
68
  value_name: "n",
69
69
  value_type: "string",
70
- description: "Limit analyzed items.",
70
+ description: "Limit returned duplicate clusters; every matching item is analyzed.",
71
71
  },
72
72
  {
73
73
  long: "--threshold",
@@ -4,6 +4,7 @@
4
4
  * Resolves the host pm SDK runtime and exposes the typed subset consumed by
5
5
  * the governance-audit package without copying core command implementations.
6
6
  */
7
+ export { SETTINGS_DEFAULTS, parseStatusFilterCsv } from "@unbrained/pm-cli/sdk";
7
8
  import * as runtime from "@unbrained/pm-cli/sdk/runtime";
8
9
 
9
10
  /** Host SDK values used by package-owned audit commands and runtime decorators. */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-governance-audit",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for governance audit and normalization workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-guide-shell",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for guide and shell completion workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-kanban",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm archetype package: a complete Kanban continuous-flow profile built on public SDK primitives.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-lifecycle-hooks",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package demonstrating safe lifecycle hook registration.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-linked-test-adapters",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for optional linked-test run management adapters and reporters.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-search-advanced",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for semantic, hybrid, and reindex search workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-templates",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for reusable create templates.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-todos",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for Todo markdown import and export.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-vcs-sdk-exemplar",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Beyond-PM exemplar: a VCS-style changeset workflow built entirely on public pm SDK and extension primitives.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pm-claude",
3
3
  "description": "Native pm CLI integration for Claude Code — 28 MCP tools, 5 workflow skills, 14 slash commands, 4 subagents (coordinator, delivery-chain, triage, verification), hybrid TUI task tracking (pm as persistent store + Claude Code task panel as live view), session context injection, and full git-based project management without leaving Claude Code.",
4
- "version": "2026.9.5",
4
+ "version": "2026.9.6",
5
5
  "author": {
6
6
  "name": "unbrained",
7
7
  "url": "https://github.com/unbraind/pm-cli"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-codex",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.6",
4
4
  "description": "Native Codex integration for pm-cli with bundled MCP tools, skills, commands, and agent workflows.",
5
5
  "author": {
6
6
  "name": "unbrained",