Skip to content

chore: version packages - #74

Open
github-actions[bot] wants to merge 1 commit into
mainfrom
changeset-release/main
Open

chore: version packages#74
github-actions[bot] wants to merge 1 commit into
mainfrom
changeset-release/main

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@taskless/cli@0.11.0

Minor Changes

  • f7ee186: Partition .taskless/ by rule engine. Migration 0004 moves ast-grep rules to sg/rules/ and sg/rule-tests/, the runtime tree to runtime/rules/ and runtime/rule-tests/, and scaffolds an inert vale/. Files move byte-for-byte, so runtime rule signatures survive.

    The directory a rule sits in now is its engine: dispatch reads the path and never parses a rule file to decide who owns it. check runs ast-grep against the committed .taskless/sg/sgconfig.yml instead of generating an ephemeral config each run.

    A rule engine the CLI does not recognize is now rejected with a message instead of failing silently: an unsupported engine from the server previously exited 0 with no output, which read as success.

    Runtime rules are discovered under runtime/rules/ rather than the pre-migration runtime-rules/. Migration 0004 moves that tree byte-for-byte, so the signatures the server validates are unchanged.

    check and rule verify read the committed .taskless/sg/sgconfig.yml rather than writing an ephemeral config on every run, so the config ast-grep uses is the one you can edit and review. A pre-migration rule set still gets a generated config, so an unmigrated project keeps running.

    Existing projects keep working without action. The pre-0004 .taskless/rules/ still runs as ast-grep, and a delivered rule that names no engine is still treated as ast-grep — a rule engine this CLI does not recognize is rejected rather than guessed at. A migration that would have to merge a file into an engine directory now refuses up front with SCAFFOLD_CONFLICT rather than failing part-way.

  • d4fca88: Add a @taskless/cli/prompts subpath export exposing the CLI's knowledge prompts as importable, topic-keyed render functions.

    getPrompt(topic, options?) and the PROMPTS map return fully rendered recipe text, with every %(KEY)s placeholder already resolved from values the package holds, so a consumer never handles a template dialect. Topic names are typed as PromptTopic and start at static, the one recipe a service-side consumer can act on; everything else stays internal until a consumer needs it. PromptOptions covers the anonymous variant, a packageManagerDlx override, and header: false for callers placing the text in an LLM system prompt, where the CLI version in the header would otherwise churn the prompt-cache key on every publish.

    The export is sourced from the same embedded recipes and the same render path taskless help <topic> serves, so the two surfaces cannot drift, and it carries no CLI runtime, so a Worker can import it without pulling in the command tree.

  • 8c91857: Name the CLI by its full invocation everywhere an agent is told to run it.

    Agent recipes said taskless agent route — a binary almost nobody has on PATH — in 114 places, npx @taskless/cli … in 40 more, and only the second form was rewritten for non-prod builds. A nightly's recipes therefore sent readers to the released package. All of it now renders through one new sprintf variable, %(TASKLESS_CLI)s, which resolves to a caller-supplied invocation, else the build's own invocation when that build is not prod, else the agent-fill marker <taskless-cli>.

    @taskless/cli/prompts gains getInstructions(topic, options?) and getRawInstructions(topic, options?), both returning { text, variables }. The raw form hands back the unrendered template and the list of variables it contains, so a host that knows its own launcher can render the text itself; variables comes from sprintf-js's own parse rather than a regex over the template. PromptOptions.invocation is the only way a consumer sets TASKLESS_CLI — the render path stays free of process so it remains importable from a Worker.

    Fixes launcher detection in user-facing error messages. getCliPrefix() read only npm_config_user_agent, which every pnpm entry point sets, so running the CLI from a package.json script told the user to run pnpm dlx @taskless/cli@latest. Detection now reads the path the binary was launched from, recognizes npx and pnpm dlx only, and answers "unknown" for everything else. The package specifier comes from the build target, so a nightly's error messages name @taskless/cli-nightly at its own version.

  • 6b07695: Ship Vale as per-platform binary packages.

    The CLI now declares @taskless/vale-<os>-<cpu> as optionalDependencies pinned
    to an exact version, so installing it also brings down a verified Vale binary for
    the host platform — no lifecycle script, and nothing to download at runtime. Only
    the matching platform installs; unsupported hosts install cleanly with none
    present and continue to fall back to a vale found on PATH.

  • 0e03ee9: Add Vale as a second static-tier rule engine, give every engine one rule layout, and rename the agent-facing command.

    check now dispatches by engine and runs ast-grep, Vale, and runtime rules
    concurrently, merging their findings into one result set. An unavailable Vale
    reports itself and the other engines still return. A Vale that times out or
    rejects its config fails the check rather than passing as a clean run.

    Every rule is now one directory, .taskless/rules/<engine>/<id>/, holding
    the rule, any per-engine config, and its tests in .tests/. Writing a rule
    means creating a directory and deleting one means rm -rf. Nothing outside it
    is touched either way, so concurrent authors never collide on a shared file.

    Vale rules carry their own .vale.ini declaring which files they apply to.
    The single config Vale reads is assembled from those per-rule files on each
    run, gitignored, and regenerated, so hand edits to it have no effect. ast-grep
    keeps its files/ignores inside the rule and needs no second file.

    rule verify is replaced by two path-addressed commands. verify <path>
    checks that a rule has the components its engine requires and needs no tests,
    so it works while you're still authoring. test <path> runs the rule's tests,
    after running verify and stopping if that fails. Both take a rule directory,
    an engine directory, or nothing at all for the whole project, and both report
    one result per rule. Addressing by path rather than id removes the ambiguity
    that arose when two engines held the same rule id.

    Projects on an older layout migrate automatically on the next command.

    BREAKING: taskless help <topic> is now taskless agent <topic>. The
    command is named for who reads it. Agents fetching a procedure are not asking
    for help, and the old name is gone rather than aliased.

    BREAKING: topics are addressed by a single token. taskless help rule create becomes taskless agent create-sg-rule; multiple positionals are no
    longer joined into a topic key. A topic name is now a literal string an agent
    copies rather than a phrase it can reorder. The renames:

    Was Now
    rule create create-sg-rule / create-remote-rule
    rule improve improve-rule
    rule delete delete-rule
    rule verify verify-rule
    rule meta rule-meta
    static create-sg-rule
    existing create-legacy-rule
    engine-selection route

    route now applies the engine reasoning itself and names a concrete
    create-*-rule topic, so engine-selection is removed rather than renamed —
    its criterion is stated once, in route. Every authoring recipe is rewritten
    for the rule-directory layout.

    BREAKING for @taskless/cli/prompts consumers. engine-selection is no
    longer exported. TOPICS is now create-sg-rule, create-vale-rule, and
    create-runtime-rule, so a consumer that decides an engine can reach the
    procedure for each destination. Because the export is a string union, a
    consumer passing the removed name dynamically breaks on upgrade rather than at
    build time.

Patch Changes

  • 87abaf3: Fix the pass/fail counts reported when a rule's ast-grep tests fail.

    ast-grep test echoes the source of a failing test case, and verify scraped
    its counts with unanchored regexes over stdout and stderr combined — so a
    fixture containing text like '7 passed; 0 failed' was read as the summary and
    verify reported ✗ failed (7 passed, 0 failed) for a run that actually had 0
    passed and 1 failed. The counts are now read from the summary line itself
    (test result: ok. / Error: test failed.), with ANSI colors stripped first.

    This only affected the reported numbers, never the pass/fail verdict, which
    comes from the exit code — but those numbers are handed to the agent driving
    improve-rule, where a wrong count can steer the next edit. Test output is also
    now decoded with a StringDecoder per stream, so a multi-byte character split
    across a chunk boundary is no longer mangled.

  • 1fb9dda: Rewrite the CLI README around what you actually do with Taskless: installing it,
    driving it from your coding agent with the taskless skill and /tskl command,
    running taskless check in CI, and where to find the docs. Telemetry — and the
    two environment variables that turn it off — is now stated plainly instead of
    being left to the source.

  • f13d501: Stop corrupting non-ASCII characters in ast-grep's error output.

    runAstGrepScan and the runtime narrow both decoded ast-grep's stderr one
    chunk at a time with chunk.toString(). A multi-byte UTF-8 sequence split
    across a chunk boundary was decoded as two invalid sequences, and both halves
    became replacement characters before the pieces were joined — the original
    bytes unrecoverable by then. Each stream now uses a single StringDecoder,
    flushed on close, matching what the Vale runner and verify already do.

    The corrupted text only ever reached an error message, so no scan result was
    ever wrong. But that message is the one a user reads when ast-grep rejects a
    rule file, naming a rule id or a path — which is exactly where a non-ASCII
    character turns up.

  • 71f4394: Tell the routing recipe what the local engines can actually read.

    route chose between sg, vale, and the runtime tier on the shape of the
    evidence alone, and had nothing to say about language reach — so a rule over a
    GitHub Actions workflow was escalated to create-runtime-rule, which needs a
    login, because nothing stated that ast-grep parses YAML. It does. Nothing in
    the repository could have said so either: the vendored ast-grep schema types
    language as a bare string with no enum, verify never checks the field, and
    detect --json reports the repo's own languages in a different vocabulary.
    Vale self-reports nothing at all.

    route now states both engines' reach, and create-vale-rule repeats Vale's
    where a matcher is written. Both read the lists from constants pinned to the
    engine versions this CLI ships, rather than from prose typed into the recipe —
    an engine bump that changes what a binary parses now fails a vendor-contract
    test instead of leaving a confident, wrong sentence in front of an agent.
    Vale's reach was measured by probing the shipped binary, tier by tier, since it
    publishes no capability listing.

    The Vale half carries a hazard worth naming on its own. Vale supports
    reStructuredText, AsciiDoc, XML, DITA, and MDX by shelling out to an external
    converter, and this CLI ships none of them — so one such file caught by a
    rule's glob exits 2 with an E100 and abandons the whole run, silencing every
    other Vale rule over every other file. create-vale-rule had been offering
    [*.{md,mdx}] as its example of widening a matcher.

    .xml is the one entry where naming the converter is not enough. It needs
    xsltproc and an XSLT stylesheet, and a stylesheet is document-specific, so
    there is nothing to ship and installing the program does not make .xml
    lintable — unlike asciidoctor, which genuinely fixes .adoc. Vale says so
    differently depending on the host, too: xsltproc not found where the program is
    absent, no XSLT transform provided where it is present, and macOS ships
    /usr/bin/xsltproc while a typical Linux CI image does not. The contract test
    now asserts Vale's checker tag, which is the same everywhere, rather than a
    substring of the converter name.

  • 87392fa: Make --help work on every command, instead of running the command.

    taskless check --help printed no usage — it ran check. So did every other
    subcommand: --help was parsed as an unrecognized flag and the command body
    executed anyway, which meant asking init how it works installed skills, and
    asking check how it works migrated the .taskless/ scaffold. The only place
    help worked was the bare taskless --help, whose own output tells you to run
    taskless <command> --help.

    --help and -h are now recognized at every depth, including nested commands
    (taskless auth login --help describes login, not auth), and a working
    directory passed before the command (taskless -d ./repo check --help) no
    longer confuses which command you asked about. The usage text itself is
    unchanged, and nothing else about how commands run has changed.

  • 32da4f9: Stop the engine-partition migration from relocating a rules tree that is already partitioned.

    A .taskless/ with no taskless.json — a manifest that was never committed, or was deleted — reads as version 0, so every migration runs against it. Migration 0004 then applied its rules/sg/rules/ move to a tree already in the current layout, burying every rule at .taskless/sg/rules/sg/<id>/; 0005 scaffolded fresh empty engine directories over the gap. Nothing errored. check scanned a tree with no rules in it and exited 0 on a clean report, so a project that had silently stopped being checked was indistinguishable from one that passes.

    0004 now reads the shape of .taskless/rules/ before moving it. A tree holding engine directories and no loose rule files is newer than the migration, not older, so it is left alone. A genuinely pre-0004 tree of flat rules/<id>.yml files still moves wholesale, as before. And a tree holding both — an already-partitioned layout with a stray rules/<id>.yml beside it, as a merge-conflict leftover produces — migrates only the stray files: moving the directory to collect them would carry the partitioned rules down with it, and 0005 never brings them back, which is the same silent clean pass by another route.

  • 0cc713e: Split the release pipeline so each workflow file carries one release design.

    release.yml held two jobs with opposite trust properties behind one header.
    It is now release-cli-changeset.yml — which reads contributor-authored
    changesets and opens the Version Packages PR holding no npm credential and no
    OIDC identity — and release-cli.yml, which keeps the credential-free
    "is this version already on npm?" gate together with the publish job it
    protects, so an OIDC-capable job is never instantiated on an ordinary merge.
    vale-binaries.yml is renamed release-vale.yml to match.

    The build and publish steps themselves are unchanged — same triggers, same
    permissions: {}, same action pins, same OIDC trusted publishing behind the
    same npm-production approval. Two operational details do differ: check and
    publish no longer share the release-* concurrency group, and the release
    now runs as two workflow runs instead of one, so its check contexts are
    Release CLI Version PR / … and Release CLI / … rather than Release / ….
    Neither is a required check.

    The header comments also get one correction: they claimed npm-production had
    no required reviewers, and it has had one all along, so a release has always
    waited for a human approval that the file said was not there.

    Publish unreleased work on main as @taskless/cli-nightly.

    Every push to main that has changesets pending now publishes the CLI under a
    second package name, stamped <next-version>-<yyyymmddhhmmss>x<short-sha> — so
    merged-but-unreleased behavior is installable with npx @taskless/cli-nightly.
    A nightly is the same build as the release it anticipates and keeps the
    taskless executable, so it is a drop-in; the rename happens at pack time, so
    @taskless/cli's own version history stays releases-only. Installing both
    globally collides on the binary and is unsupported.

    Two credential-free gates decide whether anything is built — pending changesets
    first (before any install), then whether the commit already has a nightly — so
    the publishing job is never instantiated on an ordinary push, and the merge of a
    Version Packages PR publishes the real release and no nightly with no rule
    special-casing it.

    A nightly now ships instructions for itself. The skills, commands, and recipes
    a nightly installs name npx @taskless/cli-nightly@<version> — pinned to the
    build being installed — instead of npx @taskless/cli. Previously a nightly
    carried the released CLI's text verbatim, so an agent following it ran the
    released binary: no error, just instructions for a different package, on a
    build installed precisely to exercise unreleased behavior. The version is
    stamped once and passed to both the build and the pack, so the version the
    instructions name is always the version on npm, and a nightly build without a
    valid version fails rather than falling back.

    The nightly's duplicate-suppression gate also now fails closed. An unreadable
    registry response used to read as "this commit has no nightly", and since each
    build stamps a fresh timestamp, a re-run after one would have published a
    second nightly for the same commit successfully and silently.

  • 226061d: A nightly now reports the version it is, not the release it anticipates.

    Installing a nightly wrote the previous release into .taskless/taskless.json
    install.cliVersion: "0.10.2" — while the skills written beside it, by the
    same command in the same run, pinned every invocation to
    @taskless/cli-nightly@0.11.0-…. The manifest attributed the install to a
    version that never performed it, which matters because install.cliVersion is
    what answers "what installed this?", and that question gets asked precisely
    when someone is running a nightly to reproduce unreleased behavior.

    A nightly's version is stamped when the publishable artifact is produced, and
    the committed package.json is deliberately left untouched — so the build was
    reading a file that could not know the answer. It now takes the same stamp that
    names the published package, so the version a nightly reports and the version
    it sends an agent to are the same string by construction.

    This also corrects taskless --version, the CLI version in recipe headers, and
    the cliVersion telemetry property on nightly builds. Released builds are
    unaffected. A nightly that cannot determine its own version now fails the build
    rather than quietly reporting the released one.

    The build now also refuses to emit a nightly whose reported version and
    embedded invocation disagree. Both derive from the same stamp, so they cannot
    diverge today — but that was true of the two values in this bug as well, right
    up until one of them started reading package.json instead. Deriving from one
    source is not the same as being checked against it.

  • c4a252b: Onboarding now reads the routing surface before it proposes rule candidates.

    The onboard recipe asked the agent to synthesize its bullet list of
    hypothetical rules first and consult route only afterwards, once per accepted
    bullet. So the list a user picked from was written without knowing what kind of
    rule anything would be, or what the repository already lints — and a candidate
    with nowhere to go looked exactly like a good one until the user had already
    chosen it.

    The recipe now fetches taskless agent route and runs taskless detect --json
    before proposing anything, and each bullet carries the destination it would
    route to: - no-direct-db-access [sg]: …. The annotation is provisional —
    route still decides for real at materialization time, when it has the rule's
    full description — but an unroutable candidate is now visible while it is still
    cheap to drop.

    The destination criterion itself has not moved. It is still defined once, in
    route; onboarding reads it rather than carrying a copy that would drift.

  • a7ec7a1: Complete the helpagent rename. The user-facing command was renamed in
    0.10.0, but the internals kept the old name: the recipe directory moved from
    packages/cli/src/help/ to packages/cli/src/agent/, the cli-help OpenSpec
    capability is now cli-agent, and the shipped skill and /tskl command no
    longer tell agents to run the removed npx @taskless/cli help <topic> (they
    now use agent, with the single-token topic names — route, improve-rule,
    delete-rule, create-sg-rule, and siblings).

    Telemetry rename (hard cut, no dual-emit). The cli_help event is renamed
    to cli_agent. The topic property is unchanged. PostHog dashboards keyed on
    cli_help will need updating — nothing is emitted under the old name.

  • afb4831: Let ast-grep rules see inside hidden directories such as .github/.

    ast-grep's file walker skips dot-directories unless told otherwise, and
    runAstGrepScan never told it otherwise. No sg rule could match anything
    under .github/, .circleci/, .vscode/ or .husky/, so check reported
    nothing and exited 0 on a workflow file it flags correctly the moment the same
    bytes live in a non-hidden directory. Vale has no such blind spot, which left
    the two static engines disagreeing about whether .github/ existed at all.
    Both check and the runtime engine's ast-grep narrow now pass
    --no-ignore hidden.

    Only hidden is passed, and deliberately not vcs: .gitignore is still
    respected, so the wider walk does not start reporting findings in dist/ or
    anywhere else a project has already said it does not want scanned. Rule
    discovery is untouched — ruleDirs walks by its own rules, so a rule's
    .tests/ directory is still skipped rather than parsed as a rule.

    .taskless/ is excluded from the wider walk, because it is hidden too and
    reaching it is not a fix. A rule definition is structured YAML full of id:,
    language:, severity: and rule: keys, so an ordinary user-written Yaml rule
    fires on the CLI's own rule files — a finding in a directory the user did not
    author and cannot edit without disabling their rule. The exclusion applies only
    when check walks the whole project on its own; an explicit path stays a
    request, which is the rule the Vale runner already follows.

    .git/ is excluded on the same terms. ast-grep has no exclusion of its own for
    it and .gitignore does not list it, so the default hidden-directory skip was
    the only thing holding it back: without this, a whole-project check descended
    into .git/objects and .git/logs on every run, and .git/hooks/* scripts
    matched language rules never meant to lint VCS internals.

    Both engines now decide "whole project" the same way, and it is no longer
    paths.length === 0. An explicit . is normalized to the literal path "."
    before it reaches either runner, so a length test read the most ordinary way of
    asking for a whole-project check as a user-named path and skipped the exclusions
    check was clean while check . reported findings inside .taskless/. Vale
    was already wrong in the same way and for the same reason, independently of the
    hidden-directory change, so the predicate is now shared rather than written
    twice.

  • 73cdc45: Fail test for an ast-grep rule that never demonstrates it can fire.

    verify checked that a rule's -test.yml existed and never read what was in
    it, and ast-grep test reports an empty invalid: bucket as 1 passed; 0 failed and exits zero. A rule whose fixtures were all valid: therefore
    reported ok: true, ran: true while check found nothing anywhere — verified
    looking verified, having proved nothing. test now counts the valid: and
    invalid: entries across every test file a rule owns and requires both, which
    is the rule Vale fixtures have always been held to.

    This rejects rules that passed before. Any sg rule with an empty or absent
    invalid: bucket now fails test until a fixture is added that the rule
    actually matches. That is the intended effect: adding one is how the underlying
    mistake surfaces.

    The mistake that prompted this is worth knowing about, because the pattern
    looks correct. A trailing $$$ next to a comma does not mean "zero or more" —
    the comma is itself an AST node, and under ast-grep's default smart
    strictness every node in the pattern must match, so fetch($URL, $$$REST)
    never matches fetch(url) and silently starts at two arguments. A leading
    $$$ is worse: foo($$$, $A) collapses to exactly one argument. Upstream
    considers this intended and 0.45.2 behaves identically, so there is no version
    to upgrade to; write the pattern as an object with strictness: ast to ignore
    the separator, or use any: with one branch per arity. verify --schema now
    carries a worked example, and the behaviour is pinned against the vendored
    binary so a bump that changes it fails loudly.

    create-sg-rule states all of this where a pattern is written: the arity table
    measured against the pinned binary, both remedies and the fact that
    strictness: ast moves a trailing $$$ from two arguments to one rather than
    to zero, and the fixture requirement with a case on each side of an arity
    boundary. It also names ast-grep's language: vocabulary from the same pinned
    constants — nothing local validates that field, an unrecognized spelling takes
    the whole scan down, and Tsx is a different parser from TypeScript rather
    than an alias. improve-rule gains the two notes that matter when a rule is
    rewritten rather than written: read the pattern for a comma-adjacent $$$
    before reporting it as too narrow, and re-check both fixture buckets after the
    service returns a narrowed rule.

  • 9e87aa6: Stop a rule with no tests from failing every other rule's ast-grep test run.

    Migration 0005 created a rule's .tests/ only as a side effect of moving a test file into it, so an ast-grep rule that had no test at version 3 — or one whose test file did not match the <id>-YYYYMMDD-test.yml shape the migration can attribute to a rule — arrived in the new layout with no tests directory at all. Assembly then named that directory as a testConfigs entry anyway, and ast-grep 0.41.0 treats a testDir it cannot read as fatal to the whole invocation rather than to the one rule: taskless test on any rule died with Cannot read rule directory .taskless/rules/sg/<other-id>/.tests and exit 6, naming a rule the author had never touched. --filter does not scope that away, so there was no way to run one rule's tests around it.

    0005 now gives every rules/sg/<id>/ a .tests/, holding a committed .gitkeep when it would otherwise be empty — git does not track empty directories, so without one the repair would not survive a commit and the failure would come back in CI. Assembly separately omits any testDir that is not on disk, which is what rescues a project a nightly already stamped at version 5: migrations short-circuit once the manifest is at the latest version, so those installs never re-run the amended 0005, and the same state is reachable at any version by creating a rule directory by hand. Neither change turns a missing test into a pass — verify still reports "No test file found" and test still reports "Skipped: no test file found", both reading the rule directory rather than the generated config.

  • 4960987: Stop one AsciiDoc file from disabling every Vale rule in the project.

    Vale supports AsciiDoc, reStructuredText, XML/DITA and MDX, but it parses none
    of them by itself — it shells out to asciidoctor, rst2html, dita or
    mdx2vast, and the @taskless/vale-* packages ship the binary with none of
    those alongside it. On a host without the converter Vale does not skip the file:
    it prints one E100 [lintAdoc] Runtime error on stderr, writes nothing at all to
    stdout, and exits 2. The abort is Vale's own and it is not scoped to the file
    that caused it, so every finding from every other file in the run was destroyed
    before it was ever serialized. Measured against the example project, adding a
    single .adoc took a check that reported five Vale findings across four files
    down to zero — reported as a raw JSON blob among the results, and exiting 1 the
    same way any ordinary failing check does.

    runVale now excludes the converter-dependent extensions from Vale's own walk,
    so the rest of the project is checked normally and the skipped files are named
    in a notice that says which converter would put them back in scope. The tiers
    live in one table in rules/capabilities.ts — the same record the agent recipes
    render their format lists from — measured against the pinned binary rather than
    transcribed from documentation. That is how .asc and .rest, a third AsciiDoc
    spelling and a second reStructuredText one that crash identically and were in
    neither bug report, ended up covered. Measurement also corrected four
    extensions that a documentation reading had put in the wrong tier: .tex,
    .rmd, .mkd and .mkdn are all read as plain text by this Vale, not parsed,
    so excluding them would have dropped files Vale lints perfectly well. A
    per-extension test re-measures every row against the real Vale — each tier by
    the property only that tier has, since ordinary prose fires in all of them — so
    a version bump that moves a format between tiers fails there instead of silently
    turning the engine off again.

    The engine moves to Vale 3.18.0 in the same release, and the table carries a
    standing instruction to re-measure every row on a version bump — so every row
    was re-probed against the new binary rather than carried over. Eight moved.
    .mdx gains a native parser and leaves the unsupported tier, so a matcher like
    [*.{md,mdx}] — the worked example above — is legitimate again, and [*.{md,typ}]
    takes its place as the broken one. .typ moved the opposite way: Typst now
    parses through typst2vast, an external program this build does not ship, so a
    Typst file is excluded from the run rather than read as prose the way 3.17.1
    read it. .rmd and the new .qmd and .myst are parsed as markup, and .qml,
    .scss and the new .qdoc are comment-aware where they previously fell through
    to plain text.

    The .typ move is the one that mattered to get right. An extension missing from
    the table is read as prose, which is harmless — but the moment Vale routes it to
    a converter, that same omission is a crash that takes down every Vale rule in
    the run. Bumping the binary without re-measuring would have introduced exactly
    the failure this table exists to prevent, under an extension nobody was
    watching. Re-probing also caught one change the release notes do not mention:
    PHP comment extraction now requires a real <?php tag, where 3.17.1 linted a
    bare // comment without one.

    Two details are load-bearing and were both wrong on the first attempt. Vale
    honours exactly one --glob and keeps the last, so the .taskless/ exclusion
    and the format exclusions have to travel as one negated alternation or the first
    is silently discarded. And Vale matches a --glob against the basename only
    when the pattern contains no / — combined with .taskless/** the whole
    expression goes path-wise, at which point a bare *.adoc stops matching
    docs/guide.adoc and the crash survives one directory down from wherever it was
    tested. Vale's error output is also decoded now rather than forwarded verbatim,
    so a failure reads as a sentence naming the missing program instead of a
    five-field JSON object.

    These formats are now stated as unsupported rather than as needing a tool.
    The notice used to end "Install it and put it on your PATH to have these files
    checked", which offered a path this build does not ship, does not test, and for
    .xml cannot deliver — an XSLT stylesheet is specific to the document, so no
    install makes it lintable. It also made behaviour host-dependent: macOS ships
    /usr/bin/xsltproc and typical Linux CI images do not, so the same repository
    checked differently depending on the machine. The exclusion is unconditional
    for that reason, and the programs are still named as the reason rather than as
    a remedy.

    The comment tier was reconciled against Vale's own documentation at
    docs.vale.sh/formats/code, which adds .bsh, .csx, .pod, .py3 and .sbt
    once measured. It also documents .pyi, .qml and .scss as comment-aware,
    and on 3.17.1 a bare non-comment line in each of them lints. 3.18.0 makes the
    claim true for .qml and .scss and still not for .pyi, which stays in the
    plaintext tier. That divergence is the argument for probing rather than
    transcribing: the docs describe whatever Vale is current, and copying the list
    would have shipped .pyi as comment-aware and been wrong for both builds. .pod is a reminder of how easily this is misread — it lints Perl
    comments but not POD blocks, so probing it with =head1 looks like no support
    at all.

  • db8adfa: Resolve the ast-grep binary without relying on an install-time step, and drop
    the @ast-grep/cli wrapper from what consumers install.

    • The wrapper moves to devDependencies. The seven @ast-grep/cli-<platform>
      packages were already declared in optionalDependencies, and the CLI already
      resolved them by path — the wrapper was a leftover whose only job is a
      postinstall that hardlinks the binary into itself so its bin entries work.
      Nothing here invoked those entries. Consumers now install only the platform
      package matching their host, and the wrapper's postinstall — which leaves a
      placeholder text file where the binary should be under pnpm dlx's strict
      isolation — is out of the shipped product entirely. It stays as a
      devDependency because fetch-ast-grep-schema reads its version.
    • Platform packages are pinned exactly at 0.41.0. They were carets, and the
      wrapper had been enforcing alignment implicitly by pinning its own
      optionalDependencies; without it, two hosts could resolve different ast-grep
      versions against the same rules and disagree about findings. Held at 0.41.0
      rather than taking upstream's 0.45.0, so this change stays structural.
    • Binary resolution exhausts every candidate before failing. It now searches
      the platform package, node_modules/.bin, then sg and ast-grep on PATH,
      and throws naming what it tried. Previously it returned a bare "sg" and let
      spawn's ENOENT be the error, from a caller that could not say where it had
      looked.

    Alpine improves as a side effect: upstream publishes no musl build and marks its
    Linux packages libc: ["glibc"], so today the wrapper's postinstall resolves a
    package that does not exist and exits 1, failing the install wherever dependency
    scripts run. Installing now succeeds and resolution falls through to PATH.

Build Info

npx @taskless/cli-nightly@0.11.0-20260824221400x9cd9d99

Built from: 9cd9d99
Built at: 2026-08-24 22:14:00

@github-actions
github-actions Bot force-pushed the changeset-release/main branch 7 times, most recently from 6083aac to 28c11bf Compare August 4, 2026 15:08
@github-actions
github-actions Bot force-pushed the changeset-release/main branch 15 times, most recently from 8d07d51 to 850c944 Compare August 12, 2026 02:45
@github-actions
github-actions Bot force-pushed the changeset-release/main branch 8 times, most recently from b263ba8 to 60a7925 Compare August 20, 2026 22:57
@github-actions
github-actions Bot force-pushed the changeset-release/main branch 5 times, most recently from 6941b6e to fd18682 Compare August 21, 2026 02:52
@github-actions
github-actions Bot force-pushed the changeset-release/main branch 5 times, most recently from ef2479b to 0a7bbe9 Compare August 21, 2026 17:22
@github-actions
github-actions Bot force-pushed the changeset-release/main branch 16 times, most recently from d400ea0 to 90d6cd4 Compare August 24, 2026 21:37
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from 90d6cd4 to e788c93 Compare August 24, 2026 22:12
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.

0 participants