sdd-cli 0.1.6 → 0.1.8
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/doctor.js +8 -6
- package/dist/commands/hello.js +79 -4
- package/dist/commands/req-create.d.ts +15 -1
- package/dist/commands/req-create.js +58 -32
- 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.js +17 -5
- package/dist/workspace/index.js +36 -10
- package/package.json +1 -1
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
|
@@ -9,6 +9,62 @@ 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
11
|
const route_1 = require("./route");
|
|
12
|
+
function buildAutopilotDraft(input, flow, domain) {
|
|
13
|
+
const cleanInput = input.trim();
|
|
14
|
+
const objective = cleanInput.length > 0 ? cleanInput : "Deliver a clear first requirement draft.";
|
|
15
|
+
const scopeByFlow = {
|
|
16
|
+
BUG_FIX: "Reproduce issue, isolate root cause, define fix",
|
|
17
|
+
PR_REVIEW: "Review feedback, plan responses, track actions",
|
|
18
|
+
SOFTWARE_FEATURE: "Core feature behavior and acceptance flow",
|
|
19
|
+
DATA_SCIENCE: "Dataset, modeling approach, and evaluation plan",
|
|
20
|
+
DESIGN: "Core design goals, accessibility, and deliverables",
|
|
21
|
+
HUMANITIES: "Research question, sources, and analytical lens",
|
|
22
|
+
BUSINESS: "Business objective, model assumptions, and constraints",
|
|
23
|
+
LEGAL: "Applicable legal constraints and compliance requirements",
|
|
24
|
+
LEARN: "Learning objective, structure, and practice outputs",
|
|
25
|
+
GENERIC: "Core user need and initial delivery scope"
|
|
26
|
+
};
|
|
27
|
+
const outByFlow = {
|
|
28
|
+
BUG_FIX: "Unrelated refactors not needed for this fix",
|
|
29
|
+
PR_REVIEW: "Changes outside current PR scope",
|
|
30
|
+
SOFTWARE_FEATURE: "Future enhancements after MVP",
|
|
31
|
+
DATA_SCIENCE: "Production hardening beyond first iteration",
|
|
32
|
+
DESIGN: "Full rebrand outside stated objective",
|
|
33
|
+
HUMANITIES: "Unrelated historical periods or disciplines",
|
|
34
|
+
BUSINESS: "Additional markets not in initial launch",
|
|
35
|
+
LEGAL: "Jurisdictions outside selected compliance scope",
|
|
36
|
+
LEARN: "Advanced topics outside current learning target",
|
|
37
|
+
GENERIC: "Additional ideas to evaluate in next iteration"
|
|
38
|
+
};
|
|
39
|
+
const actorByDomain = {
|
|
40
|
+
bug_fix: "developer, qa",
|
|
41
|
+
pr_review: "reviewer, contributor",
|
|
42
|
+
software: "end user, product owner, developer",
|
|
43
|
+
data_science: "analyst, data scientist, stakeholder",
|
|
44
|
+
design: "designer, end user, stakeholder",
|
|
45
|
+
humanities: "researcher, reader",
|
|
46
|
+
business: "customer, business owner, operator",
|
|
47
|
+
legal: "legal team, compliance owner",
|
|
48
|
+
learning: "learner, mentor",
|
|
49
|
+
generic: "user, stakeholder"
|
|
50
|
+
};
|
|
51
|
+
const safeFlow = scopeByFlow[flow] ? flow : "GENERIC";
|
|
52
|
+
const safeDomain = actorByDomain[domain] ? domain : "generic";
|
|
53
|
+
return {
|
|
54
|
+
domain: safeDomain === "generic" ? "software" : safeDomain,
|
|
55
|
+
actors: actorByDomain[safeDomain],
|
|
56
|
+
objective,
|
|
57
|
+
scope_in: scopeByFlow[safeFlow],
|
|
58
|
+
scope_out: outByFlow[safeFlow],
|
|
59
|
+
acceptance_criteria: "A baseline requirement is generated and ready for refinement with stakeholders",
|
|
60
|
+
nfr_security: "Follow secure defaults and data handling best practices",
|
|
61
|
+
nfr_performance: "Set practical baseline targets for first delivery",
|
|
62
|
+
nfr_availability: "Keep workflow usable and stable for normal usage",
|
|
63
|
+
constraints: "Timebox first iteration, keep implementation simple",
|
|
64
|
+
risks: "Ambiguity in requirements, underestimated scope",
|
|
65
|
+
links: ""
|
|
66
|
+
};
|
|
67
|
+
}
|
|
12
68
|
async function runHello(input, runQuestions) {
|
|
13
69
|
function loadWorkspace() {
|
|
14
70
|
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
@@ -67,6 +123,7 @@ async function runHello(input, runQuestions) {
|
|
|
67
123
|
}
|
|
68
124
|
const intent = (0, intent_1.classifyIntent)(text);
|
|
69
125
|
console.log(`Detected intent: ${intent.intent} -> ${intent.flow}`);
|
|
126
|
+
console.log("Step 1/3: Intent detected.");
|
|
70
127
|
const showRoute = await (0, prompt_1.confirm)("View route details now? (y/n) ");
|
|
71
128
|
if (showRoute) {
|
|
72
129
|
(0, route_1.runRoute)(text);
|
|
@@ -75,6 +132,7 @@ async function runHello(input, runQuestions) {
|
|
|
75
132
|
console.log("Next: run `sdd-cli route <your input>` to view details.");
|
|
76
133
|
}
|
|
77
134
|
const shouldRunQuestions = runQuestions ?? (await (0, prompt_1.confirm)("Run prompt questions now? (y/n) "));
|
|
135
|
+
console.log("Step 2/3: Requirement setup.");
|
|
78
136
|
if (shouldRunQuestions) {
|
|
79
137
|
const packs = (0, prompt_packs_1.loadPromptPacks)();
|
|
80
138
|
const packIds = intent_1.FLOW_PROMPT_PACKS[intent.flow] ?? [];
|
|
@@ -99,13 +157,30 @@ async function runHello(input, runQuestions) {
|
|
|
99
157
|
console.log(JSON.stringify(mapped, null, 2));
|
|
100
158
|
const ok = await (0, prompt_1.confirm)("Generate requirement draft now? (y/n) ");
|
|
101
159
|
if (ok) {
|
|
102
|
-
await (0, req_create_1.runReqCreate)(mapped);
|
|
160
|
+
const created = await (0, req_create_1.runReqCreate)(mapped, { autofill: true });
|
|
161
|
+
if (created) {
|
|
162
|
+
console.log(`Step 3/3: Draft created (${created.reqId}).`);
|
|
163
|
+
console.log("Next suggested command: sdd-cli req refine");
|
|
164
|
+
}
|
|
103
165
|
}
|
|
104
166
|
}
|
|
105
167
|
}
|
|
106
168
|
else {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
169
|
+
const auto = await (0, prompt_1.confirm)("Create a first requirement draft automatically now? (y/n) ");
|
|
170
|
+
if (auto) {
|
|
171
|
+
const draft = buildAutopilotDraft(text, intent.flow, intent.domain);
|
|
172
|
+
const created = await (0, req_create_1.runReqCreate)(draft, { autofill: true });
|
|
173
|
+
if (created) {
|
|
174
|
+
console.log(`Step 3/3: Draft created (${created.reqId}).`);
|
|
175
|
+
console.log("Next suggested commands:");
|
|
176
|
+
console.log("- sdd-cli req refine");
|
|
177
|
+
console.log("- sdd-cli req plan");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log("\nNext steps:");
|
|
182
|
+
console.log("- Run `sdd-cli route \"<your input>\"` to review the flow.");
|
|
183
|
+
console.log("- Run `sdd-cli req create` to draft a requirement.");
|
|
184
|
+
}
|
|
110
185
|
}
|
|
111
186
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export type RequirementDraft = {
|
|
2
|
+
project_name?: string;
|
|
3
|
+
domain?: string;
|
|
4
|
+
actors?: string;
|
|
2
5
|
objective?: string;
|
|
3
6
|
scope_in?: string;
|
|
4
7
|
scope_out?: string;
|
|
@@ -6,5 +9,16 @@ export type RequirementDraft = {
|
|
|
6
9
|
nfr_security?: string;
|
|
7
10
|
nfr_performance?: string;
|
|
8
11
|
nfr_availability?: string;
|
|
12
|
+
constraints?: string;
|
|
13
|
+
risks?: string;
|
|
14
|
+
links?: string;
|
|
9
15
|
};
|
|
10
|
-
export
|
|
16
|
+
export type ReqCreateOptions = {
|
|
17
|
+
autofill?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type ReqCreateResult = {
|
|
20
|
+
reqId: string;
|
|
21
|
+
requirementDir: string;
|
|
22
|
+
projectRoot: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function runReqCreate(draft?: RequirementDraft, options?: ReqCreateOptions): Promise<ReqCreateResult | null>;
|
|
@@ -17,20 +17,26 @@ function generateId() {
|
|
|
17
17
|
const stamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
18
18
|
return `REQ-${stamp}`;
|
|
19
19
|
}
|
|
20
|
-
async function runReqCreate(draft) {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
let
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
async function runReqCreate(draft, options) {
|
|
21
|
+
const auto = Boolean(options?.autofill);
|
|
22
|
+
const projectName = draft?.project_name?.trim() || (await (0, prompt_1.askProjectName)());
|
|
23
|
+
if (!projectName) {
|
|
24
|
+
console.log("Project name is required.");
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const domain = draft?.domain ?? (auto ? "software" : await (0, prompt_1.ask)("Domain (software, legal, design, learning, etc): "));
|
|
28
|
+
const actors = draft?.actors ?? (auto ? "user, stakeholder" : await (0, prompt_1.ask)("Actors - comma separated: "));
|
|
29
|
+
let objective = draft?.objective ?? (auto ? "Initial requirement draft from user intent." : await (0, prompt_1.ask)("Objective: "));
|
|
30
|
+
let scopeIn = draft?.scope_in ?? (auto ? "core workflow" : await (0, prompt_1.ask)("Scope (in) - comma separated: "));
|
|
31
|
+
let scopeOut = draft?.scope_out ?? (auto ? "out-of-scope details to refine later" : await (0, prompt_1.ask)("Scope (out) - comma separated: "));
|
|
32
|
+
let acceptance = draft?.acceptance_criteria ??
|
|
33
|
+
(auto ? "A first working draft is generated and reviewable by stakeholders" : await (0, prompt_1.ask)("Acceptance criteria - comma separated: "));
|
|
34
|
+
let nfrSecurity = draft?.nfr_security ?? (auto ? "Apply baseline secure defaults" : await (0, prompt_1.ask)("NFR security: "));
|
|
35
|
+
let nfrPerformance = draft?.nfr_performance ?? (auto ? "Reasonable default performance budget" : await (0, prompt_1.ask)("NFR performance: "));
|
|
36
|
+
let nfrAvailability = draft?.nfr_availability ?? (auto ? "Service remains available during normal usage" : await (0, prompt_1.ask)("NFR availability: "));
|
|
37
|
+
const constraints = draft?.constraints ?? (auto ? "" : await (0, prompt_1.ask)("Constraints - comma separated: "));
|
|
38
|
+
const risks = draft?.risks ?? (auto ? "" : await (0, prompt_1.ask)("Risks - comma separated: "));
|
|
39
|
+
const links = draft?.links ?? (auto ? "" : await (0, prompt_1.ask)("Links - comma separated: "));
|
|
34
40
|
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
35
41
|
let project;
|
|
36
42
|
try {
|
|
@@ -38,7 +44,7 @@ async function runReqCreate(draft) {
|
|
|
38
44
|
}
|
|
39
45
|
catch (error) {
|
|
40
46
|
console.log(error.message);
|
|
41
|
-
return;
|
|
47
|
+
return null;
|
|
42
48
|
}
|
|
43
49
|
const metadata = (0, index_1.createProject)(workspace, project.name, domain || "software");
|
|
44
50
|
const reqId = generateId();
|
|
@@ -67,21 +73,40 @@ async function runReqCreate(draft) {
|
|
|
67
73
|
let gates = (0, gates_1.checkRequirementGates)(requirementJson);
|
|
68
74
|
if (!gates.ok) {
|
|
69
75
|
console.log("Requirement gates failed. Please provide missing fields:");
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
objective =
|
|
73
|
-
if (
|
|
74
|
-
scopeIn =
|
|
75
|
-
if (
|
|
76
|
-
scopeOut =
|
|
77
|
-
if (
|
|
78
|
-
acceptance =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
if (auto) {
|
|
77
|
+
if (gates.missing.includes("objective"))
|
|
78
|
+
objective = "Initial requirement draft from user intent.";
|
|
79
|
+
if (gates.missing.includes("scope.in"))
|
|
80
|
+
scopeIn = "core workflow";
|
|
81
|
+
if (gates.missing.includes("scope.out"))
|
|
82
|
+
scopeOut = "out-of-scope details to refine later";
|
|
83
|
+
if (gates.missing.includes("acceptanceCriteria")) {
|
|
84
|
+
acceptance = "A first working draft is generated and reviewable by stakeholders";
|
|
85
|
+
}
|
|
86
|
+
if (gates.missing.includes("nfrs.security"))
|
|
87
|
+
nfrSecurity = "Apply baseline secure defaults";
|
|
88
|
+
if (gates.missing.includes("nfrs.performance"))
|
|
89
|
+
nfrPerformance = "Reasonable default performance budget";
|
|
90
|
+
if (gates.missing.includes("nfrs.availability"))
|
|
91
|
+
nfrAvailability = "Service remains available during normal usage";
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
for (const field of gates.missing) {
|
|
95
|
+
if (field === "objective")
|
|
96
|
+
objective = await (0, prompt_1.ask)("Objective: ");
|
|
97
|
+
if (field === "scope.in")
|
|
98
|
+
scopeIn = await (0, prompt_1.ask)("Scope (in) - comma separated: ");
|
|
99
|
+
if (field === "scope.out")
|
|
100
|
+
scopeOut = await (0, prompt_1.ask)("Scope (out) - comma separated: ");
|
|
101
|
+
if (field === "acceptanceCriteria")
|
|
102
|
+
acceptance = await (0, prompt_1.ask)("Acceptance criteria - comma separated: ");
|
|
103
|
+
if (field === "nfrs.security")
|
|
104
|
+
nfrSecurity = await (0, prompt_1.ask)("NFR security: ");
|
|
105
|
+
if (field === "nfrs.performance")
|
|
106
|
+
nfrPerformance = await (0, prompt_1.ask)("NFR performance: ");
|
|
107
|
+
if (field === "nfrs.availability")
|
|
108
|
+
nfrAvailability = await (0, prompt_1.ask)("NFR availability: ");
|
|
109
|
+
}
|
|
85
110
|
}
|
|
86
111
|
requirementJson = {
|
|
87
112
|
...requirementJson,
|
|
@@ -102,14 +127,14 @@ async function runReqCreate(draft) {
|
|
|
102
127
|
if (!gates.ok) {
|
|
103
128
|
console.log("Requirement gates still failing. Missing:");
|
|
104
129
|
gates.missing.forEach((field) => console.log(`- ${field}`));
|
|
105
|
-
return;
|
|
130
|
+
return null;
|
|
106
131
|
}
|
|
107
132
|
}
|
|
108
133
|
const validation = (0, validate_1.validateJson)("requirement.schema.json", requirementJson);
|
|
109
134
|
if (!validation.valid) {
|
|
110
135
|
console.log("Requirement validation failed:");
|
|
111
136
|
validation.errors.forEach((error) => console.log(`- ${error}`));
|
|
112
|
-
return;
|
|
137
|
+
return null;
|
|
113
138
|
}
|
|
114
139
|
const requirementDir = path_1.default.join(project.root, "requirements", "backlog", reqId);
|
|
115
140
|
fs_1.default.mkdirSync(requirementDir, { recursive: true });
|
|
@@ -148,4 +173,5 @@ async function runReqCreate(draft) {
|
|
|
148
173
|
console.log(`Created requirement in ${requirementDir}`);
|
|
149
174
|
console.log(`Project metadata stored in ${path_1.default.join(project.root, "metadata.json")}`);
|
|
150
175
|
console.log(`Project status: ${metadata.status}`);
|
|
176
|
+
return { reqId, requirementDir, projectRoot: project.root };
|
|
151
177
|
}
|
|
@@ -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.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 (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 (
|
|
58
|
+
if (shouldUseQueuedAnswers()) {
|
|
47
59
|
const queue = getQueuedAnswers();
|
|
48
60
|
const answer = queue.shift() ?? "";
|
|
49
61
|
return Promise.resolve(answer.trim());
|
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");
|