Skip to content

fix(unskip): Naht nur noch auf devloop-verwaltete Tests (v0.8.1) - #3

Merged
TMogdans merged 2 commits into
mainfrom
fix/unskip-managed-tests-only
Aug 28, 2026
Merged

TMogdans merged 2 commits into
mainfrom
fix/unskip-managed-tests-only

Conversation

@TMogdans

Copy link
Copy Markdown
Collaborator

Der Fehlalarm

verify-unskip lief auf jedem PR des Ziel-Repos — einzige Ausnahme isSpecBranch(headBranch)
(devloop/spec/* → No-op). TEST_FILE = /\.(test|spec)\.[jt]sx?$/ erfasst jede Testdatei, und
für neue Dateien gilt isAllowedTestEdit("", neu) === !hasActiveTest(neu).

Folge: außerhalb eines devloop/spec/*-Branches konnte niemand eine gewöhnliche, aktive
Testdatei hinzufügen.
Live aufgelaufen an bsk-PR #157, der ein reines Entwickler-Werkzeug
(tools/dead-members) mit 32 eigenen node:test-Tests beisteuert — mit devloop hat das nichts zu
tun. Verdikt: 5 × "new test file contains an active (non-.skip) test", während der Freigabe-Teil
sauber {"ok":true,"tier":"T1"} meldete.

Der Wächter unterstellte, dass alle Tests im Repo devloop-verwaltet sind. Die Naht (§11,
Anti-Kollusion Test↔Code) soll aber nur die aus der Spec abgeleiteten Tests schützen.

Der Fix

Neu: .devloop/managed-tests.json im Ziel-Repo — eine Glob-Liste der devloop-verwalteten
Testpfade (bare array oder { "globs": [...] }). Nur für Treffer gilt die Naht.

  • Konfigurationsdatei, nicht Branch-Name. Ein Agent wählt seinen Branch-Namen selbst; eine
    branchnamen-gekoppelte Ausnahme wäre Selbstbedienung. .devloop/** steht im geschützten Satz
    — verify-review failt bei jeder Berührung, tier-unabhängig und vor dem Unskip-Schritt.
  • Basis-Ref entscheidet. Die Liste kommt zuerst aus <base>:.devloop/managed-tests.json; ein PR
    kann die Naht nicht für sich selbst lockern. Nur wenn die Datei auf base fehlt, zählt der
    Checkout (Adoptions-PR — der berührt zwangsläufig den geschützten Satz).
  • Fail-closed & rückwärtskompatibel. Datei fehlt / leer / kaputt / falsch geformt →
    jede Testdatei ist verwaltet = exakt das Verhalten vor v0.8.1. Kein bestehendes Ziel-Repo
    wird durch den Bump still lockerer. Ein explizit leeres [] wird respektiert.
  • Sichtbarkeit. Der Ausgabe-JSON nennt jetzt managed, managedSource, checked und
    unmanaged — ein grüner Lauf ist nie still ein entwaffneter.
  • init legt das Skeleton mit globs: ["**"] an (= heutiges Verhalten) und notiert laut,
    dass es einzuengen ist.

Das bestehende isSpecBranch-No-op bleibt unangetastet — dieselbe Schwäche, aber ein eigener
Befund; hier zu ändern hieße, zwei Dinge in einem Diff zu vermischen.

Nachweise

Gegenprobe am echten Fall (bsk-Branch chore/tools-dead-members gegen origin/main, in einem
read-only --shared-Klon; das bsk-Repo wurde nicht angefasst):

Lauf Ergebnis
vorher, devloop v0.8.0 ok:false, 5 Verstöße, exit 1
nachher, neues dist, ohne Konfig ok:false, 5 Verstöße, exit 1 — Rückwärtskompatibilität
nachher, mit .devloop/managed-tests.json auf origin/main ok:true, 0 Verstöße, exit 0, managedSource:"base"
Gegenprobe: aktiver Test nach services/location-service/api/spec-tests/ geschmuggelt ok:false, 1 Verstoß, exit 1 — die Naht bleibt scharf

Mutationsproben (jeweils Implementierung gezielt verfälscht, dann npm test):

Mutation rot
M1 kaputte Konfig failt open (return [] statt null) 2 Tests (fail closed … never a partial list, a malformed managed-tests.json falls back to managing everything)
M2 isManagedTestPath Default gekippt (null ⇒ nichts verwaltet) 6 Tests, u. a. zwei bestehende (exit 1 when implement also edited an assertion)
M3 CLI bevorzugt den PR-Checkout vor base 2 Tests (the BASE list decides, when base has no list …)
M4 CLI lässt den Scope-Filter weg 2 Tests (an ORDINARY new test file … passes — the fehlalarm)
M5 init-Skeleton vom Fail-closed-** weg verengt 1 Test (init scaffolds the unskip-seam scope at its FAIL-CLOSED default)

Gates: 188 Tests grün (vorher 174, +14), tsc --noEmit sauber, npm run check:dist grün
(dist/ mitgebaut und mitcommittet).

Release

chore(release): v0.8.1 bumpt package.json + .claude-plugin/plugin.json. Tag nach dem
Merge auf main:

git tag -a v0.8.1 -m "v0.8.1 — unskip seam scoped to devloop-managed tests" <merge-sha>
git push origin v0.8.1

Der Release enthält zusätzlich 4a55f73 (per-service Stryker-Config in Monorepos), das nach dem
v0.8.0-Tag auf main landete.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BFcJSagot15i7RfhN9hkwp

TMogdans and others added 2 commits August 28, 2026 07:29
verify-unskip ran on every PR (only `devloop/spec/*` was exempt) and its
TEST_FILE regex claimed EVERY test file in the target repo. Since a new file
is allowed only if it has no active test, nobody could add an ordinary,
active test file outside a spec branch. Live: bsk PR #157 contributes a plain
dev tool (tools/dead-members) with its own node:test suite and collected
5 x "new test file contains an active (non-.skip) test" — a pure false alarm,
while the approval half reported {"ok":true,"tier":"T1"}.

The seam (§11, anti-collusion test<->code) is meant to protect the tests
spec-to-tests derived from a reviewed spec, not every test in the repo.

- core/managed-tests: parse `.devloop/managed-tests.json` (bare array or
  {globs:[...]}) and decide per path whether the seam binds it.
- cli/verify-unskip: enforce the seam only on managed paths; report `managed`,
  `managedSource`, `checked` and `unmanaged` so a green run is never silently
  a disarmed one.
- The list is read from the BASE ref first — a PR cannot widen its own
  exemption; only when the file is absent on base does the checkout count
  (adoption PR, which necessarily touches the protected set).
- Config file, NOT branch name: an agent picks its own branch name, so a
  branch-coupled exemption is self-serve. `.devloop/**` is in the protected
  set, and verify-review fails a protected-set touch unconditionally and
  BEFORE the unskip step runs.
- Fail-closed & backwards compatible: absent, empty, malformed or
  wrong-shaped config => every test file is managed = pre-v0.8.1 behaviour.
  No adopter gets looser by upgrading. An EXPLICIT `[]` is honoured.
- init scaffolds the skeleton at its fail-closed default (`globs: ["**"]`)
  and notes loudly that it must be narrowed.
- git stderr silenced: `git show <base>:<new file>` legitimately fails for
  every added file; the "fatal: path ... does not exist" noise is not a finding.

188 tests green (was 174), tsc clean, dist rebuilt in sync.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01BFcJSagot15i7RfhN9hkwp
Also ships 4a55f73 (per-service stryker config detection in monorepos), which
landed on main after the v0.8.0 tag.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01BFcJSagot15i7RfhN9hkwp
@TMogdans
TMogdans merged commit b7e7115 into main Aug 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant