@applica-software-guru/sdd 1.0.2 → 1.3.1
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/dist/commands/adapters.d.ts +3 -0
- package/dist/commands/adapters.d.ts.map +1 -0
- package/dist/commands/adapters.js +82 -0
- package/dist/commands/adapters.js.map +1 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +56 -52
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/ui.d.ts +3 -0
- package/dist/commands/ui.d.ts.map +1 -0
- package/dist/commands/ui.js +145 -0
- package/dist/commands/ui.js.map +1 -0
- package/dist/commands/upgrade.d.ts +3 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +48 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/skills.d.ts +11 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +28 -0
- package/dist/skills.js.map +1 -0
- package/package.json +3 -1
- package/skills/sdd/SKILL.md +97 -0
- package/skills/sdd/references/bugs.md +29 -0
- package/skills/sdd/references/change-requests.md +29 -0
- package/skills/sdd/references/file-format.md +65 -0
- package/skills/sdd-ui/SKILL.md +113 -0
- package/src/commands/adapters.ts +108 -0
- package/src/commands/init.ts +92 -68
- package/src/commands/ui.ts +161 -0
- package/src/commands/upgrade.ts +51 -0
- package/src/index.ts +23 -13
- package/src/skills.ts +25 -0
package/src/commands/init.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
import chalk from
|
|
3
|
-
import { input, select } from
|
|
4
|
-
import clipboardy from
|
|
5
|
-
import { existsSync, mkdirSync } from
|
|
6
|
-
import { resolve } from
|
|
7
|
-
import ora from
|
|
8
|
-
import { SDD, writeConfig, runAgent } from
|
|
9
|
-
import { printBanner } from
|
|
10
|
-
import { success, info, heading } from
|
|
11
|
-
import { renderMarkdown } from
|
|
12
|
-
|
|
13
|
-
const START_PROMPT = `Read
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { input, select } from "@inquirer/prompts";
|
|
4
|
+
import clipboardy from "clipboardy";
|
|
5
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import { SDD, writeConfig, runAgent } from "@applica-software-guru/sdd-core";
|
|
9
|
+
import { printBanner } from "../ui/banner.js";
|
|
10
|
+
import { success, info, heading } from "../ui/format.js";
|
|
11
|
+
import { renderMarkdown } from "../ui/markdown.js";
|
|
12
|
+
|
|
13
|
+
const START_PROMPT = `Read .sdd/skill/sdd/SKILL.md (fallback: .claude/skills/sdd/SKILL.md) and the documentation in product/ and system/, then run \`sdd sync\` to start working.`;
|
|
14
14
|
|
|
15
15
|
function buildBootstrapPrompt(description: string, auto: boolean): string {
|
|
16
16
|
if (auto) {
|
|
17
|
-
return `Read
|
|
17
|
+
return `Read .sdd/skill/sdd/SKILL.md first (fallback: .claude/skills/sdd/SKILL.md). This is a new SDD project.
|
|
18
18
|
|
|
19
19
|
Project goal: "${description}"
|
|
20
20
|
|
|
@@ -28,10 +28,10 @@ Your task: generate the initial documentation for this project based on the desc
|
|
|
28
28
|
- system/tech-stack.md — Technologies and frameworks
|
|
29
29
|
- system/interfaces.md — API contracts
|
|
30
30
|
|
|
31
|
-
Follow the file format described in
|
|
31
|
+
Follow the file format described in .sdd/skill/sdd/references/file-format.md for the YAML frontmatter. Do NOT write any code, only documentation. Commit all created files when done.`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
return `Read
|
|
34
|
+
return `Read .sdd/skill/sdd/SKILL.md first (fallback: .claude/skills/sdd/SKILL.md). This is a new SDD project.
|
|
35
35
|
|
|
36
36
|
Project goal: "${description}"
|
|
37
37
|
|
|
@@ -45,45 +45,49 @@ Your task: generate the initial documentation for this project. Ask me a few que
|
|
|
45
45
|
- system/tech-stack.md — Technologies and frameworks
|
|
46
46
|
- system/interfaces.md — API contracts
|
|
47
47
|
|
|
48
|
-
Follow the file format described in
|
|
48
|
+
Follow the file format described in .sdd/skill/sdd/references/file-format.md for the YAML frontmatter. Do NOT write any code, only documentation.`;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function registerInit(program: Command): void {
|
|
52
52
|
program
|
|
53
|
-
.command(
|
|
54
|
-
.description(
|
|
53
|
+
.command("init <project-name>")
|
|
54
|
+
.description("Initialize a new SDD project")
|
|
55
55
|
.action(async (projectName: string) => {
|
|
56
56
|
printBanner();
|
|
57
57
|
|
|
58
58
|
const projectDir = resolve(process.cwd(), projectName);
|
|
59
59
|
|
|
60
|
-
if (existsSync(resolve(projectDir,
|
|
61
|
-
console.log(
|
|
60
|
+
if (existsSync(resolve(projectDir, ".sdd"))) {
|
|
61
|
+
console.log(
|
|
62
|
+
chalk.yellow(
|
|
63
|
+
`\n SDD project already initialized at ${projectName}/\n`,
|
|
64
|
+
),
|
|
65
|
+
);
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
const promptTheme = {
|
|
66
|
-
prefix: chalk.cyan(
|
|
70
|
+
prefix: chalk.cyan("?"),
|
|
67
71
|
style: { message: (text: string) => chalk.cyan.bold(text) },
|
|
68
72
|
};
|
|
69
73
|
|
|
70
74
|
const description = await input({
|
|
71
|
-
message:
|
|
75
|
+
message: "What should your project do?",
|
|
72
76
|
theme: promptTheme,
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
if (!description.trim()) {
|
|
76
|
-
console.log(chalk.yellow(
|
|
80
|
+
console.log(chalk.yellow("\n No description provided. Aborting.\n"));
|
|
77
81
|
return;
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
const agentChoice = await select({
|
|
81
|
-
message:
|
|
85
|
+
message: "Which agent do you use?",
|
|
82
86
|
choices: [
|
|
83
|
-
{ value:
|
|
84
|
-
{ value:
|
|
85
|
-
{ value:
|
|
86
|
-
{ value:
|
|
87
|
+
{ value: "claude", name: "Claude Code" },
|
|
88
|
+
{ value: "codex", name: "Codex" },
|
|
89
|
+
{ value: "opencode", name: "OpenCode" },
|
|
90
|
+
{ value: "other", name: "Other" },
|
|
87
91
|
],
|
|
88
92
|
theme: promptTheme,
|
|
89
93
|
});
|
|
@@ -91,23 +95,26 @@ export function registerInit(program: Command): void {
|
|
|
91
95
|
let agentName = agentChoice;
|
|
92
96
|
let customCommand: string | undefined;
|
|
93
97
|
|
|
94
|
-
if (agentChoice ===
|
|
98
|
+
if (agentChoice === "other") {
|
|
95
99
|
agentName = await input({
|
|
96
|
-
message:
|
|
100
|
+
message: "Agent name:",
|
|
97
101
|
theme: promptTheme,
|
|
98
102
|
});
|
|
99
103
|
customCommand = await input({
|
|
100
|
-
message:
|
|
104
|
+
message: "Agent command (use $PROMPT_FILE for the prompt file path):",
|
|
101
105
|
theme: promptTheme,
|
|
102
106
|
});
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
const bootstrapMode = await select({
|
|
106
|
-
message:
|
|
110
|
+
message: "How do you want to start?",
|
|
107
111
|
choices: [
|
|
108
|
-
{ value:
|
|
109
|
-
{
|
|
110
|
-
|
|
112
|
+
{ value: "skip", name: "Write docs manually" },
|
|
113
|
+
{
|
|
114
|
+
value: "prompt",
|
|
115
|
+
name: "Generate bootstrap prompt (copy to clipboard)",
|
|
116
|
+
},
|
|
117
|
+
{ value: "auto", name: "Generate and apply bootstrap automatically" },
|
|
111
118
|
],
|
|
112
119
|
theme: promptTheme,
|
|
113
120
|
});
|
|
@@ -117,8 +124,8 @@ export function registerInit(program: Command): void {
|
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
const spinner = ora({
|
|
120
|
-
text:
|
|
121
|
-
color:
|
|
127
|
+
text: "Creating project structure...",
|
|
128
|
+
color: "cyan",
|
|
122
129
|
}).start();
|
|
123
130
|
|
|
124
131
|
const sdd = new SDD({ root: projectDir });
|
|
@@ -135,28 +142,31 @@ export function registerInit(program: Command): void {
|
|
|
135
142
|
spinner.stop();
|
|
136
143
|
|
|
137
144
|
// Project created
|
|
138
|
-
console.log(
|
|
145
|
+
console.log(
|
|
146
|
+
chalk.cyan.bold(`\n ${chalk.white(projectName)} is ready!\n`),
|
|
147
|
+
);
|
|
139
148
|
|
|
140
149
|
// Show what was created
|
|
141
|
-
console.log(chalk.dim(
|
|
150
|
+
console.log(chalk.dim(" Created:"));
|
|
142
151
|
for (const f of files) {
|
|
143
152
|
console.log(success(f));
|
|
144
153
|
}
|
|
145
|
-
console.log(success(
|
|
146
|
-
console.log(success(
|
|
147
|
-
console.log(success(
|
|
148
|
-
console.log(success(
|
|
154
|
+
console.log(success("product/"));
|
|
155
|
+
console.log(success("product/features/"));
|
|
156
|
+
console.log(success("system/"));
|
|
157
|
+
console.log(success("code/"));
|
|
158
|
+
console.log(success(".claude/skills/"));
|
|
149
159
|
|
|
150
|
-
if (bootstrapMode ===
|
|
160
|
+
if (bootstrapMode === "auto") {
|
|
151
161
|
const prompt = buildBootstrapPrompt(description.trim(), true);
|
|
152
162
|
|
|
153
|
-
console.log(chalk.dim(
|
|
154
|
-
console.log(heading(
|
|
163
|
+
console.log(chalk.dim(" ─".repeat(30)));
|
|
164
|
+
console.log(heading("Agent Prompt"));
|
|
155
165
|
console.log(renderMarkdown(prompt));
|
|
156
|
-
console.log(chalk.dim(
|
|
166
|
+
console.log(chalk.dim(" ─".repeat(30)));
|
|
157
167
|
|
|
158
168
|
console.log(info(`Using agent: ${chalk.cyan(agentName)}`));
|
|
159
|
-
console.log(info(
|
|
169
|
+
console.log(info("Starting agent...\n"));
|
|
160
170
|
|
|
161
171
|
const exitCode = await runAgent({
|
|
162
172
|
root: projectDir,
|
|
@@ -170,48 +180,62 @@ export function registerInit(program: Command): void {
|
|
|
170
180
|
process.exit(exitCode);
|
|
171
181
|
}
|
|
172
182
|
|
|
173
|
-
console.log(chalk.green(
|
|
183
|
+
console.log(chalk.green("\n Agent completed successfully."));
|
|
174
184
|
return;
|
|
175
185
|
}
|
|
176
186
|
|
|
177
|
-
if (bootstrapMode ===
|
|
187
|
+
if (bootstrapMode === "prompt") {
|
|
178
188
|
const prompt = buildBootstrapPrompt(description.trim(), false);
|
|
179
189
|
|
|
180
|
-
console.log(chalk.cyan.bold(
|
|
181
|
-
console.log(` ${chalk.white(
|
|
190
|
+
console.log(chalk.cyan.bold("\n Next steps:\n"));
|
|
191
|
+
console.log(` ${chalk.white("1.")} Enter the project folder:\n`);
|
|
182
192
|
console.log(` ${chalk.green(`cd ${projectName}`)}\n`);
|
|
183
|
-
console.log(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
console.log(
|
|
187
|
-
|
|
193
|
+
console.log(
|
|
194
|
+
` ${chalk.white("2.")} Open your AI agent and paste the prompt below.`,
|
|
195
|
+
);
|
|
196
|
+
console.log(
|
|
197
|
+
` It will ask you a few questions and generate the initial docs.\n`,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
console.log(chalk.dim(" ─".repeat(30)));
|
|
201
|
+
console.log(heading("Agent Prompt"));
|
|
188
202
|
console.log(renderMarkdown(prompt));
|
|
189
203
|
|
|
190
204
|
try {
|
|
191
205
|
await clipboardy.write(prompt);
|
|
192
|
-
console.log(
|
|
206
|
+
console.log(
|
|
207
|
+
success("Copied to clipboard — paste it into your agent.\n"),
|
|
208
|
+
);
|
|
193
209
|
} catch {
|
|
194
|
-
console.log(info(
|
|
210
|
+
console.log(info("Copy the prompt above into your agent.\n"));
|
|
195
211
|
}
|
|
196
212
|
return;
|
|
197
213
|
}
|
|
198
214
|
|
|
199
215
|
// skip — manual mode
|
|
200
|
-
console.log(chalk.cyan.bold(
|
|
201
|
-
console.log(` ${chalk.white(
|
|
216
|
+
console.log(chalk.cyan.bold("\n Next steps:\n"));
|
|
217
|
+
console.log(` ${chalk.white("1.")} Enter the project folder:\n`);
|
|
202
218
|
console.log(` ${chalk.green(`cd ${projectName}`)}\n`);
|
|
203
|
-
console.log(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
console.log(
|
|
219
|
+
console.log(
|
|
220
|
+
` ${chalk.white("2.")} Start writing your documentation in ${chalk.cyan("product/")} and ${chalk.cyan("system/")}.`,
|
|
221
|
+
);
|
|
222
|
+
console.log(
|
|
223
|
+
` Check ${chalk.cyan(".sdd/skill/sdd/SKILL.md")} for the workflow.\n`,
|
|
224
|
+
);
|
|
225
|
+
console.log(
|
|
226
|
+
` ${chalk.white("3.")} When ready, let your AI agent run:\n`,
|
|
227
|
+
);
|
|
228
|
+
console.log(` ${chalk.green("sdd sync")}\n`);
|
|
207
229
|
|
|
208
230
|
const prompt = START_PROMPT;
|
|
209
231
|
|
|
210
232
|
try {
|
|
211
233
|
await clipboardy.write(prompt);
|
|
212
|
-
console.log(
|
|
234
|
+
console.log(
|
|
235
|
+
success("Copied to clipboard — paste it into your agent.\n"),
|
|
236
|
+
);
|
|
213
237
|
} catch {
|
|
214
|
-
console.log(info(
|
|
238
|
+
console.log(info("Copy the prompt above into your agent.\n"));
|
|
215
239
|
}
|
|
216
240
|
});
|
|
217
241
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, resolve, join, basename } from 'node:path';
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs';
|
|
5
|
+
import { writeFile } from 'node:fs/promises';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { success, info, error as fmtError } from '../ui/format.js';
|
|
9
|
+
|
|
10
|
+
const SCAFFOLD_TEMPLATE = (name: string) => `import React from 'react';
|
|
11
|
+
|
|
12
|
+
export default function ${name}() {
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
|
|
15
|
+
<h1>${name}</h1>
|
|
16
|
+
<p>Start editing this component to see changes live.</p>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const PID_FILE = '.sdd/ui.pid';
|
|
23
|
+
|
|
24
|
+
function collect(value: string, prev: string[]): string[] {
|
|
25
|
+
return [...prev, value];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveSddUiDir(): string {
|
|
29
|
+
const pkgRequire = createRequire(__filename);
|
|
30
|
+
try {
|
|
31
|
+
return dirname(pkgRequire.resolve('@applica-software-guru/sdd-ui/package.json'));
|
|
32
|
+
} catch {
|
|
33
|
+
console.error(fmtError(
|
|
34
|
+
'Package @applica-software-guru/sdd-ui not found.\n' +
|
|
35
|
+
' Run: npm install @applica-software-guru/sdd-ui',
|
|
36
|
+
));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function registerUI(program: Command): void {
|
|
42
|
+
const ui = program
|
|
43
|
+
.command('ui')
|
|
44
|
+
.description('UI Component Editor — visual development with live preview');
|
|
45
|
+
|
|
46
|
+
// launch-editor
|
|
47
|
+
ui
|
|
48
|
+
.command('launch-editor <component-name>')
|
|
49
|
+
.description('Launch the split-panel UI editor for a React component')
|
|
50
|
+
.option('--screenshot <path>', 'Screenshot to show in the spec panel (repeatable)', collect, [])
|
|
51
|
+
.option('--port <n>', 'Port for the UI editor', '5174')
|
|
52
|
+
.option('--detach', 'Run in background and return immediately')
|
|
53
|
+
.action(async (componentName: string, options) => {
|
|
54
|
+
const projectRoot = process.cwd();
|
|
55
|
+
const port = options.port as string;
|
|
56
|
+
const detach = Boolean(options.detach);
|
|
57
|
+
const screenshotArgs = options.screenshot as string[];
|
|
58
|
+
|
|
59
|
+
// Resolve and validate screenshot paths
|
|
60
|
+
const screenshotPaths = screenshotArgs.map((p) => resolve(projectRoot, p));
|
|
61
|
+
for (const p of screenshotPaths) {
|
|
62
|
+
if (!existsSync(p)) {
|
|
63
|
+
console.error(fmtError(`Screenshot not found: ${p}`));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Resolve component path
|
|
69
|
+
const componentsDir = join(projectRoot, 'code', 'components');
|
|
70
|
+
const componentPath = join(componentsDir, `${componentName}.tsx`);
|
|
71
|
+
|
|
72
|
+
// Scaffold component if it doesn't exist
|
|
73
|
+
if (!existsSync(componentPath)) {
|
|
74
|
+
if (!existsSync(componentsDir)) {
|
|
75
|
+
mkdirSync(componentsDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
writeFileSync(componentPath, SCAFFOLD_TEMPLATE(componentName), 'utf-8');
|
|
78
|
+
console.log(info(`Scaffolded component at ${chalk.cyan(componentPath)}`));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sddUiDir = resolveSddUiDir();
|
|
82
|
+
|
|
83
|
+
// Write .env.local — screenshot paths are pipe-separated (| is not valid in fs paths)
|
|
84
|
+
const envLines = [
|
|
85
|
+
`VITE_COMPONENT_PATH=${componentPath}`,
|
|
86
|
+
`VITE_COMPONENT_NAME=${componentName}`,
|
|
87
|
+
`VITE_SCREENSHOT_PATHS=${screenshotPaths.join('|')}`,
|
|
88
|
+
];
|
|
89
|
+
await writeFile(join(sddUiDir, '.env.local'), envLines.join('\n'), 'utf-8');
|
|
90
|
+
|
|
91
|
+
// Print launch info
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(success(`sdd-ui starting on ${chalk.cyan(`http://localhost:${port}`)}`));
|
|
94
|
+
console.log(info(`Component: ${chalk.cyan(componentPath)}`));
|
|
95
|
+
for (const p of screenshotPaths) {
|
|
96
|
+
console.log(info(`Screenshot: ${chalk.cyan(basename(p))} ${chalk.dim(p)}`));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (detach) {
|
|
100
|
+
// Launch detached — process survives after sdd exits
|
|
101
|
+
const vite = spawn('npx', ['vite', '--port', port], {
|
|
102
|
+
cwd: sddUiDir,
|
|
103
|
+
stdio: 'ignore',
|
|
104
|
+
detached: true,
|
|
105
|
+
shell: false,
|
|
106
|
+
});
|
|
107
|
+
vite.unref();
|
|
108
|
+
|
|
109
|
+
// Save PID so `sdd ui stop` can kill it
|
|
110
|
+
const pidFile = join(projectRoot, PID_FILE);
|
|
111
|
+
writeFileSync(pidFile, String(vite.pid), 'utf-8');
|
|
112
|
+
|
|
113
|
+
console.log(info(`Running in background ${chalk.dim(`(PID ${vite.pid})`)}`));
|
|
114
|
+
console.log(chalk.dim(` Stop with: ${chalk.white('sdd ui stop')}\n`));
|
|
115
|
+
} else {
|
|
116
|
+
console.log(chalk.dim(' Press Ctrl+C to stop.\n'));
|
|
117
|
+
|
|
118
|
+
const vite = spawn('npx', ['vite', '--port', port], {
|
|
119
|
+
cwd: sddUiDir,
|
|
120
|
+
stdio: 'inherit',
|
|
121
|
+
shell: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
vite.on('error', (err) => {
|
|
125
|
+
console.error(fmtError(`Failed to start vite: ${err.message}`));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
vite.on('close', (code) => process.exit(code ?? 0));
|
|
130
|
+
|
|
131
|
+
process.on('SIGINT', () => vite.kill('SIGINT'));
|
|
132
|
+
process.on('SIGTERM', () => vite.kill('SIGTERM'));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// stop
|
|
137
|
+
ui
|
|
138
|
+
.command('stop')
|
|
139
|
+
.description('Stop a detached UI editor started with --detach')
|
|
140
|
+
.action(() => {
|
|
141
|
+
const projectRoot = process.cwd();
|
|
142
|
+
const pidFile = join(projectRoot, PID_FILE);
|
|
143
|
+
|
|
144
|
+
if (!existsSync(pidFile)) {
|
|
145
|
+
console.log(info('No running sdd-ui process found.'));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
process.kill(pid, 'SIGTERM');
|
|
153
|
+
unlinkSync(pidFile);
|
|
154
|
+
console.log(success(`Stopped sdd-ui ${chalk.dim(`(PID ${pid})`)}`));
|
|
155
|
+
} catch {
|
|
156
|
+
// Process already dead — clean up the stale pid file
|
|
157
|
+
unlinkSync(pidFile);
|
|
158
|
+
console.log(info('Process was already stopped. Cleaned up pid file.'));
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { heading, success, info } from '../ui/format.js';
|
|
7
|
+
import { installSkills } from '../skills.js';
|
|
8
|
+
|
|
9
|
+
export function registerUpgrade(program: Command): void {
|
|
10
|
+
program
|
|
11
|
+
.command('upgrade')
|
|
12
|
+
.description('Upgrade sdd to the latest version and refresh skills in the current project')
|
|
13
|
+
.action(async () => {
|
|
14
|
+
console.log(heading('SDD Upgrade'));
|
|
15
|
+
console.log(info('Running: npm install -g @applica-software-guru/sdd@latest\n'));
|
|
16
|
+
|
|
17
|
+
const exitCode = await new Promise<number>((resolve) => {
|
|
18
|
+
const child = spawn('npm', ['install', '-g', '@applica-software-guru/sdd@latest'], {
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
shell: false,
|
|
21
|
+
});
|
|
22
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
23
|
+
child.on('error', () => resolve(1));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (exitCode !== 0) {
|
|
27
|
+
console.error(chalk.red('\n Upgrade failed. Check the output above.'));
|
|
28
|
+
process.exit(exitCode);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(success('CLI upgraded.'));
|
|
33
|
+
|
|
34
|
+
// If run from inside an SDD project, refresh the skills too
|
|
35
|
+
const projectRoot = process.cwd();
|
|
36
|
+
const isSddProject = existsSync(join(projectRoot, '.sdd', 'config.yaml'));
|
|
37
|
+
|
|
38
|
+
if (isSddProject) {
|
|
39
|
+
const updated = installSkills(projectRoot);
|
|
40
|
+
if (updated) {
|
|
41
|
+
console.log(success(`Skills updated in ${chalk.cyan('.claude/skills/')}`));
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
console.log(
|
|
45
|
+
chalk.dim(
|
|
46
|
+
'\n Run this command from inside an SDD project to also refresh skill files.',
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { registerInit } from "./commands/init.js";
|
|
5
|
+
import { registerStatus } from "./commands/status.js";
|
|
6
|
+
import { registerDiff } from "./commands/diff.js";
|
|
7
|
+
import { registerSync } from "./commands/sync.js";
|
|
8
|
+
import { registerValidate } from "./commands/validate.js";
|
|
9
|
+
import { registerMarkSynced } from "./commands/mark-synced.js";
|
|
10
|
+
import { registerCR } from "./commands/cr.js";
|
|
11
|
+
import { registerBug } from "./commands/bug.js";
|
|
12
|
+
import { registerApply } from "./commands/apply.js";
|
|
13
|
+
import { registerAdapters } from "./commands/adapters.js";
|
|
14
|
+
import { registerUI } from "./commands/ui.js";
|
|
15
|
+
import { registerUpgrade } from "./commands/upgrade.js";
|
|
16
|
+
|
|
17
|
+
const packageRequire = createRequire(__filename);
|
|
18
|
+
const packageJson = packageRequire("../package.json") as { version: string };
|
|
12
19
|
|
|
13
20
|
const program = new Command();
|
|
14
21
|
|
|
15
22
|
program
|
|
16
|
-
.name(
|
|
17
|
-
.description(
|
|
18
|
-
.version(
|
|
23
|
+
.name("sdd")
|
|
24
|
+
.description("Story Driven Development — manage apps through structured documentation")
|
|
25
|
+
.version(packageJson.version);
|
|
19
26
|
|
|
20
27
|
registerInit(program);
|
|
21
28
|
registerStatus(program);
|
|
@@ -26,6 +33,9 @@ registerMarkSynced(program);
|
|
|
26
33
|
registerCR(program);
|
|
27
34
|
registerBug(program);
|
|
28
35
|
registerApply(program);
|
|
36
|
+
registerAdapters(program);
|
|
37
|
+
registerUI(program);
|
|
38
|
+
registerUpgrade(program);
|
|
29
39
|
|
|
30
40
|
program.parseAsync().catch((err) => {
|
|
31
41
|
console.error(err.message);
|
package/src/skills.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { cpSync, existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the path to the bundled skills directory inside the CLI package.
|
|
6
|
+
* Works both in development (monorepo) and when installed globally.
|
|
7
|
+
*/
|
|
8
|
+
export function getSkillsSourceDir(): string {
|
|
9
|
+
// __filename → <cli-pkg>/dist/skills.js → go up two levels to <cli-pkg>/
|
|
10
|
+
const cliPkgDir = resolve(dirname(__filename), '..');
|
|
11
|
+
return join(cliPkgDir, 'skills');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Copies all bundled skill files into <projectDir>/.claude/skills/.
|
|
16
|
+
* Safe to call on existing projects — overwrites with the latest version.
|
|
17
|
+
*/
|
|
18
|
+
export function installSkills(projectDir: string): boolean {
|
|
19
|
+
const src = getSkillsSourceDir();
|
|
20
|
+
if (!existsSync(src)) return false;
|
|
21
|
+
|
|
22
|
+
const dest = join(projectDir, '.claude', 'skills');
|
|
23
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
24
|
+
return true;
|
|
25
|
+
}
|