Skip to content

Commit 76d58aa

Browse files
committed
fix(prompts): reduce recipe citations to topic names when mechanics are off
route's final step, failure fallback and See Also named a recipe as a command to fetch, so a mechanics: false render still carried CLI invocations. Step 8 is now a whole-step substitution that names the destination by topic, and citations go through %(RECIPE_FETCH)s, which renders nothing with mechanics off. The default render is byte-identical.
1 parent 004d7c4 commit 76d58aa

4 files changed

Lines changed: 82 additions & 17 deletions

File tree

‎.changeset/prompts-host-invocation-marker.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
---
44

55
`@taskless/cli/prompts` now renders the `<taskless-cli>` marker when the caller passes no `invocation`, on every build. A nightly used to substitute its own `npx @taskless/cli-nightly@<version>` into the recipe body, so `getPrompt(topic, { header: false })` still carried a CLI version and the rendered text changed on every nightly. Pass `invocation` to name a launcher of your own. `taskless agent <topic>` is unchanged and still names the build it was run from.
6+
7+
`mechanics: false` now also covers the passages that name a recipe as a command to fetch. In `route`, the final step names the destination by its topic name (`create-vale-rule`) rather than by its fetch command, and the failure fallback and `See Also` cite topic names, so the render carries no CLI invocation at all. The default rendering is unchanged.

‎packages/cli/src/agent/route.md‎

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,7 @@ answered together.
241241
Not logged in, no GitHub owner, or not locally expressible are not
242242
choices. Do not pose them as one.
243243

244-
8. **Name the command.** Finish by telling the user, or running, the
245-
exact fetch for the destination you chose:
246-
```
247-
%(TASKLESS_CLI)s agent create-vale-rule
248-
```
249-
A destination that is not a runnable command is a category, and a
250-
category is not an answer.
244+
8. %(NAME_DESTINATION)s
251245

252246
## When two destinations both fit
253247

@@ -270,7 +264,7 @@ fails against the user's cases:
270264
- State that generating via the service uses a generation and requires
271265
login.
272266
- Call the service only after the user confirms. On yes, fetch
273-
`%(TASKLESS_CLI)s agent create-remote-rule`.
267+
`%(RECIPE_FETCH)screate-remote-rule`.
274268

275269
Never fall through silently from a failed local attempt to a service
276270
call. A developer who watches a local attempt fail reads it as a
@@ -304,9 +298,9 @@ will work.
304298

305299
## See Also
306300

307-
- `%(TASKLESS_CLI)s agent create-legacy-rule`: author in a linter the repo already uses
308-
- `%(TASKLESS_CLI)s agent create-sg-rule`: author a local ast-grep rule (no login)
309-
- `%(TASKLESS_CLI)s agent create-vale-rule`: author a local Vale rule (no login)
310-
- `%(TASKLESS_CLI)s agent create-runtime-rule`: the runtime tier, logged out
311-
- `%(TASKLESS_CLI)s agent create-remote-rule`: generate via the service (login)
312-
- `%(TASKLESS_CLI)s agent check`: run every engine over the repo
301+
- `%(RECIPE_FETCH)screate-legacy-rule`: author in a linter the repo already uses
302+
- `%(RECIPE_FETCH)screate-sg-rule`: author a local ast-grep rule (no login)
303+
- `%(RECIPE_FETCH)screate-vale-rule`: author a local Vale rule (no login)
304+
- `%(RECIPE_FETCH)screate-runtime-rule`: the runtime tier, logged out
305+
- `%(RECIPE_FETCH)screate-remote-rule`: generate via the service (login)
306+
- `%(RECIPE_FETCH)scheck`: run every engine over the repo

‎packages/cli/src/prompts/recipes.ts‎

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,13 @@ export interface RecipeOptions {
191191
*/
192192
invocation?: string;
193193
/**
194-
* Render the steps that gather evidence by running this CLI.
194+
* Render the steps that gather evidence by running this CLI, and the
195+
* passages that name a recipe as a command to fetch.
195196
*
196-
* `false` replaces them with a statement of what the caller must supply
197-
* instead. It exists because `invocation` cannot do this job: that option
197+
* `false` replaces the evidence steps with a statement of what the caller
198+
* must supply instead, and reduces a recipe citation to its topic name: the
199+
* route's final step names the destination rather than its fetch command,
200+
* and `See Also` lists topics rather than invocations. It exists because `invocation` cannot do this job: that option
198201
* substitutes the BINARY NAME inside a command, so a consumer with no CLI
199202
* setting it to a phrase renders `Run: <no CLI available> detect --json` — a
200203
* malformed instruction rather than a clean absence, and worse than either
@@ -296,6 +299,33 @@ function runBlock(
296299
return `Run:\n \`\`\`\n ${invocation} ${command}\n \`\`\`\n ${connective}`;
297300
}
298301

302+
/**
303+
* The route recipe's final step, as a whole numbered step including its title.
304+
*
305+
* The title is inside the substitution for the reason {@link hostToolsStep}
306+
* gives: "Name the command" is the part that is wrong for a caller with no
307+
* terminal, where the answer is the destination's topic name and a command
308+
* would be one more thing to strip back out (taskless/cli#469).
309+
*/
310+
function nameDestinationStep(invocation: string | undefined): string {
311+
if (invocation === undefined) {
312+
return `**Name the destination.** Finish with the topic name of the
313+
recipe that authors this rule:
314+
\`\`\`
315+
create-vale-rule
316+
\`\`\`
317+
A destination that is not one of the topic names above is a category,
318+
and a category is not an answer.`;
319+
}
320+
return `**Name the command.** Finish by telling the user, or running, the
321+
exact fetch for the destination you chose:
322+
\`\`\`
323+
${invocation} agent create-vale-rule
324+
\`\`\`
325+
A destination that is not a runnable command is a category, and a
326+
category is not an answer.`;
327+
}
328+
299329
/**
300330
* Indent every line after the first, so a multi-line block substituted into an
301331
* indented `%(KEY)s` is still the markdown unit it replaces.
@@ -493,6 +523,18 @@ export function buildVariables(
493523
options.mechanics === false
494524
? SUPPLIED_LOGIN
495525
: runBlock(resolveInvocation(options), "info --json", "and note"),
526+
// A recipe named as something to fetch. Written against the topic name in
527+
// the source (`%(RECIPE_FETCH)screate-sg-rule`), so with mechanics off the
528+
// citation reduces to the name itself rather than to a command whose
529+
// binary the caller does not have.
530+
RECIPE_FETCH:
531+
options.mechanics === false ? "" : `${resolveInvocation(options)} agent `,
532+
NAME_DESTINATION: indentBlock(
533+
nameDestinationStep(
534+
options.mechanics === false ? undefined : resolveInvocation(options)
535+
),
536+
" "
537+
),
496538
// Three steps, in descending order of how much the resolver actually
497539
// knows: the caller was told how the CLI was launched; the build is a
498540
// nightly/dev/self that knows what it is; nobody knows, so ask the agent.

‎packages/cli/test/prompts.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ describe("the CLI invocation variable", () => {
185185
"DETECT_EVIDENCE",
186186
"HOST_TOOLS",
187187
"LOGIN_EVIDENCE",
188+
"NAME_DESTINATION",
188189
"PACKAGE_MANAGER_DLX",
190+
"RECIPE_FETCH",
189191
"SOURCE_PR_REVIEW",
190192
"TASKLESS_CLI",
191193
"VALE_COMMENT_FORMATS",
@@ -382,6 +384,31 @@ describe("host mechanics suppression", () => {
382384
);
383385
});
384386

387+
it("names the destination by topic rather than by command", () => {
388+
// taskless/cli#469. Step 8, the failure fallback and See Also all named a
389+
// recipe as a command to fetch, so a caller with no terminal still got
390+
// invocations after asking for none. The citation reduces to the topic
391+
// name, which is the answer the step asks for.
392+
const rendered = getPrompt("route", { mechanics: false });
393+
expect(rendered).toContain("**Name the destination.**");
394+
expect(rendered).not.toContain("**Name the command.**");
395+
expect(rendered).toMatch(/```\n {3}create-vale-rule\n {3}```/);
396+
expect(rendered).toContain("- `create-sg-rule`: author a local ast-grep");
397+
expect(rendered).toContain("On yes, fetch\n `create-remote-rule`.");
398+
expect(rendered).not.toContain("<taskless-cli>");
399+
expect(rendered).not.toContain(" agent create-");
400+
});
401+
402+
it("still renders every recipe citation as a command by default", () => {
403+
const rendered = getPrompt("route", { invocation: "npx @taskless/cli" });
404+
expect(rendered).toContain("**Name the command.**");
405+
expect(rendered).toContain(" npx @taskless/cli agent create-vale-rule\n");
406+
expect(rendered).toContain(
407+
"- `npx @taskless/cli agent create-sg-rule`: author a local ast-grep"
408+
);
409+
expect(rendered).toContain("`npx @taskless/cli agent create-remote-rule`.");
410+
});
411+
385412
it("keeps the evidence itself, since the criteria are stated in terms of it", () => {
386413
// Dropping the steps entirely would lose the inputs, not just the
387414
// commands: the prose below each step describes what the evidence holds.

0 commit comments

Comments
 (0)