Skip to content

fix(agent): state the one-command-per-call rule in every recipe - #534

Merged
theCodeDrift merged 1 commit into
mainfrom
fix/recipe-one-command-per-call
Oct 18, 2026
Merged

theCodeDrift merged 1 commit into
mainfrom
fix/recipe-one-command-per-call

Conversation

@theCodeDrift

Copy link
Copy Markdown
Member

0.13.1 taught the installed skill to run each Taskless command as its own shell call. An agent deep in a recipe copies that recipe's own examples instead, and that is where chaining crept back in. This puts the rule in every recipe.

One shared paragraph. Every standalone recipe carries %(ONE_COMMAND_PER_CALL)s directly beneath its # Topic: line, rendered from one function with the same invocation %(TASKLESS_CLI)s resolves to:

Run each Taskless command as its own shell call. Start the line with <invocation> and chain nothing to it: no variable assignment, cd, mkdir, &&, ;, pipe, redirect, or $(…). Read its output from the call's result, and write any file this recipe asks for with your file tools.

Chained examples removed.

  • check substituted $(git diff …) into the command line. It now runs git diff --name-only as its own call and passes the paths it printed.
  • create-vale-rule piped check --json into jq, and said no single command could measure one rule. check --rule <id> shipped for check: run a single rule over the project to measure a new branch #379, so it now uses check --json --rule <id>.

Shell text in ci.md and hooks.md is content the agent writes into a CI job or hook file, not a command it runs, so it stays.

Versions. All 26 changed topics bump, so a cached recipe is refetched.

Tests. prompts.test.ts asserts the paragraph on every topic and anonymous variant, with and without the header, and its invocation-free form under mechanics: false.

OpenSpec change recipe-command-per-call (ADDED requirement on cli-agent) is archived in this PR. Single-PR delivery. Patch changeset included.

Fixes #532

The skill tells agents to run each Taskless command as its own shell call,
but an agent deep in a recipe copies the recipe's own examples, and that is
where chaining crept back in. Every standalone recipe now carries one shared
paragraph beneath its header, rendered from %(ONE_COMMAND_PER_CALL)s with the
resolved invocation (none under mechanics: false). The feedback-invite
fragment is exempt: it is appended under a recipe that already states it.

Two recipes chained a command themselves: check substituted $(git diff ...)
into the command line, and create-vale-rule piped check --json into jq. The
first now runs git diff separately; the second uses check --json --rule <id>,
which shipped for #379. Topic versions bump so cached recipes are refetched.
@theCodeDrift

Copy link
Copy Markdown
Member Author

@claude /review

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @theCodeDrift's task in 3m 18s —— View job


Review complete

Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json contained no review threads, no review summaries, and no top-level comments beyond the trigger comment itself, so this is the first review of this PR; there is nothing previously-raised to treat as addressed or still-open.

  • Read prior review data (empty — first review)
  • Read full PR diff (gh pr diff)
  • Read key source files: recipes.ts, check.md, create-vale-rule.md, ci.md, hooks.md, prompts.test.ts, survey/invite.ts, survey/constants.ts, spec/proposal, package version
  • Review for correctness, security, performance, test adequacy, clarity
  • Post findings

Assessment: solid, mechanical change — no correctness bugs found.

What I verified:

  • ONE_COMMAND_PER_CALL is wired correctly in buildVariables (recipes.ts:657-659), resolving undefined under mechanics: false exactly like the existing NAME_DESTINATION/FIXTURE_SOURCES pattern.
  • The paragraph placement (header blank-line, then paragraph, then blank line, then body) interacts correctly with stripHeader: since stripHeader only drops up to the first \n\n, and that now falls right after the header line (before the paragraph), header: false correctly keeps the rule paragraph — exactly as the proposal and spec require. Traced this by hand against addDirective/stripHeader/renderTemplate, and it holds.
  • All 26 non-fragment recipe files in packages/cli/src/agent/ got the placeholder; feedback-invite.md (the one exempted file) is untouched, and is confirmed genuinely exempt by reading survey/invite.ts (withSurveyInvite appends it header: false beneath the serving recipe) and survey/constants.ts (SURVEYED_TOPICS = create-sg-rule, create-vale-rule, create-remote-rule, all three of which do carry the paragraph).
  • check --json --rule <id> is real: confirmed the flag exists in commands/check.ts (VALUE_FLAGS = ["--timeout", "--rule"], repeatable, documented), so create-vale-rule.md's rewrite isn't describing vaporware.
  • The rewritten check.md git-diff example reads correctly in context (the src/deleted.ts placeholder usefully illustrates the "skipped path" behavior described in the very next sentence).
  • ci.md/hooks.md keep their chained shell examples ($(git diff ...), FILES=$(...)), and on inspection these are genuinely content written into a generated CI/hook file or describing that generated file's shape, not commands the agent itself runs — consistent with the PR's stated carve-out.
  • Topic-version bumps are consistent everywhere they're asserted in tests (onboard.test.ts → v9, report.test.ts → v7); no other hardcoded topic-version assertion was left stale.
  • The spec delta is ADDED Requirements only (no MODIFIED block), so the "a MODIFIED delta replaces the whole requirement" trap from CLAUDE.md doesn't apply here.
  • Version bump is patch in a 0.y.z package — correct per this repo's semver convention for added surface pre-1.0.
  • prompts.test.ts's new test iterates canonicalRecipeTopics() dynamically rather than a hardcoded list, so it's regression-proof against a future recipe that forgets the paragraph.

One inline nit posted (non-blocking):

  • packages/cli/src/prompts/recipes.ts:576 — the JSDoc says the wording "cannot drift between 27 files," but only 26 files literally embed the placeholder (feedback-invite.md is exempt and carries no copy of its own). Doc-comment precision only, no behavior impact.

Minor, non-blocking observation (not filed inline): the new "names no invocation" test (prompts.test.ts) exercises canonicalRecipeTopics() under mechanics: false but doesn't also exercise the anonymous: true variant (i.e., improve-rule.anonymous.md under mechanics: false isn't directly asserted). The file was edited identically to its canonical counterpart, so this is very unlikely to be hiding a bug — just a small coverage gap if you want full symmetry with the other new test.

* an `echo ======` separator as equals expansion and drops the rest of the
* line, and auto mode refuses a line that wraps the download-and-run command
* in glue it cannot scope (taskless/cli#532). One template, so the wording
* cannot drift between 27 files.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[New] Nit: "cannot drift between 27 files" overstates it slightly. The directory holds 27 .md files, but feedback-invite.md is exempt and never carries its own copy of this paragraph (it's appended, header-less, beneath a recipe that already states the rule). Only 26 files literally embed %(ONE_COMMAND_PER_CALL)s. Not a functional issue, just a doc-comment precision nit.

@theCodeDrift
theCodeDrift merged commit cb82a6e into main Oct 18, 2026
8 checks passed
@theCodeDrift
theCodeDrift deleted the fix/recipe-one-command-per-call branch October 18, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent recipes: state the "one Taskless command per call" rule in every recipe header

1 participant