Skip to content

Planned Taskless Rules - #168

Merged
thecodedrift merged 9 commits into
nightly/testfrom
taskless/planned-rules
Aug 26, 2026
Merged

Planned Taskless Rules#168
thecodedrift merged 9 commits into
nightly/testfrom
taskless/planned-rules

Conversation

@thecodedrift

@thecodedrift thecodedrift commented Aug 24, 2026

Copy link
Copy Markdown
Member

Stack (root → tip):

Rules for our own repository, mined from the places we already state our conventions in prose: CLAUDE.md, the two .conventions styleguides, and the PostHog notes. Nothing here is settled — the point of landing it is to have somewhere concrete to argue about what we actually want enforced.

Rule Engine Flags Today
docs-npx-cli vale pnpm dlx @taskless/cli / pnpm cli in any README 2 real violations
no-em-dashes vale em and en dashes 24 findings in READMEs
no-blocklist-phrases vale the house blocklist (reflexive agreement, performative candor, load-bearing) clean
no-hedging vale "simply", "obviously", "of course", "trivially" clean
no-index-imports sg relative imports ending in /index clean
no-pii-in-telemetry sg email/displayName/etc. in capture(), identify(), groupIdentify() clean
no-regex-over-build-output sg a test function that reads dist/ and then regexes it clean
pr-workflow-no-branches-filter sg a branches: filter under a pull_request: trigger clean
pr-workflow-ready-for-review sg a pull_request: types: list that omits ready_for_review 1 finding, see below

The two docs-npx-cli findings — README.md:37 and packages/cli/README.md:17 — are left unfixed on purpose, so the PR demonstrates a rule catching something rather than only asserting it would.

The two Vale house-style rules come from the dogfooding issue (#104 / OSS-28), scoped to **/README.md. Widening them is #169, because repo-wide they are roughly 2300 findings. Two corrections to that issue's spec, both measured and both in the rule files: no-em-dashes needs nonword: true or it silently never fires, and the proposed landed on token fires on "the plane landed on time" and was dropped.

The 24 em-dash findings in READMEs are unfixed. Rewriting them is an editorial pass on user-facing prose, so it wants a decision rather than a default: rewrite the prose, or drop the rule to warning until someone does.

The two pr-workflow-* rules come from OSS-49, which names both and records that an earlier onboarding pass stalled on whether ast-grep handles YAML. It does — Yaml is one of its 26 languages — so both build locally instead of needing a login.

pr-workflow-ready-for-review reports one finding, and it is a question rather than a defect: stack-breadcrumb.yml names types: [opened, reopened, edited, closed] with no ready_for_review. It reacts to PR metadata rather than to readiness, so omitting it may well be correct. Settling that is part of what this PR is for.

Where the rules are narrower than the conventions

Three are worth arguing about specifically, because each is a deliberate retreat from what the styleguide actually says:

  • no-index-imports is not "no barrel imports." Telling from "../wizard" (a directory barrel) from from "./ask" (a file) needs filesystem resolution, which is a cross-file question and therefore runtime-tier. The rule catches only the explicit /index form, which means packages/cli/src/commands/init.ts:16 is a real violation that nothing in this PR flags.
  • no-regex-over-build-output scopes to the enclosing function, not the file. Scoped to the file it fired three times on packages/cli/test/prompts.test.ts, where a helper deliberately regexes hand-written source and the same file separately loads a built artifact. That helper is correct and the styleguide says so.
  • prefer-do-rpc was proposed here and has since been removed. It had no instances in this repository, and the CLI has no Cloudflare code for it to reach, so it belongs with the Taskless services rather than being carried pre-emptively. The rule and its synthetic fixtures are gone as of the review pass.

Two conventions could not be expressed at all while logged out, both for the same reason — they need the cross-file graph: "a type reachable from an exported signature must itself be exported" (the declaration: true trap) and "a hand-written type must not duplicate a library type."

Where to start

The two Vale .vale.ini files. Scoping is the decision most likely to be wrong and the least visible in a diff: both rules are restricted to READMEs, both exclude **/test/fixtures/** (they were firing on CLI test inputs), and docs-npx-cli deliberately does not cover CLAUDE.md, which documents the local pnpm cli path on purpose.

The root README now records that package.json carries a cli script for running the local build. It names the script rather than reproducing the shell line, because the raw scope below is what reaches a fenced code block and Vale evaluates a raw scope against the unparsed markup, so the rule has no in-file escape hatch: measured on Vale 3.18.0, both <!-- vale docs-npx-cli.docs-npx-cli = NO --> and a blanket <!-- vale off --> are ignored, and dropping raw makes them work at the cost of every fenced-block finding.

docs-npx-cli also carries scope: [raw, code, text]. Without it the rule passes every fixture and catches nothing real, because every command in a README is in a code span or a fenced block.

Carried along

The rules/ engine partition, taskless.json, the embedded SKILL.md, and the no-eval move were already in the working tree from the install. Also backfilled: tests for no-eval, which had been failing verify for a missing test file, and every rule's language: is now on ast-grep's own spelling rather than lowercase.

Fixes #104
Refs #165
Refs #166
Refs #167

Contains #176

First slice of the broadening in #169, stacked on #168 because the rules it widens do not exist anywhere else yet.

The three prose rules currently match **/README.md only. That was not a judgement about where the house style applies — it was that landing them repo-wide meant roughly 2300 findings at level: error, which is a remediation decision rather than a technical one. This slice takes the two smallest surfaces and fixes what they catch, so it is green on its own.

What it catches, and what changed

41 findings across CLAUDE.md and .conventions/STYLEGUIDE-CODE.md:

  • 39 em dashes, rewritten as a period, comma, colon, or parentheses as each sentence wanted. Not a blanket substitution: the rule's own message names four options, and "the child is not merely behind: its history diverged" wants a different one from "repair it once. Both are local settings".
  • Two uses of "simply", both in sentences describing a real distinction rather than hedging, so they became "merely lives further down" and "just out of date".

Nothing else in either file changed.

Scope decisions a reviewer should check

Fenced code blocks are untouched, deliberately. These rules carry Vale's default scope, which does not read them, so the em dashes in the shell comments at CLAUDE.md:155 and STYLEGUIDE-CODE.md:222 are genuinely out of scope. Widening to raw to catch them would also mean the rules could no longer be suppressed per-case (see #167), and code comments in a fenced example are not the prose this rule is about.

no-hedging gains CLAUDE.md, which it did not have. It already covered .conventions/*.md, so this makes the pair consistent rather than adding a surface arbitrarily.

Where the stack goes next

Per #169's ordering, and one PR per surface so each carries its own remediation and stays independently green:

  1. This PRCLAUDE.md + .conventions/ (~47 findings)
  2. TypeScript comments — Vale reads .ts in its comments-only tier, so this touches comment prose and never code
  3. packages/cli/src/agent/*.txt — the largest surface, and the most product-facing, since it is agent-facing recipe text
  4. openspec/changes/archive/ — a permanent exclusion rather than a slice. Rewriting an archived proposal would edit the record of what was proposed at the time, which feat: resolve the CLI invocation as a recipe variable #143's review thread already settled

No changeset, here or anywhere in this stack. Taskless rules live in .taskless/ and are not part of the published package, so none of this ships a release note. changeset.yml will warn; that is expected.

One thing worth knowing

Measuring this slice on a machine with agent worktrees present produced 122 findings, of which 83 came from worktrees/. That is #166, and its fix (#172) is not on this branch. The 41 above are the real in-scope count.

Refs #169

Seven rules mined from CLAUDE.md, the .conventions styleguides, and the
posthog conventions — the places this repo already states its rules in
prose. Each one is verified and tested locally; `check` reports two real
findings, both READMEs telling external consumers to run `pnpm dlx`.

sg:
  no-index-imports              relative imports ending in /index
  no-pii-in-telemetry           PII in capture/identify/groupIdentify
  prefer-do-rpc                 `as DurableObjectStub<T>` casts
  no-regex-over-build-output    a test function that reads dist/ and regexes it
  no-pull-request-branches-filter  a branches: filter under pull_request:

vale:
  docs-npx-cli                  pnpm invocations in READMEs
  no-hedging                    simply / obviously / of course

Also backfills tests for the pre-existing no-eval rule, which was
failing `verify` for a missing test file, and puts every rule's
`language:` on ast-grep's own spelling.

The install churn — the rules/ partition, taskless.json, the embedded
SKILL.md — was already in the tree and is carried along rather than
authored here.
@thecodedrift

Copy link
Copy Markdown
Member Author

prefer-do-rpc feels like we won't need it here. The taskless services need it, but the CLI doesn't since it doesn't require cloudflre

no-pii-in-telemetry is a great usecase. Having a way to assert PII is being managed responsibly helps ensure mistakes don't get made

docs-npx-cli I'd definitely prefer this and add a note in the README that there's a "cli" script inside of package.json for running the CLI in a local environment

A sweep of merged-PR review threads and the OSS Linear backlog turned up
OSS-49, which names two planned GitHub Actions rules and records that a
prior onboarding pass stalled on whether ast-grep handles YAML. It does,
so both are buildable locally rather than runtime-tier.

Adds pr-workflow-ready-for-review, and renames the branches-filter rule
to the pr-workflow-* name OSS-49 already uses for the pair.

One finding on the real workflows: stack-breadcrumb.yml names
types: [opened, reopened, edited, closed] with no ready_for_review. That
is a question rather than a defect — it reacts to PR metadata, not to
readiness — and is left for review to settle.
OSS-28 specifies the house writing style as Vale rules. This adds the two
that can be expressed cleanly, scoped to READMEs rather than the whole
repository: broadening is a separate decision with roughly 2200 findings
attached.

no-em-dashes needs `nonword: true`, which the snippet in OSS-28 does not
carry. Measured: without it the rule parses, passes verify, and does not
fire, because Vale wraps every token in word boundaries and an em dash is
non-word on both sides.

no-blocklist-phrases drops the `landed on` token OSS-28 proposed. Measured
firing on "the plane landed on time", which is the literal sense the issue
says to protect; `we landed` already covers "we landed on the second
option", so the broader token earned nothing.

Not included: the "not X, Y" antithesis rule. OSS-28 is undecided on how to
express it and calls it a heuristic, so it wants a human judgement this pass
cannot make.

24 findings in READMEs, all em dashes, left unfixed pending a call on
whether to rewrite the prose or relax the level.
Brings the nine READMEs in line with no-em-dashes, which this branch
scopes to `**/README.md`. Twenty-four instances, rewritten as a period,
comma, colon, or parentheses per the rule's own message rather than
mechanically swapped for one substitute.
docs-npx-cli says docs use `npx @taskless/cli`, the normalized invocation
for external consumers. Two README blocks offered `pnpm dlx` alongside it,
which is the same claim the rule exists to prevent, so the alternative is
removed rather than annotated.

Annotating was measured and is not available here: a rule scoped to `raw`
reads the unparsed document, so Vale's `<!-- vale Rule = NO -->` directive
is inert for it. That scope is what lets the rule see fenced blocks at all,
and every command in a README lives in one.
The most repeated finding in the merged-PR review threads: a comment that
cites a version or a claim the code moved out from under it. On #161 alone,
five threads — "we pin 3.17.1" left behind after VALE_VERSION went to
3.18.0, a comment naming .rmd as plaintext after the diff moved it to
markup, a block still framing 3.18.0 as "the known incoming bump" after it
shipped. Same shape on #155 and #157.

The rule flags the construction rather than the stale number, which is the
half a static check can see. Vale reads .ts in its comments-only tier, so it
lints the comment text and never the code.

Zero findings on packages/cli/src today, because #161 fixed them all. Reach
verified separately by planting a forecast comment in a real source file:
fixtures run under an isolating config, so a passing test does not prove the
matcher glob reaches anything.

Also ignores .taskless/ in eslint and prettier. A rule about source comments
needs .ts fixtures, Vale decides its comments-only tier by extension, and
those files belong to no tsconfig — so lint-staged failed the commit on
"not found by the project service". Rule fixtures are inputs written to be
flagged; verify and test are what keep them honest.
@thecodedrift
thecodedrift marked this pull request as ready for review August 25, 2026 02:30
Copilot AI lite review requested due to automatic review settings August 25, 2026 02:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

pr-workflow-ready-for-review flagged this as the only finding on the repo,
and it is a real gap rather than the intended exception. `ready_for_review`
is not in the default event set, so a draft marked ready fired nothing here:
the breadcrumb went on describing the PR as a draft until some later event
happened to run.

The existing comment says tree SHAPE only, which is still true —
`synchronize` stays out because a head push never changes membership. A
draft becoming ready does change the stack, so it is named explicitly and
the comment now says why.

`taskless check` is clean at 12 rules.
prefer-do-rpc targets `$EXPR as DurableObjectStub<$T>`, a Cloudflare
Durable Objects pattern. The Taskless services need it; this repository
has no Cloudflare code for it to reach, so the rule and its synthetic
fixtures are removed rather than carried pre-emptively. Nothing else in
the tree referenced it.

docs-npx-cli stays as written, and the root README now records that
`package.json` carries a `cli` script pointing at
`./packages/cli/dist/index.js` for running the local build.

The note names the script instead of reproducing its shell invocation,
because docs-npx-cli has no per-case escape hatch: its `raw` scope is
what reaches a fenced code block, and Vale evaluates a raw scope against
the unparsed markup, so in-file directives are ignored. Measured on Vale
3.18.0, both the rule-specific comment and a blanket `vale off` fail to
suppress it, and dropping `raw` makes them work at the cost of every
fenced-block finding. The rule keeps its full reach; the finding is
recorded in its .vale.ini.
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @thecodedrift — "prefer-do-rpc feels like we won't need it here. The taskless services need it, but the CLI doesn't since it doesn't require cloudflre…"
#168 (comment)

All three settled in 09cb566.

1. prefer-do-rpc removed. .taskless/rules/sg/prefer-do-rpc/ is gone, rule and synthetic fixtures both. Nothing else in the tree referenced it: grep -rn prefer-do-rpc is now empty, no changeset mentioned it, and there is no rule index to update since verify/check discover rules from the directory tree. The PR body's table row and its "narrower than the conventions" bullet are updated to record the removal rather than propose the rule. .conventions/STYLEGUIDE-CODE.md still carries the DurableObjectRPC prose section, which is where the convention belongs while the DO code lives elsewhere. Rule count is 12 to 11.

2. no-pii-in-telemetry kept, unchanged. Re-verified and its fixtures re-run after the removal: taskless verify .taskless/rules and taskless test .taskless/rules both report 11 of 11 green.

3. docs-npx-cli kept as written, with the README note added. The script is in the root package.json, not packages/cli/package.json, and it is "cli": "./packages/cli/dist/index.js". It executes the built CLI directly and nothing rebuilds dist/ for it, so the note says to run pnpm build first. It lives under Local development as a new "Running the local build" subsection.

On the self-flagging problem, which is real and turned out to be a property of the rule's scope:

The rule fires on the literal pnpm cli, and a naive note produced two findings in the root README (prose and fenced block). The obvious fix is Vale's per-case suppression, and it does not work here. scope: [raw, code, text] is what reaches a fenced code block at all, and Vale evaluates a raw scope against the unparsed markup, so its in-file directives never apply to it. Measured on Vale 3.18.0 against this exact rule: <!-- vale docs-npx-cli.docs-npx-cli = NO --> is ignored, and so is a blanket <!-- vale off -->. Dropping raw makes both work, and costs every fenced-block finding, which is the rule's whole reason for carrying the scope.

Rather than weaken it, the note names the script instead of reproducing its shell invocation. That keeps the rule at full reach with no exception, no per-file matcher, and no broadened scope. I deliberately did not reach for pnpm run cli, which slips past the substring while documenting exactly the thing the rule exists to keep out of a README. The measurement is recorded in .taskless/rules/vale/docs-npx-cli/.vale.ini so the next person does not re-derive it.

Checks, all local: taskless verify 11 of 11, taskless test 11 of 11, taskless check --anonymous clean, pnpm typecheck clean, pnpm lint clean, pnpm test 829 passed across 53 files.

— AI Coding Agent

@thecodedrift
thecodedrift force-pushed the taskless/planned-rules branch from 09cb566 to e855198 Compare August 26, 2026 04:42
First slice of the broadening in #169. The three prose rules were scoped
to READMEs only, because landing them repo-wide meant roughly 2300
findings at level: error. This adds the two smallest surfaces and fixes
what they catch, so the slice is green on its own.

41 findings: 39 em dashes rewritten as a period, comma, colon, or
parentheses per the rule's own message rather than swapped mechanically
for one substitute, and two uses of "simply" where the sentence was
describing a real distinction ("merely lives further down", "just out of
date") rather than hedging.

Fenced code blocks are untouched. These rules carry Vale's default
scope, which does not read them, so the em dashes in the shell comments
at CLAUDE.md:155 and STYLEGUIDE-CODE.md:222 are out of scope and stay.

Remaining surfaces, in the order #169 proposes: TypeScript comments
(Vale's comments-only tier), then the agent-facing recipe text, which is
the largest. openspec/changes/archive/ gets a permanent exclusion rather
than a slice.

Refs #169
@thecodedrift
thecodedrift merged commit 26a0f66 into nightly/test Aug 26, 2026
2 checks passed
@thecodedrift
thecodedrift deleted the taskless/planned-rules branch August 26, 2026 06:31
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.

2 participants