Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/streamlined-onboarding-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

`taskless detect --json` now reports ast-grep and Vale configurations as linters (Taskless's own configuration under `.taskless/` excluded), the command-line tools on the host's `PATH` (`gh`, `git`, `jq`, and now `glab` for GitLab repositories) under `hostTools`, and an `agent` section of commands an agent may choose to run to check whether a source is usable, such as `gh auth status`. Detection still runs nothing and stays offline. `taskless info` lists `glab` among its tools too.

New `taskless report` publishes an unlisted Taskless report to tskl.es: a page showing the rules in this repository, what they were drawn from, and what they are projected to catch. With no flags it prints a draft of what the CLI knows and the facts an agent still has to supply; `--from <facts.json>` checks there are at least three working Taskless rules, withholds any fixture that looks like it holds a secret or contact detail, validates against the tskl.es schema, publishes, and records the report in `.taskless/reports/` (gitignored, since each record holds the URL that deletes it); `--delete <record>` takes a report down. The new `report` agent topic walks an agent through it. Migration 11 creates `.taskless/reports/`.
New `taskless report` publishes an unlisted Taskless report to tskl.es: a page showing the rules in this repository, what they find in the code today, and how often review raised each one's pattern. The CLI counts what the rules find itself, by running the same scan as `check --anonymous`, and an agent cannot supply or change those numbers. When the CLI could not run every rule (a rule written into another linter, a runtime rule, or a scan that failed), it publishes no counts rather than a partial total. With no flags it prints a draft of what the CLI knows, including those counts, and the facts an agent still has to supply; `--from <facts.json>` checks there are at least three working Taskless rules, withholds any fixture that looks like it holds a secret or contact detail, validates against the tskl.es schema, publishes, and records the report in `.taskless/reports/` (gitignored, since each record holds the URL that deletes it); `--delete <record>` takes a report down. The new `report` agent topic walks an agent through it. Migration 11 creates `.taskless/reports/`.

Onboarding now asks one question, "Do you want me to set up Taskless for you?", and does the rest: it mines every source it can reach, writes every rule the repository can enforce locally (always logged out), marks onboarding complete, publishes a report, sends Taskless onboarding feedback, and ends by strongly encouraging running the rules in CI. It asks anything else only when it cannot find enough rules for a report. GitLab merge requests are mined through `glab` the way GitHub pull requests are through `gh`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-14
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
## Context

`taskless check` runs its whole scan inside `checkCommand.run`: path classification, the schema refusal, `--rule` selection, the run directory, `planCheck`, engine config assembly, `runEngines`, and output. None of it is callable from another command. `taskless report` already runs every rule's tests through `verifyOneRule` while building its draft, but has never run the scan.

The live ReportV1 schema (fetched 2026-10-14) is the authority for the payload shape: `findings`, per-rule `state`, `violations`, `files`, and `surfaced`; no `fixed`, `fixedBasis`, `origin`, or `projection.perMonth`. Its rule: `findings` is present exactly when every written rule carries `violations` and `files`. Its service-side consistency checks require `findings.violations` to equal the rules' sum and `findings.files` to fall between the largest rule's `files` and their sum.

## Goals / Non-Goals

**Goals:**

- `report` and `check` run the same scan code, so a report's counts are the counts `check --anonymous` would print.
- Counts are deterministic: the CLI either has a defensible number for every written rule or publishes none.

**Non-Goals:**

- Running a legacy rule's own tool. The CLI cannot run eslint or ruff, and taking the agent's word for those counts is what this change exists to stop.
- Running runtime rules for a report. Reports are published logged out.
- Recomputing tskl.es's monthly rates in the terminal summary.

## Decisions

### Extract the scan, keep `check`'s surface

Move the part of `check` between "rules exist" and "results in hand" (open the run directory, `planCheck`, `assembleEngineConfigs`, `runEngines`, close the run) into a function under `packages/cli/src/rules/` that returns the plan and the dispatch. `check` keeps its argument handling, notices, output, exit code, and telemetry, and calls the function. `report` calls it with `anonymous: true`, no paths, and no selection.

`check` prints the plan's notices before dispatch; after the extraction they are printed when the function returns. Only stderr ordering moves, and only in text mode; `--json` output is unchanged.

_Alternative:_ spawn the built CLI's `check --json` from `report`. Rejected: it needs the built binary at a known path, doubles startup, and a test of `report` would depend on a build.

_Alternative:_ have `report` assemble its own sg and Vale invocation. Rejected: two scans that can drift apart are exactly how a report's number stops matching `check`'s.

### Anonymous is what keeps the draft offline

`planCheck` with `anonymous: true` skips reconcile entirely, so the scan makes no network call and every runtime rule is skipped. This is what lets the draft keep its "no network" requirement.

### All-or-nothing, decided in one place

A single function takes the written rules and the scan outcome and returns either `{ perRule, findings }` or nothing. It returns nothing when any of these holds:

- a written rule is `legacy`, or is a runtime rule under `.taskless/rules/runtime/`;
- the plan or the dispatch reports a failure;
- a result's `ruleId` names no rule under `.taskless/rules/sg/` or `.taskless/rules/vale/`.

The last guard matters because ast-grep reports a result under the `id:` in the rule body, and `sg-id-matches-directory` is a constraint `check` reports rather than a guarantee. Without the guard, a mismatched rule would publish a confident 0 while its real hits sat under a name nothing in the report has. Vale results already carry the bare directory id.

The draft computes this from the local rules alone. `--from` re-applies it after merging the facts, since a `legacy` entry in the facts is what removes the counts.

### File identity is the path relative to the repository root

Each result's `file` is resolved against `cwd` and made relative again before it is counted, so `./src/a.ts` and `src/a.ts` are one file. The service's lower bound (`findings.files` is at least the largest rule's `files`) then holds by construction.

### State comes from where a rule came from

`state` is set by the assembler, never by the facts: rules under `.taskless/rules/` and facts-file `legacy` entries are `written`, facts-file `cloud` entries are `candidate`. A local runtime rule is `written`, and it is the rule that triggers the runtime guard above.

### The summary reports raw totals, not rates

The summary prints `findings` when present, then Σ `surfaced.reviewComments` across Σ `surfaced.prs` over the window. It does not reproduce the service's ×30 ÷ days formula: two copies of a formula in two repositories is a number that drifts silently.

### The counting method counts every author

A comment asking for a rule's pattern costs something whoever wrote it: a bot review spends tokens and money, an author's own fix-up note spends their time and an agent's cycles, and a reviewer's spends review time. All three are what the rule saves, so bots and the pull request's author count the same as a reviewer. Excluding them was considered and rejected: it would undercount exactly the agent-heavy repositories a report is most often published for. The agent classifies each comment by reading it, because the bug report's run showed keyword matching is what an agent improvises when given no method.

## Risks / Trade-offs

- [The draft gets slower: it now scans the whole project as well as running tests.] → The draft runs once per report, and the scan is the same one CI already pays for.
- [A repository with one legacy rule never shows a findings headline.] → Accepted. tskl.es has a headline for that case, and a partly measured total would be a number the reader cannot interpret.
- [Moving plan notices after dispatch changes `check`'s stderr order.] → Text mode only, and nothing parses it. `--json` is unaffected, and its tests guard it.
- [A rule whose tests fail still runs in the scan and is counted.] → Correct: its matches are real matches. The gate already decides whether a failing rule counts toward the three.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Why

A published report never says what its rules find in the code today, even though `taskless check` measures exactly that. Every number on the page came from the facts file, so the one figure that does not depend on trusting the agent was missing, and `fixedBasis: measured` could be claimed with no method behind it. tskl.es has since reshaped ReportV1 around measured findings (live at `https://tskl.es/api/v1/schema` on 2026-10-14), and the nightly's payload no longer validates against it: every publish is refused with a 400.

## What Changes

- **The CLI measures current violations itself.** `taskless report` runs the project's static rules the way `check --anonymous` does and records, per written rule, `violations` and `files`, plus a top-level `findings: { violations, files }` where `files` counts distinct files across all rules. The draft carries them, and `--from` re-measures at publish.
- **Counts are all-or-nothing.** They are published only when the CLI ran every written rule and could attribute every result to one. A legacy rule, a local runtime rule, an engine failure, or a result under an unknown rule id omits `findings` and every per-rule count, and tskl.es falls back to a headline built from what remains. The agent can never supply them.
- **Every rule carries `state`.** Rules under `.taskless/rules/` and legacy rules are `written`; facts-file `cloud` entries are `candidate` and carry no counts.
- **BREAKING (nightly only):** `fixed` and `fixedBasis` are removed from the facts file and the payload. `origin` is renamed `surfaced: { prs, reviewComments }`.
- **BREAKING (nightly only):** `projection.perMonth` is no longer sent. tskl.es derives both monthly rates from each rule's `surfaced` and the window, and refuses a payload that carries them. `projection.inputs` is unchanged.
- The vendored ReportV1 schema is refreshed.
- The publish summary leads with the measured findings when present, and reports review counts as raw totals over the window rather than as a projection.
- The `report` and `onboard` recipes stop asking for fix counts, give one method for counting `surfaced`, and require the agent's own recap to keep "found in the code today" apart from "raised in review over the window".

Delivery shape: **single PR.** The schema change, the measurement, and the recipe changes are only correct together: landing the schema alone would publish reports with no findings, and landing the recipes alone would describe fields the CLI cannot send.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `cli-report`: the draft and the payload carry CLI-measured findings; the facts file loses fix counts and renames origin to surfaced; rules carry a state; the CLI stops computing monthly rates; the summary and the recipe change to match.

## Impact

- `packages/cli/src/commands/check.ts`: the scan it performs is extracted so `report` can run the same one.
- `packages/cli/src/report/` (`draft.ts`, `assemble.ts`, `types.ts`, `summary.ts`) and `packages/cli/src/schemas/report-facts.ts`.
- `packages/cli/src/generated/report-v1.schema.json`, refreshed by `scripts/fetch-report-schema.ts`.
- `packages/cli/src/agent/report.md` and `packages/cli/src/agent/onboard.md`.
- Tests under `packages/cli/test/` for report and check.
- No change to `taskless check`'s output or exit codes.
- Ships in a nightly. `taskless report` itself has not been released, so this extends its pending `patch` changeset rather than adding a second release note for a command no user has seen.
Loading
Loading