Skip to content

feat(cli): Interactive init wizard#17

Merged
thecodedrift merged 15 commits intomainfrom
jakob/oss-4
Apr 17, 2026
Merged

feat(cli): Interactive init wizard#17
thecodedrift merged 15 commits intomainfrom
jakob/oss-4

Conversation

@thecodedrift
Copy link
Copy Markdown
Member

@thecodedrift thecodedrift commented Apr 17, 2026

Replace the non-interactive taskless init with a five-step @clack/prompts wizard that lets users pick install locations, choose optional skills, walk through the auth vs. anonymous tradeoff, and review a diff summary before any filesystem writes.

Running bare taskless in a TTY now delegates to init and launches the wizard. Non-TTY invocations (pipes, CI) and taskless init --no-interactive preserve the prior batch-install behavior.

This PR also lands two downstream pieces that unblock the CI story end-to-end:

  • taskless check accepts positional path arguments so CI pipelines can scan only the changed files on a PR. Missing paths (e.g. files deleted in a diff) are silently filtered, so raw git diff --name-only output can be piped in directly.
  • The taskless-ci skill ships with a real body. It teaches agents two reusable patterns (full scan / diff scan) and applies them to whichever CI system the user already runs. Rather than hardcoding a fixed enumeration, the skill lists common CI systems as hints and directs agents to recognize unlisted systems by pattern.
taskless check                                        # full scan (unchanged)
taskless check src/foo.ts src/bar.ts                  # only these paths
taskless check $(git diff --name-only main...HEAD)    # diff-only scan

Why now: taskless init is a user's first touchpoint, and the current silent batch-install misses chances to explain auth, confirm choices, and record state. The skill catalog needed a notion of optional skills to make space for taskless-ci. The CLI needed path-argument support so the CI skill could generate working diff-scan workflows. And telemetry needed CLI version slicing, which was starting to block deprecation planning. All four are adjacent enough to ship together.

What's included:

  • @clack/prompts wizard with intro banner, multi-select for locations and optional skills, auth tradeoff screen with optional device-code login, and diff summary with confirm on removals
  • Shared loginInteractive() extracted from auth login so the wizard and auth subcommand share one code path
  • New applyInstallPlan() installer: explicit target+skill list, state-based surgical deletion of only files recorded in the previous manifest (never glob-deletes)
  • Install state in .taskless/taskless.json under a new install field (migration 2). Keyed by target. Unknown manifest fields round-trip cleanly
  • taskless-ci skill with a full body teaching full-scan and diff-scan patterns, non-destructive config generation, no-auth defaults, and a gate against writing CI with no rules configured
  • cliVersion (build-time) and scaffoldVersion (from .taskless/taskless.json) on every telemetry event. New events: cli_init_completed and cli_init_cancelled { atStep }
  • taskless check <paths...> support for diff-only scanning
  • 169 tests passing (was 135)

Breaking: bare taskless now launches the wizard in a TTY. Scripted users should pass --no-interactive.

OpenSpec: openspec/changes/interactive-init-wizard/. Worth reading design.md for the prompt-library tradeoff, install-state schema rationale, half-block vs. braille banner decision (screen-reader accessibility drove the call), and the "patterns over enumeration" posture for the CI skill.

Areas to review carefully:

  • src/install/install.ts — the new applyInstallPlan runs alongside legacy installForTool. Both are used (wizard vs. --no-interactive). The guarantee that deletion only touches files in the previous manifest is what keeps user-owned skills directories safe.
  • src/filesystem/migrate.tsreadManifest/writeManifest now preserve unknown top-level fields. Migration 1 was updated to explicitly drop v0 legacy fields that used to be dropped implicitly.
  • src/wizard/index.tsWizardCancelled pattern: every clack prompt goes through ask() which throws on cancel Symbol, caught at the top level so no partial writes happen.
  • src/commands/check.ts — positional path extraction reads from rawArgs and skips flag values. Non-existent paths filtered before forwarding to sg scan.
  • skills/taskless-ci/SKILL.md — ships into other people's repos and is invoked by their agents. Designed as patterns-over-enumeration: the listed CI systems are hints, not a closed switch.

Refs OSS-4, OSS-3, TSKL-214

thecodedrift and others added 6 commits April 16, 2026 17:55
… wizard

Introduces SKILL_CATALOG in packages/cli/src/install/catalog.ts
classifying each bundled skill as optional or mandatory. Only
taskless-ci is optional; existing skills are mandatory. Extends the
vite build plugin with a bidirectional coverage check between the
skills/ source tree and the catalog.

Also adds the OpenSpec change artifacts (proposal, design, specs,
tasks) for OSS-4 — the interactive init wizard — and lifts
@clack/prompts and picocolors as direct CLI dependencies in
preparation for the wizard implementation.

Refs: OSS-4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the .taskless/taskless.json install state tracking required
by the interactive init wizard. Migration 2 seeds install: {} on
forward-migration and on fresh projects. readManifest/writeManifest
now preserve unknown top-level fields so future schema additions
don't collide with this change.

Adds src/install/state.ts with readInstallState, writeInstallState,
and computeInstallDiff for the surgical re-install flow the wizard
relies on. Also adds the taskless-ci skill as a placeholder — the
full skill body lands in OSS-3; for this change the skill exists
only to wire the optional-skill plumbing through the build.

Refs: OSS-4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…emetry

Three adjacent internals that support the wizard:

1. applyInstallPlan(cwd, plan) — wizard-facing installer that accepts
   an explicit target + skill list, reads previous install state,
   surgically deletes only files recorded in the prior manifest, writes
   the new selection, and updates taskless.json install state. Never
   glob-deletes, so user-owned files in tool skills directories are
   safe.

2. loginInteractive({ cwd }) — shared device-code login routine
   extracted from auth.ts. Both `auth login` and the upcoming wizard
   auth step call this one function, keeping the device-flow code
   non-duplicated.

3. Telemetry super-properties — every posthog.capture() now carries
   cliVersion (baked at build time via Vite __VERSION__) and
   scaffoldVersion (read from .taskless/taskless.json at init time,
   0 on missing). First step toward deprecation tracking.

Refs: OSS-4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the non-interactive taskless init with a five-step wizard:
intro + banner, install locations (multi-select with detected
pre-checked), optional skills (taskless-ci for now), auth explanation
with optional device-code login, and a diff summary with confirm on
removals.

Running `taskless` with no subcommand in a TTY now delegates to the
wizard. Non-TTY and `--no-interactive` preserve the prior batch-
install behavior (mandatory skills to every detected tool, or to
.agents/ fallback). The wizard cancels cleanly on Ctrl-C with no
filesystem writes and emits cli_init_cancelled with the step name.

Includes end-to-end tests: install + manifest, surgical removal on
re-run, cancel at each step, and non-interactive parity. Updates
README, init help text, and ships a changeset noting the breaking
default-behavior change.

Refs: OSS-4, OSS-3 (taskless-ci skill body follows), TSKL-214

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The migration runner was printing "Migrating to latest .taskless/
schema..." via a bare console.error, which broke the wizard's visual
tree (the line appeared between the last summary entry and the
outro without a connecting │).

Adds an optional onNotice callback to runMigrations /
ensureTasklessDirectory. The wizard passes log.info so the message
renders as a proper tree node. Non-wizard callers still get the
default console.error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 17, 2026 02:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR makes the CLI onboarding experience interactive by replacing the default taskless init behavior with a multi-step @clack/prompts wizard, while preserving the prior batch installer behind --no-interactive and non-TTY auto-detection. It also introduces an install manifest (.taskless/taskless.json v2) for precise diffing/removals, adds an optional placeholder skill (taskless-ci), and attaches cliVersion + scaffoldVersion to all PostHog telemetry events.

Changes:

  • Add an interactive init wizard (locations, optional skills, auth, summary/confirm) and delegate bare taskless (TTY) to init.
  • Introduce install-state tracking + migration (manifest v2) and a new plan-based installer (applyInstallPlan) with surgical removals.
  • Add taskless-ci as an optional bundled skill, and enrich telemetry with cliVersion/scaffoldVersion.

Reviewed changes

Copilot reviewed 46 out of 47 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
skills/taskless-ci/SKILL.md Adds placeholder optional taskless-ci skill definition.
pnpm-lock.yaml Adds @clack/prompts/picocolors lock entries and related dependency updates.
packages/cli/vite.config.ts Adds build-time check that skills/ directories match SKILL_CATALOG.
packages/cli/test/wizard-steps.test.ts Unit tests for the ask() cancel wrapper behavior.
packages/cli/test/wizard-integration.test.ts End-to-end wizard tests with mocked clack prompts and filesystem assertions.
packages/cli/test/telemetry.test.ts Expands telemetry tests to assert cliVersion and scaffoldVersion are included.
packages/cli/test/migrate-install.test.ts Tests migration v2 behavior and unknown-field preservation.
packages/cli/test/login-interactive.test.ts Tests extracted loginInteractive() short-circuit behavior.
packages/cli/test/install-state.test.ts Tests install-state diffing and read/write round-trips.
packages/cli/test/init-no-interactive.test.ts Integration tests for init --no-interactive behavior and non-TTY auto-detection.
packages/cli/test/apply-install-plan.test.ts Tests applyInstallPlan() writes/removals and “don’t delete unknown files” guarantee.
packages/cli/src/wizard/steps/summary.ts Renders diff summary and requires confirm when removals exist.
packages/cli/src/wizard/steps/optional-skills.ts Prompts for optional skills from the catalog.
packages/cli/src/wizard/steps/locations.ts Prompts for install locations with detected pre-selection and validation.
packages/cli/src/wizard/steps/auth.ts Auth tradeoff step that can invoke shared interactive login.
packages/cli/src/wizard/intro.ts Adds ASCII banner + CLI version rendering.
packages/cli/src/wizard/index.ts Orchestrates wizard flow, diffing, telemetry events, and installs.
packages/cli/src/wizard/ask.ts Converts clack cancel symbols into a thrown WizardCancelled.
packages/cli/src/telemetry.ts Adds cliVersion/scaffoldVersion super-properties to identify + capture.
packages/cli/src/install/state.ts Adds install-state read/write helpers and diff computation.
packages/cli/src/install/install.ts Adds plan-based installer with manifest-driven surgical deletions.
packages/cli/src/install/catalog.ts Introduces skill catalog with mandatory vs optional classification.
packages/cli/src/index.ts Delegates bare taskless (TTY) to init; non-TTY shows help.
packages/cli/src/help/init.txt Updates init help text to describe wizard + --no-interactive.
packages/cli/src/filesystem/migrations/0002-install.ts Migration v2: ensure install: {} exists in manifest.
packages/cli/src/filesystem/migrations/0001-init.ts Updates migration v1 to strip legacy v0 manifest fields explicitly.
packages/cli/src/filesystem/migrate.ts Preserves unknown manifest fields and registers migration v2.
packages/cli/src/filesystem/directory.ts Allows routing migration notices via onNotice callback.
packages/cli/src/commands/init.ts Rewrites init to run wizard by default and use non-interactive install path otherwise.
packages/cli/src/commands/auth.ts Refactors auth login to use shared loginInteractive().
packages/cli/src/auth/login-interactive.ts Extracts device-code login flow into shared helper.
packages/cli/package.json Adds @clack/prompts and picocolors runtime deps.
packages/cli/README.md Documents wizard behavior, breaking change, and --no-interactive.
openspec/changes/interactive-init-wizard/tasks.md Task tracking for the interactive init wizard implementation.
openspec/changes/interactive-init-wizard/specs/skill-ci/spec.md Spec for bundling and optional classification of taskless-ci.
openspec/changes/interactive-init-wizard/specs/cli-taskless-bootstrap/spec.md Spec for migration v2 and manifest schema changes.
openspec/changes/interactive-init-wizard/specs/cli-init/spec.md Spec for wizard + non-interactive init semantics and install-state behavior.
openspec/changes/interactive-init-wizard/specs/analytics/spec.md Spec for telemetry standard properties and new init events.
openspec/changes/interactive-init-wizard/proposal.md Proposal summary and scope for the change set.
openspec/changes/interactive-init-wizard/design.md Design rationale and tradeoffs for wizard, manifest, telemetry, and banner.
openspec/changes/interactive-init-wizard/.openspec.yaml Declares OpenSpec change metadata.
eslint.config.js Adds tmp/ to ESLint ignore patterns.
.taskless/taskless.json Updates repo manifest to v2 and adds install state.
.taskless/README.md Adds Taskless directory usage README for the repo.
.taskless/.gitignore Adds .env.local.json and sgconfig.yml ignore rules.
.claude/settings.json Updates Claude permissions allow-list.
.changeset/interactive-init-wizard.md Adds changeset describing the feature and breaking behavior change.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread skills/taskless-ci/SKILL.md
Comment thread pnpm-lock.yaml Outdated
Comment thread .claude/settings.json Outdated
Comment thread packages/cli/src/commands/init.ts Outdated
thecodedrift and others added 2 commits April 16, 2026 19:48
Adds positional path support to taskless check so CI workflows can
scan only the changed files on a PR instead of the whole project.

  taskless check                                 # full scan (unchanged)
  taskless check src/foo.ts src/bar.ts           # only these files
  taskless check $(git diff --name-only main)    # diff-only scan

Paths are resolved relative to the working directory and forwarded
to `sg scan`. Paths that do not exist on disk (deleted files in a
diff) are silently filtered so raw git-diff output can be piped in
without pre-filtering. If every supplied path is missing, the CLI
exits 0 with empty results instead of falling back to a full scan.

Required by the upcoming CI skill (OSS-3), which generates workflow
files that invoke taskless check with diff-only paths on PRs. Spec
delta lives in openspec/changes/interactive-init-wizard/specs/cli-check.

Refs OSS-4, OSS-3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the taskless-ci placeholder with agent-facing instructions.
The skill teaches two reusable patterns — full scan and diff scan —
and translates them onto whichever CI system the user already runs.

Rather than enumerating a fixed switch of CI systems, the skill
lists common ones (GitHub Actions, GitLab, CircleCI, Jenkins,
Azure, Bitbucket, Buildkite, Drone, Travis) as detection hints and
tells the agent to apply the same patterns to systems it recognizes
but that aren't listed. The skill is installed into other people's
repos, so patterns over enumeration is the right posture.

Constraints the skill enforces:

  * Writes new, standalone config files — never edits the user's
    existing CI config
  * Prefers the CI system's native include/import; when none exists,
    writes to .taskless/ci/<file> and tells the user the exact line
    to add to their main config
  * Gates CI setup on a clean local `taskless check` — refuses to
    write config when no rules are configured (an always-green CI
    check is a footgun)
  * Documents the no-auth default: `taskless check` needs no secrets
    or environment variables

No slash command for this skill — removes commandName from frontmatter
so nothing is installed to commands/tskl/.

Refs OSS-4, OSS-3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thecodedrift thecodedrift marked this pull request as ready for review April 17, 2026 08:09
thecodedrift and others added 3 commits April 17, 2026 01:14
Three threads rolled into one:

1. Banner upgrade: quad-block at 60 cols replaces the half-block render.
   Quad samples 2x source pixels per cell horizontally, giving crisper
   letterforms and a faithful rendition of the "7" mark in the same
   5-row footprint. Final color is a muted teal-cyan (#3A8B9C) — reads
   as "dim cyan" without the attribute-composition flashes that the
   chalk.dim(chalk.cyan(…)) form produces across multi-line strings.

2. Converter aspect fix: quad and shade modes were stretching vertically
   because the converter didn't account for terminal cells being 1:2.
   New formula:
     sourceHeight = sourceWidth * aspectRatio * rowsPerChar /
                    (2 * colsPerChar)
   Half mode was correct by accident (its 2x vertical source pixels
   cancel the 2x cell height); quad/shade now match that correctness.

3. chalk migration: replaces the picocolors + raw ANSI escape approach.
   Chalk v5's color detection runs at import time, which breaks when
   bundled by Vite (no TTY at build time) — chalk.level is baked in
   as 0. Re-detect at runtime via NO_COLOR / FORCE_COLOR / TERM /
   COLORTERM and overwrite chalk.level before first use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closer to the "dim cyan" look the earlier chalk.dim(chalk.cyan(…))
form was trying to produce, without the attribute-composition flashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four fixes from copilot review on PR #17:

- taskless-ci SKILL.md: add metadata.commandName: "-" to match the
  repo convention used by other no-command skills
  (taskless-create-rule-anonymous, taskless-improve-rule-anonymous,
  taskless-delete-rule). Spec + design updated to describe the
  convention rather than the absence of the field.

- pnpm-workspace.yaml: explicitly exclude tmp/** so pnpm no longer
  picks up tmp/ascii-tool/ (a gitignored local design artifact) as
  a workspace member. Regenerated pnpm-lock.yaml drops the stray
  importers.tmp/ascii-tool entry that would have broken installs
  for anyone cloning the repo.

- commands/init.ts: move `const start = Date.now()` to BEFORE the
  `await runNonInteractive(cwd)` call so durationMs reflects the
  actual install time instead of essentially zero.

- .claude/settings.json: replace the machine-specific absolute path
  in the find-grep allow rule with a repo-relative path so the
  entry works for other contributors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 53 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/commands/check.ts Outdated
Comment thread packages/cli/src/auth/login-interactive.ts Outdated
Comment thread openspec/changes/interactive-init-wizard/specs/cli-init/spec.md Outdated
Comment thread packages/cli/src/commands/init.ts
Comment thread packages/cli/src/index.ts
Comment thread packages/cli/src/filesystem/migrate.ts Outdated
- check: filterExistingPaths rejects paths escaping cwd so sg scan
  can't traverse outside the project
- login-interactive: silence getToken warnings to avoid corrupting
  clack UI mid-flow
- init/index: require stdin.isTTY (in addition to stdout.isTTY) for
  interactive mode; bare `taskless` now only delegates to the wizard
  when flags are limited to `-d`/`--dir` so --help/--version/--schema
  /--json fall through to citty's default handling
- migrate: re-read manifest from disk on migration failure instead of
  writing back the pre-run `raw` snapshot; preserves earlier
  successful migrations' output
- spec: align intro-banner requirement to `chalk` (matches impl)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 53 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/index.ts Outdated
Comment thread packages/cli/src/commands/check.ts Outdated
Comment thread openspec/changes/interactive-init-wizard/design.md Outdated
- index: forward full rawArgs to init so `-d <value>` isn't dropped;
  tighten positional-arg guard to skip values that follow `-d`/`--dir`
  so `taskless -d ./repo` actually reaches the wizard-delegation branch
- check: honor `--` end-of-options marker in extractPositionalPaths so
  paths beginning with `-` can be passed explicitly
- scan: insert `--` between sg's flags and the path list so ast-grep
  can't mis-parse a `-`-prefixed path as a flag
- check.test: regression test covering `check -- -weird.js`
- design: align renderIntro() description to chalk (matches impl and
  spec from the previous round)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 53 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/filesystem/migrate.ts
thecodedrift and others added 2 commits April 17, 2026 14:26
`readRawManifest()` read `.version` off the parsed JSON immediately,
which threw TypeError for valid-but-non-object JSON (notably `null`,
arrays, numbers, strings) and bypassed the intended "corrupt manifest
→ version 0" fallback.

Now call `isPlainObject(parsed)` before touching `.version`; any
non-object shape falls through to `{ version: 0, raw: {} }` so
migrations re-run cleanly. Regression test seeds `taskless.json` with
literal `null` and asserts a clean migration to version 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thecodedrift thecodedrift merged commit a6f2849 into main Apr 17, 2026
1 check passed
thecodedrift added a commit that referenced this pull request Apr 17, 2026
Four fixes from copilot review on PR #17:

- taskless-ci SKILL.md: add metadata.commandName: "-" to match the
  repo convention used by other no-command skills
  (taskless-create-rule-anonymous, taskless-improve-rule-anonymous,
  taskless-delete-rule). Spec + design updated to describe the
  convention rather than the absence of the field.

- pnpm-workspace.yaml: explicitly exclude tmp/** so pnpm no longer
  picks up tmp/ascii-tool/ (a gitignored local design artifact) as
  a workspace member. Regenerated pnpm-lock.yaml drops the stray
  importers.tmp/ascii-tool entry that would have broken installs
  for anyone cloning the repo.

- commands/init.ts: move `const start = Date.now()` to BEFORE the
  `await runNonInteractive(cwd)` call so durationMs reflects the
  actual install time instead of essentially zero.

- .claude/settings.json: replace the machine-specific absolute path
  in the find-grep allow rule with a repo-relative path so the
  entry works for other contributors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thecodedrift thecodedrift deleted the jakob/oss-4 branch April 17, 2026 21:37
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.

2 participants