feat(cli): tell an agent what init changed, and that recipes are per-task #180
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Runs every shipped demonstration rule's own fixtures. | |
| # | |
| # ## Why this is its own workflow | |
| # | |
| # It is the one job here that deliberately executes a rule's `check.ts`. That | |
| # is arbitrary code by design — the runtime tier exists to run it — and a job | |
| # with that power should be readable as its own thing rather than buried as a | |
| # step inside a general one. | |
| # | |
| # ## Two honest caveats, so nobody reads more into this than it does | |
| # | |
| # **It does not block a merge.** Branch protection requires `Validate` and | |
| # nothing else, so a failure here is visible and advisory until someone adds | |
| # this check to the protection rule. That is the same reason the house-style | |
| # scan lives inside `validate.yml` rather than beside it. | |
| # | |
| # **It is not the only place `check.ts` runs in CI.** `test/demo-command.test.ts` | |
| # executes it too, under `Validate`. What this job adds is a different | |
| # assertion: it copies the bytes `taskless demo` would write — the manifest's | |
| # own written-path list, read straight off `assets/demo-*/` — into one bare | |
| # project, and runs the engines over them with no command and no bundle in the | |
| # way. So a break in a shipped asset tree is attributed to that tree, rather | |
| # than to the command that writes it or the imports that embed it. | |
| name: Demo rules | |
| on: | |
| push: | |
| branches: [main] | |
| # No `branches:` filter, and `ready_for_review` named explicitly. See the long | |
| # note in `validate.yml`: a filtered workflow silently stopped running on | |
| # stacked PRs past a certain depth, and `ready_for_review` is not in the | |
| # default event set. | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| permissions: | |
| contents: read | |
| jobs: | |
| demo-rules: | |
| name: Demo rules | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v6 | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build | |
| run: pnpm build | |
| # A bare project, not this repository. The runtime rule flags undeclared | |
| # `process.env` reads and the sg rule flags `eval`, so running these over | |
| # a checkout of the CLI would report the CLI's own code -- a scan of the | |
| # wrong tree that happens to produce output. | |
| - name: Stage every shipped rule in one bare project | |
| run: | | |
| set -euo pipefail | |
| PROJECT="$(mktemp -d)" | |
| echo "PROJECT=$PROJECT" >> "$GITHUB_ENV" | |
| git -C "$PROJECT" init -q | |
| node packages/cli/dist/index.js init -d "$PROJECT" | |
| # Engine, id and asset directory come from the manifest, so adding a | |
| # fourth sample needs no edit here. | |
| # The script writes the file itself. Redirecting its stdout here | |
| # captured pnpm's platform warnings alongside the manifest, and the | |
| # loop below then tried to stage a rule named "WARN". | |
| pnpm --filter @taskless/cli demo:manifests /tmp/manifests.txt | |
| cat /tmp/manifests.txt | |
| # The trailing field is the manifest's own written-path list, so this | |
| # stages exactly what `taskless demo` writes. A `cp -R` of the asset | |
| # directory would also land `prompt.md`, which the command never | |
| # writes, and this job would be judging a tree no user ever has. | |
| while read -r engine ruleId assetDir paths; do | |
| [ -z "$engine" ] && continue | |
| RULE="$PROJECT/.taskless/rules/$engine/$ruleId" | |
| for path in $paths; do | |
| mkdir -p "$RULE/$(dirname "$path")" | |
| cp "packages/cli/assets/$assetDir/$path" "$RULE/$path" | |
| done | |
| echo "staged $engine/$ruleId" | |
| done < /tmp/manifests.txt | |
| - name: Verify | |
| run: node packages/cli/dist/index.js verify -d "$PROJECT" | |
| # `--dangerously-run-scripts` is the whole point of this job: without it | |
| # the fixtures do not run, and `test` says so rather than reporting a | |
| # pass. A green run here means they executed. | |
| - name: Run every rule's fixtures | |
| run: | | |
| set -euo pipefail | |
| node packages/cli/dist/index.js test --json --dangerously-run-scripts -d "$PROJECT" | tee /tmp/report.json | |
| node -e ' | |
| const report = JSON.parse(require("fs").readFileSync("/tmp/report.json", "utf8")); | |
| const expected = require("fs").readFileSync("/tmp/manifests.txt", "utf8") | |
| .split("\n").filter(Boolean).map((line) => line.split(" ")[1]); | |
| for (const id of expected) { | |
| const rule = report.rules.find((r) => r.ruleId === id); | |
| if (!rule) throw new Error("no report for " + id); | |
| // `ran` as well as `ok`: a rule reporting a pass for fixtures | |
| // that never executed is the defect these runners exist to catch. | |
| if (rule.ran !== true) throw new Error(id + " fixtures did not run: " + JSON.stringify(rule)); | |
| if (rule.ok !== true) throw new Error(id + " fixtures failed: " + JSON.stringify(rule.errors)); | |
| } | |
| console.log("all " + expected.length + " demo rules ran and passed"); | |
| ' |