Skip to content

Migrations relocate a current-layout project when taskless.json is missing, and check then reports success #109

Description

@thecodedrift

A .taskless/ tree that is already in the current rule-directory layout but has no taskless.json reads as schema version 0. The migrations then "upgrade" it: the rules are moved to .taskless/sg/rules/sg/, which is not where the scanner looks. check finds nothing and reports success.

The failure mode is the dangerous one — not an error, a clean pass on a project whose rules were silently moved out from under it.

Reproduction

Build the CLI, then:

T=$(mktemp -d)
mkdir -p "$T/.taskless/rules/sg/no-eval"
printf 'id: no-eval\nlanguage: javascript\nseverity: error\nmessage: Avoid eval\nrule:\n  pattern: eval($$$ARGS)\n' \
  > "$T/.taskless/rules/sg/no-eval/no-eval.yml"
echo 'eval("x")' > "$T/a.js"

find "$T/.taskless" -type f          # .taskless/rules/sg/no-eval/no-eval.yml
node packages/cli/dist/index.js check -d "$T" --json; echo "exit=$?"
find "$T/.taskless" -type f

Observed:

Migrating to latest .taskless/ schema...
{"success":true,"results":[]}
exit=0

and the tree afterwards:

.taskless/rules/sg/.gitkeep                     <- emptied
.taskless/sg/rules/sg/no-eval/no-eval.yml       <- rule moved here
.taskless/taskless.json

a.js contains eval("x") and the rule has severity: error, so a correct run exits 1 with one finding. Adding {"version":5} to .taskless/taskless.json before the run makes it behave correctly, which confirms the version read is the trigger.

Why it happens

Migration 0004 moves .taskless/rules/ to .taskless/sg/rules/. Applied to a tree that already holds .taskless/rules/sg/..., that produces the doubled .taskless/sg/rules/sg/.... 0005 then creates the current directories but does not reclaim the rules stranded by 0004.

How real is it

A project created by taskless init always has a version file, so this is not the ordinary upgrade path. It is reachable by:

  • hand-authored or scripted .taskless/ trees (this is how it was found — a test fixture built in the current layout)
  • a taskless.json lost to a bad merge or a partial checkout
  • anything that copies rule directories into a fresh project without the version file

Worth deciding

The relocation itself is arguably correct behavior for a genuinely pre-versioned tree. The part that is not defensible is reporting success afterwards. Options, roughly in order of cost:

  1. Refuse to migrate when the tree already matches the current layout but declares no version — ask rather than guess.
  2. Have 0005 reclaim .taskless/sg/rules/<engine>/ into .taskless/rules/<engine>/, making the 0004+0005 pair idempotent on an already-current tree.
  3. Make an empty scan across every engine, on a project that had rule files before migrating, a loud condition rather than a pass.

(3) is the one that generalizes: an empty scan reporting success is the shape that hid this.

Found while restacking the #71#106 Vale engine stack; the fixture in packages/cli/test/vale-orchestration.test.ts now declares its version, which papers over the symptom in the tests but not the behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions