Skip to content

Releases: phantomghost2023/manual-cli

v0.11.4 — crates drill, multi-machine bounds, Node-free fallback

Choose a tag to compare

@phantomghost2023 phantomghost2023 released this 22 Sep 23:34

Four threads from the field, one release.

The canary triaged

The v0.11.3 wild run came back 8/11. All three red cells were legible:

  • npm/cli — upstream drift. Their committed package-lock.json is out of sync at HEAD; npm ci fails before any verifier runs. The matrix cell moves to microsoft/TypeScript (npm, synced lockfile, suite runs in their CI).
  • yarn-1 on react-router — upstream migration. Their HEAD now declares packageManager: yarn@pnpm@11.7.0; the tree the cell installs no longer exists. The cell pins the yarn-1 tree the local drill validated.
  • Python lifecycle on httpx — honest report. Discovery proposed tests.python from a layout that passes only through their no-dependency test helper; a plain-pytest claim cannot hold there. The lifecycle cell moves to pallets/flask, whose [tool.pytest.ini_options] the canary can read.

Two maintenance rows and one honest report — nothing reverted, exactly the split the workflow header promises.

The crates drill (sharkdp/bat, 316 locked crates)

Two bugs, both found only on the wild repo:

  • False positives (5 + 2 stale, 2.2%). Cargo registry dirs are name-version, and build metadata like toml-1.1.2+spec-1.1.0 contains dashes; last-dash splitting parsed such crates as name toml-1.1.2+spec, version 1.1.0. The split now resolves like cargo itself does: the earliest dash-remainder that parses as a full semver is the version. Healthy verdict after: ok: true — 316 crate(s) present, matching Cargo.lock in 17 ms, zero false positives; damage in registry/src + registry/cache flagged by name and repaired by cargo fetch.
  • A false negative. With a populated $CARGO_HOME and a vendor/ tree, deleting a vendored crate still verified ok: true — the shared registry cache masked the gap (the twin of the Go modules.txt hole). The vendored tree is checked on its own now (both cargo layouts), and the verdict follows the wiring rule: when .cargo/config replaces crates.io with the vendored sources, a gap is a build failure and answers no; an unwired vendor — bat ships a rustflags-only config — is spare evidence, the gap named without judging. The unwired cargo vendor damage test (deleted crate, no config) answers ok: true with the gap named; wired, the same damage answers ok: false.

observe aggregates across machines

Raise and tighten proposals now merge the committed ledger's per-machine history with the local tail, so a bound reflects the slowest machine's story: the comparator is the slowest machine's p90 against every other machine's p90, and tightening is refused when the local tail would flake the machine the ledger knows about. Ledger runs killed at the bound — which carry no duration — count as raise evidence instead of vanishing as no-data, and a raise must clear the bound that failed. One machine? Behavior unchanged.

A manual outlives Node

scripts/fallback/verify.py and scripts/fallback/brief.py: the smallest honest slice of manual verify for hosts with no Node — same manual/v1 frontmatter, command and expression checks (exit-code expectations, exists(), manifest(), lockActive()), npm/yarn-1/pnpm/venv/gomod lockfile comparisons with the same report-not-judge answers (other-platform entries skipped, uv-lock refusal honored), prerequisite satisfaction, and state.json stamps marked "tool": "python-fallback". No sandbox, no digests, no ledger — the banner says so every run. Proven on the demo repo (5/5 fresh, including the trap's exit-7 expectation), an npm fixture, and a Go checkout running its real 27-second suite under bash.

283 tests green. Full changelog in CHANGELOG.md; the drills and what they found in docs/WILD-REPOS.md.

v0.11.3 — bounds that age with the suite, and canaries that watch the wild

Choose a tag to compare

@phantomghost2023 phantomghost2023 released this 22 Sep 21:33

observe recalibrates bounds from history — and rewrites the measurement with them

A bound calibrated once at init is a snapshot; the suite it describes keeps moving. raise is the new middle direction between tighten and relax: when the worst run meets the bound or the 90th percentile crowds it (75%), observe proposes raising the bound while the claim is still fresh — before a busy machine turns a suite that drifted from 2s to 24s under a 30s bound into a false alarm. All three directions share one formula (3× p50, 1.5× p90, 1.1× worst), because two formulas can disagree about one series.

Every proposal — tighten, raise, relax — now also patches observation.measured_ms to the median of the accumulated history, with observation.samples and observation.measured_from: "verify history" as provenance. The claim stops advertising the single number init's probe took the day it was written.

The wild drills are release canaries

.github/workflows/wild.yml fires on release publish, monthly, and on demand — 8 builtin cells (pnpm/npm/yarn/bun on vuejs/core, npm/cli, react-router, oven-sh/bun; venv on httpx and flask; gomod on cobra and caddy — each cloned and installed by the ecosystem's own tooling) plus 3 lifecycle cells running the full init → probe → accept → verify path with the install performed by verify's prerequisite machinery. The one rule: a healthy tree must never be reported unsatisfied — and ok: null, correctly withheld, is a pass. test/wild.mjs is the checker; docs/WILD-REPOS.md documents every drill's false-positive rate and what it changed.

Discovery beyond package.json

manual init on spf13/cobra printed "0 candidate(s)" on a repo with a test suite — the tool shipped verifiers for eight ecosystems and proposed no claim about seven of them. Discovery now reads go.mod + _test.go globs (a testless module's go test ./... exits 0 having tested nothing, so it is not proposed), vendored-Go facts, pyproject/requirements/uv.lock and declared pytest. The Python install is chosen from the lockfile: uv sync --frozen, poetry install, or venv+pip. A repository-level go mod download prerequisite is declared when the module cache is empty — the one shared-location write discovery proposes, because a module cache is additive and re-derivable. The lifecycle canary now fails on "0 candidates": "proposed nothing" and "had nothing to propose" must not look identical.

Fixed: two bugs only a lifecycle could catch

  • A vendored Go tree was trusted on its manifest. Deleting vendor/github.com/spf13/pflag still verified ok: true — the vendor branch compared go.mod against vendor/modules.txt and never looked at the disk. Proven at caddy's scale (787 packages promised, one deleted, still "ok") before fixing: the manifest's package lines are checked as directories now (healthy caddy: 379 ms; damage: named). Same hole yarn-1's integrity check has; the lesson generalizes: a lockfile is evidence about an install, only the tree can confirm it.
  • A repo's own virtualenv never reached PATH on Windows. localBins() listed only .venv/bin; on encode/httpx a fresh venv was built, a 64 s install succeeded, and python -m pytest was still answered by the system interpreter. Both layouts are on PATH now.

277 tests / 52 suites green (11 new), own manual 5/5 fresh.

v0.11.2 — the pnpm builtin tells the truth on pnpm 12, and bounds that measure themselves

Choose a tag to compare

@phantomghost2023 phantomghost2023 released this 22 Sep 13:50

[0.11.2] - 2026-09-22

Fixed

  • The pnpm builtin reported up to 165 false "missing" packages on a freshly synced pnpm-12 tree. Measured end to end on vuejs/core (660 lockfile entries, pnpm install --frozen-lockfile exit 0 immediately before the check). Four independent causes, each found on the wild repo and each fixed against it:
    1. Peer-suffix directory names. pnpm 11 named store dirs name@version; pnpm 12 names them after the lockfile's snapshot key — the peer-resolved form, peers joined by _ — and past 60 characters stores first 27 chars + '_' + sha256(key)[:32]. The recipe was proven before use (19/19 hash directories matched, zero store directories uncovered) and matching is exact again, derived from the lockfile's own spelling rather than a heuristic.
    2. A nested YAML key hijacked the parser. snapshots: entries indent dependencies: maps; the section-key regex let four-space lines match, so the current entry flipped to a junk key and the optional: true that followed landed nowhere — @emnapi/* counted missing though its snapshot marks it optional. Regexes that count spaces now count them exactly.
    3. The package manager records itself. packageManager: pnpm@12.4.2 produces a lockfile entry for pnpm that never materializes in the project's virtual store. Same rule as platform binaries: a package not supposed to be here is not missing.
    4. pnpm 12 filters the recorded lockfile copy. node_modules/.pnpm/lock.yaml holds 645 of 660 entries — exactly the other-OS binaries dropped — so neither full nor machine-expected set equality holds. The check is now subset in both directions: every copied package must be in the lockfile (a foreign resolve adds packages), and every package this machine must have must be in the copy (a copy from another machine lacks them).
      After the fixes the wild verdict is ok: true — 489 package(s) present, matching pnpm-lock.yaml (152 platform-specific or optional skipped, 1 packageManager self-reference skipped) in 17 ms, with damage detection re-proven both ways (deleted vite@8.3.0 → flagged; pnpm install --frozen-lockfile → restored → ok: true).
  • A check stopped by its time bound stamped broken exit ?. Broken means the check ran and said no; a killed run never ran to a verdict. It is blocked — untested, not false, with a note naming the bound and suggesting max_ms. Found on the same wild repo, whose suite needs ~5 minutes against the 120 s default. (The bare exit null note was made legible as no exit within Ns earlier in the same pass.)
  • init discovered nothing on a pnpm-workspace.yaml monorepo. The earlier workspaces fix read package.json's workspaces key; vuejs/core declares its packages in pnpm's own workspace file, so the same zero-candidates breakdown recurred on the very ecosystem this drill targets. pnpm-workspace.yaml now counts as evidence that packages with code exist.
  • Sandbox teardown could crash a verify that already had its results. On Windows, an EBUSY while removing the temp worktree killed the run after the check but before state.save() — stamp lost, worktree leaked, observed live on the wild repo. Teardown is best-effort: retries briefly, then warns and moves on, so a verify's verdict survives a dirty teardown.
  • Three more package-manager builtins met their wild repos; each had its own false-positive class.
    1. npm on npm/cli: a lockfile lists every platform's variants (@typescript/typescript-aix-* …) but npm ci materializes only this machine's — 20 of 1181 "missing", every one foreign-platform. The platform rule pnpm and bun already had is npm's too now: ok: true — 1161 present, 20 platform skipped.
    2. bun on oven-sh/bun (isolated linker): bun 1.2 suffixes a store directory with +<hex> when the package resolves peers (@types+react-dom@18.3.7+52f32cb6c6aeed77), which exact name@version matching can never hit — a freshly installed healthy store read 3/23 missing. Matching folds the suffix off, raw name first so semver build metadata still matches only itself; damage/repair re-proven live (deleted typescript@6.0.2 → named; fresh install → ok: true).
    3. yarn 1 held up: healthy tree zero false positives, root-package damage reported rather than judged, --check-files repair restored ok: true.
  • init trusted node_modules existing — but npm/cli commits most of its node_modules to git (1367 files, no .bin), so a 122-package partial tree wired no prerequisite and the proposed suite claim ran against an install that was never made. When the tree exists, the ecosystem builtin gets the last word: a definitive ok: false wires requires, ok: null leaves discovery's judgment alone.
  • The git journal could silently lose an entry on a fast machine. Entry ids were second-resolution, so an accept and its immediate revert wrote the same filename — the revert overwrote the accept and the journal showed one entry where there were two. Windows passed by latency luck; the ubuntu CI runner caught it. Ids are collision-proof now.
  • A vendored Go build reported "no module cache". The gate demanded GOMODCACHE even when the tree is vendored — correct on a dev machine, wrong on a fresh ubuntu runner. A vendored build verifies without a cache.

Added

  • The repo's own manual runs in GitHub Actions on every push and PR (.github/workflows/manual.yml), with the badge in the README — the same verify a contributor gets from the pre-commit gate, executed where nobody's laptop is involved.
  • CONTRIBUTING.md documents regenerating the hero and social-preview images and keeping the two cards in sync.
  • init calibrates a suite's time bound instead of inventing one. A discovered command check no longer ships a hardcoded max_ms: 120000; the probe measures the first honest run and writes max(120000, measured × 3) rounded to seconds, with observation.measured_ms as provenance — a five-minute suite gets a bound that can hold it (vuejs/core's ~293 s → 879000) while fast suites keep today's default for manual observe to tighten later on accumulated evidence. A check that never finishes gets one 600 s calibration window, then an honest blocked. Along the way the probe adopted verify's own doctrine — a run stopped by a bound is blocked, untested not false (it used to file them broken) — and its note stopped printing max_ms as if it were seconds (120000s).
  • verify waits for a claim's own bound (checkTimeoutMs): the config default_timeout_s was passed as an explicit timeout, which the runner prefers over every claim's max_ms — calibrated bounds would have been decorative, a 15-minute suite still killed at 60 s. The config default now applies only to claims that declare no bound.
  • Regression tests for every fix above: the hash-truncated pnpm-12 directory (fixture uses the real pnpm-computed hash, not a reimplementation), the nested optional: true, the packageManager self-reference, the timeout-blocked stamp, and the EBUSY-tolerant teardown (platform-aware: a live process parked in the worktree on Windows, clean removal on POSIX).

See docs/FIELD-NOTES.md, "Eighth pass" (the pnpm-12 monorepo) and "Ninth pass" (npm, yarn 1, bun, and the bound that measures itself), for the full drill narratives.

v0.11.0 — yarn (classic + berry) and bun, measured before judged

Choose a tag to compare

@phantomghost2023 phantomghost2023 released this 22 Sep 04:13

Every Node package manager a repo can use

verify: { builtin: yarn } and verify: { builtin: bun } complete the Node set, so a cached install is checkable in 2–5 ms whichever manager wrote it — and each verifier was measured against the real manager before it was allowed a verdict.

  • yarn, both generations. Classic yarn 1 compares the pattern → resolved-URL map in node_modules/.yarn-integrity with yarn.lock and checks that every directory the install claims to have linked is on disk. Berry compares every location in node_modules/.yarn-state.yml with the locators yarn.lock resolves; a resolved package with no location here (optional, platform-specific) is reported, never judged missing. PnP stays unverifiable and says so.
  • bun, both linkers. Reads bun.lock (trailing commas and all); in a hoisted tree the lockfile key path under node_modules is the install, in an isolated one each resolved package has its store directory node_modules/.bun/<name>@<version> — scoped names spelled @scope+name, other-platform/optional entries skipped and counted.
  • The rule, enforced by the command itself. Classic yarn 1 trusts .yarn-integrity and nothing else: measured on a real 1.22.22 clone, deleting node_modules/is-odd left both the integrity file and yarn install --frozen-lockfile saying "Already up-to-date" (0.2 s) with the package still gone — while --check-files re-links it in the same 0.2 s. So a classic repo's discovered prerequisite is yarn install --frozen-lockfile --check-files, and a missing linked directory is a verdict only under a command that repairs it. Berry rejects that flag outright; bun's isolated linker is the third case of the same rule and reports like pnpm.

The damage table (each cell measured):

manager damage does its install repair it? verdict allowed
yarn 1.22.22 rm -rf node_modules/is-odd plain: no ("Already up-to-date") · --check-files/--force: yes (0.2 s) only under the repairing flag
yarn 4.9.0 a .yarn-state.yml location yes (155 ms) yes
bun 1.2 hoisted a top-level package yes (30–42 ms) yes
bun 1.2 isolated node_modules/.bun/<pkg>@<ver> no ("Checked 6 installs across 32 packages (no changes)") reported, not judged

Full details: CHANGELOG · field notes

v0.10.0 — Six ecosystem verifiers behind one word

Choose a tag to compare

@phantomghost2023 phantomghost2023 released this 22 Sep 04:13

Six ecosystems, one question

verify: { builtin: … } grows from npm-only to npm, pnpm, venv, gems, gomod, crates — plus auto, which picks from the setup's own evidence (or the one lockfile present) and says which it picked. Every builtin compares an installed tree against its own lockfile by reading directory listings, not by running the package manager's check.

builtin compares answers in
npm package-lock / npm-shrinkwrap (v1 + v3) vs node_modules ~64 ms on 403 packages
pnpm resolved set + pnpm's lockfile copy vs node_modules/.pnpm ms
venv requirements/poetry/Pipfile/uv vs dist-info dirs (both Windows and POSIX layouts) ms
gems Gemfile.lock specs vs vendor/bundle (BUNDLE_PATH honoured) ms
gomod go.mod vs $GOMODCACHE (Go's !-escaped layout) or vendor/modules.txt ms
crates Cargo.lock vs $CARGO_HOME/registry, vendor/ ms

The rule the real repos forced on us

"No" is only said when the declared command can repair it — measured, not assumed: npm ci, pip install -r, bundle install and go mod download all restore damaged trees. pnpm 11 does not (a satisfied install leaves a deleted node_modules/.pnpm/<pkg> and a hand-modified .pnpm/lock.yaml alone, even with --force), and Cargo.lock covers dev-deps a plain build never fetches. There the builtin reports instead of judging, because a verdict it can never change is a rebuild loop forever:

⚙ setup: pnpm install --frozen-lockfile — reused, not re-confirmed: 1/2 package(s) the lockfile
  lists are not in node_modules/.pnpm, e.g. is-number@6.0.0 — not a verdict: a satisfied
  `pnpm install` re-imports only when the lockfile itself changes

Also fixed: a lockfileVersion: 1 npm lock used to declare nothing — which read as a yes for an empty tree — and init's virtualenv command was POSIX-only, so the first verify on Windows wrote a prerequisite that could not run.

Proven against real trees on one machine: npm 11, pnpm 11, a python -m venv, bundler 2.6.9, go 1.25, cargo 1.90 — each installed, damaged, and re-verified.

Full details: CHANGELOG · field notes

v0.9.0 — Prerequisites: declared once, verified before trusted

Choose a tag to compare

@phantomghost2023 phantomghost2023 released this 22 Sep 04:13

Prerequisites become a first-class object

A fresh clone is not a broken repository — but npm test without an install exits 127, and a claim used to read broken, blaming the repo for the checkout. v0.9.0 makes the install a prerequisite: declared once, deduplicated by (command, evidence content), and verified before it is trusted.

Highlights

  • Prerequisites can depend on each other. build: { run: make build, requires: [node], cache: ["dist"] } — a claim listing [build, node] in the wrong order still installs before it builds. Cycles are load errors; manual setup --plan prints the deduped, ordered sequence and exits 1 while anything is unsatisfied (a CI preflight).
  • A satisfied prerequisite is verified, not merely remembered. verify: re-checks a cached install: a command, or { builtin: lockfile }, which stats the tree against package-lock.json — 64 ms against 10.9 s for npm ls --depth=0 on a 403-package express tree. That 170× gap is why the cheap answer was never being run.
  • share: true lets another checkout borrow a verified install. The store records where an install was verified (not a copy), re-verified at the moment of borrowing. A second express clone went from 43.0 s (install for itself) to 16.3 s end-to-end.

The honest findings

  • Express ships .npmrc with package-lock=false, so "nothing to compare against" had been answered as "no" — reinstalling on every verify of a perfectly fine repo. A verifier now answers yes / no / cannot-tell, and the gap is reported where the person who can act on it is looking.
  • A normalized spec re-derived its builtin from a marker string, so the verifier ran as bash -c 'builtin:lockfile' — exit 127 — and every verify distrusted its cache. Found on a real repo, not by the suite.

Verified on: a fresh expressjs/express clone (403 packages, 1261 tests) — damaged trees caught and repaired, borrowing proven across clones.

Full details: CHANGELOG · field notes