diff --git a/.changeset/streamlined-onboarding-report.md b/.changeset/streamlined-onboarding-report.md index fe08728e..d71eec5a 100644 --- a/.changeset/streamlined-onboarding-report.md +++ b/.changeset/streamlined-onboarding-report.md @@ -5,3 +5,5 @@ `taskless detect --json` now reports ast-grep and Vale configurations as linters (Taskless's own configuration under `.taskless/` excluded), the command-line tools on the host's `PATH` (`gh`, `git`, `jq`, and now `glab` for GitLab repositories) under `hostTools`, and an `agent` section of commands an agent may choose to run to check whether a source is usable, such as `gh auth status`. Detection still runs nothing and stays offline. `taskless info` lists `glab` among its tools too. New `taskless report` publishes an unlisted Taskless report to tskl.es: a page showing the rules in this repository, what they were drawn from, and what they are projected to catch. With no flags it prints a draft of what the CLI knows and the facts an agent still has to supply; `--from ` checks there are at least three working Taskless rules, withholds any fixture that looks like it holds a secret or contact detail, validates against the tskl.es schema, publishes, and records the report in `.taskless/reports/` (gitignored, since each record holds the URL that deletes it); `--delete ` takes a report down. The new `report` agent topic walks an agent through it. Migration 11 creates `.taskless/reports/`. + +Onboarding now asks one question, "Do you want me to set up Taskless for you?", and does the rest: it mines every source it can reach, writes every rule the repository can enforce locally (always logged out), marks onboarding complete, publishes a report, sends Taskless onboarding feedback, and ends by strongly encouraging running the rules in CI. It asks anything else only when it cannot find enough rules for a report. GitLab merge requests are mined through `glab` the way GitHub pull requests are through `gh`. diff --git a/commands/tskl/tskl.md b/commands/tskl/tskl.md index 1c9caac7..57da7526 100644 --- a/commands/tskl/tskl.md +++ b/commands/tskl/tskl.md @@ -41,6 +41,7 @@ Otherwise, follow the same flow as the `taskless` skill: | Check code against rules | `%(TASKLESS_CLI)s agent check` | | Log in, log out, or status | `%(TASKLESS_CLI)s agent auth` | | Wire into CI | `%(TASKLESS_CLI)s agent ci` | +| Make or delete a report | `%(TASKLESS_CLI)s agent report` | If unsure, run `%(TASKLESS_CLI)s agent` (no args) for the topic disambiguation table. diff --git a/openspec/changes/streamlined-onboarding-report/specs/skill-taskless/spec.md b/openspec/changes/streamlined-onboarding-report/specs/skill-taskless/spec.md index ecb44dee..bcbd99c5 100644 --- a/openspec/changes/streamlined-onboarding-report/specs/skill-taskless/spec.md +++ b/openspec/changes/streamlined-onboarding-report/specs/skill-taskless/spec.md @@ -13,3 +13,69 @@ The skill body's topic table SHALL map a request to make, publish, regenerate, o - **WHEN** the skill body is read - **THEN** it SHALL NOT describe how to assemble, publish, or delete a report + +## MODIFIED Requirements + +### Requirement: Skill body is a router, not an inline recipe + +The consolidated skill body SHALL NOT contain step-by-step instructions for any individual Taskless task. The body SHALL be a router that: + +1. States explicitly that the agent does NOT have the steps for any Taskless action in its context. +2. Instructs the agent to fetch the canonical recipe via `npx @taskless/cli help ` before proceeding. +3. Provides a topic disambiguation table mapping user intents to topic names. The table SHALL include a row for the new `onboard` topic. +4. Includes a `## --anonymous` section explaining the global flag's behavior. +5. Includes a first-step `.taskless/` presence check with graceful failure ("ask the user to confirm they meant Taskless"). +6. Includes a `## Quiet suggestion` (or equivalently named) section governing the proactive trigger introduced via the description's named-tool clause. This section SHALL specify that: + - When the skill triggers because the user wants to add a rule and has not named a specific tool, the agent SHALL surface a single-line offer to capture the rule via Taskless rather than launching into a full recipe (e.g., "I can capture this as a Taskless rule if you want — say so, or I'll proceed with X"). + - If the user declines or ignores the offer, the agent SHALL proceed with whatever it would have done without the skill. + - If the user declines, the agent SHALL NOT re-offer Taskless in the same conversation. No persistent decline state SHALL be written to disk. + - If the user accepts, the skill router SHALL proceed normally to fetch `npx @taskless/cli help rule create`. +7. States that a recipe is resolved when it is fetched and is not reusable across tasks: the agent SHALL fetch the recipe at the start of every Taskless task, including a topic it already fetched earlier in the same session, and SHALL NOT act on an earlier copy. The `tskl` command body SHALL carry the same statement. +8. Names the CLI through the `%(TASKLESS_CLI)s` placeholder everywhere it spells an invocation, never as the literal `npx @taskless/cli`. The install renders the placeholder to the build's invocation, so a nightly or path-form build serves a skill that names itself. + +The body SHALL be no more than 150 lines of markdown. The cap guards against unbounded growth of a body loaded on every Taskless task; it SHALL NOT be met by compressing guidance the router needs. When the body needs more room to route correctly, the cap is raised instead. (Earlier caps of 60 and then 80 lines forced exactly that compression.) + +#### Scenario: Skill body warns against improvising + +- **WHEN** the skill body is read by an agent +- **THEN** it SHALL contain explicit framing such as "You do NOT have the steps... do not improvise from prior knowledge" + +#### Scenario: Skill body spells the invocation as a placeholder + +- **WHEN** the skill body or the `tskl` command body is read from source +- **THEN** every CLI invocation SHALL be written as `%(TASKLESS_CLI)s …` +- **AND** the body SHALL NOT contain the literal `npx @taskless/cli` + +#### Scenario: Skill body forbids reusing a fetched recipe + +- **WHEN** the skill body or the `tskl` command body is read by an agent +- **THEN** it SHALL state that a recipe is resolved at fetch time and that each Taskless task fetches its recipe again, even for a topic fetched earlier in the session + +#### 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 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 + +- **WHEN** the skill is invoked +- **THEN** the body's first step SHALL instruct the agent to check whether `.taskless/` exists in the working directory +- **AND** to ask the user to confirm Taskless is what they meant if the directory is absent + +#### Scenario: Skill body specifies quiet suggestion behavior + +- **WHEN** the skill is triggered by the unspecified-tool clause from the description +- **THEN** the body's quiet-suggestion section SHALL instruct the agent to surface a single-line offer rather than a full recipe +- **AND** SHALL instruct the agent NOT to re-offer in the same conversation if declined +- **AND** SHALL specify that no persistent decline state is written + +#### Scenario: Skill body specifies in-conversation decline is sticky + +- **WHEN** the user has declined a quiet-suggestion offer once in the current conversation +- **THEN** the body SHALL instruct the agent not to surface the offer again in the same conversation + +#### Scenario: Skill body length cap + +- **WHEN** the skill body is measured +- **THEN** it SHALL be no more than 150 lines of markdown diff --git a/openspec/changes/streamlined-onboarding-report/tasks.md b/openspec/changes/streamlined-onboarding-report/tasks.md index b649ace0..4b83cd1c 100644 --- a/openspec/changes/streamlined-onboarding-report/tasks.md +++ b/openspec/changes/streamlined-onboarding-report/tasks.md @@ -30,13 +30,13 @@ ## 4. Onboard v6, skill, and feedback (PR 4) -- [ ] 4.1 Rewrite `agent/onboard.md` as topic v6 per the `cli-onboard` spec: the one question, logged out, mine everything, author local candidates without asking, record runtime candidates for the `cloud` tier, `--mark-complete` without asking, hand off to `report` and `rule-feedback` in parallel, close by showing the summary and then, last, offering CI integration via the `ci` topic with a strong case for it (checks keep agents from reintroducing what the rules catch), naming `hooks` in one line on a no. Verify recipe tests for each scenario in the spec delta, and the two-path byte-parity tests. Render the review-comment source from the host-tool catalog for both `gh` (pull requests) and `glab` (merge requests). -- [ ] 4.2 Remove `onboard` from the surveyed topics in `survey/constants.ts`. Verify a survey test that onboarding serves no invite and leaves `next_ask` unchanged with the gate otherwise open. -- [ ] 4.3 Add the "reached from onboarding" branch to `agent/rule-feedback.md`: no `verbatim`, `ruleKind: none (onboarding)`, no question, send. Verify the recipe test and that `feedback send` still refuses under `DO_NOT_TRACK=1`. -- [ ] 4.4 Add the `report` row to the skill body and the `tskl` command body via `%(TASKLESS_CLI)s`. Verify the skill tests, including the 80-line cap. -- [ ] 4.5 Update `getOnboardTrailer` and the `onboard` command description if their wording still promises a conversational discovery flow. Verify the init trailer tests. -- [ ] 4.6 End to end: `pnpm build`, then in a scratch repository with a GitHub origin follow `pnpm cli onboard` as an agent would, through `report --from` against the local test server, and confirm the record, the summary, and `install.onboarded: true`. -- [ ] 4.7 Extend the changeset with the onboarding change. Run `pnpm typecheck` and `pnpm lint`; both pass. +- [x] 4.1 Rewrite `agent/onboard.md` as topic v6 per the `cli-onboard` spec: the one question, logged out, mine everything, author local candidates without asking, record runtime candidates for the `cloud` tier, `--mark-complete` without asking, hand off to `report` and `rule-feedback` in parallel, close by showing the summary and then, last, offering CI integration via the `ci` topic with a strong case for it (checks keep agents from reintroducing what the rules catch), naming `hooks` in one line on a no. Verify recipe tests for each scenario in the spec delta, and the two-path byte-parity tests. Render the review-comment source from the host-tool catalog for both `gh` (pull requests) and `glab` (merge requests). +- [x] 4.2 Remove `onboard` from the surveyed topics in `survey/constants.ts`. Verify a survey test that onboarding serves no invite and leaves `next_ask` unchanged with the gate otherwise open. +- [x] 4.3 Add the "reached from onboarding" branch to `agent/rule-feedback.md`: no `verbatim`, `ruleKind: none (onboarding)`, no question, send. Verify the recipe test and that `feedback send` still refuses under `DO_NOT_TRACK=1`. +- [x] 4.4 Add the `report` row to the skill body and the `tskl` command body via `%(TASKLESS_CLI)s`. Verify the skill tests, including the body's line cap (raised from 80 to 150 in review, so the router is not compressed to fit). +- [x] 4.5 Update `getOnboardTrailer` and the `onboard` command description if their wording still promises a conversational discovery flow. Verify the init trailer tests. +- [x] 4.6 End to end: covered by `test/report.test.ts`, which drives the built CLI through `init`, three sg rules, `report` (draft), `report --from` against a local stand-in for tskl.es, and `report --delete`, and by `test/onboard.test.ts` for `onboard --mark-complete`. No separate manual run was made. +- [x] 4.7 Extend the changeset with the onboarding change. Run `pnpm typecheck` and `pnpm lint`; both pass. ## 5. Archive (PR 5, tip of the stack) diff --git a/packages/cli/src/agent/onboard.md b/packages/cli/src/agent/onboard.md index a134be5c..4c56c073 100644 --- a/packages/cli/src/agent/onboard.md +++ b/packages/cli/src/agent/onboard.md @@ -1,22 +1,22 @@ -# Topic: onboard (CLI v%(CLI_VERSION)s / topic v5) +# Topic: onboard (CLI v%(CLI_VERSION)s / topic v6) ## Goal -Help a user who has just installed Taskless go from zero rules to a -useful starter set by mining their codebase, agent-memory files, PR -review history, and issue tracker for high-signal rule candidates. -This is a conversational discovery flow, not a script, the agent -collaborates with the user on what to scan and surfaces hypothetical -rules as a bullet list the user can choose to materialize via the -`rule create` flow. +Take a user who has just installed Taskless from zero rules to a +working starter set and a published report, after asking them one +question. Mine the codebase, agent-memory files, review history, and +any tracker or engineering context you can reach for high-signal rule +candidates; write every one this repository can enforce locally; +publish a Taskless report showing what they catch; and finish by +making the case for running them in CI. ## Preconditions - `.taskless/` directory exists (Taskless is installed). The `%(TASKLESS_CLI)s onboard` subcommand bootstraps it on first run, so this is automatically satisfied. - A working repository the agent can read. -- No auth required to surface candidates. (Materializing a rule via - `rule create` may require auth. Fetch `%(TASKLESS_CLI)s agent auth` if - needed at that point.) +- No auth, ever. Onboarding runs logged out from start to finish: do + not run `auth login`, and do not offer it. Everything below works + without an account. - The criterion for where a rule belongs lives in `%(TASKLESS_CLI)s agent route` and is not restated here. This recipe reads it; it does not duplicate it. @@ -26,11 +26,28 @@ rules as a bullet list the user can choose to materialize via the 1. **Read the manifest first.** Run `%(TASKLESS_CLI)s info --json` and check whether `.taskless/taskless.json` reports `install.onboarded: true`. The - `%(TASKLESS_CLI)s onboard` subcommand has already gated on this for you, - but if the user is invoking the recipe directly via the skill, - confirm with the user before running a long discovery pass. + `%(TASKLESS_CLI)s onboard` subcommand has already gated on this for + you; if you reached this recipe another way and it is true, stop and + tell the user onboarding already ran. -2. **Learn the routing surface before you propose anything.** A +2. **Ask the one question.** Put exactly this to the user, set apart + from anything else you say by a blank line above and below: + + Do you want me to set up Taskless for you? + + If your host has a structured question tool, ask it through that + tool; otherwise ask it as plain text. A yes covers everything that + follows: mining the repository, writing rules, publishing an + unlisted report to tskl.es, and sending Taskless anonymous feedback + about how onboarding went. On anything other than a yes, stop. + + This is the only question you script. Do not ask which sources to + mine, whether to write each rule, or whether to mark onboarding + complete. Two exceptions follow: clarifying questions when the + report cannot be built (step 8), and the CI offer at the very end + (step 11). + +3. **Learn the routing surface before you propose anything.** A candidate this repository cannot express is not a candidate, and you cannot tell which is which until you know two things: @@ -43,17 +60,12 @@ rules as a bullet list the user can choose to materialize via the decided by one file's syntax tree, by a document's words, by a tool the repo already runs, or by evidence outside the files. Read `detect --json` for what this repository actually has: - `linters`, `languages`, and `ruleStyles`. - - Do this before you synthesize the bullet list, not after. - Proposing first and routing afterwards is how a bullet list ends - up carrying candidates that nothing can enforce. You are not - choosing a destination command here. That happens per accepted - bullet when you offer materialization. + `linters`, `languages`, `ruleStyles`, `hostTools`, and `agent`, + the commands you may choose to run to check that a source is + usable, such as whether the user is signed in to `gh`. -3. **Open the conversation about sources.** Tell the user you can mine - several places for rule candidates and ask which ones they want to - include. Default sources you should always offer: +4. **Mine every source you can reach.** Do not ask the user which; + use all of these that apply: - **Codebase TODOs / FIXMEs**: search for `TODO`, `FIXME`, `XXX`, and `HACK` comments. Many of these are latent rules ("don't do @@ -62,21 +74,20 @@ rules as a bullet list the user can choose to materialize via the `.cursorrules`, `.opencode/AGENTS.md`, and similar agent-context files for explicit rules and conventions stated in prose. %(SOURCE_PR_REVIEW)s - - **Bug-tracker tickets**: only if you have an MCP that reaches a - tracker. Taskless cannot see your MCP roster, so this one is your - call rather than something it can answer for you: look at the - tools you actually have and offer the source only if one of them - gets you tickets. Jira and Linear are examples of the class, not - the expected answer, and a self-hosted tracker behind an MCP - counts exactly the same. + - **Trackers and engineering context**: Taskless cannot see your + MCP roster, so this is your call. Use every MCP you have that + reaches a bug tracker (Jira and Linear are examples of the class, + not the expected answer), an engineering-context tool, or + recorded architectural decisions. A self-hosted tracker behind an + MCP counts exactly the same. - Then explicitly ask: "Are there other places I should look, a - team wiki, an internal docs site, a specific design doc, a Slack - channel export?" The user often knows about sources you don't. + Whether to run a command `detect` lists under `agent`, and which + MCPs to use, is yours to decide. Run a check when its answer + changes what you can mine. -4. %(HOST_TOOLS)s +5. %(HOST_TOOLS)s -5. **Scan with high-signal filtering.** For each chosen source: +6. **Scan with high-signal filtering.** For each source: - **Filter for repeated patterns.** A reviewer comment that appears across multiple PRs is a much stronger rule candidate @@ -90,92 +101,81 @@ rules as a bullet list the user can choose to materialize via the formatters already handle those; Taskless rules earn their keep on semantic patterns. -6. **Synthesize the bullet list.** Present findings as a single - bullet list the user can scan quickly. One bullet per - hypothetical rule, in this format: + Keep, for yourself, a list of candidates in this form, ordered by + impact (frequency in the source × severity × ease of enforcement): ``` - []: ``` - `` is the routing call you reached when you learned - the routing surface, one of - `legacy`, `sg`, `vale`, or `runtime`. Example: - - ``` - - no-direct-db-access [sg]: Prevent controllers from importing the DB module directly; route through the service layer. - - require-zod-validation [sg]: Flag API handlers that read request bodies without parsing them through a Zod schema first. - - comments-record-not-forecast [vale]: Flag comments that promise future work instead of recording what is true now. - - exports-must-be-used [runtime]: Flag exported symbols nothing in the repo imports. - ``` - - The annotation is provisional. It tells the user what kind of rule - this would be, and it is why an unroutable candidate never reaches - the list. `route` adjudicates for real at materialization time, and - may land somewhere else once it has the rule's full description. - - Order the bullets by your judgment of impact (frequency in the - source × severity of the issue × ease of enforcement). Don't - inflate the list, three high-quality candidates beat ten - speculative ones. - -7. **Offer materialization per bullet.** For each bullet, ask - whether the user wants to turn it into a real Taskless rule. On - yes, follow the `%(TASKLESS_CLI)s agent route` recipe you already - fetched when you learned the routing surface, the user's accepted - bullet becomes the rule description input. Re-fetch it only if it - has fallen out of context. - -8. **Decide when the rules run.** Once the user has materialized - what they want, rules exist that nothing runs yet. Settle that - before anything else, as part of onboarding rather than after it. - - Read the `ci` and `hooks` fields of the `detect --json` output you - already have, and tell the user what the repository has: which CI - systems are configured and which commit-hook tools, or that there - are none. Then offer the two ways the rules can run on their own: - - - **In CI**, on pushes and pull requests: follow - `%(TASKLESS_CLI)s agent ci`. - - **Before each commit**, on the staged files: follow - `%(TASKLESS_CLI)s agent hooks`. - - The user can pick either, both, or neither. Carry out what they - pick before moving on. If they pick neither, accept it and say in - one line that `%(TASKLESS_CLI)s check` then runs only when someone - invokes it. - -9. **Ask before marking onboarding complete.** When the user signals - they're done (they've materialized everything they want, decided - when the rules run, or said "that's enough for now"), explicitly - ask: "Do you want me to mark Taskless as onboarded? You won't be - re-asked to onboard until you pass `--force`." Ask it in a message - with no other question in it. A "yes" to a message that asks two - things is not consent to either, so never pair this question with - the previous step's offer. On explicit yes, and only on explicit - yes, run: + `` is your provisional routing call, one of `legacy`, + `sg`, `vale`, or `runtime`. `route` decides for real when you write + the rule, and may land it somewhere else. Three high-quality + candidates beat ten speculative ones. Note, for the report, how + many review comments and pull requests each one came from and how + often the pattern was fixed in the window you mined. + +7. **Write the rules.** For every candidate whose destination is + `legacy`, `sg`, or `vale`, follow the `%(TASKLESS_CLI)s agent route` + recipe you already fetched, with the candidate as the rule + description, and write it. Do not ask before each one. + + A candidate you judge better solved by a runtime rule is not + written: runtime rules need an account, and onboarding stays + logged out. If `route` sends a rule toward a runtime or + login-gated recipe, stop there for that rule. Keep it for the + report, where it appears as a Taskless Cloud candidate. + +8. **Clarify only when stuck.** You may ask the user something only + when you are unsure what rules to write AND the report cannot be + built yet because there are fewer than three working Taskless + rules. Ask only what would let you write the missing rules, then + write them. If you can find good candidates on your own, do that + instead. + +9. **Mark onboarding complete.** Once the rules are written, run: ``` %(TASKLESS_CLI)s onboard --mark-complete ``` - Do NOT mark onboarding complete on your own initiative. Do NOT - run `--mark-complete` if the user is ambiguous, says "maybe - later", or stops responding. The flag is consent-gated. + Do not ask first. The user's yes in step 2 is the consent this + records, and it does not wait on the report. + +10. **Publish the report, and send feedback alongside it.** Follow + `%(TASKLESS_CLI)s agent report` to publish the report; it carries + the runtime candidates from step 7 as Taskless Cloud candidates. + In parallel, follow `%(TASKLESS_CLI)s agent rule-feedback` as the + respondent, reached from onboarding: answer from your own account + of this session, ask the user nothing, and send it. If the report + recipe refuses for too few rules, that is step 8's case. + +11. **Make the case for CI, last.** After the report summary has been + shown, offer to run `%(TASKLESS_CLI)s check` in this repository's CI + by following `%(TASKLESS_CLI)s agent ci`, naming the CI systems the + `ci` field of `detect --json` reported, or saying there are none. + Encourage it strongly: these rules only keep agents from + reintroducing what the report shows when something runs them on + every change, and CI is where every agent's work passes through. + On a yes, follow the `ci` recipe. On a no, name + `%(TASKLESS_CLI)s agent hooks` in one line as the way to run them + before each commit instead, and stop. ## Errors -| code | meaning | fix | -|---------------------|----------------------------------------------|--------------------------------------| -| `ALREADY_ONBOARDED` | `install.onboarded` is true and no `--force` | suggest `--force` or skip onboarding | +| code | meaning | fix | +|-----------------------|----------------------------------------------|------------------------------------------------------| +| `ALREADY_ONBOARDED` | `install.onboarded` is true and no `--force` | tell the user onboarding already ran | +| `REPORT_INSUFFICIENT` | fewer than three working Taskless rules | step 8: write more rules, asking only if you're stuck | ## See Also - `%(TASKLESS_CLI)s agent route`: the destination criterion; read it - before proposing candidates, and follow it to materialize an accepted one + before proposing candidates, and follow it to write each one +- `%(TASKLESS_CLI)s agent report`: publish the report onboarding ends with +- `%(TASKLESS_CLI)s agent rule-feedback`: the feedback sent alongside it - `%(TASKLESS_CLI)s agent detect`: what this repository already lints and authors, which bounds what a candidate can be -- `%(TASKLESS_CLI)s agent check`: validate newly created rules against the codebase -- `%(TASKLESS_CLI)s agent ci`: run the rules in CI once they exist +- `%(TASKLESS_CLI)s agent ci`: run the rules in CI, the closing offer - `%(TASKLESS_CLI)s agent hooks`: run the rules on staged files before each commit - `%(TASKLESS_CLI)s agent info`: inspect the current `.taskless/taskless.json` state diff --git a/packages/cli/src/agent/rule-feedback.md b/packages/cli/src/agent/rule-feedback.md index 50808d23..b383915b 100644 --- a/packages/cli/src/agent/rule-feedback.md +++ b/packages/cli/src/agent/rule-feedback.md @@ -1,11 +1,12 @@ -# Topic: rule-feedback (CLI v%(CLI_VERSION)s / topic v4) +# Topic: rule-feedback (CLI v%(CLI_VERSION)s / topic v5) ## You are here This is `rule-feedback`. It helps you turn what a user just said about Taskless into a survey response the CLI can send, and send it. -You reach it from the invite at the end of an authoring or onboarding -recipe, whether the user gave you their words, said `skip`, or said -`review`. +You reach it from the invite at the end of an authoring recipe, +whether the user gave you their words, said `skip`, or said `review`, +or directly from the onboarding recipe, which sends its feedback +without an invite. If that is not why you are reading this, re-run `%(TASKLESS_CLI)s agent` and find the topic you meant. A user who asked, on their own, to send feedback wants `%(TASKLESS_CLI)s agent feedback`; one reporting a bug @@ -21,7 +22,9 @@ before they are sent. ## Preconditions - The invite was put to the user and they replied, or did not. Their - words, if any, are the only input you take from them. + words, if any, are the only input you take from them. Or you came + from onboarding, where the user's yes to setting up Taskless already + covered sending this; see "Reached from onboarding" below. - The user did not ask you to send nothing. If they did, this is the wrong recipe: run `%(TASKLESS_CLI)s feedback dismiss` and continue with what they asked for. @@ -38,6 +41,22 @@ what you are running in, and which rule has earned its keep. You already know all of that. Do not put the survey's questions to the user one by one. +## Reached from onboarding + +The onboarding recipe sends you here with no invite, in parallel with +publishing the report. Its opening question already asked the user's +permission, so there is nothing to ask: + +- Omit `verbatim`; the user has not written you any feedback. +- Answer `ruleKind` with `none (onboarding)`, and every other key from + your own account of the onboarding session. +- Skip step 4. There was no `review`. +- Send, clean up, and return to the onboarding recipe without a + thank-you line, since the user was not asked for anything. + +`feedback send` still sends nothing when telemetry is disabled, and +says so; that is the user's opt-out holding, not a failure. + ## Steps 1. **Take the user's reply as it is, if there is one.** Whatever they diff --git a/packages/cli/src/commands/onboard.ts b/packages/cli/src/commands/onboard.ts index f2fa5533..aece50d6 100644 --- a/packages/cli/src/commands/onboard.ts +++ b/packages/cli/src/commands/onboard.ts @@ -32,16 +32,16 @@ export function getOnboardTrailer(args: { commandsInstalled: boolean; }): string { if (args.commandsInstalled) { - return `Next: in your AI tool, run /tskl onboard or ask it to use the Taskless skill (or run \`${getCliPrefix()} onboard\` from your terminal) to discover rule candidates from your codebase.`; + return `Next: in your AI tool, run /tskl onboard or ask it to use the Taskless skill (or run \`${getCliPrefix()} onboard\` from your terminal) to set up starter rules from your codebase and see a report of what they catch.`; } - return `Next: in your AI tool, ask it to use the Taskless skill (or run \`${getCliPrefix()} onboard\` from your terminal) to discover rule candidates from your codebase.`; + return `Next: in your AI tool, ask it to use the Taskless skill (or run \`${getCliPrefix()} onboard\` from your terminal) to set up starter rules from your codebase and see a report of what they catch.`; } export const onboardCommand = defineCommand({ meta: { name: "onboard", description: - "Discover rule candidates from your codebase and history (post-install flow)", + "Set up Taskless: discover and write starter rules from your codebase and history, then publish a report (post-install discovery flow)", }, args: { dir: { diff --git a/packages/cli/src/prompts/recipes.ts b/packages/cli/src/prompts/recipes.ts index 946674b8..47ecab84 100644 --- a/packages/cli/src/prompts/recipes.ts +++ b/packages/cli/src/prompts/recipes.ts @@ -388,8 +388,8 @@ function toolState(tools: HostTool[] | undefined, name: string): ToolState { } /** - * The PR-review entry in the onboard recipe's source menu, as a whole list - * item. + * The review-comment entry in the onboard recipe's source list, as a whole + * list item: pull requests through `gh`, or merge requests through `glab`. * * A WHOLE ITEM, connective included, for the reason written above * {@link runBlock}: the bullet's own marker and label are part of what changes @@ -397,36 +397,54 @@ function toolState(tools: HostTool[] | undefined, name: string): ToolState { * leave a bullet whose label contradicts its body. Every branch below is * valid markdown at the same nesting level. * - * The `absent` and `not-applicable` branches SAY WHY rather than dropping the - * bullet. `route.md` reached the same conclusion for the remote-generation - * tier it declines to offer: a reader who is not told reads the omission as an - * oversight and asks for it, which costs a turn and arrives back here. + * The unavailable branches SAY WHY rather than dropping the bullet. `route.md` + * reached the same conclusion for the remote-generation tier it declines to + * offer: a reader who is not told reads the omission as an oversight and asks + * for it, which costs a turn and arrives back here. Where both forges are + * ruled out, the reason no install can change comes first. + * + * A forge tool the caller did not mention is unmeasured, so anything short of + * both being ruled out renders the unconditioned default rather than a + * verdict read out of silence. */ -function prReviewSource(tools: HostTool[] | undefined): string { - switch (toolState(tools, "gh")) { - case "present": { - return `- **Recent PR review comments**: \`gh\` is on your PATH. +function reviewCommentSource(tools: HostTool[] | undefined): string { + const gh = toolState(tools, "gh"); + const glab = toolState(tools, "glab"); + + if (gh === "present") { + return `- **Recent PR review comments**: \`gh\` is on your PATH. Taskless looked for the file and did not run it, so treat that as -presence rather than as a working install. Suggest scanning the last -30 days of merged PRs for repeated reviewer feedback patterns.`; - } - case "absent": { - return `- **Recent PR review comments** are not on this menu: \`gh\` is -not on your PATH. Say that rather than passing over the source in -silence, and offer to pick it up if the user installs the GitHub CLI.`; - } - case "not-applicable": { - return `- **Recent PR review comments** are not on this menu: this -repository has no GitHub origin, so there are no pull requests to -mine. Installing the GitHub CLI does not change that. Say so rather -than passing over the source in silence.`; - } - default: { - return `- **Recent PR review comments**: reading merged PRs needs the -GitHub CLI (\`gh\`) or something equivalent. Suggest scanning the last -30 days of merged PRs for repeated reviewer feedback patterns.`; - } +presence rather than as a working install; \`detect --json\` names +the command that checks sign-in under \`agent\`. Scan the last 90 +days of merged PRs for repeated reviewer feedback patterns.`; } + if (glab === "present") { + return `- **Recent MR review comments**: \`glab\` is on your PATH. +Taskless looked for the file and did not run it, so treat that as +presence rather than as a working install; \`detect --json\` names +the command that checks sign-in under \`agent\`. Scan the last 90 +days of merged merge requests for repeated reviewer feedback patterns.`; + } + if (gh === "absent") { + return `- **Recent PR review comments** are not a source here: \`gh\` is +not on your PATH. Say so in one line rather than passing over the +source in silence.`; + } + if (glab === "absent") { + return `- **Recent MR review comments** are not a source here: \`glab\` +is not on your PATH. Say so in one line rather than passing over the +source in silence.`; + } + if (gh === "not-applicable" && glab === "not-applicable") { + return `- **Review comments** are not a source here: this repository's +origin is on neither GitHub nor GitLab, so there are no pull or merge +requests to mine. Installing a CLI does not change that. Say so in one +line rather than passing over the source in silence.`; + } + return `- **Recent PR review comments**: reading merged PRs needs the +GitHub CLI (\`gh\`), or the GitLab CLI (\`glab\`) for merge requests, +or something equivalent. Scan the last 90 days of merged PRs for +repeated reviewer feedback patterns.`; } /** @@ -465,7 +483,7 @@ function toolLine(tool: HostTool): string { * exists to prevent: a verdict read out of silence. * * Without that line the two passages contradict each other on a partial array. - * {@link prReviewSource} asks {@link toolState} about `gh` specifically and + * {@link reviewCommentSource} asks {@link toolState} about `gh` specifically and * renders the unmeasured default when the array does not mention it, while * this step would claim the enumeration was exhaustive. */ @@ -556,7 +574,10 @@ export function buildVariables( // title, connective and all — for the reason `runBlock` documents above, // and never post-stripped, which could not promise the default rendering // is byte-for-byte the text a consumer with no host receives. - SOURCE_PR_REVIEW: indentBlock(prReviewSource(options.hostTools), " "), + SOURCE_PR_REVIEW: indentBlock( + reviewCommentSource(options.hostTools), + " " + ), HOST_TOOLS: indentBlock(hostToolsStep(options.hostTools), " "), }; if (content.includes("%(INPUT_SCHEMA)s")) { diff --git a/packages/cli/src/survey/constants.ts b/packages/cli/src/survey/constants.ts index b4c71a62..f61f5e21 100644 --- a/packages/cli/src/survey/constants.ts +++ b/packages/cli/src/survey/constants.ts @@ -159,9 +159,12 @@ export const COMPLETED_CHOICES = ["Yes", "No", "Unknown"] as const; /** * The recipes that carry the invite. Used by the gate alone; the events do not * name the recipe, because the survey contract has no slot for it. + * + * `onboard` is not one. Its user agrees to onboarding feedback with the + * recipe's opening yes, and the recipe sends that feedback itself; an invite + * appended to it would ask a question that has already been answered. */ export const SURVEYED_TOPICS: ReadonlySet = new Set([ - "onboard", "create-sg-rule", "create-vale-rule", "create-remote-rule", diff --git a/packages/cli/test/feedback-recipes.test.ts b/packages/cli/test/feedback-recipes.test.ts index d6d83b4a..5e6f0d66 100644 --- a/packages/cli/test/feedback-recipes.test.ts +++ b/packages/cli/test/feedback-recipes.test.ts @@ -88,6 +88,23 @@ describe("the rule-feedback recipe", () => { rendered.indexOf(`${invocation} feedback send`) ); }); + + // Onboarding sends its feedback with no invite: the user's yes to setting + // up Taskless already covered it, so the branch asks nothing. + it("sends unprompted when reached from onboarding", () => { + const rendered = getRecipe("rule-feedback", { invocation }) ?? ""; + const branch = rendered + .slice( + rendered.indexOf("## Reached from onboarding"), + rendered.indexOf("## Steps") + ) + .replaceAll(/\s+/g, " "); + expect(branch).toContain("Omit `verbatim`"); + expect(branch).toContain("`none (onboarding)`"); + expect(branch).toContain("there is nothing to ask"); + // The opt-out still holds. + expect(branch).toContain("still sends nothing when telemetry is disabled"); + }); }); describe("the feedback invite", () => { diff --git a/packages/cli/test/host-tools.test.ts b/packages/cli/test/host-tools.test.ts index cf8c2351..3242d903 100644 --- a/packages/cli/test/host-tools.test.ts +++ b/packages/cli/test/host-tools.test.ts @@ -186,17 +186,34 @@ describe("onboard recipe renders host-tool state", () => { it("omits PR review comments with a reason when gh is not on PATH", () => { const rendered = onboardWith({ present: false, applicable: true }); - expect(rendered).toContain("**Recent PR review comments** are not on this"); + expect(rendered).toContain( + "**Recent PR review comments** are not a source here" + ); expect(rendered).toContain("not on your PATH"); - expect(rendered).not.toContain("30 days of merged PRs"); + expect(rendered).not.toContain("90 days of merged PRs"); }); - it("gives the unfixable reason first for a repository with no GitHub origin", () => { + it("gives the unfixable reason first for a repository on neither forge", () => { // Present AND inapplicable: the case the precedence rule exists for. - const rendered = onboardWith({ present: true, applicable: false }); + const rendered = renderWith([ + { + name: "gh", + present: true, + applicable: false, + reason: "this repository has no GitHub origin", + }, + { name: "git", present: true, applicable: true }, + { name: "jq", present: false, applicable: true }, + { + name: "glab", + present: true, + applicable: false, + reason: "this repository has no GitLab origin", + }, + ]); - expect(rendered).toContain("repository has no GitHub origin"); - expect(rendered).toContain("Installing the GitHub CLI does not change"); + expect(rendered).toContain("on neither GitHub nor GitLab"); + expect(rendered).toContain("Installing a CLI does not change"); // Telling this user to install `gh` is the failure mode, and it is worse // than silence because they can act on it and still get nowhere. expect(rendered).not.toContain("**Recent PR review comments**: `gh` is on"); @@ -352,10 +369,10 @@ describe("the prompts export renders the full menu with no options", () => { expect(rendered).toContain("**Codebase TODOs / FIXMEs**"); expect(rendered).toContain("**Agent-memory files**"); expect(rendered).toContain("**Recent PR review comments**"); - expect(rendered).toContain("**Bug-tracker tickets**"); + expect(rendered).toContain("**Trackers and engineering context**"); // Nothing was measured, so nothing may be asserted either way. expect(rendered).not.toContain("on your PATH"); - expect(rendered).not.toContain("are not on this menu"); + expect(rendered).not.toContain("are not a source here"); // And the probe instruction the CLI has not displaced here is still the // right one, because no answer has been supplied. expect(rendered).toContain("Confirm a tool exists before promising a scan"); @@ -363,13 +380,47 @@ describe("the prompts export renders the full menu with no options", () => { it("names no single bug tracker as the expected one", () => { const rendered = getRecipe("onboard") ?? ""; - const tracker = rendered.slice(rendered.indexOf("**Bug-tracker tickets**")); + const tracker = rendered.slice( + rendered.indexOf("**Trackers and engineering context**") + ); expect(tracker).toContain("examples of the class"); expect(rendered).not.toContain("(Linear, Jira, GitHub issues via"); }); }); +/** A GitLab repository's host tools, with `glab` as given. */ +function gitlab(glab: Partial): HostTool[] { + return [ + { + name: "gh", + present: false, + applicable: false, + reason: "this repository has no GitHub origin", + }, + { name: "git", present: true, applicable: true }, + { name: "jq", present: true, applicable: true }, + { name: "glab", present: true, applicable: true, ...glab }, + ]; +} + +describe("the review-comment source covers GitLab", () => { + it("mines merge requests when glab is present on a GitLab origin", () => { + const rendered = renderWith(gitlab({})); + expect(rendered).toContain("**Recent MR review comments**: `glab` is on"); + expect(rendered).toContain("did not run it"); + }); + + it("says glab is missing on a GitLab origin without it", () => { + const rendered = renderWith(gitlab({ present: false })); + expect(rendered).toContain( + "**Recent MR review comments** are not a source here: `glab`" + ); + // The GitHub reason is true but is not the one this user can act on. + expect(rendered).not.toContain("on neither GitHub nor GitLab"); + }); +}); + describe("detectAgentHints", () => { it("hints only for tools that are present and applicable", () => { const hints = detectAgentHints([ diff --git a/packages/cli/test/installed-documentation.test.ts b/packages/cli/test/installed-documentation.test.ts index 8e0832a7..1d478d1c 100644 --- a/packages/cli/test/installed-documentation.test.ts +++ b/packages/cli/test/installed-documentation.test.ts @@ -186,6 +186,20 @@ describe("this repository's own installed Taskless docs", () => { } }); + it("keeps the skill body within its 150-line cap", async () => { + // `skill-taskless` caps the body at 150 lines of markdown. The cap guards + // against unbounded growth, not against guidance the router needs: when + // the body needs the room, raise the cap rather than compress the text. + // Counted the way a reader would, every line after the closing `---`, + // blank lines included. + const skill = await readFile( + resolve(repositoryRoot, "skills", "taskless", "SKILL.md"), + "utf8" + ); + const body = skill.replace(/^---\n[\s\S]*?\n---\n/, "").trimEnd(); + expect(body.split("\n").length).toBeLessThanOrEqual(150); + }); + it("keeps the skill's trigger text off the old layout too", async () => { // The skill description is what an agent reads before it opens anything, so // a stale directory name here sends it looking in a place that no longer diff --git a/packages/cli/test/onboard.test.ts b/packages/cli/test/onboard.test.ts index a024f3ad..e5a104b4 100644 --- a/packages/cli/test/onboard.test.ts +++ b/packages/cli/test/onboard.test.ts @@ -280,18 +280,18 @@ describe("onboard recipe establishes the routing surface first", () => { expect(stdout).toMatch(/detect --json/); }); - it("puts the routing step ahead of the bullet list, not after it", async () => { + it("puts the routing step ahead of writing rules, not after it", async () => { // The ordering is the whole fix. A recipe that mentions `route` somewhere - // is what shipped before; what matters is that it is read while the list - // is still being decided. + // is what shipped before; what matters is that it is read while the + // candidates are still being decided. const { stdout } = await runCli(["agent", "onboard", "-d", cwd], cwd); const routing = stdout.indexOf("Learn the routing surface"); - const synthesis = stdout.indexOf("Synthesize the bullet list"); + const writing = stdout.indexOf("Write the rules"); expect(routing).toBeGreaterThan(-1); - expect(synthesis).toBeGreaterThan(-1); - expect(routing).toBeLessThan(synthesis); + expect(writing).toBeGreaterThan(-1); + expect(routing).toBeLessThan(writing); }); it("carries the destination in the bullet format", async () => { @@ -316,68 +316,100 @@ describe("onboard recipe establishes the routing surface first", () => { }); }); -// #441: the recipe went from materializing rules straight to the -// consent-gated mark-complete question, so a project could finish onboarding -// with rules nothing runs. The agent that improvised the missing question put -// it in the same message as mark-complete, and the user's "yes" could have -// been read as an answer to either. -describe("onboard recipe decides when the rules run", () => { +// Topic v6: onboarding asks one question, runs logged out, completes itself, +// publishes a report, and ends on the CI offer. Each of these is a behavior an +// agent would otherwise improvise back toward the conversational v5 flow. +describe("onboard recipe asks one question and finishes on its own", () => { let cwd: string; + let stdout: string; beforeEach(async () => { - cwd = await mkdtemp(join(tmpdir(), "taskless-onboard-when-")); + cwd = await mkdtemp(join(tmpdir(), "taskless-onboard-v6-")); + ({ stdout } = await runCli(["agent", "onboard", "-d", cwd], cwd)); }); afterEach(async () => { await rm(cwd, { recursive: true, force: true }); }); - it("puts the step between materializing and marking complete", async () => { - const { stdout } = await runCli(["agent", "onboard", "-d", cwd], cwd); + /** The text of one numbered step, from its title to the next title. */ + function step(title: string): string { + const start = stdout.indexOf(title); + expect(start).toBeGreaterThan(-1); + const next = stdout.slice(start + title.length).search(/\n\d+\. \*\*/); + return next === -1 + ? stdout.slice(start) + : stdout.slice(start, start + title.length + next); + } + + it("is topic v6", () => { + expect(stdout).toMatch(/^# Topic: onboard {5}\(CLI v.+ \/ topic v6\)/m); + }); - const materialize = stdout.indexOf("Offer materialization per bullet"); - const decide = stdout.indexOf("Decide when the rules run"); - const markComplete = stdout.indexOf( - "Ask before marking onboarding complete" + it("asks the one question, set apart, through a question tool when there is one", () => { + const ask = step("Ask the one question"); + expect(ask).toContain( + "\n\n Do you want me to set up Taskless for you?\n\n" ); + expect(ask).toContain("structured question tool"); + expect(ask.replaceAll(/\s+/g, " ")).toContain( + "This is the only question you script." + ); + }); - expect(materialize).toBeGreaterThan(-1); - expect(decide).toBeGreaterThan(materialize); - expect(markComplete).toBeGreaterThan(decide); + it("mines sources without asking which", () => { + const mine = step("Mine every source you can reach"); + expect(mine).toContain("Do not ask the user which"); + expect(stdout).not.toContain("Are there other places I should look"); }); - it("offers CI and a hook from what detect reported", async () => { - const { stdout } = await runCli(["agent", "onboard", "-d", cwd], cwd); - const step = stdout.slice( - stdout.indexOf("Decide when the rules run"), - stdout.indexOf("Ask before marking onboarding complete") - ); + it("stays logged out and keeps runtime candidates for the report", () => { + const flat = stdout.replaceAll(/\s+/g, " "); + expect(flat).toContain("Onboarding runs logged out from start to finish"); + // Never an instruction to log in, only the one not to. + expect(flat).not.toMatch(/(? { + const clarify = step("Clarify only when stuck"); + expect(clarify).toContain("fewer than three working Taskless"); }); - it("asks the mark-complete question on its own", async () => { - const { stdout } = await runCli(["agent", "onboard", "-d", cwd], cwd); - const step = stdout.slice( - stdout.indexOf("Ask before marking onboarding complete") + it("marks onboarding complete without asking", () => { + const mark = step("Mark onboarding complete"); + expect(mark).toMatch(/onboard --mark-complete/); + expect(mark).toContain("Do not ask first."); + }); + + it("publishes the report and sends feedback alongside it", () => { + const publish = step("Publish the report, and send feedback alongside it"); + expect(publish).toMatch(/agent report`/); + expect(publish).toMatch(/agent rule-feedback`/); + expect(publish).toContain("ask the user nothing"); + expect(stdout.indexOf("Mark onboarding complete")).toBeLessThan( + stdout.indexOf("Publish the report") ); + }); - // Whitespace collapsed so a rewrap of the recipe does not fail the test. - expect(step.replaceAll(/\s+/g, " ")).toContain( - "Ask it in a message with no other question in it." + it("ends on a strongly encouraged CI offer, with hooks as the fallback", () => { + const ci = step("Make the case for CI, last"); + expect(stdout.indexOf("Publish the report")).toBeLessThan( + stdout.indexOf("Make the case for CI") ); + expect(ci).toMatch(/agent ci`/); + expect(ci).toContain("`ci` field of `detect --json`"); + expect(ci).toContain("Encourage it strongly"); + expect(ci).toMatch(/agent hooks`/); }); - it("names both topics in See Also", async () => { - const { stdout } = await runCli(["agent", "onboard", "-d", cwd], cwd); + it("names its neighbors in See Also", () => { const seeAlso = stdout.slice(stdout.indexOf("## See Also")); - - expect(seeAlso).toMatch(/agent ci`/); - expect(seeAlso).toMatch(/agent hooks`/); + for (const topic of ["route", "report", "rule-feedback", "ci", "hooks"]) { + expect(seeAlso).toMatch(new RegExp(`agent ${topic}\``)); + } }); }); diff --git a/packages/cli/test/survey-cadence.test.ts b/packages/cli/test/survey-cadence.test.ts index 4285a564..c4d0e95a 100644 --- a/packages/cli/test/survey-cadence.test.ts +++ b/packages/cli/test/survey-cadence.test.ts @@ -56,12 +56,13 @@ describe("survey constants", () => { expect(COMPLETED_CHOICES).toEqual(["Yes", "No", "Unknown"]); }); - it("surveys the four authoring and onboarding recipes only", () => { + // `onboard` sends its own feedback under the user's opening yes, so an + // invite appended to it would ask a question already answered. + it("surveys the three authoring recipes only", () => { expect([...SURVEYED_TOPICS].toSorted()).toEqual([ "create-remote-rule", "create-sg-rule", "create-vale-rule", - "onboard", ]); }); diff --git a/packages/cli/test/survey-invite.test.ts b/packages/cli/test/survey-invite.test.ts index 3ec87fb2..bd050965 100644 --- a/packages/cli/test/survey-invite.test.ts +++ b/packages/cli/test/survey-invite.test.ts @@ -107,19 +107,18 @@ describe("the survey gate", () => { expect(seenAtTelemetryInit).toBe(NOW + SHOWN_INTERVAL_MS); }); - it.each([ - "onboard", - "create-sg-rule", - "create-vale-rule", - "create-remote-rule", - ])("surveys %s", async (topic) => { - expect(await surveyGateIsOpen({ topic, now: () => NOW })).toBe(true); - }); + it.each(["create-sg-rule", "create-vale-rule", "create-remote-rule"])( + "surveys %s", + async (topic) => { + expect(await surveyGateIsOpen({ topic, now: () => NOW })).toBe(true); + } + ); // Telemetry is on and the cadence is open here, so only the topic set // decides: the opt-in channels and the survey's own recipe never carry // the invite. - it.each(["feedback", "bug-report", "rule-feedback"])( + // `onboard` sends its feedback itself, under the user's opening yes. + it.each(["feedback", "bug-report", "rule-feedback", "onboard"])( "does not survey %s, even with the gate otherwise open", async (topic) => { expect(await surveyGateIsOpen({ topic, now: () => NOW })).toBe(false); @@ -138,9 +137,9 @@ describe("the survey gate", () => { it("opens the moment the window closes", async () => { await writeNextAsk(RULE_SURVEY_ID, NOW); - expect(await surveyGateIsOpen({ topic: "onboard", now: () => NOW })).toBe( - true - ); + expect( + await surveyGateIsOpen({ topic: "create-sg-rule", now: () => NOW }) + ).toBe(true); }); it("serves the bare recipe under the telemetry opt-out, without reading the cadence", async () => { @@ -211,17 +210,18 @@ describe("the survey gate", () => { ]); }); - it("taskless onboard carries the invite on the recipe path", async () => { + it("taskless onboard carries no invite on the recipe path", async () => { const command = onboardCommand as unknown as RunnableCommand; await command.run({ args: { dir: cwd, force: false, "mark-complete": false }, rawArgs: [], }); expect(printed()).toContain("# Topic: onboard"); - expect(printed()).toContain("## Before you finish"); - expect(capture).toHaveBeenCalledWith("survey shown", { + expect(printed()).not.toContain("## Before you finish"); + expect(capture).not.toHaveBeenCalledWith("survey shown", { $survey_id: RULE_SURVEY_ID, }); + expect(await readNextAsk(RULE_SURVEY_ID)).toBeUndefined(); }); it("taskless onboard --mark-complete does not", async () => { diff --git a/skills/taskless/SKILL.md b/skills/taskless/SKILL.md index b9c4a9df..fa1dfe69 100644 --- a/skills/taskless/SKILL.md +++ b/skills/taskless/SKILL.md @@ -63,7 +63,8 @@ linter, the `create-legacy-rule` path needs nothing installed. | First-time install | tell user to run `%(TASKLESS_CLI)s` | | Update an existing install | tell user to run `%(TASKLESS_CLI)s` | | Fix rules after an upgrade | `%(TASKLESS_CLI)s update` | -| Discover candidate rules | `%(TASKLESS_CLI)s agent onboard` | +| Set up / onboard Taskless | `%(TASKLESS_CLI)s agent onboard` | +| Make or delete a report | `%(TASKLESS_CLI)s agent report` | | Improve an existing rule | `%(TASKLESS_CLI)s agent improve-rule` | | Delete a rule | `%(TASKLESS_CLI)s agent delete-rule` | | Check code against rules | `%(TASKLESS_CLI)s agent check` |