@unbrained/pm-cli 2026.9.5 → 2026.9.7

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 +39 -3
  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 +7 -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
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @module sdk/governance/defect-recurrence-coverage
3
+ *
4
+ * Measures policy coverage against all recorded recurrence components and pages
5
+ * the uncovered evidence without allowing pagination to weaken the verdict.
6
+ */
7
+
8
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ef2edfdc-7166-5cfc-adf1-fc8c69dfab1c")}catch(e){}}();
9
+ import { createHash } from "node:crypto";
10
+ import { stableStringify } from "../../core/shared/serialization.js";
11
+ import { DEFECT_ESCAPE_CLASSES, parseDefectRecurrencePolicy, parseDefectChangeRiskRequest, } from "./defect-recurrence.js";
12
+ import { buildDefectRecurrenceGraph, readDefectRecurrenceRecord, } from "./defect-recurrence-graph.js";
13
+ /** Validate lineage-policy and paging input at the untrusted transport boundary. */
14
+ export function parseDefectRecurrenceCoverageRequest(value) {
15
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
16
+ throw new TypeError("recurrence coverage request must be an object");
17
+ }
18
+ const input = value;
19
+ if (Object.hasOwn(input, "change")) {
20
+ throw new TypeError("recurrence coverage request does not accept change");
21
+ }
22
+ if (input.uncoveredOnly !== undefined &&
23
+ typeof input.uncoveredOnly !== "boolean") {
24
+ throw new TypeError("recurrence coverage request.uncoveredOnly must be a boolean");
25
+ }
26
+ const { policy, cursor, limit } = parseDefectChangeRiskRequest({
27
+ ...input,
28
+ change: {},
29
+ });
30
+ return {
31
+ policy,
32
+ ...(cursor === undefined ? {} : { cursor }),
33
+ ...(limit === undefined ? {} : { limit }),
34
+ ...(input.uncoveredOnly === undefined
35
+ ? {}
36
+ : { uncoveredOnly: input.uncoveredOnly }),
37
+ };
38
+ }
39
+ /** Decode a cursor only when its evidence identity and safe integer offset match. */
40
+ function coverageOffset(cursor, fingerprint) {
41
+ if (cursor === undefined)
42
+ return 0;
43
+ let value;
44
+ try {
45
+ value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
46
+ }
47
+ catch {
48
+ throw new TypeError("recurrence coverage cursor is invalid");
49
+ }
50
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
51
+ throw new TypeError("recurrence coverage cursor is invalid");
52
+ }
53
+ const record = value;
54
+ if (record.fingerprint !== fingerprint ||
55
+ typeof record.offset !== "number" ||
56
+ !Number.isSafeInteger(record.offset) ||
57
+ record.offset < 0) {
58
+ throw new TypeError("recurrence coverage cursor does not match this evidence and filter");
59
+ }
60
+ return record.offset;
61
+ }
62
+ /** Count the complete graph and hash its evidence before selecting any page. */
63
+ function summarizeCoverage(graph, records, knownIds, ids, identity, uncoveredOnly) {
64
+ const classCounts = Object.fromEntries(DEFECT_ESCAPE_CLASSES.map((key) => [key, 0]));
65
+ let coveredCount = 0;
66
+ let uncoveredCount = 0;
67
+ let missingCount = 0;
68
+ let classifiedCount = 0;
69
+ const selected = [];
70
+ const hash = createHash("sha256").update(identity);
71
+ for (const id of ids) {
72
+ const lineage = graph.membership.get(id);
73
+ const present = knownIds.has(id);
74
+ const covered = lineage.family_ids.length > 0;
75
+ if (covered && present)
76
+ coveredCount += 1;
77
+ if (!covered)
78
+ uncoveredCount += 1;
79
+ if (!present)
80
+ missingCount += 1;
81
+ const classification = records.get(id)?.escape_class;
82
+ if (classification !== undefined &&
83
+ DEFECT_ESCAPE_CLASSES.includes(classification)) {
84
+ classCounts[classification] += 1;
85
+ classifiedCount += 1;
86
+ }
87
+ hash.update(stableStringify([
88
+ id,
89
+ present,
90
+ records.get(id) ?? null,
91
+ lineage.id,
92
+ lineage.family_ids,
93
+ ]));
94
+ if (!uncoveredOnly || !covered || !present)
95
+ selected.push(id);
96
+ }
97
+ return {
98
+ classCounts,
99
+ coveredCount,
100
+ uncoveredCount,
101
+ missingCount,
102
+ classifiedCount,
103
+ selected,
104
+ fingerprint: hash.digest("hex"),
105
+ };
106
+ }
107
+ /** Evaluate all local recurs_from lineages before applying any result-row limit. */
108
+ export function analyzeDefectRecurrenceCoverage(policy, items, options = {}) {
109
+ parseDefectRecurrencePolicy(policy);
110
+ const limit = options.limit ?? 25;
111
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
112
+ throw new TypeError("recurrence coverage limit must be an integer from 1 through 100");
113
+ }
114
+ const knownIds = new Set();
115
+ const records = new Map();
116
+ for (const item of items) {
117
+ if (knownIds.has(item.id))
118
+ throw new TypeError(`Duplicate recurrence item ${item.id}`);
119
+ knownIds.add(item.id);
120
+ const record = readDefectRecurrenceRecord(item);
121
+ if (record !== undefined)
122
+ records.set(item.id, record);
123
+ }
124
+ const graph = buildDefectRecurrenceGraph(policy.families, records);
125
+ const ids = [...graph.membership.keys()].sort();
126
+ const identity = stableStringify({
127
+ policy,
128
+ uncoveredOnly: options.uncoveredOnly === true,
129
+ });
130
+ const { classCounts, coveredCount, uncoveredCount, missingCount, classifiedCount, selected, fingerprint, } = summarizeCoverage(graph, records, knownIds, ids, identity, options.uncoveredOnly === true);
131
+ const offset = coverageOffset(options.cursor, fingerprint);
132
+ if (offset > selected.length)
133
+ throw new TypeError("recurrence coverage cursor offset exceeds the selected population");
134
+ const page = selected
135
+ .slice(offset, offset + limit)
136
+ .map((id) => {
137
+ const lineage = graph.membership.get(id);
138
+ const present = knownIds.has(id);
139
+ return {
140
+ item_id: id,
141
+ lineage_id: lineage.id,
142
+ lineage_item_count: lineage.item_ids.length,
143
+ family_ids: lineage.family_ids,
144
+ present,
145
+ ...(!present
146
+ ? { finding: "missing_recurrence_item" }
147
+ : lineage.family_ids.length === 0
148
+ ? { finding: "unregistered_recurrence_item" }
149
+ : {}),
150
+ };
151
+ });
152
+ const nextOffset = offset + page.length;
153
+ const lowerBound = ids.length === 0 ? null : classCounts.production_defect / ids.length;
154
+ return {
155
+ ok: uncoveredCount === 0 && missingCount === 0,
156
+ fingerprint,
157
+ items: page,
158
+ total: selected.length,
159
+ has_more: nextOffset < selected.length,
160
+ ...(nextOffset < selected.length
161
+ ? {
162
+ next_cursor: Buffer.from(JSON.stringify({ fingerprint, offset: nextOffset })).toString("base64url"),
163
+ }
164
+ : {}),
165
+ population: {
166
+ scope: "all_recorded_recurrence_items",
167
+ item_count: ids.length,
168
+ lineage_count: graph.lineages.length,
169
+ edge_count: graph.edge_count,
170
+ covered_item_count: coveredCount,
171
+ uncovered_item_count: uncoveredCount,
172
+ missing_item_count: missingCount,
173
+ classified_item_count: classifiedCount,
174
+ unclassified_item_count: ids.length - classifiedCount,
175
+ class_counts: classCounts,
176
+ escape_rate: classifiedCount === ids.length ? lowerBound : null,
177
+ escape_rate_lower_bound: lowerBound,
178
+ },
179
+ budget_scope: "registered_families_only",
180
+ cost: {
181
+ items_scanned: items.length,
182
+ edges_scanned: graph.edge_count,
183
+ row_limit: limit,
184
+ },
185
+ row_contract: { row_keys: ["items"], jq_selector: ".items[]" },
186
+ };
187
+ }
188
+ //# sourceMappingURL=defect-recurrence-coverage.js.map
189
+ //# debugId=ef2edfdc-7166-5cfc-adf1-fc8c69dfab1c
@@ -0,0 +1,33 @@
1
+ import type { AssuranceItemRecord } from "./assurance.js";
2
+ import type { DefectRecurrenceFamily } from "./defect-recurrence.js";
3
+ /** Sparse item evidence retained so incremental graph edits can be reversed. */
4
+ export interface DefectRecurrenceRecord {
5
+ /** Unique local predecessors named by recurs_from edges. */
6
+ targets: string[];
7
+ /** Project file paths used as exact change-risk triggers after lineage registration. */
8
+ files: string[];
9
+ /** Recorded classification, never inferred from a registered family's label. */
10
+ escape_class?: string;
11
+ }
12
+ /** One weakly connected recurrence component, traversed without a depth ceiling. */
13
+ export interface DefectRecurrenceLineage {
14
+ /** Lexically first member, used as a stable identifier for the current component. */
15
+ id: string;
16
+ /** Every member, in deterministic code-point order. */
17
+ item_ids: string[];
18
+ /** Families explicitly seeded anywhere in this component. */
19
+ family_ids: string[];
20
+ }
21
+ /** Complete sparse graph used internally by the bounded public coverage report. */
22
+ export interface DefectRecurrenceGraph {
23
+ /** Components sorted by their current stable id. */
24
+ lineages: DefectRecurrenceLineage[];
25
+ /** Sparse lookup from every recurrence member to its component. */
26
+ membership: Map<string, DefectRecurrenceLineage>;
27
+ /** Number of distinct directed local recurrence edges. */
28
+ edge_count: number;
29
+ }
30
+ /** Read a sparse projection without retaining full item bodies or history. */
31
+ export declare function readDefectRecurrenceRecord(item: AssuranceItemRecord): DefectRecurrenceRecord | undefined;
32
+ /** Build iterative undirected connectivity from directed recurrence evidence. */
33
+ export declare function buildDefectRecurrenceGraph(families: readonly DefectRecurrenceFamily[], records: ReadonlyMap<string, DefectRecurrenceRecord>): DefectRecurrenceGraph;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * @module sdk/governance/defect-recurrence-graph
3
+ *
4
+ * Derives recurrence families from local recorded edges. Association, hierarchy,
5
+ * external references, and matching file globs never establish family coverage.
6
+ */
7
+
8
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9d171110-2de0-5fa2-ae85-5a4e3ab4d46e")}catch(e){}}();
9
+ import { isExternalDependencyReference, isExternalDependencySourceKind, } from "../../core/item/dependency-reference.js";
10
+ /** Extract local recurrence targets while rejecting malformed and external edges. */
11
+ function recurrenceTargets(item) {
12
+ const targets = new Set();
13
+ for (const edge of Array.isArray(item.dependencies)
14
+ ? item.dependencies
15
+ : []) {
16
+ if (typeof edge !== "object" || edge === null || Array.isArray(edge))
17
+ continue;
18
+ if (edge.kind !== "recurs_from")
19
+ continue;
20
+ if (typeof edge.id !== "string" || !edge.id.trim())
21
+ continue;
22
+ if (isExternalDependencyReference(edge.id) ||
23
+ isExternalDependencySourceKind(typeof edge.source_kind === "string" ? edge.source_kind : undefined))
24
+ continue;
25
+ targets.add(edge.id.trim());
26
+ }
27
+ return [...targets].sort();
28
+ }
29
+ /** Extract project-scoped exact file triggers from untrusted linked-file records. */
30
+ function recurrenceFiles(item) {
31
+ const files = new Set();
32
+ for (const file of Array.isArray(item.files) ? item.files : []) {
33
+ if (typeof file !== "object" || file === null || Array.isArray(file))
34
+ continue;
35
+ const entry = file;
36
+ if (entry.scope !== undefined && entry.scope !== "project")
37
+ continue;
38
+ if (typeof entry.path === "string" && entry.path.trim())
39
+ files.add(entry.path.trim());
40
+ }
41
+ return [...files].sort();
42
+ }
43
+ /** Read a sparse projection without retaining full item bodies or history. */
44
+ export function readDefectRecurrenceRecord(item) {
45
+ const targets = recurrenceTargets(item);
46
+ const files = recurrenceFiles(item);
47
+ const escapeClass = typeof item.escape_class === "string" ? item.escape_class : undefined;
48
+ if (targets.length === 0 && files.length === 0 && escapeClass === undefined)
49
+ return undefined;
50
+ return {
51
+ targets,
52
+ files,
53
+ ...(escapeClass === undefined ? {} : { escape_class: escapeClass }),
54
+ };
55
+ }
56
+ /** Traverse one component iteratively and collect only explicitly seeded families. */
57
+ function traverseLineage(start, adjacency, seeds, membership) {
58
+ const lineage = {
59
+ id: start,
60
+ item_ids: [],
61
+ family_ids: [],
62
+ };
63
+ const pending = [start];
64
+ const familyIds = new Set();
65
+ membership.set(start, lineage);
66
+ while (pending.length > 0) {
67
+ const id = pending.pop();
68
+ lineage.item_ids.push(id);
69
+ for (const familyId of seeds.get(id) ?? [])
70
+ familyIds.add(familyId);
71
+ for (const target of adjacency.get(id)) {
72
+ if (membership.has(target))
73
+ continue;
74
+ membership.set(target, lineage);
75
+ pending.push(target);
76
+ }
77
+ }
78
+ lineage.item_ids.sort();
79
+ lineage.id = lineage.item_ids[0];
80
+ lineage.family_ids = [...familyIds].sort();
81
+ return lineage;
82
+ }
83
+ /** Build iterative undirected connectivity from directed recurrence evidence. */
84
+ export function buildDefectRecurrenceGraph(families, records) {
85
+ const adjacency = new Map();
86
+ let edgeCount = 0;
87
+ for (const [id, record] of records) {
88
+ for (const target of new Set(record.targets)) {
89
+ const outgoing = adjacency.get(id) ?? new Set();
90
+ outgoing.add(target);
91
+ adjacency.set(id, outgoing);
92
+ const incoming = adjacency.get(target) ?? new Set();
93
+ incoming.add(id);
94
+ adjacency.set(target, incoming);
95
+ edgeCount += 1;
96
+ }
97
+ }
98
+ const seeds = new Map();
99
+ for (const family of families) {
100
+ for (const id of [
101
+ family.owner_item_id,
102
+ ...family.historical_item_ids,
103
+ ...(family.triggers.item_ids ?? []),
104
+ ]) {
105
+ const ids = seeds.get(id) ?? new Set();
106
+ ids.add(family.id);
107
+ seeds.set(id, ids);
108
+ }
109
+ }
110
+ const membership = new Map();
111
+ const lineages = [];
112
+ for (const start of adjacency.keys()) {
113
+ if (membership.has(start))
114
+ continue;
115
+ lineages.push(traverseLineage(start, adjacency, seeds, membership));
116
+ }
117
+ lineages.sort((left, right) => (left.id < right.id ? -1 : 1));
118
+ return { lineages, membership, edge_count: edgeCount };
119
+ }
120
+ //# sourceMappingURL=defect-recurrence-graph.js.map
121
+ //# debugId=9d171110-2de0-5fa2-ae85-5a4e3ab4d46e
@@ -1,4 +1,6 @@
1
1
  import type { AssuranceItemRecord } from "./assurance.js";
2
+ import { type DefectRecurrenceRecord } from "./defect-recurrence-graph.js";
3
+ export type { DefectRecurrenceRecord } from "./defect-recurrence-graph.js";
2
4
  /** Stable defect escape taxonomy used by project policy and PM metadata. */
3
5
  export declare const DEFECT_ESCAPE_CLASSES: readonly ["production_defect", "nightly_regression", "scanner_finding", "review_caught_late"];
4
6
  /** Evidence dispositions accepted by the fail-closed defect gate. */
@@ -117,6 +119,12 @@ export interface DefectRecurrenceIndex {
117
119
  families: DefectRecurrenceFamily[];
118
120
  /** Sparse PM item-to-family index. */
119
121
  item_families: Record<string, string[]>;
122
+ /** Direct signal matches retained separately so removing an edge removes inherited matches. */
123
+ direct_item_families?: Record<string, string[]>;
124
+ /** Sparse recurrence and linked-file evidence retained for incremental graph updates. */
125
+ recurrence_records?: Record<string, DefectRecurrenceRecord>;
126
+ /** Exact project file matches inherited from explicitly registered recurrence lineages. */
127
+ file_families?: Record<string, string[]>;
120
128
  /** Build-cost receipt. */
121
129
  build: {
122
130
  /** Items inspected during this build. */
@@ -5,10 +5,11 @@
5
5
  * explains which local and hosted checks a proposed change must run.
6
6
  */
7
7
 
8
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="59f5918f-108e-5cad-905b-7f690c3c4794")}catch(e){}}();
8
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e3a44417-3835-504e-9ccd-0fef674b437c")}catch(e){}}();
9
9
  import { createHash } from "node:crypto";
10
10
  import { stableStringify } from "../../core/shared/serialization.js";
11
11
  import { defectRecurrenceItemSignals } from "./defect-recurrence-signals.js";
12
+ import { buildDefectRecurrenceGraph, readDefectRecurrenceRecord, } from "./defect-recurrence-graph.js";
12
13
  /** Stable defect escape taxonomy used by project policy and PM metadata. */
13
14
  export const DEFECT_ESCAPE_CLASSES = [
14
15
  "production_defect",
@@ -215,23 +216,22 @@ function exactReasons(family, input) {
215
216
  }
216
217
  return reasons;
217
218
  }
219
+ /** Explain item-trigger matches from explicit policy IDs or inherited index membership. */
218
220
  function itemReasons(family, input, itemFamilies) {
219
221
  const directItemIds = new Set(family.triggers.item_ids ?? []);
220
- return uniqueSorted(input.item_ids ?? []).flatMap((itemId) => directItemIds.has(itemId) || itemFamilies[itemId]?.includes(family.id)
222
+ return uniqueSorted(input.item_ids ?? []).flatMap((itemId) => directItemIds.has(itemId) ||
223
+ (Object.hasOwn(itemFamilies, itemId) &&
224
+ itemFamilies[itemId].includes(family.id))
221
225
  ? [{ signal: "item", value: itemId, matched: family.id }]
222
226
  : []);
223
227
  }
228
+ /** Collect direct policy matches without sorting intermediate selection-only results. */
224
229
  function familyReasons(family, input, itemFamilies) {
225
- const reasons = [
230
+ return [
226
231
  ...fileReasons(family, input),
227
232
  ...exactReasons(family, input),
228
233
  ...itemReasons(family, input, itemFamilies),
229
234
  ];
230
- return reasons.sort((left, right) => left.signal !== right.signal
231
- ? left.signal.localeCompare(right.signal)
232
- : left.value !== right.value
233
- ? left.value.localeCompare(right.value)
234
- : left.matched.localeCompare(right.matched));
235
235
  }
236
236
  function readRiskCursorOffset(cursor, indexFingerprint) {
237
237
  if (!cursor)
@@ -263,6 +263,76 @@ function changeSignalCount(input) {
263
263
  input.error_codes,
264
264
  ].reduce((total, values) => total + (values?.length ?? 0), 0);
265
265
  }
266
+ /** Apply registered lineage membership to item and exact linked-file risk triggers. */
267
+ function inheritRecurrenceFamilies(families, recurrenceRecords, itemFamilies) {
268
+ const graph = buildDefectRecurrenceGraph(families, recurrenceRecords);
269
+ const fileFamilies = new Map();
270
+ for (const lineage of graph.lineages) {
271
+ if (lineage.family_ids.length === 0)
272
+ continue;
273
+ for (const id of lineage.item_ids) {
274
+ itemFamilies[id] = uniqueSorted([
275
+ ...(Object.hasOwn(itemFamilies, id) ? itemFamilies[id] : []),
276
+ ...lineage.family_ids,
277
+ ]);
278
+ for (const file of recurrenceRecords.get(id)?.files ?? []) {
279
+ const matched = fileFamilies.get(file) ?? new Set();
280
+ for (const familyId of lineage.family_ids)
281
+ matched.add(familyId);
282
+ fileFamilies.set(file, matched);
283
+ }
284
+ }
285
+ }
286
+ return fileFamilies;
287
+ }
288
+ /** Refresh changed sparse records, deleting evidence no longer present on an item. */
289
+ function refreshRecurrenceRecords(recurrenceRecords, items) {
290
+ for (const item of items) {
291
+ const record = readDefectRecurrenceRecord(item);
292
+ if (record === undefined)
293
+ recurrenceRecords.delete(item.id);
294
+ else
295
+ recurrenceRecords.set(item.id, record);
296
+ }
297
+ }
298
+ /** Compile exact identifier contributions once rather than expanding every family per item. */
299
+ function compileDirectItemSeeds(families) {
300
+ const historical = new Map();
301
+ const triggers = new Map();
302
+ for (const family of families) {
303
+ for (const [index, ids] of [
304
+ [historical, family.historical_item_ids],
305
+ [triggers, family.triggers.item_ids ?? []],
306
+ ]) {
307
+ for (const id of ids) {
308
+ const matches = index.get(id) ?? new Set();
309
+ matches.add(family.id);
310
+ index.set(id, matches);
311
+ }
312
+ }
313
+ }
314
+ return { historical, triggers };
315
+ }
316
+ /** Match identifier-only items without allocating per-family explanation records. */
317
+ function directItemMatches(item, families, seeds) {
318
+ const signals = defectRecurrenceItemSignals(item);
319
+ const hasContext = [
320
+ signals.files,
321
+ signals.package_names,
322
+ signals.tags,
323
+ signals.error_codes,
324
+ ].some((values) => values && values.length > 0);
325
+ if (hasContext) {
326
+ return families
327
+ .filter((family) => family.historical_item_ids.includes(item.id) ||
328
+ familyReasons(family, signals, {}).length > 0)
329
+ .map((family) => family.id);
330
+ }
331
+ return uniqueSorted([
332
+ ...(seeds.historical.get(item.id) ?? []),
333
+ ...(seeds.triggers.get(item.id.trim()) ?? []),
334
+ ]);
335
+ }
266
336
  /** Compile deterministic policy and PM metadata into a sparse incremental index. */
267
337
  export function buildDefectRecurrenceIndex(policy, items, options = {}) {
268
338
  validatePolicy(policy);
@@ -270,21 +340,30 @@ export function buildDefectRecurrenceIndex(policy, items, options = {}) {
270
340
  const policyFingerprint = fingerprint({ ...policy, families });
271
341
  const changed = new Set(options.changed_item_ids ?? []);
272
342
  const canReuse = options.previous_index?.policy_fingerprint === policyFingerprint;
273
- const itemFamilies = canReuse
274
- ? Object.fromEntries(Object.entries(options.previous_index?.item_families ?? {}).filter(([itemId]) => !changed.has(itemId)))
275
- : {};
343
+ const itemFamilies = Object.assign(Object.create(null), canReuse
344
+ ? Object.fromEntries(Object.entries(options.previous_index?.direct_item_families ??
345
+ options.previous_index?.item_families ??
346
+ {}).filter(([itemId]) => !changed.has(itemId)))
347
+ : {});
276
348
  const itemsReused = canReuse ? Object.keys(itemFamilies).length : 0;
349
+ const seeds = compileDirectItemSeeds(families);
277
350
  for (const item of items) {
278
- const signals = defectRecurrenceItemSignals(item);
279
- const matched = families
280
- .filter((family) => family.historical_item_ids.includes(item.id) ||
281
- familyReasons(family, signals, {}).length > 0)
282
- .map((family) => family.id);
351
+ const matched = directItemMatches(item, families, seeds);
283
352
  if (matched.length > 0)
284
353
  itemFamilies[item.id] = matched;
285
354
  else
286
355
  delete itemFamilies[item.id];
287
356
  }
357
+ const directItemFamilies = Object.fromEntries(Object.entries(itemFamilies).sort(([left], [right]) => left < right ? -1 : 1));
358
+ const recurrenceRecords = new Map(canReuse
359
+ ? Object.entries(options.previous_index?.recurrence_records ?? {}).filter(([id]) => !changed.has(id))
360
+ : []);
361
+ refreshRecurrenceRecords(recurrenceRecords, items);
362
+ const fileFamilies = inheritRecurrenceFamilies(families, recurrenceRecords, itemFamilies);
363
+ const sortedRecords = Object.fromEntries([...recurrenceRecords].sort(([left], [right]) => (left < right ? -1 : 1)));
364
+ const sortedFileFamilies = Object.fromEntries([...fileFamilies]
365
+ .sort(([left], [right]) => (left < right ? -1 : 1))
366
+ .map(([file, matches]) => [file, [...matches].sort()]));
288
367
  const sortedItemFamilies = Object.fromEntries(Object.entries(itemFamilies)
289
368
  .sort(([left], [right]) => (left < right ? -1 : 1))
290
369
  .map(([itemId, familyIds]) => [itemId, uniqueSorted(familyIds)]));
@@ -294,9 +373,15 @@ export function buildDefectRecurrenceIndex(policy, items, options = {}) {
294
373
  index_fingerprint: fingerprint({
295
374
  policy_fingerprint: policyFingerprint,
296
375
  item_families: sortedItemFamilies,
376
+ direct_item_families: directItemFamilies,
377
+ recurrence_records: sortedRecords,
378
+ file_families: sortedFileFamilies,
297
379
  }),
298
380
  families,
299
381
  item_families: sortedItemFamilies,
382
+ direct_item_families: directItemFamilies,
383
+ recurrence_records: sortedRecords,
384
+ file_families: sortedFileFamilies,
300
385
  build: {
301
386
  items_scanned: items.length,
302
387
  items_reused: itemsReused,
@@ -313,6 +398,22 @@ export function analyzeDefectChangeRisk(index, input, options = {}) {
313
398
  const offset = readRiskCursorOffset(options.cursor, index.index_fingerprint);
314
399
  const matches = index.families.flatMap((family) => {
315
400
  const reasons = familyReasons(family, input, index.item_families);
401
+ for (const file of uniqueSorted(input.files ?? [])) {
402
+ if (index.file_families &&
403
+ Object.hasOwn(index.file_families, file) &&
404
+ index.file_families[file].includes(family.id)) {
405
+ reasons.push({
406
+ signal: "file",
407
+ value: file,
408
+ matched: `recurs_from:${family.id}`,
409
+ });
410
+ }
411
+ }
412
+ reasons.sort((left, right) => left.signal !== right.signal
413
+ ? left.signal.localeCompare(right.signal)
414
+ : left.value !== right.value
415
+ ? left.value.localeCompare(right.value)
416
+ : left.matched.localeCompare(right.matched));
316
417
  return reasons.length === 0
317
418
  ? []
318
419
  : [
@@ -520,4 +621,4 @@ export function parseDefectChangeRiskRequest(value) {
520
621
  };
521
622
  }
522
623
  //# sourceMappingURL=defect-recurrence.js.map
523
- //# debugId=59f5918f-108e-5cad-905b-7f690c3c4794
624
+ //# debugId=e3a44417-3835-504e-9ccd-0fef674b437c
@@ -16,7 +16,7 @@ export interface HealthCheck {
16
16
  name: "settings" | "directories" | "settings_values" | "telemetry" | "extensions" | "storage" | "locks" | "integrity" | "history_drift" | "vectorization";
17
17
  /** Lifecycle state reported for status. */
18
18
  status: HealthStatus;
19
- /** Boolean companion to status for direct agent and assurance predicates. */
19
+ /** True when this check has no gate-failing findings; advisory warnings retain warn status. */
20
20
  ok: boolean;
21
21
  /** Value that configures or reports details for this contract. */
22
22
  details: Record<string, unknown>;
@@ -40,6 +40,17 @@ export interface HealthFinding {
40
40
  export interface HealthResult {
41
41
  /** Whether the operation completed without a blocking failure. */
42
42
  ok: boolean;
43
+ /** Invocation policy and process outcome; `ok` is the authoritative health predicate. */
44
+ verdict?: {
45
+ /** Field consumers must branch on to decide project health. */
46
+ authority: "ok";
47
+ /** Present when a failed verdict sets a nonzero process exit; omitted means false. */
48
+ strict_exit?: true;
49
+ /** Present when missing merge drivers block this invocation; omitted means false. */
50
+ require_merge_drivers?: true;
51
+ /** Process exit code for this invocation, independent of diagnostic severity. */
52
+ exit_code: number;
53
+ };
43
54
  /** Value that configures or reports checks for this contract. */
44
55
  checks: HealthCheck[];
45
56
  /** Number of warning entries represented by this result. */
@@ -67,6 +78,10 @@ export interface HealthResult {
67
78
  }
68
79
  /** Documents the run health options payload exchanged by command, SDK, and package integrations. */
69
80
  export interface RunHealthOptions {
81
+ /** Match CLI strict-exit policy: require merge drivers and exit nonzero on blocking findings. */
82
+ strictExit?: boolean;
83
+ /** Compatibility alias for strictExit across SDK and MCP invocations. */
84
+ failOnWarn?: boolean;
70
85
  /** Value that configures or reports strict directories for this contract. */
71
86
  strictDirectories?: boolean;
72
87
  /** Treat absent clone-local merge drivers as a required health failure instead of an advisory. */
@@ -85,7 +100,7 @@ export interface RunHealthOptions {
85
100
  verboseAuthorEvents?: boolean;
86
101
  /** Value that configures or reports skip vectors for this contract. */
87
102
  skipVectors?: boolean;
88
- /** Value that configures or reports skip integrity for this contract. */
103
+ /** Skip optional integrity work; full output and required merge-driver enforcement still run it. */
89
104
  skipIntegrity?: boolean;
90
105
  /** Value that configures or reports skip drift for this contract. */
91
106
  skipDrift?: boolean;