From 9875360e135b522c270efdc9c0dfb20ec54c150a Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Tue, 18 Aug 2026 23:27:01 -0700 Subject: [PATCH 1/3] refactor(cli)!: complete the help -> agent rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user-facing command became `taskless agent ` in #102, but the rename stopped at the surface. This carries it through the rest: - Specs: `taskless help …` invocations and "help subcommand" prose across analytics, cli, cli-init, cli-knowledge-prompts, cli-onboard, skills, and skill-taskless now name `agent` and the single-token topics. The three references in cli-agent that assert the *removed* command no longer serves recipes are kept deliberately. - Capability: `openspec/specs/cli-help/` -> `openspec/specs/cli-agent/`, with the two live cross-references updated. Archived changes are untouched. - Source: `packages/cli/src/help/` -> `packages/cli/src/agent/` (20 recipes), including the `import.meta.glob` the build depends on. The three test files named for the old command are renamed to match. - Telemetry: `cli_help` -> `cli_agent`, a hard cut with no dual-emit. The comment recording the earlier "keep cli_help" decision is replaced, and the existing source-scan guard now also catches a `cli_help` capture site. Fixes #107 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jwc9FFroR3mTZ4hLiSkkX3 --- .changeset/rename-help-to-agent.md | 15 +++++ README.md | 8 +-- commands/tskl/tskl.md | 28 ++++----- openspec/specs/analytics/spec.md | 32 +++++----- .../specs/{cli-help => cli-agent}/spec.md | 61 ++++++++++--------- openspec/specs/cli-init/spec.md | 8 +-- openspec/specs/cli-knowledge-prompts/spec.md | 19 +++--- openspec/specs/cli-onboard/spec.md | 18 +++--- openspec/specs/cli-rules/spec.md | 2 +- openspec/specs/cli/spec.md | 14 ++--- openspec/specs/skill-taskless/spec.md | 4 +- openspec/specs/skills/spec.md | 4 +- packages/cli/src/{help => agent}/auth.txt | 0 packages/cli/src/{help => agent}/check.txt | 0 packages/cli/src/{help => agent}/ci.txt | 0 .../{help => agent}/create-legacy-rule.txt | 0 .../{help => agent}/create-remote-rule.txt | 0 .../{help => agent}/create-runtime-rule.txt | 0 .../src/{help => agent}/create-sg-rule.txt | 0 .../src/{help => agent}/create-vale-rule.txt | 0 .../cli/src/{help => agent}/delete-rule.txt | 0 packages/cli/src/{help => agent}/detect.txt | 0 .../improve-rule.anonymous.txt | 0 .../cli/src/{help => agent}/improve-rule.txt | 0 packages/cli/src/{help => agent}/info.txt | 0 packages/cli/src/{help => agent}/init.txt | 0 packages/cli/src/{help => agent}/onboard.txt | 0 packages/cli/src/{help => agent}/route.txt | 0 .../cli/src/{help => agent}/rule-meta.txt | 0 packages/cli/src/{help => agent}/rule.txt | 0 packages/cli/src/{help => agent}/update.txt | 0 .../cli/src/{help => agent}/verify-rule.txt | 0 packages/cli/src/commands/agent.ts | 22 +++---- packages/cli/src/prompts/index.ts | 4 +- packages/cli/src/prompts/recipes.ts | 30 ++++----- packages/cli/src/telemetry-run.ts | 2 +- ...sions.test.ts => agent-extensions.test.ts} | 25 ++++---- ...est.ts => agent-routing-telemetry.test.ts} | 10 +-- ...emetry.test.ts => agent-telemetry.test.ts} | 26 ++++---- packages/cli/test/prompts.test.ts | 4 +- .../cli/test/recipe-cross-references.test.ts | 12 ++-- skills/taskless/SKILL.md | 44 ++++++------- 42 files changed, 207 insertions(+), 185 deletions(-) create mode 100644 .changeset/rename-help-to-agent.md rename openspec/specs/{cli-help => cli-agent}/spec.md (75%) rename packages/cli/src/{help => agent}/auth.txt (100%) rename packages/cli/src/{help => agent}/check.txt (100%) rename packages/cli/src/{help => agent}/ci.txt (100%) rename packages/cli/src/{help => agent}/create-legacy-rule.txt (100%) rename packages/cli/src/{help => agent}/create-remote-rule.txt (100%) rename packages/cli/src/{help => agent}/create-runtime-rule.txt (100%) rename packages/cli/src/{help => agent}/create-sg-rule.txt (100%) rename packages/cli/src/{help => agent}/create-vale-rule.txt (100%) rename packages/cli/src/{help => agent}/delete-rule.txt (100%) rename packages/cli/src/{help => agent}/detect.txt (100%) rename packages/cli/src/{help => agent}/improve-rule.anonymous.txt (100%) rename packages/cli/src/{help => agent}/improve-rule.txt (100%) rename packages/cli/src/{help => agent}/info.txt (100%) rename packages/cli/src/{help => agent}/init.txt (100%) rename packages/cli/src/{help => agent}/onboard.txt (100%) rename packages/cli/src/{help => agent}/route.txt (100%) rename packages/cli/src/{help => agent}/rule-meta.txt (100%) rename packages/cli/src/{help => agent}/rule.txt (100%) rename packages/cli/src/{help => agent}/update.txt (100%) rename packages/cli/src/{help => agent}/verify-rule.txt (100%) rename packages/cli/test/{help-extensions.test.ts => agent-extensions.test.ts} (96%) rename packages/cli/test/{help-routing-telemetry.test.ts => agent-routing-telemetry.test.ts} (86%) rename packages/cli/test/{help-telemetry.test.ts => agent-telemetry.test.ts} (73%) diff --git a/.changeset/rename-help-to-agent.md b/.changeset/rename-help-to-agent.md new file mode 100644 index 00000000..97866ebd --- /dev/null +++ b/.changeset/rename-help-to-agent.md @@ -0,0 +1,15 @@ +--- +"@taskless/cli": patch +--- + +Complete the `help` → `agent` rename. The user-facing command was renamed in +0.10.0, but the internals kept the old name: the recipe directory moved from +`packages/cli/src/help/` to `packages/cli/src/agent/`, the `cli-help` OpenSpec +capability is now `cli-agent`, and the shipped skill and `/tskl` command no +longer tell agents to run the removed `npx @taskless/cli help ` (they +now use `agent`, with the single-token topic names — `route`, `improve-rule`, +`delete-rule`, `create-sg-rule`, and siblings). + +**Telemetry rename (hard cut, no dual-emit).** The `cli_help` event is renamed +to `cli_agent`. The `topic` property is unchanged. PostHog dashboards keyed on +`cli_help` will need updating — nothing is emitted under the old name. diff --git a/README.md b/README.md index 56c5dc18..42de46fd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ skills/ commands/ tskl/tskl.md # Single /tskl router command packages/ - cli/ # @taskless/cli — recipes live in cli/src/help/ + cli/ # @taskless/cli — recipes live in cli/src/agent/ scripts/ sync-skill-versions.ts # Syncs metadata.version to CLI version .claude-plugin/ # Claude Code Plugin Marketplace manifest @@ -20,7 +20,7 @@ scripts/ ## Skill -Starting in v0.7, Taskless ships a **single consolidated skill** (`taskless`) plus a single `/tskl` slash command. The skill body is a small router; per-task instructions live behind `npx @taskless/cli help ` and are fetched on demand. +Starting in v0.7, Taskless ships a **single consolidated skill** (`taskless`) plus a single `/tskl` slash command. The skill body is a small router; per-task instructions live behind `npx @taskless/cli agent ` and are fetched on demand. | Skill | Command | Description | | ---------- | ------------- | ------------------------------------------------------ | @@ -97,9 +97,9 @@ Tagging started at `v0.9.0`; earlier releases were not tagged. In v0.7+, new agent-facing instructions are added as **recipes**, not skills. To add a recipe: -1. Create `packages/cli/src/help/.txt` following the canonical template (Goal / Preconditions / Steps / Input schema / Errors / See Also). +1. Create `packages/cli/src/agent/.txt` following the canonical template (Goal / Preconditions / Steps / Input schema / Errors / See Also). 2. Use `{{CLI_VERSION}}` and `{{INPUT_SCHEMA}}` placeholders for runtime interpolation. -3. For topics with a substantively different local-only flow, add `.anonymous.txt`. The help command's variant lookup is automatic. +3. For topics with a substantively different local-only flow, add `.anonymous.txt`. The `agent` command's variant lookup is automatic. 4. Update the topic table in `skills/taskless/SKILL.md` and `commands/tskl/tskl.md` so agents can discover the new topic. ### Distribution channels diff --git a/commands/tskl/tskl.md b/commands/tskl/tskl.md index 44997d9e..2ed3c308 100644 --- a/commands/tskl/tskl.md +++ b/commands/tskl/tskl.md @@ -1,6 +1,6 @@ --- name: "Taskless" -description: Run any Taskless action — create/improve/delete a rule, run check, manage auth, or wire CI. Routes via `npx @taskless/cli help ` to fetch the canonical recipe and follow it. +description: Run any Taskless action — create/improve/delete a rule, run check, manage auth, or wire CI. Routes via `npx @taskless/cli agent ` to fetch the canonical recipe and follow it. category: Taskless argument-hint: tags: @@ -20,23 +20,23 @@ with Taskless before proceeding. Otherwise, follow the same flow as the `taskless` skill: 1. Identify the topic from `$ARGUMENTS` using the table below. -2. Fetch the canonical recipe with `npx @taskless/cli help ` (or - `npx @taskless/cli help --anonymous` if the user is offline or +2. Fetch the canonical recipe with `npx @taskless/cli agent ` (or + `npx @taskless/cli agent --anonymous` if the user is offline or explicitly asked for anonymous mode). 3. Follow the recipe step-by-step. The recipe is canonical for the currently-installed CLI version; do not improvise from prior knowledge. ## Topics -| User wants | Topic | -| -------------------------- | ------------------------------------- | -| Update Taskless skills | run `npx @taskless/cli update` | -| Create a new rule | `npx @taskless/cli help rule create` | -| Improve an existing rule | `npx @taskless/cli help rule improve` | -| Delete a rule | `npx @taskless/cli help rule delete` | -| Check code against rules | `npx @taskless/cli help check` | -| Log in, log out, or status | `npx @taskless/cli help auth` | -| Wire into CI | `npx @taskless/cli help ci` | - -If unsure, run `npx @taskless/cli help` (no args) for the topic +| User wants | Topic | +| -------------------------- | -------------------------------------- | +| Update Taskless skills | run `npx @taskless/cli update` | +| Create a new rule | `npx @taskless/cli agent route` | +| Improve an existing rule | `npx @taskless/cli agent improve-rule` | +| Delete a rule | `npx @taskless/cli agent delete-rule` | +| Check code against rules | `npx @taskless/cli agent check` | +| Log in, log out, or status | `npx @taskless/cli agent auth` | +| Wire into CI | `npx @taskless/cli agent ci` | + +If unsure, run `npx @taskless/cli agent` (no args) for the topic disambiguation table. diff --git a/openspec/specs/analytics/spec.md b/openspec/specs/analytics/spec.md index 02dc1f4e..65b378df 100644 --- a/openspec/specs/analytics/spec.md +++ b/openspec/specs/analytics/spec.md @@ -150,7 +150,7 @@ CLI events SHALL use the `cli_` prefix, with the taxonomy organized as a `warningCount`, `findings`) - `cli_error` — a single failure event with `command` and `code` (a stable `CLIErrorCode`) -- `cli_help` — fired when the help command serves a request, with a `topic` +- `cli_agent` — fired when the `agent` command serves a request, with a `topic` property (the served topic; the exact literal `"(index)"` when invoked with no topic; the attempted topic for an unknown request). This replaces the previous `help_index`, `help_`, and `help_unknown` events. @@ -168,16 +168,16 @@ Commands that carry no concrete state beyond the invocation (e.g. `info`, - **AND** SHALL receive a `cli_rule_created` event - **AND** SHALL NOT receive `cli_rule_create` or `cli_rule_create_completed` -#### Scenario: Help fetch emits cli_help with a topic +#### Scenario: Recipe fetch emits cli_agent with a topic -- **WHEN** an agent runs `taskless help rule create` -- **THEN** PostHog SHALL receive a `cli_help` event with `topic: "rule create"` -- **AND** SHALL NOT receive a `help_rule_create` event +- **WHEN** an agent runs `taskless agent create-sg-rule` +- **THEN** PostHog SHALL receive a `cli_agent` event with `topic: "create-sg-rule"` +- **AND** SHALL NOT receive a `help_create_sg_rule` event -#### Scenario: Help with no topic emits cli_help with the index marker +#### Scenario: Fetch with no topic emits cli_agent with the index marker -- **WHEN** an agent runs `taskless help` -- **THEN** PostHog SHALL receive a `cli_help` event with `topic: "(index)"` +- **WHEN** an agent runs `taskless agent` +- **THEN** PostHog SHALL receive a `cli_agent` event with `topic: "(index)"` - **AND** SHALL NOT receive a `help_index` event #### Scenario: A command failure emits cli_error @@ -196,24 +196,24 @@ Commands that carry no concrete state beyond the invocation (e.g. `info`, The taxonomy SHALL keep wrong-topic re-routing derivable as a funnel signal from the new events: -- A `cli_help { topic: A }` event not followed by the concrete event for topic A +- A `cli_agent { topic: A }` event not followed by the concrete event for topic A (or by `cli_run` with the corresponding `command`), and then a subsequent - `cli_help { topic: B }`, indicates the agent fetched recipe A, did not act on + `cli_agent { topic: B }`, indicates the agent fetched recipe A, did not act on it, and re-routed to topic B. -- A `cli_help` index-marker event followed by a `cli_help { topic }` event +- A `cli_agent` index-marker event followed by a `cli_agent { topic }` event indicates the agent consulted the index before picking a topic (baseline). -- A `cli_help { topic }` event with no subsequent acting `cli_run` and no further - `cli_help` event indicates the agent abandoned the action. +- A `cli_agent { topic }` event with no subsequent acting `cli_run` and no further + `cli_agent` event indicates the agent abandoned the action. No additional events SHALL be added to capture this signal directly — it is -derivable from the `cli_help` / `cli_run` sequence. Dashboards SHOULD surface +derivable from the `cli_agent` / `cli_run` sequence. Dashboards SHOULD surface re-routing rates per topic. #### Scenario: Funnel data supports wrong-topic detection - **WHEN** dashboards are constructed in PostHog -- **THEN** the `cli_help` (with `topic`) and `cli_run` (with `command`) events - SHALL be sufficient to compute "rate of `cli_help { topic }` not followed by a +- **THEN** the `cli_agent` (with `topic`) and `cli_run` (with `command`) events + SHALL be sufficient to compute "rate of `cli_agent { topic }` not followed by a corresponding acting `cli_run` within N minutes" ### Requirement: Telemetry failures are silent diff --git a/openspec/specs/cli-help/spec.md b/openspec/specs/cli-agent/spec.md similarity index 75% rename from openspec/specs/cli-help/spec.md rename to openspec/specs/cli-agent/spec.md index 46c464c3..cc90b9d1 100644 --- a/openspec/specs/cli-help/spec.md +++ b/openspec/specs/cli-agent/spec.md @@ -1,12 +1,14 @@ -# CLI Help +# CLI Agent ## Purpose -TBD — Defines the help subcommand for the `@taskless/cli` package, including help text display, embedding, and formatting. +Defines the `agent` subcommand for the `@taskless/cli` package, including recipe display, embedding, and formatting. + ## Requirements -### Requirement: Help subcommand displays rich help text for commands -The CLI SHALL support an `agent` subcommand that accepts at most one positional argument identifying a topic AND an optional `--anonymous` boolean flag. Topics SHALL be addressed by a single token; the subcommand SHALL NOT join multiple positionals into a topic key. When a topic is provided, the subcommand SHALL look up a matching help text file embedded at build time using the following resolution order: +### Requirement: Agent subcommand serves recipes for commands + +The CLI SHALL support an `agent` subcommand that accepts at most one positional argument identifying a topic AND an optional `--anonymous` boolean flag. Topics SHALL be addressed by a single token; the subcommand SHALL NOT join multiple positionals into a topic key. When a topic is provided, the subcommand SHALL look up a matching recipe file embedded at build time using the following resolution order: 1. If `--anonymous` is set AND `.anonymous.txt` exists in the embedded map, return that file. 2. Otherwise, return `.txt`. @@ -37,23 +39,23 @@ The subcommand is named for its reader. It serves agents fetching a procedure, n - **WHEN** a user runs `taskless help check` - **THEN** the CLI SHALL NOT print recipe text for `check` -### Requirement: Help text files are embedded at build time +### Requirement: Recipe files are embedded at build time -Help text files SHALL be located at `packages/cli/src/help/` as plain `.txt` files. The Vite build SHALL embed these files into the CLI bundle via `import.meta.glob` with raw imports. A help file SHALL exist for every registered command and subcommand. +Recipe files SHALL be located at `packages/cli/src/agent/` as plain `.txt` files. The Vite build SHALL embed these files into the CLI bundle via `import.meta.glob` with raw imports. A recipe file SHALL exist for every registered command and subcommand. -#### Scenario: Help files are available without filesystem access +#### Scenario: Recipe files are available without filesystem access -- **WHEN** the CLI is invoked via `npx @taskless/cli help check` -- **THEN** the help text SHALL be served from the embedded bundle without reading the filesystem +- **WHEN** the CLI is invoked via `npx @taskless/cli agent check` +- **THEN** the recipe SHALL be served from the embedded bundle without reading the filesystem -#### Scenario: Help file naming convention +#### Scenario: Recipe file naming convention -- **WHEN** a help file is created for the `rule create` subcommand -- **THEN** the file SHALL be named `rule-create.txt` in `packages/cli/src/help/` +- **WHEN** a recipe file is created for the `rule create` subcommand +- **THEN** the file SHALL be named `rule-create.txt` in `packages/cli/src/agent/` -### Requirement: Help text files follow a consistent format +### Requirement: Recipe files follow a consistent format -Every help text file at `packages/cli/src/help/.txt` SHALL follow the canonical recipe template: a single-line header `# Topic: (CLI v%(CLI_VERSION)s / topic v)`, followed by `## Goal`, `## Preconditions`, `## Steps`, optional `## Input schema` (for recipes that take `--from`), `## Errors`, and `## See Also` sections in that order. Recipe templates SHALL use sprintf-js `%(KEY)s` named-argument placeholders for all substitution. The header SHALL embed `%(CLI_VERSION)s` for the CLI version. Topics that document a `--from` input SHALL embed `%(INPUT_SCHEMA)s` inside the `## Input schema` fenced code block. The topic version integer in the header SHALL be a literal value maintained by the recipe author and bumped when the recipe changes meaningfully. +Every recipe file at `packages/cli/src/agent/.txt` SHALL follow the canonical recipe template: a single-line header `# Topic: (CLI v%(CLI_VERSION)s / topic v)`, followed by `## Goal`, `## Preconditions`, `## Steps`, optional `## Input schema` (for recipes that take `--from`), `## Errors`, and `## See Also` sections in that order. Recipe templates SHALL use sprintf-js `%(KEY)s` named-argument placeholders for all substitution. The header SHALL embed `%(CLI_VERSION)s` for the CLI version. Topics that document a `--from` input SHALL embed `%(INPUT_SCHEMA)s` inside the `## Input schema` fenced code block. The topic version integer in the header SHALL be a literal value maintained by the recipe author and bumped when the recipe changes meaningfully. #### Scenario: Recipe contains all template sections @@ -100,13 +102,13 @@ Recipe authors SHALL escape any literal `%` character in recipe content as `%%` #### Scenario: No legacy placeholder syntax remains in recipes -- **WHEN** any `.txt` file under `packages/cli/src/help/` is read +- **WHEN** any `.txt` file under `packages/cli/src/agent/` is read - **THEN** it SHALL NOT contain a `{{KEY}}` mustache-style placeholder - **AND** all substitution SHALL be expressed as `%(KEY)s` sprintf-js named arguments -### Requirement: onboard topic is registered in the help index +### Requirement: onboard topic is registered in the agent index -A help topic `onboard` SHALL be registered. The CLI SHALL embed `packages/cli/src/help/onboard.txt` at build time via the existing `import.meta.glob` mechanism. `taskless agent onboard` SHALL print the contents of `onboard.txt`. The topic SHALL appear in the output of `taskless agent` (the index) with a one-line summary describing it as the post-install rule-discovery flow. +An agent topic `onboard` SHALL be registered. The CLI SHALL embed `packages/cli/src/agent/onboard.txt` at build time via the existing `import.meta.glob` mechanism. `taskless agent onboard` SHALL print the contents of `onboard.txt`. The topic SHALL appear in the output of `taskless agent` (the index) with a one-line summary describing it as the post-install rule-discovery flow. #### Scenario: The onboard topic returns the recipe @@ -120,20 +122,20 @@ A help topic `onboard` SHALL be registered. The CLI SHALL embed `packages/cli/sr - **THEN** the topic index SHALL include a row for `onboard` - **AND** the row SHALL describe it as the post-install rule-discovery flow -### Requirement: help_onboard intent telemetry +### Requirement: onboard intent telemetry -Fetching the `onboard` topic SHALL emit the command's single intent event, `cli_help`, carrying `onboard` as its `topic` property. +Fetching the `onboard` topic SHALL emit the command's single intent event, `cli_agent`, carrying `onboard` as its `topic` property. Per-topic event names (`help_onboard` and siblings) are not emitted. One event with a topic property is filterable the same way and does not grow the event vocabulary every time a topic is added or renamed, which this change would otherwise have to do for every rename below. #### Scenario: Fetching onboard captures its topic - **WHEN** an agent runs `taskless agent onboard` -- **THEN** PostHog SHALL receive a `cli_help` event whose `topic` property is `onboard` +- **THEN** PostHog SHALL receive a `cli_agent` event whose `topic` property is `onboard` -### Requirement: Routing topics are registered in the help system +### Requirement: Routing topics are registered in the agent system -The help system SHALL register `route` and each `create-*-rule` recipe as embedded topics, retrievable via `taskless agent ` and listed in the topic index, consistent with the existing topic embedding and format requirements. +The agent system SHALL register `route` and each `create-*-rule` recipe as embedded topics, retrievable via `taskless agent ` and listed in the topic index, consistent with the existing topic embedding and format requirements. `existing`, `static`, and `remote` are no longer topics. `route` applies the criterion they carried and names a concrete destination, so an agent reaches an authoring recipe in one fetch. @@ -156,16 +158,16 @@ The help system SHALL register `route` and each `create-*-rule` recipe as embedd ### Requirement: Routing topics emit intent telemetry -Fetching a routing recipe SHALL emit the command's single intent event, `cli_help`, carrying the served topic as its `topic` property. +Fetching a routing recipe SHALL emit the command's single intent event, `cli_agent`, carrying the served topic as its `topic` property. #### Scenario: Intent is captured for routing recipes - **WHEN** the agent fetches `route` or any `create-*-rule` topic -- **THEN** the command SHALL capture a `cli_help` event whose `topic` property is that topic name +- **THEN** the command SHALL capture a `cli_agent` event whose `topic` property is that topic name ### Requirement: Anonymous variant lookup uses a compile-time map -The help command SHALL construct, at build time, a Set of topic names that have a corresponding `.anonymous.txt` file. Lookup at runtime SHALL be O(1). The Set SHALL be derived from `import.meta.glob` matching `*.anonymous.txt` in the help directory. +The `agent` command SHALL construct, at build time, a Set of topic names that have a corresponding `.anonymous.txt` file. Lookup at runtime SHALL be O(1). The Set SHALL be derived from `import.meta.glob` matching `*.anonymous.txt` in the recipe directory. #### Scenario: Topics with variants are detected at build time @@ -195,9 +197,9 @@ For every recipe topic that documents a CLI command accepting `--from `, t - **WHEN** a user runs `taskless agent improve-rule` - **THEN** the output SHALL contain an `## Input schema` section with the rule-improve JSON Schema -### Requirement: Help command emits intent telemetry +### Requirement: Agent command emits intent telemetry -The `agent` command SHALL emit one PostHog event, `cli_help`, on every invocation, carrying a `topic` property: +The `agent` command SHALL emit one PostHog event, `cli_agent`, on every invocation, carrying a `topic` property: - the served topic when a positional resolves to a known topic - the attempted topic string when it resolves to none @@ -207,12 +209,12 @@ The `agent` command SHALL emit one PostHog event, `cli_help`, on every invocatio #### Scenario: Topic fetch captures the topic - **WHEN** an agent runs `taskless agent create-sg-rule` -- **THEN** PostHog SHALL receive a `cli_help` event whose `topic` property is `create-sg-rule` +- **THEN** PostHog SHALL receive a `cli_agent` event whose `topic` property is `create-sg-rule` #### Scenario: Index fetch captures the index - **WHEN** an agent runs `taskless agent` (no args) -- **THEN** PostHog SHALL receive a `cli_help` event whose `topic` property is `(index)` +- **THEN** PostHog SHALL receive a `cli_agent` event whose `topic` property is `(index)` ### Requirement: Routing recipes name a destination, not a second decision @@ -240,4 +242,3 @@ No embedded recipe SHALL contain the string `taskless help`. Recipes cross-refer - **WHEN** the embedded recipe set is inspected - **THEN** no recipe SHALL contain `taskless help` - diff --git a/openspec/specs/cli-init/spec.md b/openspec/specs/cli-init/spec.md index a853c4ea..62b05a1d 100644 --- a/openspec/specs/cli-init/spec.md +++ b/openspec/specs/cli-init/spec.md @@ -349,18 +349,18 @@ The `init` subcommand SHALL use the resolved working directory from the global ` ### Requirement: Bare taskless invocation launches the init wizard -The CLI entry point SHALL delegate to `init` when invoked with no positional subcommand AND a TTY is attached. When stdout is NOT a TTY, bare `taskless` SHALL print a non-interactive preamble explaining the context, followed by the help index (instead of attempting the wizard or printing only top-level help). +The CLI entry point SHALL delegate to `init` when invoked with no positional subcommand AND a TTY is attached. When stdout is NOT a TTY, bare `taskless` SHALL print a non-interactive preamble explaining the context, followed by the agent topic index (instead of attempting the wizard or printing only top-level help). #### Scenario: Bare taskless in a TTY launches the wizard - **WHEN** a user runs `taskless` with no subcommand and stdout is a TTY - **THEN** the CLI SHALL behave as if `taskless init` were invoked -#### Scenario: Bare taskless without a TTY prints preamble + help index +#### Scenario: Bare taskless without a TTY prints preamble + agent topic index - **WHEN** `taskless` is invoked with no subcommand and stdout is not a TTY -- **THEN** the CLI SHALL print a short preamble noting the non-interactive context (e.g. "For interactive install, run from a terminal. For agent recipes, use: taskless help") -- **AND** SHALL then print the help index (same content as `taskless help`) +- **THEN** the CLI SHALL print a short preamble noting the non-interactive context (e.g. "For interactive install, run from a terminal. For agent recipes, run `taskless agent` (no args) for the topic index.") +- **AND** SHALL then print the agent topic index (same content as `taskless agent`) - **AND** SHALL NOT launch the wizard - **AND** SHALL NOT silently install diff --git a/openspec/specs/cli-knowledge-prompts/spec.md b/openspec/specs/cli-knowledge-prompts/spec.md index 335cf450..bef5076b 100644 --- a/openspec/specs/cli-knowledge-prompts/spec.md +++ b/openspec/specs/cli-knowledge-prompts/spec.md @@ -2,9 +2,9 @@ ## Purpose -The CLI's `help/*.txt` recipes are the authoritative guidance Taskless gives an +The CLI's `agent/*.txt` recipes are the authoritative guidance Taskless gives an agent about authoring and operating rules. Until now the only way to read them -was to run `taskless help`, which puts them out of reach of anything that cannot +was to run `taskless agent`, which puts them out of reach of anything that cannot spawn the CLI — notably the service-side generator, which needs the same text to brief a model. @@ -15,10 +15,12 @@ cannot drift into giving different guidance. The export carries no CLI runtime, so a Worker can import it without dragging in the command tree, and topic membership is an explicit hand-maintained list so a new recipe file cannot silently become public API. + ## Requirements + ### Requirement: The package exposes knowledge prompts via a dedicated import -The package SHALL expose its knowledge prompts (the `help/*.txt` recipes) through a subpath export `@taskless/cli/prompts`, built into `dist` and listed in `files`, so consumers can import them without invoking the CLI. +The package SHALL expose its knowledge prompts (the `agent/*.txt` recipes) through a subpath export `@taskless/cli/prompts`, built into `dist` and listed in `files`, so consumers can import them without invoking the CLI. #### Scenario: Importing a prompt by topic @@ -39,11 +41,11 @@ The export SHALL provide a `PromptTopic` union of the available canonical topics - **WHEN** a consumer calls `PROMPTS.static()` with no arguments - **THEN** it returns the same string as `getPrompt("static")` -### Requirement: The export and the help command share one source and one renderer +### Requirement: The export and the agent command share one source and one renderer -The prompt export SHALL be sourced from the same embedded `help/*.txt` content that `commands/help.ts` serves, and SHALL render it through the same render path, with no duplicated embedding and no duplicated interpolation logic. Both surfaces SHALL return identical text for the same topic and equivalent options. +The prompt export SHALL be sourced from the same embedded `agent/*.txt` content that `commands/agent.ts` serves, and SHALL render it through the same render path, with no duplicated embedding and no duplicated interpolation logic. Both surfaces SHALL return identical text for the same topic and equivalent options. -#### Scenario: Parity between import and help command +#### Scenario: Parity between import and agent command - **WHEN** the `help` command renders topic `T` and a consumer calls `getPrompt("T")` - **THEN** the two texts are identical, including under a non-prod build target where the CLI invocation is rewritten @@ -126,9 +128,9 @@ What the requirement actually protects is not the version number but the notice. ### Requirement: Topic membership is explicit and verified against the recipe files -`PromptTopic` SHALL be derived from an explicit, hand-maintained list of exported topics rather than inferred from whatever recipe files are present, so that adding or removing a `help/*.txt` file cannot silently change the public API. Recipe files deliberately withheld from the export SHALL be recorded in an explicit internal-topics list. +`PromptTopic` SHALL be derived from an explicit, hand-maintained list of exported topics rather than inferred from whatever recipe files are present, so that adding or removing an `agent/*.txt` file cannot silently change the public API. Recipe files deliberately withheld from the export SHALL be recorded in an explicit internal-topics list. -An automated check SHALL assert that the set of canonical `help/*.txt` topics on disk is exactly the union of the exported topics and the internal-topics list, failing when the two diverge in either direction. +An automated check SHALL assert that the set of canonical `agent/*.txt` topics on disk is exactly the union of the exported topics and the internal-topics list, failing when the two diverge in either direction. #### Scenario: A new recipe file is added without being classified @@ -162,4 +164,3 @@ A consumer that can decide a rule belongs to an engine must be able to reach the - **WHEN** a consumer imports `TOPICS` - **THEN** it SHALL NOT contain `static` or `engine-selection`, neither of which names a recipe any more - diff --git a/openspec/specs/cli-onboard/spec.md b/openspec/specs/cli-onboard/spec.md index bfefd49f..76fccd2f 100644 --- a/openspec/specs/cli-onboard/spec.md +++ b/openspec/specs/cli-onboard/spec.md @@ -8,7 +8,7 @@ This capability covers: - **Subcommand surface**: three modes via flag combinations — default prints the recipe (refused when already complete), `--force` re-runs regardless of state, `--mark-complete` writes `install.onboarded: true`. `--force` and `--mark-complete` are mutually exclusive. - **Manifest gating and writes**: the optional 3-state `install.onboarded` field on `.taskless/taskless.json` (absent / `false` / `true`) is the single source of truth for whether onboarding is complete. Only this subcommand writes the field, and only via `--mark-complete`. The agent invokes `--mark-complete` only after explicit user confirmation per the recipe. -- **Recipe embedding**: the recipe lives at `packages/cli/src/help/onboard.txt`, embedded into the CLI bundle at build time and rendered via the same sprintf-js path the help command uses (`taskless help onboard` returns the same content as `taskless onboard --force`). +- **Recipe embedding**: the recipe lives at `packages/cli/src/agent/onboard.txt`, embedded into the CLI bundle at build time and rendered via the same sprintf-js path the `agent` command uses (`taskless agent onboard` returns the same content as `taskless onboard --force`). - **Telemetry**: emits `cli_onboard_recipe` (with a `forced` property), `cli_onboard_already_done`, and `cli_onboard_marked_complete` PostHog events on the appropriate code paths. ## Requirements @@ -46,7 +46,7 @@ The CLI SHALL support a `taskless onboard` subcommand. The subcommand SHALL acce ### Requirement: Onboard gates on the onboarded manifest field -When invoked without `--mark-complete`, the `taskless onboard` subcommand SHALL read `.taskless/taskless.json` and inspect the optional `install.onboarded` field. If the field equals `true` AND `--force` is not set, the subcommand SHALL print a short message stating the user is already onboarded and that `--force` re-runs the recipe, then exit with code 0 without printing the recipe. If the field is absent, `false`, or `--force` is set, the subcommand SHALL print the recipe content embedded from `packages/cli/src/help/onboard.txt` to stdout and exit with code 0. +When invoked without `--mark-complete`, the `taskless onboard` subcommand SHALL read `.taskless/taskless.json` and inspect the optional `install.onboarded` field. If the field equals `true` AND `--force` is not set, the subcommand SHALL print a short message stating the user is already onboarded and that `--force` re-runs the recipe, then exit with code 0 without printing the recipe. If the field is absent, `false`, or `--force` is set, the subcommand SHALL print the recipe content embedded from `packages/cli/src/agent/onboard.txt` to stdout and exit with code 0. #### Scenario: Already onboarded without --force prints a short notice @@ -113,23 +113,23 @@ When invoked with `--mark-complete`, the `taskless onboard` subcommand SHALL wri ### Requirement: Onboard recipe is embedded from help/onboard.txt -The CLI build SHALL embed `packages/cli/src/help/onboard.txt` into the bundle via the same `import.meta.glob` mechanism used for other help topics. The `taskless onboard` subcommand SHALL read the recipe content from the embedded bundle, not from the filesystem at runtime. The embedded recipe SHALL be the same content returned by `taskless help onboard`. +The CLI build SHALL embed `packages/cli/src/agent/onboard.txt` into the bundle via the same `import.meta.glob` mechanism used for other agent topics. The `taskless onboard` subcommand SHALL read the recipe content from the embedded bundle, not from the filesystem at runtime. The embedded recipe SHALL be the same content returned by `taskless agent onboard`. #### Scenario: Recipe is available without filesystem access - **WHEN** a user runs `taskless onboard` via `npx @taskless/cli` - **THEN** the recipe content SHALL be served from the embedded bundle -- **AND** SHALL NOT require any filesystem reads under `packages/cli/src/help/` +- **AND** SHALL NOT require any filesystem reads under `packages/cli/src/agent/` -#### Scenario: Onboard and help return the same recipe +#### Scenario: Onboard and agent return the same recipe - **WHEN** a user runs `taskless onboard --force` (recipe path) -- **AND** a user runs `taskless help onboard` +- **AND** a user runs `taskless agent onboard` - **THEN** the printed recipe content SHALL be identical between the two invocations ### Requirement: Onboard recipe follows the canonical recipe template and is conversational -The `onboard.txt` file SHALL follow the canonical recipe template defined in the `cli-help` capability (header with CLI version + topic version, `## Goal`, `## Preconditions`, `## Steps`, `## Errors`, `## See Also`). The `## Steps` section SHALL describe a conversational discovery flow rather than a fixed sequence. Specifically, the recipe SHALL instruct the agent to: +The `onboard.txt` file SHALL follow the canonical recipe template defined in the `cli-agent` capability (header with CLI version + topic version, `## Goal`, `## Preconditions`, `## Steps`, `## Errors`, `## See Also`). The `## Steps` section SHALL describe a conversational discovery flow rather than a fixed sequence. Specifically, the recipe SHALL instruct the agent to: 1. Read `.taskless/taskless.json` and respect the `install.onboarded` field. 2. Open the conversation with a short menu of known sources for rule candidates: codebase TODOs/FIXMEs (via ripgrep or built-in search), agent-memory files (CLAUDE.md, AGENTS.md, .cursorrules, etc.), recent PR review comments (when `gh` is available), and issue-tracker tickets (when a relevant MCP is detected). @@ -168,10 +168,10 @@ The recipe SHALL warn the agent against marking onboarding complete without expl - **THEN** it SHALL instruct the agent to ask for explicit user confirmation before invoking `npx @taskless/cli onboard --mark-complete` - **AND** SHALL warn that the agent must NOT mark onboarding complete without that confirmation -#### Scenario: Recipe references the rule create topic in See Also +#### Scenario: Recipe references the rule-authoring route topic in See Also - **WHEN** the `## See Also` section is read -- **THEN** it SHALL include a reference to `taskless help rule create` +- **THEN** it SHALL include a reference to `taskless agent route` ### Requirement: Onboard emits intent telemetry diff --git a/openspec/specs/cli-rules/spec.md b/openspec/specs/cli-rules/spec.md index c8623f24..79f8c626 100644 --- a/openspec/specs/cli-rules/spec.md +++ b/openspec/specs/cli-rules/spec.md @@ -170,7 +170,7 @@ The API calls for rule generation (`POST /cli/api/request` and `GET /cli/api/req ### Requirement: Rules improve has an agent recipe -`taskless agent improve-rule` SHALL return the recipe per `cli-help` requirements. The recipe file is `improve-rule.txt`, with an `improve-rule.anonymous.txt` variant for the local-only flow. +`taskless agent improve-rule` SHALL return the recipe per `cli-agent` requirements. The recipe file is `improve-rule.txt`, with an `improve-rule.anonymous.txt` variant for the local-only flow. #### Scenario: The recipe resolves by its single-token name diff --git a/openspec/specs/cli/spec.md b/openspec/specs/cli/spec.md index c5f233c6..03d435fb 100644 --- a/openspec/specs/cli/spec.md +++ b/openspec/specs/cli/spec.md @@ -31,7 +31,7 @@ The package SHALL declare a `bin` field in `package.json` pointing to the built ### Requirement: CLI builds with Vite -The CLI SHALL use Vite in library mode to produce a single bundled ESM output file. The build configuration SHALL live in `packages/cli/vite.config.ts`. The Vite build SHALL embed skills from `skills/`, commands from `commands/taskless/`, and help text files from `packages/cli/src/help/` via `import.meta.glob` with raw file imports. The build SHALL assert that every embedded skill's `metadata.version` matches the CLI's `package.json` version, failing with an error if any mismatch is detected. +The CLI SHALL use Vite in library mode to produce a single bundled ESM output file. The build configuration SHALL live in `packages/cli/vite.config.ts`. The Vite build SHALL embed skills from `skills/`, commands from `commands/taskless/`, and agent recipe files from `packages/cli/src/agent/` via `import.meta.glob` with raw file imports. The build SHALL assert that every embedded skill's `metadata.version` matches the CLI's `package.json` version, failing with an error if any mismatch is detected. #### Scenario: Build produces executable output @@ -48,10 +48,10 @@ The CLI SHALL use Vite in library mode to produce a single bundled ESM output fi - **WHEN** `pnpm build` is run in `packages/cli/` - **THEN** every `.md` file under `commands/taskless/` at the repo root SHALL be embedded in the output bundle -#### Scenario: Build embeds help text files +#### Scenario: Build embeds agent recipe files - **WHEN** `pnpm build` is run in `packages/cli/` -- **THEN** every `.txt` file under `packages/cli/src/help/` SHALL be embedded in the output bundle +- **THEN** every `.txt` file under `packages/cli/src/agent/` SHALL be embedded in the output bundle #### Scenario: Build fails on version mismatch @@ -69,12 +69,12 @@ The CLI SHALL have a `packages/cli/tsconfig.json` that extends `../../tsconfig.b ### Requirement: CLI stub entry point -The CLI entry point SHALL use citty to define a main command with subcommand support and a global `-d` (alias `--dir`) argument that sets the working directory, a global `--json` boolean argument for machine-readable output, and a global `--schema` boolean argument for printing JSON Schema definitions. When invoked with no arguments, the CLI SHALL display help text listing available subcommands. The CLI SHALL externalize Node.js built-in modules and bundle all other dependencies. The `check` subcommand, the `auth` subcommand group, the `rules` subcommand group, and the `help` subcommand SHALL be registered alongside existing subcommands. The `update-engine` subcommand SHALL NOT be registered. +The CLI entry point SHALL use citty to define a main command with subcommand support and a global `-d` (alias `--dir`) argument that sets the working directory, a global `--json` boolean argument for machine-readable output, and a global `--schema` boolean argument for printing JSON Schema definitions. When invoked with no arguments, the CLI SHALL display help text listing available subcommands. The CLI SHALL externalize Node.js built-in modules and bundle all other dependencies. The `check` subcommand, the `auth` subcommand group, the `rules` subcommand group, and the `agent` subcommand SHALL be registered alongside existing subcommands. The `update-engine` subcommand SHALL NOT be registered. #### Scenario: Running the CLI with no arguments shows help - **WHEN** the CLI is executed with no arguments -- **THEN** it SHALL print help text to stdout listing available subcommands including `help` +- **THEN** it SHALL print help text to stdout listing available subcommands including `agent` - **AND** the help text SHALL NOT list `update-engine` #### Scenario: Running the CLI with an unknown subcommand shows help @@ -151,8 +151,8 @@ The CLI entry point SHALL use citty to define a main command with subcommand sup #### Scenario: Help subcommand is registered -- **WHEN** a user runs `taskless help` -- **THEN** the CLI SHALL route to the help subcommand handler +- **WHEN** a user runs `taskless agent` +- **THEN** the CLI SHALL route to the `agent` subcommand handler ### Requirement: CLI manages .taskless/.gitignore diff --git a/openspec/specs/skill-taskless/spec.md b/openspec/specs/skill-taskless/spec.md index 651c3664..dd694bb9 100644 --- a/openspec/specs/skill-taskless/spec.md +++ b/openspec/specs/skill-taskless/spec.md @@ -84,8 +84,8 @@ The body SHALL be no more than 80 lines of markdown to keep the always-loaded su #### Scenario: Skill body lists available topics including onboard - **WHEN** the skill body is read by an agent -- **THEN** it SHALL include a table or list mapping user intents to the corresponding `tskl help ` invocations -- **AND** the table SHALL include a row for `onboard` mapped to `npx @taskless/cli help onboard` (or equivalent invocation of the onboard topic) +- **THEN** it SHALL include a table or list mapping user intents to the corresponding `tskl agent ` invocations +- **AND** the table SHALL include a row for `onboard` mapped to `npx @taskless/cli agent onboard` (or equivalent invocation of the onboard topic) #### Scenario: Skill body checks for .taskless directory diff --git a/openspec/specs/skills/spec.md b/openspec/specs/skills/spec.md index 894ae640..f798ea7d 100644 --- a/openspec/specs/skills/spec.md +++ b/openspec/specs/skills/spec.md @@ -10,7 +10,7 @@ Defines the structure, conventions, and distribution model for Taskless skills, The single skill SHALL be defined at `skills/taskless/SKILL.md` with YAML frontmatter (`name`, `description`, `metadata`) followed by markdown instructions. The `name` field SHALL be exactly `taskless` (no per-task prefix). The `metadata` field SHALL include `author`, `version`, and `commandName: tskl` keys. The `version` SHALL be used for staleness detection when the skill is installed into target repositories. -The skill body SHALL begin by instructing the agent that it does NOT have step-by-step instructions for any Taskless action and that recipes must be fetched via `npx @taskless/cli agent ` before proceeding. The body SHALL NOT contain inline step-by-step recipes for any individual task — those live in `packages/cli/src/help/.txt` files served by the help subcommand. +The skill body SHALL begin by instructing the agent that it does NOT have step-by-step instructions for any Taskless action and that recipes must be fetched via `npx @taskless/cli agent ` before proceeding. The body SHALL NOT contain inline step-by-step recipes for any individual task — those live in `packages/cli/src/agent/.txt` files served by the `agent` subcommand. #### Scenario: Skill directory contains valid SKILL.md @@ -20,7 +20,7 @@ The skill body SHALL begin by instructing the agent that it does NOT have step-b - **AND** the `metadata` field SHALL include `author: taskless`, `version` (string matching CLI package version), and `commandName: tskl` - **AND** the markdown body SHALL contain the router instructions described above (no per-task recipes inline) -#### Scenario: Skill body delegates to CLI help +#### Scenario: Skill body delegates to the CLI agent command - **WHEN** the skill body is read - **THEN** it SHALL instruct the agent to fetch the canonical recipe via `npx @taskless/cli agent ` before performing any Taskless action diff --git a/packages/cli/src/help/auth.txt b/packages/cli/src/agent/auth.txt similarity index 100% rename from packages/cli/src/help/auth.txt rename to packages/cli/src/agent/auth.txt diff --git a/packages/cli/src/help/check.txt b/packages/cli/src/agent/check.txt similarity index 100% rename from packages/cli/src/help/check.txt rename to packages/cli/src/agent/check.txt diff --git a/packages/cli/src/help/ci.txt b/packages/cli/src/agent/ci.txt similarity index 100% rename from packages/cli/src/help/ci.txt rename to packages/cli/src/agent/ci.txt diff --git a/packages/cli/src/help/create-legacy-rule.txt b/packages/cli/src/agent/create-legacy-rule.txt similarity index 100% rename from packages/cli/src/help/create-legacy-rule.txt rename to packages/cli/src/agent/create-legacy-rule.txt diff --git a/packages/cli/src/help/create-remote-rule.txt b/packages/cli/src/agent/create-remote-rule.txt similarity index 100% rename from packages/cli/src/help/create-remote-rule.txt rename to packages/cli/src/agent/create-remote-rule.txt diff --git a/packages/cli/src/help/create-runtime-rule.txt b/packages/cli/src/agent/create-runtime-rule.txt similarity index 100% rename from packages/cli/src/help/create-runtime-rule.txt rename to packages/cli/src/agent/create-runtime-rule.txt diff --git a/packages/cli/src/help/create-sg-rule.txt b/packages/cli/src/agent/create-sg-rule.txt similarity index 100% rename from packages/cli/src/help/create-sg-rule.txt rename to packages/cli/src/agent/create-sg-rule.txt diff --git a/packages/cli/src/help/create-vale-rule.txt b/packages/cli/src/agent/create-vale-rule.txt similarity index 100% rename from packages/cli/src/help/create-vale-rule.txt rename to packages/cli/src/agent/create-vale-rule.txt diff --git a/packages/cli/src/help/delete-rule.txt b/packages/cli/src/agent/delete-rule.txt similarity index 100% rename from packages/cli/src/help/delete-rule.txt rename to packages/cli/src/agent/delete-rule.txt diff --git a/packages/cli/src/help/detect.txt b/packages/cli/src/agent/detect.txt similarity index 100% rename from packages/cli/src/help/detect.txt rename to packages/cli/src/agent/detect.txt diff --git a/packages/cli/src/help/improve-rule.anonymous.txt b/packages/cli/src/agent/improve-rule.anonymous.txt similarity index 100% rename from packages/cli/src/help/improve-rule.anonymous.txt rename to packages/cli/src/agent/improve-rule.anonymous.txt diff --git a/packages/cli/src/help/improve-rule.txt b/packages/cli/src/agent/improve-rule.txt similarity index 100% rename from packages/cli/src/help/improve-rule.txt rename to packages/cli/src/agent/improve-rule.txt diff --git a/packages/cli/src/help/info.txt b/packages/cli/src/agent/info.txt similarity index 100% rename from packages/cli/src/help/info.txt rename to packages/cli/src/agent/info.txt diff --git a/packages/cli/src/help/init.txt b/packages/cli/src/agent/init.txt similarity index 100% rename from packages/cli/src/help/init.txt rename to packages/cli/src/agent/init.txt diff --git a/packages/cli/src/help/onboard.txt b/packages/cli/src/agent/onboard.txt similarity index 100% rename from packages/cli/src/help/onboard.txt rename to packages/cli/src/agent/onboard.txt diff --git a/packages/cli/src/help/route.txt b/packages/cli/src/agent/route.txt similarity index 100% rename from packages/cli/src/help/route.txt rename to packages/cli/src/agent/route.txt diff --git a/packages/cli/src/help/rule-meta.txt b/packages/cli/src/agent/rule-meta.txt similarity index 100% rename from packages/cli/src/help/rule-meta.txt rename to packages/cli/src/agent/rule-meta.txt diff --git a/packages/cli/src/help/rule.txt b/packages/cli/src/agent/rule.txt similarity index 100% rename from packages/cli/src/help/rule.txt rename to packages/cli/src/agent/rule.txt diff --git a/packages/cli/src/help/update.txt b/packages/cli/src/agent/update.txt similarity index 100% rename from packages/cli/src/help/update.txt rename to packages/cli/src/agent/update.txt diff --git a/packages/cli/src/help/verify-rule.txt b/packages/cli/src/agent/verify-rule.txt similarity index 100% rename from packages/cli/src/help/verify-rule.txt rename to packages/cli/src/agent/verify-rule.txt diff --git a/packages/cli/src/commands/agent.ts b/packages/cli/src/commands/agent.ts index da79afac..a769decd 100644 --- a/packages/cli/src/commands/agent.ts +++ b/packages/cli/src/commands/agent.ts @@ -75,13 +75,11 @@ export function createAgentCommand(subCommands: SubCommandsDef) { const telemetry = await getTelemetry(cwd); if (positionals.length === 0) { - // cli_help with the index marker: agent fetched the topic list. - // The event name stays `cli_help` even though the command is now - // `agent`: dashboards key on it, it is not part of any agent-facing - // contract, and renaming it in the same change that breaks the - // `TOPICS` export would take those dashboards dark for a reason - // unrelated to this change. - telemetry.capture("cli_help", { topic: "(index)" }); + // cli_agent with the index marker: agent fetched the topic list. + // The event was renamed from `cli_help` to match the command; the + // rename is a hard cut with no dual-emit, so PostHog dashboards keyed + // on the old name need updating. + telemetry.capture("cli_agent", { topic: "(index)" }); console.log("Taskless CLI\n"); console.log( @@ -131,7 +129,7 @@ export function createAgentCommand(subCommands: SubCommandsDef) { // A single hyphenated token is a literal string to copy, so extra // positionals are an error rather than something to guess at. if (positionals.length > 1) { - telemetry.capture("cli_help", { topic: positionals.join(" ") }); + telemetry.capture("cli_agent", { topic: positionals.join(" ") }); console.error(`Too many arguments: ${positionals.join(" ")}`); console.error( "A topic is a single token. Run `taskless agent` for the topic index." @@ -149,13 +147,13 @@ export function createAgentCommand(subCommands: SubCommandsDef) { const recipe = getRecipe(key, { anonymous: args.anonymous }); if (recipe) { - // cli_help: agent fetched a specific recipe (intent signal). The topic + // cli_agent: agent fetched a specific recipe (intent signal). The topic // is the served topic; filtering on it replaces the old per-topic events. - telemetry.capture("cli_help", { topic: key }); + telemetry.capture("cli_agent", { topic: key }); console.log(recipe.trimEnd()); } else { - // cli_help for an unknown topic — still the attempted topic string. - telemetry.capture("cli_help", { topic: key }); + // cli_agent for an unknown topic — still the attempted topic string. + telemetry.capture("cli_agent", { topic: key }); console.error(`Unknown command: ${key}`); console.error("Run `taskless agent` for available topics."); process.exitCode = 1; diff --git a/packages/cli/src/prompts/index.ts b/packages/cli/src/prompts/index.ts index 4a2506c0..8f8c97a2 100644 --- a/packages/cli/src/prompts/index.ts +++ b/packages/cli/src/prompts/index.ts @@ -20,7 +20,7 @@ import { getRecipe, type RecipeOptions } from "./recipes.js"; /** * Topics exported as public API. Hand-maintained rather than derived from the * recipe files, because an exported name is a promise held for a major version - * and a new `help/*.txt` must not be able to publish one by existing. The + * and a new `agent/*.txt` must not be able to publish one by existing. The * completeness check in `test/prompts.test.ts` asserts this list plus * {@link INTERNAL_TOPICS} accounts for every canonical recipe on disk. * @@ -94,7 +94,7 @@ export function getPrompt(topic: PromptTopic, options?: PromptOptions): string { const rendered = getRecipe(topic, options); if (rendered === undefined) { throw new Error( - `No recipe is embedded for prompt topic "${topic}". This is a packaging fault: TOPICS lists a topic with no help/${topic}.txt behind it.` + `No recipe is embedded for prompt topic "${topic}". This is a packaging fault: TOPICS lists a topic with no agent/${topic}.txt behind it.` ); } return rendered; diff --git a/packages/cli/src/prompts/recipes.ts b/packages/cli/src/prompts/recipes.ts index 0815c54a..04aff9e1 100644 --- a/packages/cli/src/prompts/recipes.ts +++ b/packages/cli/src/prompts/recipes.ts @@ -5,33 +5,33 @@ import { applyCliInvocation } from "../util/invocation"; import { inputSchema as ruleCreateInputSchema } from "../schemas/rules-create"; import { inputSchema as ruleImproveInputSchema } from "../schemas/rules-improve"; -// Help text files embedded at build time via Vite import.meta.glob. +// Agent recipe files embedded at build time via Vite import.meta.glob. // Filename convention: .txt for the canonical recipe and // .anonymous.txt for the local-only variant (when the flow // genuinely differs). // // This module is the single embed and the single render path for the -// recipes. Both the `help` command and the `@taskless/cli/prompts` +// recipes. Both the `agent` command and the `@taskless/cli/prompts` // export consume it, so the two surfaces cannot drift. It must stay // free of the CLI runtime — no citty, telemetry, filesystem, or // network — so a Worker can import the prompts entry without pulling // the command tree in behind it. -const helpFiles: Record = import.meta.glob("../help/*.txt", { +const recipeFiles: Record = import.meta.glob("../agent/*.txt", { query: "?raw", import: "default", eager: true, }); // Build two lookup maps: -// - helpMap: "rule-create" → canonical recipe text +// - recipeMap: "rule-create" → canonical recipe text // - anonymousMap: "rule-create" → anonymous variant text (if exists) -function buildHelpMaps(): { - helpMap: Map; +function buildRecipeMaps(): { + recipeMap: Map; anonymousMap: Map; } { - const helpMap = new Map(); + const recipeMap = new Map(); const anonymousMap = new Map(); - for (const [path, content] of Object.entries(helpFiles)) { + for (const [path, content] of Object.entries(recipeFiles)) { const filename = path .split("/") .pop() @@ -41,17 +41,17 @@ function buildHelpMaps(): { const topic = filename.slice(0, -".anonymous".length); anonymousMap.set(topic, content); } else { - helpMap.set(filename, content); + recipeMap.set(filename, content); } } - return { helpMap, anonymousMap }; + return { recipeMap, anonymousMap }; } -const { helpMap, anonymousMap } = buildHelpMaps(); +const { recipeMap, anonymousMap } = buildRecipeMaps(); /** The canonical `.txt` recipe names present in the build. */ export function canonicalRecipeTopics(): string[] { - return [...helpMap.keys()]; + return [...recipeMap.keys()]; } // Topic → Zod input schema. When a recipe contains the %(INPUT_SCHEMA)s @@ -149,7 +149,7 @@ function stripHeader(content: string): string { } /** - * Look up a help topic from the embedded recipe map and return the rendered + * Look up an agent recipe topic from the embedded recipe map and return the rendered * text. Anonymous variants are preferred when `anonymous` is set and a * variant exists; otherwise the canonical recipe is returned. Returns * `undefined` when the topic is unknown. @@ -159,8 +159,8 @@ export function getRecipe( options: RecipeOptions = {} ): string | undefined { const content = options.anonymous - ? (anonymousMap.get(topic) ?? helpMap.get(topic)) - : helpMap.get(topic); + ? (anonymousMap.get(topic) ?? recipeMap.get(topic)) + : recipeMap.get(topic); if (content === undefined) return undefined; return renderRecipe(content, topic, options); } diff --git a/packages/cli/src/telemetry-run.ts b/packages/cli/src/telemetry-run.ts index 26607bfb..3933522b 100644 --- a/packages/cli/src/telemetry-run.ts +++ b/packages/cli/src/telemetry-run.ts @@ -7,7 +7,7 @@ import { CLIError } from "./util/cli-error"; * Derive the cli_run `command` property from the raw argv. Flags (and the * value after `-d`/`--dir`) are skipped; the first positional is the command, * and `rule` keeps its subcommand (e.g. `rule create`) since that distinction - * is meaningful. `agent`'s topic is recorded separately on cli_help, so the + * is meaningful. `agent`'s topic is recorded separately on cli_agent, so the * command for an agent invocation is just `agent`. */ export function resolveCommandName(rawArguments: string[]): string { diff --git a/packages/cli/test/help-extensions.test.ts b/packages/cli/test/agent-extensions.test.ts similarity index 96% rename from packages/cli/test/help-extensions.test.ts rename to packages/cli/test/agent-extensions.test.ts index 8ea2d88b..40867dcb 100644 --- a/packages/cli/test/help-extensions.test.ts +++ b/packages/cli/test/agent-extensions.test.ts @@ -103,16 +103,13 @@ describe("taskless agent ", () => { "create-vale-rule", "create-runtime-rule", "create-remote-rule", - ])( - "resolves the %s recipe without an unknown-topic error", - async (topic) => { - const result = await runCli(["agent", topic, "-d", cwd]); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain(`# Topic: ${topic}`); - expect(result.stdout).toContain("## Goal"); - expect(result.stderr).not.toContain("Unknown command"); - } - ); + ])("resolves the %s recipe without an unknown-topic error", async (topic) => { + const result = await runCli(["agent", topic, "-d", cwd]); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain(`# Topic: ${topic}`); + expect(result.stdout).toContain("## Goal"); + expect(result.stderr).not.toContain("Unknown command"); + }); // D9: a reader who arrived at the wrong recipe should find that out in the // first line, where recovery is a re-decision, rather than after authoring @@ -240,7 +237,13 @@ describe("taskless agent --anonymous (variant lookup)", () => { it("falls back to the canonical recipe when no variant exists (check)", async () => { const canonical = await runCli(["agent", "check", "-d", cwd]); - const anonymous = await runCli(["agent", "check", "--anonymous", "-d", cwd]); + const anonymous = await runCli([ + "agent", + "check", + "--anonymous", + "-d", + cwd, + ]); expect(anonymous.exitCode).toBe(0); // Same body — falls back to check.txt since no check.anonymous.txt expect(anonymous.stdout).toBe(canonical.stdout); diff --git a/packages/cli/test/help-routing-telemetry.test.ts b/packages/cli/test/agent-routing-telemetry.test.ts similarity index 86% rename from packages/cli/test/help-routing-telemetry.test.ts rename to packages/cli/test/agent-routing-telemetry.test.ts index d2862bf7..1187e9e6 100644 --- a/packages/cli/test/help-routing-telemetry.test.ts +++ b/packages/cli/test/agent-routing-telemetry.test.ts @@ -24,7 +24,7 @@ interface RunnableCommand { }) => Promise; } -describe("agent routing topics emit cli_help intent telemetry", () => { +describe("agent routing topics emit cli_agent intent telemetry", () => { let logSpy: ReturnType; let exitCode: typeof process.exitCode; @@ -44,8 +44,8 @@ describe("agent routing topics emit cli_help intent telemetry", () => { logSpy.mockRestore(); }); - // Every name here has a recipe in `src/help/`. That matters because the - // command captures `cli_help` on both branches — topic found and topic not + // Every name here has a recipe in `src/agent/`. That matters because the + // command captures `cli_agent` on both branches — topic found and topic not // found — so a list of removed topics would still satisfy the assertion // while pinning the unknown-topic path instead of routing. The exit code is // what tells the two apart: an unknown topic exits 1. @@ -56,7 +56,7 @@ describe("agent routing topics emit cli_help intent telemetry", () => { "create-vale-rule", "create-runtime-rule", "create-remote-rule", - ])("captures cli_help for %s and resolves it", async (topic) => { + ])("captures cli_agent for %s and resolves it", async (topic) => { const command = createAgentCommand({}) as unknown as RunnableCommand; await command.run({ args: { dir: process.cwd(), anonymous: false }, @@ -64,7 +64,7 @@ describe("agent routing topics emit cli_help intent telemetry", () => { }); expect(capture).toHaveBeenCalledWith( - "cli_help", + "cli_agent", expect.objectContaining({ topic }) ); expect(process.exitCode).toBe(0); diff --git a/packages/cli/test/help-telemetry.test.ts b/packages/cli/test/agent-telemetry.test.ts similarity index 73% rename from packages/cli/test/help-telemetry.test.ts rename to packages/cli/test/agent-telemetry.test.ts index b103c238..36647db6 100644 --- a/packages/cli/test/help-telemetry.test.ts +++ b/packages/cli/test/agent-telemetry.test.ts @@ -30,7 +30,7 @@ async function runAgent(rawArguments: string[]): Promise { }); } -describe("agent emits cli_help { topic }", () => { +describe("agent emits cli_agent { topic }", () => { let logSpy: ReturnType; let errorSpy: ReturnType; @@ -47,17 +47,17 @@ describe("agent emits cli_help { topic }", () => { it("captures the served topic", async () => { await runAgent(["agent", "rule-create"]); - expect(capture).toHaveBeenCalledWith("cli_help", { topic: "rule-create" }); + expect(capture).toHaveBeenCalledWith("cli_agent", { topic: "rule-create" }); }); it("captures the index marker for no topic", async () => { await runAgent(["agent"]); - expect(capture).toHaveBeenCalledWith("cli_help", { topic: "(index)" }); + expect(capture).toHaveBeenCalledWith("cli_agent", { topic: "(index)" }); }); it("captures the attempted topic for an unknown topic", async () => { await runAgent(["agent", "nope"]); - expect(capture).toHaveBeenCalledWith("cli_help", { topic: "nope" }); + expect(capture).toHaveBeenCalledWith("cli_agent", { topic: "nope" }); }); // The rejected invocation is still an intent signal — it says an agent @@ -65,13 +65,13 @@ describe("agent emits cli_help { topic }", () => { // under the same event with the attempted words joined for readability. it("captures the attempted words when too many positionals are given", async () => { await runAgent(["agent", "rule", "create"]); - expect(capture).toHaveBeenCalledWith("cli_help", { topic: "rule create" }); + expect(capture).toHaveBeenCalledWith("cli_agent", { topic: "rule create" }); }); }); -// Rather than asserting "no help_* event" inside every behavioral test above, -// prove it once at the source: after this change lands, no legacy help_* event -// name is emitted anywhere in the CLI. +// Rather than asserting "no legacy event" inside every behavioral test above, +// prove it once at the source: no legacy help_* event name, and no `cli_help` +// capture, remains anywhere in the CLI. function collectSourceFiles(directory: string): string[] { const files: string[] = []; for (const entry of readdirSync(directory, { withFileTypes: true })) { @@ -82,12 +82,14 @@ function collectSourceFiles(directory: string): string[] { return files; } -describe("no legacy help_* event remains in the CLI source", () => { - it("emits no help_* event-name literal under src/", () => { +describe("no legacy help telemetry event remains in the CLI source", () => { + it("emits no help_* or cli_help event name under src/", () => { const sourceDirectory = resolve(import.meta.dirname, "../src"); // Match a string/template literal that begins with help_ (e.g. "help_index", - // "help_unknown", or a `help_${...}` topic event). - const legacyHelpEvent = /["`]help_/; + // "help_unknown", or a `help_${...}` topic event), or a capture call still + // using the pre-rename `cli_help` name. The `cli_help` arm is anchored to + // the capture call so prose explaining the rename does not trip it. + const legacyHelpEvent = /["`]help_|capture\(\s*["'`]cli_help/; const offenders = collectSourceFiles(sourceDirectory).filter((file) => legacyHelpEvent.test(readFileSync(file, "utf8")) ); diff --git a/packages/cli/test/prompts.test.ts b/packages/cli/test/prompts.test.ts index 9f365cd7..162b8217 100644 --- a/packages/cli/test/prompts.test.ts +++ b/packages/cli/test/prompts.test.ts @@ -16,7 +16,7 @@ import { canonicalRecipeTopics, getRecipe } from "../src/prompts/recipes"; const execFileAsync = promisify(execFile); -const helpDirectory = resolve(import.meta.dirname, "../src/help"); +const recipeDirectory = resolve(import.meta.dirname, "../src/agent"); const distributionDirectory = resolve(import.meta.dirname, "../dist"); const binPath = resolve(distributionDirectory, "index.js"); const distributionPromptsPath = resolve(distributionDirectory, "prompts.js"); @@ -42,7 +42,7 @@ async function importBuiltPrompts(): Promise<{ /** Canonical `.txt` names on disk, excluding `.anonymous` variants. */ async function canonicalTopicsOnDisk(): Promise { - const entries = await readdir(helpDirectory); + const entries = await readdir(recipeDirectory); return entries .filter((name) => name.endsWith(".txt")) .map((name) => name.slice(0, -".txt".length)) diff --git a/packages/cli/test/recipe-cross-references.test.ts b/packages/cli/test/recipe-cross-references.test.ts index 8311da9d..c03bd1e4 100644 --- a/packages/cli/test/recipe-cross-references.test.ts +++ b/packages/cli/test/recipe-cross-references.test.ts @@ -14,14 +14,14 @@ import { describe, expect, it } from "vitest"; * imports, and a prose cross-reference is not one, so there is no structured * answer to ask it for. */ -const helpDirectory = resolve(import.meta.dirname, "../src/help"); +const recipeDirectory = resolve(import.meta.dirname, "../src/agent"); async function recipeFiles(): Promise { - const entries = await readdir(helpDirectory); + const entries = await readdir(recipeDirectory); return entries.filter((entry) => entry.endsWith(".txt")).toSorted(); } -/** Topic names that resolve, i.e. `.txt` exists in the help directory. */ +/** Topic names that resolve, i.e. `.txt` exists in the recipe directory. */ async function embeddedTopics(): Promise> { const files = await recipeFiles(); return new Set( @@ -35,7 +35,7 @@ describe("shipped recipes name only commands that exist", () => { it("contains no reference to the former `taskless help` command", async () => { const offenders: string[] = []; for (const file of await recipeFiles()) { - const source = await readFile(join(helpDirectory, file), "utf8"); + const source = await readFile(join(recipeDirectory, file), "utf8"); for (const [index, line] of source.split("\n").entries()) { if (line.includes("taskless help")) { offenders.push(`${file}:${String(index + 1)}: ${line.trim()}`); @@ -52,7 +52,7 @@ describe("shipped recipes name only commands that exist", () => { const dangling: string[] = []; for (const file of await recipeFiles()) { - const source = await readFile(join(helpDirectory, file), "utf8"); + const source = await readFile(join(recipeDirectory, file), "utf8"); for (const [index, line] of source.split("\n").entries()) { // `taskless agent `, however it is punctuated around. for (const match of line.matchAll(/taskless agent ([a-z][a-z-]*)/g)) { @@ -73,7 +73,7 @@ describe("shipped recipes name only commands that exist", () => { it("opens each recipe with a header naming its own topic", async () => { const mismatched: string[] = []; for (const file of await recipeFiles()) { - const source = await readFile(join(helpDirectory, file), "utf8"); + const source = await readFile(join(recipeDirectory, file), "utf8"); const topic = file.replace(/(\.anonymous)?\.txt$/, ""); const firstLine = source.split("\n")[0] ?? ""; if (!firstLine.startsWith(`# Topic: ${topic}`)) { diff --git a/skills/taskless/SKILL.md b/skills/taskless/SKILL.md index 6801a975..5f03bf09 100644 --- a/skills/taskless/SKILL.md +++ b/skills/taskless/SKILL.md @@ -28,45 +28,47 @@ compatibility: Designed for Agents implementing the Agent Skills specification. # Taskless You do NOT have the steps for any Taskless action in your context. The current -canonical recipes live behind `npx @taskless/cli help `. Always fetch +canonical recipes live behind `npx @taskless/cli agent `. Always fetch the recipe first; do not improvise from prior knowledge — recipes change with each CLI version. ## Authoring a rule: always start at route For any request to add/write/create a rule — whether or not the user names a -tool (eslint, ruff, biome, stylelint, ast-grep) — fetch `npx @taskless/cli help route` -and follow it. Do NOT fetch `rule create` directly, and do NOT author from +tool (eslint, ruff, biome, stylelint, ast-grep) — fetch `npx @taskless/cli agent route` +and follow it. Do NOT fetch a `create-*-rule` topic directly, and do NOT author from your own linter knowledge. `route` runs `detect`, reasons about the request, -and decides whether the rule is built in an existing linter (`existing`), as a -local ast-grep rule (`static`), or via the Taskless service (`remote`) — and it +and decides whether the rule is built in a linter the repo already uses +(`create-legacy-rule`), as a local ast-grep rule (`create-sg-rule`), or via +the Taskless service (`create-remote-rule`) — and it keeps the work local before any login. This skill is a thin router: all authoring judgment lives in the fetched recipes. ## Confirm Taskless is installed when a path needs it -`route` and the `existing` path only read the repo, so they need no install. If -routing lands on a local Taskless rule (`static`) or the service (`remote`) and +`route` and the `create-legacy-rule` path only read the repo, so they need no +install. If routing lands on a local Taskless rule (`create-sg-rule`) or the +service (`create-remote-rule`) and the working directory has no `.taskless/` directory, offer to run `npx @taskless/cli` to install. If the user only wanted help with their own -linter, the `existing` path needs nothing installed. +linter, the `create-legacy-rule` path needs nothing installed. ## Topics -| User wants | Topic | -| -------------------------- | ------------------------------------- | -| Author/create a rule | `npx @taskless/cli help route` | -| First-time install | tell user to run `npx @taskless/cli` | -| Update existing install | `npx @taskless/cli update` | -| Discover candidate rules | `npx @taskless/cli help onboard` | -| Improve an existing rule | `npx @taskless/cli help rule improve` | -| Delete a rule | `npx @taskless/cli help rule delete` | -| Check code against rules | `npx @taskless/cli help check` | -| Log in, log out, or status | `npx @taskless/cli help auth` | -| Wire into CI | `npx @taskless/cli help ci` | +| User wants | Topic | +| -------------------------- | -------------------------------------- | +| Author/create a rule | `npx @taskless/cli agent route` | +| First-time install | tell user to run `npx @taskless/cli` | +| Update existing install | `npx @taskless/cli update` | +| Discover candidate rules | `npx @taskless/cli agent onboard` | +| Improve an existing rule | `npx @taskless/cli agent improve-rule` | +| Delete a rule | `npx @taskless/cli agent delete-rule` | +| Check code against rules | `npx @taskless/cli agent check` | +| Log in, log out, or status | `npx @taskless/cli agent auth` | +| Wire into CI | `npx @taskless/cli agent ci` | If the user's intent is ambiguous between two topics, run -`npx @taskless/cli help` (no args) to see the disambiguation table, or ask +`npx @taskless/cli agent` (no args) to see the disambiguation table, or ask the user. ## --anonymous @@ -74,7 +76,7 @@ the user. Any rule/check command accepts `--anonymous` to skip the Taskless API and use local-only behavior. When the user is offline OR explicitly asks for anonymous mode, fetch the recipe with -`npx @taskless/cli help --anonymous`, which returns the local-only +`npx @taskless/cli agent --anonymous`, which returns the local-only flow (when one exists for that topic). ## First-run latency From 60c5a20a0706e886c8cf7d43530dfc2864de3cf6 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Wed, 19 Aug 2026 11:46:04 -0700 Subject: [PATCH 2/3] test(cli): widen the recipe cross-reference guard to the npx invocation form The dangling-topic guard matched only `taskless agent `, so the `npx @taskless/cli agent ` phrasing in create-runtime-rule.txt:92 and route.txt:125 bypassed it entirely -- the exact failure class the guard's doc comment exists to catch. Anchor both this guard and the `taskless help` negative assertion on either the binary name or the package specifier, which keeps prose ("the agent should...") from being read as a citation. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jwc9FFroR3mTZ4hLiSkkX3 --- packages/cli/test/recipe-cross-references.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/cli/test/recipe-cross-references.test.ts b/packages/cli/test/recipe-cross-references.test.ts index c03bd1e4..e0b31e39 100644 --- a/packages/cli/test/recipe-cross-references.test.ts +++ b/packages/cli/test/recipe-cross-references.test.ts @@ -37,7 +37,7 @@ describe("shipped recipes name only commands that exist", () => { for (const file of await recipeFiles()) { const source = await readFile(join(recipeDirectory, file), "utf8"); for (const [index, line] of source.split("\n").entries()) { - if (line.includes("taskless help")) { + if (/(?:taskless|@taskless\/cli) help\b/.test(line)) { offenders.push(`${file}:${String(index + 1)}: ${line.trim()}`); } } @@ -54,8 +54,13 @@ describe("shipped recipes name only commands that exist", () => { for (const file of await recipeFiles()) { const source = await readFile(join(recipeDirectory, file), "utf8"); for (const [index, line] of source.split("\n").entries()) { - // `taskless agent `, however it is punctuated around. - for (const match of line.matchAll(/taskless agent ([a-z][a-z-]*)/g)) { + // `taskless agent ` however it is punctuated around, and the + // `npx @taskless/cli agent ` form recipes use when the CLI is + // not assumed to be on PATH. Both are anchored on a package/binary + // name so that prose ("the agent should…") is not read as a citation. + for (const match of line.matchAll( + /(?:taskless|@taskless\/cli) agent ([a-z][a-z-]*)/g + )) { const topic = match[1]; if (topic !== undefined && !topics.has(topic)) { dangling.push(`${file}:${String(index + 1)} cites '${topic}'`); From a4cdde98607a735ff76ad4e8bc18f6c37a9cb1ff Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Wed, 19 Aug 2026 13:08:08 -0700 Subject: [PATCH 3/3] docs(openspec): finish the rename in the two specs the sweep missed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both found by the re-review that ran after the review workflow was fixed to check out the PR ref. Neither was reachable before: they sit outside the diff hunks, so a reviewer reading only `gh pr diff` could not see them, and a reviewer reading `main` had no `agent` tree at all. `cli-knowledge-prompts/spec.md` had the densest concentration of misses of any spec touched — six references, several a paragraph away from lines this sweep did update, including a scenario whose heading was renamed to "agent command" while its WHEN clause still said `help`. `cli/spec.md`'s scenario body was updated but its heading still read "Help subcommand is registered", which left it naming a subcommand that no longer exists, in a list where every sibling is named for its own command. --- openspec/specs/cli-knowledge-prompts/spec.md | 12 ++++++------ openspec/specs/cli/spec.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openspec/specs/cli-knowledge-prompts/spec.md b/openspec/specs/cli-knowledge-prompts/spec.md index bef5076b..d3d171bf 100644 --- a/openspec/specs/cli-knowledge-prompts/spec.md +++ b/openspec/specs/cli-knowledge-prompts/spec.md @@ -10,7 +10,7 @@ brief a model. This capability publishes those recipes as a typed subpath export, `@taskless/cli/prompts`, rendered through the same embed and the same render -path the `help` command uses. One source and one renderer means the two surfaces +path the `agent` command uses. One source and one renderer means the two surfaces cannot drift into giving different guidance. The export carries no CLI runtime, so a Worker can import it without dragging in the command tree, and topic membership is an explicit hand-maintained list so a new recipe file cannot @@ -47,7 +47,7 @@ The prompt export SHALL be sourced from the same embedded `agent/*.txt` content #### Scenario: Parity between import and agent command -- **WHEN** the `help` command renders topic `T` and a consumer calls `getPrompt("T")` +- **WHEN** the `agent` command renders topic `T` and a consumer calls `getPrompt("T")` - **THEN** the two texts are identical, including under a non-prod build target where the CLI invocation is rewritten ### Requirement: The export returns fully-rendered prompt text @@ -71,7 +71,7 @@ Calling a prompt SHALL return finished text with every `%(KEY)s` placeholder sub ### Requirement: The version header is suppressible -Rendered prompts SHALL begin with a header line naming the topic and the CLI version. Because that version participates in an LLM consumer's prompt-cache key, `PromptOptions.header` SHALL allow suppressing it. It SHALL default to `true`, leaving the `help` command's output and all existing behavior unchanged. +Rendered prompts SHALL begin with a header line naming the topic and the CLI version. Because that version participates in an LLM consumer's prompt-cache key, `PromptOptions.header` SHALL allow suppressing it. It SHALL default to `true`, leaving the `agent` command's output and all existing behavior unchanged. #### Scenario: Header suppressed for a cache-stable system prompt @@ -80,7 +80,7 @@ Rendered prompts SHALL begin with a header line naming the topic and the CLI ver #### Scenario: Header present by default -- **WHEN** a prompt is called with no options, or the `help` command renders a topic +- **WHEN** a prompt is called with no options, or the `agent` command renders a topic - **THEN** the header line is present, exactly as it renders today #### Scenario: Build defines are inlined into the prompts entry @@ -134,12 +134,12 @@ An automated check SHALL assert that the set of canonical `agent/*.txt` topics o #### Scenario: A new recipe file is added without being classified -- **WHEN** a new canonical `help/.txt` is added and appears in neither the exported topics nor the internal-topics list +- **WHEN** a new canonical `agent/.txt` is added and appears in neither the exported topics nor the internal-topics list - **THEN** the completeness check SHALL fail, requiring the author to either export the topic or record it as internal #### Scenario: An exported topic loses its recipe file -- **WHEN** a topic remains in `PromptTopic` but its canonical `help/.txt` no longer exists +- **WHEN** a topic remains in `PromptTopic` but its canonical `agent/.txt` no longer exists - **THEN** the completeness check SHALL fail, rather than the topic rendering empty or undefined at runtime #### Scenario: A deliberately internal recipe stays unexported diff --git a/openspec/specs/cli/spec.md b/openspec/specs/cli/spec.md index 03d435fb..f3a927e3 100644 --- a/openspec/specs/cli/spec.md +++ b/openspec/specs/cli/spec.md @@ -149,7 +149,7 @@ The CLI entry point SHALL use citty to define a main command with subcommand sup - **WHEN** a user runs `taskless rule` - **THEN** the CLI SHALL route to the rule subcommand group -#### Scenario: Help subcommand is registered +#### Scenario: Agent subcommand is registered - **WHEN** a user runs `taskless agent` - **THEN** the CLI SHALL route to the `agent` subcommand handler