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: 2 additions & 0 deletions .changeset/engine-partitioned-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ A rule engine the CLI does not recognize is now rejected with a message instead

Runtime rules are discovered under `runtime/rules/` rather than the pre-migration `runtime-rules/`. Migration `0004` moves that tree byte-for-byte, so the signatures the server validates are unchanged.

`check` and `rule verify` read the committed `.taskless/sg/sgconfig.yml` rather than writing an ephemeral config on every run, so the config ast-grep uses is the one you can edit and review. A pre-migration rule set still gets a generated config, so an unmigrated project keeps running.

Existing projects keep working without action. The pre-`0004` `.taskless/rules/` still runs as ast-grep, and a delivered rule that names no engine is still treated as ast-grep — a rule engine this CLI does not recognize is rejected rather than guessed at. A migration that would have to merge a file into an engine directory now refuses up front with `SCAFFOLD_CONFLICT` rather than failing part-way.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## ADDED Requirements
## MODIFIED Requirements

### Requirement: Check subcommand executes ast-grep scan

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,18 @@

## 4. ast-grep engine over the committed config

- [ ] 4.1 Update `rules/scan.ts` to run `sg scan --config .taskless/sg/sgconfig.yml --json=stream` and remove ephemeral `sgconfig.yml` generation from the check path
- [ ] 4.2 Update `rules/verify.ts` to run `sg test -c .taskless/sg/sgconfig.yml` over `sg/rule-tests/`
- [ ] 4.3 Tests: scan/verify run against the committed `sg/` config; `sg` binary-not-found prints an error and exits 1
- [x] 4.1 Update `rules/scan.ts` to run `sg scan --config .taskless/sg/sgconfig.yml --json=stream` and remove ephemeral `sgconfig.yml` generation from the check path — `runAstGrepScan` takes the config path and defaults to the committed one; `resolveSgConfigPath` returns it without writing anything
- [x] 4.2 Update `rules/verify.ts` to run `sg test -c .taskless/sg/sgconfig.yml` over `sg/rule-tests/` — through the same resolver, so verify and check agree on which config a rule set uses
- [x] 4.3 Tests: scan/verify run against the committed `sg/` config; `sg` binary-not-found prints an error and exits 1 — `test/sg-committed-config.test.ts`, including a rule directory only the committed config declares (proving it is read, not reconstructed) and the bundled CLI run from outside the workspace with an empty `PATH`

> `generateSgConfig` survives, narrowed to exactly one caller: `resolveSgConfigPath` generating a config
> for the pre-migration `.taskless/rules/` layout, which has no committed config of its own. Its
> `rulesDirectory` / `testDirectory` options are required for that alone. The materialized `.run/` set is
> **not** a second caller — the runtime narrow writes its own `sgconfig.yml` (`rules/runtime/narrow.ts`)
> and never goes through `generateSgConfig`.

## 5. Quality gates

- [ ] 5.1 `pnpm --filter @taskless/cli typecheck && lint && test` clean
- [ ] 5.2 Verify `check` output is identical before and after the relayout on a real `.taskless/` — same findings, same exit code. This change is a no-op to the user, so a difference is a regression
- [ ] 5.3 Update CLI help/onboarding text that names `.taskless/rules/` for the engine-partitioned layout, and `.taskless/.gitignore` handling
- [x] 5.1 `pnpm --filter @taskless/cli typecheck && lint && test` clean — 421 passed, 0 failed
- [x] 5.2 Verify `check` output is identical before and after the relayout on a real `.taskless/` — same findings, same exit code. This change is a no-op to the user, so a difference is a regression. Ran against this repo's own rule set: the pre-change scan (ast-grep over the old ephemeral `ruleDirs: [rules]` config) and the post-migration CLI produced the same 2 findings — same rule, file, position, severity, message — and the same exit code 1. The published CLI was not used as the baseline because `pnpm dlx` is unavailable here; the reproduction is the exact command the old check path ran
- [x] 5.3 Update CLI help/onboarding text that names `.taskless/rules/` for the engine-partitioned layout, and `.taskless/.gitignore` handling — 12 `help/*.txt` recipes, `packages/cli/README.md`, and the `.taskless/README.md` that `0001` writes (now describing the per-engine directories). `.gitignore` handling landed in 1.7
75 changes: 24 additions & 51 deletions openspec/specs/cli-check/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ TBD — Defines the `taskless check` subcommand that validates project setup and

### Requirement: Check subcommand works without taskless.json

The `check` command SHALL NOT require `.taskless/taskless.json` to exist. The command SHALL only require the presence of rule files in `.taskless/rules/`.
The `check` command SHALL NOT require `.taskless/taskless.json` to exist. The command SHALL only require the presence of ast-grep rule files, in the `sg` engine directory `.taskless/sg/rules/` or the pre-migration `.taskless/rules/`.

#### Scenario: Check succeeds without taskless.json

- **WHEN** a user runs `taskless check` in a directory with `.taskless/rules/*.yml` files but no `taskless.json`
- **THEN** the CLI SHALL proceed to generate `sgconfig.yml` and run the scanner
- **WHEN** a user runs `taskless check` in a directory with `.taskless/sg/rules/*.yml` files but no `taskless.json`
- **THEN** the CLI SHALL run the scanner against the committed `.taskless/sg/sgconfig.yml`

#### Scenario: Check exits cleanly with no .taskless/ directory

Expand All @@ -23,63 +23,40 @@ The `check` command SHALL NOT require `.taskless/taskless.json` to exist. The co

#### Scenario: Check exits cleanly with empty rules directory

- **WHEN** a user runs `taskless check` and `.taskless/rules/` contains no `.yml` files
- **WHEN** a user runs `taskless check` and no engine directory holds any rule files
- **THEN** the CLI SHALL print a warning that no rules were found
- **AND** the CLI SHALL exit with code 0

### Requirement: Check subcommand generates ephemeral sgconfig.yml

The `check` command SHALL generate an `sgconfig.yml` file in `.taskless/` before invoking ast-grep. The generated config SHALL set `ruleDirs` to `['rules']` and `testConfigs` to `[{testDir: 'rule-tests'}]`. The file SHALL be written to `.taskless/sgconfig.yml` which is gitignored via `.taskless/.gitignore`. If `.taskless/.gitignore` does not exist, the CLI SHALL create it before writing the config.

#### Scenario: sgconfig.yml is generated at check time

- **WHEN** a user runs `taskless check`
- **AND** `.taskless/rules/` contains rule files
- **THEN** the CLI SHALL write `.taskless/sgconfig.yml` with `ruleDirs: ['rules']`
- **AND** the CLI SHALL pass `--config .taskless/sgconfig.yml` to ast-grep

#### Scenario: Existing sgconfig.yml is overwritten

- **WHEN** `.taskless/sgconfig.yml` already exists (from a previous run or legacy scaffold)
- **THEN** the CLI SHALL overwrite it with the freshly generated content

### Requirement: Check subcommand warns when no rules exist

The CLI SHALL check for the presence of YAML rule files in the `.taskless/rules/` directory. When no rule files are found, the CLI SHALL warn the user and exit cleanly.
The CLI SHALL check for the presence of YAML rule files in the `sg` engine directory `.taskless/sg/rules/`, and in the pre-migration `.taskless/rules/` where a project has not been migrated. When no rule files are found in either, the CLI SHALL warn the user and exit cleanly.

#### Scenario: No rule files in rules directory
#### Scenario: No rule files in any rules directory

- **WHEN** a user runs `taskless check` and `.taskless/rules/` contains no `.yml` files
- **WHEN** a user runs `taskless check` and neither `.taskless/sg/rules/` nor `.taskless/rules/` contains `.yml` files
- **THEN** the CLI SHALL print a warning message indicating no rules were found
- **AND** the CLI SHALL exit with code 0

#### Scenario: Rules directory contains rule files
#### Scenario: Engine rules directory contains rule files

- **WHEN** a user runs `taskless check` and `.taskless/rules/` contains one or more `.yml` files
- **WHEN** a user runs `taskless check` and `.taskless/sg/rules/` contains one or more `.yml` files
- **THEN** the CLI SHALL proceed to run the scanner

### Requirement: Check subcommand executes ast-grep scan
#### Scenario: Only the pre-migration rules directory contains rule files

The CLI SHALL generate an ephemeral `sgconfig.yml` in `.taskless/` and execute
`sg scan --config .taskless/sgconfig.yml --json=stream` using `child_process.spawn` with
`shell: true` for cross-platform binary resolution. The `sg` binary SHALL be resolved from
the `@ast-grep/cli` dependency via PATH. When reconciliation succeeds, the scan SHALL cover
only the blessed `run`-set rule files; on the unauthenticated/`--anonymous` path, or when an
authenticated reconciliation degrades to a local scan, the scan SHALL cover all local rule
files as before.
- **WHEN** a user runs `taskless check` and only `.taskless/rules/` contains `.yml` files
- **THEN** the CLI SHALL run the scanner against a generated config for that layout, so an unmigrated rule set still runs

#### Scenario: ast-grep scan runs with generated config
### Requirement: Check subcommand executes ast-grep scan

- **WHEN** the CLI executes the scanner
- **THEN** it SHALL first write `.taskless/sgconfig.yml`
- **AND** it SHALL invoke `sg scan` with `--config .taskless/sgconfig.yml` and `--json=stream`
- **AND** the working directory for the spawned process SHALL be the resolved project directory
The CLI SHALL execute `sg scan --config .taskless/sg/sgconfig.yml --json=stream` using `child_process.spawn` with `shell: true` for cross-platform binary resolution, reading the **committed** ast-grep config at `.taskless/sg/sgconfig.yml`. No `sgconfig.yml` is generated at check time. The `sg` binary SHALL be resolved from the `@ast-grep/cli` dependency via PATH. Reconciliation/run-set semantics for runtime rules are unchanged.

#### Scenario: Scan is limited to the run set when reconciled
#### Scenario: ast-grep scan runs with the committed config

- **WHEN** reconciliation succeeded and returned a `run` set
- **THEN** the generated scan configuration SHALL cause `sg scan` to evaluate only the
`run`-set rule files
- **WHEN** the CLI executes the ast-grep scanner
- **THEN** it SHALL invoke `sg scan` with `--config .taskless/sg/sgconfig.yml` and `--json=stream`
- **AND** it SHALL NOT write or generate a config file
Comment thread
thecodedrift marked this conversation as resolved.
- **AND** the working directory for the spawned process SHALL be the resolved project directory

#### Scenario: ast-grep binary is not found

Expand Down Expand Up @@ -248,7 +225,7 @@ When `taskless check --json` exits with an error, the output SHALL conform to th
### Requirement: Check selects what it runs from auth state

`taskless check` SHALL NOT require authentication, and it SHALL choose what it runs from the
current auth state. **Static ast-grep rules** (single `*.yml` files under `.taskless/rules/`)
current auth state. **Static ast-grep rules** (single `*.yml` files under `.taskless/sg/rules/`, or the pre-migration `.taskless/rules/`)
SHALL always run without contacting the server, on every path (the offline linter posture).
**Runtime rules** (directories with `metadata.taskless.kind: runtime`) SHALL run only on a
signature-validated path: when a token is available and `--anonymous` is not set the CLI SHALL
Expand Down Expand Up @@ -321,16 +298,12 @@ non-zero code solely because reconciliation failed, and the warning SHALL be sup

### Requirement: Check dispatches static and runtime rules to distinct executors

`taskless check` SHALL execute **static** ast-grep rules under `.taskless/rules/` with the
ast-grep scanner as before, and **runtime** rules under `.taskless/runtime-rules/` (directories
with `metadata.taskless.kind: runtime`, per the `cli-runtime-rule-execution` capability) with
the runtime harness. Findings from both executors SHALL be aggregated into the same result set
and SHALL count toward the exit code identically.
`taskless check` SHALL dispatch rules to distinct executors by their engine directory: **ast-grep** rules under `.taskless/sg/` via the ast-grep scanner, **Vale** rules under `.taskless/vale/` via the Vale runner (per the `cli-vale-rule-engine` capability), and **runtime** rules under `.taskless/runtime/rules/` via the runtime harness (per the `cli-runtime-rule-execution` capability). Findings from all executors SHALL be aggregated into the same result set and SHALL count toward the exit code identically.

#### Scenario: Mixed corpus runs both executors
#### Scenario: Mixed corpus runs all executors

- **WHEN** `.taskless/rules/` contains static rules and `.taskless/runtime-rules/` contains runtime rules
- **THEN** the CLI SHALL run static rules through `sg scan` and runtime rules through the runtime harness
- **WHEN** `.taskless/sg/` contains ast-grep rules, `.taskless/vale/` contains Vale rules, and `.taskless/runtime/rules/` contains runtime rules
- **THEN** the CLI SHALL run ast-grep rules through `sg scan`, Vale rules through the Vale runner, and runtime rules through the runtime harness
- **AND** SHALL merge their findings into one result set

### Requirement: Check runs runtime rules only on a signature-validated path
Expand Down
Loading
Loading