sdtk-design-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { parseFlags } = require("../lib/args");
6
+ const { ValidationError } = require("../lib/errors");
7
+ const {
8
+ assertProjectLocalPath,
9
+ describeDesignPaths,
10
+ resolveProjectPath,
11
+ } = require("../lib/design-paths");
12
+
13
+ const INIT_FLAG_DEFS = {
14
+ help: { type: "boolean" },
15
+ "project-path": { type: "string" },
16
+ force: { type: "boolean" },
17
+ "no-state": { type: "boolean" },
18
+ };
19
+
20
+ function toPosix(value) {
21
+ return String(value || "").replace(/\\/g, "/");
22
+ }
23
+
24
+ function cmdInitHelp() {
25
+ console.log(`SDTK-DESIGN Init
26
+
27
+ Usage:
28
+ sdtk-design init [--project-path <path>] [--force] [--no-state]
29
+
30
+ Example:
31
+ sdtk-design init
32
+
33
+ Creates:
34
+ docs/design/
35
+ docs/design/wireframes/
36
+ docs/design/reviews/
37
+ docs/design/README.md
38
+ .sdtk/design/manifest.json unless --no-state is used
39
+
40
+ Safety:
41
+ Local files only.
42
+ Existing managed files are skipped unless --force is explicit.
43
+ No .sdtk/atlas creation or mutation.
44
+ No SDTK-WIKI output mutation.
45
+ No network call, Pro entitlement, or production app code generation.`);
46
+ return 0;
47
+ }
48
+
49
+ function designReadmeContent() {
50
+ return [
51
+ "# SDTK-DESIGN Workspace",
52
+ "",
53
+ "This folder contains human-facing design artifacts for the local MVP workflow.",
54
+ "",
55
+ "## Expected Phase A Artifacts",
56
+ "",
57
+ "- `DESIGN_BRIEF.md`: target user, pain point, MVP promise, CTA, non-goals, success metric, assumptions, and open questions.",
58
+ "- `SCREEN_MAP.md`: 3 to 6 MVP screens with purpose, primary action, required data, and state handling.",
59
+ "- `wireframes/`: low-fidelity screen specs such as `LANDING.md`, `ONBOARDING.md`, and `DASHBOARD.md`.",
60
+ "- `DESIGN_SYSTEM.md`: typography, colors, spacing, components, tone, and accessibility baseline.",
61
+ "- `DESIGN_HANDOFF.md`: deterministic SDTK-CODE handoff with readiness verdict.",
62
+ "",
63
+ "## Recommended Next Commands",
64
+ "",
65
+ "```powershell",
66
+ "sdtk-design brief --idea \"<your MVP idea>\"",
67
+ "sdtk-design screens",
68
+ "sdtk-design wireframe --screen landing",
69
+ "sdtk-design system",
70
+ "sdtk-design handoff",
71
+ "```",
72
+ "",
73
+ "## Boundary",
74
+ "",
75
+ "SDTK-DESIGN stores human-facing artifacts under `docs/design`. Internal design state may live under `.sdtk/design`. It must not create or mutate `.sdtk/atlas`.",
76
+ "",
77
+ ].join("\n");
78
+ }
79
+
80
+ function manifestContent(projectPath) {
81
+ return `${JSON.stringify({
82
+ schema_version: 1,
83
+ product: "SDTK-DESIGN",
84
+ state: "initialized",
85
+ created_at: new Date().toISOString(),
86
+ project_path: projectPath,
87
+ docs_path: "docs/design",
88
+ managed_by: "sdtk-design",
89
+ }, null, 2)}\n`;
90
+ }
91
+
92
+ function ensureDirectory(dirPath, projectPath, result) {
93
+ assertProjectLocalPath(dirPath, projectPath, "directory");
94
+ if (fs.existsSync(dirPath)) {
95
+ if (!fs.statSync(dirPath).isDirectory()) {
96
+ throw new ValidationError(`Expected directory but found a file: ${dirPath}. No project files were changed.`);
97
+ }
98
+ result.skipped.push(toPosix(path.relative(projectPath, dirPath)) + "/");
99
+ return;
100
+ }
101
+ fs.mkdirSync(dirPath, { recursive: true });
102
+ result.created.push(toPosix(path.relative(projectPath, dirPath)) + "/");
103
+ }
104
+
105
+ function writeManagedFile(filePath, content, projectPath, force, result) {
106
+ assertProjectLocalPath(filePath, projectPath, "managed file");
107
+ if (fs.existsSync(filePath) && !force) {
108
+ result.skipped.push(toPosix(path.relative(projectPath, filePath)));
109
+ return;
110
+ }
111
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
112
+ fs.writeFileSync(filePath, content, "utf-8");
113
+ result.created.push(toPosix(path.relative(projectPath, filePath)));
114
+ }
115
+
116
+ function runDesignInit({ projectPath, force = false, state = true }) {
117
+ const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
118
+ if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
119
+ throw new ValidationError(`--project-path is not a valid directory: ${resolvedProjectPath}. No project files were changed.`);
120
+ }
121
+
122
+ const paths = describeDesignPaths(resolvedProjectPath);
123
+ const result = {
124
+ projectPath: resolvedProjectPath,
125
+ created: [],
126
+ skipped: [],
127
+ stateEnabled: Boolean(state),
128
+ };
129
+
130
+ ensureDirectory(paths.designDocsPath, resolvedProjectPath, result);
131
+ ensureDirectory(paths.wireframesPath, resolvedProjectPath, result);
132
+ ensureDirectory(paths.reviewsPath, resolvedProjectPath, result);
133
+ writeManagedFile(paths.designReadmePath, designReadmeContent(), resolvedProjectPath, force, result);
134
+
135
+ if (state) {
136
+ ensureDirectory(paths.designStatePath, resolvedProjectPath, result);
137
+ writeManagedFile(paths.manifestPath, manifestContent(resolvedProjectPath), resolvedProjectPath, force, result);
138
+ }
139
+
140
+ return result;
141
+ }
142
+
143
+ function cmdInit(args) {
144
+ const { flags } = parseFlags(args || [], INIT_FLAG_DEFS);
145
+ if (flags.help) return cmdInitHelp();
146
+
147
+ const result = runDesignInit({
148
+ projectPath: flags["project-path"],
149
+ force: Boolean(flags.force),
150
+ state: !flags["no-state"],
151
+ });
152
+
153
+ console.log(`[design] Initialized SDTK-DESIGN workspace: ${result.projectPath}`);
154
+ console.log(`[design] Created: ${result.created.length}`);
155
+ console.log(`[design] Skipped: ${result.skipped.length}`);
156
+ console.log(`[design] State: ${result.stateEnabled ? ".sdtk/design enabled" : "disabled by --no-state"}`);
157
+ console.log("[design] No .sdtk/atlas, SDTK-WIKI output, source files, network, or app code was modified.");
158
+ console.log('[design] Next: sdtk-design brief --idea "<your MVP idea>"');
159
+ return 0;
160
+ }
161
+
162
+ module.exports = {
163
+ cmdInit,
164
+ cmdInitHelp,
165
+ runDesignInit,
166
+ };
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { parseFlags } = require("../lib/args");
6
+ const { describeDesignPaths, isPathInsideOrEqual, resolveProjectPath } = require("../lib/design-paths");
7
+ const { ValidationError } = require("../lib/errors");
8
+
9
+ const REVIEW_FLAG_DEFS = {
10
+ help: { type: "boolean" },
11
+ artifact: { type: "string" },
12
+ "project-path": { type: "string" },
13
+ force: { type: "boolean" },
14
+ };
15
+
16
+ function cmdReviewHelp() {
17
+ console.log(`SDTK-DESIGN Review
18
+
19
+ Usage:
20
+ sdtk-design review --artifact docs/design/wireframes/LANDING.md [--project-path <path>] [--force]
21
+
22
+ Example:
23
+ sdtk-design review --artifact docs/design/wireframes/LANDING.md
24
+
25
+ Reads:
26
+ A project-local markdown design artifact.
27
+
28
+ Creates:
29
+ docs/design/reviews/DESIGN_REVIEW_YYYYMMDD.md
30
+
31
+ Safety:
32
+ Local markdown artifact review only.
33
+ Existing same-day review report is not overwritten unless --force is explicit.
34
+ No URL, browser, screenshot, vision, or DOM review in BK-161.
35
+ No .sdtk/atlas creation or mutation.
36
+ No SDTK-WIKI output mutation.
37
+ No network call, Pro entitlement, or production app code generation.`);
38
+ return 0;
39
+ }
40
+
41
+ function formatDateYYYYMMDD(date = new Date()) {
42
+ return date.toISOString().slice(0, 10).replace(/-/g, "");
43
+ }
44
+
45
+ function toPosix(value) {
46
+ return String(value || "").replace(/\\/g, "/");
47
+ }
48
+
49
+ function resolveArtifactPath(artifact, projectPath) {
50
+ const raw = String(artifact || "").trim();
51
+ if (!raw) {
52
+ throw new ValidationError('Missing required --artifact "<path>". No project files were changed.');
53
+ }
54
+ const resolved = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(projectPath, raw);
55
+ if (!isPathInsideOrEqual(resolved, projectPath)) {
56
+ throw new ValidationError(`Refusing to review artifact outside project root: ${resolved}. No project files were changed.`);
57
+ }
58
+ return resolved;
59
+ }
60
+
61
+ function hasHeading(content, heading) {
62
+ return content.toLowerCase().includes(heading.toLowerCase());
63
+ }
64
+
65
+ function reviewContent({ artifactRelativePath, artifactContent }) {
66
+ const hasPrimaryCta = hasHeading(artifactContent, "Primary CTA") || artifactContent.toLowerCase().includes("cta");
67
+ const hasMobile = hasHeading(artifactContent, "Mobile Notes") || artifactContent.toLowerCase().includes("mobile");
68
+ const hasStates = hasHeading(artifactContent, "State Handling") || ["empty", "success", "error"].every((term) => artifactContent.toLowerCase().includes(term));
69
+ const hasAcceptance = hasHeading(artifactContent, "Acceptance Criteria");
70
+
71
+ return [
72
+ "# Design Review",
73
+ "",
74
+ "## Reviewed Artifact",
75
+ "",
76
+ `- Artifact: \`${artifactRelativePath}\``,
77
+ "- Review mode: local markdown artifact review",
78
+ "- No URL, browser, screenshot, vision, or DOM review was used.",
79
+ "",
80
+ "## Findings By Severity",
81
+ "",
82
+ "| Severity | Finding | Recommended action |",
83
+ "|---|---|---|",
84
+ `| High | ${hasPrimaryCta ? "No high-severity CTA blocker found in the artifact." : "Primary CTA is missing or not explicit."} | ${hasPrimaryCta ? "Keep the CTA visually dominant during implementation." : "Add one explicit primary CTA before handoff."} |`,
85
+ `| Medium | ${hasMobile ? "Mobile notes are present." : "Mobile behavior is not documented."} | ${hasMobile ? "Validate mobile layout during implementation." : "Add mobile layout notes for narrow screens."} |`,
86
+ `| Medium | ${hasStates ? "State handling is represented." : "Empty, success, or error states are incomplete."} | ${hasStates ? "Carry the states into implementation acceptance criteria." : "Document empty, success, and error handling."} |`,
87
+ `| Low | ${hasAcceptance ? "Acceptance criteria are present." : "Acceptance criteria are missing."} | ${hasAcceptance ? "Use criteria as SDTK-CODE test obligations." : "Add concrete acceptance criteria before coding."} |`,
88
+ "",
89
+ "## UX Issues",
90
+ "",
91
+ "- Keep the screen focused on one job and one dominant next action.",
92
+ "- Avoid adding enterprise configuration or secondary flows before the MVP action works.",
93
+ "- Ensure empty states teach the user what to do next instead of only saying no data exists.",
94
+ "",
95
+ "## CTA / Conversion Notes",
96
+ "",
97
+ "- The Primary CTA should be visible in the first viewport and repeated only where it helps the user finish the workflow.",
98
+ "- CTA copy should use a concrete verb, such as Add, Continue, Save, or Review.",
99
+ "- Supporting copy should reduce uncertainty before asking for setup or data entry.",
100
+ "",
101
+ "## Mobile / Accessibility Notes",
102
+ "",
103
+ "- Mobile layout should stay single-column with no horizontal scrolling.",
104
+ "- Interactive targets should be at least 44px by 44px where practical.",
105
+ "- Do not rely on color alone for status; pair it with visible text.",
106
+ "- Preserve keyboard focus and accessible names for icon-only controls.",
107
+ "",
108
+ "## Actionable Fixes",
109
+ "",
110
+ "- Confirm the screen has one visually dominant primary CTA.",
111
+ "- Confirm empty, success, and error states are visible in the artifact or downstream implementation plan.",
112
+ "- Confirm mobile notes are present before SDTK-CODE implementation starts.",
113
+ "- Confirm the artifact stays within SDTK-DESIGN scope and does not imply generated production app code.",
114
+ "",
115
+ "## Boundaries",
116
+ "",
117
+ "- No Figma, Lovable, v0, or full app-builder replacement behavior is claimed.",
118
+ "- No network call, Pro entitlement, `.sdtk/atlas`, or SDTK-WIKI output is required.",
119
+ "",
120
+ ].join("\n");
121
+ }
122
+
123
+ function runDesignReview({ artifact, projectPath, force = false }) {
124
+ const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
125
+ if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
126
+ throw new ValidationError(`--project-path is not a valid directory: ${resolvedProjectPath}. No project files were changed.`);
127
+ }
128
+
129
+ const artifactPath = resolveArtifactPath(artifact, resolvedProjectPath);
130
+ if (!fs.existsSync(artifactPath) || !fs.statSync(artifactPath).isFile()) {
131
+ throw new ValidationError(`--artifact is not a readable file: ${artifactPath}. No project files were changed.`);
132
+ }
133
+
134
+ const paths = describeDesignPaths(resolvedProjectPath);
135
+ const reportName = `DESIGN_REVIEW_${formatDateYYYYMMDD()}.md`;
136
+ const reportPath = path.join(paths.reviewsPath, reportName);
137
+ if (fs.existsSync(reportPath) && !force) {
138
+ throw new ValidationError(`docs/design/reviews/${reportName} already exists. Re-run with --force to replace this managed review report.`);
139
+ }
140
+
141
+ const artifactContent = fs.readFileSync(artifactPath, "utf-8");
142
+ const artifactRelativePath = toPosix(path.relative(resolvedProjectPath, artifactPath));
143
+ fs.mkdirSync(paths.reviewsPath, { recursive: true });
144
+ fs.writeFileSync(reportPath, reviewContent({ artifactRelativePath, artifactContent }), "utf-8");
145
+
146
+ return {
147
+ projectPath: resolvedProjectPath,
148
+ relativeReportPath: `docs/design/reviews/${reportName}`,
149
+ forced: Boolean(force),
150
+ };
151
+ }
152
+
153
+ function cmdReview(args) {
154
+ const { flags } = parseFlags(args || [], REVIEW_FLAG_DEFS);
155
+ if (flags.help) return cmdReviewHelp();
156
+
157
+ const result = runDesignReview({
158
+ artifact: flags.artifact,
159
+ projectPath: flags["project-path"],
160
+ force: Boolean(flags.force),
161
+ });
162
+
163
+ console.log(`[design] Wrote ${result.relativeReportPath}: ${result.projectPath}`);
164
+ console.log(`[design] Review mode: local artifact`);
165
+ console.log(`[design] Overwrite: ${result.forced ? "enabled by --force" : "not needed"}`);
166
+ console.log("[design] No URL, browser, screenshot, vision, network, .sdtk/atlas, or SDTK-WIKI output was used.");
167
+ return 0;
168
+ }
169
+
170
+ module.exports = {
171
+ cmdReview,
172
+ cmdReviewHelp,
173
+ formatDateYYYYMMDD,
174
+ reviewContent,
175
+ runDesignReview,
176
+ };
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { parseFlags } = require("../lib/args");
6
+ const { describeDesignPaths, resolveProjectPath } = require("../lib/design-paths");
7
+ const { ValidationError } = require("../lib/errors");
8
+
9
+ const SCREENS_FLAG_DEFS = {
10
+ help: { type: "boolean" },
11
+ "project-path": { type: "string" },
12
+ force: { type: "boolean" },
13
+ };
14
+
15
+ function cmdScreensHelp() {
16
+ console.log(`SDTK-DESIGN Screens
17
+
18
+ Usage:
19
+ sdtk-design screens [--project-path <path>] [--force]
20
+
21
+ Example:
22
+ sdtk-design screens
23
+
24
+ Reads:
25
+ docs/design/DESIGN_BRIEF.md
26
+
27
+ Creates:
28
+ docs/design/SCREEN_MAP.md
29
+
30
+ Safety:
31
+ Local files only.
32
+ Existing SCREEN_MAP.md is not overwritten unless --force is explicit.
33
+ No .sdtk/atlas creation or mutation.
34
+ No SDTK-WIKI output mutation.
35
+ No network call, Pro entitlement, or production app code generation.`);
36
+ return 0;
37
+ }
38
+
39
+ function includesAny(text, terms) {
40
+ const value = text.toLowerCase();
41
+ return terms.some((term) => value.includes(term));
42
+ }
43
+
44
+ function extractBriefLine(briefContent, heading) {
45
+ const lines = briefContent.split(/\r?\n/);
46
+ const headingIndex = lines.findIndex((line) => line.trim().toLowerCase() === heading.toLowerCase());
47
+ if (headingIndex < 0) return "";
48
+ for (let index = headingIndex + 1; index < lines.length; index += 1) {
49
+ const line = lines[index].trim();
50
+ if (line.startsWith("## ")) return "";
51
+ if (line.startsWith("- ")) return line.slice(2).trim();
52
+ if (line) return line;
53
+ }
54
+ return "";
55
+ }
56
+
57
+ function inferScreenContext(briefContent) {
58
+ const isCrm = includesAny(briefContent, ["crm", "lead", "follow-up", "pipeline"]);
59
+ if (isCrm) {
60
+ return {
61
+ productLabel: "lightweight CRM",
62
+ targetUser: extractBriefLine(briefContent, "## Target User") || "Solo consultants.",
63
+ promise: extractBriefLine(briefContent, "## MVP Promise") || "A lightweight CRM for lead tracking, follow-ups, notes, and simple pipeline visibility.",
64
+ primaryRecord: "lead",
65
+ primaryAction: "Add first lead",
66
+ dashboardObject: "active leads, follow-ups, and simple pipeline status",
67
+ emptyObject: "leads",
68
+ };
69
+ }
70
+ return {
71
+ productLabel: "focused MVP workspace",
72
+ targetUser: extractBriefLine(briefContent, "## Target User") || "Solo founders.",
73
+ promise: extractBriefLine(briefContent, "## MVP Promise") || "A focused MVP workflow with a clear next action.",
74
+ primaryRecord: "record",
75
+ primaryAction: "Start first workflow",
76
+ dashboardObject: "active work and next actions",
77
+ emptyObject: "records",
78
+ };
79
+ }
80
+
81
+ function screenMapContent(briefContent) {
82
+ const context = inferScreenContext(briefContent);
83
+ const screens = [
84
+ {
85
+ id: "LANDING",
86
+ name: "Landing",
87
+ purpose: `Explain the ${context.productLabel} promise quickly and route the user into the first meaningful action.`,
88
+ action: context.primaryAction,
89
+ data: "Value proposition, primary CTA, proof points, and a short preview of the first workflow.",
90
+ empty: "Show concise promise copy and a single CTA when no workspace exists yet.",
91
+ success: "User understands the product promise and starts onboarding.",
92
+ error: "If onboarding cannot start, show a clear retry path and keep the CTA visible.",
93
+ },
94
+ {
95
+ id: "ONBOARDING",
96
+ name: "Onboarding",
97
+ purpose: `Capture only the minimum setup needed before the user can create the first ${context.primaryRecord}.`,
98
+ action: context.primaryAction,
99
+ data: "User name or workspace label, first workflow preference, and optional sample data choice.",
100
+ empty: "Offer one guided setup path and one option to skip non-essential fields.",
101
+ success: `Workspace is ready and the user can create the first ${context.primaryRecord}.`,
102
+ error: "Inline validation identifies the missing required field and preserves entered values.",
103
+ },
104
+ {
105
+ id: "DASHBOARD",
106
+ name: "Dashboard",
107
+ purpose: `Show ${context.dashboardObject} with enough context to decide the next action.`,
108
+ action: context.primaryAction,
109
+ data: "Summary counters, prioritized list, next action date, recent notes, and simple status or stage.",
110
+ empty: `Explain that no ${context.emptyObject} exist yet and keep the first-create CTA prominent.`,
111
+ success: "User can scan current work, add or update a record, and leave with a visible next action.",
112
+ error: "If records fail to load, show a recoverable error with retry and no destructive action.",
113
+ },
114
+ ];
115
+
116
+ const lines = [
117
+ "# Screen Map",
118
+ "",
119
+ "## Brief Input Summary",
120
+ "",
121
+ `- Target user: ${context.targetUser}`,
122
+ `- MVP promise: ${context.promise}`,
123
+ "",
124
+ "## User Journey",
125
+ "",
126
+ "1. Visitor lands on the product promise and chooses the primary CTA.",
127
+ "2. User completes a short setup path that avoids enterprise configuration.",
128
+ "3. User reaches the dashboard and performs the first repeated workflow action.",
129
+ "",
130
+ "## MVP Screens",
131
+ "",
132
+ ];
133
+
134
+ for (const screen of screens) {
135
+ lines.push(
136
+ `### ${screen.id} - ${screen.name}`,
137
+ "",
138
+ `- Purpose: ${screen.purpose}`,
139
+ `- Primary action: ${screen.action}`,
140
+ `- Data needed: ${screen.data}`,
141
+ `- Empty state: ${screen.empty}`,
142
+ `- Success state: ${screen.success}`,
143
+ `- Error state: ${screen.error}`,
144
+ ""
145
+ );
146
+ }
147
+
148
+ lines.push(
149
+ "## Out Of Scope For MVP Screen Map",
150
+ "",
151
+ "- Production application code, database schema, and integration implementation.",
152
+ "- Complex enterprise configuration, advanced analytics, and multi-team administration.",
153
+ "- Figma, Lovable, v0, or full app-builder replacement behavior.",
154
+ ""
155
+ );
156
+
157
+ return lines.join("\n");
158
+ }
159
+
160
+ function runDesignScreens({ projectPath, force = false }) {
161
+ const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
162
+ if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
163
+ throw new ValidationError(`--project-path is not a valid directory: ${resolvedProjectPath}. No project files were changed.`);
164
+ }
165
+
166
+ const paths = describeDesignPaths(resolvedProjectPath);
167
+ if (!fs.existsSync(paths.designBriefPath)) {
168
+ throw new ValidationError("Missing docs/design/DESIGN_BRIEF.md. Run sdtk-design brief --idea \"<idea>\" first. No project files were changed.");
169
+ }
170
+ if (fs.existsSync(paths.screenMapPath) && !force) {
171
+ throw new ValidationError("docs/design/SCREEN_MAP.md already exists. Re-run with --force to replace this managed screen map.");
172
+ }
173
+
174
+ const briefContent = fs.readFileSync(paths.designBriefPath, "utf-8");
175
+ fs.mkdirSync(path.dirname(paths.screenMapPath), { recursive: true });
176
+ fs.writeFileSync(paths.screenMapPath, screenMapContent(briefContent), "utf-8");
177
+
178
+ return {
179
+ projectPath: resolvedProjectPath,
180
+ screenMapPath: paths.screenMapPath,
181
+ relativeScreenMapPath: "docs/design/SCREEN_MAP.md",
182
+ forced: Boolean(force),
183
+ };
184
+ }
185
+
186
+ function cmdScreens(args) {
187
+ const { flags } = parseFlags(args || [], SCREENS_FLAG_DEFS);
188
+ if (flags.help) return cmdScreensHelp();
189
+
190
+ const result = runDesignScreens({
191
+ projectPath: flags["project-path"],
192
+ force: Boolean(flags.force),
193
+ });
194
+
195
+ console.log(`[design] Wrote ${result.relativeScreenMapPath}: ${result.projectPath}`);
196
+ console.log(`[design] Overwrite: ${result.forced ? "enabled by --force" : "not needed"}`);
197
+ console.log("[design] No .sdtk/atlas, SDTK-WIKI output, source files, network, or app code was modified.");
198
+ console.log("[design] Next: sdtk-design wireframe --screen landing");
199
+ return 0;
200
+ }
201
+
202
+ module.exports = {
203
+ cmdScreens,
204
+ cmdScreensHelp,
205
+ inferScreenContext,
206
+ runDesignScreens,
207
+ screenMapContent,
208
+ };
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { runDesignBrief } = require("./brief");
6
+ const { runDesignScreens } = require("./screens");
7
+ const { runDesignSystem } = require("./system");
8
+ const { runDesignWireframe } = require("./wireframe");
9
+ const { parseFlags } = require("../lib/args");
10
+ const { describeDesignPaths, resolveProjectPath } = require("../lib/design-paths");
11
+ const { ValidationError } = require("../lib/errors");
12
+
13
+ const START_FLAG_DEFS = {
14
+ help: { type: "boolean" },
15
+ idea: { type: "string" },
16
+ "project-path": { type: "string" },
17
+ force: { type: "boolean" },
18
+ };
19
+
20
+ const REQUIRED_WIREFRAME_FILES = ["LANDING.md", "ONBOARDING.md", "DASHBOARD.md"];
21
+
22
+ function cmdStartHelp() {
23
+ console.log(`SDTK-DESIGN Start
24
+
25
+ Usage:
26
+ sdtk-design start --idea "<rough MVP idea>" [--project-path <path>] [--force]
27
+
28
+ Example:
29
+ sdtk-design start --idea "I want to build a lightweight CRM for solo consultants to track leads."
30
+
31
+ Runs:
32
+ brief -> screens -> wireframe --screen all -> system
33
+
34
+ Creates:
35
+ docs/design/DESIGN_BRIEF.md
36
+ docs/design/SCREEN_MAP.md
37
+ docs/design/wireframes/LANDING.md
38
+ docs/design/wireframes/ONBOARDING.md
39
+ docs/design/wireframes/DASHBOARD.md
40
+ docs/design/DESIGN_SYSTEM.md
41
+
42
+ Safety:
43
+ Local files only.
44
+ Existing managed core design outputs are not overwritten unless --force is explicit.
45
+ No review, handoff, URL, browser, screenshot, vision, or DOM work.
46
+ No .sdtk/atlas creation or mutation.
47
+ No SDTK-WIKI output mutation.
48
+ No network call, Pro entitlement, or production app code generation.`);
49
+ return 0;
50
+ }
51
+
52
+ function normalizeIdea(idea) {
53
+ return String(idea || "").replace(/\s+/g, " ").trim();
54
+ }
55
+
56
+ function coreOutputTargets(paths) {
57
+ return [
58
+ { relativePath: "docs/design/DESIGN_BRIEF.md", filePath: paths.designBriefPath },
59
+ { relativePath: "docs/design/SCREEN_MAP.md", filePath: paths.screenMapPath },
60
+ { relativePath: "docs/design/DESIGN_SYSTEM.md", filePath: paths.designSystemPath },
61
+ ...REQUIRED_WIREFRAME_FILES.map((fileName) => ({
62
+ relativePath: `docs/design/wireframes/${fileName}`,
63
+ filePath: path.join(paths.wireframesPath, fileName),
64
+ })),
65
+ ];
66
+ }
67
+
68
+ function runDesignStart({ idea, projectPath, force = false }) {
69
+ const normalizedIdea = normalizeIdea(idea);
70
+ if (!normalizedIdea) {
71
+ throw new ValidationError('Missing required --idea "<rough MVP idea>". No project files were changed.');
72
+ }
73
+
74
+ const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
75
+ if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
76
+ throw new ValidationError(`--project-path is not a valid directory: ${resolvedProjectPath}. No project files were changed.`);
77
+ }
78
+
79
+ const paths = describeDesignPaths(resolvedProjectPath);
80
+ const existing = coreOutputTargets(paths).filter((target) => fs.existsSync(target.filePath));
81
+ if (existing.length > 0 && !force) {
82
+ const files = existing.map((target) => target.relativePath).join(", ");
83
+ throw new ValidationError(`Managed core design output already exists: ${files}. Re-run with --force to replace core outputs.`);
84
+ }
85
+
86
+ runDesignBrief({ idea: normalizedIdea, projectPath: resolvedProjectPath, force });
87
+ runDesignScreens({ projectPath: resolvedProjectPath, force });
88
+ runDesignWireframe({ screen: "all", projectPath: resolvedProjectPath, force });
89
+ runDesignSystem({ projectPath: resolvedProjectPath, force });
90
+
91
+ return {
92
+ projectPath: resolvedProjectPath,
93
+ written: coreOutputTargets(paths).map((target) => target.relativePath),
94
+ forced: Boolean(force),
95
+ };
96
+ }
97
+
98
+ function cmdStart(args) {
99
+ const { flags } = parseFlags(args || [], START_FLAG_DEFS);
100
+ if (flags.help) return cmdStartHelp();
101
+
102
+ const result = runDesignStart({
103
+ idea: flags.idea,
104
+ projectPath: flags["project-path"],
105
+ force: Boolean(flags.force),
106
+ });
107
+
108
+ console.log(`[design] Started SDTK-DESIGN package: ${result.projectPath}`);
109
+ console.log(`[design] Wrote core artifacts: ${result.written.join(", ")}`);
110
+ console.log(`[design] Overwrite: ${result.forced ? "enabled by --force" : "not needed"}`);
111
+ console.log("[design] No review, handoff, URL, browser, screenshot, vision, network, .sdtk/atlas, or SDTK-WIKI output was used.");
112
+ console.log("[design] Next: sdtk-design review --artifact docs/design/wireframes/LANDING.md");
113
+ return 0;
114
+ }
115
+
116
+ module.exports = {
117
+ cmdStart,
118
+ cmdStartHelp,
119
+ coreOutputTargets,
120
+ runDesignStart,
121
+ };