Skip to content

feat(cli): Add warden skill installation to warden init#182

Open
dcramer wants to merge 9 commits intomainfrom
feat/init-skill-install
Open

feat(cli): Add warden skill installation to warden init#182
dcramer wants to merge 9 commits intomainfrom
feat/init-skill-install

Conversation

@dcramer
Copy link
Member

@dcramer dcramer commented Feb 19, 2026

warden init now installs the built-in warden skill alongside the config
and workflow scaffolding. In interactive mode it prompts with a default-yes
[Y/n]; with --force it installs unconditionally; in CI it skips silently.

When .claude/ exists in the repo, a .claude/skills/warden symlink is
created pointing to .agents/skills/warden for Claude Code compatibility.

Also restructures the init output into a sectioned layout (CONFIG, WORKFLOW,
SKILL) matching the visual style of the analysis flow, instead of flat
Created/Skipped lines.

Install the built-in warden skill during init with an interactive
prompt (default yes). Copies skill files from the package into
.agents/skills/warden/ and creates a .claude/skills/warden symlink
when .claude/ exists.

Restructures init output into sectioned layout (CONFIG, WORKFLOW,
SKILL) matching the analysis flow visual style.

Co-Authored-By: Claude <noreply@anthropic.com>
Use Node's cpSync({ recursive: true }) instead of a hand-rolled
directory copy helper. Matches the existing pattern in evals/runner.ts.

Co-Authored-By: Claude <noreply@anthropic.com>
Without force: true, cpSync silently skips existing files, which
means --force wouldn't actually refresh stale skill content.

Co-Authored-By: Claude <noreply@anthropic.com>
process.stderr.write(key === '\r' || key === '\n' ? 'y' : key);
process.stderr.write('\n');

if (key !== 'n') {
Copy link

Choose a reason for hiding this comment

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

Uppercase 'N' input not treated as decline

Medium Severity

The interactive skill install prompt checks key !== 'n' (lowercase only), so typing 'N' is not recognized as a decline and will proceed with installation. The [Y/n] prompt convention implies both cases of n mean "no." The check needs to be case-insensitive, e.g. comparing against key.toLowerCase().

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

False positive — readSingleKey() in src/cli/input.ts:24 already calls key.toLowerCase() before returning, so uppercase N is converted to n before the check.

Copy link
Member Author

Choose a reason for hiding this comment

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

False positive. readSingleKey() in src/cli/input.ts:24 already lowercases all input via resolve(key.toLowerCase()) before returning. The comparison against "n" is guaranteed to be case-insensitive.

@dcramer dcramer marked this pull request as ready for review February 19, 2026 02:32
existsSync follows symlinks and returns false for broken symlinks,
but symlinkSync throws EEXIST if the symlink file itself exists.
Use lstatSync to detect any symlink (broken or valid) before
attempting to create one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of just detecting broken symlinks to avoid EEXIST, actively
remove any existing symlink (broken or valid) and recreate it. Since
installWardenSkill is only called when the user wants installation
(--force or interactive confirm), a fresh symlink is always correct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
unlinkSync throws EISDIR for directories, causing the catch to
swallow the error and symlinkSync to crash with EEXIST. Use rmSync
with recursive+force to handle files, symlinks, and directories
uniformly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace lstatSync+rmSync guard with a single rmSync call.
force:true already handles missing paths silently, making the
lstatSync check unnecessary and avoiding behavior differences
across Node.js versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

reporter.text(` 1. Add a skill: ${chalk.cyan('warden add <skill-name>')}`);
reporter.text(` 2. Set ${chalk.cyan('WARDEN_ANTHROPIC_API_KEY')} in .env.local`);
reporter.text(` 3. Add ${chalk.cyan('WARDEN_ANTHROPIC_API_KEY')} to organization or repository secrets`);
reporter.text(` 1. Set ${chalk.cyan('WARDEN_ANTHROPIC_API_KEY')} in .env.local`);
Copy link

Choose a reason for hiding this comment

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

Misleading tip when skill skipped in non-interactive mode

Low Severity

When the skill doesn't exist but is skipped due to non-interactive mode (or user declining), filesCreated stays at 0 without the skill being installed. The filesCreated === 0 check then shows "All configuration files already exist" — which is factually wrong since the skill was never installed. This is a likely upgrade scenario: users with pre-existing config/workflow but no skill directory running warden init in CI get told everything is set up when it isn't.

Additional Locations (1)

Fix in Cursor Fix in Web

Use non-recursive rmSync first to handle files and symlinks (including
broken ones), then fall back to recursive rmSync for directories.
Fixes potential behavior difference with recursive rmSync on broken
symlinks across Node.js versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rmSync with force:true may silently skip broken symlinks because it
internally uses stat (which follows symlinks) to check existence.
Instead, attempt symlinkSync first and handle EEXIST by removing the
existing entry (unlinkSync for files/symlinks, rmSync for dirs) then
retrying.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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

Comments