procedure-cli 0.1.13 → 0.1.14

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.
Files changed (42) hide show
  1. package/dist/steps/build-test.js +1 -1
  2. package/dist/steps/build-test.js.map +1 -1
  3. package/dist/steps/product-context.js +1 -1
  4. package/dist/steps/product-context.js.map +1 -1
  5. package/dist/steps/project-info.js +1 -1
  6. package/dist/steps/project-info.js.map +1 -1
  7. package/package.json +5 -1
  8. package/.claude/settings.local.json +0 -27
  9. package/.env.example +0 -2
  10. package/AGENTS.md +0 -134
  11. package/CLAUDE.md +0 -138
  12. package/CODE-FIXED.md +0 -252
  13. package/CODE-REVIEW.md +0 -558
  14. package/config/defaults.json +0 -8
  15. package/config/powerline-config.json +0 -52
  16. package/config/stacks/typescript-node.json +0 -15
  17. package/docs/GIAI-THICH-CLAUDE-MD.md +0 -206
  18. package/docs/PRD.md +0 -141
  19. package/docs/USER-STORIES.md +0 -324
  20. package/src/app.tsx +0 -213
  21. package/src/cli.tsx +0 -19
  22. package/src/components/banner.tsx +0 -23
  23. package/src/components/gutter-line.tsx +0 -16
  24. package/src/components/guttered-select.tsx +0 -231
  25. package/src/components/step-indicator.tsx +0 -32
  26. package/src/components/timeline.tsx +0 -57
  27. package/src/lib/fs.ts +0 -23
  28. package/src/lib/git.ts +0 -41
  29. package/src/lib/powerline.ts +0 -48
  30. package/src/lib/template.ts +0 -161
  31. package/src/lib/types.ts +0 -70
  32. package/src/providers/openai.ts +0 -5
  33. package/src/providers/zai.ts +0 -7
  34. package/src/steps/architecture.tsx +0 -72
  35. package/src/steps/build-test.tsx +0 -114
  36. package/src/steps/generation.tsx +0 -176
  37. package/src/steps/powerline.tsx +0 -254
  38. package/src/steps/product-context.tsx +0 -269
  39. package/src/steps/project-info.tsx +0 -183
  40. package/src/steps/stack-style.tsx +0 -304
  41. package/src/theme.ts +0 -15
  42. package/tsconfig.json +0 -17
@@ -1,48 +0,0 @@
1
- import { readFileSync, writeFileSync, copyFileSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
- import { ensureDir, fileExists, resolveTemplatePath } from './fs.js';
4
-
5
- interface ClaudeSettings {
6
- statusLine?: {
7
- type: string;
8
- command: string;
9
- padding: number;
10
- };
11
- [key: string]: unknown;
12
- }
13
-
14
- export function setupPowerline(targetDir: string): void {
15
- const claudeDir = resolve(targetDir, '.claude');
16
- ensureDir(claudeDir);
17
-
18
- const settingsPath = resolve(claudeDir, 'settings.json');
19
-
20
- let settings: ClaudeSettings = {};
21
- if (fileExists(settingsPath)) {
22
- try {
23
- const raw = readFileSync(settingsPath, 'utf-8');
24
- settings = JSON.parse(raw) as ClaudeSettings;
25
- } catch {
26
- // Malformed settings.json — start fresh, original is not overwritten destructively
27
- settings = {};
28
- }
29
- }
30
-
31
- settings.statusLine = {
32
- type: 'command',
33
- command:
34
- 'npx -y @owloops/claude-powerline@latest --config=.claude/powerline-config.json',
35
- padding: 0,
36
- };
37
-
38
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
39
-
40
- // Copy bundled powerline-config.json to target .claude/
41
- const bundledConfig = resolve(
42
- resolveTemplatePath('..'),
43
- 'config',
44
- 'powerline-config.json',
45
- );
46
- const destConfig = resolve(claudeDir, 'powerline-config.json');
47
- copyFileSync(bundledConfig, destConfig);
48
- }
@@ -1,161 +0,0 @@
1
- import { readFileSync, writeFileSync, existsSync, chmodSync } from 'node:fs';
2
- import { resolve, dirname } from 'node:path';
3
- import Handlebars from 'handlebars';
4
- import { ensureDir, resolveTemplatePath, homeBinPath } from './fs.js';
5
- import type { WizardAnswers } from './types.js';
6
-
7
- Handlebars.registerHelper('eq', (a: unknown, b: unknown) => a === b);
8
-
9
- export function renderTemplate(
10
- templatePath: string,
11
- data: Record<string, unknown>,
12
- ): string {
13
- const source = readFileSync(templatePath, 'utf-8');
14
- const template = Handlebars.compile(source);
15
- return template(data);
16
- }
17
-
18
- export function writeTemplate(
19
- templatePath: string,
20
- outputPath: string,
21
- data: Record<string, unknown>,
22
- ): void {
23
- const rendered = renderTemplate(templatePath, data);
24
- ensureDir(dirname(outputPath));
25
- writeFileSync(outputPath, rendered, 'utf-8');
26
- }
27
-
28
- const TEMPLATE_MAP: Array<{ template: string; output: string }> = [
29
- { template: 'CLAUDE.md.hbs', output: 'CLAUDE.md' },
30
- { template: 'README.md.hbs', output: 'README.md' },
31
- { template: 'docs/PRD.md.hbs', output: 'docs/PRD.md' },
32
- { template: 'docs/USER-STORIES.md.hbs', output: 'docs/USER-STORIES.md' },
33
- { template: '.env.example.hbs', output: '.env.example' },
34
- ];
35
-
36
- /**
37
- * Check which output files already exist in the target directory.
38
- * Returns a list of file paths that would be overwritten.
39
- */
40
- export function checkConflicts(targetDir: string): string[] {
41
- const allOutputs = [
42
- ...TEMPLATE_MAP.map((t) => t.output),
43
- '.gitignore',
44
- ];
45
- return allOutputs.filter((f) => existsSync(resolve(targetDir, f)));
46
- }
47
-
48
- export function scaffoldAll(targetDir: string, data: WizardAnswers): void {
49
- for (const { template, output } of TEMPLATE_MAP) {
50
- const templatePath = resolveTemplatePath(template);
51
- const outputPath = resolve(targetDir, output);
52
- writeTemplate(templatePath, outputPath, data as unknown as Record<string, unknown>);
53
- }
54
-
55
- // Copy static .gitignore (no templating needed)
56
- const gitignoreSrc = resolveTemplatePath('.gitignore');
57
- const gitignoreDest = resolve(targetDir, '.gitignore');
58
- const content = readFileSync(gitignoreSrc, 'utf-8');
59
- ensureDir(dirname(gitignoreDest));
60
- writeFileSync(gitignoreDest, content, 'utf-8');
61
- }
62
-
63
- export interface ReleaseConflicts {
64
- releaseScriptExists: boolean;
65
- packageJsonHasRelease: boolean;
66
- packageJsonExists: boolean;
67
- }
68
-
69
- export function checkReleaseConflicts(
70
- projectName: string,
71
- targetDir: string,
72
- ): ReleaseConflicts {
73
- const scriptPath = homeBinPath(`${projectName}-release`);
74
- const pkgPath = resolve(targetDir, 'package.json');
75
-
76
- let packageJsonExists = false;
77
- let packageJsonHasRelease = false;
78
-
79
- if (existsSync(pkgPath)) {
80
- packageJsonExists = true;
81
- try {
82
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
83
- packageJsonHasRelease = !!pkg.scripts?.['release:patch'];
84
- } catch {
85
- // malformed package.json — treat as no release scripts
86
- }
87
- }
88
-
89
- return {
90
- releaseScriptExists: existsSync(scriptPath),
91
- packageJsonHasRelease,
92
- packageJsonExists,
93
- };
94
- }
95
-
96
- export interface ScaffoldReleaseResult {
97
- scriptPath: string;
98
- skipped: boolean;
99
- reason?: string;
100
- }
101
-
102
- export function scaffoldRelease(
103
- projectName: string,
104
- data: WizardAnswers,
105
- ): ScaffoldReleaseResult {
106
- const scriptPath = homeBinPath(`${projectName}-release`);
107
-
108
- if (existsSync(scriptPath)) {
109
- return { scriptPath, skipped: true, reason: 'already exists' };
110
- }
111
-
112
- const templatePath = resolveTemplatePath('bin/release.sh.hbs');
113
- const rendered = renderTemplate(templatePath, data as unknown as Record<string, unknown>);
114
- ensureDir(dirname(scriptPath));
115
- writeFileSync(scriptPath, rendered, 'utf-8');
116
- chmodSync(scriptPath, 0o755);
117
-
118
- return { scriptPath, skipped: false };
119
- }
120
-
121
- export interface PackageJsonReleaseResult {
122
- created: boolean;
123
- modified: boolean;
124
- skipped: boolean;
125
- }
126
-
127
- export function ensurePackageJsonReleaseScripts(
128
- targetDir: string,
129
- projectName: string,
130
- ): PackageJsonReleaseResult {
131
- const pkgPath = resolve(targetDir, 'package.json');
132
- const releaseScripts = {
133
- 'release:patch': `"$HOME/bin/${projectName}-release" patch`,
134
- 'release:minor': `"$HOME/bin/${projectName}-release" minor`,
135
- 'release:major': `"$HOME/bin/${projectName}-release" major`,
136
- };
137
-
138
- if (existsSync(pkgPath)) {
139
- try {
140
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
141
- if (pkg.scripts?.['release:patch']) {
142
- return { created: false, modified: false, skipped: true };
143
- }
144
- pkg.scripts = { ...pkg.scripts, ...releaseScripts };
145
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
146
- return { created: false, modified: true, skipped: false };
147
- } catch {
148
- // malformed — skip
149
- return { created: false, modified: false, skipped: true };
150
- }
151
- }
152
-
153
- const pkg = {
154
- name: projectName,
155
- version: '0.1.0',
156
- scripts: releaseScripts,
157
- };
158
- ensureDir(dirname(pkgPath));
159
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
160
- return { created: true, modified: false, skipped: false };
161
- }
package/src/lib/types.ts DELETED
@@ -1,70 +0,0 @@
1
- export type StepId =
2
- | 'project-info'
3
- | 'stack-style'
4
- | 'build-test'
5
- | 'architecture'
6
- | 'product-context'
7
- | 'generation'
8
- | 'powerline';
9
-
10
- export interface UserStoryScenario {
11
- name: string;
12
- given: string;
13
- when: string;
14
- then: string;
15
- }
16
-
17
- export interface UserStory {
18
- title: string;
19
- asA: string;
20
- iWant: string;
21
- soThat: string;
22
- feature: string;
23
- scenarios: UserStoryScenario[];
24
- }
25
-
26
- export interface EnvVar {
27
- key: string;
28
- comment: string;
29
- }
30
-
31
- export interface WizardAnswers {
32
- // Step 1: Project Info
33
- projectName: string;
34
- description: string;
35
- packageManager: string;
36
- license: string;
37
-
38
- // Step 2: Stack & Style
39
- codeStyle: string[];
40
- framework: string;
41
- language: string;
42
-
43
- // Step 3: Build & Test
44
- buildCommand: string;
45
- testCommand: string;
46
- typecheckCommand: string;
47
- lintCommand: string;
48
- prCommand: string;
49
-
50
- // Step 4: Architecture
51
- architecture: string;
52
- architectureNotes: string;
53
-
54
- // Step 5: Product Context
55
- problem: string;
56
- users: string;
57
- coreFeatures: string[];
58
- nonGoals: string[];
59
- techStack: string;
60
- userStories: UserStory[];
61
- envVars: EnvVar[];
62
-
63
- // Step 6: Generation
64
- generationSkipped: boolean;
65
-
66
- // Step 7: Setup
67
- setupPowerline: boolean;
68
- setupGit: boolean;
69
- setupRelease: boolean;
70
- }
@@ -1,5 +0,0 @@
1
- import { createOpenAI } from "@ai-sdk/openai";
2
-
3
- export const openai = createOpenAI({
4
- apiKey: process.env.OPENAI_API_KEY,
5
- });
@@ -1,7 +0,0 @@
1
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
2
-
3
- export const zai = createOpenAICompatible({
4
- name: "zai",
5
- baseURL: "https://api.z.ai/v1",
6
- apiKey: process.env.ZAI_API_KEY,
7
- });
@@ -1,72 +0,0 @@
1
- import React, { useState } from "react";
2
- import { Text } from "ink";
3
- import { GutterLine } from "../components/gutter-line.js";
4
- import { GutteredSelect } from "../components/guttered-select.js";
5
- import type { WizardAnswers } from "../lib/types.js";
6
- import { C } from "../theme.js";
7
-
8
- interface Props {
9
- onComplete: (answers: Partial<WizardAnswers>) => void;
10
- }
11
-
12
- const ARCHITECTURE_OPTIONS = [
13
- { label: "Monorepo — Multiple packages in one repository", value: "Monorepo" },
14
- { label: "MVC — Model-View-Controller separation", value: "MVC" },
15
- { label: "Feature folders — Co-located components, hooks, utils per feature", value: "Feature folders" },
16
- { label: "Layered — Presentation > Business > Data access layers", value: "Layered" },
17
- { label: "Microservices — Independent deployable services", value: "Microservices" },
18
- { label: "Serverless — Function-based, event-driven architecture", value: "Serverless" },
19
- { label: "Modular monolith — Single deploy, strict module boundaries", value: "Modular monolith" },
20
- ];
21
-
22
- const ARCHITECTURE_NOTES: Record<string, string> = {
23
- Monorepo:
24
- "Shared packages in packages/, apps in apps/. Use workspace protocol for cross-references.",
25
- MVC:
26
- "Controllers handle HTTP, models handle data, views handle presentation. Keep business logic in models.",
27
- "Feature folders":
28
- "Each feature owns its components, hooks, utils, and tests. Shared code in shared/ or lib/.",
29
- Layered:
30
- "Strict dependency direction: Presentation -> Business -> Data. No skipping layers.",
31
- Microservices:
32
- "Each service owns its data store. Communicate via APIs or message queues. Deploy independently.",
33
- Serverless:
34
- "One function per endpoint. Keep functions stateless. Use managed services for state and storage.",
35
- "Modular monolith":
36
- "Define explicit module boundaries with public APIs. No direct cross-module database access.",
37
- };
38
-
39
- export default function Architecture({ onComplete }: Props) {
40
- const [selected, setSelected] = useState<string | null>(null);
41
-
42
- function handleChange(value: string) {
43
- const notes = ARCHITECTURE_NOTES[value] ?? "";
44
- setSelected(value);
45
- // Use setTimeout to allow React to render the selection before completing
46
- setTimeout(() => {
47
- onComplete({ architecture: value, architectureNotes: notes });
48
- }, 100);
49
- }
50
-
51
- if (selected) {
52
- return (
53
- <>
54
- <GutterLine>
55
- <Text color={C.overlay1}>Architecture: {selected}</Text>
56
- </GutterLine>
57
- <GutterLine>
58
- <Text color={C.overlay1}>Notes: {ARCHITECTURE_NOTES[selected]}</Text>
59
- </GutterLine>
60
- </>
61
- );
62
- }
63
-
64
- return (
65
- <>
66
- <GutterLine>
67
- <Text bold>Architecture pattern:</Text>
68
- </GutterLine>
69
- <GutteredSelect options={ARCHITECTURE_OPTIONS} onChange={handleChange} />
70
- </>
71
- );
72
- }
@@ -1,114 +0,0 @@
1
- import React, { useState, useEffect } from "react";
2
- import { Text, useInput } from "ink";
3
- import { TextInput } from "@inkjs/ui";
4
- import { GutterLine } from "../components/gutter-line.js";
5
- import type { WizardAnswers } from "../lib/types.js";
6
- import { C } from "../theme.js";
7
-
8
- interface Props {
9
- initialValues?: Partial<WizardAnswers>;
10
- onComplete: (answers: Partial<WizardAnswers>) => void;
11
- }
12
-
13
- type Field =
14
- | "buildCommand"
15
- | "testCommand"
16
- | "typecheckCommand"
17
- | "lintCommand"
18
- | "prCommand";
19
-
20
- const FIELDS: { key: Field; label: string; fallback: string; required: boolean }[] = [
21
- { key: "buildCommand", label: "Build command", fallback: "npm run build", required: true },
22
- { key: "testCommand", label: "Test command", fallback: "npm run test", required: true },
23
- { key: "typecheckCommand", label: "Typecheck command", fallback: "tsc --noEmit", required: false },
24
- { key: "lintCommand", label: "Lint command", fallback: "npm run lint", required: false },
25
- { key: "prCommand", label: "Pre-PR command", fallback: "npm run lint && npm run test", required: false },
26
- ];
27
-
28
- export default function BuildTest({ initialValues, onComplete }: Props) {
29
- const [fieldIndex, setFieldIndex] = useState(0);
30
- const [answers, setAnswers] = useState<Record<string, string>>({});
31
- const [error, setError] = useState("");
32
- const [liveInput, setLiveInput] = useState("");
33
-
34
- const current = FIELDS[fieldIndex]!;
35
- const preset = initialValues?.[current.key] as string | undefined;
36
- const defaultVal = preset || current.fallback;
37
-
38
- // Reset live input when navigating to a different field
39
- useEffect(() => {
40
- setLiveInput("");
41
- }, [fieldIndex]);
42
-
43
- useInput((input, key) => {
44
- if (key.shift && key.tab && fieldIndex > 0) {
45
- setFieldIndex((f) => f - 1);
46
- setError("");
47
- } else if (key.tab && !key.shift) {
48
- const val = liveInput.trim() || answers[current.key] || defaultVal;
49
- if (current.required && !val.trim()) {
50
- setError(`${current.label} is required`);
51
- return;
52
- }
53
- setError("");
54
- const next = { ...answers, [current.key]: val };
55
- setAnswers(next);
56
- if (fieldIndex < FIELDS.length - 1) {
57
- setFieldIndex((f) => f + 1);
58
- } else {
59
- onComplete(next as unknown as Partial<WizardAnswers>);
60
- }
61
- }
62
- });
63
-
64
- function handleSubmit(value: string) {
65
- const val = value.trim() || defaultVal;
66
- if (current.required && !val) {
67
- setError(`${current.label} is required`);
68
- return;
69
- }
70
- setError("");
71
- const next = { ...answers, [current.key]: val };
72
- setAnswers(next);
73
- if (fieldIndex < FIELDS.length - 1) {
74
- setFieldIndex(fieldIndex + 1);
75
- } else {
76
- onComplete(next as unknown as Partial<WizardAnswers>);
77
- }
78
- }
79
-
80
- const isFirst = fieldIndex === 0;
81
- const isLast = fieldIndex === FIELDS.length - 1;
82
-
83
- return (
84
- <>
85
- {FIELDS.slice(0, fieldIndex).map((f) => (
86
- <GutterLine key={f.key}>
87
- <Text color={C.overlay1}>
88
- {f.label}: {answers[f.key]}
89
- </Text>
90
- </GutterLine>
91
- ))}
92
-
93
- <GutterLine>
94
- <Text bold>{current.label}</Text>
95
- <Text color={C.overlay1}> ({defaultVal})</Text>
96
- <Text bold>: </Text>
97
- <TextInput placeholder={defaultVal} onChange={setLiveInput} onSubmit={handleSubmit} />
98
- </GutterLine>
99
-
100
- {error && (
101
- <GutterLine>
102
- <Text color={C.red}>{error}</Text>
103
- </GutterLine>
104
- )}
105
-
106
- <GutterLine>
107
- <Text color={C.overlay1}>
108
- {!isFirst ? "Shift+Tab prev " : ""}
109
- {isLast ? "Tab / Enter confirm" : "Tab next Enter confirm"}
110
- </Text>
111
- </GutterLine>
112
- </>
113
- );
114
- }
@@ -1,176 +0,0 @@
1
- import React, { useEffect, useState } from "react";
2
- import { Text } from "ink";
3
- import { ConfirmInput, Spinner } from "@inkjs/ui";
4
- import { GutterLine } from "../components/gutter-line.js";
5
- import { scaffoldAll, checkConflicts } from "../lib/template.js";
6
- import type { WizardAnswers } from "../lib/types.js";
7
- import { C } from "../theme.js";
8
-
9
- interface Props {
10
- answers: WizardAnswers;
11
- onComplete: (partial: Partial<WizardAnswers>) => void;
12
- }
13
-
14
- const FILES_TO_GENERATE = [
15
- "CLAUDE.md",
16
- "README.md",
17
- "docs/PRD.md",
18
- "docs/USER-STORIES.md",
19
- ".env.example",
20
- ".gitignore",
21
- ];
22
-
23
- export default function Generation({ answers, onComplete }: Props) {
24
- const [phase, setPhase] = useState<"summary" | "running" | "done" | "error">("summary");
25
- const [errorMsg, setErrorMsg] = useState("");
26
-
27
- useEffect(() => {
28
- if (phase !== "running") return;
29
-
30
- try {
31
- scaffoldAll(process.cwd(), answers);
32
- setPhase("done");
33
- const timer = setTimeout(() => onComplete({ generationSkipped: false }), 1500);
34
- return () => clearTimeout(timer);
35
- } catch (err) {
36
- setPhase("error");
37
- setErrorMsg(err instanceof Error ? err.message : String(err));
38
- }
39
- }, [phase]);
40
-
41
- function handleConfirm() { setPhase("running"); }
42
- function handleCancel() { onComplete({ generationSkipped: true }); }
43
-
44
- if (phase === "summary") {
45
- const conflicts = checkConflicts(process.cwd());
46
- const codeStyleDisplay = answers.codeStyle.length > 0
47
- ? answers.codeStyle.join(", ")
48
- : "N/A";
49
-
50
- return (
51
- <>
52
- {/* ── Project Info ── */}
53
- <GutterLine>
54
- <Text bold color={C.sapphire}>Project Info</Text>
55
- </GutterLine>
56
- <GutterLine>
57
- <Text> Name: <Text bold>{answers.projectName || "untitled"}</Text></Text>
58
- </GutterLine>
59
- <GutterLine>
60
- <Text> Description: {answers.description || "N/A"}</Text>
61
- </GutterLine>
62
- <GutterLine>
63
- <Text> Package manager: {answers.packageManager} | License: {answers.license}</Text>
64
- </GutterLine>
65
-
66
- {/* ── Stack & Style ── */}
67
- <GutterLine><Text> </Text></GutterLine>
68
- <GutterLine>
69
- <Text bold color={C.sapphire}>Stack & Style</Text>
70
- </GutterLine>
71
- <GutterLine>
72
- <Text> Language: {answers.language || "N/A"} | Framework: {answers.framework || "N/A"}</Text>
73
- </GutterLine>
74
- <GutterLine>
75
- <Text> Code style: {codeStyleDisplay}</Text>
76
- </GutterLine>
77
-
78
- {/* ── Build & Test ── */}
79
- <GutterLine><Text> </Text></GutterLine>
80
- <GutterLine>
81
- <Text bold color={C.sapphire}>Build & Test</Text>
82
- </GutterLine>
83
- <GutterLine>
84
- <Text> Build: {answers.buildCommand || "N/A"} | Test: {answers.testCommand || "N/A"}</Text>
85
- </GutterLine>
86
- {(answers.typecheckCommand || answers.lintCommand) && (
87
- <GutterLine>
88
- <Text> Typecheck: {answers.typecheckCommand || "N/A"} | Lint: {answers.lintCommand || "N/A"}</Text>
89
- </GutterLine>
90
- )}
91
- {answers.prCommand && (
92
- <GutterLine>
93
- <Text> PR: {answers.prCommand}</Text>
94
- </GutterLine>
95
- )}
96
-
97
- {/* ── Architecture ── */}
98
- <GutterLine><Text> </Text></GutterLine>
99
- <GutterLine>
100
- <Text bold color={C.sapphire}>Architecture</Text>
101
- </GutterLine>
102
- <GutterLine>
103
- <Text> {answers.architecture || "N/A"}</Text>
104
- </GutterLine>
105
-
106
- {/* ── Product Context ── */}
107
- <GutterLine><Text> </Text></GutterLine>
108
- <GutterLine>
109
- <Text bold color={C.sapphire}>Product Context</Text>
110
- </GutterLine>
111
- <GutterLine>
112
- <Text> Problem: {answers.problem || "N/A"}</Text>
113
- </GutterLine>
114
- <GutterLine>
115
- <Text> Users: {answers.users || "N/A"}</Text>
116
- </GutterLine>
117
- {answers.techStack && (
118
- <GutterLine>
119
- <Text> Tech stack: {answers.techStack}</Text>
120
- </GutterLine>
121
- )}
122
- <GutterLine>
123
- <Text> Features: {answers.coreFeatures.length > 0 ? answers.coreFeatures.join(", ") : "N/A"}</Text>
124
- </GutterLine>
125
- {answers.nonGoals.length > 0 && (
126
- <GutterLine>
127
- <Text> Non-goals: {answers.nonGoals.join(", ")}</Text>
128
- </GutterLine>
129
- )}
130
-
131
- {/* ── Files ── */}
132
- <GutterLine><Text> </Text></GutterLine>
133
- <GutterLine>
134
- <Text bold color={C.sapphire}>Files to generate</Text>
135
- </GutterLine>
136
- <GutterLine>
137
- <Text> {FILES_TO_GENERATE.join(", ")}</Text>
138
- </GutterLine>
139
- {conflicts.length > 0 && (
140
- <GutterLine>
141
- <Text color={C.peach}> ⚠ Will overwrite: {conflicts.join(", ")}</Text>
142
- </GutterLine>
143
- )}
144
-
145
- {/* ── Confirm ── */}
146
- <GutterLine><Text> </Text></GutterLine>
147
- <GutterLine>
148
- <Text>Proceed? </Text>
149
- <ConfirmInput onConfirm={handleConfirm} onCancel={handleCancel} />
150
- </GutterLine>
151
- </>
152
- );
153
- }
154
-
155
- if (phase === "running") {
156
- return (
157
- <GutterLine>
158
- <Spinner label="Generating project files..." />
159
- </GutterLine>
160
- );
161
- }
162
-
163
- if (phase === "error") {
164
- return (
165
- <GutterLine>
166
- <Text color={C.red}>Error generating files: {errorMsg}</Text>
167
- </GutterLine>
168
- );
169
- }
170
-
171
- return (
172
- <GutterLine>
173
- <Text color={C.green}>All project files generated successfully.</Text>
174
- </GutterLine>
175
- );
176
- }