Skip to content

feat(cli): tell an agent what init changed, and that recipes are per-task - #329

Merged
thecodedrift merged 4 commits into
mainfrom
feat/init-upgrade-context
Sep 11, 2026
Merged

feat(cli): tell an agent what init changed, and that recipes are per-task#329
thecodedrift merged 4 commits into
mainfrom
feat/init-upgrade-context

Conversation

@thecodedrift

Copy link
Copy Markdown
Member

taskless init --no-interactive now tells the agent that runs it what happened and what to do next, and every recipe says it is not reusable across tasks. Two gaps from the self-healing-CLI notes: an agent that check sent to init got a write summary and an onboarding pointer, nothing about committing the rewrite or running update; and an agent that fetched agent check once kept using that text for the rest of the session, including after the upgrade that changed it.

What an agent sees after init. The human path prints an upgrade trailer after the install summary and before the onboarding line (which stays last): the directories that now hold changed files and that they belong in the next commit, plus, when the recorded CLI version moved, the transition and a pointer at taskless update. Nothing prints on a no-op re-install. The --json envelope gains the same as fields:

{
  "success": true,
  "commandsInstalled": true,
  "cliVersion": { "previous": "0.10.2", "installed": "0.11.1" },
  "targets": [
    { "dir": ".taskless", "mode": "canonical", "writtenSkills": ["taskless"], "writtenCommands": ["tskl.md"], "removedSkills": [], "removedCommands": [] },
    { "dir": ".claude", "mode": "reference", "writtenSkills": ["taskless"], "writtenCommands": ["tskl.md"], "removedSkills": [], "removedCommands": [] }
  ],
  "changed": true,
  "migrated": { "...": "unchanged, present only when a migration ran" }
}

targets is the per-target summary that used to go to stderr under --json. cliVersion.previous is null on a project with no recorded install. changed is true when a migration ran, any target wrote or removed something, or the version moved (a version move rewrites install.cliVersion in a tracked file, so it counts even when no skill bytes did).

One behavior change worth reading. The canonical .taskless/ store was rewritten on every install and reported as written every time, so a no-op re-install was indistinguishable from an upgrade in the summary. writeCanonicalSkill/writeCanonicalCommand now compare bytes and skip an identical file, returning { path, changed }. One apply-install-plan test that pinned the unconditional rewrite is replaced by two: identical bytes are not reported, changed bytes are. The SCAFFOLD_MIGRATION_REQUIRED refusal names init --no-interactive when stdout is not a TTY, since a bare init under a pipe prints the topic index and migrates nothing.

Recipes and the skill. Every packages/cli/src/agent/*.md gets a byte-identical second line saying the text was resolved at fetch time and the next task fetches again; stripHeader now drops the block through the first blank line, so header: false renderings carry neither the version nor the directive and the body is unchanged. The init recipe (topic v2) is rewritten for the agent: the non-interactive invocation, the envelope, then commit, update after a version move, and treating the current session's skills as stale. SKILL.md gains a short section with the same directive (body at 80 lines, the cap) and tskl.md a fourth step.

The OpenSpec change is archived here. Four specs were modified; I diffed requirement and scenario headings before and after the archive and nothing was dropped. The cli-agent format requirement also had a stale .txt spelling corrected to .md in passing. Independent of #328.

@thecodedrift thecodedrift left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be parameterized, because if someone uses the raw recipes, this statement many not be true.

We said this so often, that honestly the agent subcommand should just add this as a header. It's specific to the agent subcommands

Comment thread openspec/changes/archive/2026-09-10-init-upgrade-context/design.md Outdated
Comment thread openspec/changes/archive/2026-09-10-init-upgrade-context/design.md Outdated
Comment thread openspec/changes/archive/2026-09-10-init-upgrade-context/design.md Outdated
Comment thread packages/cli/src/agent/auth.md Outdated
Comment thread packages/cli/src/agent/check.md Outdated
Comment thread packages/cli/src/agent/update.md Outdated
Comment thread packages/cli/src/agent/verify-rule.md Outdated
Comment thread packages/cli/src/prompts/recipes.ts
Comment thread skills/taskless/SKILL.md Outdated
Comment thread skills/taskless/SKILL.md Outdated
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @thecodedrift — "We said this so often, that honestly the agent subcommand should just add this as a header…"

Agreed and done in d832a8f: the directive is rendered by agent (and update/onboard, which serve the same text) via RecipeOptions.directive, not baked into the recipe files, and the prompts export never carries it. All 20 per-file threads resolved with that. The two SKILL.md "needs placeholder" threads are still open with a question about which placeholder you mean.

— AI Coding Agent

…task

`init --no-interactive` ends with an upgrade trailer naming the directories
that hold changed files and, after a version move, pointing at `update`.
The --json envelope carries the same as `cliVersion`, `targets`, and
`changed`. A canonical file whose bytes already match the bundle is no
longer rewritten or reported, so a no-op re-install reads as one. The
migration refusal names `init --no-interactive` for a piped caller, and
the init recipe is rewritten for the agent that runs it.

Every recipe carries a fetch-time directive on its second line, stripped
with the version under `header: false`; the skill and tskl command say a
recipe is fetched again for each task.

Archives openspec change init-upgrade-context.
- `init` is the batch install in every context; `--no-interactive` is
  dropped and a bare TTY invocation calls the wizard directly.
- The fetch-time directive is rendered by the `agent`, `update`, and
  `onboard` commands rather than written into recipe files, and now
  also says a session that installed or upgraded holds a stale skill.
  The prompts export does not carry it.
- The upgrade trailer prints directly after the summary, before the
  reload banner.
- The init recipe tells the agent to name what needs committing rather
  than to stage it.
- The TTY-dependent refusal wording is reverted: `init` is the answer
  everywhere.

Re-archives the change with the revised deltas.
…sources

The canonical write searched the prose for the literal `npx @taskless/cli`
and rewrote it for nightly/dev builds. That is whitespace-sensitive: a
wrapped line or a doubled space escaped the rewrite silently. The sources
now carry the recipes' token and `renderInvocationPlaceholder` substitutes
it exactly at install; a source test forbids the literal.

Re-archives the change with the added requirement.
@thecodedrift
thecodedrift force-pushed the feat/init-upgrade-context branch from 873abe5 to 5b981e9 Compare September 11, 2026 00:00
@thecodedrift
thecodedrift marked this pull request as ready for review September 11, 2026 00:00
@thecodedrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @thecodedrift's task in 7m 59s —— View job


Review complete

Review mode: incremental — read 28 prior review thread(s) before reviewing.

  • Read .prior-review.json (28 threads)
  • Fetch PR diff via gh pr diff (2599 lines across 43 files)
  • Run finder angles (line-by-line, removed-behavior, cross-file, reuse/simplification/efficiency, altitude, CLAUDE.md conventions)
  • Verify candidates
  • Post findings

Treated as already addressed (not repeating):

  • All 20 per-recipe-file "this should be parameterized" threads (auth.md, check.md, ci.md, create-legacy-rule.md, create-remote-rule.md, create-runtime-rule.md, create-sg-rule.md, create-vale-rule.md, delete-rule.md, detect.md, improve-rule.md/.anonymous.md, info.md, init.md, onboard.md, route.md, rule-meta.md, rule.md, update.md, verify-rule.md) — the directive line was pulled out of every recipe file and is now rendered by the agent/update/onboard commands via RecipeOptions.directive (confirmed: none of those files appear in the current diff any more).
  • The three design.md threads (dropping --no-interactive this release, agent init vs init naming, upgrade-trailer-before-onboarding ordering) — all reflected in the current design.md/init.ts content.
  • packages/cli/src/prompts/recipes.ts:372 (where to add the directive) — resolved via RecipeOptions.directive/fetchTimeDirective.
  • Both skills/taskless/SKILL.md "needs placeholder" threads — resolved via the %(TASKLESS_CLI)s token and renderInvocationPlaceholder.

New findings, both posted inline:

  1. packages/cli/src/index.ts:102 — The bare-invocation dispatch to the interactive wizard dropped the process.env.CI === "true"/"1" guard that shouldRunInteractively() used to enforce (that whole function, and the CI check with it, was deleted from commands/init.ts). The new path launches runWizard on TTY detection alone (stdout.isTTY && stdin.isTTY). A grep -rn "process.env.CI" packages/cli/src finds nothing post-change — the safety net wasn't relocated, it's just gone. Environments that present a pseudo-TTY on both streams while still being CI (docker run -it, some self-hosted runners) would now hang on the interactive wizard instead of falling through to the batch path. No test exercises this either way.

  2. packages/cli/src/commands/init.ts:113 — The new upgrade trailer's update pointer is built from buildInvocation() (the fixed build-time invocation) rather than getCliPrefix(), which is this codebase's established convention for a "message a human reads" and adapts to the actual launcher (pnpm dlx, yarn dlx, …) via processLauncherContext(). This is the same principle the PR itself relies on for the recipe directive ("the command an agent is told to re-run is the one that served it," per design.md), just not applied here. A project reached via pnpm dlx @taskless/cli init would see the trailer suggest the npx form instead. Noting there's a pre-existing precedent for buildInvocation() in similar messages elsewhere (filesystem/migrate.ts), so this may be a deliberate/consistent choice rather than new — flagging for a second look regardless.

Everything else checked out: the changed/targets/cliVersion envelope logic matches the new cli-init spec exactly, writeIfChanged's byte-comparison correctly gates what's reported as written, the stripHeader/addDirective header-block handling behaves correctly for all real recipe content (the two code paths never actually compose, so the directive-aware stripHeader search is effectively unexercised, but it's harmless), and the OpenSpec archive/spec deltas line up with the implementation.

Comment thread packages/cli/src/index.ts Outdated
Comment thread packages/cli/src/commands/init.ts Outdated
… in the upgrade trailer

Dropping --no-interactive moved the wizard launch into index.ts and left
behind the CI check init used to apply: a pseudo-terminal in CI would
have launched the wizard into a job nobody is watching. The guard is now
a pure shouldLaunchWizard with its own tests.

The upgrade trailer's update pointer uses getCliPrefix(), the convention
for messages a person reads, rather than the build's fixed invocation.
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @claude — "Review complete … New findings, both posted inline"

Both addressed in 74f65a6: the CI guard on the wizard is restored as a tested shouldLaunchWizard, and the upgrade trailer's update pointer uses getCliPrefix(). Threads replied to and resolved.

— AI Coding Agent

@thecodedrift
thecodedrift merged commit 1391aa6 into main Sep 11, 2026
6 checks passed
@thecodedrift
thecodedrift deleted the feat/init-upgrade-context branch September 11, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant