Skip to content

feat(skill-installer): add spec-kit installer target#6

Open
CodeArtsAgent wants to merge 1 commit into
mainfrom
feat-skill-installer-speckit
Open

feat(skill-installer): add spec-kit installer target#6
CodeArtsAgent wants to merge 1 commit into
mainfrom
feat-skill-installer-speckit

Conversation

@CodeArtsAgent

@CodeArtsAgent CodeArtsAgent commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a new spec-kit target to the skill-installer meta-installer, bridging GitHub spec-kit (the specify CLI) into CodeArts.
  • Mirrors the existing openspec adapter: generates speckit-* SDD skills via a donor integration, copies them into .codeartsdoer/skills/, registers them, and tracks them via manifest for clean reversible uninstall.
  • Updates README.md and SKILL.md target lists/docs.

How it works

  • ensureUv() — detects uv (PATH + ~/.local/bin, ~/.cargo/bin); auto-installs via pip or the official installer if missing.
  • ensureCli()uv tool install --python 3.12 specify-cli; uv auto-provisions Python 3.12 (no hard system-Python gate). Resolves the specify binary via PATH then uv tool dir --bin.
  • generateDonorSkills()specify init proj --integration copilot --integration-options="--skills" in a temp dir, then recursively finds speckit-*/SKILL.md (donor-agnostic, resilient to layout changes).
  • ensureFrontmatter() — additively guarantees CodeArts-required name + description in each SKILL.md.
  • Full init / update / delete / status with manifest tracking + status-file registration.

Verification

  • node --check passes; installer.js list shows spec-kit.
  • End-to-end on macOS: init --project installed uv 0.11.29 + specify 0.13.0 (Python 3.12.13), generated/registered 10 speckit-* skills → status HEALTHY. delete --project removed all 10 skills, status entries, and manifest with no residue.

Notes

  • The E2E test under scripts/test/ was not added — that harness is not present in this checkout.
  • Heavy install steps (uv wheel build) can exceed short shell timeouts on slow networks; runs fine when allowed to complete.

Summary by CodeRabbit

  • New Features

    • Added Spec Kit as a supported skill-installer target.
    • Added project and user support for initializing, updating, deleting, and checking Spec Kit skills.
    • Spec Kit skills are generated and installed automatically, with installed content and health status tracked.
  • Documentation

    • Updated supported-target documentation with Spec Kit commands, scope, skills, and setup requirements.
    • Documented automatic uv installation and Python 3.12 provisioning.

Bridge GitHub spec-kit (specify CLI) into CodeArts via a new target adapter.
Generates speckit-* SDD skills using the copilot --skills donor, normalizes
SKILL.md frontmatter, and tracks installs via manifest for clean uninstall.
Provisions Python 3.12 through uv (auto-installed if missing).
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The skill installer adds a spec-kit target, registers it, provisions uv and specify-cli, generates and installs Spec Kit skills, manages lifecycle commands, and documents supported scopes, commands, requirements, and layout.

Changes

Spec Kit integration

Layer / File(s) Summary
Target registration and documentation
README.md, skills/skill-installer/SKILL.md, skills/skill-installer/scripts/targets/index.js
Registers spec-kit as a supported target and documents its scopes, commands, requirements, and adapter layout.
Spec Kit toolchain and donor generation
skills/skill-installer/scripts/targets/spec-kit.js
Discovers or installs uv, provisions specify-cli with Python 3.12, runs specify init proj, locates generated skills, and normalizes their frontmatter.
Skill installation lifecycle
skills/skill-installer/scripts/targets/spec-kit.js
Implements skill copying, status updates, manifest creation, deletion, and health reporting for init, update, delete, and status commands.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SkillInstaller
  participant uv
  participant specify-cli
  participant SkillsDirectory
  SkillInstaller->>uv: Discover or install uv
  SkillInstaller->>uv: Install specify-cli with Python 3.12
  SkillInstaller->>specify-cli: Run specify init proj
  specify-cli-->>SkillInstaller: Return generated speckit-* skills
  SkillInstaller->>SkillsDirectory: Copy skills and write manifest
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a new spec-kit installer target to skill-installer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-skill-installer-speckit

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
skills/skill-installer/scripts/targets/spec-kit.js (1)

145-163: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Predictable temp directory name — minor insecure-temp-file pattern.

spec-kit-installer-${action}-${process.pid} under os.tmpdir() is guessable (PID-based), and generateDonorSkills does rmDir then mkdirSync on that fixed path rather than a unique random directory. On a shared multi-user host this is a classic race/symlink-pre-plant hazard (CWE-377) before the recursive rmDir/copy runs.

🔒 Use a securely-generated unique temp dir
 function installSkills(ctx, action, specifyVersion) {
   const { skillsDir, statusFile } = ctx;
-  const tmpDir = path.join(os.tmpdir(), `spec-kit-installer-${action}-${process.pid}`);
+  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `spec-kit-installer-${action}-`));
   const { cmd } = ensureCli(action === 'update');
   const { skillDirs, skillNames } = generateDonorSkills(tmpDir, cmd);
 function generateDonorSkills(tmpDir, specifyCmd) {
   console.log(`==> Generating Spec Kit skills (donor: ${DONOR_INTEGRATION} ${DONOR_OPTIONS})...`);
-  rmDir(tmpDir);
-  fs.mkdirSync(tmpDir, { recursive: true });
   run(`${specifyCmd} init proj --integration ${DONOR_INTEGRATION} --integration-options=${q(DONOR_OPTIONS)}`, {

Also applies to: 165-181

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/skill-installer/scripts/targets/spec-kit.js` around lines 145 - 163,
Replace the predictable PID-based temporary directory used by
generateDonorSkills and the related caller flow with a securely generated unique
directory under os.tmpdir(), using the platform temp-directory API. Create it
atomically before writing into it, remove the preemptive rmDir of the shared
predictable path, and update cleanup to target the generated directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@skills/skill-installer/scripts/targets/spec-kit.js`:
- Around line 165-205: Update installSkills to read the existing manifest before
replacing it, identify previously tracked skill names absent from the newly
generated skillNames, remove their directories from skillsDir, and disable those
names in statusFile. Perform this reconciliation before writing the new
manifest, while preserving the current installation/update flow for retained and
newly generated skills.

---

Nitpick comments:
In `@skills/skill-installer/scripts/targets/spec-kit.js`:
- Around line 145-163: Replace the predictable PID-based temporary directory
used by generateDonorSkills and the related caller flow with a securely
generated unique directory under os.tmpdir(), using the platform temp-directory
API. Create it atomically before writing into it, remove the preemptive rmDir of
the shared predictable path, and update cleanup to target the generated
directory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c6a3114-9e7c-4b5c-af63-01f25537e8e3

📥 Commits

Reviewing files that changed from the base of the PR and between 7d7d91f and 73f0283.

📒 Files selected for processing (4)
  • README.md
  • skills/skill-installer/SKILL.md
  • skills/skill-installer/scripts/targets/index.js
  • skills/skill-installer/scripts/targets/spec-kit.js

Comment on lines +165 to +205
function installSkills(ctx, action, specifyVersion) {
const { skillsDir, statusFile } = ctx;
const tmpDir = path.join(os.tmpdir(), `spec-kit-installer-${action}-${process.pid}`);
const { cmd } = ensureCli(action === 'update');
const { skillDirs, skillNames } = generateDonorSkills(tmpDir, cmd);

if (!fs.existsSync(skillsDir)) fs.mkdirSync(skillsDir, { recursive: true });

console.log(`\n==> ${action === 'update' ? 'Overwriting' : 'Installing'} skills...`);
for (const dir of skillDirs) {
const name = path.basename(dir);
const dest = path.join(skillsDir, name);
cpDir(dir, dest);
ensureFrontmatter(dest, name);
console.log(` ${name}`);
}

console.log('\n==> Registering skills...');
enableSkills(statusFile, skillNames);

console.log('\n==> Writing manifest...');
const manifestFile = manifestFileFor(skillsDir);
const files = [];
for (const name of skillNames) {
const dir = path.join(skillsDir, name);
if (fs.existsSync(dir)) collectFiles(dir, files);
}
writeManifest(manifestFile, {
installedAt: new Date().toISOString(),
target: 'spec-kit',
specifyVersion,
skillsDir,
skillNames,
files
});
console.log(` Manifest saved: ${files.length} files tracked.`);

rmDir(tmpDir);
console.log(`\n==> Done! Spec Kit skills ${action === 'update' ? 'updated' : 'installed'}. Restart CodeArts to apply.`);
console.log(' Use the /speckit.* commands (constitution, specify, plan, tasks, implement) in your agent.');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

update() never cleans up skills removed/renamed by a newer spec-kit version — causes residue.

installSkills copies the freshly generated skillNames, enables them, and overwrites the manifest with only that new list. If a future specify-cli release renames or drops a speckit-* skill, the old skill's directory is never removed, its name is never disabled in the status file (only delete() calls disableSkills), and the new manifest no longer tracks it — so a later delete won't clean it up either. This orphans files/status entries permanently, contradicting the "reversible ... without residue" goal stated in the PR objectives.

🧹 Reconcile against the previous manifest before overwriting
 function installSkills(ctx, action, specifyVersion) {
   const { skillsDir, statusFile } = ctx;
   const tmpDir = path.join(os.tmpdir(), `spec-kit-installer-${action}-${process.pid}`);
   const { cmd } = ensureCli(action === 'update');
   const { skillDirs, skillNames } = generateDonorSkills(tmpDir, cmd);

   if (!fs.existsSync(skillsDir)) fs.mkdirSync(skillsDir, { recursive: true });

+  const manifestFile = manifestFileFor(skillsDir);
+  const previousManifest = readManifest(manifestFile);
+  const staleNames = (previousManifest && Array.isArray(previousManifest.skillNames))
+    ? previousManifest.skillNames.filter(n => !skillNames.includes(n))
+    : [];
+  if (staleNames.length) {
+    console.log(`\n==> Removing skills no longer generated: ${staleNames.join(', ')}`);
+    for (const name of staleNames) {
+      const dir = path.join(skillsDir, name);
+      if (fs.existsSync(dir)) rmDir(dir);
+    }
+    disableSkills(statusFile, staleNames);
+  }
+
   console.log(`\n==> ${action === 'update' ? 'Overwriting' : 'Installing'} skills...`);
   for (const dir of skillDirs) {
     ...
   }

   console.log('\n==> Registering skills...');
   enableSkills(statusFile, skillNames);

   console.log('\n==> Writing manifest...');
-  const manifestFile = manifestFileFor(skillsDir);
   const files = [];

Also applies to: 219-222

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/skill-installer/scripts/targets/spec-kit.js` around lines 165 - 205,
Update installSkills to read the existing manifest before replacing it, identify
previously tracked skill names absent from the newly generated skillNames,
remove their directories from skillsDir, and disable those names in statusFile.
Perform this reconciliation before writing the new manifest, while preserving
the current installation/update flow for retained and newly generated skills.

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