@jphutchins/code-review 0.1.0-alpha.58 → 0.1.0-alpha.59

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.
package/README.md CHANGED
@@ -38,7 +38,7 @@ npx @jphutchins/code-review <subcommand>
38
38
  | Subcommand | What it does |
39
39
  | --- | --- |
40
40
  | `post` | Post a complete review from findings + envelope + diff — the one-call path. The findings are listed in the sticky summary; `--inline` renders the in-diff ones as comments on the diff instead. Also renders the review into `$GITHUB_STEP_SUMMARY`: the sticky is overwritten each round, the run summary keeps each round as it stood |
41
- | `gather` | Resolve the PR from the CI head SHA and gather the review inputs (diff with git-diff fallback, PR context, prior bot review, the prior review's answered-findings registry, failing-job logs) into the workspace for the agent |
41
+ | `gather` | Resolve the PR from the CI head SHA and gather the review inputs (diff with git-diff fallback, PR context, prior bot review, the prior review's answered-findings registry, failing-job logs) into the directory named by `--out-dir` (default: the current directory; the shipped workflows use `$RUNNER_TEMP/gather`) |
42
42
  | `parse-command` | Resolve a PR's head from its number and parse a ChatOps trigger comment (`/code-review [24m] [$1.00] <instructions>`) into review overrides — the on-demand comment trigger |
43
43
  | `react` | Add/remove a GitHub comment reaction — the ChatOps acknowledgement (👀 on receipt, 🚀 on completion) |
44
44
  | `await-ci` | Wait for a PR head's CI run to conclude and emit its real conclusion + run id — so an on-demand comment review routes on the CI result (success → full, failure → mechanic) instead of reviewing blind |
package/dist/index.js CHANGED
@@ -76,11 +76,16 @@ var UriString = t.refinement(
76
76
  (s) => !/\s/.test(s) && URL.canParse(s),
77
77
  "UriString"
78
78
  );
79
+ var strictExact = (name, shape, members) => {
80
+ const keys = new Set(members.flatMap((m) => Object.keys(m.props)));
81
+ return t.exact(
82
+ t.refinement(shape, (x) => Object.keys(x).every((k) => keys.has(k)), name)
83
+ );
84
+ };
79
85
  var RuleUrlCodec = t.partial({
80
86
  code_url: UriString
81
87
  });
82
- var FindingRequired = t.type({
83
- id: t.string,
88
+ var FindingCoreRequired = t.type({
84
89
  path: t.string,
85
90
  start_line: LineNumber,
86
91
  end_line: LineNumber,
@@ -91,28 +96,25 @@ var FindingRequired = t.type({
91
96
  confidence: Confidence,
92
97
  likelihood: Likelihood
93
98
  });
99
+ var FindingIdRequired = t.type({ id: t.string });
94
100
  var FindingOptional = t.partial({
95
101
  side: SideCodec,
96
102
  recommendation: t.string,
97
103
  patch: t.string
98
104
  });
99
- var FindingShape = t.intersection([FindingRequired, RuleUrlCodec, FindingOptional]);
100
- var EndGeStart = t.refinement(
101
- FindingShape,
102
- (f) => f.end_line >= f.start_line,
103
- "EndGeStart"
104
- );
105
- var FINDING_KEYS = /* @__PURE__ */ new Set([
106
- ...Object.keys(FindingRequired.props),
107
- ...Object.keys(RuleUrlCodec.props),
108
- ...Object.keys(FindingOptional.props)
105
+ var EndGeStart = (shape) => t.refinement(shape, (f) => f.end_line >= f.start_line, "EndGeStart");
106
+ var FindingShape = t.intersection([
107
+ FindingCoreRequired,
108
+ FindingIdRequired,
109
+ RuleUrlCodec,
110
+ FindingOptional
111
+ ]);
112
+ var FindingCodec = strictExact("FindingStrict", EndGeStart(FindingShape), [
113
+ FindingCoreRequired,
114
+ FindingIdRequired,
115
+ RuleUrlCodec,
116
+ FindingOptional
109
117
  ]);
110
- var FindingStrict = t.refinement(
111
- EndGeStart,
112
- (f) => Object.keys(f).every((k) => FINDING_KEYS.has(k)),
113
- "FindingStrict"
114
- );
115
- var FindingCodec = t.exact(FindingStrict);
116
118
  var SystemicRequired = t.type({
117
119
  title: t.string,
118
120
  description: t.string,
@@ -127,47 +129,34 @@ var SystemicOptional = t.partial({
127
129
  paths: t.array(t.string)
128
130
  });
129
131
  var SystemicProblemShape = t.intersection([SystemicRequired, RuleUrlCodec, SystemicOptional]);
130
- var SYSTEMIC_KEYS = /* @__PURE__ */ new Set([
131
- ...Object.keys(SystemicRequired.props),
132
- ...Object.keys(RuleUrlCodec.props),
133
- ...Object.keys(SystemicOptional.props)
132
+ var SystemicProblemCodec = strictExact("SystemicProblemStrict", SystemicProblemShape, [
133
+ SystemicRequired,
134
+ RuleUrlCodec,
135
+ SystemicOptional
134
136
  ]);
135
- var SystemicProblemStrict = t.refinement(
136
- SystemicProblemShape,
137
- (s) => Object.keys(s).every((k) => SYSTEMIC_KEYS.has(k)),
138
- "SystemicProblemStrict"
137
+ var ConsecutiveRounds = t.refinement(
138
+ t.number,
139
+ (n) => Number.isSafeInteger(n) && n >= 1,
140
+ "ConsecutiveRounds"
141
+ );
142
+ var StartRound = t.refinement(
143
+ t.number,
144
+ (n) => Number.isSafeInteger(n) && n >= 1,
145
+ "StartRound"
139
146
  );
140
- var SystemicProblemCodec = t.exact(SystemicProblemStrict);
141
147
  var RecurringShape = t.type({
142
148
  id: t.string,
143
- consecutive_rounds: t.refinement(
144
- t.number,
145
- (n) => Number.isSafeInteger(n) && n >= 1,
146
- "ConsecutiveRounds"
147
- ),
148
- start_round: t.refinement(
149
- t.number,
150
- (n) => Number.isSafeInteger(n) && n >= 1,
151
- "StartRound"
152
- )
149
+ consecutive_rounds: ConsecutiveRounds,
150
+ start_round: StartRound
153
151
  });
154
- var RECURRING_KEYS = new Set(Object.keys(RecurringShape.props));
155
- var RecurringCodec = t.refinement(
156
- RecurringShape,
157
- (r) => Object.keys(r).every((k) => RECURRING_KEYS.has(k)),
158
- "RecurringStrict"
159
- );
152
+ var RecurringCodec = strictExact("RecurringStrict", RecurringShape, [RecurringShape]);
160
153
  var ScopeMetastasisShape = t.type({
161
154
  decision_prompt: t.string,
162
155
  recurring: t.array(RecurringCodec)
163
156
  });
164
- var SCOPE_METASTASIS_KEYS = new Set(Object.keys(ScopeMetastasisShape.props));
165
- var ScopeMetastasisStrict = t.refinement(
166
- ScopeMetastasisShape,
167
- (s) => Object.keys(s).every((k) => SCOPE_METASTASIS_KEYS.has(k)),
168
- "ScopeMetastasisStrict"
169
- );
170
- var ScopeMetastasisCodec = t.exact(ScopeMetastasisStrict);
157
+ var ScopeMetastasisCodec = strictExact("ScopeMetastasisStrict", ScopeMetastasisShape, [
158
+ ScopeMetastasisShape
159
+ ]);
171
160
  var RoundNumber = t.refinement(
172
161
  t.number,
173
162
  (n) => Number.isSafeInteger(n) && n >= 1,
@@ -181,6 +170,7 @@ var idFrequencyCodec = (name) => new t.Type(
181
170
  const entries = [];
182
171
  for (const [k, v] of Object.entries(u)) {
183
172
  if (typeof v !== "number" || !Number.isSafeInteger(v) || v < 0) return t.failure(v, c);
173
+ if (v === 0) continue;
184
174
  entries.push([k, v]);
185
175
  }
186
176
  return t.success(Object.fromEntries(entries));
@@ -195,16 +185,10 @@ var ConvergenceRoundOptional = t.partial({
195
185
  sha: t.string
196
186
  });
197
187
  var ConvergenceRoundShape = t.intersection([ConvergenceRoundRequired, ConvergenceRoundOptional]);
198
- var CONVERGENCE_ROUND_KEYS = /* @__PURE__ */ new Set([
199
- ...Object.keys(ConvergenceRoundRequired.props),
200
- ...Object.keys(ConvergenceRoundOptional.props)
188
+ var ConvergenceRoundCodec = strictExact("ConvergenceRoundStrict", ConvergenceRoundShape, [
189
+ ConvergenceRoundRequired,
190
+ ConvergenceRoundOptional
201
191
  ]);
202
- var ConvergenceRoundStrict = t.refinement(
203
- ConvergenceRoundShape,
204
- (r) => Object.keys(r).every((k) => CONVERGENCE_ROUND_KEYS.has(k)),
205
- "ConvergenceRoundStrict"
206
- );
207
- var ConvergenceRoundCodec = t.exact(ConvergenceRoundStrict);
208
192
  var ConvergenceCoreShape = t.type({
209
193
  score: FiniteNumber,
210
194
  threshold: FiniteNumber,
@@ -212,16 +196,10 @@ var ConvergenceCoreShape = t.type({
212
196
  });
213
197
  var ConvergenceOptional = t.partial({ rounds: t.array(ConvergenceRoundCodec) });
214
198
  var ConvergenceShape = t.intersection([ConvergenceCoreShape, ConvergenceOptional]);
215
- var CONVERGENCE_KEYS = /* @__PURE__ */ new Set([
216
- ...Object.keys(ConvergenceCoreShape.props),
217
- ...Object.keys(ConvergenceOptional.props)
199
+ var ConvergenceCodec = strictExact("ConvergenceStrict", ConvergenceShape, [
200
+ ConvergenceCoreShape,
201
+ ConvergenceOptional
218
202
  ]);
219
- var ConvergenceStrict = t.refinement(
220
- ConvergenceShape,
221
- (c) => Object.keys(c).every((k) => CONVERGENCE_KEYS.has(k)),
222
- "ConvergenceStrict"
223
- );
224
- var ConvergenceCodec = t.exact(ConvergenceStrict);
225
203
  var LineDelta = t.refinement(
226
204
  t.number,
227
205
  (n) => Number.isInteger(n) && n >= 0,
@@ -271,102 +249,66 @@ var usableCountsMap = (v) => {
271
249
  );
272
250
  return entries.length === 0 ? void 0 : Object.fromEntries(entries);
273
251
  };
252
+ var mergedCountsMaps = (ids, codes) => {
253
+ const idsMap = usableCountsMap(ids);
254
+ const codesMap = usableCountsMap(codes);
255
+ if (idsMap === void 0) return codesMap;
256
+ if (codesMap === void 0) return idsMap;
257
+ return { ...codesMap, ...idsMap };
258
+ };
274
259
  var LegacyRuleCodec = t.partial({
275
260
  code: t.string,
276
261
  id: t.string,
277
262
  code_url: UriString
278
263
  });
279
- var FindingShapeV09 = t.intersection([
280
- t.type({
281
- path: t.string,
282
- start_line: LineNumber,
283
- end_line: LineNumber,
284
- severity: SeverityCodec,
285
- title: t.string,
286
- description: t.string,
287
- reasoning: t.string,
288
- confidence: Confidence,
289
- likelihood: Likelihood
290
- }),
291
- LegacyRuleCodec,
292
- t.partial({
293
- side: SideCodec,
294
- recommendation: t.string,
295
- patch: t.string
296
- })
297
- ]);
298
- var EndGeStartV09 = t.refinement(
299
- FindingShapeV09,
300
- (f) => f.end_line >= f.start_line,
301
- "EndGeStartV09"
302
- );
303
- var FindingCodecV09 = t.exact(EndGeStartV09);
264
+ var FindingShapeV09 = t.intersection([FindingCoreRequired, LegacyRuleCodec, FindingOptional]);
265
+ var FindingCodecV09 = t.exact(EndGeStart(FindingShapeV09));
304
266
  var SystemicV09Optional = t.partial({
305
267
  finding_codes: t.array(t.string),
306
268
  finding_ids: t.array(t.string),
307
269
  paths: t.array(t.string)
308
270
  });
309
271
  var SystemicV09Shape = t.intersection([SystemicRequired, LegacyRuleCodec, SystemicV09Optional]);
310
- var SYSTEMIC_V09_KEYS = /* @__PURE__ */ new Set([
311
- ...Object.keys(SystemicRequired.props),
312
- ...Object.keys(LegacyRuleCodec.props),
313
- ...Object.keys(SystemicV09Optional.props)
314
- ]);
315
- var SystemicV09Strict = t.refinement(
316
- SystemicV09Shape,
317
- (s) => Object.keys(s).every((k) => SYSTEMIC_V09_KEYS.has(k)),
318
- "SystemicV09Strict"
319
- );
320
- var SystemicProblemCodecV09 = t.exact(SystemicV09Strict);
321
- var RecurringV09Shape = t.intersection([
322
- t.partial({ code: t.string, id: t.string }),
323
- t.type({
324
- consecutive_rounds: t.refinement(
325
- t.number,
326
- (n) => Number.isSafeInteger(n) && n >= 1,
327
- "ConsecutiveRoundsV09"
328
- ),
329
- start_round: t.refinement(
330
- t.number,
331
- (n) => Number.isSafeInteger(n) && n >= 1,
332
- "StartRoundV09"
333
- )
334
- })
272
+ var SystemicProblemCodecV09 = strictExact("SystemicV09Strict", SystemicV09Shape, [
273
+ SystemicRequired,
274
+ LegacyRuleCodec,
275
+ SystemicV09Optional
335
276
  ]);
277
+ var RecurringV09Identity = t.partial({ code: t.string, id: t.string });
278
+ var RecurringV09Counts = t.type({
279
+ consecutive_rounds: ConsecutiveRounds,
280
+ start_round: StartRound
281
+ });
282
+ var RecurringV09Shape = t.intersection([RecurringV09Identity, RecurringV09Counts]);
336
283
  var ScopeMetastasisV09Shape = t.type({
337
284
  decision_prompt: t.string,
338
285
  recurring: t.array(RecurringV09Shape)
339
286
  });
340
- var SCOPE_METASTASIS_V09_KEYS = new Set(Object.keys(ScopeMetastasisV09Shape.props));
341
- var ScopeMetastasisV09Strict = t.refinement(
342
- ScopeMetastasisV09Shape,
343
- (s) => Object.keys(s).every((k) => SCOPE_METASTASIS_V09_KEYS.has(k)),
344
- "ScopeMetastasisV09Strict"
345
- );
346
- var ScopeMetastasisCodecV09 = t.exact(ScopeMetastasisV09Strict);
287
+ var ScopeMetastasisCodecV09 = strictExact("ScopeMetastasisV09Strict", ScopeMetastasisV09Shape, [
288
+ ScopeMetastasisV09Shape
289
+ ]);
347
290
  var CodeFrequencyV09 = idFrequencyCodec("CodeFrequencyV09");
291
+ var ConvergenceRoundV09Optional = t.partial({
292
+ score: FiniteNumber,
293
+ codes: CodeFrequencyV09,
294
+ ids: CodeFrequencyV09,
295
+ sha: t.string
296
+ });
348
297
  var ConvergenceRoundV09Shape = t.intersection([
349
- t.type({ round: RoundNumber }),
350
- t.partial({ score: FiniteNumber, codes: CodeFrequencyV09, ids: CodeFrequencyV09, sha: t.string })
298
+ ConvergenceRoundRequired,
299
+ ConvergenceRoundV09Optional
351
300
  ]);
352
- var CONVERGENCE_ROUND_V09_KEYS = /* @__PURE__ */ new Set(["round", "score", "codes", "ids", "sha"]);
353
- var ConvergenceRoundV09Strict = t.refinement(
301
+ var ConvergenceRoundCodecV09 = strictExact(
302
+ "ConvergenceRoundV09Strict",
354
303
  ConvergenceRoundV09Shape,
355
- (r) => Object.keys(r).every((k) => CONVERGENCE_ROUND_V09_KEYS.has(k)),
356
- "ConvergenceRoundV09Strict"
304
+ [ConvergenceRoundRequired, ConvergenceRoundV09Optional]
357
305
  );
358
- var ConvergenceRoundCodecV09 = t.exact(ConvergenceRoundV09Strict);
359
- var ConvergenceV09Shape = t.intersection([
306
+ var ConvergenceV09Optional = t.partial({ rounds: t.array(ConvergenceRoundCodecV09) });
307
+ var ConvergenceV09Shape = t.intersection([ConvergenceCoreShape, ConvergenceV09Optional]);
308
+ var ConvergenceCodecV09 = strictExact("ConvergenceV09Strict", ConvergenceV09Shape, [
360
309
  ConvergenceCoreShape,
361
- t.partial({ rounds: t.array(ConvergenceRoundCodecV09) })
310
+ ConvergenceV09Optional
362
311
  ]);
363
- var CONVERGENCE_V09_KEYS = /* @__PURE__ */ new Set([...Object.keys(ConvergenceCoreShape.props), "rounds"]);
364
- var ConvergenceV09Strict = t.refinement(
365
- ConvergenceV09Shape,
366
- (c) => Object.keys(c).every((k) => CONVERGENCE_V09_KEYS.has(k)),
367
- "ConvergenceV09Strict"
368
- );
369
- var ConvergenceCodecV09 = t.exact(ConvergenceV09Strict);
370
312
  var FindingsV09Shape = t.intersection([
371
313
  t.type({
372
314
  // Any pre-0.10 minor — the registry dispatches on major.minor before decoding, so the codec
@@ -419,7 +361,7 @@ var normalizeV09 = (doc) => {
419
361
  converged: doc.convergence.converged,
420
362
  ...doc.convergence.rounds !== void 0 ? {
421
363
  rounds: doc.convergence.rounds.map((r) => {
422
- const ids = usableCountsMap(r.ids) ?? usableCountsMap(r.codes);
364
+ const ids = mergedCountsMaps(r.ids, r.codes);
423
365
  return {
424
366
  round: r.round,
425
367
  ...r.score !== void 0 ? { score: r.score } : {},
@@ -490,26 +432,17 @@ var FlatModelPricesShape = t.type({
490
432
  cache_read: NonNegativePrice,
491
433
  cache_write: NonNegativePrice
492
434
  });
493
- var FLAT_PRICE_KEYS = new Set(Object.keys(FlatModelPricesShape.props));
494
- var FlatModelPricesStrict = t.refinement(
495
- FlatModelPricesShape,
496
- (p) => Object.keys(p).every((k) => FLAT_PRICE_KEYS.has(k)),
497
- "FlatModelPricesStrict"
498
- );
499
- var FlatModelPricesCodec = t.exact(FlatModelPricesStrict);
435
+ var FlatModelPricesCodec = strictExact("FlatModelPricesStrict", FlatModelPricesShape, [
436
+ FlatModelPricesShape
437
+ ]);
500
438
  var UTC_HHMM_RE = /^([01][0-9]|2[0-3]):[0-5][0-9]$/;
501
439
  var UtcHHMM = t.refinement(t.string, (s) => UTC_HHMM_RE.test(s), "UtcHHMM");
502
- var PriceSlotShape = t.intersection([
503
- t.type({ utc_from: UtcHHMM, utc_to: UtcHHMM }),
440
+ var PriceSlotAnchor = t.type({ utc_from: UtcHHMM, utc_to: UtcHHMM });
441
+ var PriceSlotShape = t.intersection([PriceSlotAnchor, FlatModelPricesShape]);
442
+ var PriceSlotCodec = strictExact("PriceSlotStrict", PriceSlotShape, [
443
+ PriceSlotAnchor,
504
444
  FlatModelPricesShape
505
445
  ]);
506
- var PRICE_SLOT_KEYS = /* @__PURE__ */ new Set(["utc_from", "utc_to", ...Object.keys(FlatModelPricesShape.props)]);
507
- var PriceSlotStrict = t.refinement(
508
- PriceSlotShape,
509
- (s) => Object.keys(s).every((k) => PRICE_SLOT_KEYS.has(k)),
510
- "PriceSlotStrict"
511
- );
512
- var PriceSlotCodec = t.exact(PriceSlotStrict);
513
446
  var NonEmptyPriceSlots = t.refinement(
514
447
  t.array(PriceSlotCodec),
515
448
  (a) => a.length >= 1,
@@ -518,16 +451,10 @@ var NonEmptyPriceSlots = t.refinement(
518
451
  var SlottedRequired = t.type({ slots: NonEmptyPriceSlots });
519
452
  var SlottedOptional = t.partial({ weekend_slots: NonEmptyPriceSlots });
520
453
  var SlottedShape = t.intersection([SlottedRequired, SlottedOptional]);
521
- var SLOTTED_KEYS = /* @__PURE__ */ new Set([
522
- ...Object.keys(SlottedRequired.props),
523
- ...Object.keys(SlottedOptional.props)
454
+ var SlottedModelPricesCodec = strictExact("SlottedModelPricesStrict", SlottedShape, [
455
+ SlottedRequired,
456
+ SlottedOptional
524
457
  ]);
525
- var SlottedModelPricesStrict = t.refinement(
526
- SlottedShape,
527
- (s) => Object.keys(s).every((k) => SLOTTED_KEYS.has(k)),
528
- "SlottedModelPricesStrict"
529
- );
530
- var SlottedModelPricesCodec = t.exact(SlottedModelPricesStrict);
531
458
  var ModelPricesCodec = t.union([FlatModelPricesCodec, SlottedModelPricesCodec]);
532
459
  var PriceMapCodec = t.type({
533
460
  _updated: t.string,
@@ -830,10 +757,13 @@ var isSeverityCounts = (u) => typeof u === "object" && u !== null && SEVERITIES.
830
757
  var MAX_IDS_PER_ROUND = 8;
831
758
  var hasId = (ids, code) => ids !== void 0 && Object.prototype.hasOwnProperty.call(ids, code);
832
759
  var escapeCodeBackticks = (code) => code.replace(/`/g, "-").replace(/\r?\n/g, " ");
833
- var normalizeIdCounts = (ids, priorCodes) => {
834
- const entries = Object.entries(usableCountsMap(ids) ?? {});
760
+ var normalizeIdCounts = (ids, priorCodes, preferredKeys, preValidated = false) => {
761
+ const entries = Object.entries((preValidated ? ids : usableCountsMap(ids)) ?? {});
835
762
  if (entries.length === 0) return void 0;
836
763
  const sorted = entries.sort((a, b) => {
764
+ const aPreferred = preferredKeys?.has(a[0]) === true ? 1 : 0;
765
+ const bPreferred = preferredKeys?.has(b[0]) === true ? 1 : 0;
766
+ if (aPreferred !== bPreferred) return bPreferred - aPreferred;
837
767
  if (b[1] !== a[1]) return b[1] - a[1];
838
768
  const aPrior = hasId(priorCodes, a[0]) ? 1 : 0;
839
769
  const bPrior = hasId(priorCodes, b[0]) ? 1 : 0;
@@ -853,7 +783,12 @@ var parseRounds = (body) => {
853
783
  let priorCodes;
854
784
  for (const u of decoded.filter(isSeverityCounts)) {
855
785
  const rec = u;
856
- const codes = normalizeIdCounts(rec["ids"], priorCodes) ?? normalizeIdCounts(rec["codes"], priorCodes);
786
+ const codes = normalizeIdCounts(
787
+ mergedCountsMaps(rec["ids"], rec["codes"]),
788
+ priorCodes,
789
+ new Set(Object.keys(usableCountsMap(rec["ids"]) ?? {})),
790
+ true
791
+ );
857
792
  priorCodes = codes;
858
793
  const sha = rec["sha"];
859
794
  const shaStr = typeof sha === "string" && sha !== "" ? sha : void 0;
@@ -1086,7 +1021,7 @@ var withLegacyConvergenceIds = (raw) => {
1086
1021
  const mapped = rounds.map((r) => {
1087
1022
  if (typeof r !== "object" || r === null) return r;
1088
1023
  const round = r;
1089
- const ids = usableCountsMap(round["ids"]) ?? usableCountsMap(round["codes"]);
1024
+ const ids = mergedCountsMaps(round["ids"], round["codes"]);
1090
1025
  if (ids === void 0) {
1091
1026
  return round["ids"] !== void 0 || round["codes"] !== void 0 ? Object.fromEntries(Object.entries(round).filter(([k]) => k !== "ids" && k !== "codes")) : round;
1092
1027
  }
@@ -1448,10 +1383,29 @@ var answeredRegistryFrom = (comments, botLogin) => {
1448
1383
  }
1449
1384
  return [...byKey.values()];
1450
1385
  };
1451
- var matches = (f, e) => e.code === resolveFindingId(f) || isSynthesizedFindingId(e.code) && e.title === f.title;
1452
- var isVerbatimReRaise = (f, e) => f.title === e.title && f.description === e.description && f.reasoning === e.reasoning && f.severity === e.severity && f.path === e.path && (f.patch ?? null) === e.patch;
1453
- var isAnsweredDrop = (f, e) => matches(f, e) && isVerbatimReRaise(f, e) && f.severity !== "critical";
1386
+ var isSynthesizedTitleMatch = (f, e) => isSynthesizedFindingId(e.code) && e.title === f.title;
1387
+ var matches = (resolvedId, f, e) => e.code === resolvedId || isSynthesizedTitleMatch(f, e);
1388
+ var VERBATIM_FIELDS = ["title", "description", "reasoning", "severity", "path", "patch"];
1389
+ var verbatimFieldEqual = (f, e, field) => field === "patch" ? (f.patch ?? null) === e.patch : f[field] === e[field];
1390
+ var isVerbatimReRaise = (f, e) => VERBATIM_FIELDS.every((field) => verbatimFieldEqual(f, e, field));
1391
+ var isAnsweredDrop = (resolvedId, f, e) => matches(resolvedId, f, e) && isVerbatimReRaise(f, e) && f.severity !== "critical";
1392
+ var verbatimMatchCount = (f, e) => VERBATIM_FIELDS.reduce((count, field) => count + (verbatimFieldEqual(f, e, field) ? 1 : 0), 0);
1454
1393
  var answeredNoteKey = (f) => f.id !== "" ? f.id : `title:${f.title}`;
1394
+ var NO_TITLE_MATCH = { entry: void 0, score: -1 };
1395
+ var bestTitleMatch = (f, registry) => {
1396
+ let best;
1397
+ let bestScore = -1;
1398
+ for (const e of registry) {
1399
+ if (!isSynthesizedTitleMatch(f, e)) continue;
1400
+ const score = verbatimMatchCount(f, e);
1401
+ if (score > bestScore) {
1402
+ best = e;
1403
+ bestScore = score;
1404
+ if (score === VERBATIM_FIELDS.length) break;
1405
+ }
1406
+ }
1407
+ return { entry: best, score: bestScore };
1408
+ };
1455
1409
  var answeredNote = (e) => `Re-raised; prior answer at ${e.replyUrl} by ${e.replyAuthor} \u2014 cite the new evidence that invalidates it.`;
1456
1410
  var applyAnswered = (findings, registry) => {
1457
1411
  const kept = [];
@@ -1460,14 +1414,18 @@ var applyAnswered = (findings, registry) => {
1460
1414
  const droppedFindingIds = [];
1461
1415
  let droppedCount = 0;
1462
1416
  for (const f of findings) {
1463
- const entry = registry.find((e) => e.code === resolveFindingId(f)) ?? registry.find((e) => isSynthesizedFindingId(e.code) && e.title === f.title);
1417
+ const resolvedId = resolveFindingId(f);
1418
+ const idMatch = registry.find((e) => e.code === resolvedId);
1419
+ const { entry: titleMatch, score: titleScore } = idMatch === void 0 ? bestTitleMatch(f, registry) : NO_TITLE_MATCH;
1420
+ const entry = idMatch ?? titleMatch;
1464
1421
  if (entry === void 0) {
1465
1422
  kept.push(f);
1466
1423
  continue;
1467
1424
  }
1468
- if (isAnsweredDrop(f, entry)) {
1425
+ const dropped = (idMatch !== void 0 ? isVerbatimReRaise(f, entry) : titleScore === VERBATIM_FIELDS.length) && f.severity !== "critical";
1426
+ if (dropped) {
1469
1427
  droppedByEntry.set(entry.replyId, entry);
1470
- droppedFindingIds.push(resolveFindingId(f));
1428
+ droppedFindingIds.push(resolvedId);
1471
1429
  droppedCount += 1;
1472
1430
  } else {
1473
1431
  kept.push(f);
@@ -3181,30 +3139,44 @@ var priorIdsFrom = (doc) => {
3181
3139
  });
3182
3140
  return [...idsOf("findings", false), ...idsOf("systemic_problems", true)];
3183
3141
  };
3142
+ var appendBestEffort = (path, label, body) => {
3143
+ if (path === void 0 || path === "") return;
3144
+ try {
3145
+ appendFileSync(path, body);
3146
+ } catch (err) {
3147
+ process.stderr.write(`Warning: could not write ${label}: ${errMsg(err)}
3148
+ `);
3149
+ }
3150
+ };
3151
+ var writePostedSignal = (value) => {
3152
+ appendBestEffort(
3153
+ process.env["GITHUB_OUTPUT"],
3154
+ "the posted signal to GITHUB_OUTPUT",
3155
+ `posted=${value ? "true" : "false"}
3156
+ `
3157
+ );
3158
+ };
3184
3159
  var appendRunSummary = (summaryPath, body) => {
3185
3160
  if (summaryPath === void 0 || summaryPath === "") return;
3186
3161
  const rendered = body();
3187
- try {
3188
- appendFileSync(summaryPath, `
3162
+ appendBestEffort(summaryPath, "the run summary", `
3189
3163
  ${rendered}
3190
3164
  `);
3191
- } catch (err) {
3192
- process.stderr.write(`Warning: could not write the run summary: ${errMsg(err)}
3193
- `);
3194
- }
3195
3165
  };
3196
- var upsertSticky = async (repo, prNumber, existing, body, ghApi) => {
3166
+ var upsertSticky = async (repo, prNumber, existing, body, ghApi, signal = false) => {
3197
3167
  if (existing !== null) {
3198
3168
  const patched = await patchComment(repo, existing.id, body, ghApi);
3199
3169
  process.stderr.write(
3200
3170
  `Updated sticky comment #${String(existing.id)} on PR #${String(prNumber)}
3201
3171
  `
3202
3172
  );
3173
+ if (signal) writePostedSignal(true);
3203
3174
  return { id: existing.id, url: patched?.html_url };
3204
3175
  }
3205
3176
  const posted = await postComment(repo, prNumber, body, ghApi);
3206
3177
  process.stderr.write(`Posted new sticky comment on PR #${String(prNumber)}
3207
3178
  `);
3179
+ if (signal && posted !== null) writePostedSignal(true);
3208
3180
  return posted ? { id: posted.id, url: posted.html_url } : null;
3209
3181
  };
3210
3182
  var isBotReview = (r) => typeof r === "object" && r !== null && typeof r.id === "number" && typeof r.state === "string" && typeof r.user?.login === "string";
@@ -3327,6 +3299,7 @@ var post = async (input, ghApi = runGhApi, readArtifact = ghArtifactReader) => {
3327
3299
  if (resolution.kind === "none") {
3328
3300
  process.stderr.write(`No open PR for ${input.headSha} \u2014 nothing to post
3329
3301
  `);
3302
+ writePostedSignal(false);
3330
3303
  process.exit(0);
3331
3304
  }
3332
3305
  if (resolution.kind === "not-open") {
@@ -3334,6 +3307,7 @@ var post = async (input, ghApi = runGhApi, readArtifact = ghArtifactReader) => {
3334
3307
  `PR #${String(resolution.prNumber)} for ${input.headSha} is not open (state: ${resolution.state}) \u2014 nothing to post
3335
3308
  `
3336
3309
  );
3310
+ writePostedSignal(false);
3337
3311
  process.exit(0);
3338
3312
  }
3339
3313
  const prNumber = resolution.prNumber;
@@ -3374,6 +3348,7 @@ var post = async (input, ghApi = runGhApi, readArtifact = ghArtifactReader) => {
3374
3348
  process.stderr.write(
3375
3349
  message ?? "Review did not complete and the sticky already reflects a completed review \u2014 leaving it in place\n"
3376
3350
  );
3351
+ writePostedSignal(true);
3377
3352
  process.exit(0);
3378
3353
  };
3379
3354
  const logAnsweredDrops = () => {
@@ -3401,7 +3376,7 @@ ${dropNote}` : ""}`,
3401
3376
  sticky.body
3402
3377
  )
3403
3378
  );
3404
- await upsertSticky(input.repo, prNumber, sticky, body, ghApi);
3379
+ await upsertSticky(input.repo, prNumber, sticky, body, ghApi, true);
3405
3380
  process.exit(0);
3406
3381
  };
3407
3382
  const prices = JSON.parse(readFileSync(input.pricesPath, "utf-8"));
@@ -3479,7 +3454,8 @@ ${dropNote}` : ""}`,
3479
3454
  prNumber,
3480
3455
  existingSticky,
3481
3456
  renderNotice("The diff for this PR is empty \u2014 nothing to review.", discussion),
3482
- ghApi
3457
+ ghApi,
3458
+ true
3483
3459
  );
3484
3460
  process.exit(0);
3485
3461
  }
@@ -3492,7 +3468,8 @@ ${dropNote}` : ""}`,
3492
3468
  prNumber,
3493
3469
  existingSticky,
3494
3470
  renderNotice(noticeMessageFor(findingsResult), discussion),
3495
- ghApi
3471
+ ghApi,
3472
+ true
3496
3473
  );
3497
3474
  process.exit(0);
3498
3475
  }
@@ -3617,7 +3594,7 @@ ${dropNote}` : ""}`,
3617
3594
  orphanedUnresolvable: orphanResolveFailed
3618
3595
  })
3619
3596
  );
3620
- await upsertSticky(input.repo, prNumber, existingSticky, body, ghApi);
3597
+ await upsertSticky(input.repo, prNumber, existingSticky, body, ghApi, true);
3621
3598
  appendRunSummary(process.env["GITHUB_STEP_SUMMARY"], () => body);
3622
3599
  if (inlineRequested) {
3623
3600
  process.stderr.write(
@@ -3743,7 +3720,8 @@ ${dropNote}` : ""}`,
3743
3720
  prNumber,
3744
3721
  existingSticky,
3745
3722
  renderBody(initialDisposition),
3746
- ghApi
3723
+ ghApi,
3724
+ true
3747
3725
  );
3748
3726
  const priorInlineComments = await listPriorBotCommentIds(
3749
3727
  input.repo,
@@ -5683,9 +5661,10 @@ var seedDraftCmd = defineCommand({
5683
5661
  const resolved = resolvedPriorValue(strippedPrior);
5684
5662
  const doc = resolved === null ? strippedPrior : {
5685
5663
  ...resolved,
5686
- findings: answeredRegistry !== null && answeredRegistry.length > 0 ? resolved.findings.filter(
5687
- (f) => !answeredRegistry.some((e) => isAnsweredDrop(f, e))
5688
- ) : resolved.findings
5664
+ findings: answeredRegistry !== null && answeredRegistry.length > 0 ? resolved.findings.filter((f) => {
5665
+ const resolvedId = resolveFindingId(f);
5666
+ return !answeredRegistry.some((e) => isAnsweredDrop(resolvedId, f, e));
5667
+ }) : resolved.findings
5689
5668
  };
5690
5669
  const carried = doc["scope_metastasis"];
5691
5670
  if (ScopeMetastasisCodec.decode(carried)._tag === "Right") return doc;