Skip to content

feat(cli): tell an agent what init changed, and that recipes are per-task #180

feat(cli): tell an agent what init changed, and that recipes are per-task

feat(cli): tell an agent what init changed, and that recipes are per-task #180

Workflow file for this run

# 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");
'