@biffo/cli 0.316.3 → 0.317.1

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.
@@ -204,7 +204,57 @@ class Branch:
204
204
  label: str
205
205
 
206
206
  def key(self) -> str:
207
- return f"{self.path}:{self.kind}:{self.label}"
207
+ """The ratchet's identity for this branch. Must not collide (#2026).
208
+
209
+ Two textually-identical branches in one file — two different
210
+ functions each with a bare `except ValueError:` — are common: the
211
+ vocabulary of exception type names is finite and heavily reused, and
212
+ `label` for a `fallback` is truncated to 50 chars on top of that. A
213
+ key of `path:kind:label` alone collapses both onto one entry, so a
214
+ second, genuinely new, never-executed branch sharing a label with an
215
+ already-baselined one in the same file was silently absorbed into
216
+ that entry: it was printed (the analyser saw it) but never marked
217
+ NEW, and `--check` exited 0 over an unverified branch nobody had
218
+ looked at. Confirmed live: a second unexecuted `except ValueError`
219
+ added in the same file as an already-baselined one produced no NEW
220
+ marker and exit 0, while a distinctly-labeled addition in the same
221
+ run was correctly flagged — isolating the cause to the missing
222
+ position, not to anything else about the change.
223
+
224
+ `line` is included to close that gap, chosen deliberately over an
225
+ occurrence ordinal among same-labeled branches in the file (e.g.
226
+ "2nd `except ValueError` in this file"). Both carry a cost and
227
+ neither is free:
228
+
229
+ - **Line number** (chosen): a new branch cannot collide with an
230
+ existing key at all, short of landing on the exact line number a
231
+ deleted branch used to occupy — vanishingly unlikely, since that
232
+ requires a line-count-preserving edit that puts an unrelated new
233
+ branch at that exact spot. The cost is churn: an unrelated edit
234
+ that shifts a baselined branch down a few lines (an import added
235
+ above it, a docstring reflowed) makes its key change too, so it
236
+ reads as NEW and the gate cries wolf until `--write` re-accepts
237
+ it. `unexecuted()`'s own docstring already documents that this
238
+ script's line-based coverage join is shift-sensitive in exactly
239
+ this way, so this does not introduce a new fragility, only extends
240
+ an existing one from the coverage join into the baseline key.
241
+ - **Occurrence ordinal** (rejected): stable under a line shift
242
+ elsewhere in the file, but ambiguous under reordering. If a
243
+ baselined branch is removed and an unrelated new same-labeled
244
+ branch appears earlier in the file than a survivor, the survivor's
245
+ ordinal shifts onto the new branch's — reproducing this exact
246
+ issue by a different route, because an ordinal is still a
247
+ position, just a fragile relative one instead of a stable
248
+ absolute one.
249
+
250
+ Level reached: 3 (fail-closed) rather than 4 (detect-only) — a
251
+ distinct line number for every distinct AST node means a genuinely
252
+ new branch cannot be absorbed into an existing entry at all, not
253
+ merely flagged more often. Changing this format means every
254
+ instance's committed baseline must be regenerated; see the PR that
255
+ introduced this comment for the exact command.
256
+ """
257
+ return f"{self.path}:{self.line}:{self.kind}:{self.label}"
208
258
 
209
259
 
210
260
  def _handler_label(node: ast.ExceptHandler) -> str:
@@ -204,7 +204,57 @@ class Branch:
204
204
  label: str
205
205
 
206
206
  def key(self) -> str:
207
- return f"{self.path}:{self.kind}:{self.label}"
207
+ """The ratchet's identity for this branch. Must not collide (#2026).
208
+
209
+ Two textually-identical branches in one file — two different
210
+ functions each with a bare `except ValueError:` — are common: the
211
+ vocabulary of exception type names is finite and heavily reused, and
212
+ `label` for a `fallback` is truncated to 50 chars on top of that. A
213
+ key of `path:kind:label` alone collapses both onto one entry, so a
214
+ second, genuinely new, never-executed branch sharing a label with an
215
+ already-baselined one in the same file was silently absorbed into
216
+ that entry: it was printed (the analyser saw it) but never marked
217
+ NEW, and `--check` exited 0 over an unverified branch nobody had
218
+ looked at. Confirmed live: a second unexecuted `except ValueError`
219
+ added in the same file as an already-baselined one produced no NEW
220
+ marker and exit 0, while a distinctly-labeled addition in the same
221
+ run was correctly flagged — isolating the cause to the missing
222
+ position, not to anything else about the change.
223
+
224
+ `line` is included to close that gap, chosen deliberately over an
225
+ occurrence ordinal among same-labeled branches in the file (e.g.
226
+ "2nd `except ValueError` in this file"). Both carry a cost and
227
+ neither is free:
228
+
229
+ - **Line number** (chosen): a new branch cannot collide with an
230
+ existing key at all, short of landing on the exact line number a
231
+ deleted branch used to occupy — vanishingly unlikely, since that
232
+ requires a line-count-preserving edit that puts an unrelated new
233
+ branch at that exact spot. The cost is churn: an unrelated edit
234
+ that shifts a baselined branch down a few lines (an import added
235
+ above it, a docstring reflowed) makes its key change too, so it
236
+ reads as NEW and the gate cries wolf until `--write` re-accepts
237
+ it. `unexecuted()`'s own docstring already documents that this
238
+ script's line-based coverage join is shift-sensitive in exactly
239
+ this way, so this does not introduce a new fragility, only extends
240
+ an existing one from the coverage join into the baseline key.
241
+ - **Occurrence ordinal** (rejected): stable under a line shift
242
+ elsewhere in the file, but ambiguous under reordering. If a
243
+ baselined branch is removed and an unrelated new same-labeled
244
+ branch appears earlier in the file than a survivor, the survivor's
245
+ ordinal shifts onto the new branch's — reproducing this exact
246
+ issue by a different route, because an ordinal is still a
247
+ position, just a fragile relative one instead of a stable
248
+ absolute one.
249
+
250
+ Level reached: 3 (fail-closed) rather than 4 (detect-only) — a
251
+ distinct line number for every distinct AST node means a genuinely
252
+ new branch cannot be absorbed into an existing entry at all, not
253
+ merely flagged more often. Changing this format means every
254
+ instance's committed baseline must be regenerated; see the PR that
255
+ introduced this comment for the exact command.
256
+ """
257
+ return f"{self.path}:{self.line}:{self.kind}:{self.label}"
208
258
 
209
259
 
210
260
  def _handler_label(node: ast.ExceptHandler) -> str:
package/dist/index.js CHANGED
@@ -10330,7 +10330,8 @@ async function checkViaProvenance(name, record, repoUrl, git) {
10330
10330
  name,
10331
10331
  status: "cannot-tell",
10332
10332
  method: "unresolvable",
10333
- detail: `could not reach ${repoUrl} (network or authentication failure)`
10333
+ detail: `could not reach ${repoUrl} (network or authentication failure)`,
10334
+ source: repoUrl
10334
10335
  };
10335
10336
  }
10336
10337
  if (remoteHeadSha === record.sha) {
@@ -10338,7 +10339,8 @@ async function checkViaProvenance(name, record, repoUrl, git) {
10338
10339
  name,
10339
10340
  status: "up-to-date",
10340
10341
  method: "provenance",
10341
- detail: `matches ${repoUrl}'s default branch (${shortSha(remoteHeadSha)})`
10342
+ detail: `matches ${repoUrl}'s default branch (${shortSha(remoteHeadSha)})`,
10343
+ source: repoUrl
10342
10344
  };
10343
10345
  }
10344
10346
  let clone;
@@ -10349,7 +10351,8 @@ async function checkViaProvenance(name, record, repoUrl, git) {
10349
10351
  name,
10350
10352
  status: "cannot-tell",
10351
10353
  method: "unresolvable",
10352
- detail: `could not clone ${repoUrl} to count commits behind (network or authentication failure)`
10354
+ detail: `could not clone ${repoUrl} to count commits behind (network or authentication failure)`,
10355
+ source: repoUrl
10353
10356
  };
10354
10357
  }
10355
10358
  try {
@@ -10359,7 +10362,8 @@ async function checkViaProvenance(name, record, repoUrl, git) {
10359
10362
  name,
10360
10363
  status: "cannot-tell",
10361
10364
  method: "unresolvable",
10362
- detail: `recorded commit ${shortSha(record.sha)} was not found in ${repoUrl}'s history (rebased or force-pushed?) \u2014 cannot count commits behind`
10365
+ detail: `recorded commit ${shortSha(record.sha)} was not found in ${repoUrl}'s history (rebased or force-pushed?) \u2014 cannot count commits behind`,
10366
+ source: repoUrl
10363
10367
  };
10364
10368
  }
10365
10369
  if (commitsBehind === 0) {
@@ -10367,7 +10371,8 @@ async function checkViaProvenance(name, record, repoUrl, git) {
10367
10371
  name,
10368
10372
  status: "up-to-date",
10369
10373
  method: "provenance",
10370
- detail: `matches ${repoUrl}'s default branch (${shortSha(remoteHeadSha)})`
10374
+ detail: `matches ${repoUrl}'s default branch (${shortSha(remoteHeadSha)})`,
10375
+ source: repoUrl
10371
10376
  };
10372
10377
  }
10373
10378
  return {
@@ -10375,7 +10380,8 @@ async function checkViaProvenance(name, record, repoUrl, git) {
10375
10380
  status: "behind",
10376
10381
  commitsBehind,
10377
10382
  method: "provenance",
10378
- detail: `${commitsBehind} commit(s) behind ${repoUrl}'s default branch`
10383
+ detail: `${commitsBehind} commit(s) behind ${repoUrl}'s default branch`,
10384
+ source: repoUrl
10379
10385
  };
10380
10386
  } finally {
10381
10387
  git.cleanup(clone);
@@ -10392,7 +10398,8 @@ async function checkViaContentDiff(name, pluginDir2, sourceDir, opts, git) {
10392
10398
  name,
10393
10399
  status: "cannot-tell",
10394
10400
  method: "unresolvable",
10395
- detail: `could not clone ${sourceDir} for a content comparison (network or authentication failure)`
10401
+ detail: `could not clone ${sourceDir} for a content comparison (network or authentication failure)`,
10402
+ source: sourceDir
10396
10403
  };
10397
10404
  }
10398
10405
  effectiveSourceDir = cloneDir;
@@ -10406,7 +10413,8 @@ async function checkViaContentDiff(name, pluginDir2, sourceDir, opts, git) {
10406
10413
  status: "up-to-date",
10407
10414
  method: "content-diff",
10408
10415
  filesDiffering: 0,
10409
- detail: `byte-identical to ${originDescription} (no provenance recorded, so an exact commit could not be named)`
10416
+ detail: `byte-identical to ${originDescription} (no provenance recorded, so an exact commit could not be named)`,
10417
+ source: originDescription
10410
10418
  };
10411
10419
  }
10412
10420
  return {
@@ -10414,7 +10422,8 @@ async function checkViaContentDiff(name, pluginDir2, sourceDir, opts, git) {
10414
10422
  status: "behind",
10415
10423
  filesDiffering,
10416
10424
  method: "content-diff",
10417
- detail: `${filesDiffering} file(s) differ from ${originDescription} (no provenance recorded, so an exact commit count could not be determined)`
10425
+ detail: `${filesDiffering} file(s) differ from ${originDescription} (no provenance recorded, so an exact commit count could not be determined)`,
10426
+ source: originDescription
10418
10427
  };
10419
10428
  } finally {
10420
10429
  if (cloneDir) git.cleanup(cloneDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.316.3",
3
+ "version": "0.317.1",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",