procedure-cli 0.1.12 → 0.1.13
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/.claude/settings.local.json +2 -1
- package/AGENTS.md +82 -61
- package/CODE-FIXED.md +55 -0
- package/CODE-REVIEW.md +127 -0
- package/README.md +15 -3
- package/dist/steps/build-test.js +32 -3
- package/dist/steps/build-test.js.map +1 -1
- package/dist/steps/powerline.js +11 -3
- package/dist/steps/powerline.js.map +1 -1
- package/dist/steps/product-context.js +54 -19
- package/dist/steps/product-context.js.map +1 -1
- package/dist/steps/project-info.js +30 -3
- package/dist/steps/project-info.js.map +1 -1
- package/docs/PRD.md +2 -1
- package/docs/USER-STORIES.md +66 -6
- package/package.json +1 -1
- package/src/steps/build-test.tsx +41 -6
- package/src/steps/powerline.tsx +17 -1
- package/src/steps/product-context.tsx +100 -39
- package/src/steps/project-info.tsx +57 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { Text } from "ink";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Text, useInput } from "ink";
|
|
3
3
|
import { TextInput } from "@inkjs/ui";
|
|
4
4
|
import { GutterLine } from "../components/gutter-line.js";
|
|
5
5
|
import { GutteredMultiSelect } from "../components/guttered-select.js";
|
|
@@ -57,6 +57,7 @@ export default function ProductContext({ initialValues, onComplete }: Props) {
|
|
|
57
57
|
const [phaseIndex, setPhaseIndex] = useState(0);
|
|
58
58
|
const [answers, setAnswers] = useState<Record<string, string>>({});
|
|
59
59
|
const [error, setError] = useState("");
|
|
60
|
+
const [liveInput, setLiveInput] = useState("");
|
|
60
61
|
|
|
61
62
|
// Prefill tech stack from Stack & Style selections (language + framework)
|
|
62
63
|
const prefillTechStack: string[] = [];
|
|
@@ -74,6 +75,56 @@ export default function ProductContext({ initialValues, onComplete }: Props) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
const currentPhase = PHASE_ORDER[phaseIndex]!;
|
|
78
|
+
const isFirst = phaseIndex === 0;
|
|
79
|
+
const isLast = phaseIndex === PHASE_ORDER.length - 1;
|
|
80
|
+
|
|
81
|
+
// Reset live input when navigating to a different field
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
setLiveInput("");
|
|
84
|
+
}, [phaseIndex]);
|
|
85
|
+
|
|
86
|
+
const isMultiSelectPhase = currentPhase === "techStack" || currentPhase === "coreFeatures";
|
|
87
|
+
|
|
88
|
+
useInput((input, key) => {
|
|
89
|
+
if (key.shift && key.tab && phaseIndex > 0) {
|
|
90
|
+
setPhaseIndex((i) => i - 1);
|
|
91
|
+
setError("");
|
|
92
|
+
} else if (key.tab && !key.shift && !isMultiSelectPhase) {
|
|
93
|
+
// Tab only advances text phases — multi-select phases use Enter to confirm
|
|
94
|
+
const required = currentPhase === "problem" || currentPhase === "users";
|
|
95
|
+
const val = liveInput.trim() || answers[currentPhase] || "";
|
|
96
|
+
if (required && !val) {
|
|
97
|
+
setError("This field is required");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
setError("");
|
|
101
|
+
const next = { ...answers, [currentPhase]: val };
|
|
102
|
+
setAnswers(next);
|
|
103
|
+
if (phaseIndex < PHASE_ORDER.length - 1) {
|
|
104
|
+
setPhaseIndex((i) => i + 1);
|
|
105
|
+
} else {
|
|
106
|
+
finalize(next);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
function finalize(data: Record<string, string>) {
|
|
112
|
+
onComplete({
|
|
113
|
+
problem: data.problem,
|
|
114
|
+
users: data.users,
|
|
115
|
+
techStack: data.techStack,
|
|
116
|
+
coreFeatures: (data.coreFeatures || "")
|
|
117
|
+
.split(",")
|
|
118
|
+
.map((s) => s.trim())
|
|
119
|
+
.filter(Boolean),
|
|
120
|
+
nonGoals: (data.nonGoals || "")
|
|
121
|
+
.split(",")
|
|
122
|
+
.map((s) => s.trim())
|
|
123
|
+
.filter(Boolean),
|
|
124
|
+
userStories: [],
|
|
125
|
+
envVars: [],
|
|
126
|
+
});
|
|
127
|
+
}
|
|
77
128
|
|
|
78
129
|
function advanceToNext(key: string, value: string) {
|
|
79
130
|
const next = { ...answers, [key]: value };
|
|
@@ -83,21 +134,7 @@ export default function ProductContext({ initialValues, onComplete }: Props) {
|
|
|
83
134
|
if (phaseIndex < PHASE_ORDER.length - 1) {
|
|
84
135
|
setPhaseIndex(phaseIndex + 1);
|
|
85
136
|
} else {
|
|
86
|
-
|
|
87
|
-
problem: next.problem,
|
|
88
|
-
users: next.users,
|
|
89
|
-
techStack: next.techStack,
|
|
90
|
-
coreFeatures: (next.coreFeatures || "")
|
|
91
|
-
.split(",")
|
|
92
|
-
.map((s) => s.trim())
|
|
93
|
-
.filter(Boolean),
|
|
94
|
-
nonGoals: (next.nonGoals || "")
|
|
95
|
-
.split(",")
|
|
96
|
-
.map((s) => s.trim())
|
|
97
|
-
.filter(Boolean),
|
|
98
|
-
userStories: [],
|
|
99
|
-
envVars: [],
|
|
100
|
-
});
|
|
137
|
+
finalize(next);
|
|
101
138
|
}
|
|
102
139
|
}
|
|
103
140
|
|
|
@@ -105,7 +142,7 @@ export default function ProductContext({ initialValues, onComplete }: Props) {
|
|
|
105
142
|
return (value: string) => {
|
|
106
143
|
const val = value.trim();
|
|
107
144
|
if (required && !val) {
|
|
108
|
-
setError(
|
|
145
|
+
setError("This field is required");
|
|
109
146
|
return;
|
|
110
147
|
}
|
|
111
148
|
advanceToNext(key, val);
|
|
@@ -142,23 +179,35 @@ export default function ProductContext({ initialValues, onComplete }: Props) {
|
|
|
142
179
|
{completedFields}
|
|
143
180
|
|
|
144
181
|
{currentPhase === "problem" && (
|
|
145
|
-
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
182
|
+
<>
|
|
183
|
+
<GutterLine>
|
|
184
|
+
<Text bold>What problem does this solve? </Text>
|
|
185
|
+
<TextInput
|
|
186
|
+
placeholder="Describe the core problem..."
|
|
187
|
+
onChange={setLiveInput}
|
|
188
|
+
onSubmit={handleTextSubmit("problem", true)}
|
|
189
|
+
/>
|
|
190
|
+
</GutterLine>
|
|
191
|
+
<GutterLine>
|
|
192
|
+
<Text color={C.overlay1}>{"Tab next Enter confirm"}</Text>
|
|
193
|
+
</GutterLine>
|
|
194
|
+
</>
|
|
152
195
|
)}
|
|
153
196
|
|
|
154
197
|
{currentPhase === "users" && (
|
|
155
|
-
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
198
|
+
<>
|
|
199
|
+
<GutterLine>
|
|
200
|
+
<Text bold>Who are the users? </Text>
|
|
201
|
+
<TextInput
|
|
202
|
+
placeholder={usersPlaceholder}
|
|
203
|
+
onChange={setLiveInput}
|
|
204
|
+
onSubmit={handleTextSubmit("users", true)}
|
|
205
|
+
/>
|
|
206
|
+
</GutterLine>
|
|
207
|
+
<GutterLine>
|
|
208
|
+
<Text color={C.overlay1}>{"Shift+Tab prev Tab next Enter confirm"}</Text>
|
|
209
|
+
</GutterLine>
|
|
210
|
+
</>
|
|
162
211
|
)}
|
|
163
212
|
|
|
164
213
|
{currentPhase === "techStack" && (
|
|
@@ -171,6 +220,9 @@ export default function ProductContext({ initialValues, onComplete }: Props) {
|
|
|
171
220
|
initialSelected={prefillTechStack}
|
|
172
221
|
onSubmit={handleTechStackSubmit}
|
|
173
222
|
/>
|
|
223
|
+
<GutterLine>
|
|
224
|
+
<Text color={C.overlay1}>{"Shift+Tab prev ↑↓ move Space toggle Enter confirm"}</Text>
|
|
225
|
+
</GutterLine>
|
|
174
226
|
</>
|
|
175
227
|
)}
|
|
176
228
|
|
|
@@ -185,17 +237,26 @@ export default function ProductContext({ initialValues, onComplete }: Props) {
|
|
|
185
237
|
allowCustom
|
|
186
238
|
customPlaceholder="e.g. Export PDF, Onboarding flow..."
|
|
187
239
|
/>
|
|
240
|
+
<GutterLine>
|
|
241
|
+
<Text color={C.overlay1}>{"Shift+Tab prev ↑↓ move Space toggle Enter confirm"}</Text>
|
|
242
|
+
</GutterLine>
|
|
188
243
|
</>
|
|
189
244
|
)}
|
|
190
245
|
|
|
191
246
|
{currentPhase === "nonGoals" && (
|
|
192
|
-
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
247
|
+
<>
|
|
248
|
+
<GutterLine>
|
|
249
|
+
<Text bold>Non-goals (comma-separated): </Text>
|
|
250
|
+
<TextInput
|
|
251
|
+
placeholder="e.g. Mobile app, Real-time sync..."
|
|
252
|
+
onChange={setLiveInput}
|
|
253
|
+
onSubmit={handleTextSubmit("nonGoals", false)}
|
|
254
|
+
/>
|
|
255
|
+
</GutterLine>
|
|
256
|
+
<GutterLine>
|
|
257
|
+
<Text color={C.overlay1}>{"Shift+Tab prev Tab / Enter confirm"}</Text>
|
|
258
|
+
</GutterLine>
|
|
259
|
+
</>
|
|
199
260
|
)}
|
|
200
261
|
|
|
201
262
|
{error && (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { Text } from "ink";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Text, useInput } from "ink";
|
|
3
3
|
import { TextInput } from "@inkjs/ui";
|
|
4
4
|
import { GutterLine } from "../components/gutter-line.js";
|
|
5
5
|
import { GutteredSelect } from "../components/guttered-select.js";
|
|
@@ -38,8 +38,39 @@ export default function ProjectInfo({ onComplete }: Props) {
|
|
|
38
38
|
const [stepIndex, setStepIndex] = useState(0);
|
|
39
39
|
const [answers, setAnswers] = useState<Record<string, string>>({});
|
|
40
40
|
const [error, setError] = useState("");
|
|
41
|
+
const [liveInput, setLiveInput] = useState("");
|
|
41
42
|
|
|
42
43
|
const currentStep = STEP_ORDER[stepIndex]!;
|
|
44
|
+
const isFirst = stepIndex === 0;
|
|
45
|
+
|
|
46
|
+
// Reset live input when navigating to a different field
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
setLiveInput("");
|
|
49
|
+
}, [stepIndex]);
|
|
50
|
+
|
|
51
|
+
const isSelectStep = currentStep === "packageManager" || currentStep === "license";
|
|
52
|
+
|
|
53
|
+
useInput((input, key) => {
|
|
54
|
+
if (key.shift && key.tab && stepIndex > 0) {
|
|
55
|
+
setStepIndex((i) => i - 1);
|
|
56
|
+
setError("");
|
|
57
|
+
} else if (key.tab && !key.shift && !isSelectStep) {
|
|
58
|
+
// Tab only advances text steps — select steps require an explicit Enter selection
|
|
59
|
+
const val = liveInput.trim() || answers[currentStep] || "";
|
|
60
|
+
if (!val) {
|
|
61
|
+
setError(
|
|
62
|
+
currentStep === "projectName"
|
|
63
|
+
? "Project name is required"
|
|
64
|
+
: "Description is required"
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
setError("");
|
|
69
|
+
const next = { ...answers, [currentStep]: val };
|
|
70
|
+
setAnswers(next);
|
|
71
|
+
setStepIndex((i) => i + 1);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
43
74
|
|
|
44
75
|
function advance(key: string, value: string) {
|
|
45
76
|
const next = { ...answers, [key]: value };
|
|
@@ -89,17 +120,27 @@ export default function ProjectInfo({ onComplete }: Props) {
|
|
|
89
120
|
{completed}
|
|
90
121
|
|
|
91
122
|
{currentStep === "projectName" && (
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
123
|
+
<>
|
|
124
|
+
<GutterLine>
|
|
125
|
+
<Text bold>Project name: </Text>
|
|
126
|
+
<TextInput placeholder="..." onChange={setLiveInput} onSubmit={handleTextSubmit} />
|
|
127
|
+
</GutterLine>
|
|
128
|
+
<GutterLine>
|
|
129
|
+
<Text color={C.overlay1}>{"Tab next Enter confirm"}</Text>
|
|
130
|
+
</GutterLine>
|
|
131
|
+
</>
|
|
96
132
|
)}
|
|
97
133
|
|
|
98
134
|
{currentStep === "description" && (
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
135
|
+
<>
|
|
136
|
+
<GutterLine>
|
|
137
|
+
<Text bold>One-line description: </Text>
|
|
138
|
+
<TextInput placeholder="..." onChange={setLiveInput} onSubmit={handleTextSubmit} />
|
|
139
|
+
</GutterLine>
|
|
140
|
+
<GutterLine>
|
|
141
|
+
<Text color={C.overlay1}>{"Shift+Tab prev Tab next Enter confirm"}</Text>
|
|
142
|
+
</GutterLine>
|
|
143
|
+
</>
|
|
103
144
|
)}
|
|
104
145
|
|
|
105
146
|
{currentStep === "packageManager" && (
|
|
@@ -111,6 +152,9 @@ export default function ProjectInfo({ onComplete }: Props) {
|
|
|
111
152
|
options={PACKAGE_MANAGER_OPTIONS}
|
|
112
153
|
onChange={handleSelectChange("packageManager")}
|
|
113
154
|
/>
|
|
155
|
+
<GutterLine>
|
|
156
|
+
<Text color={C.overlay1}>{"Shift+Tab prev ↑↓ move Enter select"}</Text>
|
|
157
|
+
</GutterLine>
|
|
114
158
|
</>
|
|
115
159
|
)}
|
|
116
160
|
|
|
@@ -123,6 +167,9 @@ export default function ProjectInfo({ onComplete }: Props) {
|
|
|
123
167
|
options={LICENSE_OPTIONS}
|
|
124
168
|
onChange={handleSelectChange("license")}
|
|
125
169
|
/>
|
|
170
|
+
<GutterLine>
|
|
171
|
+
<Text color={C.overlay1}>{"Shift+Tab prev ↑↓ move Enter select"}</Text>
|
|
172
|
+
</GutterLine>
|
|
126
173
|
</>
|
|
127
174
|
)}
|
|
128
175
|
|