@applica-software-guru/sdd 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/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +37 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/cr.d.ts +3 -0
- package/dist/commands/cr.d.ts.map +1 -0
- package/dist/commands/cr.js +76 -0
- package/dist/commands/cr.js.map +1 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +40 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +108 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mark-synced.d.ts +3 -0
- package/dist/commands/mark-synced.d.ts.map +1 -0
- package/dist/commands/mark-synced.js +28 -0
- package/dist/commands/mark-synced.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +43 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +15 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +41 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/ui/banner.d.ts +2 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +13 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/format.d.ts +14 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +90 -0
- package/dist/ui/format.js.map +1 -0
- package/package.json +21 -0
- package/src/commands/cr.ts +84 -0
- package/src/commands/diff.ts +43 -0
- package/src/commands/init.ts +120 -0
- package/src/commands/mark-synced.ts +26 -0
- package/src/commands/status.ts +38 -0
- package/src/commands/sync.ts +13 -0
- package/src/commands/validate.ts +44 -0
- package/src/index.ts +29 -0
- package/src/ui/banner.ts +7 -0
- package/src/ui/format.ts +88 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.statusIcon = statusIcon;
|
|
7
|
+
exports.statusLabel = statusLabel;
|
|
8
|
+
exports.createStatusTable = createStatusTable;
|
|
9
|
+
exports.heading = heading;
|
|
10
|
+
exports.success = success;
|
|
11
|
+
exports.warning = warning;
|
|
12
|
+
exports.error = error;
|
|
13
|
+
exports.info = info;
|
|
14
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
15
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
16
|
+
function statusIcon(status) {
|
|
17
|
+
switch (status) {
|
|
18
|
+
case 'synced':
|
|
19
|
+
return chalk_1.default.green('✓');
|
|
20
|
+
case 'new':
|
|
21
|
+
return chalk_1.default.cyan('+');
|
|
22
|
+
case 'changed':
|
|
23
|
+
return chalk_1.default.yellow('~');
|
|
24
|
+
case 'deleted':
|
|
25
|
+
return chalk_1.default.red('✗');
|
|
26
|
+
default:
|
|
27
|
+
return chalk_1.default.gray('?');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function statusLabel(status) {
|
|
31
|
+
switch (status) {
|
|
32
|
+
case 'synced':
|
|
33
|
+
return chalk_1.default.green.bold('synced');
|
|
34
|
+
case 'new':
|
|
35
|
+
return chalk_1.default.cyan('new');
|
|
36
|
+
case 'changed':
|
|
37
|
+
return chalk_1.default.yellow('changed');
|
|
38
|
+
case 'deleted':
|
|
39
|
+
return chalk_1.default.red('deleted');
|
|
40
|
+
default:
|
|
41
|
+
return chalk_1.default.gray(status);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function createStatusTable(files) {
|
|
45
|
+
const table = new cli_table3_1.default({
|
|
46
|
+
head: [
|
|
47
|
+
chalk_1.default.cyan.bold(''),
|
|
48
|
+
chalk_1.default.cyan.bold('File'),
|
|
49
|
+
chalk_1.default.cyan.bold('Version'),
|
|
50
|
+
chalk_1.default.cyan.bold('Status'),
|
|
51
|
+
],
|
|
52
|
+
style: {
|
|
53
|
+
head: [],
|
|
54
|
+
border: ['gray'],
|
|
55
|
+
},
|
|
56
|
+
chars: {
|
|
57
|
+
top: '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐',
|
|
58
|
+
bottom: '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘',
|
|
59
|
+
left: '│', 'left-mid': '├',
|
|
60
|
+
mid: '─', 'mid-mid': '┼',
|
|
61
|
+
right: '│', 'right-mid': '┤',
|
|
62
|
+
middle: '│',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
for (const f of files) {
|
|
66
|
+
table.push([
|
|
67
|
+
statusIcon(f.status),
|
|
68
|
+
chalk_1.default.white(f.relativePath),
|
|
69
|
+
chalk_1.default.dim(`v${f.version}`),
|
|
70
|
+
statusLabel(f.status),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
return table.toString();
|
|
74
|
+
}
|
|
75
|
+
function heading(text) {
|
|
76
|
+
return '\n' + chalk_1.default.cyan.bold(` ${text}`) + '\n';
|
|
77
|
+
}
|
|
78
|
+
function success(text) {
|
|
79
|
+
return chalk_1.default.green(` ✓ ${text}`);
|
|
80
|
+
}
|
|
81
|
+
function warning(text) {
|
|
82
|
+
return chalk_1.default.yellow(` ⚠ ${text}`);
|
|
83
|
+
}
|
|
84
|
+
function error(text) {
|
|
85
|
+
return chalk_1.default.red(` ✗ ${text}`);
|
|
86
|
+
}
|
|
87
|
+
function info(text) {
|
|
88
|
+
return chalk_1.default.dim(` ${text}`);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/ui/format.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@applica-software-guru/sdd",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Story Driven Development",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sdd": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@inquirer/prompts": "^7.0.0",
|
|
14
|
+
"@applica-software-guru/sdd-core": "^0.1.0",
|
|
15
|
+
"chalk": "^4.1.2",
|
|
16
|
+
"cli-table3": "^0.6.5",
|
|
17
|
+
"clipboardy": "^2.3.0",
|
|
18
|
+
"commander": "^13.0.0",
|
|
19
|
+
"ora": "^5.4.1"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { SDD } from '@applica-software-guru/sdd-core';
|
|
4
|
+
import { heading, info } from '../ui/format.js';
|
|
5
|
+
|
|
6
|
+
function statusLabel(status: string): string {
|
|
7
|
+
switch (status) {
|
|
8
|
+
case 'draft':
|
|
9
|
+
return chalk.yellow('draft');
|
|
10
|
+
case 'applied':
|
|
11
|
+
return chalk.green('applied');
|
|
12
|
+
default:
|
|
13
|
+
return chalk.gray(status);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function registerCR(program: Command): void {
|
|
18
|
+
const cr = program
|
|
19
|
+
.command('cr')
|
|
20
|
+
.description('Manage change requests');
|
|
21
|
+
|
|
22
|
+
cr.command('list')
|
|
23
|
+
.description('List all change requests with their status')
|
|
24
|
+
.action(async () => {
|
|
25
|
+
const sdd = new SDD({ root: process.cwd() });
|
|
26
|
+
const crs = await sdd.changeRequests();
|
|
27
|
+
|
|
28
|
+
console.log(heading('Change Requests'));
|
|
29
|
+
|
|
30
|
+
if (crs.length === 0) {
|
|
31
|
+
console.log(info('No change requests found.\n'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const cr of crs) {
|
|
36
|
+
const icon = cr.frontmatter.status === 'applied' ? chalk.green(' ✓') : chalk.yellow(' ●');
|
|
37
|
+
console.log(`${icon} ${chalk.white(cr.relativePath)} ${chalk.dim(`[${statusLabel(cr.frontmatter.status)}]`)} ${chalk.cyan(cr.frontmatter.title)}`);
|
|
38
|
+
}
|
|
39
|
+
console.log('');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
cr.command('pending')
|
|
43
|
+
.description('Show draft change requests for the agent to process')
|
|
44
|
+
.action(async () => {
|
|
45
|
+
const sdd = new SDD({ root: process.cwd() });
|
|
46
|
+
const pending = await sdd.pendingChangeRequests();
|
|
47
|
+
|
|
48
|
+
if (pending.length === 0) {
|
|
49
|
+
console.log(heading('Change Requests'));
|
|
50
|
+
console.log(info('No pending change requests.\n'));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(heading(`Pending Change Requests (${pending.length})`));
|
|
55
|
+
|
|
56
|
+
for (const cr of pending) {
|
|
57
|
+
console.log(chalk.cyan.bold(` --- ${cr.relativePath} ---`));
|
|
58
|
+
console.log(chalk.cyan(` Title: ${cr.frontmatter.title}`));
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log(cr.body.trim().split('\n').map((line: string) => ` ${line}`).join('\n'));
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command('mark-cr-applied [files...]')
|
|
67
|
+
.description('Mark change requests as applied')
|
|
68
|
+
.action(async (files: string[]) => {
|
|
69
|
+
const sdd = new SDD({ root: process.cwd() });
|
|
70
|
+
const marked = await sdd.markCRApplied(files.length > 0 ? files : undefined);
|
|
71
|
+
|
|
72
|
+
console.log(heading('Mark CR Applied'));
|
|
73
|
+
|
|
74
|
+
if (marked.length === 0) {
|
|
75
|
+
console.log(info('No draft change requests to mark.\n'));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const f of marked) {
|
|
80
|
+
console.log(chalk.green(` ✓ ${f}`));
|
|
81
|
+
}
|
|
82
|
+
console.log(chalk.dim(`\n ${marked.length} change request(s) marked as applied.\n`));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { SDD } from '@applica-software-guru/sdd-core';
|
|
4
|
+
import { heading } from '../ui/format.js';
|
|
5
|
+
|
|
6
|
+
export function registerDiff(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('diff')
|
|
9
|
+
.description('Show files that need to be synced')
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const sdd = new SDD({ root: process.cwd() });
|
|
12
|
+
const pending = await sdd.pending();
|
|
13
|
+
|
|
14
|
+
console.log(heading('Pending'));
|
|
15
|
+
|
|
16
|
+
if (pending.length === 0) {
|
|
17
|
+
console.log(chalk.green(' ✓ Everything is synced.\n'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(chalk.yellow(` ${pending.length} file(s) pending:\n`));
|
|
22
|
+
|
|
23
|
+
for (const f of pending) {
|
|
24
|
+
const { status } = f.frontmatter;
|
|
25
|
+
const icon =
|
|
26
|
+
status === 'deleted'
|
|
27
|
+
? chalk.red(' ✗')
|
|
28
|
+
: status === 'new'
|
|
29
|
+
? chalk.cyan(' +')
|
|
30
|
+
: chalk.yellow(' ~');
|
|
31
|
+
|
|
32
|
+
const label =
|
|
33
|
+
status === 'deleted'
|
|
34
|
+
? chalk.red('deleted')
|
|
35
|
+
: status === 'new'
|
|
36
|
+
? chalk.cyan('new')
|
|
37
|
+
: chalk.yellow('changed');
|
|
38
|
+
|
|
39
|
+
console.log(`${icon} ${chalk.white(f.relativePath)} ${chalk.dim(`(${label})`)}`);
|
|
40
|
+
}
|
|
41
|
+
console.log('');
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { input } 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 } from '@applica-software-guru/sdd-core';
|
|
9
|
+
import { printBanner } from '../ui/banner.js';
|
|
10
|
+
import { success, info } from '../ui/format.js';
|
|
11
|
+
|
|
12
|
+
const START_PROMPT = `Read INSTRUCTIONS.md and the documentation in product/ and system/, then run \`sdd sync\` to start working.`;
|
|
13
|
+
|
|
14
|
+
function buildBootstrapPrompt(description: string): string {
|
|
15
|
+
return `Read INSTRUCTIONS.md first. This is a new SDD project.
|
|
16
|
+
|
|
17
|
+
Project goal: "${description}"
|
|
18
|
+
|
|
19
|
+
Your task: generate the initial documentation for this project. Ask me a few questions first to understand the project better (target users, main features, technical preferences), then create all documentation files:
|
|
20
|
+
|
|
21
|
+
- product/vision.md — Product vision and goals
|
|
22
|
+
- product/users.md — User personas
|
|
23
|
+
- product/features/*.md — One file per main feature
|
|
24
|
+
- system/entities.md — Data models (use ### headings per entity)
|
|
25
|
+
- system/architecture.md — Architecture decisions
|
|
26
|
+
- system/tech-stack.md — Technologies and frameworks
|
|
27
|
+
- system/interfaces.md — API contracts
|
|
28
|
+
|
|
29
|
+
Follow the file format described in INSTRUCTIONS.md for the YAML frontmatter. Do NOT write any code, only documentation.`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerInit(program: Command): void {
|
|
33
|
+
program
|
|
34
|
+
.command('init <project-name>')
|
|
35
|
+
.description('Initialize a new SDD project')
|
|
36
|
+
.option('--bootstrap', 'Generate a prompt to create initial documentation with an agent')
|
|
37
|
+
.action(async (projectName: string, options) => {
|
|
38
|
+
printBanner();
|
|
39
|
+
|
|
40
|
+
const projectDir = resolve(process.cwd(), projectName);
|
|
41
|
+
|
|
42
|
+
if (existsSync(resolve(projectDir, '.sdd'))) {
|
|
43
|
+
console.log(chalk.yellow(`\n SDD project already initialized at ${projectName}/\n`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const description = await input({
|
|
48
|
+
message: 'What should your project do?',
|
|
49
|
+
theme: {
|
|
50
|
+
prefix: chalk.cyan('?'),
|
|
51
|
+
style: { message: (text: string) => chalk.cyan.bold(text) },
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!description.trim()) {
|
|
56
|
+
console.log(chalk.yellow('\n No description provided. Aborting.\n'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!existsSync(projectDir)) {
|
|
61
|
+
mkdirSync(projectDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const spinner = ora({
|
|
65
|
+
text: 'Creating project structure...',
|
|
66
|
+
color: 'cyan',
|
|
67
|
+
}).start();
|
|
68
|
+
|
|
69
|
+
const sdd = new SDD({ root: projectDir });
|
|
70
|
+
const files = await sdd.init({ description: description.trim() });
|
|
71
|
+
|
|
72
|
+
spinner.stop();
|
|
73
|
+
|
|
74
|
+
// Project created
|
|
75
|
+
console.log(chalk.cyan.bold(`\n ${chalk.white(projectName)} is ready!\n`));
|
|
76
|
+
|
|
77
|
+
// Show what was created
|
|
78
|
+
console.log(chalk.dim(' Created:'));
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
console.log(success(f));
|
|
81
|
+
}
|
|
82
|
+
console.log(success('product/'));
|
|
83
|
+
console.log(success('product/features/'));
|
|
84
|
+
console.log(success('system/'));
|
|
85
|
+
console.log(success('code/'));
|
|
86
|
+
|
|
87
|
+
// Next steps
|
|
88
|
+
console.log(chalk.cyan.bold('\n Next steps:\n'));
|
|
89
|
+
|
|
90
|
+
console.log(` ${chalk.white('1.')} Enter the project folder:\n`);
|
|
91
|
+
console.log(` ${chalk.green(`cd ${projectName}`)}\n`);
|
|
92
|
+
|
|
93
|
+
if (options.bootstrap) {
|
|
94
|
+
console.log(` ${chalk.white('2.')} Open your AI agent and paste the prompt below.`);
|
|
95
|
+
console.log(` It will ask you a few questions and generate the initial docs.\n`);
|
|
96
|
+
} else {
|
|
97
|
+
console.log(` ${chalk.white('2.')} Start writing your documentation in ${chalk.cyan('product/')} and ${chalk.cyan('system/')}.`);
|
|
98
|
+
console.log(` Check ${chalk.cyan('INSTRUCTIONS.md')} for the file format.\n`);
|
|
99
|
+
|
|
100
|
+
console.log(` ${chalk.white('3.')} When ready, let your AI agent run:\n`);
|
|
101
|
+
console.log(` ${chalk.green('sdd sync')}\n`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Prompt
|
|
105
|
+
const prompt = options.bootstrap
|
|
106
|
+
? buildBootstrapPrompt(description.trim())
|
|
107
|
+
: START_PROMPT;
|
|
108
|
+
|
|
109
|
+
console.log(chalk.dim(' ─'.repeat(30)));
|
|
110
|
+
console.log(chalk.cyan.bold('\n Agent prompt:\n'));
|
|
111
|
+
console.log(chalk.white(` ${prompt.split('\n').join('\n ')}\n`));
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
await clipboardy.write(prompt);
|
|
115
|
+
console.log(success('Copied to clipboard — paste it into your agent.\n'));
|
|
116
|
+
} catch {
|
|
117
|
+
console.log(info('Copy the prompt above into your agent.\n'));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { SDD } from '@applica-software-guru/sdd-core';
|
|
4
|
+
import { heading, success, info } from '../ui/format.js';
|
|
5
|
+
|
|
6
|
+
export function registerMarkSynced(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('mark-synced [files...]')
|
|
9
|
+
.description('Mark specific files (or all) as synced')
|
|
10
|
+
.action(async (files: string[]) => {
|
|
11
|
+
const sdd = new SDD({ root: process.cwd() });
|
|
12
|
+
const marked = await sdd.markSynced(files.length > 0 ? files : undefined);
|
|
13
|
+
|
|
14
|
+
console.log(heading('Mark Synced'));
|
|
15
|
+
|
|
16
|
+
if (marked.length === 0) {
|
|
17
|
+
console.log(info('No pending files to mark.\n'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const f of marked) {
|
|
22
|
+
console.log(success(f));
|
|
23
|
+
}
|
|
24
|
+
console.log(chalk.dim(`\n ${marked.length} file(s) marked as synced.\n`));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { SDD } from '@applica-software-guru/sdd-core';
|
|
4
|
+
import { createStatusTable, heading, info } from '../ui/format.js';
|
|
5
|
+
|
|
6
|
+
export function registerStatus(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('status')
|
|
9
|
+
.description('Show status of all story files')
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const sdd = new SDD({ root: process.cwd() });
|
|
12
|
+
const result = await sdd.status();
|
|
13
|
+
|
|
14
|
+
if (result.files.length === 0) {
|
|
15
|
+
console.log(heading('Status'));
|
|
16
|
+
console.log(info('No story files found.\n'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const counts = {
|
|
21
|
+
new: result.files.filter((f) => f.status === 'new').length,
|
|
22
|
+
changed: result.files.filter((f) => f.status === 'changed').length,
|
|
23
|
+
deleted: result.files.filter((f) => f.status === 'deleted').length,
|
|
24
|
+
synced: result.files.filter((f) => f.status === 'synced').length,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
console.log(heading('Story Files'));
|
|
28
|
+
console.log(createStatusTable(result.files));
|
|
29
|
+
console.log('');
|
|
30
|
+
|
|
31
|
+
const parts: string[] = [];
|
|
32
|
+
if (counts.new) parts.push(chalk.cyan.bold(`${counts.new} new`));
|
|
33
|
+
if (counts.changed) parts.push(chalk.yellow.bold(`${counts.changed} changed`));
|
|
34
|
+
if (counts.deleted) parts.push(chalk.red.bold(`${counts.deleted} deleted`));
|
|
35
|
+
if (counts.synced) parts.push(chalk.green.bold(`${counts.synced} synced`));
|
|
36
|
+
console.log(` ${parts.join(' ')}\n`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { SDD } from '@applica-software-guru/sdd-core';
|
|
3
|
+
|
|
4
|
+
export function registerSync(program: Command): void {
|
|
5
|
+
program
|
|
6
|
+
.command('sync')
|
|
7
|
+
.description('Output the sync prompt for pending files (new/changed/deleted)')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const sdd = new SDD({ root: process.cwd() });
|
|
10
|
+
const prompt = await sdd.sync();
|
|
11
|
+
process.stdout.write(prompt);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { SDD } from '@applica-software-guru/sdd-core';
|
|
5
|
+
import { heading, success, warning, error } from '../ui/format.js';
|
|
6
|
+
|
|
7
|
+
export function registerValidate(program: Command): void {
|
|
8
|
+
program
|
|
9
|
+
.command('validate')
|
|
10
|
+
.description('Validate documentation for issues')
|
|
11
|
+
.action(async () => {
|
|
12
|
+
const spinner = ora({ text: 'Validating...', color: 'cyan' }).start();
|
|
13
|
+
|
|
14
|
+
const sdd = new SDD({ root: process.cwd() });
|
|
15
|
+
const result = await sdd.validate();
|
|
16
|
+
|
|
17
|
+
spinner.stop();
|
|
18
|
+
|
|
19
|
+
console.log(heading('Validation'));
|
|
20
|
+
|
|
21
|
+
if (result.valid && result.issues.length === 0) {
|
|
22
|
+
console.log(success('No issues found.\n'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const issue of result.issues) {
|
|
27
|
+
if (issue.severity === 'error') {
|
|
28
|
+
console.log(error(`${chalk.white(issue.filePath)}: ${issue.message}`));
|
|
29
|
+
} else {
|
|
30
|
+
console.log(warning(`${chalk.white(issue.filePath)}: ${issue.message}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const errors = result.issues.filter((i) => i.severity === 'error').length;
|
|
35
|
+
const warnings = result.issues.filter((i) => i.severity === 'warning').length;
|
|
36
|
+
console.log(
|
|
37
|
+
`\n ${chalk.red.bold(String(errors))} error(s) ${chalk.yellow.bold(String(warnings))} warning(s)\n`
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (!result.valid) {
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerInit } from './commands/init.js';
|
|
4
|
+
import { registerStatus } from './commands/status.js';
|
|
5
|
+
import { registerDiff } from './commands/diff.js';
|
|
6
|
+
import { registerSync } from './commands/sync.js';
|
|
7
|
+
import { registerValidate } from './commands/validate.js';
|
|
8
|
+
import { registerMarkSynced } from './commands/mark-synced.js';
|
|
9
|
+
import { registerCR } from './commands/cr.js';
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name('sdd')
|
|
15
|
+
.description('Story Driven Development — manage apps through structured documentation')
|
|
16
|
+
.version('0.1.0');
|
|
17
|
+
|
|
18
|
+
registerInit(program);
|
|
19
|
+
registerStatus(program);
|
|
20
|
+
registerDiff(program);
|
|
21
|
+
registerSync(program);
|
|
22
|
+
registerValidate(program);
|
|
23
|
+
registerMarkSynced(program);
|
|
24
|
+
registerCR(program);
|
|
25
|
+
|
|
26
|
+
program.parseAsync().catch((err) => {
|
|
27
|
+
console.error(err.message);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
package/src/ui/banner.ts
ADDED
package/src/ui/format.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
|
|
4
|
+
export function statusIcon(status: string): string {
|
|
5
|
+
switch (status) {
|
|
6
|
+
case 'synced':
|
|
7
|
+
return chalk.green('✓');
|
|
8
|
+
case 'new':
|
|
9
|
+
return chalk.cyan('+');
|
|
10
|
+
case 'changed':
|
|
11
|
+
return chalk.yellow('~');
|
|
12
|
+
case 'deleted':
|
|
13
|
+
return chalk.red('✗');
|
|
14
|
+
default:
|
|
15
|
+
return chalk.gray('?');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function statusLabel(status: string): string {
|
|
20
|
+
switch (status) {
|
|
21
|
+
case 'synced':
|
|
22
|
+
return chalk.green.bold('synced');
|
|
23
|
+
case 'new':
|
|
24
|
+
return chalk.cyan('new');
|
|
25
|
+
case 'changed':
|
|
26
|
+
return chalk.yellow('changed');
|
|
27
|
+
case 'deleted':
|
|
28
|
+
return chalk.red('deleted');
|
|
29
|
+
default:
|
|
30
|
+
return chalk.gray(status);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createStatusTable(
|
|
35
|
+
files: Array<{ relativePath: string; status: string; version: string; lastModified?: string }>
|
|
36
|
+
): string {
|
|
37
|
+
const table = new Table({
|
|
38
|
+
head: [
|
|
39
|
+
chalk.cyan.bold(''),
|
|
40
|
+
chalk.cyan.bold('File'),
|
|
41
|
+
chalk.cyan.bold('Version'),
|
|
42
|
+
chalk.cyan.bold('Status'),
|
|
43
|
+
],
|
|
44
|
+
style: {
|
|
45
|
+
head: [],
|
|
46
|
+
border: ['gray'],
|
|
47
|
+
},
|
|
48
|
+
chars: {
|
|
49
|
+
top: '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐',
|
|
50
|
+
bottom: '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘',
|
|
51
|
+
left: '│', 'left-mid': '├',
|
|
52
|
+
mid: '─', 'mid-mid': '┼',
|
|
53
|
+
right: '│', 'right-mid': '┤',
|
|
54
|
+
middle: '│',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
for (const f of files) {
|
|
59
|
+
table.push([
|
|
60
|
+
statusIcon(f.status),
|
|
61
|
+
chalk.white(f.relativePath),
|
|
62
|
+
chalk.dim(`v${f.version}`),
|
|
63
|
+
statusLabel(f.status),
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return table.toString();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function heading(text: string): string {
|
|
71
|
+
return '\n' + chalk.cyan.bold(` ${text}`) + '\n';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function success(text: string): string {
|
|
75
|
+
return chalk.green(` ✓ ${text}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function warning(text: string): string {
|
|
79
|
+
return chalk.yellow(` ⚠ ${text}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function error(text: string): string {
|
|
83
|
+
return chalk.red(` ✗ ${text}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function info(text: string): string {
|
|
87
|
+
return chalk.dim(` ${text}`);
|
|
88
|
+
}
|