specdacular 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/install.js ADDED
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const readline = require('readline');
7
+
8
+ // Colors
9
+ const cyan = '\x1b[36m';
10
+ const green = '\x1b[32m';
11
+ const yellow = '\x1b[33m';
12
+ const dim = '\x1b[2m';
13
+ const reset = '\x1b[0m';
14
+
15
+ // Get version from package.json
16
+ const pkg = require('../package.json');
17
+
18
+ // Parse args
19
+ const args = process.argv.slice(2);
20
+ const hasGlobal = args.includes('--global') || args.includes('-g');
21
+ const hasLocal = args.includes('--local') || args.includes('-l');
22
+ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
23
+ const hasHelp = args.includes('--help') || args.includes('-h');
24
+
25
+ const banner = '\n' +
26
+ cyan + ' ███████╗██████╗ ███████╗ ██████╗\n' +
27
+ ' ██╔════╝██╔══██╗██╔════╝██╔════╝\n' +
28
+ ' ███████╗██████╔╝█████╗ ██║ \n' +
29
+ ' ╚════██║██╔═══╝ ██╔══╝ ██║ \n' +
30
+ ' ███████║██║ ███████╗╚██████╗\n' +
31
+ ' ╚══════╝╚═╝ ╚══════╝ ╚═════╝' + reset + '\n' +
32
+ '\n' +
33
+ ' Specdacular ' + dim + 'v' + pkg.version + reset + '\n' +
34
+ ' Feature planning for existing codebases.\n';
35
+
36
+ console.log(banner);
37
+
38
+ // Show help if requested
39
+ if (hasHelp) {
40
+ console.log(` ${yellow}Usage:${reset} npx specdacular [options]\n
41
+ ${yellow}Options:${reset}
42
+ ${cyan}-g, --global${reset} Install globally (to ~/.claude/)
43
+ ${cyan}-l, --local${reset} Install locally (to ./.claude/)
44
+ ${cyan}-u, --uninstall${reset} Uninstall specdacular
45
+ ${cyan}-h, --help${reset} Show this help message
46
+
47
+ ${yellow}Examples:${reset}
48
+ ${dim}# Interactive install${reset}
49
+ npx specdacular
50
+
51
+ ${dim}# Install globally${reset}
52
+ npx specdacular --global
53
+
54
+ ${dim}# Install to current project only${reset}
55
+ npx specdacular --local
56
+
57
+ ${dim}# Uninstall${reset}
58
+ npx specdacular --global --uninstall
59
+ `);
60
+ process.exit(0);
61
+ }
62
+
63
+ /**
64
+ * Get the global config directory
65
+ */
66
+ function getGlobalDir() {
67
+ if (process.env.CLAUDE_CONFIG_DIR) {
68
+ return expandTilde(process.env.CLAUDE_CONFIG_DIR);
69
+ }
70
+ return path.join(os.homedir(), '.claude');
71
+ }
72
+
73
+ /**
74
+ * Expand ~ to home directory
75
+ */
76
+ function expandTilde(filePath) {
77
+ if (filePath && filePath.startsWith('~/')) {
78
+ return path.join(os.homedir(), filePath.slice(2));
79
+ }
80
+ return filePath;
81
+ }
82
+
83
+ /**
84
+ * Read and parse settings.json
85
+ */
86
+ function readSettings(settingsPath) {
87
+ if (fs.existsSync(settingsPath)) {
88
+ try {
89
+ return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
90
+ } catch (e) {
91
+ return {};
92
+ }
93
+ }
94
+ return {};
95
+ }
96
+
97
+ /**
98
+ * Write settings.json with proper formatting
99
+ */
100
+ function writeSettings(settingsPath, settings) {
101
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
102
+ }
103
+
104
+ /**
105
+ * Recursively copy directory with path replacement in .md files
106
+ */
107
+ function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
108
+ if (fs.existsSync(destDir)) {
109
+ fs.rmSync(destDir, { recursive: true });
110
+ }
111
+ fs.mkdirSync(destDir, { recursive: true });
112
+
113
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
114
+
115
+ for (const entry of entries) {
116
+ const srcPath = path.join(srcDir, entry.name);
117
+ const destPath = path.join(destDir, entry.name);
118
+
119
+ if (entry.isDirectory()) {
120
+ copyWithPathReplacement(srcPath, destPath, pathPrefix);
121
+ } else if (entry.name.endsWith('.md')) {
122
+ let content = fs.readFileSync(srcPath, 'utf8');
123
+ // Replace path references
124
+ content = content.replace(/~\/\.claude\//g, pathPrefix);
125
+ fs.writeFileSync(destPath, content);
126
+ } else {
127
+ fs.copyFileSync(srcPath, destPath);
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Verify a directory exists and contains files
134
+ */
135
+ function verifyInstalled(dirPath, description) {
136
+ if (!fs.existsSync(dirPath)) {
137
+ console.error(` ${yellow}✗${reset} Failed to install ${description}`);
138
+ return false;
139
+ }
140
+ try {
141
+ const entries = fs.readdirSync(dirPath);
142
+ if (entries.length === 0) {
143
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: empty`);
144
+ return false;
145
+ }
146
+ } catch (e) {
147
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: ${e.message}`);
148
+ return false;
149
+ }
150
+ return true;
151
+ }
152
+
153
+ /**
154
+ * Uninstall specdacular
155
+ */
156
+ function uninstall(isGlobal) {
157
+ const targetDir = isGlobal ? getGlobalDir() : path.join(process.cwd(), '.claude');
158
+ const locationLabel = isGlobal ? targetDir.replace(os.homedir(), '~') : './.claude';
159
+
160
+ console.log(` Uninstalling from ${cyan}${locationLabel}${reset}\n`);
161
+
162
+ if (!fs.existsSync(targetDir)) {
163
+ console.log(` ${yellow}⚠${reset} Directory does not exist: ${locationLabel}`);
164
+ return;
165
+ }
166
+
167
+ let removedCount = 0;
168
+
169
+ // Remove specd commands
170
+ const specCommandsDir = path.join(targetDir, 'commands', 'specd');
171
+ if (fs.existsSync(specCommandsDir)) {
172
+ fs.rmSync(specCommandsDir, { recursive: true });
173
+ removedCount++;
174
+ console.log(` ${green}✓${reset} Removed commands/specd/`);
175
+ }
176
+
177
+ // Remove specdacular directory
178
+ const specDir = path.join(targetDir, 'specdacular');
179
+ if (fs.existsSync(specDir)) {
180
+ fs.rmSync(specDir, { recursive: true });
181
+ removedCount++;
182
+ console.log(` ${green}✓${reset} Removed specdacular/`);
183
+ }
184
+
185
+ // Remove specd agents
186
+ const agentsDir = path.join(targetDir, 'agents');
187
+ if (fs.existsSync(agentsDir)) {
188
+ const files = fs.readdirSync(agentsDir);
189
+ let agentCount = 0;
190
+ for (const file of files) {
191
+ if (file.startsWith('specd-') && file.endsWith('.md')) {
192
+ fs.unlinkSync(path.join(agentsDir, file));
193
+ agentCount++;
194
+ }
195
+ }
196
+ if (agentCount > 0) {
197
+ removedCount++;
198
+ console.log(` ${green}✓${reset} Removed ${agentCount} specd agents`);
199
+ }
200
+ }
201
+
202
+ if (removedCount === 0) {
203
+ console.log(` ${yellow}⚠${reset} No specdacular files found to remove.`);
204
+ } else {
205
+ console.log(`\n ${green}Done!${reset} Specdacular has been uninstalled.\n`);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Install specdacular
211
+ */
212
+ function install(isGlobal) {
213
+ const src = path.join(__dirname, '..');
214
+ const targetDir = isGlobal ? getGlobalDir() : path.join(process.cwd(), '.claude');
215
+ const locationLabel = isGlobal ? targetDir.replace(os.homedir(), '~') : './.claude';
216
+ const pathPrefix = isGlobal ? `${targetDir}/` : './.claude/';
217
+
218
+ console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
219
+
220
+ const failures = [];
221
+
222
+ // Install commands
223
+ const commandsDir = path.join(targetDir, 'commands');
224
+ fs.mkdirSync(commandsDir, { recursive: true });
225
+
226
+ const specSrc = path.join(src, 'commands', 'specd');
227
+ const specDest = path.join(commandsDir, 'specd');
228
+ if (fs.existsSync(specSrc)) {
229
+ copyWithPathReplacement(specSrc, specDest, pathPrefix);
230
+ if (verifyInstalled(specDest, 'commands/specd')) {
231
+ console.log(` ${green}✓${reset} Installed commands/specd`);
232
+ } else {
233
+ failures.push('commands/specd');
234
+ }
235
+ }
236
+
237
+ // Install specdacular core
238
+ const coreSrc = path.join(src, 'specdacular');
239
+ const coreDest = path.join(targetDir, 'specdacular');
240
+ if (fs.existsSync(coreSrc)) {
241
+ copyWithPathReplacement(coreSrc, coreDest, pathPrefix);
242
+ if (verifyInstalled(coreDest, 'specdacular')) {
243
+ console.log(` ${green}✓${reset} Installed specdacular`);
244
+ } else {
245
+ failures.push('specdacular');
246
+ }
247
+ }
248
+
249
+ // Install agents
250
+ const agentsSrc = path.join(src, 'agents');
251
+ if (fs.existsSync(agentsSrc)) {
252
+ const agentsDest = path.join(targetDir, 'agents');
253
+ fs.mkdirSync(agentsDest, { recursive: true });
254
+
255
+ // Remove old specd agents
256
+ if (fs.existsSync(agentsDest)) {
257
+ for (const file of fs.readdirSync(agentsDest)) {
258
+ if (file.startsWith('specd-') && file.endsWith('.md')) {
259
+ fs.unlinkSync(path.join(agentsDest, file));
260
+ }
261
+ }
262
+ }
263
+
264
+ // Copy new agents
265
+ const agentEntries = fs.readdirSync(agentsSrc, { withFileTypes: true });
266
+ for (const entry of agentEntries) {
267
+ if (entry.isFile() && entry.name.endsWith('.md')) {
268
+ let content = fs.readFileSync(path.join(agentsSrc, entry.name), 'utf8');
269
+ content = content.replace(/~\/\.claude\//g, pathPrefix);
270
+ fs.writeFileSync(path.join(agentsDest, entry.name), content);
271
+ }
272
+ }
273
+ if (verifyInstalled(agentsDest, 'agents')) {
274
+ console.log(` ${green}✓${reset} Installed agents`);
275
+ } else {
276
+ failures.push('agents');
277
+ }
278
+ }
279
+
280
+ // Write VERSION file
281
+ const versionDest = path.join(targetDir, 'specdacular', 'VERSION');
282
+ fs.mkdirSync(path.dirname(versionDest), { recursive: true });
283
+ fs.writeFileSync(versionDest, pkg.version);
284
+ console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
285
+
286
+ if (failures.length > 0) {
287
+ console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
288
+ process.exit(1);
289
+ }
290
+
291
+ console.log(`
292
+ ${green}Done!${reset} Launch Claude Code and run ${cyan}/specd:help${reset}.
293
+
294
+ ${yellow}Commands:${reset}
295
+ /specd:map-codebase - Analyze and document your codebase
296
+ /specd:help - Show all commands
297
+ `);
298
+ }
299
+
300
+ /**
301
+ * Prompt for install location
302
+ */
303
+ function promptLocation() {
304
+ if (!process.stdin.isTTY) {
305
+ console.log(` ${yellow}Non-interactive terminal, defaulting to global install${reset}\n`);
306
+ install(true);
307
+ return;
308
+ }
309
+
310
+ const rl = readline.createInterface({
311
+ input: process.stdin,
312
+ output: process.stdout
313
+ });
314
+
315
+ let answered = false;
316
+
317
+ rl.on('close', () => {
318
+ if (!answered) {
319
+ answered = true;
320
+ console.log(`\n ${yellow}Installation cancelled${reset}\n`);
321
+ process.exit(0);
322
+ }
323
+ });
324
+
325
+ const globalPath = getGlobalDir().replace(os.homedir(), '~');
326
+
327
+ console.log(` ${yellow}Where would you like to install?${reset}
328
+
329
+ ${cyan}1${reset}) Global ${dim}(${globalPath})${reset} - available in all projects
330
+ ${cyan}2${reset}) Local ${dim}(./.claude)${reset} - this project only
331
+ `);
332
+
333
+ rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
334
+ answered = true;
335
+ rl.close();
336
+ const choice = answer.trim() || '1';
337
+ const isGlobal = choice !== '2';
338
+ install(isGlobal);
339
+ });
340
+ }
341
+
342
+ // Main logic
343
+ if (hasGlobal && hasLocal) {
344
+ console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
345
+ process.exit(1);
346
+ } else if (hasUninstall) {
347
+ if (!hasGlobal && !hasLocal) {
348
+ console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
349
+ process.exit(1);
350
+ }
351
+ uninstall(hasGlobal);
352
+ } else if (hasGlobal || hasLocal) {
353
+ install(hasGlobal);
354
+ } else {
355
+ promptLocation();
356
+ }
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: specd:help
3
+ description: Show all specdacular commands and usage guide
4
+ allowed-tools:
5
+ - Read
6
+ ---
7
+
8
+ <objective>
9
+ Display available specdacular commands and usage guidance.
10
+ </objective>
11
+
12
+ <output>
13
+ # Specdacular
14
+
15
+ **Feature planning for existing codebases.**
16
+
17
+ ## Commands
18
+
19
+ | Command | Description |
20
+ |---------|-------------|
21
+ | `/specd:map-codebase` | Analyze codebase and generate documentation |
22
+ | `/specd:help` | Show this help |
23
+
24
+ ## Quick Start
25
+
26
+ 1. **Map your codebase first:**
27
+ ```
28
+ /specd:map-codebase
29
+ ```
30
+ This spawns 4 parallel agents to analyze your codebase and creates 7 documents in `.specd/codebase/`.
31
+
32
+ 2. **Review the generated docs:**
33
+ - `STACK.md` - Technologies and dependencies
34
+ - `ARCHITECTURE.md` - System design and patterns
35
+ - `STRUCTURE.md` - Directory layout
36
+ - `CONVENTIONS.md` - Code style and patterns
37
+ - `TESTING.md` - Test structure
38
+ - `INTEGRATIONS.md` - External services
39
+ - `CONCERNS.md` - Technical debt and issues
40
+
41
+ ## Updating
42
+
43
+ ```bash
44
+ npx specdacular@latest
45
+ ```
46
+
47
+ ---
48
+
49
+ *More commands coming soon for feature planning workflows.*
50
+ </output>
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: specd:map-codebase
3
+ description: Analyze codebase with parallel mapper agents to produce .specd/codebase/ documents
4
+ argument-hint: "[optional: specific area to map, e.g., 'api' or 'auth']"
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ - Glob
9
+ - Grep
10
+ - Write
11
+ - Task
12
+ ---
13
+
14
+ <objective>
15
+ Analyze existing codebase using parallel specd-codebase-mapper agents to produce structured codebase documents.
16
+
17
+ Each mapper agent explores a focus area and **writes documents directly** to `.specd/codebase/`. The orchestrator only receives confirmations, keeping context usage minimal.
18
+
19
+ Output: .specd/codebase/ folder with 7 structured documents about the codebase state.
20
+ </objective>
21
+
22
+ <execution_context>
23
+ @~/.claude/specdacular/workflows/map-codebase.md
24
+ </execution_context>
25
+
26
+ <context>
27
+ Focus area: $ARGUMENTS (optional - if provided, tells agents to focus on specific subsystem)
28
+
29
+ **Load project state if exists:**
30
+ Check for .specd/codebase/STATE.md - loads context if project already initialized
31
+
32
+ **This command can run:**
33
+ - Before starting feature planning - understand the codebase first
34
+ - After major changes - refresh codebase understanding
35
+ - Anytime to update codebase documentation
36
+ </context>
37
+
38
+ <when_to_use>
39
+ **Use map-codebase for:**
40
+ - First time working with a codebase (understand what exists)
41
+ - Before planning a new feature (know the landscape)
42
+ - After significant refactoring (update documentation)
43
+ - Onboarding to an unfamiliar codebase
44
+
45
+ **Skip map-codebase for:**
46
+ - Trivial codebases (<5 files)
47
+ - When you already have recent codebase docs
48
+ </when_to_use>
49
+
50
+ <process>
51
+ 1. Check if .specd/codebase/ already exists (offer to refresh or skip)
52
+ 2. Create .specd/codebase/ directory structure
53
+ 3. Spawn 4 parallel specd-codebase-mapper agents:
54
+ - Agent 1: tech focus → writes STACK.md, INTEGRATIONS.md
55
+ - Agent 2: arch focus → writes ARCHITECTURE.md, STRUCTURE.md
56
+ - Agent 3: quality focus → writes CONVENTIONS.md, TESTING.md
57
+ - Agent 4: concerns focus → writes CONCERNS.md
58
+ 4. Wait for agents to complete, collect confirmations (NOT document contents)
59
+ 5. Verify all 7 documents exist with line counts
60
+ 6. Commit codebase map
61
+ 7. Report completion
62
+ </process>
63
+
64
+ <success_criteria>
65
+ - [ ] .specd/codebase/ directory created
66
+ - [ ] All 7 codebase documents written by mapper agents
67
+ - [ ] Documents follow template structure
68
+ - [ ] Parallel agents completed without errors
69
+ </success_criteria>
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "specdacular",
3
+ "version": "0.1.0",
4
+ "description": "Feature planning system for existing codebases. Map, understand, and plan features in large projects.",
5
+ "bin": {
6
+ "specdacular": "bin/install.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "commands",
11
+ "agents",
12
+ "specdacular"
13
+ ],
14
+ "keywords": [
15
+ "claude",
16
+ "claude-code",
17
+ "ai",
18
+ "codebase-analysis",
19
+ "feature-planning",
20
+ "spec-driven-development"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+ssh://git@github.com/victorbalan/specdacular.git"
27
+ },
28
+ "homepage": "https://github.com/victorbalan/specdacular",
29
+ "bugs": {
30
+ "url": "https://github.com/victorbalan/specdacular/issues"
31
+ },
32
+ "engines": {
33
+ "node": ">=16.7.0"
34
+ }
35
+ }