Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/score-folded-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"uptide": patch
---

`uptide pack test` counts a ground-truth site that a folded finding lists as its evidence as covered by that finding, so recall reflects what `check` reports. The typescript pack's breaking recall goes from 17% to 99% on unchanged ground truth: its TypeScript 6 `types` default errors fold into one tsconfig finding each.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The contract: [migration packs](docs/packs.md).
| `@sentry/nextjs` | 10.x → 11.x | 100% | 100% | 8 | cohstats/coh3-stats, xdoubleu/tools.xdoubleu.com, maximunited/yomu | verified |
| `@sentry/react` | 10.x → 11.x | 100% | 100% | 2 | owine/youth-activity-scheduler, successible/cleanslate | candidate |
| `stripe` | 14 and newer | 100% | 92% | 21 | unkeyed/unkey, nextjs/saas-starter, starl3xx/wallet-to-social, AtlasDevHQ/atlas | verified |
| `typescript` | 5.x → 6.x | 100% | 17% | 1399 | m-lyon/recipe, robrechtme/recipes, JosephMaynard/dependency-radar, CroffZ/github-release-notifier, SignalK/signalk-derived-data, shogo82148/typescript-action, Borewit/music-metadata | verified |
| `typescript` | 5.x → 6.x | 100% | 99% | 1399 | m-lyon/recipe, robrechtme/recipes, JosephMaynard/dependency-radar, CroffZ/github-release-notifier, SignalK/signalk-derived-data, shogo82148/typescript-action, Borewit/music-metadata | verified |
| `vitest` | 4.x → 5.x | 100% | 50% | 32 | votingworks/vxsuite, vitorvasc/opentelemetry-ecosystem-explorer | verified |
| `zod` | 3.x → 4.x | 100% | 64% | 219 | ugurkocde/AwesomeIntune, howardyang2009/PATH, band-ai/band-sdk-typescript, vzdc-artcc/website, CANShift/canshift-core | verified |
<!-- packs:end -->
Expand Down
4 changes: 3 additions & 1 deletion docs/packs.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ site is reported is part of the contract (`reported`):
findings are claimed and folds compiler errors that one edit elsewhere resolves into a
finding at that edit, as their evidence. The typescript pack folds the "Cannot find
name" errors of the 6.0 `types: []` default into one at the tsconfig that leaves
`types` unset.
`types` unset. `pack test` counts a ground-truth site listed as such evidence as
covered, at the site's own rule and severity; evidence the truth does not name is not
scored.
- `decision`: the pull request lists it under "Decisions for you", with the files and lines.
- `test-follow-up`: an assertion is updated only after the migrated code made that test
fail, and the test then has to pass (zod's reworded default messages).
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/packs/tooling/pack-test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ it('scores an anchored finding at its anchor; the call sites listed as evidence
expect(predictedSites(report, aiPack).map((s) => `${s.file}:${s.line} ${s.rule}`)).toEqual([
'packages/lib/hook.ts:6 tool-context',
]);
// Evidence the ground truth names is covered by the anchor, at the truth's rule and severity;
// evidence it does not name (x.tsx:11) stays unscored.
const expected = [
{ file: 'apps/b/src/y.tsx', line: 59, rule: 'generic', severity: 'breaking' as const },
];
expect(predictedSites(report, aiPack, expected)).toEqual([
{ file: 'apps/b/src/y.tsx', line: 59, rule: 'generic', severity: 'breaking' },
{ file: 'packages/lib/hook.ts', line: 6, rule: 'tool-context', severity: 'breaking' },
]);
});

it('counts a required move as moved, and a move check reports only a new major makes as explained', () => {
Expand Down
18 changes: 16 additions & 2 deletions packages/core/src/packs/tooling/pack-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,14 @@ const key = (s: { file: string; line: number }): string => `${s.file}:${s.line}`
/**
* The sites `check` shows for the package, from its plan: the findings worth acting on,
* grouped under the rule `fix` would apply. Repository-relative, as ground truth is written.
* An `expected` site an anchored finding lists as evidence is shown too (see below).
*/
export function predictedSites(report: CheckReport, pack: Pack): ScoredSite[] {
export function predictedSites(
report: CheckReport,
pack: Pack,
expected: ScoredSite[] = [],
): ScoredSite[] {
const wanted = new Map(expected.map((e) => [key(e), e]));
const own = new Set([...pack.rules.map((r) => r.id), ...pack.behavior.map((b) => b.id)]);
const sites = new Map<string, ScoredSite>();
for (const p of report.packages) {
Expand Down Expand Up @@ -184,6 +190,14 @@ export function predictedSites(report: CheckReport, pack: Pack): ScoredSite[] {
// One site, one rule: the more severe group wins, as the plan lists it first.
if (!sites.has(key(site))) sites.set(key(site), site);
}
// Evidence the ground truth names is covered: `check` reports the line under its anchor
// (TypeScript 6's `types` errors under one tsconfig finding, #136's option cascades), so
// it scores as the site the truth expects, at that site's rule and severity. Evidence the
// truth does not name stays unscored.
for (const k of evidence) {
const site = wanted.get(k);
if (site && !sites.has(k)) sites.set(k, { ...site });
}
}
return [...sites.values()].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
}
Expand Down Expand Up @@ -350,7 +364,7 @@ async function scoreRepo(
if (installed.length > 0 && !installed.includes(entry.from))
base.installedMismatch = `the lockfile has ${installed.join(', ')}, the ground truth says ${entry.from}`;
const own = report.packages.filter((p) => p.name === pack.name);
const predicted = predictedSites(report, pack);
const predicted = predictedSites(report, pack, expected);
return {
...base,
...companionOutcome(own, entry.with, (name) => dependsOn(project, dir, name, pack.name)),
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/packs/typescript/RESEARCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ The recall figure measures the original truth's convention (every error line a s
the engine's (one site per edit). It is not a count of breaks `check` misses: every folded
error is listed under its tsconfig finding.

**Scoring the folded evidence.** Since then `pack test` counts a ground-truth site that an
anchored finding lists as evidence as covered by that finding, at the site's own rule and
severity (`predictedSites`, as for #136's option cascades). The ground truth did not change.
Re-scored 2026-10-15: breaking recall **99% (1,379 of 1,399)**, 0 breaking false positives,
the same as before the fold. The 20 breaking misses are the second-round and same-change lines
listed under "Measured `pack test`".

## Verified under UPT-48

`pnpm uptide pack test typescript --write` on main (`530f90c`, UPT-48's gate), with its own
Expand All @@ -226,7 +233,7 @@ is unchanged (same digest as the candidate record).
- Repositories: 7 public (gate: ≥2).
- Real breaking sites: 1,399 expected (gate: ≥5).
- Breaking false positives: 0 (gate: 0). Breaking precision 100%, recall 17% (240 of 1,399; the
folded `types` errors above).
folded `types` errors above), 99% (1,379) once the scorer counts folded evidence (above).
- Behavior notes: precision 20%, recall 50% (2 TP, 8 FP, 2 FN), all `changed-defaults` anchors at
`tsconfig*.json:1`. Notes do not count against a breaking-class gate.
- Per-rule real-world true positives: `base-url` 4, `module-resolution-node10` 2, `target-es5` 1,
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/packs/typescript/verification.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"repos": 7,
"breaking": {
"precision": 1,
"recall": 0.1716,
"recall": 0.9857,
"falsePositives": 0,
"truePositives": 240,
"falseNegatives": 1159
"truePositives": 1379,
"falseNegatives": 20
},
"behavior": {
"precision": 0.2,
Expand Down
Loading