Skip to content

chore(ci): make vendor upgrade PRs self-consistent so Validate is green by construction - #374

Merged
theCodeDrift merged 5 commits into
mainfrom
chore/self-consistent-vendor-upgrades
Sep 22, 2026
Merged

theCodeDrift merged 5 commits into
mainfrom
chore/self-consistent-vendor-upgrades

Conversation

@theCodeDrift

Copy link
Copy Markdown
Member

What

The bot pin-bump workflows (vale-upgrade.yml → vendor/vale/upgrade, ast-grep-upgrade.yml → vendor/ast-grep/upgrade) now produce a commit that is self-consistent under engine-version-consistency.test.ts, and run that test before vendor-pr.cjs pushes anything.

Why

VALE_VERSION / AST_GREP_VERSION in packages/cli/src/rules/capabilities.ts are maintained by hand, and test/engine-version-consistency.test.ts holds them to the pins in packages/cli/package.json. That check is deliberate and the constant is deliberately not derived from package.json (the test's docblock says why). The bot moved only the pins, so every bot PR failed Validate by construction.

Today's instance: #368 (red) with child #372, which had to carry the constant, the regenerated Vale vocabulary and a test literal along with its real work, and the stack could not use GitHub's landing because its base was red. Neither open PR is affected by this change; see the rollout note.

What the bot now does

Both engines

  • --write in sg-detect.cjs / vale-upgrade-detect.cjs also rewrites the export const <NAME> = "…"; line in capabilities.ts, via a new bumpVersionConstant in pin-bump.cjs. It anchors on the exact declaration, fails the run unless it is found exactly once, and leaves every other byte alone (a {@link VALE_VERSION} or a version in a docblock is not a candidate). Covered by node --test: constant rewritten, unrelated constant untouched, missing declaration throws, doubled declaration refused, idempotent.
  • After the lockfile is regenerated, pnpm install (registry-only, default GITHUB_TOKEN, no NODE_AUTH_TOKEN; under CI pnpm applies --frozen-lockfile itself and the just-regenerated lockfile satisfies it).
  • pnpm --filter @taskless/cli test --project cli engine-version-consistency before vendor-pr.cjs. It needs no build. A failure fails the run and proposes nothing.
  • The regenerated artifacts are added to the staged paths.

Vale: VALE_VERSION takes the base version (3.22.0, not the stamp). pnpm --filter @taskless/cli generate:vale-schema regenerates src/generated/vale-vocabulary.ts and vale-vocabulary-report.md from the new binary. Its generator refuses a binary that does not report VALE_VERSION, which is why the constant moves first, and vale-rule.ts typechecks VALE_VOCABULARY_VERSION against VALE_VERSION, which is why the vocabulary has to move at all.

ast-grep: pnpm --filter @taskless/cli generate:ast-grep-schema refetches src/generated/ast-grep-rule-schema.json from raw.githubusercontent.com by the new tag. The job already reaches GitHub for release notes, so this is the same network. The script used to stamp a wall-clock time into $comment, which would have made the artifact differ on every scheduled run and defeated vendor-pr.cjs's "unchanged, do not force-push" guard; the stamp is dropped (the version and URL are the provenance, git has the date). The committed schema is regenerated here and the comment is the only byte that changed.

What stays in the child PR

Judgment, not bookkeeping: the measured vendor contract (vale-vendor-contract, vale-schema-contract, ast-grep-vendor-contract), the update ledger, any migration, and the changeset prose.

One deliberate deviation from the brief: the contract tests are not in the bot's gate, only engine-version-consistency is. They hold a recorded corpus of what the binary does, so a new engine that adds a language or changes a verdict fails them, correctly, and the fix is exactly the child's work. Gated in the bot job, that signal would become a scheduled run failing every day with no PR and no changelog to read; left on Validate, it is a red check on a PR that already carries upstream's notes. Measured: rewinding ast-grep to 0.45.2 through the exact workflow steps passed engine-version-consistency and failed four behaviours the contract recorded at 0.45.3. So "green by construction" means the bookkeeping is consistent; a red contract on a bot PR is upstream telling us something.

Also

  • test/reconcile-marker.test.ts asserted { sg: "0.45.3", vale: "3.21.0" } as literals. It now asserts against the constants; the test is about the marker carrying the engine versions, not their values. Other version literals under test/ are measured facts ("changed at 0.45.3", a "Vale 3.21.0" block) and stay.
  • Header comments of both workflows and the engine-version-consistency docblock describe the new division of labour. Validate is untouched and the consistency test is not weakened.

How verified

Locally, in a worktree:

  • pnpm test:scripts (453 pass), pnpm typecheck, pnpm lint (builds, then pnpm cli check: no issues), pnpm --filter @taskless/cli test (96 files, 1588 pass), prettier --check on every touched file, both workflows parsed with the yaml package. actionlint is not installed here.
  • Vale, end to end, against the real registry (3.22.0 is ahead of the pin today): node .github/scripts/vale-upgrade-detect.cjs --write moved six pins and VALE_VERSION 3.21.0 → 3.22.0; pnpm install --lockfile-only --ignore-scripts; CI=1 pnpm install; generate:vale-schema against the 3.22.0 binary (6s, diff is the version string only, prettier-clean); pnpm --filter @taskless/cli test --project cli engine-version-consistency vale-schema-contract 21/21 pass, with the binary-spawning assertion confirmed to run against the 3.22.0 package; pnpm --filter @taskless/cli typecheck clean. Then reverted and reinstalled.
  • ast-grep, rewound to 0.45.2 via bumpPins + bumpVersionConstant, then the same lockfile / install / generate:ast-grep-schema / test steps: engine-version-consistency passed, ast-grep-vendor-contract failed on four 0.45.3-measured behaviours (the case described above). Then reverted.
  • pnpm … test -- <filter> with a literal -- reaches vitest and runs the whole suite; the workflow steps omit it.

The workflows themselves run next on the next upstream release (Vale daily, ast-grep weekly, or workflow_dispatch).

Rollout

No effect on the open #368/#372: those branches were built by the old workflow and land as planned. The next bot PR is the first exercise. If vale-upgrade.yml is dispatched after this merges and before #368 lands, vendor-pr.cjs will rebuild vendor/vale/upgrade from main with the fuller commit; its foreign-commit guard still refuses to touch a branch with a human commit on it.

@theCodeDrift theCodeDrift added the skip-changeset PR intentionally ships no release note (bypasses the changeset requirement) label Sep 21, 2026
@theCodeDrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Claude finished @theCodeDrift's task in 8m 15s —— View job


Review complete

Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json contains no reviewThreads and no reviews, only the @claude /review trigger comment itself — this is the first review of this PR, so the full diff was assessed from scratch (nothing to classify as already-addressed).

  • Read .prior-review.json
  • Fetch PR diff and metadata (gh pr diff 374, gh pr view)
  • Review each changed file (all 12: pin-bump.cjs/test, sg-detect.cjs/test, vale-upgrade-detect.cjs/test, both workflow YAMLs, fetch-ast-grep-schema.ts, the generated schema, and both test files under packages/cli/test/)
  • Post findings

Overall assessment: this is a well-reasoned, well-tested change — bumpVersionConstant's anchor was verified against the actual capabilities.ts declarations (both match exactly), the new tests cover the missing/duplicated-declaration and untouched-neighbour cases thoroughly, and the workflow step ordering (bump → lockfile → real install → regenerate artifacts → consistency check → propose) is sound. I found one real correctness gap and two minor cleanups, posted inline:

  1. .github/scripts/sg-detect.cjs (and the same pattern in vale-upgrade-detect.cjs) — writeFileSync(packageJsonPath, bumped) now happens before the new bumpVersionConstant(...) call, which can throw (renamed/missing/duplicated constant). That leaves package.json bumped on disk while capabilities.ts is untouched, directly contradicting the file's own comment that "the file on disk is either fully bumped or untouched." Masked in CI because the ephemeral checkout is discarded on step failure, but real for a local --write run and worth fixing (validate/compute both writes before persisting either) or at least updating the stale comment.
  2. Both upgrade workflows — the new Install step runs a plain unfiltered pnpm install, pulling in every workspace package's dependencies when only @taskless/cli's closure is needed for the schema regen + consistency check. pnpm install --filter @taskless/cli... would be cheaper on every scheduled run.
  3. vale-upgrade-detect.cjs — minor: baseVersion(upstream) is now called a third time; could be computed once and reused.

No security issues, no test-adequacy gaps beyond what's noted above, and no CLAUDE.md convention violations found in the diff.

Comment thread .github/scripts/sg-detect.cjs Outdated
Comment thread .github/workflows/ast-grep-upgrade.yml
Comment thread .github/scripts/vale-upgrade-detect.cjs Outdated
@theCodeDrift
theCodeDrift force-pushed the chore/self-consistent-vendor-upgrades branch from 4a105f4 to 6ba8a27 Compare September 22, 2026 00:12
@theCodeDrift

Copy link
Copy Markdown
Member Author

Re: @claude[bot] — "Claude finished @theCodeDrift's task in 8m 15s — Review complete…"
#374 (comment)

Read. The medium item (filtered pnpm install) was measured and is a no-op in this workspace; replied and resolved on the thread. The three low items (partial write before the constant rewrite, baseVersion computed three times) are collected for the owner's call and left open. Branch rebased onto main at fd896f9, which brought in the Vale 3.22.0 upgrade; the reconcile-marker assertion now reads the constants, so it needed no literal.

— AI Coding Agent

@theCodeDrift
theCodeDrift force-pushed the chore/self-consistent-vendor-upgrades branch from 61817a0 to c992194 Compare September 22, 2026 00:27
…pgrades

sg-detect.cjs --write and vale-upgrade-detect.cjs --write now also rewrite
AST_GREP_VERSION / VALE_VERSION in packages/cli/src/rules/capabilities.ts,
via a new bumpVersionConstant in pin-bump.cjs that anchors on the exact
declaration line, fails unless it is found exactly once, and leaves every
other byte alone. Vale's constant takes the base version the binary
reports, not the stamped npm version.

engine-version-consistency.test.ts holds the constant to the pins, so a
bot commit that moved only the pins failed Validate by construction
(#368). The test stays; the bot commit becomes consistent.
…ance comment

ast-grep-upgrade.yml is about to refetch the schema on every scheduled
run and hand it to vendor-pr.cjs, which force-pushes the rolling branch
only when the proposed files differ. A wall-clock stamp in $comment made
the artifact differ on every run, so an unchanged upstream would still
rewrite the branch. The version and URL are the provenance; git has the
date. Regenerated: the only byte that changed is that comment.
…hing it

Both upgrade workflows now install after the pin bump, regenerate the
engine artifact from the new version (the Vale vocabulary from the new
binary; the ast-grep rule schema from upstream's tag), run
engine-version-consistency against the result, and only then hand the
files to vendor-pr.cjs. A failing check fails the run and proposes
nothing.

The contract tests (vale-schema-contract, vale-vendor-contract,
ast-grep-vendor-contract) are deliberately not part of that gate: they
record what the binary does, and a new engine that changes a behaviour
should surface as a red Validate on a pull request carrying upstream's
notes, not as a scheduled run failing with nothing to read. That edit,
the ledger, any migration and the changeset prose stay in the child.

Measured locally by driving the exact steps against Vale 3.22.0 (ahead
on npm today) and ast-grep 0.45.2 (rewound), see the workflow comments.
…not pin literals

reconcile-marker.test.ts pinned { sg: "0.45.3", vale: "3.21.0" } as
literals. The test is about the marker carrying the engine versions, not
their values, and engine-version-consistency already holds the constants
to the pins; the literal was one more thing a bot pin bump broke by
construction. The other version literals under test/ are measured facts
("changed at 0.45.3", a "Vale 3.21.0" block) and stay.

engine-version-consistency's docblock now says the bot bumps satisfy it
by construction and what the check is still for.
sg-detect.cjs and vale-upgrade-detect.cjs computed the package.json
rewrite, wrote it, and only then computed the capabilities.ts rewrite,
so a constant declaration that had moved left package.json bumped on
disk without its constant, which is exactly the half-done bump the
surrounding comment promised could not happen. Both rewrites are now
computed before either file is written; a refused run leaves a clean
tree. One test per script pins that.

vale-upgrade-detect.cjs also computes baseVersion(upstream) once, in
main(), instead of at three call sites.
@theCodeDrift
theCodeDrift force-pushed the chore/self-consistent-vendor-upgrades branch from c992194 to 46c7468 Compare September 22, 2026 00:29
@theCodeDrift
theCodeDrift merged commit 119d06d into main Sep 22, 2026
4 checks passed
@theCodeDrift
theCodeDrift deleted the chore/self-consistent-vendor-upgrades branch September 22, 2026 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changeset PR intentionally ships no release note (bypasses the changeset requirement)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant