-
Notifications
You must be signed in to change notification settings - Fork 0
feat(cli): reclaim update for the rules ledger, and record reconciliation
#196
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
3 commits
Select commit
Hold shift + click to select a range
66ead69
feat(cli): reclaim `update` for the rules ledger, and record reconcil…
thecodedrift 5a77fcd
fix(cli): validate the reconciliation input and honour --json on the …
thecodedrift 35bff14
test(cli): pin that a nightly and its release are the same version
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
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,19 @@ | ||
| --- | ||
| "@taskless/cli": minor | ||
| --- | ||
|
|
||
| `taskless update` now tells an agent what an upgrade changed for the rules already in a project, and records when that work is done. | ||
|
|
||
| **BREAKING for anyone scripting `taskless update`.** It used to mean "reinstall the skills non-interactively", which is what `taskless init --no-interactive` already does through the same code path, and what running `taskless` does on its own. Scripts relying on the old behavior should call `taskless init --no-interactive`. | ||
|
|
||
| The word is reclaimed for the job an agent actually needs. Running the CLI migrates the `.taskless/` layout and refreshes skills: that is the directory, and it is automatic. No migration can rewrite the rules themselves, and a rewriter that now requires a `fix`, or a rule whose matching semantics shifted under a new engine, is a question about content. An agent that has run a migration and watched it succeed will otherwise reasonably conclude the upgrade is finished. | ||
|
|
||
| `taskless update` with no flags serves a ledger: one section per release, in order, saying what that version means for existing rules. Sections are cumulative, and a version with nothing to do says so explicitly, because an agent cannot tell "nothing here" from "nobody wrote this". The first entry covers 0.11.0: the newly required `fix` on rewriters, Markdown's block-only grammar and its two opposite failure shapes, `sg run --lang` accepting alias spellings, and the matching-semantics changes that alter what a valid rule matches with no error at all. | ||
|
|
||
| `taskless update --reconciledTo=<version>` records that the walk finished, in a new `rules` section of `.taskless/taskless.json` alongside the existing `install`. It stores the CLI version and the ast-grep and Vale versions the rules are now valid against. | ||
|
|
||
| The two namespaces are separate because they drift. `install` records how the scaffold got here and moves on a skills refresh; `rules` records what the rules are valid against and moves only on a completed reconciliation. Keying rule work off `install.cliVersion` would let an agent skip entries it never performed, and it would fail quietly: the walk would report nothing to do while the rules stayed wrong. | ||
|
|
||
| The version is validated rather than trusted. A value ahead of the installed CLI is rejected, since this build carries no entries for it, and the marker is never moved backwards. | ||
|
|
||
| `taskless info --json` reports both namespaces, so an agent reads where to start from the same payload it already fetches. |
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 |
|---|---|---|
| @@ -1,48 +1,130 @@ | ||
| # Topic: update (CLI v%(CLI_VERSION)s / topic v1) | ||
| # Topic: update (CLI v%(CLI_VERSION)s / topic v2) | ||
|
|
||
| ## Goal | ||
| Update Taskless skills in the user's coding-agent tools to the | ||
| latest bundled version. Non-interactive, no wizard, no prompts. | ||
| Installs to all detected tool locations using the same logic as | ||
| `%(TASKLESS_CLI)s init --no-interactive`, but exposed as its own subcommand | ||
| so the agent can run it directly without explaining flags. | ||
| ## You are here | ||
| This is `update`. It tells you what an upgrade changed for the rules | ||
| ALREADY IN THIS PROJECT, and what to do about them. | ||
|
|
||
| It is not about installing. Running `%(TASKLESS_CLI)s` migrates the | ||
| `.taskless/` layout and refreshes the installed skills on its own. That | ||
| handles the DIRECTORY. This recipe handles the RULES, which no migration | ||
| can rewrite for you: a rewriter that now needs a `fix`, a rule whose | ||
| matching semantics shifted under a new engine, a rule that could now be | ||
| expressed better with a newly supported language. | ||
|
|
||
| An agent that has run a migration and watched it succeed will otherwise | ||
| reasonably conclude the upgrade is done. It is not. The directory is | ||
| migrated; the rules may still need work. | ||
|
|
||
| This is the right command when the user has Taskless already | ||
| installed and just wants to refresh to a new version (e.g. after | ||
| running `%(TASKLESS_CLI)s`). | ||
| ## Goal | ||
| Walk the ledger below from the version this project was last reconciled | ||
| to, up to the installed CLI, doing what each section says. Then record | ||
| that you finished. | ||
|
|
||
| ## Preconditions | ||
| - None at the user level. Works in any directory. | ||
| - For tool detection: at least one of `.claude/`, `.opencode/`, | ||
| `.cursor/`, or a related marker file exists. Otherwise installs | ||
| to `.agents/skills/` as a fallback. | ||
| - `.taskless/` exists. If it does not, there are no rules to reconcile. | ||
| - You can read `.taskless/taskless.json`. | ||
|
|
||
| ## Steps | ||
|
|
||
| ``` | ||
| %(TASKLESS_CLI)s update | ||
| ``` | ||
|
|
||
| The CLI: | ||
| 1. Detects installed tools (Claude Code, OpenCode, Cursor, etc.) | ||
| 2. Reads the previous install state from `.taskless/taskless.json` | ||
| 3. Computes the diff (skills/commands to add, remove) | ||
| 4. Rewrites the canonical `taskless` skill and `tskl` command in | ||
| `.taskless/`, and refreshes the reference stub in each detected | ||
| tool directory | ||
| 5. Converts any stale full copies or symlinks left by older installs | ||
| into stubs, and removes obsolete files from prior versions | ||
| 6. Updates the install manifest | ||
| 7. Prints a summary including what was written and removed | ||
| 1. **Find where to start.** Run: | ||
| ``` | ||
| %(TASKLESS_CLI)s info --json | ||
| ``` | ||
| and read `rules.reconciledTo`, plus `rules.engines.sg` and | ||
| `rules.engines.vale` for the engine versions the rules were built | ||
| against. | ||
|
|
||
| **No `rules.reconciledTo` at all** means this project has never | ||
| recorded a reconciliation. That is NOT the same as being behind. A | ||
| project created at the installed version has no history to walk, so | ||
| do nothing except step 4. Only walk sections if you can see an | ||
| earlier version to walk FROM. | ||
|
|
||
| 2. **Walk forward, in order.** Start at the section after | ||
| `reconciledTo` and continue to the installed version. Sections are | ||
| cumulative, never replaceable: a later one can depend on an earlier | ||
| one having run, so do not skip ahead to the newest. | ||
|
|
||
| 3. **Do the work each section names.** Each says what changed and what | ||
| it means for existing rules. A section that says there is nothing to | ||
| do means exactly that; it is a claim, not an oversight. | ||
|
|
||
| 4. **Record that you finished.** Run: | ||
| ``` | ||
| %(TASKLESS_CLI)s update --reconciledTo=<installed version> | ||
| ``` | ||
| Only after the walk is complete. A partial or abandoned walk must not | ||
| be recorded: the next agent will start from what you wrote and skip | ||
| everything you did not do, and it will look like there was nothing to | ||
| do. The CLI rejects a version it has no entries for, and refuses to | ||
| move the marker backwards, but it cannot tell whether you actually | ||
| read the sections. | ||
|
|
||
| This also records the engine versions the rules are now valid | ||
| against, which is what lets a later upgrade say what moved. | ||
|
|
||
| ## The ledger | ||
|
|
||
| ### Migrating to 0.11.0 | ||
|
|
||
| The vendored ast-grep moves from 0.41.0 to 0.45.2, and Vale is at | ||
| 3.18.0. Four things follow for existing rules. | ||
|
|
||
| **A rewriter now requires `fix`.** `SerializableRewriter.required` goes | ||
| from `["rule","id"]` to `["id","fix","rule"]`, so a `rewriters:` entry | ||
| without a `fix:` is now rejected where it was accepted before. | ||
|
|
||
| Run `%(TASKLESS_CLI)s verify` and it names the offending rewriter | ||
| directly. It cannot be auto-fixed: `fix` is replacement text, which is | ||
| authorial intent. A rewriter with no `fix` could never have done | ||
| anything, so this surfaces a rule that was already dead rather than | ||
| breaking one that worked. | ||
|
|
||
| **Markdown is now a language, with a narrow shape.** `language: | ||
| Markdown` parses. What it sees is the BLOCK tree only: `document`, | ||
| `section`, `atx_heading`, `setext_heading`, `fenced_code_block`, | ||
| `list_item`, `paragraph`. Everything inside a line collapses into one | ||
| opaque `inline` node, so there is no `link`, no `emphasis`, no | ||
| `strong_emphasis`. | ||
|
|
||
| Two failure shapes, and they are opposites. `kind: link` is a HARD | ||
| CONFIG ERROR: exit 8, `Kind \`link\` is invalid`, which aborts config | ||
| parsing and takes every other rule's report down with it. A pattern like | ||
| `[$T]($U)` is the quiet one: it parses, runs, exits 0, and matches | ||
| nothing forever. | ||
|
|
||
| So "no bare URLs" and "link text must not say click here" are not | ||
| ast-grep rules even now. Use Vale, which reads prose, and note that Vale | ||
| DOES see YAML frontmatter: with `scope: raw` it sees the keys too, and | ||
| `extends: occurrence` with `min: 1` can require a field to be present. | ||
|
|
||
| **`sg run --lang` accepts the alias spellings.** At 0.41.0 `--lang C++` | ||
| was rejected while `language: C++` parsed. At 0.45.2 both are accepted. | ||
| This affects the flag only, not a rule's `language:` field, so no rule | ||
| file needs changing. It is recorded because the divergence used to be | ||
| documented as a thing to work around. | ||
|
|
||
| **Matching semantics moved, and this is the part you cannot check | ||
| locally.** Metavariable bindings no longer leak across `nthChild` | ||
| siblings, nor out of a negated `not`, and root metavariables now match | ||
| comments. A valid, unchanged rule can match a DIFFERENT SET OF NODES | ||
| with no error and no warning. | ||
|
|
||
| There is no differential tooling yet. If a rule uses `nthChild`, a | ||
| negated `not`, or a bare root metavariable, re-run its fixtures and read | ||
| the findings rather than the exit code: the rule can still pass its | ||
| tests while reporting differently on real code. | ||
|
|
||
| ## Errors | ||
|
|
||
| `update` is non-interactive. On success it exits 0 and prints a | ||
| human-readable summary of skills/commands added, removed, or kept in | ||
| sync (same surface as `init --no-interactive`). On failure it exits | ||
| non-zero with the error message on stderr. | ||
| With `--json`, `--reconciledTo` failures emit `{ ok: false, code, message }`: | ||
|
|
||
| | code | meaning | fix | | ||
| |-----------------|--------------------------------------------------|------------------------------------------| | ||
| | `INVALID_INPUT` | version is ahead of this CLI, or moves backwards | reconcile to the installed version | | ||
|
|
||
| ## See Also | ||
|
|
||
| - `%(TASKLESS_CLI)s agent init`: interactive variant (wizard with prompts) | ||
| - `%(TASKLESS_CLI)s agent info`: verify what's installed and check staleness | ||
| - `%(TASKLESS_CLI)s agent check`: run every engine over the repo | ||
| - `%(TASKLESS_CLI)s agent improve-rule`: rewrite a rule the walk flagged | ||
| - `%(TASKLESS_CLI)s agent info`: what is installed, and staleness |
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.