sdd-cli 0.1.7 → 0.1.9
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/hello.js +128 -5
- package/dist/commands/req-create.d.ts +15 -1
- package/dist/commands/req-create.js +58 -32
- package/dist/commands/req-finish.d.ts +11 -1
- package/dist/commands/req-finish.js +16 -13
- package/dist/commands/req-plan.d.ts +11 -1
- package/dist/commands/req-plan.js +42 -36
- package/dist/commands/req-start.d.ts +11 -1
- package/dist/commands/req-start.js +21 -14
- package/dist/commands/test-plan.d.ts +11 -1
- package/dist/commands/test-plan.js +20 -13
- package/package.json +1 -1
package/dist/commands/hello.js
CHANGED
|
@@ -8,7 +8,67 @@ 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 req_plan_1 = require("./req-plan");
|
|
12
|
+
const req_start_1 = require("./req-start");
|
|
13
|
+
const req_finish_1 = require("./req-finish");
|
|
11
14
|
const route_1 = require("./route");
|
|
15
|
+
const test_plan_1 = require("./test-plan");
|
|
16
|
+
function buildAutopilotDraft(input, flow, domain) {
|
|
17
|
+
const cleanInput = input.trim();
|
|
18
|
+
const objective = cleanInput.length > 0 ? cleanInput : "Deliver a clear first requirement draft.";
|
|
19
|
+
const scopeByFlow = {
|
|
20
|
+
BUG_FIX: "Reproduce issue, isolate root cause, define fix",
|
|
21
|
+
PR_REVIEW: "Review feedback, plan responses, track actions",
|
|
22
|
+
SOFTWARE_FEATURE: "Core feature behavior and acceptance flow",
|
|
23
|
+
DATA_SCIENCE: "Dataset, modeling approach, and evaluation plan",
|
|
24
|
+
DESIGN: "Core design goals, accessibility, and deliverables",
|
|
25
|
+
HUMANITIES: "Research question, sources, and analytical lens",
|
|
26
|
+
BUSINESS: "Business objective, model assumptions, and constraints",
|
|
27
|
+
LEGAL: "Applicable legal constraints and compliance requirements",
|
|
28
|
+
LEARN: "Learning objective, structure, and practice outputs",
|
|
29
|
+
GENERIC: "Core user need and initial delivery scope"
|
|
30
|
+
};
|
|
31
|
+
const outByFlow = {
|
|
32
|
+
BUG_FIX: "Unrelated refactors not needed for this fix",
|
|
33
|
+
PR_REVIEW: "Changes outside current PR scope",
|
|
34
|
+
SOFTWARE_FEATURE: "Future enhancements after MVP",
|
|
35
|
+
DATA_SCIENCE: "Production hardening beyond first iteration",
|
|
36
|
+
DESIGN: "Full rebrand outside stated objective",
|
|
37
|
+
HUMANITIES: "Unrelated historical periods or disciplines",
|
|
38
|
+
BUSINESS: "Additional markets not in initial launch",
|
|
39
|
+
LEGAL: "Jurisdictions outside selected compliance scope",
|
|
40
|
+
LEARN: "Advanced topics outside current learning target",
|
|
41
|
+
GENERIC: "Additional ideas to evaluate in next iteration"
|
|
42
|
+
};
|
|
43
|
+
const actorByDomain = {
|
|
44
|
+
bug_fix: "developer, qa",
|
|
45
|
+
pr_review: "reviewer, contributor",
|
|
46
|
+
software: "end user, product owner, developer",
|
|
47
|
+
data_science: "analyst, data scientist, stakeholder",
|
|
48
|
+
design: "designer, end user, stakeholder",
|
|
49
|
+
humanities: "researcher, reader",
|
|
50
|
+
business: "customer, business owner, operator",
|
|
51
|
+
legal: "legal team, compliance owner",
|
|
52
|
+
learning: "learner, mentor",
|
|
53
|
+
generic: "user, stakeholder"
|
|
54
|
+
};
|
|
55
|
+
const safeFlow = scopeByFlow[flow] ? flow : "GENERIC";
|
|
56
|
+
const safeDomain = actorByDomain[domain] ? domain : "generic";
|
|
57
|
+
return {
|
|
58
|
+
domain: safeDomain === "generic" ? "software" : safeDomain,
|
|
59
|
+
actors: actorByDomain[safeDomain],
|
|
60
|
+
objective,
|
|
61
|
+
scope_in: scopeByFlow[safeFlow],
|
|
62
|
+
scope_out: outByFlow[safeFlow],
|
|
63
|
+
acceptance_criteria: "A baseline requirement is generated and ready for refinement with stakeholders",
|
|
64
|
+
nfr_security: "Follow secure defaults and data handling best practices",
|
|
65
|
+
nfr_performance: "Set practical baseline targets for first delivery",
|
|
66
|
+
nfr_availability: "Keep workflow usable and stable for normal usage",
|
|
67
|
+
constraints: "Timebox first iteration, keep implementation simple",
|
|
68
|
+
risks: "Ambiguity in requirements, underestimated scope",
|
|
69
|
+
links: ""
|
|
70
|
+
};
|
|
71
|
+
}
|
|
12
72
|
async function runHello(input, runQuestions) {
|
|
13
73
|
function loadWorkspace() {
|
|
14
74
|
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
@@ -67,6 +127,7 @@ async function runHello(input, runQuestions) {
|
|
|
67
127
|
}
|
|
68
128
|
const intent = (0, intent_1.classifyIntent)(text);
|
|
69
129
|
console.log(`Detected intent: ${intent.intent} -> ${intent.flow}`);
|
|
130
|
+
console.log("Step 1/3: Intent detected.");
|
|
70
131
|
const showRoute = await (0, prompt_1.confirm)("View route details now? (y/n) ");
|
|
71
132
|
if (showRoute) {
|
|
72
133
|
(0, route_1.runRoute)(text);
|
|
@@ -74,7 +135,8 @@ async function runHello(input, runQuestions) {
|
|
|
74
135
|
else {
|
|
75
136
|
console.log("Next: run `sdd-cli route <your input>` to view details.");
|
|
76
137
|
}
|
|
77
|
-
const shouldRunQuestions = runQuestions
|
|
138
|
+
const shouldRunQuestions = runQuestions === true;
|
|
139
|
+
console.log("Step 2/3: Requirement setup.");
|
|
78
140
|
if (shouldRunQuestions) {
|
|
79
141
|
const packs = (0, prompt_packs_1.loadPromptPacks)();
|
|
80
142
|
const packIds = intent_1.FLOW_PROMPT_PACKS[intent.flow] ?? [];
|
|
@@ -99,13 +161,74 @@ async function runHello(input, runQuestions) {
|
|
|
99
161
|
console.log(JSON.stringify(mapped, null, 2));
|
|
100
162
|
const ok = await (0, prompt_1.confirm)("Generate requirement draft now? (y/n) ");
|
|
101
163
|
if (ok) {
|
|
102
|
-
await (0, req_create_1.runReqCreate)(mapped);
|
|
164
|
+
const created = await (0, req_create_1.runReqCreate)(mapped, { autofill: true });
|
|
165
|
+
if (created) {
|
|
166
|
+
console.log(`Step 3/3: Draft created (${created.reqId}).`);
|
|
167
|
+
console.log("Next suggested command: sdd-cli req refine");
|
|
168
|
+
}
|
|
103
169
|
}
|
|
104
170
|
}
|
|
105
171
|
}
|
|
106
172
|
else {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
173
|
+
const activeProject = (0, flags_1.getFlags)().project || (await (0, prompt_1.askProjectName)());
|
|
174
|
+
if (!activeProject) {
|
|
175
|
+
console.log("Project name is required to run autopilot.");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
(0, flags_1.setFlags)({ project: activeProject });
|
|
179
|
+
const draft = buildAutopilotDraft(text, intent.flow, intent.domain);
|
|
180
|
+
draft.project_name = activeProject;
|
|
181
|
+
console.log("Step 3/7: Creating requirement draft automatically...");
|
|
182
|
+
const created = await (0, req_create_1.runReqCreate)(draft, { autofill: true });
|
|
183
|
+
if (!created) {
|
|
184
|
+
console.log("Autopilot stopped at requirement creation.");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
console.log(`Step 4/7: Planning requirement ${created.reqId}...`);
|
|
188
|
+
const planned = await (0, req_plan_1.runReqPlan)({
|
|
189
|
+
projectName: activeProject,
|
|
190
|
+
reqId: created.reqId,
|
|
191
|
+
autofill: true,
|
|
192
|
+
seedText: text
|
|
193
|
+
});
|
|
194
|
+
if (!planned) {
|
|
195
|
+
console.log("Autopilot stopped at planning.");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
console.log(`Step 5/7: Starting implementation plan for ${created.reqId}...`);
|
|
199
|
+
const started = await (0, req_start_1.runReqStart)({
|
|
200
|
+
projectName: activeProject,
|
|
201
|
+
reqId: created.reqId,
|
|
202
|
+
autofill: true,
|
|
203
|
+
seedText: text
|
|
204
|
+
});
|
|
205
|
+
if (!started) {
|
|
206
|
+
console.log("Autopilot stopped at start phase.");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
console.log(`Step 6/7: Updating test plan for ${created.reqId}...`);
|
|
210
|
+
const tested = await (0, test_plan_1.runTestPlan)({
|
|
211
|
+
projectName: activeProject,
|
|
212
|
+
reqId: created.reqId,
|
|
213
|
+
autofill: true,
|
|
214
|
+
seedText: text
|
|
215
|
+
});
|
|
216
|
+
if (!tested) {
|
|
217
|
+
console.log("Autopilot stopped at test planning.");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
console.log(`Step 7/7: Finalizing requirement ${created.reqId}...`);
|
|
221
|
+
const finished = await (0, req_finish_1.runReqFinish)({
|
|
222
|
+
projectName: activeProject,
|
|
223
|
+
reqId: created.reqId,
|
|
224
|
+
autofill: true,
|
|
225
|
+
seedText: text
|
|
226
|
+
});
|
|
227
|
+
if (!finished) {
|
|
228
|
+
console.log("Autopilot stopped at finish phase.");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
console.log(`Autopilot completed successfully for ${created.reqId}.`);
|
|
232
|
+
console.log(`Artifacts finalized at: ${finished.doneDir}`);
|
|
110
233
|
}
|
|
111
234
|
}
|
|
@@ -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
|
}
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
export declare function runReqFinish(): Promise<
|
|
1
|
+
export declare function runReqFinish(options?: ReqFinishOptions): Promise<ReqFinishResult | null>;
|
|
2
|
+
export type ReqFinishOptions = {
|
|
3
|
+
projectName?: string;
|
|
4
|
+
reqId?: string;
|
|
5
|
+
autofill?: boolean;
|
|
6
|
+
seedText?: string;
|
|
7
|
+
};
|
|
8
|
+
export type ReqFinishResult = {
|
|
9
|
+
reqId: string;
|
|
10
|
+
doneDir: string;
|
|
11
|
+
};
|
|
@@ -22,12 +22,13 @@ function findRequirementDir(projectRoot, reqId) {
|
|
|
22
22
|
return inProgress;
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
|
-
async function runReqFinish() {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
25
|
+
async function runReqFinish(options) {
|
|
26
|
+
const auto = Boolean(options?.autofill);
|
|
27
|
+
const projectName = options?.projectName ?? (await (0, prompt_1.askProjectName)());
|
|
28
|
+
const reqId = options?.reqId ?? (await (0, prompt_1.ask)("Requirement ID (REQ-...): "));
|
|
28
29
|
if (!projectName || !reqId) {
|
|
29
30
|
console.log("Project name and requirement ID are required.");
|
|
30
|
-
return;
|
|
31
|
+
return null;
|
|
31
32
|
}
|
|
32
33
|
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
33
34
|
let project;
|
|
@@ -36,12 +37,12 @@ async function runReqFinish() {
|
|
|
36
37
|
}
|
|
37
38
|
catch (error) {
|
|
38
39
|
console.log(error.message);
|
|
39
|
-
return;
|
|
40
|
+
return null;
|
|
40
41
|
}
|
|
41
42
|
const requirementDir = findRequirementDir(project.root, reqId);
|
|
42
43
|
if (!requirementDir) {
|
|
43
44
|
console.log("Requirement not found.");
|
|
44
|
-
return;
|
|
45
|
+
return null;
|
|
45
46
|
}
|
|
46
47
|
const jsonFiles = fs_1.default.readdirSync(requirementDir).filter((file) => file.endsWith(".json"));
|
|
47
48
|
const schemaMap = {
|
|
@@ -61,13 +62,14 @@ async function runReqFinish() {
|
|
|
61
62
|
if (!result.valid) {
|
|
62
63
|
console.log(`Validation failed for ${file}:`);
|
|
63
64
|
result.errors.forEach((error) => console.log(`- ${error}`));
|
|
64
|
-
return;
|
|
65
|
+
return null;
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
68
|
+
const seed = (options?.seedText ?? "").trim() || "initial scope";
|
|
69
|
+
const overview = auto ? `Project delivery for ${seed}` : await (0, prompt_1.ask)("Project overview (for README): ");
|
|
70
|
+
const howToRun = auto ? "Run CLI commands through sdd-cli flow." : await (0, prompt_1.ask)("How to run (for README): ");
|
|
71
|
+
const archSummary = auto ? "CLI + templates + schema validation architecture." : await (0, prompt_1.ask)("Architecture summary (for README): ");
|
|
72
|
+
const testingNotes = auto ? "Validated with unit and integration CLI tests." : await (0, prompt_1.ask)("Testing notes (for README): ");
|
|
71
73
|
const readmeTemplate = (0, render_1.loadTemplate)("project-readme");
|
|
72
74
|
const readmeRendered = (0, render_1.renderTemplate)(readmeTemplate, {
|
|
73
75
|
project_name: project.name,
|
|
@@ -97,7 +99,7 @@ async function runReqFinish() {
|
|
|
97
99
|
if (!readmeValidation.valid) {
|
|
98
100
|
console.log("Project README validation failed:");
|
|
99
101
|
readmeValidation.errors.forEach((error) => console.log(`- ${error}`));
|
|
100
|
-
return;
|
|
102
|
+
return null;
|
|
101
103
|
}
|
|
102
104
|
const sourceDir = requirementDir;
|
|
103
105
|
const sourceStatus = path_1.default.basename(path_1.default.dirname(sourceDir));
|
|
@@ -147,7 +149,8 @@ async function runReqFinish() {
|
|
|
147
149
|
(0, index_1.updateProjectStatus)(workspace, project.name, sourceStatus);
|
|
148
150
|
}
|
|
149
151
|
console.log(`Failed to finish requirement: ${error.message}`);
|
|
150
|
-
return;
|
|
152
|
+
return null;
|
|
151
153
|
}
|
|
152
154
|
console.log(`Moved requirement to ${doneDir}`);
|
|
155
|
+
return { reqId, doneDir };
|
|
153
156
|
}
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type ReqPlanOptions = {
|
|
2
|
+
projectName?: string;
|
|
3
|
+
reqId?: string;
|
|
4
|
+
autofill?: boolean;
|
|
5
|
+
seedText?: string;
|
|
6
|
+
};
|
|
7
|
+
export type ReqPlanResult = {
|
|
8
|
+
reqId: string;
|
|
9
|
+
targetDir: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function runReqPlan(options?: ReqPlanOptions): Promise<ReqPlanResult | null>;
|
|
@@ -22,12 +22,17 @@ function findRequirementDir(projectRoot, reqId) {
|
|
|
22
22
|
return wip;
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
25
|
+
function defaultSeed(seedText) {
|
|
26
|
+
const text = (seedText ?? "").trim();
|
|
27
|
+
return text.length > 0 ? text : "first delivery";
|
|
28
|
+
}
|
|
29
|
+
async function runReqPlan(options) {
|
|
30
|
+
const auto = Boolean(options?.autofill);
|
|
31
|
+
const projectName = options?.projectName ?? (await (0, prompt_1.askProjectName)());
|
|
32
|
+
const reqId = options?.reqId ?? (await (0, prompt_1.ask)("Requirement ID (REQ-...): "));
|
|
28
33
|
if (!projectName || !reqId) {
|
|
29
34
|
console.log("Project name and requirement ID are required.");
|
|
30
|
-
return;
|
|
35
|
+
return null;
|
|
31
36
|
}
|
|
32
37
|
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
33
38
|
let project;
|
|
@@ -36,17 +41,17 @@ async function runReqPlan() {
|
|
|
36
41
|
}
|
|
37
42
|
catch (error) {
|
|
38
43
|
console.log(error.message);
|
|
39
|
-
return;
|
|
44
|
+
return null;
|
|
40
45
|
}
|
|
41
46
|
let requirementDir = findRequirementDir(project.root, reqId);
|
|
42
47
|
if (!requirementDir) {
|
|
43
48
|
console.log("Requirement not found in backlog or wip.");
|
|
44
|
-
return;
|
|
49
|
+
return null;
|
|
45
50
|
}
|
|
46
51
|
const requirementJsonPath = path_1.default.join(requirementDir, "requirement.json");
|
|
47
52
|
if (!fs_1.default.existsSync(requirementJsonPath)) {
|
|
48
53
|
console.log("Missing requirement.json. Run `req create` first.");
|
|
49
|
-
return;
|
|
54
|
+
return null;
|
|
50
55
|
}
|
|
51
56
|
const requirementJson = JSON.parse(fs_1.default.readFileSync(requirementJsonPath, "utf-8"));
|
|
52
57
|
let gates = (0, gates_1.checkRequirementGates)(requirementJson);
|
|
@@ -54,13 +59,13 @@ async function runReqPlan() {
|
|
|
54
59
|
console.log("Requirement gates failed. Please update the requirement first:");
|
|
55
60
|
gates.missing.forEach((field) => console.log(`- ${field}`));
|
|
56
61
|
console.log("Run `sdd-cli req refine` to complete missing fields.");
|
|
57
|
-
return;
|
|
62
|
+
return null;
|
|
58
63
|
}
|
|
59
64
|
const requirementValidation = (0, validate_1.validateJson)("requirement.schema.json", requirementJson);
|
|
60
65
|
if (!requirementValidation.valid) {
|
|
61
66
|
console.log("Requirement validation failed:");
|
|
62
67
|
requirementValidation.errors.forEach((error) => console.log(`- ${error}`));
|
|
63
|
-
return;
|
|
68
|
+
return null;
|
|
64
69
|
}
|
|
65
70
|
const wipDir = path_1.default.join(project.root, "requirements", "wip", reqId);
|
|
66
71
|
if (requirementDir.includes(path_1.default.join("requirements", "backlog"))) {
|
|
@@ -75,32 +80,33 @@ async function runReqPlan() {
|
|
|
75
80
|
}
|
|
76
81
|
requirementJson.updatedAt = new Date().toISOString();
|
|
77
82
|
fs_1.default.writeFileSync(path_1.default.join(targetDir, "requirement.json"), JSON.stringify(requirementJson, null, 2), "utf-8");
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
83
|
+
const seed = defaultSeed(options?.seedText ?? requirementJson.objective);
|
|
84
|
+
const overview = auto ? `Functional overview for ${seed}` : await (0, prompt_1.ask)("Functional overview: ");
|
|
85
|
+
const actors = auto ? "user, system" : await (0, prompt_1.ask)("Actors - comma separated: ");
|
|
86
|
+
const useCases = auto ? `capture need for ${seed}, deliver first iteration` : await (0, prompt_1.ask)("Use cases - comma separated: ");
|
|
87
|
+
const flows = auto ? "discover, plan, implement, validate" : await (0, prompt_1.ask)("Flows - comma separated: ");
|
|
88
|
+
const rules = auto ? "maintain traceability, validate artifacts" : await (0, prompt_1.ask)("Business rules - comma separated: ");
|
|
89
|
+
const errors = auto ? "invalid input, missing artifact" : await (0, prompt_1.ask)("Errors - comma separated: ");
|
|
90
|
+
const acceptance = auto ? "artifacts generated, schemas valid" : await (0, prompt_1.ask)("Acceptance criteria - comma separated: ");
|
|
91
|
+
const stack = auto ? "node, typescript" : await (0, prompt_1.ask)("Tech stack - comma separated: ");
|
|
92
|
+
const interfaces = auto ? "cli commands, markdown artifacts" : await (0, prompt_1.ask)("Interfaces - comma separated: ");
|
|
93
|
+
const dataModel = auto ? "requirement json, spec json" : await (0, prompt_1.ask)("Data model - comma separated: ");
|
|
94
|
+
const security = auto ? "safe defaults, input validation" : await (0, prompt_1.ask)("Security - comma separated: ");
|
|
95
|
+
const techErrors = auto ? "clear cli errors, retry guidance" : await (0, prompt_1.ask)("Error handling - comma separated: ");
|
|
96
|
+
const performance = auto ? "fast local generation" : await (0, prompt_1.ask)("Performance - comma separated: ");
|
|
97
|
+
const observability = auto ? "progress logs, changelog entries" : await (0, prompt_1.ask)("Observability - comma separated: ");
|
|
98
|
+
const context = auto ? "CLI orchestrator with schema validation" : await (0, prompt_1.ask)("Architecture context: ");
|
|
99
|
+
const containers = auto ? "cli runtime, workspace files" : await (0, prompt_1.ask)("Containers - comma separated: ");
|
|
100
|
+
const components = auto ? "router, generators, validators" : await (0, prompt_1.ask)("Components - comma separated: ");
|
|
101
|
+
const deployment = auto ? "local workstation" : await (0, prompt_1.ask)("Deployment - comma separated: ");
|
|
102
|
+
const diagrams = auto ? "context.mmd, container.mmd" : await (0, prompt_1.ask)("Diagrams - comma separated: ");
|
|
103
|
+
const criticalPaths = auto ? "hello to requirement, requirement to plan" : await (0, prompt_1.ask)("Test critical paths - comma separated: ");
|
|
104
|
+
const edgeCases = auto ? "missing fields, invalid schema" : await (0, prompt_1.ask)("Test edge cases - comma separated: ");
|
|
105
|
+
const acceptanceTests = auto ? "generate and validate complete bundle" : await (0, prompt_1.ask)("Acceptance tests - comma separated: ");
|
|
106
|
+
const regressions = auto ? "flags behavior, template loading" : await (0, prompt_1.ask)("Regression tests - comma separated: ");
|
|
107
|
+
const coverageTarget = auto ? "80%" : await (0, prompt_1.ask)("Coverage target: ");
|
|
102
108
|
const flags = (0, flags_1.getFlags)();
|
|
103
|
-
const improveNote = flags.improve ? await (0, prompt_1.ask)("Improve focus (optional): ") : "";
|
|
109
|
+
const improveNote = flags.improve && !auto ? await (0, prompt_1.ask)("Improve focus (optional): ") : "";
|
|
104
110
|
const functionalJson = {
|
|
105
111
|
overview: overview || "N/A",
|
|
106
112
|
actors: (0, list_1.parseList)(actors),
|
|
@@ -143,9 +149,8 @@ async function runReqPlan() {
|
|
|
143
149
|
if (failures.length > 0) {
|
|
144
150
|
console.log("Spec validation failed:");
|
|
145
151
|
failures.forEach((error) => console.log(`- ${error}`));
|
|
146
|
-
return;
|
|
152
|
+
return null;
|
|
147
153
|
}
|
|
148
|
-
console.log("Spec validation passed.");
|
|
149
154
|
const functionalTemplate = (0, render_1.loadTemplate)("functional-spec");
|
|
150
155
|
const technicalTemplate = (0, render_1.loadTemplate)("technical-spec");
|
|
151
156
|
const architectureTemplate = (0, render_1.loadTemplate)("architecture");
|
|
@@ -219,4 +224,5 @@ async function runReqPlan() {
|
|
|
219
224
|
const changeEntry = `\n- ${new Date().toISOString()} planned requirement ${reqId}\n`;
|
|
220
225
|
fs_1.default.appendFileSync(changelog, changeEntry, "utf-8");
|
|
221
226
|
console.log(`Generated specs in ${targetDir}`);
|
|
227
|
+
return { reqId, targetDir };
|
|
222
228
|
}
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type ReqStartOptions = {
|
|
2
|
+
projectName?: string;
|
|
3
|
+
reqId?: string;
|
|
4
|
+
autofill?: boolean;
|
|
5
|
+
seedText?: string;
|
|
6
|
+
};
|
|
7
|
+
export type ReqStartResult = {
|
|
8
|
+
reqId: string;
|
|
9
|
+
targetDir: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function runReqStart(options?: ReqStartOptions): Promise<ReqStartResult | null>;
|
|
@@ -24,12 +24,17 @@ function findRequirementDir(projectRoot, reqId) {
|
|
|
24
24
|
return inProgress;
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
27
|
+
function defaultSeed(seedText) {
|
|
28
|
+
const text = (seedText ?? "").trim();
|
|
29
|
+
return text.length > 0 ? text : "initial delivery";
|
|
30
|
+
}
|
|
31
|
+
async function runReqStart(options) {
|
|
32
|
+
const auto = Boolean(options?.autofill);
|
|
33
|
+
const projectName = options?.projectName ?? (await (0, prompt_1.askProjectName)());
|
|
34
|
+
const reqId = options?.reqId ?? (await (0, prompt_1.ask)("Requirement ID (REQ-...): "));
|
|
30
35
|
if (!projectName || !reqId) {
|
|
31
36
|
console.log("Project name and requirement ID are required.");
|
|
32
|
-
return;
|
|
37
|
+
return null;
|
|
33
38
|
}
|
|
34
39
|
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
35
40
|
let project;
|
|
@@ -38,12 +43,12 @@ async function runReqStart() {
|
|
|
38
43
|
}
|
|
39
44
|
catch (error) {
|
|
40
45
|
console.log(error.message);
|
|
41
|
-
return;
|
|
46
|
+
return null;
|
|
42
47
|
}
|
|
43
48
|
let requirementDir = findRequirementDir(project.root, reqId);
|
|
44
49
|
if (!requirementDir) {
|
|
45
50
|
console.log("Requirement not found.");
|
|
46
|
-
return;
|
|
51
|
+
return null;
|
|
47
52
|
}
|
|
48
53
|
const requirementPath = requirementDir;
|
|
49
54
|
const requiredSpecs = [
|
|
@@ -56,7 +61,7 @@ async function runReqStart() {
|
|
|
56
61
|
if (missing.length > 0) {
|
|
57
62
|
console.log("Cannot start. Missing specs:");
|
|
58
63
|
missing.forEach((spec) => console.log(`- ${spec.file}`));
|
|
59
|
-
return;
|
|
64
|
+
return null;
|
|
60
65
|
}
|
|
61
66
|
for (const spec of requiredSpecs) {
|
|
62
67
|
const data = JSON.parse(fs_1.default.readFileSync(path_1.default.join(requirementPath, spec.file), "utf-8"));
|
|
@@ -64,7 +69,7 @@ async function runReqStart() {
|
|
|
64
69
|
if (!result.valid) {
|
|
65
70
|
console.log(`Spec validation failed for ${spec.file}:`);
|
|
66
71
|
result.errors.forEach((error) => console.log(`- ${error}`));
|
|
67
|
-
return;
|
|
72
|
+
return null;
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
const inProgressDir = path_1.default.join(project.root, "requirements", "in-progress", reqId);
|
|
@@ -82,12 +87,13 @@ async function runReqStart() {
|
|
|
82
87
|
requirementJson.updatedAt = new Date().toISOString();
|
|
83
88
|
fs_1.default.writeFileSync(requirementJsonPath, JSON.stringify(requirementJson, null, 2), "utf-8");
|
|
84
89
|
}
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
90
|
+
const seed = defaultSeed(options?.seedText);
|
|
91
|
+
const milestones = auto ? `milestone for ${seed}` : await (0, prompt_1.ask)("Milestones - comma separated: ");
|
|
92
|
+
const tasks = auto ? "implement core flow, validate outputs" : await (0, prompt_1.ask)("Tasks - comma separated: ");
|
|
93
|
+
const dependencies = auto ? "node runtime, templates, schemas" : await (0, prompt_1.ask)("Dependencies - comma separated: ");
|
|
94
|
+
const risks = auto ? "scope drift, missing validation" : await (0, prompt_1.ask)("Risks - comma separated: ");
|
|
89
95
|
const flags = (0, flags_1.getFlags)();
|
|
90
|
-
const improveNote = flags.improve ? await (0, prompt_1.ask)("Improve focus (optional): ") : "";
|
|
96
|
+
const improveNote = flags.improve && !auto ? await (0, prompt_1.ask)("Improve focus (optional): ") : "";
|
|
91
97
|
const implementationTemplate = (0, render_1.loadTemplate)("implementation-plan");
|
|
92
98
|
const rendered = (0, render_1.renderTemplate)(implementationTemplate, {
|
|
93
99
|
title: project.name,
|
|
@@ -110,7 +116,7 @@ async function runReqStart() {
|
|
|
110
116
|
if (!validation.valid) {
|
|
111
117
|
console.log("Quality validation failed:");
|
|
112
118
|
validation.errors.forEach((error) => console.log(`- ${error}`));
|
|
113
|
-
return;
|
|
119
|
+
return null;
|
|
114
120
|
}
|
|
115
121
|
fs_1.default.writeFileSync(path_1.default.join(targetDir, "implementation-plan.md"), rendered, "utf-8");
|
|
116
122
|
fs_1.default.writeFileSync(path_1.default.join(targetDir, "quality.json"), JSON.stringify(qualityJson, null, 2), "utf-8");
|
|
@@ -145,4 +151,5 @@ async function runReqStart() {
|
|
|
145
151
|
fs_1.default.appendFileSync(changelog, changeEntry, "utf-8");
|
|
146
152
|
console.log(`Implementation plan generated in ${targetDir}`);
|
|
147
153
|
console.log(`Status updated to in-progress for ${project.name}`);
|
|
154
|
+
return { reqId, targetDir };
|
|
148
155
|
}
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type TestPlanOptions = {
|
|
2
|
+
projectName?: string;
|
|
3
|
+
reqId?: string;
|
|
4
|
+
autofill?: boolean;
|
|
5
|
+
seedText?: string;
|
|
6
|
+
};
|
|
7
|
+
export type TestPlanResult = {
|
|
8
|
+
reqId: string;
|
|
9
|
+
requirementDir: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function runTestPlan(options?: TestPlanOptions): Promise<TestPlanResult | null>;
|
|
@@ -23,12 +23,17 @@ function findRequirementDir(projectRoot, reqId) {
|
|
|
23
23
|
}
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
26
|
+
function defaultSeed(seedText) {
|
|
27
|
+
const text = (seedText ?? "").trim();
|
|
28
|
+
return text.length > 0 ? text : "initial scope";
|
|
29
|
+
}
|
|
30
|
+
async function runTestPlan(options) {
|
|
31
|
+
const auto = Boolean(options?.autofill);
|
|
32
|
+
const projectName = options?.projectName ?? (await (0, prompt_1.askProjectName)());
|
|
33
|
+
const reqId = options?.reqId ?? (await (0, prompt_1.ask)("Requirement ID (REQ-...): "));
|
|
29
34
|
if (!projectName || !reqId) {
|
|
30
35
|
console.log("Project name and requirement ID are required.");
|
|
31
|
-
return;
|
|
36
|
+
return null;
|
|
32
37
|
}
|
|
33
38
|
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
34
39
|
let project;
|
|
@@ -37,20 +42,21 @@ async function runTestPlan() {
|
|
|
37
42
|
}
|
|
38
43
|
catch (error) {
|
|
39
44
|
console.log(error.message);
|
|
40
|
-
return;
|
|
45
|
+
return null;
|
|
41
46
|
}
|
|
42
47
|
const requirementDir = findRequirementDir(project.root, reqId);
|
|
43
48
|
if (!requirementDir) {
|
|
44
49
|
console.log("Requirement not found.");
|
|
45
|
-
return;
|
|
50
|
+
return null;
|
|
46
51
|
}
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
+
const seed = defaultSeed(options?.seedText);
|
|
53
|
+
const criticalPaths = auto ? `core path for ${seed}` : await (0, prompt_1.ask)("Test critical paths - comma separated: ");
|
|
54
|
+
const edgeCases = auto ? "invalid data, missing inputs" : await (0, prompt_1.ask)("Test edge cases - comma separated: ");
|
|
55
|
+
const acceptanceTests = auto ? "happy path end-to-end generation" : await (0, prompt_1.ask)("Acceptance tests - comma separated: ");
|
|
56
|
+
const regressions = auto ? "existing command behavior remains valid" : await (0, prompt_1.ask)("Regression tests - comma separated: ");
|
|
57
|
+
const coverageTarget = auto ? "80%" : await (0, prompt_1.ask)("Coverage target: ");
|
|
52
58
|
const flags = (0, flags_1.getFlags)();
|
|
53
|
-
const improveNote = flags.improve ? await (0, prompt_1.ask)("Improve focus (optional): ") : "";
|
|
59
|
+
const improveNote = flags.improve && !auto ? await (0, prompt_1.ask)("Improve focus (optional): ") : "";
|
|
54
60
|
const testPlanJson = {
|
|
55
61
|
criticalPaths: (0, list_1.parseList)(criticalPaths),
|
|
56
62
|
edgeCases: (0, list_1.parseList)(edgeCases),
|
|
@@ -62,7 +68,7 @@ async function runTestPlan() {
|
|
|
62
68
|
if (!validation.valid) {
|
|
63
69
|
console.log("Test plan validation failed:");
|
|
64
70
|
validation.errors.forEach((error) => console.log(`- ${error}`));
|
|
65
|
-
return;
|
|
71
|
+
return null;
|
|
66
72
|
}
|
|
67
73
|
const template = (0, render_1.loadTemplate)("test-plan");
|
|
68
74
|
const rendered = (0, render_1.renderTemplate)(template, {
|
|
@@ -86,4 +92,5 @@ async function runTestPlan() {
|
|
|
86
92
|
fs_1.default.appendFileSync(progressLog, improveEntry, "utf-8");
|
|
87
93
|
}
|
|
88
94
|
console.log(`Test plan updated in ${requirementDir}`);
|
|
95
|
+
return { reqId, requirementDir };
|
|
89
96
|
}
|