pmpt-cli 1.6.0 → 1.7.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.
- package/dist/commands/init.js +143 -11
- package/dist/commands/plan.js +1 -26
- package/dist/lib/clipboard.js +25 -0
- package/dist/lib/scanner.js +289 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
|
-
import { existsSync } from 'fs';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
3
|
import { resolve } from 'path';
|
|
4
4
|
import { initializeProject, isInitialized } from '../lib/config.js';
|
|
5
5
|
import { isGitRepo, getGitInfo, formatGitInfo } from '../lib/git.js';
|
|
6
6
|
import { cmdPlan } from './plan.js';
|
|
7
|
+
import { scanProject, scanResultToAnswers } from '../lib/scanner.js';
|
|
8
|
+
import { savePlanDocuments, initPlanProgress, savePlanProgress } from '../lib/plan.js';
|
|
9
|
+
import { copyToClipboard } from '../lib/clipboard.js';
|
|
7
10
|
export async function cmdInit(path, options) {
|
|
8
11
|
p.intro('pmpt init');
|
|
9
12
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
@@ -12,7 +15,16 @@ export async function cmdInit(path, options) {
|
|
|
12
15
|
process.exit(1);
|
|
13
16
|
}
|
|
14
17
|
if (isInitialized(projectPath)) {
|
|
15
|
-
p.
|
|
18
|
+
p.log.warn('Project already initialized.');
|
|
19
|
+
p.log.message('');
|
|
20
|
+
p.log.info('Available commands:');
|
|
21
|
+
p.log.message(' pmpt plan — Generate or view AI prompt');
|
|
22
|
+
p.log.message(' pmpt save — Save a snapshot');
|
|
23
|
+
p.log.message(' pmpt watch — Auto-save on file changes');
|
|
24
|
+
p.log.message(' pmpt history — View version history');
|
|
25
|
+
p.log.message('');
|
|
26
|
+
p.log.message('To reinitialize, remove .pmpt/ and run `pmpt init` again.');
|
|
27
|
+
p.outro('');
|
|
16
28
|
process.exit(0);
|
|
17
29
|
}
|
|
18
30
|
// Detect Git repository
|
|
@@ -102,17 +114,137 @@ export async function cmdInit(path, options) {
|
|
|
102
114
|
notes.push(' pmpt watch # Auto-save on changes');
|
|
103
115
|
notes.push(' pmpt history # View versions');
|
|
104
116
|
p.note(notes.join('\n'), 'Project Info');
|
|
105
|
-
//
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
// Scan for existing project
|
|
118
|
+
const scanResult = scanProject(projectPath);
|
|
119
|
+
if (scanResult.isExistingProject) {
|
|
120
|
+
// Show scan summary
|
|
121
|
+
const scanNotes = [];
|
|
122
|
+
if (scanResult.packageInfo) {
|
|
123
|
+
scanNotes.push(`Package: ${scanResult.packageInfo.name}`);
|
|
124
|
+
if (scanResult.packageInfo.description) {
|
|
125
|
+
scanNotes.push(`Description: ${scanResult.packageInfo.description}`);
|
|
126
|
+
}
|
|
127
|
+
scanNotes.push(`Dependencies: ${scanResult.packageInfo.dependencies.length} production, ${scanResult.packageInfo.devDependencies.length} dev`);
|
|
128
|
+
}
|
|
129
|
+
if (scanResult.detectedFramework) {
|
|
130
|
+
scanNotes.push(`Framework: ${scanResult.detectedFramework}`);
|
|
131
|
+
}
|
|
132
|
+
if (scanResult.directoryStructure.length > 0) {
|
|
133
|
+
scanNotes.push(`Structure: ${scanResult.directoryStructure.map((d) => d + '/').join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
if (scanResult.gitSummary) {
|
|
136
|
+
const gs = scanResult.gitSummary;
|
|
137
|
+
let gitLine = `Git: ${gs.totalCommits} commits`;
|
|
138
|
+
if (gs.firstCommitDate) {
|
|
139
|
+
gitLine += ` since ${gs.firstCommitDate.split('T')[0]}`;
|
|
140
|
+
}
|
|
141
|
+
gitLine += `, ${gs.contributors} contributor(s)`;
|
|
142
|
+
scanNotes.push(gitLine);
|
|
143
|
+
}
|
|
144
|
+
p.note(scanNotes.join('\n'), 'Existing Project Detected');
|
|
145
|
+
const scanChoice = await p.select({
|
|
146
|
+
message: 'Auto-generate plan from detected project files?',
|
|
147
|
+
options: [
|
|
148
|
+
{ value: 'auto', label: 'Auto-generate plan', hint: 'Recommended — instant AI prompt from project analysis' },
|
|
149
|
+
{ value: 'manual', label: 'Manual planning', hint: '5 questions interactive flow' },
|
|
150
|
+
{ value: 'skip', label: 'Skip for now' },
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
if (p.isCancel(scanChoice)) {
|
|
154
|
+
p.cancel('Cancelled');
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
if (scanChoice === 'auto') {
|
|
158
|
+
// Ask for project description
|
|
159
|
+
const defaultDesc = scanResult.readmeDescription
|
|
160
|
+
|| scanResult.packageInfo?.description
|
|
161
|
+
|| '';
|
|
162
|
+
const userDesc = await p.text({
|
|
163
|
+
message: 'Briefly describe what this project does:',
|
|
164
|
+
defaultValue: defaultDesc || undefined,
|
|
165
|
+
placeholder: defaultDesc || 'e.g., A web app for sharing AI project histories',
|
|
166
|
+
});
|
|
167
|
+
if (p.isCancel(userDesc)) {
|
|
168
|
+
p.cancel('Cancelled');
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
const s2 = p.spinner();
|
|
172
|
+
s2.start('Scanning project and generating plan...');
|
|
173
|
+
const answers = scanResultToAnswers(scanResult, userDesc);
|
|
174
|
+
const { planPath, promptPath } = savePlanDocuments(projectPath, answers);
|
|
175
|
+
const progress = initPlanProgress(projectPath);
|
|
176
|
+
progress.completed = true;
|
|
177
|
+
progress.answers = answers;
|
|
178
|
+
savePlanProgress(projectPath, progress);
|
|
179
|
+
s2.stop('Plan generated!');
|
|
180
|
+
p.log.message('');
|
|
181
|
+
p.log.success('Two documents have been created:');
|
|
182
|
+
p.log.message('');
|
|
183
|
+
const docExplanation = [
|
|
184
|
+
`1. plan.md — Your product overview`,
|
|
185
|
+
` Location: ${planPath}`,
|
|
186
|
+
'',
|
|
187
|
+
`2. pmpt.md — AI prompt (THE IMPORTANT ONE!)`,
|
|
188
|
+
` Copy this to Claude/ChatGPT/Codex`,
|
|
189
|
+
` Location: ${promptPath}`,
|
|
190
|
+
];
|
|
191
|
+
p.note(docExplanation.join('\n'), 'Auto-generated from project scan');
|
|
192
|
+
// Copy to clipboard
|
|
193
|
+
const content = readFileSync(promptPath, 'utf-8');
|
|
194
|
+
const copied = copyToClipboard(content);
|
|
195
|
+
if (copied) {
|
|
196
|
+
p.log.message('');
|
|
197
|
+
p.log.success('AI prompt copied to clipboard!');
|
|
198
|
+
p.log.message('');
|
|
199
|
+
const banner = [
|
|
200
|
+
'',
|
|
201
|
+
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
202
|
+
'┃ ┃',
|
|
203
|
+
'┃ 📋 NEXT STEP ┃',
|
|
204
|
+
'┃ ┃',
|
|
205
|
+
'┃ Open your AI coding tool and press: ┃',
|
|
206
|
+
'┃ ┃',
|
|
207
|
+
'┃ ⌘ + V (Mac) ┃',
|
|
208
|
+
'┃ Ctrl + V (Windows/Linux) ┃',
|
|
209
|
+
'┃ ┃',
|
|
210
|
+
'┃ Your project context is ready! 🚀 ┃',
|
|
211
|
+
'┃ ┃',
|
|
212
|
+
'┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
|
|
213
|
+
'',
|
|
214
|
+
];
|
|
215
|
+
console.log(banner.join('\n'));
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
p.log.warn('Could not copy to clipboard.');
|
|
219
|
+
p.log.info(`Read it at: ${promptPath}`);
|
|
220
|
+
}
|
|
221
|
+
p.log.info('Tips:');
|
|
222
|
+
p.log.message(' pmpt plan — View or edit your AI prompt');
|
|
223
|
+
p.log.message(' pmpt save — Save a snapshot anytime');
|
|
224
|
+
p.log.message(' pmpt watch — Auto-save on file changes');
|
|
225
|
+
p.outro('Ready to go!');
|
|
226
|
+
}
|
|
227
|
+
else if (scanChoice === 'manual') {
|
|
228
|
+
p.log.message('');
|
|
229
|
+
await cmdPlan(projectPath);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
233
|
+
}
|
|
113
234
|
}
|
|
114
235
|
else {
|
|
115
|
-
|
|
236
|
+
// New/empty project — original flow
|
|
237
|
+
const startPlan = await p.confirm({
|
|
238
|
+
message: 'Start planning? (Generate AI prompt with 5 quick questions)',
|
|
239
|
+
initialValue: true,
|
|
240
|
+
});
|
|
241
|
+
if (!p.isCancel(startPlan) && startPlan) {
|
|
242
|
+
p.log.message('');
|
|
243
|
+
await cmdPlan(projectPath);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
247
|
+
}
|
|
116
248
|
}
|
|
117
249
|
}
|
|
118
250
|
catch (error) {
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,35 +1,10 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
|
-
import { execSync } from 'child_process';
|
|
5
4
|
import { isInitialized } from '../lib/config.js';
|
|
5
|
+
import { copyToClipboard } from '../lib/clipboard.js';
|
|
6
6
|
import { cmdWatch } from './watch.js';
|
|
7
7
|
import { PLAN_QUESTIONS, getPlanProgress, initPlanProgress, savePlanProgress, savePlanDocuments, } from '../lib/plan.js';
|
|
8
|
-
// Cross-platform clipboard copy
|
|
9
|
-
function copyToClipboard(text) {
|
|
10
|
-
try {
|
|
11
|
-
const platform = process.platform;
|
|
12
|
-
if (platform === 'darwin') {
|
|
13
|
-
execSync('pbcopy', { input: text });
|
|
14
|
-
}
|
|
15
|
-
else if (platform === 'win32') {
|
|
16
|
-
execSync('clip', { input: text });
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
// Linux - try xclip or xsel
|
|
20
|
-
try {
|
|
21
|
-
execSync('xclip -selection clipboard', { input: text });
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
execSync('xsel --clipboard --input', { input: text });
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
8
|
export async function cmdPlan(path, options) {
|
|
34
9
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
35
10
|
// Check initialization
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
export function copyToClipboard(text) {
|
|
3
|
+
try {
|
|
4
|
+
const platform = process.platform;
|
|
5
|
+
if (platform === 'darwin') {
|
|
6
|
+
execSync('pbcopy', { input: text });
|
|
7
|
+
}
|
|
8
|
+
else if (platform === 'win32') {
|
|
9
|
+
execSync('clip', { input: text });
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
// Linux - try xclip or xsel
|
|
13
|
+
try {
|
|
14
|
+
execSync('xclip -selection clipboard', { input: text });
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
execSync('xsel --clipboard --input', { input: text });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { isGitRepo } from './git.js';
|
|
5
|
+
// ── Scanner Functions ──
|
|
6
|
+
function git(path, args) {
|
|
7
|
+
try {
|
|
8
|
+
return execSync(`git ${args}`, {
|
|
9
|
+
cwd: path,
|
|
10
|
+
encoding: 'utf-8',
|
|
11
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
12
|
+
}).trim();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function scanPackageJson(projectPath) {
|
|
19
|
+
const pkgPath = join(projectPath, 'package.json');
|
|
20
|
+
if (!existsSync(pkgPath))
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
const raw = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
24
|
+
return {
|
|
25
|
+
name: raw.name || basename(projectPath),
|
|
26
|
+
description: raw.description || null,
|
|
27
|
+
dependencies: Object.keys(raw.dependencies || {}),
|
|
28
|
+
devDependencies: Object.keys(raw.devDependencies || {}),
|
|
29
|
+
scripts: Object.keys(raw.scripts || {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function scanReadme(projectPath) {
|
|
37
|
+
const candidates = ['README.md', 'readme.md', 'Readme.md'];
|
|
38
|
+
let content = null;
|
|
39
|
+
for (const name of candidates) {
|
|
40
|
+
const filePath = join(projectPath, name);
|
|
41
|
+
if (existsSync(filePath)) {
|
|
42
|
+
try {
|
|
43
|
+
content = readFileSync(filePath, 'utf-8');
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!content)
|
|
52
|
+
return null;
|
|
53
|
+
// Split by paragraph and find the first meaningful one
|
|
54
|
+
const paragraphs = content.split(/\n\n+/);
|
|
55
|
+
for (const p of paragraphs) {
|
|
56
|
+
const trimmed = p.trim();
|
|
57
|
+
// Skip headings, badges, images, empty lines, HTML tags
|
|
58
|
+
if (!trimmed)
|
|
59
|
+
continue;
|
|
60
|
+
if (trimmed.startsWith('#'))
|
|
61
|
+
continue;
|
|
62
|
+
if (trimmed.startsWith('[') || trimmed.startsWith('!'))
|
|
63
|
+
continue;
|
|
64
|
+
if (trimmed.startsWith('<'))
|
|
65
|
+
continue;
|
|
66
|
+
if (trimmed.startsWith('```'))
|
|
67
|
+
continue;
|
|
68
|
+
// Found a text paragraph
|
|
69
|
+
const cleaned = trimmed.replace(/\n/g, ' ').trim();
|
|
70
|
+
return cleaned.length > 500 ? cleaned.slice(0, 500) + '...' : cleaned;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const FRAMEWORK_CONFIGS = [
|
|
75
|
+
['astro.config.*', 'Astro'],
|
|
76
|
+
['next.config.*', 'Next.js'],
|
|
77
|
+
['nuxt.config.*', 'Nuxt'],
|
|
78
|
+
['svelte.config.*', 'SvelteKit'],
|
|
79
|
+
['remix.config.*', 'Remix'],
|
|
80
|
+
['gatsby-config.*', 'Gatsby'],
|
|
81
|
+
['angular.json', 'Angular'],
|
|
82
|
+
['vite.config.*', 'Vite'],
|
|
83
|
+
];
|
|
84
|
+
const DEP_FRAMEWORKS = [
|
|
85
|
+
['next', 'Next.js'],
|
|
86
|
+
['nuxt', 'Nuxt'],
|
|
87
|
+
['@angular/core', 'Angular'],
|
|
88
|
+
['svelte', 'Svelte'],
|
|
89
|
+
['vue', 'Vue'],
|
|
90
|
+
['react', 'React'],
|
|
91
|
+
['express', 'Express'],
|
|
92
|
+
['fastify', 'Fastify'],
|
|
93
|
+
['@nestjs/core', 'NestJS'],
|
|
94
|
+
['hono', 'Hono'],
|
|
95
|
+
['django', 'Django'],
|
|
96
|
+
['flask', 'Flask'],
|
|
97
|
+
];
|
|
98
|
+
function detectFramework(projectPath, packageInfo) {
|
|
99
|
+
// Check config files first
|
|
100
|
+
for (const [pattern, name] of FRAMEWORK_CONFIGS) {
|
|
101
|
+
if (pattern.includes('*')) {
|
|
102
|
+
const base = pattern.replace('.*', '');
|
|
103
|
+
try {
|
|
104
|
+
const files = readdirSync(projectPath);
|
|
105
|
+
if (files.some((f) => f.startsWith(base)))
|
|
106
|
+
return name;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// ignore
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
if (existsSync(join(projectPath, pattern)))
|
|
114
|
+
return name;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Fall back to dependencies
|
|
118
|
+
if (packageInfo) {
|
|
119
|
+
const allDeps = [...packageInfo.dependencies, ...packageInfo.devDependencies];
|
|
120
|
+
for (const [dep, name] of DEP_FRAMEWORKS) {
|
|
121
|
+
if (allDeps.includes(dep))
|
|
122
|
+
return name;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const MEANINGFUL_DIRS = new Set([
|
|
128
|
+
'src', 'app', 'pages', 'components', 'routes', 'views',
|
|
129
|
+
'lib', 'utils', 'helpers', 'hooks', 'stores', 'store',
|
|
130
|
+
'api', 'server', 'services', 'middleware',
|
|
131
|
+
'public', 'static', 'assets', 'styles', 'css',
|
|
132
|
+
'tests', '__tests__', 'test', 'spec',
|
|
133
|
+
'scripts', 'config', 'database', 'db', 'models',
|
|
134
|
+
'layouts', 'templates',
|
|
135
|
+
]);
|
|
136
|
+
function scanDirectoryStructure(projectPath) {
|
|
137
|
+
try {
|
|
138
|
+
const entries = readdirSync(projectPath);
|
|
139
|
+
return entries
|
|
140
|
+
.filter((name) => {
|
|
141
|
+
if (name.startsWith('.'))
|
|
142
|
+
return false;
|
|
143
|
+
if (name === 'node_modules' || name === 'dist' || name === 'build')
|
|
144
|
+
return false;
|
|
145
|
+
try {
|
|
146
|
+
return statSync(join(projectPath, name)).isDirectory();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
.filter((name) => MEANINGFUL_DIRS.has(name))
|
|
153
|
+
.sort();
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function scanGitHistory(projectPath) {
|
|
160
|
+
if (!isGitRepo(projectPath))
|
|
161
|
+
return null;
|
|
162
|
+
const countStr = git(projectPath, 'rev-list --count HEAD');
|
|
163
|
+
if (!countStr)
|
|
164
|
+
return null;
|
|
165
|
+
const totalCommits = parseInt(countStr, 10);
|
|
166
|
+
if (isNaN(totalCommits) || totalCommits === 0)
|
|
167
|
+
return null;
|
|
168
|
+
const recentRaw = git(projectPath, 'log --oneline -5 --format=%s');
|
|
169
|
+
const recentCommits = recentRaw
|
|
170
|
+
? recentRaw.split('\n').filter(Boolean)
|
|
171
|
+
: [];
|
|
172
|
+
const firstCommitDate = git(projectPath, 'log --reverse --format=%cI -1') || null;
|
|
173
|
+
let contributors = 1;
|
|
174
|
+
const shortlogRaw = git(projectPath, 'shortlog -sn HEAD');
|
|
175
|
+
if (shortlogRaw) {
|
|
176
|
+
contributors = shortlogRaw.split('\n').filter(Boolean).length;
|
|
177
|
+
}
|
|
178
|
+
return { totalCommits, recentCommits, firstCommitDate, contributors };
|
|
179
|
+
}
|
|
180
|
+
// ── Main Functions ──
|
|
181
|
+
export function scanProject(projectPath) {
|
|
182
|
+
const packageInfo = scanPackageJson(projectPath);
|
|
183
|
+
const readmeDescription = scanReadme(projectPath);
|
|
184
|
+
const detectedFramework = detectFramework(projectPath, packageInfo);
|
|
185
|
+
const directoryStructure = scanDirectoryStructure(projectPath);
|
|
186
|
+
const gitSummary = scanGitHistory(projectPath);
|
|
187
|
+
const isExistingProject = packageInfo !== null ||
|
|
188
|
+
readmeDescription !== null ||
|
|
189
|
+
directoryStructure.length > 0 ||
|
|
190
|
+
(gitSummary !== null && gitSummary.totalCommits > 0);
|
|
191
|
+
return {
|
|
192
|
+
isExistingProject,
|
|
193
|
+
packageInfo,
|
|
194
|
+
readmeDescription,
|
|
195
|
+
detectedFramework,
|
|
196
|
+
directoryStructure,
|
|
197
|
+
gitSummary,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
export function scanResultToAnswers(result, userDescription) {
|
|
201
|
+
// projectName
|
|
202
|
+
const projectName = result.packageInfo?.name || basename(process.cwd());
|
|
203
|
+
// productIdea — user's own description
|
|
204
|
+
const productIdea = userDescription;
|
|
205
|
+
// additionalContext — scanned technical info
|
|
206
|
+
const contextParts = [];
|
|
207
|
+
contextParts.push('Existing project with established codebase.');
|
|
208
|
+
if (result.detectedFramework) {
|
|
209
|
+
contextParts.push(`- Framework: ${result.detectedFramework}`);
|
|
210
|
+
}
|
|
211
|
+
if (result.directoryStructure.length > 0) {
|
|
212
|
+
contextParts.push(`- Project structure: ${result.directoryStructure.map((d) => d + '/').join(', ')}`);
|
|
213
|
+
}
|
|
214
|
+
if (result.gitSummary) {
|
|
215
|
+
const gs = result.gitSummary;
|
|
216
|
+
let gitLine = `- Git history: ${gs.totalCommits} commits`;
|
|
217
|
+
if (gs.firstCommitDate) {
|
|
218
|
+
gitLine += ` since ${gs.firstCommitDate.split('T')[0]}`;
|
|
219
|
+
}
|
|
220
|
+
gitLine += `, ${gs.contributors} contributor(s)`;
|
|
221
|
+
contextParts.push(gitLine);
|
|
222
|
+
if (gs.recentCommits.length > 0) {
|
|
223
|
+
contextParts.push(`- Recent work: ${gs.recentCommits.map((c) => `"${c}"`).join(', ')}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const additionalContext = contextParts.join('\n');
|
|
227
|
+
// coreFeatures — from scripts and directory structure
|
|
228
|
+
const featureParts = [];
|
|
229
|
+
if (result.packageInfo?.scripts.length) {
|
|
230
|
+
for (const script of result.packageInfo.scripts) {
|
|
231
|
+
featureParts.push(`${script} (npm script)`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (result.directoryStructure.length > 0) {
|
|
235
|
+
const dirHints = {
|
|
236
|
+
pages: 'Page routing',
|
|
237
|
+
routes: 'Route handling',
|
|
238
|
+
components: 'Component library',
|
|
239
|
+
api: 'API layer',
|
|
240
|
+
server: 'Server-side logic',
|
|
241
|
+
tests: 'Test suite',
|
|
242
|
+
__tests__: 'Test suite',
|
|
243
|
+
test: 'Test suite',
|
|
244
|
+
models: 'Data models',
|
|
245
|
+
database: 'Database layer',
|
|
246
|
+
db: 'Database layer',
|
|
247
|
+
middleware: 'Middleware',
|
|
248
|
+
layouts: 'Layout system',
|
|
249
|
+
};
|
|
250
|
+
for (const dir of result.directoryStructure) {
|
|
251
|
+
if (dirHints[dir]) {
|
|
252
|
+
featureParts.push(`${dirHints[dir]} (${dir}/ directory)`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const coreFeatures = featureParts.length > 0
|
|
257
|
+
? featureParts.join('; ')
|
|
258
|
+
: 'Existing project features';
|
|
259
|
+
// techStack — from framework + dependencies
|
|
260
|
+
const stackParts = [];
|
|
261
|
+
if (result.detectedFramework) {
|
|
262
|
+
stackParts.push(result.detectedFramework);
|
|
263
|
+
}
|
|
264
|
+
if (result.packageInfo) {
|
|
265
|
+
// Add top production dependencies (exclude framework already listed)
|
|
266
|
+
const frameworkLower = result.detectedFramework?.toLowerCase() || '';
|
|
267
|
+
const topDeps = result.packageInfo.dependencies
|
|
268
|
+
.filter((d) => !d.startsWith('@types/') && !d.toLowerCase().includes(frameworkLower))
|
|
269
|
+
.slice(0, 8);
|
|
270
|
+
stackParts.push(...topDeps);
|
|
271
|
+
// Add notable dev deps
|
|
272
|
+
const notableDevDeps = ['typescript', 'eslint', 'prettier', 'jest', 'vitest', 'mocha', 'tailwindcss'];
|
|
273
|
+
for (const d of result.packageInfo.devDependencies) {
|
|
274
|
+
if (notableDevDeps.includes(d) && !stackParts.includes(d)) {
|
|
275
|
+
stackParts.push(d);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const techStack = stackParts.length > 0
|
|
280
|
+
? stackParts.join(', ')
|
|
281
|
+
: '';
|
|
282
|
+
return {
|
|
283
|
+
projectName,
|
|
284
|
+
productIdea,
|
|
285
|
+
additionalContext,
|
|
286
|
+
coreFeatures,
|
|
287
|
+
techStack,
|
|
288
|
+
};
|
|
289
|
+
}
|