Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
7c0b1d0
docs(openspec): Propose local-rule-routing change
thecodedrift Jun 11, 2026
9e4d3cb
feat(cli): Add deterministic detect command for rule routing
thecodedrift Jun 11, 2026
acec1fe
feat(cli): Add route/existing/static/remote authoring recipes
thecodedrift Jun 11, 2026
ef45b0a
feat(cli): Surface routing recipes in the help index
thecodedrift Jun 11, 2026
7a29587
feat(skill): Engage routing when a linter is named
thecodedrift Jun 11, 2026
1ecf2a5
test(cli): Add route honesty eval fixtures and finalize change
thecodedrift Jun 11, 2026
e995d50
docs(openspec): Address review feedback on local-rule-routing contract
thecodedrift Jun 12, 2026
6923eb3
docs(openspec): Clarify detect output schema is internal, not published
thecodedrift Jun 12, 2026
95df819
docs(cli): Show detect output shape in the route recipe
thecodedrift Jun 12, 2026
525d53d
docs(cli): Align route recipe with the three-state contract
thecodedrift Jun 12, 2026
2a95a01
Merge branch 'feat/local-rule-routing' into feat/local-rule-detect
thecodedrift Jun 12, 2026
9b277e2
Merge branch 'feat/local-rule-detect' into feat/local-rule-recipes
thecodedrift Jun 12, 2026
eb100f8
Merge branch 'feat/local-rule-recipes' into feat/local-rule-help
thecodedrift Jun 12, 2026
aaacd87
Merge branch 'feat/local-rule-help' into feat/local-rule-skill
thecodedrift Jun 12, 2026
f1ca12a
Merge branch 'feat/local-rule-skill' into feat/local-rule-eval
thecodedrift Jun 12, 2026
0903037
fix(cli): Harden detect against malformed manifests and false positives
thecodedrift Jun 12, 2026
7c32f98
docs(cli): Address review on routing recipes
thecodedrift Jun 12, 2026
546f988
fix(skill): Target @taskless/cli in the changeset; align tool lists
thecodedrift Jun 12, 2026
2afab02
Merge branch 'feat/local-rule-detect' into feat/local-rule-recipes
thecodedrift Jun 12, 2026
211088d
Merge branch 'feat/local-rule-recipes' into feat/local-rule-help
thecodedrift Jun 12, 2026
6357302
Merge branch 'feat/local-rule-help' into feat/local-rule-skill
thecodedrift Jun 12, 2026
82c9177
Merge branch 'feat/local-rule-skill' into feat/local-rule-eval
thecodedrift Jun 12, 2026
7d9f4ce
test(cli): Cover every eval trap and fix the fixtures path
thecodedrift Jun 12, 2026
1b9ef65
chore(openspec): Archive local-rule-routing and sync specs
thecodedrift Jun 12, 2026
ceac5ce
docs(openspec): Propose local-rule-routing change
thecodedrift Jun 11, 2026
19df0b7
docs(openspec): Address review feedback on local-rule-routing contract
thecodedrift Jun 12, 2026
934cfb9
docs(openspec): Clarify detect output schema is internal, not published
thecodedrift Jun 12, 2026
7bc4b6a
ci(openspec): Skip the archive check on non-tip stacked PRs (#31)
thecodedrift Jun 12, 2026
3a3af25
Merge branch 'main' into feat/local-rule-routing
thecodedrift Jun 12, 2026
3b28727
Merge remote-tracking branch 'origin/feat/local-rule-routing' into fe…
thecodedrift Jun 12, 2026
2346cb9
Merge branch 'feat/local-rule-routing' into feat/local-rule-detect
thecodedrift Jun 12, 2026
5b8af65
Merge branch 'feat/local-rule-detect' into feat/local-rule-recipes
thecodedrift Jun 12, 2026
6d18ca5
Merge branch 'feat/local-rule-recipes' into feat/local-rule-help
thecodedrift Jun 12, 2026
0da9de5
Merge branch 'feat/local-rule-help' into feat/local-rule-skill
thecodedrift Jun 12, 2026
21d468f
Merge branch 'feat/local-rule-skill' into feat/local-rule-eval
thecodedrift Jun 12, 2026
9be2000
feat(cli): Make detect monorepo-aware; languages → linters; drop fram…
thecodedrift Jun 14, 2026
c3ee5dd
Merge branch 'feat/local-rule-detect' into feat/local-rule-recipes
thecodedrift Jun 14, 2026
8f89d56
Merge branch 'feat/local-rule-recipes' into feat/local-rule-help
thecodedrift Jun 14, 2026
cb0e2d1
Merge branch 'feat/local-rule-help' into feat/local-rule-skill
thecodedrift Jun 14, 2026
f66fd50
Merge branch 'feat/local-rule-skill' into feat/local-rule-eval
thecodedrift Jun 14, 2026
0d05e94
docs(cli): Update route recipe detect example for the new shape
thecodedrift Jun 14, 2026
3d838a9
docs(cli): Sync cli-detect spec to new shape; document test layers
thecodedrift Jun 14, 2026
569f02a
Merge branch 'feat/local-rule-recipes' into feat/local-rule-help
thecodedrift Jun 14, 2026
7e0bedd
Merge branch 'feat/local-rule-help' into feat/local-rule-skill
thecodedrift Jun 14, 2026
4c196aa
Merge branch 'feat/local-rule-skill' into feat/local-rule-eval
thecodedrift Jun 14, 2026
b4c50eb
feat(cli): Add linters for already-detected Go, Rust, and PHP
thecodedrift Jun 14, 2026
afd79fe
Merge branch 'feat/local-rule-detect' into feat/local-rule-recipes
thecodedrift Jun 14, 2026
2c5e215
Merge branch 'feat/local-rule-recipes' into feat/local-rule-help
thecodedrift Jun 14, 2026
d386942
Merge branch 'feat/local-rule-help' into feat/local-rule-skill
thecodedrift Jun 14, 2026
116190a
Merge branch 'feat/local-rule-skill' into feat/local-rule-eval
thecodedrift Jun 14, 2026
d30341c
chore(cli): Anchor the telemetry-taxonomy reconciliation in code
thecodedrift Jun 14, 2026
26c7ced
chore(cli): Point the telemetry-taxonomy TODOs at issue #39
thecodedrift Jun 14, 2026
4129dcd
Merge branch 'main' into feat/local-rule-routing
thecodedrift Jun 14, 2026
99ef9ab
Merge branch 'feat/local-rule-routing' into feat/local-rule-detect
thecodedrift Jun 14, 2026
947c872
Merge branch 'feat/local-rule-detect' into feat/local-rule-recipes
thecodedrift Jun 14, 2026
6ea3252
Merge branch 'feat/local-rule-recipes' into feat/local-rule-help
thecodedrift Jun 14, 2026
924bfa9
Merge branch 'feat/local-rule-help' into feat/local-rule-skill
thecodedrift Jun 14, 2026
9a832c5
Merge branch 'feat/local-rule-skill' into feat/local-rule-eval
thecodedrift Jun 14, 2026
d60e74f
chore(cli): Resolve #39 — conform detect/help to the cli_ taxonomy
thecodedrift Jun 14, 2026
e69d88e
chore(cli): Drop cli_detect on the detect branch (telemetry taxonomy)
thecodedrift Jun 14, 2026
a797a39
test(cli): Assert cli_help for routing topics (telemetry taxonomy)
thecodedrift Jun 14, 2026
12576e3
Merge branch 'feat/local-rule-detect' into feat/local-rule-recipes
thecodedrift Jun 14, 2026
c4d2556
Merge branch 'feat/local-rule-recipes' into feat/local-rule-help
thecodedrift Jun 14, 2026
6792837
Merge branch 'feat/local-rule-help' into feat/local-rule-skill
thecodedrift Jun 14, 2026
cbb8259
Merge branch 'feat/local-rule-skill' into feat/local-rule-eval
thecodedrift Jun 14, 2026
3ef9846
Merge pull request #24 from taskless/feat/local-rule-detect
thecodedrift Jun 15, 2026
e82a9cd
Merge remote-tracking branch 'origin/main' into feat/local-rule-routing
thecodedrift Jun 15, 2026
520f88d
Merge remote-tracking branch 'origin/feat/local-rule-eval' into feat/…
thecodedrift Jun 15, 2026
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
23 changes: 23 additions & 0 deletions .changeset/detect-monorepo-node22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@taskless/cli": minor
---

Require Node.js 22+ and make `taskless detect` monorepo-aware.

- **Node floor raised to 22+.** Node 20 reached end-of-life, and detect now uses
the built-in `fs.glob` walker (Node 22+). This is a breaking engine change,
which pre-1.0 is a minor bump.
- **`detect` is monorepo-aware.** A single bounded tree walk (curated ignore
list + depth cap) finds linter configs and language manifests anywhere in the
repo, not just the root, so a linter configured in a sub-package is detected
with its path as evidence.
- **languages → linters flow.** A linter's dependency evidence is read only from
its own language's manifest (`package.json` for node, `pyproject.toml` /
`requirements.txt` for Python), parsed with real parsers (`smol-toml`,
`yaml`), instead of conflating ecosystems. A malformed manifest drops only its
own signal.
- **Dropped the `frameworks` field** from `detect` output. The routing recipe
never consumed it; the contract now matches its sole consumer.
- **Filled obvious linter gaps** for languages detect already recognizes:
golangci-lint (Go), Clippy (Rust), and PHPStan / PHP_CodeSniffer / Psalm
(PHP).
10 changes: 10 additions & 0 deletions .changeset/local-rule-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@taskless/cli": minor
---

Add a local-first rule-routing layer. A new deterministic `taskless detect`
command plus `route`/`existing`/`static`/`remote` recipes let the agent author
rules in an existing linter or as a local ast-grep rule on-device, only
escalating to the login-gated service (with confirmation) when a rule cannot be
built locally. The skill now engages this routing flow when a user names a
linter instead of suppressing itself.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-11
282 changes: 282 additions & 0 deletions openspec/changes/archive/2026-06-12-local-rule-routing/design.md

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions openspec/changes/archive/2026-06-12-local-rule-routing/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
## Why

Authoring a rule today routes straight to `rule create`, which is login-gated and
runs generation off the developer's machine. There is no local front door that
asks "what kind of rule is this, and can I build it on-device first?" — so users
hit a login wall before Taskless has demonstrated it needs the service. At the
same time, the skill actively _suppresses_ itself when a user names a linter
(eslint, ruff, biome), stepping back from exactly the requests where Taskless
could help author the rule in that tool's own dialect. We want a local routing
layer that keeps authoring on-device whenever possible, engages other linters
instead of deferring, and suggests login only when it is not reasonably
confident a rule can be built locally — or when a confident local attempt has
failed and the user confirms spending a generation.

## What Changes

- **NEW `taskless detect --json`** — a deterministic, offline repo-signal scan:
which linters are configured, languages present, and the styles of the repo's
own existing rules. No LLM, no network. Feeds the routing recipe.
- **NEW routing recipe layer** under `help`, replacing the rule-type-agnostic
`rule create` entry as the front door for "author a rule":
- `route` — the lightweight **local classifier**. Biased to stay local;
suggests login when it is not reasonably confident the rule is locally
solvable, or when a confident local attempt fails and the user confirms.
- `existing` — author a rule in a linter already detected in the repo, in that
tool's dialect, sourced from the repo's own rules plus the agent's WebFetch.
- `static` — author a local ast-grep rule on-device, verified against the
user's success/failure cases (the lineage of `rule create --anonymous`).
- `remote` — collect inputs and call the Taskless service, which runs the heavy
classifier and returns either a static or a runtime rule (login required).
- **NEW confidence-gated routing contract** — `route` first writes an explicit
rationale (what `detect` shows, whether an existing linter covers it, whether
ast-grep can express it, resulting local-solvability confidence), and only then
names the destination it believes is correct, as a conclusion of that reasoning.
It commits to a local path on **reasonable** confidence (not certainty); when
that confidence is absent it selects `remote` directly. Try-verify-escalate is
the _failure fallback_: a local path committed to in good faith that fails
verification escalates — but escalation to the generation-consuming, login-gated
service **prompts and confirms with the user first**, never silently. When
multiple paths fit, `route` asks the user and explains the trade-offs.
- **MODIFIED skill trigger posture** — naming a specific linter should _engage_
the authoring flow via `route`, not quiet the skill to a one-line offer.
- This change does **not** define static-vs-runtime classification. That cut is
owned upstream by the Runtime Rules project and lives behind `remote`; `route`
only decides local-vs-remote.

## Capabilities

### New Capabilities

- `cli-detect`: A deterministic `taskless detect --json` command that scans the
working directory for linter configs, languages, and the repo's own
rule-authoring styles, emitting structured signals for downstream routing. No
inference, no network.
- `cli-rule-routing`: The `route` / `existing` / `static` / `remote` recipe layer
and the confidence-gated routing contract that determines the destination
upfront, keeps authoring local when there is confidence it is locally solvable,
and routes to the login-gated service directly when that confidence is absent —
avoiding developer-visible failed local attempts.

### Modified Capabilities

- `cli-help`: Register the four routing topics (`route`, `existing`, `static`,
`remote`) in the help index and emit their intent telemetry, consistent with
existing topic registration and embedding requirements.
- `skill-taskless`: Reverse the named-tool suppression clause — when a user names
a linter or asks to author a rule, the skill routes through
`npx @taskless/cli help route` instead of quieting. The skill stays a thin
router and adds no rule knowledge of its own. **Hard constraint:** the skill
`description` field has a
1024-character ceiling (Agent Skills spec). Reversing the suppression clause and
adding routing trigger language compete for that budget, so trigger wording must
be _tightened_ as this and future capabilities land — not appended. Treat the
1024-char limit as a gating check whenever the description changes.

## Impact

- **New command**: `packages/cli/src/commands/detect.ts`, registered in
`packages/cli/src/index.ts`; a `detect` output schema under `schemas/`.
- **New help recipes**: `packages/cli/src/help/{route,existing,static,remote}.txt`,
embedded via the existing `import.meta.glob` build step.
- **Skill body + description**: `skills/taskless/SKILL.md` routing/trigger text.
- **Reused, unchanged**: `rule create` (remote backend for `remote`),
`rule verify` (the local verification gate), the canonical on-disk rule
shape — remote and local paths already write the same files/paths, so
`check`/`improve`/`verify` see one dialect.
- **Upstream dependency**: the Runtime Rules project (TSKL `Runtime Rules`) owns
static-vs-runtime; this change references it and never redefines it.
- **No new network dependencies**; `detect` and the local authoring paths are
offline-capable. Only `remote` requires auth.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
## ADDED Requirements

### Requirement: Detect subcommand exists

The CLI SHALL provide a `taskless detect` subcommand registered in the top-level
command list, with a `--json` flag and the standard `--dir`/`-d` working-directory
flag.

#### Scenario: Detect is registered

- **WHEN** `taskless detect --help` is run
- **THEN** the command SHALL be recognized and print its usage
- **AND** the command SHALL accept `--json` and `--dir`/`-d`

### Requirement: Detect scans deterministic repo signals only

The `detect` command SHALL emit only deterministic signals derived from files on
disk: configured linters, detected languages, and the styles of the repo's own
existing rules. It SHALL NOT perform any LLM inference and SHALL NOT match the
request against any catalog of known packaged linter rules.

Detection follows a languages → linters flow: languages are inferred first, and
a linter's dependency evidence is then read from the manifest of that linter's
own language (a node dependency from `package.json`, a Python dependency from
`pyproject.toml`/`requirements.txt`) rather than conflating ecosystems. A
recognized linter config file on disk is honored regardless of the languages
inferred.

#### Scenario: Linter configs are detected from disk

- **WHEN** the working directory contains a recognized linter config (for
example `.eslintrc*`, `eslint.config.js`, `ruff.toml`, a `[tool.ruff]` block in
`pyproject.toml`, `.rubocop.yml`, `biome.json`, or `stylelint` config)
- **THEN** `detect --json` SHALL report each configured linter it found

#### Scenario: Languages are reported

- **WHEN** `detect --json` runs in a repository
- **THEN** the output SHALL include the languages inferred from manifest and
marker files present on disk and from the linters detected

#### Scenario: A linter dependency is sourced from its own language's manifest

- **WHEN** a dependency-evidenced linter (for example `ruff`) is named only in a
manifest belonging to a different language (for example `package.json`)
- **THEN** `detect --json` SHALL NOT report that linter from the mismatched
manifest

#### Scenario: Configs in monorepo sub-packages are detected

- **WHEN** a linter config or language manifest lives in a sub-package rather
than the repository root (for example `packages/api/.eslintrc.json`)
- **THEN** `detect --json` SHALL detect it and SHALL carry the path it was found
at in the linter's evidence
- **AND** the scan SHALL prune a curated set of ignored directories (for example
`node_modules`, `.git`, build output) and SHALL bound traversal depth

#### Scenario: The repo's own rule styles are surfaced

- **WHEN** the working directory contains existing rule definitions (for example
custom linter rules or `.taskless/rules/`)
- **THEN** `detect --json` SHALL surface a description of those existing rule
styles for downstream authoring

#### Scenario: No packaged-rule catalog matching

- **WHEN** `detect --json` runs
- **THEN** the output SHALL NOT claim a request maps to a specific named packaged
rule (such matching is left to the authoring recipe, not the command)

### Requirement: Detect runs offline with no network or auth

The `detect` command SHALL complete without network access and without
authentication.

#### Scenario: Detect works without login or network

- **WHEN** `detect --json` runs while logged out and offline
- **THEN** it SHALL produce its signal output successfully
- **AND** it SHALL NOT require or prompt for authentication

### Requirement: Detect emits a stable JSON shape

When `--json` is set, `detect` SHALL emit a single structured JSON object whose
shape is validated internally against a stable Zod output schema before being
printed, consistent with how other `--json` commands in the CLI (e.g. `info`,
`check`) validate their output. The schema is an internal contract, not a
published artifact, and `detect` does not expose a `--schema` mode.

#### Scenario: JSON output validates against the internal schema

- **WHEN** `detect --json` succeeds
- **THEN** stdout SHALL be a single JSON object that the command has validated
against its internal output schema (linters, languages, existing rule styles)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## ADDED Requirements

### Requirement: Routing topics are registered in the help system

The help system SHALL register the routing recipes `route`, `existing`, `static`,
and `remote` as embedded help topics, retrievable via `taskless help <topic>` and
listed in the help index, consistent with the existing topic embedding and format
requirements.

#### Scenario: Routing topics resolve

- **WHEN** `taskless help route`, `taskless help existing`,
`taskless help static`, or `taskless help remote` is run
- **THEN** the corresponding recipe text SHALL be returned
- **AND** an unknown-topic error SHALL NOT be raised for any of the four

#### Scenario: Routing topics appear in the index

- **WHEN** `taskless help` (no arguments) is run
- **THEN** the topic index SHALL include the routing topics so an agent can
discover the authoring front door

### Requirement: Routing topics emit intent telemetry

Fetching a routing recipe SHALL emit a per-topic intent telemetry event,
consistent with the existing `help_<topic>` telemetry convention.

#### Scenario: Help topic intent is captured for routing recipes

- **WHEN** the agent fetches `route`, `existing`, `static`, or `remote`
- **THEN** the help command SHALL capture the corresponding `help_<topic>` intent
event with the topic name
Loading