pmpt-cli 1.0.0 → 1.1.1
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/README.md +42 -33
- package/dist/commands/hist.js +3 -3
- package/dist/commands/init.js +18 -25
- package/dist/commands/plan.js +123 -36
- package/dist/commands/save.js +5 -5
- package/dist/commands/squash.js +1 -1
- package/dist/commands/status.js +2 -2
- package/dist/commands/watch.js +5 -5
- package/dist/index.js +4 -4
- package/dist/lib/config.js +14 -9
- package/dist/lib/history.js +14 -15
- package/dist/lib/plan.js +35 -44
- package/dist/lib/watcher.js +7 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,57 +1,66 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pmpt-cli
|
|
2
2
|
|
|
3
|
-
CLI tool for
|
|
3
|
+
CLI tool for recording and sharing your AI-driven product development journey.
|
|
4
|
+
|
|
5
|
+
**Website**: [pmptwiki.com](https://pmptwiki.com)
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm install -g
|
|
10
|
+
npm install -g pmpt-cli
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
### Create a new document
|
|
13
|
+
## Quick Start
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
# Initialize project
|
|
17
|
+
pmpt init
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Start product planning (6 questions → AI prompt)
|
|
20
|
+
pmpt plan
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
# Save snapshot manually
|
|
23
|
+
pmpt save
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Checks frontmatter schema, required fields, and minimum content length before submission.
|
|
25
|
+
# Or auto-detect file changes
|
|
26
|
+
pmpt watch
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
# View version history
|
|
29
|
+
pmpt history
|
|
30
|
+
pmpt history --compact # Hide minor changes
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
# Squash versions
|
|
33
|
+
pmpt squash v2 v5 # Merge v2-v5 into v2
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
## Folder Structure
|
|
36
37
|
|
|
37
|
-
### Logout
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
promptwiki logout
|
|
41
38
|
```
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
.promptwiki/
|
|
40
|
+
├── config.json # Config file
|
|
41
|
+
├── pmpt/ # Working folder (MD files)
|
|
42
|
+
└── .history/ # Version history
|
|
43
|
+
```
|
|
44
44
|
|
|
45
45
|
## Workflow
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
1. `pmpt init` → Initialize project
|
|
48
|
+
2. `pmpt plan` → Answer 6 questions → `pmpt.md` generated
|
|
49
|
+
3. Copy `pmpt.md` to AI → Build with AI conversation
|
|
50
|
+
4. `pmpt save` or `pmpt watch` → Save progress
|
|
51
|
+
5. `pmpt submit` → Share to archive (coming soon)
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
| Command | Description |
|
|
56
|
+
|---------|-------------|
|
|
57
|
+
| `pmpt init` | Initialize project folder |
|
|
58
|
+
| `pmpt plan` | Quick product planning with 6 questions |
|
|
59
|
+
| `pmpt save` | Save current state as snapshot |
|
|
60
|
+
| `pmpt watch` | Auto-detect file changes |
|
|
61
|
+
| `pmpt status` | Check project status |
|
|
62
|
+
| `pmpt history` | View version history |
|
|
63
|
+
| `pmpt squash` | Merge multiple versions |
|
|
55
64
|
|
|
56
65
|
## License
|
|
57
66
|
|
package/dist/commands/hist.js
CHANGED
|
@@ -37,7 +37,7 @@ export function cmdHistory(path, options) {
|
|
|
37
37
|
}
|
|
38
38
|
const snapshots = getAllSnapshots(projectPath);
|
|
39
39
|
if (snapshots.length === 0) {
|
|
40
|
-
p.intro('
|
|
40
|
+
p.intro('pmpt history');
|
|
41
41
|
p.log.warn('No snapshots saved yet.');
|
|
42
42
|
p.log.info('Save snapshots with pmpt save or pmpt watch.');
|
|
43
43
|
p.outro('');
|
|
@@ -60,8 +60,8 @@ export function cmdHistory(path, options) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
const title = options?.compact
|
|
63
|
-
? `
|
|
64
|
-
: `
|
|
63
|
+
? `pmpt history (${displaySnapshots.length} shown, ${hiddenVersions.length} hidden)`
|
|
64
|
+
: `pmpt history (${snapshots.length} total)`;
|
|
65
65
|
p.intro(title);
|
|
66
66
|
for (const snapshot of displaySnapshots) {
|
|
67
67
|
const dateStr = new Date(snapshot.timestamp).toLocaleString(undefined, {
|
package/dist/commands/init.js
CHANGED
|
@@ -5,7 +5,7 @@ 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
7
|
export async function cmdInit(path, options) {
|
|
8
|
-
p.intro('
|
|
8
|
+
p.intro('pmpt init');
|
|
9
9
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
10
10
|
if (!existsSync(projectPath)) {
|
|
11
11
|
p.outro(`Path does not exist: ${projectPath}`);
|
|
@@ -27,14 +27,12 @@ export async function cmdInit(path, options) {
|
|
|
27
27
|
}
|
|
28
28
|
// Build confirmation message
|
|
29
29
|
const confirmMessage = [
|
|
30
|
-
`
|
|
30
|
+
`Initialize pmpt in this folder?`,
|
|
31
31
|
` Path: ${projectPath}`,
|
|
32
|
+
` Docs: .pmpt/docs/`,
|
|
32
33
|
];
|
|
33
34
|
if (isGit && gitInfo) {
|
|
34
35
|
confirmMessage.push(` Git: ${formatGitInfo(gitInfo)}`);
|
|
35
|
-
if (repoUrl) {
|
|
36
|
-
confirmMessage.push(` Repository: ${repoUrl}`);
|
|
37
|
-
}
|
|
38
36
|
}
|
|
39
37
|
const confirm = await p.confirm({
|
|
40
38
|
message: confirmMessage.join('\n'),
|
|
@@ -46,17 +44,11 @@ export async function cmdInit(path, options) {
|
|
|
46
44
|
}
|
|
47
45
|
// If Git repo but no repoUrl, suggest connecting
|
|
48
46
|
if (isGit && !repoUrl) {
|
|
49
|
-
p.log.info(`Tip: Connect a GitHub repo with --repo for more features!`);
|
|
50
|
-
p.log.message(` • Auto-record commit hash for each version`);
|
|
51
|
-
p.log.message(` • Create PRs directly with pmpt submit`);
|
|
52
|
-
p.log.message(` • Others can reproduce exact code states`);
|
|
53
|
-
p.log.message('');
|
|
54
47
|
const repoChoice = await p.select({
|
|
55
|
-
message: 'Connect GitHub repository?',
|
|
48
|
+
message: 'Connect GitHub repository? (optional)',
|
|
56
49
|
options: [
|
|
50
|
+
{ value: 'skip', label: 'Skip for now', hint: 'Recommended' },
|
|
57
51
|
{ value: 'now', label: 'Connect now', hint: 'Enter repository URL' },
|
|
58
|
-
{ value: 'later', label: 'Connect later', hint: 'Re-run with pmpt init --repo <url>' },
|
|
59
|
-
{ value: 'skip', label: 'Skip', hint: 'Use Git tracking only' },
|
|
60
52
|
],
|
|
61
53
|
});
|
|
62
54
|
if (p.isCancel(repoChoice)) {
|
|
@@ -88,30 +80,31 @@ export async function cmdInit(path, options) {
|
|
|
88
80
|
trackGit: isGit,
|
|
89
81
|
});
|
|
90
82
|
s.stop('Initialized');
|
|
83
|
+
// Build folder structure display
|
|
91
84
|
const notes = [
|
|
92
85
|
`Path: ${config.projectPath}`,
|
|
93
86
|
'',
|
|
94
87
|
'Folder structure:',
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
` .pmpt/`,
|
|
89
|
+
` ├── config.json Config`,
|
|
90
|
+
` ├── docs/ Your docs`,
|
|
91
|
+
` └── .history/ Snapshots`,
|
|
99
92
|
];
|
|
100
93
|
if (config.repo) {
|
|
101
|
-
notes.push('', `
|
|
94
|
+
notes.push('', `Repository: ${config.repo}`);
|
|
102
95
|
}
|
|
103
96
|
if (config.trackGit) {
|
|
104
97
|
notes.push(`Git tracking: Enabled`);
|
|
105
98
|
}
|
|
106
|
-
notes.push('', '
|
|
107
|
-
notes.push(' pmpt plan #
|
|
108
|
-
notes.push(' pmpt save # Save
|
|
109
|
-
notes.push(' pmpt watch # Auto-
|
|
110
|
-
notes.push(' pmpt history # View
|
|
99
|
+
notes.push('', 'Commands:');
|
|
100
|
+
notes.push(' pmpt plan # Generate AI prompt');
|
|
101
|
+
notes.push(' pmpt save # Save snapshot');
|
|
102
|
+
notes.push(' pmpt watch # Auto-save on changes');
|
|
103
|
+
notes.push(' pmpt history # View versions');
|
|
111
104
|
p.note(notes.join('\n'), 'Project Info');
|
|
112
105
|
// Ask to start plan mode
|
|
113
106
|
const startPlan = await p.confirm({
|
|
114
|
-
message: 'Start
|
|
107
|
+
message: 'Start planning? (Generate AI prompt with 5 quick questions)',
|
|
115
108
|
initialValue: true,
|
|
116
109
|
});
|
|
117
110
|
if (!p.isCancel(startPlan) && startPlan) {
|
|
@@ -119,7 +112,7 @@ export async function cmdInit(path, options) {
|
|
|
119
112
|
await cmdPlan(projectPath);
|
|
120
113
|
}
|
|
121
114
|
else {
|
|
122
|
-
p.outro('
|
|
115
|
+
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
118
|
catch (error) {
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
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';
|
|
4
5
|
import { isInitialized } from '../lib/config.js';
|
|
5
6
|
import { cmdWatch } from './watch.js';
|
|
6
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
|
+
}
|
|
7
33
|
export async function cmdPlan(path, options) {
|
|
8
34
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
9
35
|
// Check initialization
|
|
10
36
|
if (!isInitialized(projectPath)) {
|
|
11
|
-
p.intro('
|
|
37
|
+
p.intro('pmpt plan');
|
|
12
38
|
p.log.error('Project not initialized.');
|
|
13
39
|
p.log.info('Run `pmpt init` first to initialize the project.');
|
|
14
40
|
p.outro('');
|
|
@@ -30,12 +56,13 @@ export async function cmdPlan(path, options) {
|
|
|
30
56
|
let progress = getPlanProgress(projectPath);
|
|
31
57
|
// If already completed
|
|
32
58
|
if (progress?.completed) {
|
|
33
|
-
p.intro('
|
|
59
|
+
p.intro('pmpt plan');
|
|
34
60
|
p.log.success('Plan already completed.');
|
|
35
61
|
const action = await p.select({
|
|
36
62
|
message: 'What would you like to do?',
|
|
37
63
|
options: [
|
|
38
|
-
{ value: '
|
|
64
|
+
{ value: 'copy', label: 'Copy AI prompt to clipboard', hint: 'Ready for Ctrl+V' },
|
|
65
|
+
{ value: 'view', label: 'View AI prompt', hint: 'Display in terminal' },
|
|
39
66
|
{ value: 'restart', label: 'Restart plan', hint: 'Start fresh' },
|
|
40
67
|
{ value: 'watch', label: 'Start file watching', hint: 'pmpt watch' },
|
|
41
68
|
{ value: 'exit', label: 'Exit' },
|
|
@@ -45,8 +72,7 @@ export async function cmdPlan(path, options) {
|
|
|
45
72
|
p.outro('See you next time!');
|
|
46
73
|
process.exit(0);
|
|
47
74
|
}
|
|
48
|
-
if (action === 'view') {
|
|
49
|
-
// Read pmpt.md from pmpt folder
|
|
75
|
+
if (action === 'copy' || action === 'view') {
|
|
50
76
|
const { getPmptDir } = await import('../lib/config.js');
|
|
51
77
|
const { existsSync } = await import('fs');
|
|
52
78
|
const { join } = await import('path');
|
|
@@ -55,12 +81,44 @@ export async function cmdPlan(path, options) {
|
|
|
55
81
|
try {
|
|
56
82
|
if (existsSync(promptPath)) {
|
|
57
83
|
const content = readFileSync(promptPath, 'utf-8');
|
|
84
|
+
if (action === 'copy') {
|
|
85
|
+
const copied = copyToClipboard(content);
|
|
86
|
+
if (copied) {
|
|
87
|
+
p.log.success('AI prompt copied to clipboard!');
|
|
88
|
+
p.log.message('');
|
|
89
|
+
// Eye-catching next step banner
|
|
90
|
+
const banner = [
|
|
91
|
+
'',
|
|
92
|
+
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
93
|
+
'┃ ┃',
|
|
94
|
+
'┃ 📋 NEXT STEP ┃',
|
|
95
|
+
'┃ ┃',
|
|
96
|
+
'┃ Open your AI coding tool and press: ┃',
|
|
97
|
+
'┃ ┃',
|
|
98
|
+
'┃ ⌘ + V (Mac) ┃',
|
|
99
|
+
'┃ Ctrl + V (Windows/Linux) ┃',
|
|
100
|
+
'┃ ┃',
|
|
101
|
+
'┃ Your product journey starts now! 🚀 ┃',
|
|
102
|
+
'┃ ┃',
|
|
103
|
+
'┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
|
|
104
|
+
'',
|
|
105
|
+
];
|
|
106
|
+
console.log(banner.join('\n'));
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
p.log.warn('Could not copy to clipboard. Showing content instead:');
|
|
110
|
+
p.log.message('');
|
|
111
|
+
console.log(content);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
p.log.message('');
|
|
116
|
+
p.log.info('=== AI Prompt ===');
|
|
117
|
+
p.log.message('');
|
|
118
|
+
console.log(content);
|
|
119
|
+
}
|
|
58
120
|
p.log.message('');
|
|
59
|
-
p.log.info(
|
|
60
|
-
p.log.message('');
|
|
61
|
-
console.log(content);
|
|
62
|
-
p.log.message('');
|
|
63
|
-
p.log.info(`File location: ${promptPath}`);
|
|
121
|
+
p.log.info(`File: ${promptPath}`);
|
|
64
122
|
}
|
|
65
123
|
else {
|
|
66
124
|
p.log.error('AI prompt file not found.');
|
|
@@ -85,8 +143,8 @@ export async function cmdPlan(path, options) {
|
|
|
85
143
|
if (!progress) {
|
|
86
144
|
progress = initPlanProgress(projectPath);
|
|
87
145
|
}
|
|
88
|
-
p.intro('
|
|
89
|
-
p.log.info(
|
|
146
|
+
p.intro('pmpt plan — Your Product Journey Starts Here!');
|
|
147
|
+
p.log.info(`Answer ${PLAN_QUESTIONS.length} quick questions to generate your AI prompt.`);
|
|
90
148
|
p.log.message('You can answer in any language you prefer.');
|
|
91
149
|
p.log.message('');
|
|
92
150
|
const answers = {};
|
|
@@ -109,42 +167,67 @@ export async function cmdPlan(path, options) {
|
|
|
109
167
|
}
|
|
110
168
|
// Generate documents
|
|
111
169
|
const s = p.spinner();
|
|
112
|
-
s.start('Generating
|
|
170
|
+
s.start('Generating your AI prompt...');
|
|
113
171
|
const { planPath, promptPath } = savePlanDocuments(projectPath, answers);
|
|
114
172
|
// Update progress
|
|
115
173
|
progress.completed = true;
|
|
116
174
|
progress.answers = answers;
|
|
117
175
|
savePlanProgress(projectPath, progress);
|
|
118
176
|
s.stop('Done!');
|
|
119
|
-
// Show
|
|
120
|
-
p.log.message('');
|
|
121
|
-
p.log.success('Plan completed!');
|
|
177
|
+
// Show document explanation
|
|
122
178
|
p.log.message('');
|
|
123
|
-
p.log.
|
|
124
|
-
p.log.info(`AI prompt: ${promptPath}`);
|
|
179
|
+
p.log.success('Two documents have been created:');
|
|
125
180
|
p.log.message('');
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
181
|
+
const docExplanation = [
|
|
182
|
+
`1. plan.md — Your product overview`,
|
|
183
|
+
` • Features checklist to track progress`,
|
|
184
|
+
` • Reference for you`,
|
|
185
|
+
` Location: ${planPath}`,
|
|
186
|
+
'',
|
|
187
|
+
`2. pmpt.md — AI prompt (THE IMPORTANT ONE!)`,
|
|
188
|
+
` • Copy this to Claude/ChatGPT/Codex`,
|
|
189
|
+
` • AI will help you build step by step`,
|
|
190
|
+
` • AI will update this doc as you progress`,
|
|
191
|
+
` Location: ${promptPath}`,
|
|
192
|
+
];
|
|
193
|
+
p.note(docExplanation.join('\n'), 'What are these files?');
|
|
194
|
+
// Copy to clipboard
|
|
195
|
+
const content = readFileSync(promptPath, 'utf-8');
|
|
196
|
+
const copied = copyToClipboard(content);
|
|
197
|
+
if (copied) {
|
|
198
|
+
p.log.message('');
|
|
199
|
+
p.log.success('AI prompt copied to clipboard!');
|
|
200
|
+
p.log.message('');
|
|
201
|
+
// Eye-catching next step banner
|
|
202
|
+
const banner = [
|
|
203
|
+
'',
|
|
204
|
+
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
205
|
+
'┃ ┃',
|
|
206
|
+
'┃ 📋 NEXT STEP ┃',
|
|
207
|
+
'┃ ┃',
|
|
208
|
+
'┃ Open your AI coding tool and press: ┃',
|
|
209
|
+
'┃ ┃',
|
|
210
|
+
'┃ ⌘ + V (Mac) ┃',
|
|
211
|
+
'┃ Ctrl + V (Windows/Linux) ┃',
|
|
212
|
+
'┃ ┃',
|
|
213
|
+
'┃ Your product journey starts now! 🚀 ┃',
|
|
214
|
+
'┃ ┃',
|
|
215
|
+
'┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
|
|
216
|
+
'',
|
|
217
|
+
];
|
|
218
|
+
console.log(banner.join('\n'));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// Fallback: show prompt
|
|
133
222
|
p.log.message('');
|
|
134
|
-
p.log.info('=== AI Prompt (copy
|
|
223
|
+
p.log.info('=== AI Prompt (copy this to AI) ===');
|
|
135
224
|
p.log.message('');
|
|
136
225
|
console.log(content);
|
|
137
226
|
p.log.message('');
|
|
138
227
|
}
|
|
139
|
-
//
|
|
140
|
-
p.log.message('');
|
|
141
|
-
p.log.step('Next steps:');
|
|
142
|
-
p.log.message('1. Copy the AI prompt above and paste to Claude/ChatGPT');
|
|
143
|
-
p.log.message('2. Build your product with AI');
|
|
144
|
-
p.log.message('3. Save snapshots with pmpt save (or pmpt watch for auto-save)');
|
|
145
|
-
p.log.message('');
|
|
228
|
+
// Ask about watch mode
|
|
146
229
|
const startWatch = await p.confirm({
|
|
147
|
-
message: 'Start file watching? (
|
|
230
|
+
message: 'Start file watching? (auto-save versions as you work)',
|
|
148
231
|
initialValue: false,
|
|
149
232
|
});
|
|
150
233
|
if (!p.isCancel(startWatch) && startWatch) {
|
|
@@ -152,7 +235,11 @@ export async function cmdPlan(path, options) {
|
|
|
152
235
|
await cmdWatch(projectPath);
|
|
153
236
|
}
|
|
154
237
|
else {
|
|
155
|
-
p.log.
|
|
156
|
-
p.
|
|
238
|
+
p.log.message('');
|
|
239
|
+
p.log.info('Tips:');
|
|
240
|
+
p.log.message(' pmpt save — Save a snapshot anytime');
|
|
241
|
+
p.log.message(' pmpt watch — Auto-save on file changes');
|
|
242
|
+
p.log.message(' pmpt history — View version history');
|
|
243
|
+
p.outro('Good luck with your build!');
|
|
157
244
|
}
|
|
158
245
|
}
|
package/dist/commands/save.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { existsSync, statSync } from 'fs';
|
|
4
|
-
import { isInitialized,
|
|
4
|
+
import { isInitialized, getDocsDir } from '../lib/config.js';
|
|
5
5
|
import { createFullSnapshot, getTrackedFiles } from '../lib/history.js';
|
|
6
6
|
export async function cmdSave(fileOrPath) {
|
|
7
7
|
const projectPath = fileOrPath && existsSync(fileOrPath) && statSync(fileOrPath).isDirectory()
|
|
@@ -11,13 +11,13 @@ export async function cmdSave(fileOrPath) {
|
|
|
11
11
|
p.log.error('Project not initialized. Run `pmpt init` first.');
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
|
-
p.intro('
|
|
15
|
-
const
|
|
14
|
+
p.intro('pmpt save');
|
|
15
|
+
const docsDir = getDocsDir(projectPath);
|
|
16
16
|
const files = getTrackedFiles(projectPath);
|
|
17
17
|
if (files.length === 0) {
|
|
18
18
|
p.log.warn('No files to save.');
|
|
19
|
-
p.log.info(`
|
|
20
|
-
p.log.info('Start with `pmpt plan` or add MD files to the
|
|
19
|
+
p.log.info(`Docs folder: ${docsDir}`);
|
|
20
|
+
p.log.info('Start with `pmpt plan` or add MD files to the docs folder.');
|
|
21
21
|
p.outro('');
|
|
22
22
|
return;
|
|
23
23
|
}
|
package/dist/commands/squash.js
CHANGED
|
@@ -31,7 +31,7 @@ export async function cmdSquash(from, to, path) {
|
|
|
31
31
|
p.log.error(`Need at least 2 versions to squash. Found ${toSquash.length} in range v${fromVersion}-v${toVersion}.`);
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
|
-
p.intro('
|
|
34
|
+
p.intro('pmpt squash');
|
|
35
35
|
p.log.info(`Squashing v${fromVersion} through v${toVersion} (${toSquash.length} versions)`);
|
|
36
36
|
const confirm = await p.confirm({
|
|
37
37
|
message: `This will keep v${fromVersion} and delete v${fromVersion + 1} through v${toVersion}. Continue?`,
|
package/dist/commands/status.js
CHANGED
|
@@ -11,7 +11,7 @@ export function cmdStatus(path) {
|
|
|
11
11
|
const config = loadConfig(projectPath);
|
|
12
12
|
const tracked = getTrackedFiles(projectPath);
|
|
13
13
|
const snapshots = getAllSnapshots(projectPath);
|
|
14
|
-
p.intro('
|
|
14
|
+
p.intro('pmpt status');
|
|
15
15
|
const notes = [
|
|
16
16
|
`Path: ${projectPath}`,
|
|
17
17
|
`Created: ${new Date(config.createdAt).toLocaleString()}`,
|
|
@@ -20,7 +20,7 @@ export function cmdStatus(path) {
|
|
|
20
20
|
notes.push(`Last published: ${new Date(config.lastPublished).toLocaleString()}`);
|
|
21
21
|
}
|
|
22
22
|
notes.push('');
|
|
23
|
-
notes.push(`
|
|
23
|
+
notes.push(`Docs folder: ${config.docsPath}/`);
|
|
24
24
|
notes.push(`Snapshots: ${snapshots.length}`);
|
|
25
25
|
notes.push('');
|
|
26
26
|
notes.push(`Tracked files: ${tracked.length}`);
|
package/dist/commands/watch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { isInitialized,
|
|
3
|
+
import { isInitialized, getDocsDir } from '../lib/config.js';
|
|
4
4
|
import { startWatching } from '../lib/watcher.js';
|
|
5
5
|
export function cmdWatch(path) {
|
|
6
6
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
@@ -8,9 +8,9 @@ export function cmdWatch(path) {
|
|
|
8
8
|
p.log.error('Project not initialized. Run `pmpt init` first.');
|
|
9
9
|
process.exit(1);
|
|
10
10
|
}
|
|
11
|
-
const
|
|
12
|
-
p.intro('
|
|
13
|
-
p.log.info(`Watching: ${
|
|
11
|
+
const docsDir = getDocsDir(projectPath);
|
|
12
|
+
p.intro('pmpt watch — File Watcher');
|
|
13
|
+
p.log.info(`Watching: ${docsDir}`);
|
|
14
14
|
p.log.info('Auto-saving snapshots on MD file changes.');
|
|
15
15
|
p.log.info('Press Ctrl+C to stop.');
|
|
16
16
|
p.log.message('');
|
|
@@ -27,7 +27,7 @@ export function cmdWatch(path) {
|
|
|
27
27
|
p.log.message('');
|
|
28
28
|
p.log.info('Stopping watcher...');
|
|
29
29
|
watcher.close();
|
|
30
|
-
p.outro('
|
|
30
|
+
p.outro('Watcher stopped');
|
|
31
31
|
process.exit(0);
|
|
32
32
|
});
|
|
33
33
|
}
|
package/dist/index.js
CHANGED
|
@@ -19,16 +19,16 @@ program
|
|
|
19
19
|
Examples:
|
|
20
20
|
$ pmpt init Initialize project
|
|
21
21
|
$ pmpt plan Start product planning (6 questions → AI prompt)
|
|
22
|
-
$ pmpt save Save snapshot of
|
|
22
|
+
$ pmpt save Save snapshot of docs folder
|
|
23
23
|
$ pmpt watch Auto-detect file changes
|
|
24
24
|
$ pmpt history View version history
|
|
25
25
|
$ pmpt history --compact Hide minor changes
|
|
26
26
|
$ pmpt squash v2 v5 Merge versions v2-v5 into v2
|
|
27
27
|
|
|
28
28
|
Folder structure:
|
|
29
|
-
.
|
|
29
|
+
.pmpt/
|
|
30
30
|
├── config.json Config file
|
|
31
|
-
├──
|
|
31
|
+
├── docs/ Working folder (MD files)
|
|
32
32
|
└── .history/ Version history
|
|
33
33
|
|
|
34
34
|
Documentation: https://pmptwiki.com
|
|
@@ -45,7 +45,7 @@ program
|
|
|
45
45
|
.action(cmdWatch);
|
|
46
46
|
program
|
|
47
47
|
.command('save [path]')
|
|
48
|
-
.description('Save current state of
|
|
48
|
+
.description('Save current state of docs folder as snapshot')
|
|
49
49
|
.action(cmdSave);
|
|
50
50
|
program
|
|
51
51
|
.command('status [path]')
|
package/dist/lib/config.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
const CONFIG_DIR = '.
|
|
3
|
+
const CONFIG_DIR = '.pmpt';
|
|
4
4
|
const CONFIG_FILE = 'config.json';
|
|
5
|
-
const
|
|
6
|
-
const HISTORY_DIR = '.history';
|
|
5
|
+
const DEFAULT_DOCS_DIR = 'docs';
|
|
6
|
+
const HISTORY_DIR = '.history';
|
|
7
7
|
export function getConfigDir(projectPath) {
|
|
8
8
|
return join(projectPath, CONFIG_DIR);
|
|
9
9
|
}
|
|
10
|
-
export function
|
|
11
|
-
return join(getConfigDir(projectPath),
|
|
10
|
+
export function getDocsDir(projectPath) {
|
|
11
|
+
return join(getConfigDir(projectPath), DEFAULT_DOCS_DIR);
|
|
12
12
|
}
|
|
13
|
+
// Alias for backward compatibility
|
|
14
|
+
export const getPmptDir = getDocsDir;
|
|
13
15
|
export function getHistoryDir(projectPath) {
|
|
14
16
|
return join(getConfigDir(projectPath), HISTORY_DIR);
|
|
15
17
|
}
|
|
@@ -18,15 +20,18 @@ export function isInitialized(projectPath) {
|
|
|
18
20
|
}
|
|
19
21
|
export function initializeProject(projectPath, options) {
|
|
20
22
|
const configDir = getConfigDir(projectPath);
|
|
21
|
-
const pmptDir = getPmptDir(projectPath);
|
|
22
23
|
const historyDir = getHistoryDir(projectPath);
|
|
24
|
+
const docsDir = getDocsDir(projectPath);
|
|
25
|
+
// docsPath is always .pmpt/docs
|
|
26
|
+
const docsPath = join(CONFIG_DIR, DEFAULT_DOCS_DIR);
|
|
27
|
+
// Create directories
|
|
23
28
|
mkdirSync(configDir, { recursive: true });
|
|
24
|
-
mkdirSync(pmptDir, { recursive: true });
|
|
25
29
|
mkdirSync(historyDir, { recursive: true });
|
|
30
|
+
mkdirSync(docsDir, { recursive: true });
|
|
26
31
|
const config = {
|
|
27
32
|
projectPath,
|
|
28
|
-
|
|
29
|
-
ignorePatterns: ['node_modules/**', '.
|
|
33
|
+
docsPath,
|
|
34
|
+
ignorePatterns: ['node_modules/**', '.pmpt/.history/**', 'dist/**'],
|
|
30
35
|
createdAt: new Date().toISOString(),
|
|
31
36
|
repo: options?.repo,
|
|
32
37
|
trackGit: options?.trackGit ?? true,
|
package/dist/lib/history.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'fs';
|
|
2
2
|
import { basename, join, relative } from 'path';
|
|
3
|
-
import { getHistoryDir,
|
|
3
|
+
import { getHistoryDir, getDocsDir, loadConfig } from './config.js';
|
|
4
4
|
import { getGitInfo, isGitRepo } from './git.js';
|
|
5
5
|
import glob from 'fast-glob';
|
|
6
6
|
/**
|
|
7
|
-
* pmpt
|
|
7
|
+
* .pmpt/docs 폴더의 MD 파일을 스냅샷으로 저장
|
|
8
8
|
* .history/v{N}-{timestamp}/ 폴더에 모든 파일 복사
|
|
9
9
|
*/
|
|
10
10
|
export function createFullSnapshot(projectPath) {
|
|
11
11
|
const historyDir = getHistoryDir(projectPath);
|
|
12
|
-
const
|
|
12
|
+
const docsDir = getDocsDir(projectPath);
|
|
13
13
|
mkdirSync(historyDir, { recursive: true });
|
|
14
14
|
// 다음 버전 번호 찾기
|
|
15
15
|
const existing = getAllSnapshots(projectPath);
|
|
@@ -18,12 +18,12 @@ export function createFullSnapshot(projectPath) {
|
|
|
18
18
|
const snapshotName = `v${version}-${timestamp}`;
|
|
19
19
|
const snapshotDir = join(historyDir, snapshotName);
|
|
20
20
|
mkdirSync(snapshotDir, { recursive: true });
|
|
21
|
-
//
|
|
21
|
+
// docs 폴더의 MD 파일 복사
|
|
22
22
|
const files = [];
|
|
23
|
-
if (existsSync(
|
|
24
|
-
const mdFiles = glob.sync('**/*.md', { cwd:
|
|
23
|
+
if (existsSync(docsDir)) {
|
|
24
|
+
const mdFiles = glob.sync('**/*.md', { cwd: docsDir });
|
|
25
25
|
for (const file of mdFiles) {
|
|
26
|
-
const srcPath = join(
|
|
26
|
+
const srcPath = join(docsDir, file);
|
|
27
27
|
const destPath = join(snapshotDir, file);
|
|
28
28
|
// 하위 디렉토리가 있으면 생성
|
|
29
29
|
const destDir = join(snapshotDir, file.split('/').slice(0, -1).join('/'));
|
|
@@ -71,9 +71,9 @@ export function createFullSnapshot(projectPath) {
|
|
|
71
71
|
*/
|
|
72
72
|
export function createSnapshot(projectPath, filePath) {
|
|
73
73
|
const historyDir = getHistoryDir(projectPath);
|
|
74
|
-
const
|
|
75
|
-
const relPath = relative(
|
|
76
|
-
// 파일이
|
|
74
|
+
const docsDir = getDocsDir(projectPath);
|
|
75
|
+
const relPath = relative(docsDir, filePath);
|
|
76
|
+
// 파일이 docs 폴더 외부에 있는 경우
|
|
77
77
|
if (relPath.startsWith('..')) {
|
|
78
78
|
// 프로젝트 루트 기준 상대 경로 사용
|
|
79
79
|
const projectRelPath = relative(projectPath, filePath);
|
|
@@ -83,7 +83,6 @@ export function createSnapshot(projectPath, filePath) {
|
|
|
83
83
|
}
|
|
84
84
|
function createSingleFileSnapshot(projectPath, filePath, relPath) {
|
|
85
85
|
const historyDir = getHistoryDir(projectPath);
|
|
86
|
-
const fileName = basename(filePath, '.md');
|
|
87
86
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
88
87
|
// 해당 파일의 기존 버전 수 확인
|
|
89
88
|
const existing = getFileHistory(projectPath, relPath);
|
|
@@ -223,11 +222,11 @@ export function getAllHistory(projectPath) {
|
|
|
223
222
|
return entries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
224
223
|
}
|
|
225
224
|
/**
|
|
226
|
-
* 추적 중인 파일 목록 (pmpt
|
|
225
|
+
* 추적 중인 파일 목록 (.pmpt/docs 기준)
|
|
227
226
|
*/
|
|
228
227
|
export function getTrackedFiles(projectPath) {
|
|
229
|
-
const
|
|
230
|
-
if (!existsSync(
|
|
228
|
+
const docsDir = getDocsDir(projectPath);
|
|
229
|
+
if (!existsSync(docsDir))
|
|
231
230
|
return [];
|
|
232
|
-
return glob.sync('**/*.md', { cwd:
|
|
231
|
+
return glob.sync('**/*.md', { cwd: docsDir });
|
|
233
232
|
}
|
package/dist/lib/plan.js
CHANGED
|
@@ -3,72 +3,65 @@ import { join } from 'path';
|
|
|
3
3
|
import { getConfigDir, getPmptDir } from './config.js';
|
|
4
4
|
import { createFullSnapshot } from './history.js';
|
|
5
5
|
const PLAN_FILE = 'plan-progress.json';
|
|
6
|
-
//
|
|
6
|
+
// 5 simple questions (answer in any language you prefer)
|
|
7
|
+
// This is the first step of your product journey with AI!
|
|
7
8
|
export const PLAN_QUESTIONS = [
|
|
8
9
|
{
|
|
9
10
|
key: 'projectName',
|
|
10
|
-
question: '
|
|
11
|
+
question: 'What should we call your project?',
|
|
11
12
|
placeholder: 'my-awesome-app',
|
|
12
13
|
required: true,
|
|
13
14
|
},
|
|
14
15
|
{
|
|
15
|
-
key: '
|
|
16
|
-
question:
|
|
17
|
-
placeholder: 'e.g.,
|
|
16
|
+
key: 'productIdea',
|
|
17
|
+
question: "What would you like to build with AI? (Let's start your journey!)",
|
|
18
|
+
placeholder: 'e.g., A Chrome extension that summarizes long articles, A budget tracking app for freelancers',
|
|
18
19
|
multiline: true,
|
|
19
20
|
required: true,
|
|
20
21
|
},
|
|
21
22
|
{
|
|
22
|
-
key: '
|
|
23
|
-
question: '
|
|
24
|
-
placeholder: 'e.g.,
|
|
23
|
+
key: 'additionalContext',
|
|
24
|
+
question: 'Any additional context AI should know? (optional)',
|
|
25
|
+
placeholder: 'e.g., I prefer simple UI, Must work offline, Target audience is students',
|
|
25
26
|
multiline: true,
|
|
26
|
-
required:
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
key: 'targetUser',
|
|
30
|
-
question: 'Who is your target user?',
|
|
31
|
-
placeholder: 'e.g., Startup backend developers',
|
|
32
|
-
required: true,
|
|
27
|
+
required: false,
|
|
33
28
|
},
|
|
34
29
|
{
|
|
35
30
|
key: 'coreFeatures',
|
|
36
|
-
question: '
|
|
37
|
-
placeholder: 'e.g
|
|
31
|
+
question: 'Key features to include? (separate with commas or semicolons)',
|
|
32
|
+
placeholder: 'e.g., User login; Dashboard; Export to PDF; Dark mode',
|
|
38
33
|
multiline: true,
|
|
39
34
|
required: true,
|
|
40
35
|
},
|
|
41
36
|
{
|
|
42
37
|
key: 'techStack',
|
|
43
|
-
question: 'Preferred tech stack? (optional)',
|
|
44
|
-
placeholder: 'e.g., React, Node.js, PostgreSQL',
|
|
38
|
+
question: 'Preferred tech stack? (optional - AI can suggest if unsure)',
|
|
39
|
+
placeholder: 'e.g., React, Node.js, PostgreSQL — or "up to you"',
|
|
45
40
|
multiline: false,
|
|
46
41
|
required: false,
|
|
47
42
|
},
|
|
48
43
|
];
|
|
49
44
|
// Generate AI prompt (language-agnostic template)
|
|
50
45
|
export function generateAIPrompt(answers) {
|
|
46
|
+
// Parse features (support comma, semicolon, or newline separators)
|
|
51
47
|
const features = answers.coreFeatures
|
|
52
|
-
.split(
|
|
48
|
+
.split(/[,;\n]/)
|
|
53
49
|
.map((f) => f.trim())
|
|
54
50
|
.filter((f) => f)
|
|
55
51
|
.map((f) => `- ${f}`)
|
|
56
52
|
.join('\n');
|
|
53
|
+
const contextSection = answers.additionalContext
|
|
54
|
+
? `\n## Additional Context\n${answers.additionalContext}\n`
|
|
55
|
+
: '';
|
|
57
56
|
const techSection = answers.techStack
|
|
58
57
|
? `\n## Tech Stack Preferences\n${answers.techStack}\n`
|
|
59
58
|
: '';
|
|
60
59
|
return `# ${answers.projectName} — Product Development Request
|
|
61
60
|
|
|
62
|
-
##
|
|
63
|
-
${answers.
|
|
64
|
-
|
|
65
|
-
##
|
|
66
|
-
${answers.solution}
|
|
67
|
-
|
|
68
|
-
## Target User
|
|
69
|
-
${answers.targetUser}
|
|
70
|
-
|
|
71
|
-
## MVP Core Features
|
|
61
|
+
## What I Want to Build
|
|
62
|
+
${answers.productIdea}
|
|
63
|
+
${contextSection}
|
|
64
|
+
## Key Features
|
|
72
65
|
${features}
|
|
73
66
|
${techSection}
|
|
74
67
|
---
|
|
@@ -77,14 +70,14 @@ Please help me build this product based on the requirements above.
|
|
|
77
70
|
|
|
78
71
|
1. First, review the requirements and ask if anything is unclear.
|
|
79
72
|
2. Propose a technical architecture.
|
|
80
|
-
3. Outline the
|
|
73
|
+
3. Outline the implementation steps.
|
|
81
74
|
4. Start coding from the first step.
|
|
82
75
|
|
|
83
76
|
I'll confirm progress at each step before moving to the next.
|
|
84
77
|
|
|
85
78
|
## Documentation Rule
|
|
86
79
|
|
|
87
|
-
**Important:** Update this document (located at \`.
|
|
80
|
+
**Important:** Update this document (located at \`.pmpt/docs/pmpt.md\`) at these moments:
|
|
88
81
|
- When architecture or tech decisions are finalized
|
|
89
82
|
- When a feature is implemented (mark as done)
|
|
90
83
|
- When a development phase is completed
|
|
@@ -102,31 +95,29 @@ This keeps a living record of our development journey.
|
|
|
102
95
|
}
|
|
103
96
|
// Generate plan document
|
|
104
97
|
export function generatePlanDocument(answers) {
|
|
98
|
+
// Parse features (support comma, semicolon, or newline separators)
|
|
105
99
|
const features = answers.coreFeatures
|
|
106
|
-
.split(
|
|
100
|
+
.split(/[,;\n]/)
|
|
107
101
|
.map((f) => f.trim())
|
|
108
102
|
.filter((f) => f)
|
|
109
103
|
.map((f) => `- [ ] ${f}`)
|
|
110
104
|
.join('\n');
|
|
105
|
+
const contextSection = answers.additionalContext
|
|
106
|
+
? `\n## Additional Context\n${answers.additionalContext}\n`
|
|
107
|
+
: '';
|
|
111
108
|
const techSection = answers.techStack
|
|
112
109
|
? `\n## Tech Stack\n${answers.techStack}\n`
|
|
113
110
|
: '';
|
|
114
111
|
return `# ${answers.projectName}
|
|
115
112
|
|
|
116
|
-
##
|
|
117
|
-
${answers.
|
|
118
|
-
|
|
119
|
-
##
|
|
120
|
-
${answers.solution}
|
|
121
|
-
|
|
122
|
-
## Target User
|
|
123
|
-
${answers.targetUser}
|
|
124
|
-
|
|
125
|
-
## MVP Features
|
|
113
|
+
## Product Idea
|
|
114
|
+
${answers.productIdea}
|
|
115
|
+
${contextSection}
|
|
116
|
+
## Features
|
|
126
117
|
${features}
|
|
127
118
|
${techSection}
|
|
128
119
|
---
|
|
129
|
-
*Generated by
|
|
120
|
+
*Generated by pmpt plan*
|
|
130
121
|
`;
|
|
131
122
|
}
|
|
132
123
|
export function getPlanProgress(projectPath) {
|
package/dist/lib/watcher.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import chokidar from 'chokidar';
|
|
2
|
-
import { loadConfig,
|
|
2
|
+
import { loadConfig, getDocsDir } from './config.js';
|
|
3
3
|
import { createFullSnapshot } from './history.js';
|
|
4
4
|
import { readFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
5
6
|
export function startWatching(projectPath, onSnapshot) {
|
|
6
7
|
const config = loadConfig(projectPath);
|
|
7
8
|
if (!config) {
|
|
8
9
|
throw new Error('Project not initialized. Run `pmpt init` first.');
|
|
9
10
|
}
|
|
10
|
-
const
|
|
11
|
-
// Watch all MD files in
|
|
12
|
-
const watcher = chokidar.watch('**/*.md', {
|
|
13
|
-
cwd: pmptDir,
|
|
11
|
+
const docsDir = getDocsDir(projectPath);
|
|
12
|
+
// Watch all MD files in docs folder
|
|
13
|
+
const watcher = chokidar.watch(join(docsDir, '**/*.md'), {
|
|
14
14
|
ignoreInitial: true,
|
|
15
15
|
persistent: true,
|
|
16
16
|
awaitWriteFinish: {
|
|
@@ -34,9 +34,8 @@ export function startWatching(projectPath, onSnapshot) {
|
|
|
34
34
|
debounceTimer = setTimeout(saveSnapshot, 1000);
|
|
35
35
|
};
|
|
36
36
|
watcher.on('add', (path) => {
|
|
37
|
-
const fullPath = `${pmptDir}/${path}`;
|
|
38
37
|
try {
|
|
39
|
-
const content = readFileSync(
|
|
38
|
+
const content = readFileSync(path, 'utf-8');
|
|
40
39
|
fileContents.set(path, content);
|
|
41
40
|
debouncedSave();
|
|
42
41
|
}
|
|
@@ -45,9 +44,8 @@ export function startWatching(projectPath, onSnapshot) {
|
|
|
45
44
|
}
|
|
46
45
|
});
|
|
47
46
|
watcher.on('change', (path) => {
|
|
48
|
-
const fullPath = `${pmptDir}/${path}`;
|
|
49
47
|
try {
|
|
50
|
-
const newContent = readFileSync(
|
|
48
|
+
const newContent = readFileSync(path, 'utf-8');
|
|
51
49
|
const oldContent = fileContents.get(path);
|
|
52
50
|
// Only snapshot if content actually changed
|
|
53
51
|
if (oldContent !== newContent) {
|