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
65 changes: 65 additions & 0 deletions openspec/specs/cli-rule-routing/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
## Purpose

TBD - created by archiving change local-rule-routing. Update Purpose after archive.

## Requirements

### Requirement: Route is the local authoring classifier

The CLI SHALL provide a `route` help recipe that instructs the agent to classify a rule-authoring request into one of five destinations — `create-legacy-rule`, `create-sg-rule`, `create-vale-rule`, `create-runtime-rule`, or `create-remote-rule` — using `taskless detect --json` signals plus the user's intent. The `route` recipe SHALL read the user's login state before dispatching, since it determines which destinations are reachable. It SHALL remain biased to stay local: local authoring that works SHALL NOT be abandoned for the service.
Expand Down Expand Up @@ -247,3 +249,66 @@ One statement is the point. A criterion copied into each destination is five cop
- **WHEN** the embedded recipe set is inspected
- **THEN** there SHALL be no topic whose only purpose is selecting among engines

### Requirement: Route states each local engine's reach from the pinned engine versions

The `route` recipe SHALL state what each local engine can actually read, so
that an agent choosing between `create-sg-rule`, `create-vale-rule`, and the
runtime destinations is not left to guess whether an engine parses the language
in front of it.

The recipe SHALL state, for the ast-grep engine, the set of languages the
pinned ast-grep release parses, spelled as a rule's `language:` field must
spell them. It SHALL state, for the Vale engine, that Vale treats a file
according to its extension in tiers — markup, comment text only, and a
plaintext fallback that lints an unparsed file as whole-file prose — and that a
set of formats fails rather than lints because Vale delegates their parse to an
external converter this CLI does not ship. The recipe SHALL state that such a
failure aborts the entire Vale pass rather than skipping the offending file.

These statements SHALL be **derived from the pinned engine versions rather
than transcribed into the recipe text**. The recipe SHALL carry substitution
markers resolved at render time from constants that name the engine version
they were taken from, and those constants SHALL be pinned to the engines'
observable behaviour by tests that invoke the engine binaries. A transcribed
list would go stale on the next engine bump with nothing to detect it, and a
stale claim about engine reach is more harmful than no claim, because an agent
acts on it.

The recipe SHALL NOT treat a language absent from both engines' reach as
automatically requiring the runtime tier. It SHALL direct the agent to consider
`create-legacy-rule` first, since a linter the repository already runs may
cover the language, and that destination requires no login.

The recipe SHALL distinguish an engine's reach from an engine's availability.
Reach is a property of the pinned engine version; availability is a property of
the host on which the CLI is running, and a language within reach is still
unusable where the engine's platform binary did not resolve.

#### Scenario: Route names ast-grep's languages

- **WHEN** the rendered `route` recipe is read
- **THEN** it SHALL name the languages the pinned ast-grep release parses, including `Yaml`
- **AND** an agent SHALL be able to conclude from it that a rule over a GitHub Actions workflow is expressible as an `sg` rule

#### Scenario: Route names Vale's tiers and its unreadable formats

- **WHEN** the rendered `route` recipe is read
- **THEN** it SHALL name the extensions Vale parses as markup, the extensions where Vale lints comment text only, and the plaintext fallback that applies to everything else
- **AND** it SHALL name the formats whose parse Vale delegates to an absent external converter
- **AND** it SHALL state that one such file fails the whole Vale pass rather than only itself

#### Scenario: The reach statements carry no unresolved marker

- **WHEN** the `route` recipe is rendered
- **THEN** no `%(…)s` substitution marker SHALL remain in the text an agent receives

#### Scenario: A bumped engine cannot leave the recipe stale

- **WHEN** an engine binary is upgraded to a version whose reach differs from the constants the recipe renders
- **THEN** a vendor-contract test that invokes the engine SHALL fail
- **AND** the recipe SHALL NOT be able to state the superseded reach without that failure

#### Scenario: An unreachable language is not routed to runtime by default

- **WHEN** the rule's language appears in neither engine's reach
- **THEN** `route` SHALL direct the agent to consider `create-legacy-rule` before escalating to a runtime destination
24 changes: 18 additions & 6 deletions openspec/specs/cli-rule-validation/spec.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# cli-rule-validation Specification

## Purpose

TBD - created by archiving change self-contained-rules. Update Purpose after archive.

## Requirements

### Requirement: Rules are validated and tested by path, not by id

The CLI SHALL provide `verify <path>` and `test <path>`. Both SHALL accept a path to a rule's canonical location or to any directory above it, and SHALL resolve the owning engine from the path's position under `.taskless/rules/<engine>/` rather than by parsing the file.
Expand Down Expand Up @@ -39,11 +42,11 @@ The two commands split because they have different preconditions. An agent part-

Per engine, `verify` SHALL check:

| Engine | Components |
|-----------|--------------------------------------------------------------------------------|
| `sg` | `<id>.yml` against the ast-grep schema and the Taskless required fields |
| `vale` | `<id>.yml` against Vale's own validation, and the rule's `.vale.ini` |
| `runtime` | `check.ts` present, and at least one capture rule under `captures/` |
| Engine | Components |
| --------- | ----------------------------------------------------------------------- |
| `sg` | `<id>.yml` against the ast-grep schema and the Taskless required fields |
| `vale` | `<id>.yml` against Vale's own validation, and the rule's `.vale.ini` |
| `runtime` | `check.ts` present, and at least one capture rule under `captures/` |

#### Scenario: A rule with no fixtures still verifies

Expand All @@ -62,6 +65,8 @@ Per engine, `verify` SHALL check:

Ordering is the point. When a rule is both malformed and under-fixtured, the fixture complaint is the less useful of the two errors and is the one that surfaces first if the checks run in the other order — so the author is told their fixtures are incomplete while the reason the rule could never have run goes unmentioned.

A rule that populates only one bucket has proved only half of what a rule claims, whatever its engine. An engine SHALL NOT be trusted to report this itself: `ast-grep test` reports an empty `invalid:` bucket as `1 passed; 0 failed` and exits zero, so a rule that has never matched anything is indistinguishable from one that passed.

#### Scenario: A malformed rule reports the malformation, not the fixtures

- **WHEN** `test` runs against a rule that is both invalid and missing a fixture bucket
Expand All @@ -75,6 +80,14 @@ Ordering is the point. When a rule is both malformed and under-fixtured, the fix
- **AND** every `pass/` document SHALL produce none
- **AND** a rule populating only one bucket SHALL be reported as unverified rather than passing

#### Scenario: ast-grep fixtures are counted per bucket

- **WHEN** `test` runs against an ast-grep rule
- **THEN** the `valid:` and `invalid:` entries SHALL be counted across every `-test.yml` file the rule owns
- **AND** a rule populating only one bucket SHALL be reported as unverified rather than passing
- **AND** a rule whose buckets are all empty or absent SHALL be reported as unverified rather than passing
- **AND** a green `ast-grep test` run SHALL NOT on its own be sufficient to report the rule as passing

### Requirement: The generation loop runs verify and test

The rule generation loop SHALL run `verify` and then `test` against a newly authored or newly delivered rule, and SHALL treat a failure of either as a rule that is not ready to report as complete.
Expand All @@ -84,4 +97,3 @@ The rule generation loop SHALL run `verify` and then `test` against a newly auth
- **WHEN** a rule is authored locally or written by the service
- **THEN** the loop SHALL run `verify` and `test` against its path
- **AND** SHALL surface a failure rather than reporting the rule as written

Loading