@applica-software-guru/sdd 1.0.3 → 1.3.3
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 +50 -75
- 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 +9 -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 +6 -2
- 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 +65 -92
- package/src/commands/ui.ts +161 -0
- package/src/commands/upgrade.ts +51 -0
- package/src/index.ts +20 -14
- 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,
|
|
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, 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,69 +45,47 @@ 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,
|
|
60
|
+
if (existsSync(resolve(projectDir, ".sdd"))) {
|
|
61
61
|
console.log(chalk.yellow(`\n SDD project already initialized at ${projectName}/\n`));
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const promptTheme = {
|
|
66
|
-
prefix: chalk.cyan(
|
|
66
|
+
prefix: chalk.cyan("?"),
|
|
67
67
|
style: { message: (text: string) => chalk.cyan.bold(text) },
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
const description = await input({
|
|
71
|
-
message:
|
|
71
|
+
message: "What should your project do?",
|
|
72
72
|
theme: promptTheme,
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
if (!description.trim()) {
|
|
76
|
-
console.log(chalk.yellow(
|
|
76
|
+
console.log(chalk.yellow("\n No description provided. Aborting.\n"));
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
const agentChoice = await select({
|
|
81
|
-
message: 'Which agent do you use?',
|
|
82
|
-
choices: [
|
|
83
|
-
{ value: 'claude', name: 'Claude Code' },
|
|
84
|
-
{ value: 'codex', name: 'Codex' },
|
|
85
|
-
{ value: 'opencode', name: 'OpenCode' },
|
|
86
|
-
{ value: 'other', name: 'Other' },
|
|
87
|
-
],
|
|
88
|
-
theme: promptTheme,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
let agentName = agentChoice;
|
|
92
|
-
let customCommand: string | undefined;
|
|
93
|
-
|
|
94
|
-
if (agentChoice === 'other') {
|
|
95
|
-
agentName = await input({
|
|
96
|
-
message: 'Agent name:',
|
|
97
|
-
theme: promptTheme,
|
|
98
|
-
});
|
|
99
|
-
customCommand = await input({
|
|
100
|
-
message: 'Agent command (use $PROMPT_FILE for the prompt file path):',
|
|
101
|
-
theme: promptTheme,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
80
|
const bootstrapMode = await select({
|
|
106
|
-
message:
|
|
81
|
+
message: "How do you want to start?",
|
|
107
82
|
choices: [
|
|
108
|
-
{ value:
|
|
109
|
-
{
|
|
110
|
-
|
|
83
|
+
{ value: "skip", name: "Write docs manually" },
|
|
84
|
+
{
|
|
85
|
+
value: "prompt",
|
|
86
|
+
name: "Generate bootstrap prompt (copy to clipboard)",
|
|
87
|
+
},
|
|
88
|
+
{ value: "auto", name: "Generate and apply bootstrap automatically" },
|
|
111
89
|
],
|
|
112
90
|
theme: promptTheme,
|
|
113
91
|
});
|
|
@@ -117,52 +95,45 @@ export function registerInit(program: Command): void {
|
|
|
117
95
|
}
|
|
118
96
|
|
|
119
97
|
const spinner = ora({
|
|
120
|
-
text:
|
|
121
|
-
color:
|
|
98
|
+
text: "Creating project structure...",
|
|
99
|
+
color: "cyan",
|
|
122
100
|
}).start();
|
|
123
101
|
|
|
124
102
|
const sdd = new SDD({ root: projectDir });
|
|
125
103
|
const files = await sdd.init({ description: description.trim() });
|
|
126
104
|
|
|
127
|
-
// Save agent config
|
|
128
|
-
const config = await sdd.config();
|
|
129
|
-
config.agent = agentName;
|
|
130
|
-
if (customCommand) {
|
|
131
|
-
config.agents = { [agentName]: customCommand };
|
|
132
|
-
}
|
|
133
|
-
await writeConfig(projectDir, config);
|
|
134
|
-
|
|
135
105
|
spinner.stop();
|
|
136
106
|
|
|
137
107
|
// Project created
|
|
138
108
|
console.log(chalk.cyan.bold(`\n ${chalk.white(projectName)} is ready!\n`));
|
|
139
109
|
|
|
140
110
|
// Show what was created
|
|
141
|
-
console.log(chalk.dim(
|
|
111
|
+
console.log(chalk.dim(" Created:"));
|
|
142
112
|
for (const f of files) {
|
|
143
113
|
console.log(success(f));
|
|
144
114
|
}
|
|
145
|
-
console.log(success(
|
|
146
|
-
console.log(success(
|
|
147
|
-
console.log(success(
|
|
148
|
-
console.log(success(
|
|
149
|
-
|
|
150
|
-
|
|
115
|
+
console.log(success("product/"));
|
|
116
|
+
console.log(success("product/features/"));
|
|
117
|
+
console.log(success("system/"));
|
|
118
|
+
console.log(success("code/"));
|
|
119
|
+
console.log(success(".claude/skills/"));
|
|
120
|
+
|
|
121
|
+
if (bootstrapMode === "auto") {
|
|
122
|
+
const agentName = "claude";
|
|
151
123
|
const prompt = buildBootstrapPrompt(description.trim(), true);
|
|
152
124
|
|
|
153
|
-
console.log(chalk.dim(
|
|
154
|
-
console.log(heading(
|
|
125
|
+
console.log(chalk.dim(" ─".repeat(30)));
|
|
126
|
+
console.log(heading("Agent Prompt"));
|
|
155
127
|
console.log(renderMarkdown(prompt));
|
|
156
|
-
console.log(chalk.dim(
|
|
128
|
+
console.log(chalk.dim(" ─".repeat(30)));
|
|
157
129
|
|
|
158
|
-
console.log(info(`Using agent: ${chalk.cyan(agentName)}`));
|
|
159
|
-
console.log(info(
|
|
130
|
+
console.log(info(`Using agent: ${chalk.cyan(agentName)} (default)`));
|
|
131
|
+
console.log(info("Starting agent...\n"));
|
|
160
132
|
|
|
161
133
|
const exitCode = await runAgent({
|
|
162
134
|
root: projectDir,
|
|
163
135
|
prompt,
|
|
164
136
|
agent: agentName,
|
|
165
|
-
agents: customCommand ? { [agentName]: customCommand } : undefined,
|
|
166
137
|
});
|
|
167
138
|
|
|
168
139
|
if (exitCode !== 0) {
|
|
@@ -170,48 +141,50 @@ export function registerInit(program: Command): void {
|
|
|
170
141
|
process.exit(exitCode);
|
|
171
142
|
}
|
|
172
143
|
|
|
173
|
-
console.log(chalk.green(
|
|
144
|
+
console.log(chalk.green("\n Agent completed successfully."));
|
|
174
145
|
return;
|
|
175
146
|
}
|
|
176
147
|
|
|
177
|
-
if (bootstrapMode ===
|
|
148
|
+
if (bootstrapMode === "prompt") {
|
|
178
149
|
const prompt = buildBootstrapPrompt(description.trim(), false);
|
|
179
150
|
|
|
180
|
-
console.log(chalk.cyan.bold(
|
|
181
|
-
console.log(` ${chalk.white(
|
|
151
|
+
console.log(chalk.cyan.bold("\n Next steps:\n"));
|
|
152
|
+
console.log(` ${chalk.white("1.")} Enter the project folder:\n`);
|
|
182
153
|
console.log(` ${chalk.green(`cd ${projectName}`)}\n`);
|
|
183
|
-
console.log(` ${chalk.white(
|
|
154
|
+
console.log(` ${chalk.white("2.")} Open your AI agent and paste the prompt below.`);
|
|
184
155
|
console.log(` It will ask you a few questions and generate the initial docs.\n`);
|
|
185
156
|
|
|
186
|
-
console.log(chalk.dim(
|
|
187
|
-
console.log(heading(
|
|
157
|
+
console.log(chalk.dim(" ─".repeat(30)));
|
|
158
|
+
console.log(heading("Agent Prompt"));
|
|
188
159
|
console.log(renderMarkdown(prompt));
|
|
189
160
|
|
|
190
161
|
try {
|
|
191
162
|
await clipboardy.write(prompt);
|
|
192
|
-
console.log(success(
|
|
163
|
+
console.log(success("Copied to clipboard — paste it into your agent.\n"));
|
|
193
164
|
} catch {
|
|
194
|
-
console.log(info(
|
|
165
|
+
console.log(info("Copy the prompt above into your agent.\n"));
|
|
195
166
|
}
|
|
196
167
|
return;
|
|
197
168
|
}
|
|
198
169
|
|
|
199
170
|
// skip — manual mode
|
|
200
|
-
console.log(chalk.cyan.bold(
|
|
201
|
-
console.log(` ${chalk.white(
|
|
171
|
+
console.log(chalk.cyan.bold("\n Next steps:\n"));
|
|
172
|
+
console.log(` ${chalk.white("1.")} Enter the project folder:\n`);
|
|
202
173
|
console.log(` ${chalk.green(`cd ${projectName}`)}\n`);
|
|
203
|
-
console.log(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
console.log(` ${chalk.
|
|
174
|
+
console.log(
|
|
175
|
+
` ${chalk.white("2.")} Start writing your documentation in ${chalk.cyan("product/")} and ${chalk.cyan("system/")}.`,
|
|
176
|
+
);
|
|
177
|
+
console.log(` Check ${chalk.cyan(".sdd/skill/sdd/SKILL.md")} for the workflow.\n`);
|
|
178
|
+
console.log(` ${chalk.white("3.")} When ready, let your AI agent run:\n`);
|
|
179
|
+
console.log(` ${chalk.green("sdd sync")}\n`);
|
|
207
180
|
|
|
208
181
|
const prompt = START_PROMPT;
|
|
209
182
|
|
|
210
183
|
try {
|
|
211
184
|
await clipboardy.write(prompt);
|
|
212
|
-
console.log(success(
|
|
185
|
+
console.log(success("Copied to clipboard — paste it into your agent.\n"));
|
|
213
186
|
} catch {
|
|
214
|
-
console.log(info(
|
|
187
|
+
console.log(info("Copy the prompt above into your agent.\n"));
|
|
215
188
|
}
|
|
216
189
|
});
|
|
217
190
|
}
|
|
@@ -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,24 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from
|
|
3
|
-
import { createRequire } from
|
|
4
|
-
import { registerInit } from
|
|
5
|
-
import { registerStatus } from
|
|
6
|
-
import { registerDiff } from
|
|
7
|
-
import { registerSync } from
|
|
8
|
-
import { registerValidate } from
|
|
9
|
-
import { registerMarkSynced } from
|
|
10
|
-
import { registerCR } from
|
|
11
|
-
import { registerBug } from
|
|
12
|
-
import { registerApply } from
|
|
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";
|
|
13
16
|
|
|
14
17
|
const packageRequire = createRequire(__filename);
|
|
15
|
-
const packageJson = packageRequire(
|
|
18
|
+
const packageJson = packageRequire("../package.json") as { version: string };
|
|
16
19
|
|
|
17
20
|
const program = new Command();
|
|
18
21
|
|
|
19
22
|
program
|
|
20
|
-
.name(
|
|
21
|
-
.description(
|
|
23
|
+
.name("sdd")
|
|
24
|
+
.description("Story Driven Development — manage apps through structured documentation")
|
|
22
25
|
.version(packageJson.version);
|
|
23
26
|
|
|
24
27
|
registerInit(program);
|
|
@@ -30,6 +33,9 @@ registerMarkSynced(program);
|
|
|
30
33
|
registerCR(program);
|
|
31
34
|
registerBug(program);
|
|
32
35
|
registerApply(program);
|
|
36
|
+
registerAdapters(program);
|
|
37
|
+
registerUI(program);
|
|
38
|
+
registerUpgrade(program);
|
|
33
39
|
|
|
34
40
|
program.parseAsync().catch((err) => {
|
|
35
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
|
+
}
|