sdd-cli 0.1.6 → 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.
@@ -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
  }
@@ -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
- if (fs_1.default.statSync(srcPath).isFile()) {
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
- fs_1.default.writeFileSync(path_1.default.join(projectRoot, "project-readme.md"), readmeRendered, "utf-8");
115
- fs_1.default.writeFileSync(path_1.default.join(projectRoot, "project-readme.json"), JSON.stringify(readmeJson, null, 2), "utf-8");
116
- const decisionLog = path_1.default.join(doneDir, "decision-log");
117
- if (fs_1.default.existsSync(decisionLog)) {
118
- const archiveRoot = path_1.default.join(projectRoot, "decision-log", reqId);
119
- fs_1.default.mkdirSync(path_1.default.dirname(archiveRoot), { recursive: true });
120
- fs_1.default.renameSync(decisionLog, archiveRoot);
121
- }
122
- const progressLog = path_1.default.join(doneDir, "progress-log.md");
123
- if (!fs_1.default.existsSync(progressLog)) {
124
- fs_1.default.writeFileSync(progressLog, "# Progress Log\n\n", "utf-8");
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
- const logEntry = `\n- ${new Date().toISOString()} finished requirement ${reqId}\n`;
127
- fs_1.default.appendFileSync(progressLog, logEntry, "utf-8");
128
- const changelog = path_1.default.join(doneDir, "changelog.md");
129
- if (!fs_1.default.existsSync(changelog)) {
130
- fs_1.default.writeFileSync(changelog, "# Changelog\n\n", "utf-8");
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
  }
@@ -10,11 +10,21 @@ const flags = {
10
10
  output: undefined
11
11
  };
12
12
  function setFlags(next) {
13
- flags.approve = Boolean(next.approve);
14
- flags.improve = Boolean(next.improve);
15
- flags.parallel = Boolean(next.parallel);
16
- flags.project = typeof next.project === "string" ? next.project : undefined;
17
- flags.output = typeof next.output === "string" ? next.output : undefined;
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 };
@@ -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.js CHANGED
@@ -12,14 +12,26 @@ const readline_1 = __importDefault(require("readline"));
12
12
  const flags_1 = require("../context/flags");
13
13
  let queuedAnswers = null;
14
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
+ }
15
21
  function getQueuedAnswers() {
16
22
  if (queuedAnswers) {
17
23
  return queuedAnswers;
18
24
  }
19
- if (!process.stdin.isTTY) {
20
- const raw = fs_1.default.readFileSync(0, "utf-8");
21
- queuedAnswers = raw.split(/\r?\n/).filter((line) => line.length > 0);
22
- return queuedAnswers;
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
+ }
23
35
  }
24
36
  queuedAnswers = [];
25
37
  return queuedAnswers;
@@ -43,7 +55,7 @@ function closePrompt() {
43
55
  }
44
56
  process.on("exit", () => closePrompt());
45
57
  function ask(question) {
46
- if (!process.stdin.isTTY) {
58
+ if (shouldUseQueuedAnswers()) {
47
59
  const queue = getQueuedAnswers();
48
60
  const answer = queue.shift() ?? "";
49
61
  return Promise.resolve(answer.trim());
@@ -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 raw = fs_1.default.readFileSync(workspace.indexPath, "utf-8");
65
- const parsed = JSON.parse(raw);
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
- metadata = JSON.parse(fs_1.default.readFileSync(metadataPath, "utf-8"));
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 indexRaw = fs_1.default.readFileSync(workspace.indexPath, "utf-8");
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 indexRaw = fs_1.default.readFileSync(workspace.indexPath, "utf-8");
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
- if (fs_1.default.existsSync(metadataPath)) {
129
- const metadata = JSON.parse(fs_1.default.readFileSync(metadataPath, "utf-8"));
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-cli",
3
- "version": "0.1.6",
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": {