Repository navigation
fix: give a reference stub a recovery path #202
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
2 commits
Select commit
Hold shift + click to select a range
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
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
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,35 @@ | ||
| --- | ||
| "@taskless/cli": patch | ||
| --- | ||
|
|
||
| Tell the reader how to restore a canonical file a reference stub cannot find. | ||
|
|
||
| The stub written into `.claude/`, `.cursor/`, `.opencode/`, and `.agents/` was | ||
| two sentences: this is a stub, read `.taskless/skills/<name>/SKILL.md`. When | ||
| that canonical file is not on disk, the agent does not fail to find a skill. It | ||
| finds the skill, follows it to a path that does not exist, and the stub says | ||
| nothing about what to do next. Command stubs had the identical shape and the | ||
| identical dead end. | ||
|
|
||
| Two ordinary situations produce it. An install writes untracked files, so a | ||
| worktree created before they are committed has the stub and not the canonical | ||
| file, which is how it was first hit. And a project that ignores | ||
| `.taskless/skills/` commits the stub and never the canonical file, permanently. | ||
| Nothing in the CLI causes the second case: `addToGitignore` is only ever called | ||
| with `.env.local.json`, `/sgconfig.yml`, and `.run/`. A repository that builds | ||
| the CLI makes that choice for itself, and this one does. | ||
|
|
||
| Both stubs now carry one more line naming the command that restores the file. | ||
| The command is `init` rather than a bare run, because a bare invocation | ||
| installs only from a TTY; without one it prints a preamble and hands off to | ||
| `agent`, which is precisely the context an agent reading a stub is in. And it | ||
| is this build's invocation rather than a hardcoded `npx @taskless/cli`, passed | ||
| through the same rewrite canonical content uses, so a `dev` or `self` build | ||
| names its own binary and a nightly names the nightly instead of sending someone | ||
| to install the released package over it. | ||
|
|
||
| Stub frontmatter still carries no version, and nothing added here varies per | ||
| release, so the footprint outside `.taskless` moves once and then holds. A stub | ||
| already on disk is rewritten once by the next install, detected on a fragment | ||
| of the sentence that is the same in every build so a prod install and a local | ||
| one do not rewrite each other's stubs on every run. |
58 changes: 58 additions & 0 deletions
58
openspec/changes/archive/2026-08-28-stub-recovery-instruction/proposal.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,58 @@ | ||
| ## Why | ||
|
|
||
| A reference stub is the only Taskless file most agents ever discover. Its body | ||
| is two sentences: this is a stub, read the canonical file. When the canonical | ||
| file is not on disk, the agent does not fail to find a skill. It finds the | ||
| skill, follows it to a path that does not exist, and the stub says nothing | ||
| about what to do next. | ||
|
|
||
| Two ordinary situations produce that state. An install writes untracked files, | ||
| so a second worktree created before they are committed has the stub and not the | ||
| canonical file. And a project that ignores `.taskless/skills/`, as this | ||
| repository does because it builds the CLI, commits the stub and never the | ||
| canonical file, permanently. | ||
|
|
||
| The recovery already exists: running the CLI reinstalls the scaffold. It is | ||
| not advertised at the one moment a reader needs it. | ||
|
|
||
| ## What Changes | ||
|
|
||
| - **Both stub builders emit a recovery sentence.** Skill stubs and command | ||
| stubs gain one line naming the command that restores the canonical file. | ||
| - **The command is the build's own.** The sentence is written in the published | ||
| `npx @taskless/cli` form and passed through the same invocation rewrite that | ||
| canonical content uses, so a `dev`/`self` build names its own binary and a | ||
| nightly names the nightly rather than the released package. | ||
| - **`init`, not a bare run.** A bare invocation installs only from a TTY; in a | ||
| non-interactive context it prints a preamble and hands off to `agent`, which | ||
| is exactly the context an agent reading a stub is in. `init` installs in | ||
| both. | ||
| - **A one-time rewrite for stubs already on disk.** Install rewrites a stub | ||
| whose body predates the instruction, detected on a build-independent | ||
| fragment so a prod build and a `dev` build never rewrite each other's stubs. | ||
|
|
||
| Stub content still carries nothing that varies per release, so the footprint | ||
| outside `.taskless` moves once and then stays put. | ||
|
|
||
| **Delivery is a single PR.** Two builders, one install predicate, tests, and a | ||
| spec delta. | ||
|
|
||
| ## Capabilities | ||
|
|
||
| ### Modified Capabilities | ||
|
|
||
| - `cli-init`: a reference stub tells the reader how to restore a canonical file | ||
| that is missing, rather than ending at a path that does not exist. | ||
|
|
||
| ## Impact | ||
|
|
||
| - **Modified**: `packages/cli/src/install/canonical.ts` (the recovery sentence | ||
| and the migration predicate), `packages/cli/src/install/install.ts` | ||
| (`referenceNeedsRewrite`), `packages/cli/test/canonical-store.test.ts`, | ||
| `packages/cli/test/apply-install-plan.test.ts`. | ||
| - **Modified**: `.agents/skills/taskless/SKILL.md`, this repository's own | ||
| committed stub, which is a live instance of the second situation above. | ||
| - **Unchanged**: stub frontmatter, which still carries no version, and the | ||
| canonical store, which is unaffected. | ||
|
|
||
| **Tracking:** taskless/cli#200 |
38 changes: 38 additions & 0 deletions
38
...pec/changes/archive/2026-08-28-stub-recovery-instruction/specs/cli-init/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,38 @@ | ||
| ## ADDED Requirements | ||
|
|
||
| ### Requirement: A reference stub says how to restore a missing canonical file | ||
|
|
||
| A reference stub's body SHALL state what to do when the canonical file it points at is not present, naming a command that restores it. A stub that only delegates leaves a reader who follows it at a dead end, and the two states that produce a missing canonical file (an install whose untracked files never reached this working directory, and a project that ignores the canonical store) are both reached without anyone doing anything wrong. | ||
|
|
||
| Skill stubs and command stubs SHALL carry the same instruction. | ||
|
|
||
| The command named SHALL be the one that runs the build which wrote the stub, resolved the same way canonical content resolves it. A stub emitted by a development or nightly build SHALL NOT direct the reader to the released package. | ||
|
|
||
| The command named SHALL install without an interactive terminal, since the reader of a stub is typically an agent in a non-interactive context. | ||
|
|
||
| Stub content SHALL carry nothing that varies per release, so that adding this instruction changes the footprint outside the canonical store exactly once. | ||
|
|
||
| A stub already on disk whose body predates this instruction SHALL be rewritten once by the next install, rather than waiting for its frontmatter to change. Detection of such a stub SHALL NOT depend on the build that wrote it, so that builds with different invocations do not rewrite one another's stubs. | ||
|
|
||
| #### Scenario: Stub names the command that restores a missing canonical file | ||
|
|
||
| - **WHEN** the CLI writes a skill stub or a command stub | ||
| - **THEN** its body SHALL say what to do if the canonical file does not exist | ||
| - **AND** SHALL name a command that restores it | ||
|
|
||
| #### Scenario: A development build points at itself | ||
|
|
||
| - **WHEN** a `dev` or `self` build writes a stub | ||
| - **THEN** the command named SHALL be that build's own invocation | ||
| - **AND** SHALL NOT be the released package | ||
|
|
||
| #### Scenario: Stub bytes do not move with the CLI version | ||
|
|
||
| - **WHEN** two installs of different CLI versions write a stub for the same skill | ||
| - **THEN** the two stubs SHALL be identical | ||
|
|
||
| #### Scenario: An older stub is rewritten once | ||
|
|
||
| - **WHEN** an install finds a stub whose body carries no recovery instruction | ||
| - **THEN** the install SHALL rewrite that stub | ||
| - **AND** a subsequent install SHALL leave the rewritten stub untouched |
22 changes: 22 additions & 0 deletions
22
openspec/changes/archive/2026-08-28-stub-recovery-instruction/tasks.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,22 @@ | ||
| Delivery shape: **single PR**. Two string builders, one install predicate, tests, and a spec delta. No unit of this is meaningful on its own. | ||
|
|
||
| ## 1. Fix | ||
|
|
||
| - [x] 1.1 Add the recovery sentence to `buildSkillStub` and `buildCommandStub` | ||
| - [x] 1.2 Route the invocation through `applyCliInvocation`, so a `dev`/`self`/`nightly` build names the binary that wrote the stub rather than the released package | ||
| - [x] 1.3 Name `init` rather than a bare run, which installs only from a TTY | ||
| - [x] 1.4 Add `stubPredatesRecovery` and let `referenceNeedsRewrite` rewrite such a stub once | ||
| - [x] 1.5 Update this repository's own committed stub | ||
|
|
||
| ## 2. Tests | ||
|
|
||
| - [x] 2.1 Both stubs carry the instruction and name the build's own invocation | ||
| - [x] 2.2 Stub bytes do not move with the CLI version | ||
| - [x] 2.3 A stub predating the instruction is rewritten; a current one is not | ||
|
|
||
| ## 3. Verification | ||
|
|
||
| - [x] 3.1 End to end: install into a scratch directory, delete the canonical file, read the stub | ||
| - [x] 3.2 `pnpm build`, `pnpm typecheck`, `pnpm lint`, `pnpm test` | ||
| - [x] 3.3 `pnpm openspec validate --all --strict` | ||
| - [x] 3.4 Changeset |
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
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.