Skip to content

Commit b6cbecd

Browse files
feat(sdd): simplify CLI with clone-on-demand flow and --sdd flag; remove path flags; add tests and docs; ensure scripts executable; support SPEC_KIT_REPO/REF
1 parent 4175343 commit b6cbecd

File tree

4 files changed

+382
-6
lines changed

4 files changed

+382
-6
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ logs/user_prompt_submit.json
1818

1919
# Bun build artifacts
2020
*.bun-build
21-
claude-code/hooks/*.bun-build
21+
claude-code/hooks/*.bun-build
22+
.code/

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,30 @@ This ensures consistent rule enforcement and discoverability, regardless of the
162162

163163
- Use the CLI to quickly scaffold and manage rules for your AI coding tools.
164164
- All rules follow a strict, documented structure for maximum compatibility.
165-
- The rules registry bridges the gap for tools that can't parse frontmatter, ensuring all rules are discoverable and actionable.
165+
- The rules registry bridges the gap for tools that can't parse frontmatter, ensuring all rules are discoverable and actionable.
166+
167+
---
168+
169+
## Spec-Driven Development (SDD)
170+
171+
Use the Spec Kit workflow to drive a spec → plan → tasks process via slash commands (Claude Code) or manual scripts.
172+
173+
Quickstart (simple):
174+
- Ensure Git and bash are available (macOS/Linux/WSL). Initialize a repo in your project if needed.
175+
- Install SDD assets into a project (clones Spec Kit automatically to a temp folder):
176+
- `node create-rule.js --sdd --targetFolder=<project>`
177+
- Optional: set `SPEC_KIT_REPO` (and `SPEC_KIT_REF`) to point to a fork/branch.
178+
- Claude Code users: open the project and run `/specify "Your feature"`. Commands are installed at `.claude/commands`.
179+
- Or run manually:
180+
- `bash scripts/create-new-feature.sh --json "Your feature"`
181+
- `bash scripts/setup-plan.sh --json` (must be on `^[0-9]{3}-` feature branch)
182+
- `bash scripts/check-task-prerequisites.sh --json`
183+
184+
Artifacts are created under `specs/<feature-branch>/`:
185+
- `spec.md`, `plan.md`, `research.md`, `data-model.md`, `contracts/`, `quickstart.md`, and `tasks.md`.
186+
187+
Troubleshooting:
188+
- Not on feature branch: `/plan` and `/tasks` require a `^[0-9]{3}-` branch (the scripts will error clearly).
189+
- Scripts not found: Ensure you ran `--sdd` and that `scripts/` exists in your project.
190+
- Templates missing: Re-run `--sdd`. Existing files with different content are backed up as `.bak`.
191+
- JSON outputs: All SDD scripts support `--json` and print stable JSON with key paths for automation.

create-rule.js

Lines changed: 136 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,24 +410,42 @@ async function main() {
410410
const toolArg = args.find(arg => arg.startsWith('--tool='));
411411
const targetFolderArg = args.find(arg => arg.startsWith('--targetFolder='));
412412
const homeDirArg = args.find(arg => arg.startsWith('--homeDir='));
413-
const isNonInteractive = !!toolArg;
413+
const sddShortcut = args.includes('--sdd');
414+
const isNonInteractive = !!toolArg || sddShortcut;
414415

415416
let tool = toolArg ? toolArg.split('=')[1] : null;
416417
let targetFolder = targetFolderArg ? targetFolderArg.split('=')[1] : null;
417418
let overrideHomeDir = homeDirArg ? homeDirArg.split('=')[1] : null;
418419

420+
// Convenience: allow --sdd without specifying a tool
421+
if (!tool && sddShortcut) {
422+
tool = 'sdd';
423+
}
424+
419425
if (!tool) {
420426
const answers = await inquirer.prompt([
421427
{
422428
type: 'list',
423429
name: 'tool',
424430
message: 'Select the tool:',
425-
choices: Object.keys(TOOL_CONFIG),
431+
choices: [...Object.keys(TOOL_CONFIG), 'sdd'],
426432
},
427433
]);
428434
tool = answers.tool;
429435
}
430436

437+
// Handle SDD-only installation mode (no rules), copying assets from spec-kit into a project
438+
if (tool === 'sdd') {
439+
// Single, simple path: clone repo then copy assets
440+
const repo = process.env.SPEC_KIT_REPO || 'https://github.com/github/spec-kit.git';
441+
const ref = process.env.SPEC_KIT_REF || '';
442+
// pick destination
443+
const dest = targetFolder || (await inquirer.prompt([{ type: 'input', name: 'sddDest', message: 'Project folder for Spec Kit (SDD) assets:', validate: (v) => !!v.trim() || 'Folder name required' }])).sddDest;
444+
const clonePath = await cloneSpecKit(repo, ref);
445+
await copySpecKitAssets(clonePath, dest);
446+
return;
447+
}
448+
431449
const config = TOOL_CONFIG[tool];
432450

433451
if (config.copyEntireFolder) {
@@ -568,4 +586,119 @@ async function main() {
568586
main().catch((err) => {
569587
console.error(err);
570588
process.exit(1);
571-
});
589+
});
590+
591+
// --- Spec Kit integration: clone-on-demand, then copy ---
592+
import { execSync } from 'child_process';
593+
594+
async function cloneSpecKit(repoUrl, ref = '') {
595+
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'spec-kit-'));
596+
const cloneDir = path.join(base, 'src');
597+
showProgress(`Cloning Spec Kit from ${repoUrl}`);
598+
execSync(`git clone --depth 1 ${repoUrl} ${cloneDir}`, { stdio: 'pipe' });
599+
if (ref && ref.trim()) {
600+
execSync(`git -C ${cloneDir} fetch origin ${ref} --depth 1`, { stdio: 'pipe' });
601+
execSync(`git -C ${cloneDir} checkout ${ref}`, { stdio: 'pipe' });
602+
}
603+
completeProgress('Cloned Spec Kit');
604+
return cloneDir;
605+
}
606+
async function copySpecKitAssets(specKitRoot, projectRoot) {
607+
const requiredFiles = {
608+
claudeCommands: [
609+
'.claude/commands/specify.md',
610+
'.claude/commands/plan.md',
611+
'.claude/commands/tasks.md',
612+
],
613+
scripts: [
614+
'scripts/create-new-feature.sh',
615+
'scripts/setup-plan.sh',
616+
'scripts/check-task-prerequisites.sh',
617+
'scripts/common.sh',
618+
'scripts/get-feature-paths.sh',
619+
'scripts/update-agent-context.sh',
620+
],
621+
templates: [
622+
'templates/spec-template.md',
623+
'templates/plan-template.md',
624+
'templates/tasks-template.md',
625+
'templates/agent-file-template.md',
626+
],
627+
memory: [
628+
'memory/constitution.md',
629+
],
630+
};
631+
632+
const exists = (p) => fs.existsSync(p);
633+
const src = (rel) => path.join(specKitRoot, rel);
634+
const dst = (rel) => path.join(projectRoot, rel);
635+
636+
if (!exists(specKitRoot)) {
637+
throw new Error(`[SDD] Spec Kit path not found: ${specKitRoot}`);
638+
}
639+
640+
console.log(`\n⠋ Installing Spec Kit (SDD) assets from: ${specKitRoot}`);
641+
642+
// Ensure directories
643+
const ensureParent = (p) => fs.mkdirSync(path.dirname(p), { recursive: true });
644+
const copyFileIdempotent = (from, to, makeExecutable = false) => {
645+
ensureParent(to);
646+
if (exists(to)) {
647+
const a = fs.readFileSync(from);
648+
const b = fs.readFileSync(to);
649+
if (Buffer.compare(a, b) === 0) {
650+
return 'skipped';
651+
}
652+
fs.copyFileSync(to, `${to}.bak`);
653+
}
654+
fs.copyFileSync(from, to);
655+
if (makeExecutable) {
656+
try { fs.chmodSync(to, 0o755); } catch {}
657+
}
658+
return 'copied';
659+
};
660+
661+
// Commands
662+
for (const rel of requiredFiles.claudeCommands) {
663+
const from = src(`.${path.sep}${rel.split('/').slice(1).join(path.sep)}`); // map .claude/commands under spec-kit root
664+
const to = dst(rel);
665+
if (!exists(from)) {
666+
// In spec-kit, commands live at .claude/commands
667+
const alt = src(rel);
668+
copyFileIdempotent(exists(alt) ? alt : from, to);
669+
} else {
670+
copyFileIdempotent(from, to);
671+
}
672+
}
673+
674+
// Scripts (executable)
675+
for (const rel of requiredFiles.scripts) {
676+
const from = src(rel);
677+
const to = dst(rel);
678+
copyFileIdempotent(from, to, true);
679+
}
680+
681+
// Templates
682+
for (const rel of requiredFiles.templates) {
683+
const from = src(rel);
684+
const to = dst(rel);
685+
copyFileIdempotent(from, to);
686+
}
687+
688+
// Memory
689+
for (const rel of requiredFiles.memory) {
690+
const from = src(rel);
691+
const to = dst(rel);
692+
copyFileIdempotent(from, to);
693+
}
694+
695+
// Optional quickstart doc (generated)
696+
const quickstartPath = dst('docs/sdd-quickstart.md');
697+
ensureParent(quickstartPath);
698+
if (!exists(quickstartPath)) {
699+
const qs = `# Spec-Driven Development (SDD) Quickstart\n\n- Requires: Git, bash (macOS/Linux/WSL).\n- Commands live in \`.claude/commands\` for Claude Code.\n\nWorkflow:\n- /specify → creates branch + \`specs/###-.../spec.md\`\n- /plan → fills \`plan.md\` and generates research/data model/contracts/quickstart\n- /tasks → creates \`tasks.md\` from available docs\n\nRun manually (if needed):\n- \`bash scripts/create-new-feature.sh --json \"My feature\"\`\n- \`bash scripts/setup-plan.sh --json\` (must be on feature branch)\n- \`bash scripts/check-task-prerequisites.sh --json\`\n\nBranch rule: must match \`^[0-9]{3}-\` for /plan and /tasks.\n`;
700+
fs.writeFileSync(quickstartPath, qs);
701+
}
702+
703+
console.log('\x1b[32m✓ Installed Spec Kit assets\x1b[0m');
704+
}

0 commit comments

Comments
 (0)