sdd-cli 0.1.5 → 0.1.7
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/README.md +10 -0
- package/dist/cli.js +4 -0
- package/dist/commands/doctor.js +8 -6
- package/dist/commands/hello.js +35 -5
- package/dist/commands/req-export.js +5 -1
- package/dist/commands/req-finish.js +47 -29
- package/dist/commands/req-refine.js +3 -0
- package/dist/commands/req-report.js +7 -2
- package/dist/context/flags.js +15 -5
- package/dist/templates/render.js +4 -1
- package/dist/ui/prompt.d.ts +1 -0
- package/dist/ui/prompt.js +39 -11
- package/dist/workspace/index.js +36 -10
- package/flows/README.md +3 -29
- package/package.json +2 -1
- package/router/README.md +25 -23
- package/schemas/README.md +7 -0
- package/templates/README.md +12 -0
package/README.md
CHANGED
|
@@ -12,6 +12,12 @@ Build the foundation once, then lift everything else. The tool provides a durabl
|
|
|
12
12
|
|
|
13
13
|
Mission and vision live in `docs/MISSION.md` and `docs/VISION.md`.
|
|
14
14
|
|
|
15
|
+
Start with `docs/INDEX.md` for a full documentation map and `docs/STYLE.md` for formatting guidance.
|
|
16
|
+
Contributing guidelines live in `docs/CONTRIBUTING.md`.
|
|
17
|
+
Use the PR template in `.github/PULL_REQUEST_TEMPLATE.md`.
|
|
18
|
+
Maintenance guidance lives in `docs/MAINTENANCE.md`.
|
|
19
|
+
Install troubleshooting lives in `docs/TROUBLESHOOTING.md`.
|
|
20
|
+
|
|
15
21
|
Deep process, commands, interactions, and diagrams live in:
|
|
16
22
|
- `docs/PROCESS.md`
|
|
17
23
|
- `docs/COMMANDS.md`
|
|
@@ -55,6 +61,10 @@ Examples and templates:
|
|
|
55
61
|
- `examples/schemas/`
|
|
56
62
|
- `examples/diagrams/`
|
|
57
63
|
- `examples/packs/`
|
|
64
|
+
- `examples/README.md`
|
|
65
|
+
- `templates/README.md`
|
|
66
|
+
- `schemas/README.md`
|
|
67
|
+
- `flows/README.md`
|
|
58
68
|
- `templates/`
|
|
59
69
|
- `schemas/`
|
|
60
70
|
|
package/dist/cli.js
CHANGED
|
@@ -46,6 +46,7 @@ const route_1 = require("./commands/route");
|
|
|
46
46
|
const doctor_1 = require("./commands/doctor");
|
|
47
47
|
const paths_1 = require("./paths");
|
|
48
48
|
const flags_1 = require("./context/flags");
|
|
49
|
+
const prompt_1 = require("./ui/prompt");
|
|
49
50
|
const program = new commander_1.Command();
|
|
50
51
|
function getVersion() {
|
|
51
52
|
try {
|
|
@@ -76,6 +77,9 @@ program.hook("preAction", (thisCommand, actionCommand) => {
|
|
|
76
77
|
output: typeof opts.output === "string" ? opts.output : undefined
|
|
77
78
|
});
|
|
78
79
|
});
|
|
80
|
+
program.hook("postAction", () => {
|
|
81
|
+
(0, prompt_1.closePrompt)();
|
|
82
|
+
});
|
|
79
83
|
program
|
|
80
84
|
.command("hello")
|
|
81
85
|
.description("Start an interactive session and route intent")
|
package/dist/commands/doctor.js
CHANGED
|
@@ -67,11 +67,6 @@ function runDoctor(projectName, reqId) {
|
|
|
67
67
|
];
|
|
68
68
|
root = candidates.find((candidate) => fs_1.default.existsSync(candidate)) ?? root;
|
|
69
69
|
}
|
|
70
|
-
const jsonFiles = collectJsonFiles(root);
|
|
71
|
-
if (jsonFiles.length === 0) {
|
|
72
|
-
console.log("No JSON artifacts found in workspace.");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
70
|
let failures = 0;
|
|
76
71
|
const promptResult = (0, validate_prompt_packs_1.validatePromptPacks)();
|
|
77
72
|
if (!promptResult.valid) {
|
|
@@ -85,6 +80,10 @@ function runDoctor(projectName, reqId) {
|
|
|
85
80
|
console.log("Template validation failed:");
|
|
86
81
|
templateResult.errors.forEach((error) => console.log(`- ${error}`));
|
|
87
82
|
}
|
|
83
|
+
const jsonFiles = collectJsonFiles(root);
|
|
84
|
+
if (jsonFiles.length === 0) {
|
|
85
|
+
console.log("No JSON artifacts found in workspace.");
|
|
86
|
+
}
|
|
88
87
|
for (const filePath of jsonFiles) {
|
|
89
88
|
const schema = inferSchema(filePath);
|
|
90
89
|
if (!schema) {
|
|
@@ -101,9 +100,12 @@ function runDoctor(projectName, reqId) {
|
|
|
101
100
|
console.log(`Valid: ${filePath}`);
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
|
-
if (failures === 0) {
|
|
103
|
+
if (failures === 0 && jsonFiles.length > 0) {
|
|
105
104
|
console.log("All JSON artifacts are valid.");
|
|
106
105
|
}
|
|
106
|
+
else if (failures === 0) {
|
|
107
|
+
console.log("Prompt packs and templates are valid.");
|
|
108
|
+
}
|
|
107
109
|
else {
|
|
108
110
|
console.log(`Validation failed for ${failures} artifact(s).`);
|
|
109
111
|
}
|
package/dist/commands/hello.js
CHANGED
|
@@ -8,12 +8,30 @@ const prompt_packs_1 = require("../router/prompt-packs");
|
|
|
8
8
|
const prompt_map_1 = require("../router/prompt-map");
|
|
9
9
|
const req_create_1 = require("./req-create");
|
|
10
10
|
const flags_1 = require("../context/flags");
|
|
11
|
+
const route_1 = require("./route");
|
|
11
12
|
async function runHello(input, runQuestions) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
function loadWorkspace() {
|
|
14
|
+
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
15
|
+
(0, index_1.ensureWorkspace)(workspace);
|
|
16
|
+
const projects = (0, index_1.listProjects)(workspace);
|
|
17
|
+
return { workspace, projects };
|
|
18
|
+
}
|
|
19
|
+
let { workspace, projects } = loadWorkspace();
|
|
15
20
|
console.log("Hello from sdd-cli.");
|
|
16
21
|
console.log(`Workspace: ${workspace.root}`);
|
|
22
|
+
const useWorkspace = await (0, prompt_1.confirm)("Use this workspace path? (y/n) ");
|
|
23
|
+
if (!useWorkspace) {
|
|
24
|
+
const nextPath = await (0, prompt_1.ask)("Workspace path to use (blank to exit): ");
|
|
25
|
+
if (!nextPath) {
|
|
26
|
+
console.log("Run again from the desired folder or pass --output <path>.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
(0, flags_1.setFlags)({ output: nextPath });
|
|
30
|
+
const reloaded = loadWorkspace();
|
|
31
|
+
workspace = reloaded.workspace;
|
|
32
|
+
projects = reloaded.projects;
|
|
33
|
+
console.log(`Workspace updated: ${workspace.root}`);
|
|
34
|
+
}
|
|
17
35
|
const flags = (0, flags_1.getFlags)();
|
|
18
36
|
if (projects.length > 0) {
|
|
19
37
|
console.log("Active projects:");
|
|
@@ -49,8 +67,15 @@ async function runHello(input, runQuestions) {
|
|
|
49
67
|
}
|
|
50
68
|
const intent = (0, intent_1.classifyIntent)(text);
|
|
51
69
|
console.log(`Detected intent: ${intent.intent} -> ${intent.flow}`);
|
|
52
|
-
|
|
53
|
-
if (
|
|
70
|
+
const showRoute = await (0, prompt_1.confirm)("View route details now? (y/n) ");
|
|
71
|
+
if (showRoute) {
|
|
72
|
+
(0, route_1.runRoute)(text);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log("Next: run `sdd-cli route <your input>` to view details.");
|
|
76
|
+
}
|
|
77
|
+
const shouldRunQuestions = runQuestions ?? (await (0, prompt_1.confirm)("Run prompt questions now? (y/n) "));
|
|
78
|
+
if (shouldRunQuestions) {
|
|
54
79
|
const packs = (0, prompt_packs_1.loadPromptPacks)();
|
|
55
80
|
const packIds = intent_1.FLOW_PROMPT_PACKS[intent.flow] ?? [];
|
|
56
81
|
const answers = {};
|
|
@@ -78,4 +103,9 @@ async function runHello(input, runQuestions) {
|
|
|
78
103
|
}
|
|
79
104
|
}
|
|
80
105
|
}
|
|
106
|
+
else {
|
|
107
|
+
console.log("\nNext steps:");
|
|
108
|
+
console.log("- Run `sdd-cli route \"<your input>\"` to review the flow.");
|
|
109
|
+
console.log("- Run `sdd-cli req create` to draft a requirement.");
|
|
110
|
+
}
|
|
81
111
|
}
|
|
@@ -37,7 +37,11 @@ async function runReqExport() {
|
|
|
37
37
|
for (const entry of fs_1.default.readdirSync(sourceDir)) {
|
|
38
38
|
const srcPath = path_1.default.join(sourceDir, entry);
|
|
39
39
|
const destPath = path_1.default.join(targetDir, entry);
|
|
40
|
-
|
|
40
|
+
const stat = fs_1.default.statSync(srcPath);
|
|
41
|
+
if (stat.isDirectory()) {
|
|
42
|
+
fs_1.default.cpSync(srcPath, destPath, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
else if (stat.isFile()) {
|
|
41
45
|
fs_1.default.copyFileSync(srcPath, destPath);
|
|
42
46
|
}
|
|
43
47
|
}
|
|
@@ -64,17 +64,6 @@ async function runReqFinish() {
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
const doneDir = path_1.default.join(project.root, "requirements", "done", reqId);
|
|
68
|
-
fs_1.default.mkdirSync(path_1.default.dirname(doneDir), { recursive: true });
|
|
69
|
-
fs_1.default.renameSync(requirementDir, doneDir);
|
|
70
|
-
(0, index_1.updateProjectStatus)(workspace, project.name, "done");
|
|
71
|
-
const requirementJsonPath = path_1.default.join(doneDir, "requirement.json");
|
|
72
|
-
if (fs_1.default.existsSync(requirementJsonPath)) {
|
|
73
|
-
const requirementJson = JSON.parse(fs_1.default.readFileSync(requirementJsonPath, "utf-8"));
|
|
74
|
-
requirementJson.status = "done";
|
|
75
|
-
requirementJson.updatedAt = new Date().toISOString();
|
|
76
|
-
fs_1.default.writeFileSync(requirementJsonPath, JSON.stringify(requirementJson, null, 2), "utf-8");
|
|
77
|
-
}
|
|
78
67
|
const overview = await (0, prompt_1.ask)("Project overview (for README): ");
|
|
79
68
|
const howToRun = await (0, prompt_1.ask)("How to run (for README): ");
|
|
80
69
|
const archSummary = await (0, prompt_1.ask)("Architecture summary (for README): ");
|
|
@@ -110,26 +99,55 @@ async function runReqFinish() {
|
|
|
110
99
|
readmeValidation.errors.forEach((error) => console.log(`- ${error}`));
|
|
111
100
|
return;
|
|
112
101
|
}
|
|
102
|
+
const sourceDir = requirementDir;
|
|
103
|
+
const sourceStatus = path_1.default.basename(path_1.default.dirname(sourceDir));
|
|
104
|
+
const doneDir = path_1.default.join(project.root, "requirements", "done", reqId);
|
|
113
105
|
const projectRoot = project.root;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
106
|
+
let moved = false;
|
|
107
|
+
try {
|
|
108
|
+
if (sourceDir !== doneDir) {
|
|
109
|
+
fs_1.default.mkdirSync(path_1.default.dirname(doneDir), { recursive: true });
|
|
110
|
+
fs_1.default.renameSync(sourceDir, doneDir);
|
|
111
|
+
moved = true;
|
|
112
|
+
}
|
|
113
|
+
(0, index_1.updateProjectStatus)(workspace, project.name, "done");
|
|
114
|
+
const requirementJsonPath = path_1.default.join(doneDir, "requirement.json");
|
|
115
|
+
if (fs_1.default.existsSync(requirementJsonPath)) {
|
|
116
|
+
const requirementJson = JSON.parse(fs_1.default.readFileSync(requirementJsonPath, "utf-8"));
|
|
117
|
+
requirementJson.status = "done";
|
|
118
|
+
requirementJson.updatedAt = new Date().toISOString();
|
|
119
|
+
fs_1.default.writeFileSync(requirementJsonPath, JSON.stringify(requirementJson, null, 2), "utf-8");
|
|
120
|
+
}
|
|
121
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, "project-readme.md"), readmeRendered, "utf-8");
|
|
122
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, "project-readme.json"), JSON.stringify(readmeJson, null, 2), "utf-8");
|
|
123
|
+
const decisionLog = path_1.default.join(doneDir, "decision-log");
|
|
124
|
+
if (fs_1.default.existsSync(decisionLog)) {
|
|
125
|
+
const archiveRoot = path_1.default.join(projectRoot, "decision-log", reqId);
|
|
126
|
+
fs_1.default.mkdirSync(path_1.default.dirname(archiveRoot), { recursive: true });
|
|
127
|
+
fs_1.default.renameSync(decisionLog, archiveRoot);
|
|
128
|
+
}
|
|
129
|
+
const progressLog = path_1.default.join(doneDir, "progress-log.md");
|
|
130
|
+
if (!fs_1.default.existsSync(progressLog)) {
|
|
131
|
+
fs_1.default.writeFileSync(progressLog, "# Progress Log\n\n", "utf-8");
|
|
132
|
+
}
|
|
133
|
+
const logEntry = `\n- ${new Date().toISOString()} finished requirement ${reqId}\n`;
|
|
134
|
+
fs_1.default.appendFileSync(progressLog, logEntry, "utf-8");
|
|
135
|
+
const changelog = path_1.default.join(doneDir, "changelog.md");
|
|
136
|
+
if (!fs_1.default.existsSync(changelog)) {
|
|
137
|
+
fs_1.default.writeFileSync(changelog, "# Changelog\n\n", "utf-8");
|
|
138
|
+
}
|
|
139
|
+
const changeEntry = `\n- ${new Date().toISOString()} finished requirement ${reqId}\n`;
|
|
140
|
+
fs_1.default.appendFileSync(changelog, changeEntry, "utf-8");
|
|
125
141
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (moved && fs_1.default.existsSync(doneDir) && !fs_1.default.existsSync(sourceDir)) {
|
|
144
|
+
fs_1.default.renameSync(doneDir, sourceDir);
|
|
145
|
+
}
|
|
146
|
+
if (sourceStatus && sourceStatus !== "done") {
|
|
147
|
+
(0, index_1.updateProjectStatus)(workspace, project.name, sourceStatus);
|
|
148
|
+
}
|
|
149
|
+
console.log(`Failed to finish requirement: ${error.message}`);
|
|
150
|
+
return;
|
|
131
151
|
}
|
|
132
|
-
const changeEntry = `\n- ${new Date().toISOString()} finished requirement ${reqId}\n`;
|
|
133
|
-
fs_1.default.appendFileSync(changelog, changeEntry, "utf-8");
|
|
134
152
|
console.log(`Moved requirement to ${doneDir}`);
|
|
135
153
|
}
|
|
@@ -143,6 +143,9 @@ async function runReqRefine() {
|
|
|
143
143
|
const mdPath = reqPath.replace("requirement.json", "requirement.md");
|
|
144
144
|
fs_1.default.writeFileSync(mdPath, rendered, "utf-8");
|
|
145
145
|
const changelogPath = path_1.default.join(path_1.default.dirname(reqPath), "changelog.md");
|
|
146
|
+
if (!fs_1.default.existsSync(changelogPath)) {
|
|
147
|
+
fs_1.default.writeFileSync(changelogPath, "# Changelog\n\n", "utf-8");
|
|
148
|
+
}
|
|
146
149
|
const logEntry = `\n- ${new Date().toISOString()} refined requirement ${updated.id}\n`;
|
|
147
150
|
fs_1.default.appendFileSync(changelogPath, logEntry, "utf-8");
|
|
148
151
|
if (flags.improve) {
|
|
@@ -14,8 +14,7 @@ const REQUIRED_FILES = [
|
|
|
14
14
|
"technical-spec.json",
|
|
15
15
|
"architecture.json",
|
|
16
16
|
"test-plan.json",
|
|
17
|
-
"quality.json"
|
|
18
|
-
"project-readme.json"
|
|
17
|
+
"quality.json"
|
|
19
18
|
];
|
|
20
19
|
async function runReqReport() {
|
|
21
20
|
const projectName = await (0, prompt_1.askProjectName)();
|
|
@@ -48,5 +47,11 @@ async function runReqReport() {
|
|
|
48
47
|
if (!exists)
|
|
49
48
|
missing += 1;
|
|
50
49
|
}
|
|
50
|
+
const projectReadmePath = path_1.default.join(project.root, "project-readme.json");
|
|
51
|
+
const projectReadmeExists = fs_1.default.existsSync(projectReadmePath);
|
|
52
|
+
console.log(`${projectReadmeExists ? "OK" : "MISSING"}: ../project-readme.json`);
|
|
53
|
+
if (!projectReadmeExists) {
|
|
54
|
+
missing += 1;
|
|
55
|
+
}
|
|
51
56
|
console.log(`Missing files: ${missing}`);
|
|
52
57
|
}
|
package/dist/context/flags.js
CHANGED
|
@@ -10,11 +10,21 @@ const flags = {
|
|
|
10
10
|
output: undefined
|
|
11
11
|
};
|
|
12
12
|
function setFlags(next) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
if ("approve" in next) {
|
|
14
|
+
flags.approve = Boolean(next.approve);
|
|
15
|
+
}
|
|
16
|
+
if ("improve" in next) {
|
|
17
|
+
flags.improve = Boolean(next.improve);
|
|
18
|
+
}
|
|
19
|
+
if ("parallel" in next) {
|
|
20
|
+
flags.parallel = Boolean(next.parallel);
|
|
21
|
+
}
|
|
22
|
+
if ("project" in next) {
|
|
23
|
+
flags.project = typeof next.project === "string" ? next.project : undefined;
|
|
24
|
+
}
|
|
25
|
+
if ("output" in next) {
|
|
26
|
+
flags.output = typeof next.output === "string" ? next.output : undefined;
|
|
27
|
+
}
|
|
18
28
|
}
|
|
19
29
|
function getFlags() {
|
|
20
30
|
return { ...flags };
|
package/dist/templates/render.js
CHANGED
|
@@ -12,7 +12,10 @@ function loadTemplate(name) {
|
|
|
12
12
|
const root = (0, paths_1.getRepoRoot)();
|
|
13
13
|
const mdPath = path_1.default.join(root, "templates", `${name}.md`);
|
|
14
14
|
const ymlPath = path_1.default.join(root, "templates", `${name}.yml`);
|
|
15
|
-
const filePath = fs_1.default.existsSync(mdPath) ? mdPath : ymlPath;
|
|
15
|
+
const filePath = fs_1.default.existsSync(mdPath) ? mdPath : fs_1.default.existsSync(ymlPath) ? ymlPath : null;
|
|
16
|
+
if (!filePath) {
|
|
17
|
+
throw new Error(`Template not found: ${name} (.md or .yml)`);
|
|
18
|
+
}
|
|
16
19
|
return fs_1.default.readFileSync(filePath, "utf-8");
|
|
17
20
|
}
|
|
18
21
|
function renderTemplate(template, data) {
|
package/dist/ui/prompt.d.ts
CHANGED
package/dist/ui/prompt.js
CHANGED
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.closePrompt = closePrompt;
|
|
6
7
|
exports.ask = ask;
|
|
7
8
|
exports.askProjectName = askProjectName;
|
|
8
9
|
exports.confirm = confirm;
|
|
@@ -10,31 +11,58 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
10
11
|
const readline_1 = __importDefault(require("readline"));
|
|
11
12
|
const flags_1 = require("../context/flags");
|
|
12
13
|
let queuedAnswers = null;
|
|
14
|
+
let rl = null;
|
|
15
|
+
function shouldUseQueuedAnswers() {
|
|
16
|
+
if (process.env.SDD_STDIN === "1") {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return !process.stdin.isTTY && !process.stdout.isTTY;
|
|
20
|
+
}
|
|
13
21
|
function getQueuedAnswers() {
|
|
14
22
|
if (queuedAnswers) {
|
|
15
23
|
return queuedAnswers;
|
|
16
24
|
}
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
if (shouldUseQueuedAnswers()) {
|
|
26
|
+
try {
|
|
27
|
+
const raw = fs_1.default.readFileSync(0, "utf-8");
|
|
28
|
+
queuedAnswers = raw.split(/\r?\n/).filter((line) => line.length > 0);
|
|
29
|
+
return queuedAnswers;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
queuedAnswers = [];
|
|
33
|
+
return queuedAnswers;
|
|
34
|
+
}
|
|
21
35
|
}
|
|
22
36
|
queuedAnswers = [];
|
|
23
37
|
return queuedAnswers;
|
|
24
38
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
39
|
+
function getInterface() {
|
|
40
|
+
if (rl) {
|
|
41
|
+
return rl;
|
|
42
|
+
}
|
|
43
|
+
rl = readline_1.default.createInterface({
|
|
44
|
+
input: process.stdin,
|
|
45
|
+
output: process.stdout
|
|
46
|
+
});
|
|
47
|
+
return rl;
|
|
48
|
+
}
|
|
49
|
+
function closePrompt() {
|
|
50
|
+
if (!rl) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
rl.close();
|
|
54
|
+
rl = null;
|
|
55
|
+
}
|
|
56
|
+
process.on("exit", () => closePrompt());
|
|
30
57
|
function ask(question) {
|
|
31
|
-
if (
|
|
58
|
+
if (shouldUseQueuedAnswers()) {
|
|
32
59
|
const queue = getQueuedAnswers();
|
|
33
60
|
const answer = queue.shift() ?? "";
|
|
34
61
|
return Promise.resolve(answer.trim());
|
|
35
62
|
}
|
|
36
63
|
return new Promise((resolve) => {
|
|
37
|
-
|
|
64
|
+
const prompt = getInterface();
|
|
65
|
+
prompt.question(question, (answer) => {
|
|
38
66
|
resolve(answer.trim());
|
|
39
67
|
});
|
|
40
68
|
});
|
package/dist/workspace/index.js
CHANGED
|
@@ -15,6 +15,22 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
15
15
|
const path_1 = __importDefault(require("path"));
|
|
16
16
|
const os_1 = __importDefault(require("os"));
|
|
17
17
|
const flags_1 = require("../context/flags");
|
|
18
|
+
function readJsonFile(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs_1.default.readFileSync(filePath, "utf-8");
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function readWorkspaceIndex(workspace) {
|
|
28
|
+
const parsed = readJsonFile(workspace.indexPath);
|
|
29
|
+
if (!parsed || !Array.isArray(parsed.projects)) {
|
|
30
|
+
return { projects: [] };
|
|
31
|
+
}
|
|
32
|
+
return { projects: parsed.projects };
|
|
33
|
+
}
|
|
18
34
|
function getWorkspaceInfo() {
|
|
19
35
|
const flags = (0, flags_1.getFlags)();
|
|
20
36
|
const root = flags.output
|
|
@@ -61,9 +77,8 @@ function listProjects(workspace) {
|
|
|
61
77
|
if (!fs_1.default.existsSync(workspace.indexPath)) {
|
|
62
78
|
return [];
|
|
63
79
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
return (parsed.projects ?? []).map((project) => ({
|
|
80
|
+
const parsed = readJsonFile(workspace.indexPath);
|
|
81
|
+
return (parsed?.projects ?? []).map((project) => ({
|
|
67
82
|
name: project.name ?? "unknown",
|
|
68
83
|
status: project.status ?? "unknown"
|
|
69
84
|
}));
|
|
@@ -80,7 +95,20 @@ function ensureProject(workspace, name, domain) {
|
|
|
80
95
|
const metadataPath = path_1.default.join(projectRoot, "metadata.json");
|
|
81
96
|
let metadata;
|
|
82
97
|
if (fs_1.default.existsSync(metadataPath)) {
|
|
83
|
-
|
|
98
|
+
const parsed = readJsonFile(metadataPath);
|
|
99
|
+
if (parsed) {
|
|
100
|
+
metadata = parsed;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const now = new Date().toISOString();
|
|
104
|
+
metadata = {
|
|
105
|
+
name: project.name,
|
|
106
|
+
status: "backlog",
|
|
107
|
+
domain,
|
|
108
|
+
createdAt: now,
|
|
109
|
+
updatedAt: now
|
|
110
|
+
};
|
|
111
|
+
}
|
|
84
112
|
}
|
|
85
113
|
else {
|
|
86
114
|
const now = new Date().toISOString();
|
|
@@ -93,8 +121,7 @@ function ensureProject(workspace, name, domain) {
|
|
|
93
121
|
};
|
|
94
122
|
fs_1.default.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
95
123
|
}
|
|
96
|
-
const
|
|
97
|
-
const index = JSON.parse(indexRaw);
|
|
124
|
+
const index = readWorkspaceIndex(workspace);
|
|
98
125
|
index.projects = index.projects ?? [];
|
|
99
126
|
const existing = index.projects.find((entry) => entry.name === project.name);
|
|
100
127
|
if (existing) {
|
|
@@ -112,8 +139,7 @@ function createProject(workspace, name, domain) {
|
|
|
112
139
|
function updateProjectStatus(workspace, name, status) {
|
|
113
140
|
ensureWorkspace(workspace);
|
|
114
141
|
const project = getProjectInfo(workspace, name);
|
|
115
|
-
const
|
|
116
|
-
const index = JSON.parse(indexRaw);
|
|
142
|
+
const index = readWorkspaceIndex(workspace);
|
|
117
143
|
index.projects = index.projects ?? [];
|
|
118
144
|
const existing = index.projects.find((entry) => entry.name === project.name);
|
|
119
145
|
if (existing) {
|
|
@@ -125,8 +151,8 @@ function updateProjectStatus(workspace, name, status) {
|
|
|
125
151
|
fs_1.default.writeFileSync(workspace.indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
126
152
|
const projectRoot = project.root;
|
|
127
153
|
const metadataPath = path_1.default.join(projectRoot, "metadata.json");
|
|
128
|
-
|
|
129
|
-
|
|
154
|
+
const metadata = fs_1.default.existsSync(metadataPath) ? readJsonFile(metadataPath) : null;
|
|
155
|
+
if (metadata) {
|
|
130
156
|
metadata.status = status;
|
|
131
157
|
metadata.updatedAt = new Date().toISOString();
|
|
132
158
|
fs_1.default.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
package/flows/README.md
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
# Flows
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- `LAWYER.md`
|
|
6
|
-
- `TEACHER.md`
|
|
7
|
-
- `ADMISSIONS_ADMIN.md`
|
|
8
|
-
- `STATE_ADMIN.md`
|
|
9
|
-
- `TAXES_ADMIN.md`
|
|
10
|
-
- `STUDENT_UNIVERSITY.md`
|
|
11
|
-
- `DATA_SCIENTIST.md`
|
|
12
|
-
- `PROGRAMMER.md`
|
|
13
|
-
- `BUG_FIX.md`
|
|
14
|
-
- `ECOMMERCE.md`
|
|
15
|
-
- `RETAIL_STORE.md`
|
|
16
|
-
- `COURT_SYSTEM.md`
|
|
17
|
-
- `GRAPHIC_DESIGN.md`
|
|
18
|
-
- `ART.md`
|
|
19
|
-
- `HISTORY.md`
|
|
20
|
-
- `SOCIOLOGY.md`
|
|
21
|
-
- `ECONOMICS.md`
|
|
22
|
-
- `PR_REVIEW.md`
|
|
23
|
-
|
|
24
|
-
Each flow includes:
|
|
25
|
-
- Discovery questions
|
|
26
|
-
- Required artifacts
|
|
27
|
-
- Risk and compliance notes
|
|
28
|
-
- Acceptance criteria examples
|
|
29
|
-
- Recommended outputs and handoff steps
|
|
1
|
+
# Flows
|
|
2
|
+
|
|
3
|
+
Domain playbooks used by the router and discovery flows.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdd-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "SDD-first, AI-native CLI for end-to-end delivery.",
|
|
5
5
|
"homepage": "https://github.com/jdsalasca/sdd-tool#readme",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"build": "tsc -p tsconfig.json",
|
|
28
28
|
"start": "node dist/cli.js",
|
|
29
29
|
"dev": "ts-node src/cli.ts",
|
|
30
|
+
"preinstall": "node scripts/preinstall.js",
|
|
30
31
|
"pretest": "npm run build",
|
|
31
32
|
"test": "node --test tests/*.test.js"
|
|
32
33
|
},
|
package/router/README.md
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
# Router scripts
|
|
2
|
-
|
|
3
|
-
These scripts define intent detection and the step-by-step conversation for each flow.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
1
|
+
# Router scripts
|
|
2
|
+
|
|
3
|
+
These scripts define intent detection and the step-by-step conversation for each flow.
|
|
4
|
+
|
|
5
|
+
See `flows/README.md` for the domain playbooks.
|
|
6
|
+
|
|
7
|
+
Each script includes:
|
|
8
|
+
- Entry signals
|
|
9
|
+
- Required questions
|
|
10
|
+
- Required outputs
|
|
11
|
+
- Gate checks
|
|
12
|
+
- Suggested agent roles
|
|
13
|
+
- Scripted Q/A tree with optional branches
|
|
14
|
+
|
|
15
|
+
Available flows:
|
|
16
|
+
- BUG_FIX
|
|
17
|
+
- LEARN
|
|
18
|
+
- SOFTWARE_FEATURE
|
|
19
|
+
- DATA_SCIENCE
|
|
20
|
+
- DESIGN
|
|
21
|
+
- HUMANITIES
|
|
22
|
+
- BUSINESS
|
|
23
|
+
- LEGAL
|
|
24
|
+
- PR_REVIEW
|
|
25
|
+
- GENERIC
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Templates
|
|
2
|
+
|
|
3
|
+
Markdown and YAML templates used to generate SDD artifacts.
|
|
4
|
+
|
|
5
|
+
## Index
|
|
6
|
+
- `templates/template-index.json` lists all templates and placeholders.
|
|
7
|
+
- `templates/gate-index.json` lists gate prompt templates.
|
|
8
|
+
- `templates/prompt-pack-index.json` lists prompt packs.
|
|
9
|
+
|
|
10
|
+
## Rules
|
|
11
|
+
- Keep placeholders aligned with `templates/validate` rules.
|
|
12
|
+
- Use lowercase file names with dashes (`functional-spec.md`).
|