-
Notifications
You must be signed in to change notification settings - Fork 0
feat(prompts): export route from @taskless/cli/prompts #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3377152
docs(openspec): the export covers the chooser as well as its destinat…
thecodedrift 337fbea
docs: a renamed requirement makes its delta a silent no-op
thecodedrift 97c0a36
feat(prompts): export route, so a consumer stops writing its own chooser
thecodedrift 0ee3819
feat(prompts): mechanics: false, so a consumer without a CLI gets a c…
thecodedrift a24de96
fix(prompts): the connective belongs to the substitution, not the tem…
thecodedrift 081c675
docs(openspec): mechanics gets a requirement, and raw names two kinds…
thecodedrift File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| --- | ||
| "@taskless/cli": patch | ||
| --- | ||
|
|
||
| `route` is now exported from `@taskless/cli/prompts`. | ||
|
|
||
| It was withheld because it contains local mechanics a service consumer cannot | ||
| run, and because a consumer could adjudicate ambiguous calls from the three | ||
| authoring recipes. Both halves were wrong in the same way. The mechanics are | ||
| real and a consumer ignores them, which is a smaller adaptation than restating | ||
| the criteria; and adjudicating from the destinations is what the platform | ||
| generator actually tried. It hand-wrote the same judgement, arrived at | ||
| `static | runtime` with nowhere to put `vale`, and generated every prose rule as | ||
| an ast-grep rule while its own delivery layer could already serve a Vale one. | ||
|
|
||
| Exporting a chooser without its destinations strands a consumer that can route | ||
| but not author. Exporting destinations without the chooser strands one that can | ||
| author but not route, and it writes its own chooser rather than stopping. | ||
|
|
||
| `getPrompt` gains `mechanics: false`, which replaces the two steps that gather | ||
| evidence by running the CLI with a statement of what the caller supplies | ||
| instead. `invocation` could not serve this: it substitutes a binary name inside | ||
| a command, so a consumer with no CLI rendered an instruction to run something | ||
| that does not parse. The default rendering is unchanged, byte for byte. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| schema: spec-driven | ||
| created: 2026-09-02 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| ## Why | ||
|
|
||
| `cli-knowledge-prompts` says `route` is not exported, and gives the reason: it | ||
| contains local mechanics a Worker cannot run, so until that changes a consumer | ||
| "gets each destination's own scope from these three and adjudicates a genuinely | ||
| ambiguous call itself." | ||
|
|
||
| That second half failed in production, and the platform generator is the | ||
| evidence. It adjudicated the ambiguous calls itself: it hand-wrote the same | ||
| judgement `route` describes, reached a two-value classification with no way to | ||
| name `vale`, and generated every prose rule as an ast-grep rule while its own | ||
| delivery layer could already serve a Vale one. It shipped a tier it could not | ||
| produce, and neither side noticed until the demonstration returned the wrong | ||
| one. | ||
|
|
||
| The local mechanics remain real and do not follow. A consumer ignores them, | ||
| which is a smaller and more honest adaptation than restating the criteria. | ||
|
|
||
| ## What Changes | ||
|
|
||
| **`route` joins `TOPICS`.** The requirement covers both halves of the decision | ||
| rather than the destinations alone: a consumer that can author for every engine | ||
| must also be able to reach the choice between them. | ||
|
|
||
| The spec's own argument already contains this. It says exporting a chooser | ||
| without its destinations strands a consumer that can route but not author. The | ||
| mirror is what we shipped, and it is worse in one specific way: a consumer | ||
| missing a destination stops, while a consumer missing the chooser writes its | ||
| own. | ||
|
|
||
| ## Capabilities | ||
|
|
||
| ### Modified Capabilities | ||
|
|
||
| - `cli-knowledge-prompts`: the exported-topics requirement covers the chooser as | ||
| well as the destinations, and records why the withholding argument did not | ||
| survive contact with a consumer. | ||
|
|
||
| ## Impact | ||
|
|
||
| - `openspec/specs/cli-knowledge-prompts/spec.md` — one requirement, amended. | ||
| - `packages/cli/src/prompts/index.ts` — `route` moves from `INTERNAL_TOPICS` to | ||
| `TOPICS`, shipping in the same PR. | ||
| - The completeness check (`TOPICS + INTERNAL_TOPICS` accounts for every canonical | ||
| recipe on disk) is unaffected: the topic moves between lists. |
85 changes: 85 additions & 0 deletions
85
openspec/changes/export-route-topic/specs/cli-knowledge-prompts/spec.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| ## MODIFIED Requirements | ||
|
|
||
| ### Requirement: Exported topics cover every engine a rule can be routed to | ||
|
|
||
| `TOPICS` SHALL export `route`, the recipe that chooses an authoring destination, and the authoring recipe for each engine it can choose — `create-sg-rule`, `create-vale-rule`, and `create-runtime-rule`. | ||
|
|
||
| A consumer that can decide a rule belongs to an engine must be able to reach the procedure for authoring one, and a consumer that can author for each engine must be able to reach the choice between them. Exporting either half alone strands the other, and the two are not symmetric in their consequences: a consumer missing a destination stops, while **a consumer missing the chooser writes its own**, which is the divergence this export exists to prevent. | ||
|
|
||
| `route` was withheld on the grounds that it contains local mechanics a service consumer cannot run, and that such a consumer could adjudicate a genuinely ambiguous call from the three destinations. The first is true and does not follow: a consumer ignores the mechanics, which is a smaller adaptation than restating the criteria. The second was tried and failed in production — the platform generator hand-wrote the same judgement, reached a two-value classification with no way to name `vale`, and generated every prose rule as an ast-grep rule while its own delivery layer could already serve a Vale one. | ||
|
|
||
| `engine-selection` leaves the export because it stops existing: the criterion it carried now lives in `route`, stated once. | ||
|
|
||
| #### Scenario: The chooser is reachable from the export | ||
|
|
||
| - **WHEN** a consumer imports `TOPICS` | ||
| - **THEN** it SHALL contain `route` | ||
| - **AND** `route` SHALL be a member of `PromptTopic` | ||
|
|
||
| #### Scenario: Every engine's authoring path is reachable from the export | ||
|
|
||
| - **WHEN** a consumer imports `TOPICS` | ||
| - **THEN** it SHALL contain `create-sg-rule`, `create-vale-rule`, and `create-runtime-rule` | ||
|
|
||
| #### Scenario: The exported set follows the rename | ||
|
|
||
| - **WHEN** a consumer imports `TOPICS` | ||
| - **THEN** it SHALL NOT contain `static` or `engine-selection`, neither of which names a recipe any more | ||
|
|
||
| ### Requirement: The export provides rendered and raw instruction accessors | ||
|
|
||
| The export SHALL provide `getInstructions(topic, options?)` and `getRawInstructions(topic, options?)`, each returning `{ text: string; variables: string[] }`. | ||
|
|
||
| `getInstructions` SHALL return finished text — the same string `getPrompt` returns for the same topic and options — alongside the names of the sprintf variables the topic's template contains. `getRawInstructions` SHALL return the **unrendered** template text alongside the same variable names, so a host that knows a value the package cannot know can render it itself. | ||
|
|
||
| **Not every variable is one a host can know.** The names a raw consumer receives cover two kinds: values a host may supply — `TASKLESS_CLI` and `PACKAGE_MANAGER_DLX`, each with an agent-fill marker as its default — and values only the package can resolve, which include the build version, the pinned engine capabilities, an input schema, and any pre-composed block a rendering option selects between. A host rendering the raw template itself SHALL substitute the first kind and SHALL take the second from a rendered accessor rather than composing it, because the package's own composition is what keeps the surrounding prose coherent. | ||
|
|
||
| `mechanics` and `header` affect the rendered form only. The raw template is what a topic contains before any option is applied, so it carries the placeholders unconditionally. | ||
|
|
||
| Both SHALL throw on a topic with no embedded recipe, matching `getPrompt`. The existing internal `getRecipe` accessor SHALL continue to return `undefined` for an unknown topic; the `agent` command distinguishes an unknown topic from a failure and cannot use a throwing accessor. | ||
|
|
||
| `variables` SHALL be identical between the two functions for the same topic, since both describe the same template. | ||
|
|
||
| #### Scenario: Rendered instructions match the existing accessor | ||
|
|
||
| - **WHEN** a consumer calls `getInstructions(t)` and `getPrompt(t)` for the same topic and options | ||
| - **THEN** `getInstructions(t).text` SHALL equal `getPrompt(t)` | ||
|
|
||
| #### Scenario: Raw instructions are re-renderable | ||
|
|
||
| - **WHEN** a consumer renders `getRawInstructions(t).text` with sprintf-js against the same variable values the package used | ||
| - **THEN** the result SHALL equal `getInstructions(t).text` | ||
|
|
||
| #### Scenario: An unknown topic throws | ||
|
|
||
| - **WHEN** either accessor is called with a topic that has no embedded recipe | ||
| - **THEN** it SHALL throw, rather than returning an empty or undefined result | ||
|
|
||
| #### Scenario: A package-composed variable is not a host-fillable one | ||
|
|
||
| - **WHEN** a consumer reads `variables` from a raw accessor | ||
| - **THEN** the list SHALL include names the package resolves itself, which a host cannot supply a correct value for | ||
|
|
||
| ## ADDED Requirements | ||
|
|
||
| ### Requirement: The host-bound steps are suppressible | ||
|
|
||
| `PromptOptions` SHALL accept `mechanics`, defaulting to `true`. When `false`, a recipe's steps that gather evidence by running this CLI SHALL render as a statement of what the caller supplies instead of as a command to run. | ||
|
|
||
| The substitution SHALL include the connective the step's own prose continues from, so the step reads as a sentence in both modes. Replacing only the command leaves the prose depending on a clause that is no longer there. | ||
|
|
||
| The evidence SHALL NOT be dropped along with the command. A recipe's criteria are stated in terms of what those steps gather, so a consumer told only that the step does not apply loses the inputs rather than the commands. | ||
|
|
||
| **Rationale.** `invocation` cannot serve this. It substitutes the launcher inside a command, so a consumer with no CLI that sets it to a phrase renders an instruction to execute something that does not parse — a malformed rendering, which is worse than either honest answer. | ||
|
|
||
| #### Scenario: The default renders the command | ||
|
|
||
| - **WHEN** a consumer calls `getPrompt(t)` for a topic with host-bound steps | ||
| - **THEN** the step SHALL render the command to run, unchanged from what the `agent` command prints | ||
|
|
||
| #### Scenario: Suppressing them names what the caller supplies | ||
|
|
||
| - **WHEN** a consumer calls `getPrompt(t, { mechanics: false })` | ||
| - **THEN** no command to run SHALL appear in those steps | ||
| - **AND** the step SHALL name the evidence the caller is expected to provide | ||
| - **AND** the surrounding prose SHALL remain grammatical |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Tasks | ||
|
|
||
| One PR, alongside the export it describes. | ||
|
|
||
| ## 1. Export it | ||
|
|
||
| - [x] 1.1 Move `route` from `INTERNAL_TOPICS` to `TOPICS`, and rewrite both doc comments so the old justification does not stand next to the new behaviour | ||
| - [x] 1.2 Confirm the completeness check still holds: the topic moved between lists rather than appearing or vanishing | ||
|
|
||
| ## 2. Say so in the spec | ||
|
|
||
| - [x] 2.1 Amend the exported-topics requirement to cover the chooser as well as the destinations | ||
| - [x] 2.2 Record why the withholding argument failed, in the requirement rather than only in a commit message. It was tried in production and produced a generator that could not name `vale` | ||
| - [x] 2.3 Carry every existing scenario into the MODIFIED delta, and verify by archiving on a scratch commit. A delta replaces a requirement rather than patching it | ||
| - [x] 2.4 Keep the requirement's TITLE unchanged. **Measured**: `openspec archive` matches a MODIFIED block to the standing requirement by title, so renaming it applies nothing — the standing text survives untouched and the delta is discarded, while `openspec validate --strict` still passes. The amended framing lives in the body instead | ||
|
|
||
| ## 3. Make the export usable by the consumer asking for it | ||
|
|
||
| - [x] 3.1 Add `mechanics: false`, which replaces the two CLI-running steps with a statement of what the caller supplies. `invocation` could not do this job: it substitutes a binary NAME inside a command, so a consumer with no CLI setting it to a phrase renders `Run: <phrase> detect --json` — an instruction to execute something that does not parse | ||
| - [x] 3.2 Keep the evidence rather than dropping the steps. The routing criteria are stated in terms of linters, languages, rule styles, `loggedIn` and `ghOwner`, so a consumer that reads "nothing here" loses the inputs and not just the commands | ||
| - [x] 3.3 Render the steps as whole blocks through the variable table rather than stripping them afterwards, so the default can be proved unchanged. **Measured byte-identical**: 18161 bytes before and after, `diff` clean | ||
| - [x] 3.4 Point `TASKLESS_CLI` and the new blocks at one invocation resolver, so the command inside a step and the command elsewhere in the recipe cannot disagree | ||
| - [x] 3.5 Absorb each step's connective into the substitution. Replacing only the command left `This returns` with nothing to refer to and `and note` as a sentence fragment — a malformed rendering, which is the defect this option removes rather than relocates. Asserted by a test rather than read once | ||
|
|
||
| ## 4. Close out | ||
|
|
||
| - [ ] 4.1 Archive the change |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.