Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ Where code or diff context is available, the engine-selection topic SHALL weigh
- **AND WHEN** the equivalent rule requires normalizing a captured value to match a declaration elsewhere
- **THEN** it selects `runtime`, despite an identically phrased request

### Requirement: Ambiguity defaults to an engine known to be available
### Requirement: Ambiguity resolves to an engine known to be available

When no engine is clearly indicated, the engine-selection topic SHALL default to `sg` and record why the call was close. The topic SHALL state this as a property — the default names an engine known to be available — rather than as a bare fact about `sg`, so it stays correct wherever an engine can be unavailable or a route withheld.
When no engine is clearly indicated, the engine-selection topic SHALL direct the reader to choose an engine whose availability can be asserted in the situation at hand, and to give that availability as the reason for the call. The topic SHALL NOT name a fixed fallback engine. Both `sg` and `vale` ship as platform binaries, so either can be the missing one on an unsupported architecture or where an install was blocked; server-side the constraint is different again, `sg` being the only ungated route. A named default is wrong in whichever of those situations it failed to anticipate, which is why the requirement is stated as a property rather than as a fact about any one engine.

#### Scenario: Ambiguous request defaults to sg
#### Scenario: Ambiguous request resolves to an assertably available engine

- **WHEN** the available context does not disambiguate which engine can enforce a rule
- **THEN** the topic selects `sg` and states the reasoning that made the call close
- **THEN** the topic selects an engine whose availability it can assert, and states that availability as the reasoning that made the call close

#### Scenario: The default is never an unavailable engine

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ When the `vale` binary cannot be found or invoked, the system SHALL report that

The system SHALL verify a Vale rule from a `.taskless/vale/rule-tests/<rule>/` subdirectory containing `pass/` and `fail/` fixture documents. Because verification is one-time (not per-check), the system SHALL **generate** an ephemeral `.vale.ini` at verify time (StylesPath plus only that rule enabled) rather than requiring a committed one — the subdirectory holds fixtures only. Verification SHALL assert that every `fail/` fixture produces at least one finding for the rule and every `pass/` fixture produces none (mirroring ast-grep's `invalid`/`valid`).

Both buckets SHALL hold at least one document before a rule can be reported as verified. A `fail/` fixture proves the rule fires; a `pass/` fixture proves it does not over-fire; either alone establishes half the claim. A rule populating only one bucket SHALL be reported as unverified rather than passing, and the report SHALL distinguish that half-written state from a rule carrying no fixtures at all — a rule with only `pass/` fixtures would otherwise pass trivially, on an empty set of expected failures, having never demonstrated that it fires.

#### Scenario: Fail fixture triggers, pass fixture does not

- **WHEN** verify runs for a rule and generates an isolating `.vale.ini` enabling only that rule
Expand All @@ -95,6 +97,12 @@ The system SHALL verify a Vale rule from a `.taskless/vale/rule-tests/<rule>/` s
- **WHEN** a `fail/` fixture for a rule produces no finding
- **THEN** verification reports a failure for that rule

#### Scenario: A one-sided fixture set is not verified

- **WHEN** a rule has `fail/` fixtures but no `pass/` fixtures, or `pass/` fixtures but no `fail/`
- **THEN** verification reports the rule as unverified rather than passing
- **AND** the result distinguishes a half-written fixture set from a rule with no fixtures at all

### Requirement: Taskless breadcrumbs use a namespaced ignored key in the Vale config

Any Taskless-owned breadcrumb the system records in `.vale.ini` SHALL use a `tskl) <name> = <value>` key. The system SHALL NOT rely on Vale enforcing these keys; they are read only by Taskless tooling, and Vale's ini parser accepts and ignores them. Each Taskless-owned matcher SHALL carry a `tskl) rule = <id>` key naming its owning rule, so tooling can locate and update the right rule's matchers even when its scoping is split across multiple (possibly duplicate) matchers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@

## 3. Engine-selection knowledge topic

- [ ] 3.1 Author `packages/cli/src/help/<engine-selection>.txt` covering: the three engine definitions (`sg` in-file syntax tree incl. relational correlation, `vale` prose/markup, `runtime` cross-file/graph/metadata/normalization), the reason-before-answer procedure, and a worked example table. The original seed (`tmp/SEED-engine-selection-prose.md`) no longer exists — rebuild the examples rather than treating the file as a prerequisite; the content requirements are stated here and in 3.2–3.4, which is the authority
- [ ] 3.2 State the ambiguity default as a property — the default names an engine known to be available — and note that `@ast-grep/cli` ships as a dependency while the Vale binary is external, so `sg` satisfies it locally
- [ ] 3.3 Carry the three boundary cases: prose-about-code vs structure, Vale is per-document (cross-document prose consistency is `runtime`), and `sg`/`vale` are both static-tier so trust tier is a separate axis
- [ ] 3.4 Keep the topic scoped to engine choice — no authoring-destination guidance, no tool-calling mechanics from the seed's source prompt
- [ ] 3.5 Register the topic in the help index and add `route`/`static` cross-references to it
- [ ] 3.6 Tests: the topic resolves via `taskless help` and appears in the index; the topic file matches the established recipe header/format convention
- [ ] 3.7 Add the topic to `TOPICS` in `packages/cli/src/prompts/index.ts`, in the **same unit as the topic file**. This is not optional bookkeeping: `TOPICS ∪ INTERNAL_TOPICS` must account for every canonical recipe on disk, and `test/prompts.test.ts` asserts that in both directions — so a recipe file added without a classification turns the suite red. `TOPICS` rather than `INTERNAL_TOPICS` per D7: the platform generator is the intended consumer, and exporting the entry is what lets it render the same text `taskless help` serves instead of keeping its own copy
- [x] 3.1 Author `packages/cli/src/help/<engine-selection>.txt` covering: the three engine definitions (`sg` in-file syntax tree incl. relational correlation, `vale` prose/markup, `runtime` cross-file/graph/metadata/normalization), the reason-before-answer procedure, and a worked example table. The original seed (`tmp/SEED-engine-selection-prose.md`) no longer exists — rebuild the examples rather than treating the file as a prerequisite; the content requirements are stated here and in 3.2–3.4, which is the authority
- [x] 3.2 State the ambiguity default as a property — the default names an engine known to be available. Written per D7 rather than this line's parenthetical, which is stale: `add-vale-binary-packages` ships the Vale binary as an `optionalDependency` too, so both `sg` and `vale` are normally present locally. The property still binds on an unsupported architecture or a blocked install, and server-side `sg` remains the only ungated route
- [x] 3.3 Carry the three boundary cases: prose-about-code vs structure, Vale is per-document (cross-document prose consistency is `runtime`), and `sg`/`vale` are both static-tier so trust tier is a separate axis
- [x] 3.4 Keep the topic scoped to engine choice — no authoring-destination guidance, no tool-calling mechanics from the seed's source prompt
- [x] 3.5 Register the topic in the help index and add `route`/`static` cross-references to it
- [x] 3.6 Tests: the topic resolves via `taskless help` and appears in the index; the topic file matches the established recipe header/format convention
- [x] 3.7 Add the topic to `TOPICS` in `packages/cli/src/prompts/index.ts`, in the **same unit as the topic file**. This is not optional bookkeeping: `TOPICS ∪ INTERNAL_TOPICS` must account for every canonical recipe on disk, and `test/prompts.test.ts` asserts that in both directions — so a recipe file added without a classification turns the suite red. `TOPICS` rather than `INTERNAL_TOPICS` per D7: the platform generator is the intended consumer, and exporting the entry is what lets it render the same text `taskless help` serves instead of keeping its own copy

> Export via `@taskless/cli/prompts` **is** part of this change (D7). `export-knowledge-prompts` landed first, so the one-line `TOPICS` entry falls here — see task 3.7.

## 4. Quality gates

- [ ] 4.1 `pnpm --filter @taskless/cli typecheck && lint && test` clean
- [ ] 4.2 With the Vale binary absent, confirm ast-grep and runtime results still return and only Vale reports unavailable
- [x] 4.1 `pnpm --filter @taskless/cli typecheck && lint && test` clean
- [x] 4.2 With the Vale binary absent, confirm ast-grep and runtime results still return and only Vale reports unavailable
14 changes: 14 additions & 0 deletions openspec/specs/cli-check/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,17 @@ SHALL be the only way to execute runtime rules on an unverified path.
- **WHEN** `--dangerously-run-scripts` and `--json` are both set
- **THEN** stdout SHALL contain only the existing `{ success, results }` JSON shape
- **AND** the unverified-execution warning SHALL NOT appear in stdout

### Requirement: Check runs engines concurrently and merges their results

`taskless check` SHALL run its per-engine executors concurrently and merge their `CheckResult`s into a single result set. A missing or unavailable engine SHALL NOT abort the others; its absence SHALL be reported while the remaining engines still produce results.

#### Scenario: ast-grep and Vale run concurrently and merge

- **WHEN** `.taskless/sg/` and `.taskless/vale/` both contain rules
- **THEN** the CLI runs both engines concurrently and returns one merged result set whose findings count toward the exit code identically

#### Scenario: One engine unavailable, others proceed

- **WHEN** the `vale` binary is unavailable but `.taskless/sg/` has rules
- **THEN** the CLI reports the Vale engine as unavailable and still returns ast-grep results
23 changes: 23 additions & 0 deletions openspec/specs/cli-help/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,29 @@ consistent with the existing `help_<topic>` telemetry convention.
- **THEN** the help command SHALL capture the corresponding `help_<topic>` intent
event with the topic name

### Requirement: The engine-selection topic is registered in the help system

The help system SHALL register the engine-selection recipe as an embedded help topic, retrievable via `taskless help <topic>` and listed in the help index, consistent with the existing topic embedding and format requirements.

#### Scenario: Engine-selection topic resolves

- **WHEN** `taskless help` is run for the engine-selection topic
- **THEN** the recipe text SHALL be returned and an unknown-topic error SHALL NOT be raised

#### Scenario: Engine-selection topic appears in the index

- **WHEN** `taskless help` is run with no arguments
- **THEN** the topic index SHALL include the engine-selection topic so an agent can discover it

### Requirement: Routing recipes reference engine selection

The `route` and `static` recipes SHALL reference the engine-selection topic so an agent following the local authoring flow applies the same engine test the service applies, rather than assuming ast-grep.

#### Scenario: Local flow reaches engine selection

- **WHEN** an agent follows `route` to a destination that authors a Taskless rule
- **THEN** the recipe directs it to the engine-selection topic before the rule is authored

## Goal

<one paragraph stating what this recipe accomplishes>
Expand Down
9 changes: 9 additions & 0 deletions openspec/specs/cli-rule-format/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,12 @@ When `taskless.json`'s `version` exceeds the highest migration the installed CLI

- **WHEN** the same condition holds and `--allow-version-mismatches` is set
- **THEN** the CLI proceeds without applying migrations

### Requirement: Vale styles live under the rules StyleName

The system SHALL place Vale styles under `.taskless/vale/rules/` so that `rules` is Vale's StyleName, with `.vale.ini` configured `StylesPath = .` and `BasedOnStyles = rules`. The Vale check identifier `rules.<name>` SHALL be normalized to `ruleId = <name>` in results.

#### Scenario: Style resolution and identity

- **WHEN** a Vale style exists at `.taskless/vale/rules/no-simply.yml`
- **THEN** Vale loads it as `rules.no-simply`, and the CLI reports its findings with `ruleId` `no-simply`
66 changes: 66 additions & 0 deletions openspec/specs/cli-rule-routing/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,69 @@ SHALL NOT itself decide static versus runtime.
- **WHEN** the service returns a generated rule via `remote`
- **THEN** the written files and paths SHALL match the shape produced by the
local `static` path

### Requirement: An engine-selection topic states which engine can enforce a rule

The CLI SHALL provide a knowledge topic that decides, for a requested rule, **which engine can enforce it** — `sg`, `vale`, or `runtime` — valued as the engine's on-disk directory name. The topic SHALL define each engine by the information a rule fundamentally needs:

- **`sg`** — expressible as a pattern over a single file's syntax tree, including correlation between constructs within that same file via relational operators.
- **`vale`** — the target is prose or markup content rather than code structure.
- **`runtime`** — needs information no single file's syntax tree contains: cross-file consistency, import or call graph, comparison against a non-code file, file metadata, or values requiring normalization a static pattern cannot express.

The topic SHALL instruct that the decision follow from what the rule fundamentally needs rather than how the request was phrased, and that the reasoning be stated before the engine is named.

#### Scenario: Engine named for a single-file structural rule

- **WHEN** the topic is applied to a request expressible as a pattern over one file's syntax tree
- **THEN** it selects `sg`

#### Scenario: Engine named for a prose rule

- **WHEN** the topic is applied to a request targeting prose or markup content
- **THEN** it selects `vale`

#### Scenario: Engine named for a cross-file rule

- **WHEN** the topic is applied to a request requiring information beyond a single file's syntax tree
- **THEN** it selects `runtime`

### Requirement: Engine selection is a separate axis from authoring destination

The engine-selection topic SHALL decide only which engine enforces a rule, and SHALL NOT decide where the rule is authored — that remains the `route` topic's concern. Locally the two compose, `route` first and engine selection second.

The topic SHALL NOT describe login, reconciliation, or signing as inputs to the engine choice: `sg` and `vale` are both static-tier, and only `runtime` carries those concerns, so trust tier is a distinct axis from engine selection.

#### Scenario: Topic stays clear of authoring destination

- **WHEN** the engine-selection topic is applied
- **THEN** it names an engine and does not select among `existing`, `static`, or `remote` authoring destinations

#### Scenario: Trust tier is not an engine-selection input

- **WHEN** the topic distinguishes `sg` from `vale`
- **THEN** it does so on the prose-versus-structure axis, not on any auth, reconcile, or signing property, since both are static-tier

### Requirement: Available code context outranks the phrasing of the request

Where code or diff context is available, the engine-selection topic SHALL weigh the concrete syntactic form present in the repository above the wording of the request, since the same request routes differently depending on the form the code actually takes.

#### Scenario: Concrete form changes the engine

- **WHEN** a rule is statically correlatable in the form the repository actually contains
- **THEN** the topic selects `sg`
- **AND WHEN** the equivalent rule requires normalizing a captured value to match a declaration elsewhere
- **THEN** it selects `runtime`, despite an identically phrased request

### Requirement: Ambiguity resolves to an engine known to be available

When no engine is clearly indicated, the engine-selection topic SHALL direct the reader to choose an engine whose availability can be asserted in the situation at hand, and to give that availability as the reason for the call. The topic SHALL NOT name a fixed fallback engine. Both `sg` and `vale` ship as platform binaries, so either can be the missing one on an unsupported architecture or where an install was blocked; server-side the constraint is different again, `sg` being the only ungated route. A named default is wrong in whichever of those situations it failed to anticipate, which is why the requirement is stated as a property rather than as a fact about any one engine.

#### Scenario: Ambiguous request resolves to an assertably available engine

- **WHEN** the available context does not disambiguate which engine can enforce a rule
- **THEN** the topic selects an engine whose availability it can assert, and states that availability as the reasoning that made the call close

#### Scenario: The default is never an unavailable engine

- **WHEN** an engine is unavailable in the current environment, such as the Vale binary being absent
- **THEN** the ambiguity default SHALL NOT name it
Loading
Loading