feat(skill-installer): add spec-kit installer target#6
Conversation
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).
📝 WalkthroughWalkthroughThe skill installer adds a ChangesSpec Kit integration
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
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
skills/skill-installer/scripts/targets/spec-kit.js (1)
145-163: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winPredictable temp directory name — minor insecure-temp-file pattern.
spec-kit-installer-${action}-${process.pid}underos.tmpdir()is guessable (PID-based), andgenerateDonorSkillsdoesrmDirthenmkdirSyncon 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 recursivermDir/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
📒 Files selected for processing (4)
README.mdskills/skill-installer/SKILL.mdskills/skill-installer/scripts/targets/index.jsskills/skill-installer/scripts/targets/spec-kit.js
| 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.'); | ||
| } |
There was a problem hiding this comment.
🗄️ 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.
Summary
spec-kittarget to theskill-installermeta-installer, bridging GitHub spec-kit (thespecifyCLI) into CodeArts.openspecadapter: generatesspeckit-*SDD skills via a donor integration, copies them into.codeartsdoer/skills/, registers them, and tracks them via manifest for clean reversible uninstall.README.mdandSKILL.mdtarget 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 thespecifybinary via PATH thenuv tool dir --bin.generateDonorSkills()—specify init proj --integration copilot --integration-options="--skills"in a temp dir, then recursively findsspeckit-*/SKILL.md(donor-agnostic, resilient to layout changes).ensureFrontmatter()— additively guarantees CodeArts-requiredname+descriptionin each SKILL.md.init/update/delete/statuswith manifest tracking + status-file registration.Verification
node --checkpasses;installer.js listshowsspec-kit.init --projectinstalled uv 0.11.29 + specify 0.13.0 (Python 3.12.13), generated/registered 10speckit-*skills →statusHEALTHY.delete --projectremoved all 10 skills, status entries, and manifest with no residue.Notes
scripts/test/was not added — that harness is not present in this checkout.Summary by CodeRabbit
New Features
Documentation
uvinstallation and Python 3.12 provisioning.