pmpt-cli 1.2.1 → 1.3.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.
@@ -1,125 +1,48 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { resolve, join, basename } from 'path';
3
- import { existsSync, writeFileSync, statSync } from 'fs';
4
- import { execSync } from 'child_process';
5
- import { isInitialized, getConfigDir, getHistoryDir, loadConfig } from '../lib/config.js';
3
+ import { existsSync, readFileSync, writeFileSync, statSync } from 'fs';
4
+ import { isInitialized, getDocsDir, loadConfig } from '../lib/config.js';
6
5
  import { getAllSnapshots } from '../lib/history.js';
7
6
  import { getPlanProgress } from '../lib/plan.js';
7
+ import { createPmptFile, SCHEMA_VERSION } from '../lib/pmptFile.js';
8
+ import glob from 'fast-glob';
8
9
  /**
9
- * Create a zip file without external dependencies
10
- * Uses native zip command on macOS/Linux, PowerShell on Windows
10
+ * Read all files from a snapshot directory
11
11
  */
12
- function createZip(sourceDir, outputPath) {
13
- try {
14
- const platform = process.platform;
15
- if (platform === 'win32') {
16
- // PowerShell Compress-Archive
17
- execSync(`powershell -command "Compress-Archive -Path '${sourceDir}/*' -DestinationPath '${outputPath}'"`, {
18
- stdio: 'pipe',
19
- });
12
+ function readSnapshotFiles(snapshotDir) {
13
+ const files = {};
14
+ if (!existsSync(snapshotDir))
15
+ return files;
16
+ const mdFiles = glob.sync('**/*.md', { cwd: snapshotDir });
17
+ for (const file of mdFiles) {
18
+ const filePath = join(snapshotDir, file);
19
+ try {
20
+ files[file] = readFileSync(filePath, 'utf-8');
20
21
  }
21
- else {
22
- // Unix zip command
23
- execSync(`cd "${sourceDir}" && zip -r "${outputPath}" .`, {
24
- stdio: 'pipe',
25
- });
22
+ catch {
23
+ // Skip files that can't be read
26
24
  }
27
- return true;
28
- }
29
- catch {
30
- return false;
31
25
  }
26
+ return files;
32
27
  }
33
28
  /**
34
- * Generate metadata markdown file
29
+ * Read current docs folder
35
30
  */
36
- function generateMetadataMarkdown(projectPath, snapshots, planProgress, config) {
37
- const answers = planProgress?.answers || {};
38
- const projectName = answers.projectName || basename(projectPath);
39
- // Timeline info
40
- const firstSnapshot = snapshots[0];
41
- const lastSnapshot = snapshots[snapshots.length - 1];
42
- const startDate = firstSnapshot?.timestamp || 'N/A';
43
- const lastDate = lastSnapshot?.timestamp || 'N/A';
44
- // Git info from last snapshot
45
- const gitInfo = lastSnapshot?.git;
46
- // All unique files across snapshots
47
- const allFiles = new Set();
48
- for (const snapshot of snapshots) {
49
- for (const file of snapshot.files) {
50
- allFiles.add(file);
31
+ function readDocsFolder(docsDir) {
32
+ const files = {};
33
+ if (!existsSync(docsDir))
34
+ return files;
35
+ const mdFiles = glob.sync('**/*.md', { cwd: docsDir });
36
+ for (const file of mdFiles) {
37
+ const filePath = join(docsDir, file);
38
+ try {
39
+ files[file] = readFileSync(filePath, 'utf-8');
40
+ }
41
+ catch {
42
+ // Skip files that can't be read
51
43
  }
52
44
  }
53
- // Parse features
54
- const features = answers.coreFeatures
55
- ? answers.coreFeatures
56
- .split(/[,;\n]/)
57
- .map((f) => f.trim())
58
- .filter((f) => f)
59
- .map((f) => `- ${f}`)
60
- .join('\n')
61
- : 'Not specified';
62
- let md = `# ${projectName}
63
-
64
- > Exported from pmpt-cli
65
-
66
- ## Project Overview
67
-
68
- **Product Idea:**
69
- ${answers.productIdea || 'Not specified'}
70
-
71
- ${answers.additionalContext ? `**Additional Context:**\n${answers.additionalContext}\n` : ''}
72
- **Key Features:**
73
- ${features}
74
-
75
- **Tech Stack:**
76
- ${answers.techStack || 'Not specified'}
77
-
78
- ---
79
-
80
- ## Export Information
81
-
82
- | Item | Value |
83
- |------|-------|
84
- | Total Iterations | ${snapshots.length} |
85
- | First Version | ${startDate} |
86
- | Last Version | ${lastDate} |
87
- | Files Tracked | ${allFiles.size} |
88
- `;
89
- if (gitInfo) {
90
- md += `| Git Branch | ${gitInfo.branch} |
91
- | Last Commit | ${gitInfo.commit} |
92
- `;
93
- }
94
- md += `
95
- ---
96
-
97
- ## Version Timeline
98
-
99
- | Version | Date | Files | Git |
100
- |---------|------|-------|-----|
101
- `;
102
- for (const snapshot of snapshots) {
103
- const gitCol = snapshot.git
104
- ? `${snapshot.git.commit}${snapshot.git.dirty ? ' (dirty)' : ''}`
105
- : '-';
106
- md += `| v${snapshot.version} | ${snapshot.timestamp} | ${snapshot.files.length} | ${gitCol} |\n`;
107
- }
108
- md += `
109
- ---
110
-
111
- ## File List
112
-
113
- `;
114
- for (const file of Array.from(allFiles).sort()) {
115
- md += `- ${file}\n`;
116
- }
117
- md += `
118
- ---
119
-
120
- *Generated by [pmpt-cli](https://pmptwiki.com)*
121
- `;
122
- return md;
45
+ return files;
123
46
  }
124
47
  export async function cmdExport(path, options) {
125
48
  const projectPath = path ? resolve(path) : process.cwd();
@@ -138,69 +61,75 @@ export async function cmdExport(path, options) {
138
61
  return;
139
62
  }
140
63
  const projectName = planProgress?.answers?.projectName || basename(projectPath);
141
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 10);
142
- const exportName = `pmpt-export-${projectName}-${timestamp}`;
143
- // Output path
64
+ const timestamp = new Date().toISOString().slice(0, 10);
65
+ const exportName = `${projectName}-${timestamp}`;
66
+ // Output path - .pmpt extension
144
67
  const outputPath = options?.output
145
68
  ? resolve(options.output)
146
- : resolve(projectPath, `${exportName}.zip`);
69
+ : resolve(projectPath, `${exportName}.pmpt`);
147
70
  const s = p.spinner();
148
- s.start('Preparing export...');
149
- // Create temp directory for export
150
- const tempDir = join(projectPath, '.pmpt', '.export-temp');
151
- execSync(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`, { stdio: 'pipe' });
152
- // Copy history folder
153
- const historyDir = getHistoryDir(projectPath);
154
- execSync(`cp -r "${historyDir}" "${tempDir}/history"`, { stdio: 'pipe' });
155
- // Generate and save metadata
156
- const metadataContent = generateMetadataMarkdown(projectPath, snapshots, planProgress, config);
157
- writeFileSync(join(tempDir, 'README.md'), metadataContent, 'utf-8');
158
- // Copy current docs if exists
159
- const docsDir = join(getConfigDir(projectPath), 'docs');
160
- if (existsSync(docsDir)) {
161
- execSync(`cp -r "${docsDir}" "${tempDir}/docs"`, { stdio: 'pipe' });
162
- }
163
- // Copy config (without sensitive data)
164
- if (config) {
165
- const exportConfig = {
166
- projectPath: basename(projectPath),
167
- docsPath: config.docsPath,
168
- createdAt: config.createdAt,
169
- trackGit: config.trackGit,
170
- };
171
- writeFileSync(join(tempDir, 'config.json'), JSON.stringify(exportConfig, null, 2), 'utf-8');
71
+ s.start('Creating .pmpt file...');
72
+ // Build history array with file contents
73
+ const history = [];
74
+ for (const snapshot of snapshots) {
75
+ const files = readSnapshotFiles(snapshot.snapshotDir);
76
+ history.push({
77
+ version: snapshot.version,
78
+ timestamp: snapshot.timestamp,
79
+ files,
80
+ git: snapshot.git,
81
+ });
172
82
  }
173
- // Copy plan progress if exists
174
- if (planProgress) {
175
- writeFileSync(join(tempDir, 'plan.json'), JSON.stringify(planProgress, null, 2), 'utf-8');
83
+ // Read current docs
84
+ const docsDir = getDocsDir(projectPath);
85
+ const docs = readDocsFolder(docsDir);
86
+ // Build metadata
87
+ const meta = {
88
+ projectName,
89
+ description: planProgress?.answers?.productIdea,
90
+ createdAt: config?.createdAt || new Date().toISOString(),
91
+ exportedAt: new Date().toISOString(),
92
+ };
93
+ // Convert plan answers to typed format
94
+ const planAnswers = planProgress?.answers
95
+ ? {
96
+ projectName: planProgress.answers.projectName,
97
+ productIdea: planProgress.answers.productIdea,
98
+ additionalContext: planProgress.answers.additionalContext,
99
+ coreFeatures: planProgress.answers.coreFeatures,
100
+ techStack: planProgress.answers.techStack,
101
+ }
102
+ : undefined;
103
+ // Create .pmpt file content
104
+ const pmptContent = createPmptFile(meta, planAnswers, docs, history);
105
+ // Write file
106
+ try {
107
+ writeFileSync(outputPath, pmptContent, 'utf-8');
176
108
  }
177
- s.message('Creating archive...');
178
- // Create zip
179
- const zipSuccess = createZip(tempDir, outputPath);
180
- // Cleanup temp directory
181
- execSync(`rm -rf "${tempDir}"`, { stdio: 'pipe' });
182
- if (!zipSuccess) {
109
+ catch (err) {
183
110
  s.stop('Export failed');
184
- p.log.error('Failed to create zip archive.');
185
- p.log.info('Make sure `zip` command is available on your system.');
111
+ p.log.error('Failed to write .pmpt file.');
186
112
  process.exit(1);
187
113
  }
188
114
  s.stop('Export complete!');
189
- // Summary
115
+ // File size
190
116
  const fileSizeBytes = statSync(outputPath).size;
191
117
  const fileSize = fileSizeBytes < 1024
192
118
  ? `${fileSizeBytes} B`
193
119
  : fileSizeBytes < 1024 * 1024
194
120
  ? `${(fileSizeBytes / 1024).toFixed(1)} KB`
195
121
  : `${(fileSizeBytes / 1024 / 1024).toFixed(1)} MB`;
122
+ // Summary
196
123
  const summary = [
197
124
  `Project: ${projectName}`,
198
125
  `Versions: ${snapshots.length}`,
126
+ `Schema: v${SCHEMA_VERSION}`,
199
127
  `Size: ${fileSize}`,
200
128
  '',
201
129
  `Output: ${outputPath}`,
202
130
  ];
203
131
  p.note(summary.join('\n'), 'Export Summary');
204
- p.log.info('Share this file to let others reproduce your AI development journey!');
132
+ p.log.info('Share this .pmpt file to let others reproduce your AI development journey!');
133
+ p.log.message(' pmpt import <file.pmpt> — Import on another machine');
205
134
  p.outro('');
206
135
  }
@@ -1,50 +1,98 @@
1
1
  import * as p from '@clack/prompts';
2
- import { resolve, join } from 'path';
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, rmSync } from 'fs';
4
- import { execSync } from 'child_process';
2
+ import { resolve, join, dirname } from 'path';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
5
4
  import { isInitialized, getConfigDir, getHistoryDir, getDocsDir, initializeProject } from '../lib/config.js';
5
+ import { validatePmptFile } from '../lib/pmptFile.js';
6
6
  /**
7
- * Extract zip file
8
- * Uses native unzip command on macOS/Linux, PowerShell on Windows
7
+ * Restore history from .pmpt file
9
8
  */
10
- function extractZip(zipPath, destDir) {
11
- try {
12
- const platform = process.platform;
13
- mkdirSync(destDir, { recursive: true });
14
- if (platform === 'win32') {
15
- // PowerShell Expand-Archive
16
- execSync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, {
17
- stdio: 'pipe',
18
- });
19
- }
20
- else {
21
- // Unix unzip command
22
- execSync(`unzip -o "${zipPath}" -d "${destDir}"`, {
23
- stdio: 'pipe',
24
- });
9
+ function restoreHistory(historyDir, history) {
10
+ mkdirSync(historyDir, { recursive: true });
11
+ for (const version of history) {
12
+ const timestamp = version.timestamp.replace(/[:.]/g, '-').slice(0, 19);
13
+ const snapshotName = `v${version.version}-${timestamp}`;
14
+ const snapshotDir = join(historyDir, snapshotName);
15
+ mkdirSync(snapshotDir, { recursive: true });
16
+ // Write files
17
+ for (const [filename, content] of Object.entries(version.files)) {
18
+ const filePath = join(snapshotDir, filename);
19
+ const fileDir = dirname(filePath);
20
+ if (fileDir !== snapshotDir) {
21
+ mkdirSync(fileDir, { recursive: true });
22
+ }
23
+ writeFileSync(filePath, content, 'utf-8');
25
24
  }
26
- return true;
25
+ // Write metadata
26
+ const metaPath = join(snapshotDir, '.meta.json');
27
+ writeFileSync(metaPath, JSON.stringify({
28
+ version: version.version,
29
+ timestamp: version.timestamp,
30
+ files: Object.keys(version.files),
31
+ git: version.git,
32
+ }, null, 2), 'utf-8');
27
33
  }
28
- catch {
29
- return false;
34
+ }
35
+ /**
36
+ * Restore docs from .pmpt file
37
+ */
38
+ function restoreDocs(docsDir, docs) {
39
+ mkdirSync(docsDir, { recursive: true });
40
+ for (const [filename, content] of Object.entries(docs)) {
41
+ const filePath = join(docsDir, filename);
42
+ const fileDir = dirname(filePath);
43
+ if (fileDir !== docsDir) {
44
+ mkdirSync(fileDir, { recursive: true });
45
+ }
46
+ writeFileSync(filePath, content, 'utf-8');
30
47
  }
31
48
  }
32
- export async function cmdImport(zipFile, options) {
33
- if (!zipFile) {
34
- p.log.error('Please provide a zip file path.');
35
- p.log.info('Usage: pmpt import <file.zip>');
49
+ export async function cmdImport(pmptFile, options) {
50
+ if (!pmptFile) {
51
+ p.log.error('Please provide a .pmpt file path.');
52
+ p.log.info('Usage: pmpt import <file.pmpt>');
36
53
  process.exit(1);
37
54
  }
38
- const zipPath = resolve(zipFile);
39
- if (!existsSync(zipPath)) {
40
- p.log.error(`File not found: ${zipPath}`);
55
+ const filePath = resolve(pmptFile);
56
+ if (!existsSync(filePath)) {
57
+ p.log.error(`File not found: ${filePath}`);
41
58
  process.exit(1);
42
59
  }
43
- if (!zipPath.endsWith('.zip')) {
44
- p.log.error('Please provide a .zip file.');
60
+ if (!filePath.endsWith('.pmpt')) {
61
+ p.log.error('Please provide a .pmpt file.');
62
+ p.log.info('Use `pmpt export` to create a .pmpt file.');
45
63
  process.exit(1);
46
64
  }
47
65
  p.intro('pmpt import');
66
+ const s = p.spinner();
67
+ s.start('Reading .pmpt file...');
68
+ // Read file
69
+ let fileContent;
70
+ try {
71
+ fileContent = readFileSync(filePath, 'utf-8');
72
+ }
73
+ catch {
74
+ s.stop('Read failed');
75
+ p.log.error('Failed to read file.');
76
+ process.exit(1);
77
+ }
78
+ // Validate
79
+ s.message('Validating...');
80
+ const validation = validatePmptFile(fileContent);
81
+ if (!validation.success || !validation.data) {
82
+ s.stop('Validation failed');
83
+ p.log.error(validation.error || 'Invalid .pmpt file.');
84
+ process.exit(1);
85
+ }
86
+ const pmptData = validation.data;
87
+ s.stop('Validation passed');
88
+ // Show summary and confirm
89
+ const summaryLines = [
90
+ `Project: ${pmptData.meta.projectName}`,
91
+ `Versions: ${pmptData.history.length}`,
92
+ `Schema: v${pmptData.schemaVersion}`,
93
+ pmptData.meta.description ? `Description: ${pmptData.meta.description.slice(0, 50)}...` : '',
94
+ ].filter(Boolean);
95
+ p.note(summaryLines.join('\n'), 'Import Preview');
48
96
  const projectPath = process.cwd();
49
97
  // Check if already initialized
50
98
  if (isInitialized(projectPath) && !options?.force) {
@@ -62,84 +110,40 @@ export async function cmdImport(zipFile, options) {
62
110
  return;
63
111
  }
64
112
  }
65
- const s = p.spinner();
66
- s.start('Extracting archive...');
67
- // Create temp directory for extraction
68
- const tempDir = join(projectPath, '.pmpt-import-temp');
69
- rmSync(tempDir, { recursive: true, force: true });
70
- const extracted = extractZip(zipPath, tempDir);
71
- if (!extracted) {
72
- rmSync(tempDir, { recursive: true, force: true });
73
- s.stop('Extraction failed');
74
- p.log.error('Failed to extract zip file.');
75
- p.log.info('Make sure `unzip` command is available on your system.');
76
- process.exit(1);
77
- }
78
- s.message('Importing project...');
79
- // Read imported data
80
- let importedPlan = null;
81
- let importedConfig = null;
82
- let projectName = 'imported-project';
83
- // Read plan.json if exists
84
- const planPath = join(tempDir, 'plan.json');
85
- if (existsSync(planPath)) {
86
- try {
87
- importedPlan = JSON.parse(readFileSync(planPath, 'utf-8'));
88
- if (importedPlan.answers?.projectName) {
89
- projectName = importedPlan.answers.projectName;
90
- }
91
- }
92
- catch {
93
- // Ignore parse errors
94
- }
95
- }
96
- // Read config.json if exists
97
- const configPath = join(tempDir, 'config.json');
98
- if (existsSync(configPath)) {
99
- try {
100
- importedConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
101
- }
102
- catch {
103
- // Ignore parse errors
104
- }
105
- }
113
+ const importSpinner = p.spinner();
114
+ importSpinner.start('Importing project...');
106
115
  // Initialize project if not exists
107
116
  if (!isInitialized(projectPath)) {
108
- initializeProject(projectPath, {
109
- trackGit: importedConfig?.trackGit ?? true,
110
- });
117
+ initializeProject(projectPath, { trackGit: true });
111
118
  }
112
119
  const pmptDir = getConfigDir(projectPath);
113
120
  const historyDir = getHistoryDir(projectPath);
114
121
  const docsDir = getDocsDir(projectPath);
115
- // Import history
116
- const importedHistoryDir = join(tempDir, 'history');
117
- if (existsSync(importedHistoryDir)) {
118
- // Copy all version folders
119
- cpSync(importedHistoryDir, historyDir, { recursive: true, force: true });
120
- }
121
- // Import docs
122
- const importedDocsDir = join(tempDir, 'docs');
123
- if (existsSync(importedDocsDir)) {
124
- cpSync(importedDocsDir, docsDir, { recursive: true, force: true });
122
+ // Restore history
123
+ restoreHistory(historyDir, pmptData.history);
124
+ // Restore docs
125
+ if (pmptData.docs) {
126
+ restoreDocs(docsDir, pmptData.docs);
125
127
  }
126
- // Import plan progress
127
- if (importedPlan) {
128
+ // Restore plan progress
129
+ if (pmptData.plan) {
128
130
  const planProgressPath = join(pmptDir, 'plan-progress.json');
129
- writeFileSync(planProgressPath, JSON.stringify(importedPlan, null, 2), 'utf-8');
131
+ writeFileSync(planProgressPath, JSON.stringify({
132
+ completed: true,
133
+ startedAt: pmptData.meta.createdAt,
134
+ updatedAt: pmptData.meta.exportedAt,
135
+ answers: pmptData.plan,
136
+ }, null, 2), 'utf-8');
130
137
  }
131
138
  // Count imported versions
132
139
  let versionCount = 0;
133
140
  if (existsSync(historyDir)) {
134
- const { readdirSync } = await import('fs');
135
141
  versionCount = readdirSync(historyDir).filter(d => d.startsWith('v')).length;
136
142
  }
137
- // Cleanup temp directory
138
- rmSync(tempDir, { recursive: true, force: true });
139
- s.stop('Import complete!');
143
+ importSpinner.stop('Import complete!');
140
144
  // Summary
141
145
  const summary = [
142
- `Project: ${projectName}`,
146
+ `Project: ${pmptData.meta.projectName}`,
143
147
  `Versions imported: ${versionCount}`,
144
148
  `Location: ${pmptDir}`,
145
149
  ];
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ const program = new Command();
16
16
  program
17
17
  .name('pmpt')
18
18
  .description('pmpt — Record and share your AI-driven product development journey')
19
- .version('1.0.0')
19
+ .version('1.3.0')
20
20
  .addHelpText('after', `
21
21
  Examples:
22
22
  $ pmpt init Initialize project
@@ -26,8 +26,8 @@ Examples:
26
26
  $ pmpt history View version history
27
27
  $ pmpt history --compact Hide minor changes
28
28
  $ pmpt squash v2 v5 Merge versions v2-v5 into v2
29
- $ pmpt export Export history as shareable zip
30
- $ pmpt import <file.zip> Import project from zip
29
+ $ pmpt export Export as .pmpt file (single JSON)
30
+ $ pmpt import <file.pmpt> Import from .pmpt file
31
31
 
32
32
  Folder structure:
33
33
  .pmpt/
@@ -0,0 +1,106 @@
1
+ /**
2
+ * .pmpt File Format Schema (v1.0)
3
+ *
4
+ * Single JSON file format for sharing pmpt projects.
5
+ */
6
+ import { z } from 'zod';
7
+ // Schema version
8
+ export const SCHEMA_VERSION = '1.0';
9
+ export const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
10
+ // Git info schema
11
+ const GitInfoSchema = z.object({
12
+ commit: z.string(),
13
+ commitFull: z.string().optional(),
14
+ branch: z.string().optional(),
15
+ dirty: z.boolean().optional(),
16
+ tag: z.string().optional(),
17
+ }).optional();
18
+ // Single version/snapshot schema
19
+ const VersionSchema = z.object({
20
+ version: z.number().min(1),
21
+ timestamp: z.string(),
22
+ files: z.record(z.string(), z.string()), // filename -> content
23
+ git: GitInfoSchema,
24
+ });
25
+ // Plan answers schema
26
+ const PlanSchema = z.object({
27
+ projectName: z.string(),
28
+ productIdea: z.string().optional(),
29
+ additionalContext: z.string().optional(),
30
+ coreFeatures: z.string().optional(),
31
+ techStack: z.string().optional(),
32
+ }).optional();
33
+ // Project metadata schema
34
+ const MetaSchema = z.object({
35
+ projectName: z.string(),
36
+ author: z.string().optional(),
37
+ description: z.string().optional(),
38
+ createdAt: z.string(),
39
+ exportedAt: z.string(),
40
+ });
41
+ // Full .pmpt file schema
42
+ export const PmptFileSchema = z.object({
43
+ schemaVersion: z.string(),
44
+ cliMinVersion: z.string().optional(),
45
+ meta: MetaSchema,
46
+ plan: PlanSchema,
47
+ docs: z.record(z.string(), z.string()).optional(), // current docs
48
+ history: z.array(VersionSchema),
49
+ });
50
+ /**
51
+ * Validate .pmpt file content
52
+ */
53
+ export function validatePmptFile(content) {
54
+ // Check size
55
+ if (content.length > MAX_FILE_SIZE) {
56
+ return {
57
+ success: false,
58
+ error: `File too large. Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB`,
59
+ };
60
+ }
61
+ // Parse JSON
62
+ let json;
63
+ try {
64
+ json = JSON.parse(content);
65
+ }
66
+ catch {
67
+ return {
68
+ success: false,
69
+ error: 'Invalid JSON format',
70
+ };
71
+ }
72
+ // Validate schema
73
+ const result = PmptFileSchema.safeParse(json);
74
+ if (!result.success) {
75
+ const firstError = result.error.errors[0];
76
+ return {
77
+ success: false,
78
+ error: `Validation error: ${firstError.path.join('.')} - ${firstError.message}`,
79
+ };
80
+ }
81
+ // Check schema version
82
+ if (result.data.schemaVersion !== SCHEMA_VERSION) {
83
+ return {
84
+ success: false,
85
+ error: `Unsupported schema version: ${result.data.schemaVersion}. Current: ${SCHEMA_VERSION}`,
86
+ };
87
+ }
88
+ return {
89
+ success: true,
90
+ data: result.data,
91
+ };
92
+ }
93
+ /**
94
+ * Create .pmpt file content from project data
95
+ */
96
+ export function createPmptFile(meta, plan, docs, history) {
97
+ const pmptFile = {
98
+ schemaVersion: SCHEMA_VERSION,
99
+ cliMinVersion: '1.3.0',
100
+ meta,
101
+ plan,
102
+ docs,
103
+ history,
104
+ };
105
+ return JSON.stringify(pmptFile, null, 2);
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmpt-cli",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Record and share your AI-driven product development journey",
5
5
  "type": "module",
6
6
  "bin": {