pmpt-cli 1.13.0 → 1.14.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/squash.js +92 -12
- package/dist/index.js +6 -3
- package/dist/mcp.js +29 -2
- package/package.json +1 -1
package/dist/commands/squash.js
CHANGED
|
@@ -1,15 +1,99 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
|
-
import { resolve, join } from 'path';
|
|
3
|
-
import { existsSync, rmSync, writeFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { resolve, join, basename } from 'path';
|
|
3
|
+
import { existsSync, rmSync, renameSync, writeFileSync, readFileSync } from 'fs';
|
|
4
4
|
import { isInitialized, getHistoryDir } from '../lib/config.js';
|
|
5
5
|
import { getAllSnapshots } from '../lib/history.js';
|
|
6
|
-
export async function cmdSquash(from, to,
|
|
7
|
-
const projectPath = path ? resolve(path) : process.cwd();
|
|
6
|
+
export async function cmdSquash(from, to, opts) {
|
|
7
|
+
const projectPath = opts?.path ? resolve(opts.path) : process.cwd();
|
|
8
8
|
if (!isInitialized(projectPath)) {
|
|
9
9
|
p.log.error('Project not initialized. Run `pmpt init` first.');
|
|
10
10
|
process.exit(1);
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const snapshots = getAllSnapshots(projectPath);
|
|
13
|
+
if (snapshots.length === 0) {
|
|
14
|
+
p.log.error('No snapshots found.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
if (opts?.auto) {
|
|
18
|
+
return autoSquash(projectPath, snapshots);
|
|
19
|
+
}
|
|
20
|
+
// Manual squash: require from and to
|
|
21
|
+
if (!from || !to) {
|
|
22
|
+
p.log.error('Usage: pmpt squash v2 v5 or pmpt squash --auto');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
return manualSquash(projectPath, snapshots, from, to);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Renumber remaining snapshots to v1, v2, v3... sequentially.
|
|
29
|
+
* Renames directories and updates .meta.json version fields.
|
|
30
|
+
*/
|
|
31
|
+
function renumberSnapshots(projectPath) {
|
|
32
|
+
const historyDir = getHistoryDir(projectPath);
|
|
33
|
+
const remaining = getAllSnapshots(projectPath); // sorted by version
|
|
34
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
35
|
+
const snap = remaining[i];
|
|
36
|
+
const newVersion = i + 1;
|
|
37
|
+
if (snap.version === newVersion)
|
|
38
|
+
continue; // already correct
|
|
39
|
+
// Rename directory: v5-20260228T164006 → v2-20260228T164006
|
|
40
|
+
const dirName = basename(snap.snapshotDir);
|
|
41
|
+
const newDirName = dirName.replace(/^v\d+/, `v${newVersion}`);
|
|
42
|
+
const newDir = join(historyDir, newDirName);
|
|
43
|
+
renameSync(snap.snapshotDir, newDir);
|
|
44
|
+
// Update .meta.json
|
|
45
|
+
const metaPath = join(newDir, '.meta.json');
|
|
46
|
+
if (existsSync(metaPath)) {
|
|
47
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
48
|
+
meta.version = newVersion;
|
|
49
|
+
writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf-8');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function autoSquash(projectPath, snapshots) {
|
|
54
|
+
// Find empty snapshots (no changed files)
|
|
55
|
+
const emptySnapshots = snapshots.filter(s => s.changedFiles && s.changedFiles.length === 0);
|
|
56
|
+
if (emptySnapshots.length === 0) {
|
|
57
|
+
p.log.info('No empty snapshots found. Nothing to clean up.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
p.intro('pmpt squash --auto');
|
|
61
|
+
p.log.info(`Found ${emptySnapshots.length} empty snapshot(s) (no file changes):`);
|
|
62
|
+
for (const s of emptySnapshots) {
|
|
63
|
+
const git = s.git ? ` [${s.git.commit}]` : '';
|
|
64
|
+
p.log.message(` v${s.version} — ${s.timestamp.slice(0, 16)}${git}`);
|
|
65
|
+
}
|
|
66
|
+
const confirm = await p.confirm({
|
|
67
|
+
message: `Delete ${emptySnapshots.length} empty snapshot(s) and renumber?`,
|
|
68
|
+
initialValue: true,
|
|
69
|
+
});
|
|
70
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
71
|
+
p.cancel('Cancelled');
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
const sp = p.spinner();
|
|
75
|
+
sp.start('Removing empty snapshots...');
|
|
76
|
+
try {
|
|
77
|
+
let deleted = 0;
|
|
78
|
+
for (const snapshot of emptySnapshots) {
|
|
79
|
+
if (existsSync(snapshot.snapshotDir)) {
|
|
80
|
+
rmSync(snapshot.snapshotDir, { recursive: true });
|
|
81
|
+
deleted++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
renumberSnapshots(projectPath);
|
|
85
|
+
sp.stop('Cleaned up');
|
|
86
|
+
const remaining = snapshots.length - deleted;
|
|
87
|
+
p.log.success(`Removed ${deleted} empty snapshot(s), renumbered to v1-v${remaining}`);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
sp.stop('Failed');
|
|
91
|
+
p.log.error(error.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
p.outro('View history: pmpt history');
|
|
95
|
+
}
|
|
96
|
+
async function manualSquash(projectPath, snapshots, from, to) {
|
|
13
97
|
const fromVersion = parseInt(from.replace(/^v/, ''), 10);
|
|
14
98
|
const toVersion = parseInt(to.replace(/^v/, ''), 10);
|
|
15
99
|
if (isNaN(fromVersion) || isNaN(toVersion)) {
|
|
@@ -20,11 +104,6 @@ export async function cmdSquash(from, to, path) {
|
|
|
20
104
|
p.log.error('First version must be less than second version.');
|
|
21
105
|
process.exit(1);
|
|
22
106
|
}
|
|
23
|
-
const snapshots = getAllSnapshots(projectPath);
|
|
24
|
-
if (snapshots.length === 0) {
|
|
25
|
-
p.log.error('No snapshots found.');
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
107
|
const versionList = snapshots.map(s => `v${s.version}`).join(', ');
|
|
29
108
|
// Find snapshots to squash
|
|
30
109
|
const toSquash = snapshots.filter(s => s.version >= fromVersion && s.version <= toVersion);
|
|
@@ -46,7 +125,6 @@ export async function cmdSquash(from, to, path) {
|
|
|
46
125
|
const s = p.spinner();
|
|
47
126
|
s.start('Squashing versions...');
|
|
48
127
|
try {
|
|
49
|
-
const historyDir = getHistoryDir(projectPath);
|
|
50
128
|
const keepSnapshot = toSquash[0]; // Keep the first one
|
|
51
129
|
const deleteSnapshots = toSquash.slice(1); // Delete the rest
|
|
52
130
|
// Delete the snapshots we're squashing
|
|
@@ -64,8 +142,10 @@ export async function cmdSquash(from, to, path) {
|
|
|
64
142
|
meta.squashedAt = new Date().toISOString();
|
|
65
143
|
writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf-8');
|
|
66
144
|
}
|
|
145
|
+
renumberSnapshots(projectPath);
|
|
67
146
|
s.stop('Squashed');
|
|
68
|
-
|
|
147
|
+
const remaining = getAllSnapshots(projectPath);
|
|
148
|
+
p.log.success(`Squashed v${fromVersion}-v${toVersion}, renumbered to v1-v${remaining.length}`);
|
|
69
149
|
p.log.info(`Deleted ${deleteSnapshots.length} version(s)`);
|
|
70
150
|
}
|
|
71
151
|
catch (error) {
|
package/dist/index.js
CHANGED
|
@@ -68,6 +68,7 @@ Examples:
|
|
|
68
68
|
$ pmpt diff v1 v2 Compare two versions
|
|
69
69
|
$ pmpt diff v3 Compare v3 to working copy
|
|
70
70
|
$ pmpt squash v2 v5 Merge versions v2-v5 into v2
|
|
71
|
+
$ pmpt squash --auto Auto-remove empty snapshots
|
|
71
72
|
$ pmpt export Export as .pmpt file (single JSON)
|
|
72
73
|
$ pmpt import <file.pmpt> Import from .pmpt file
|
|
73
74
|
$ pmpt login Authenticate with pmptwiki
|
|
@@ -119,9 +120,11 @@ program
|
|
|
119
120
|
.option('-f, --file <name>', 'Compare specific file only')
|
|
120
121
|
.action(cmdDiff);
|
|
121
122
|
program
|
|
122
|
-
.command('squash
|
|
123
|
-
.description('Squash
|
|
124
|
-
.
|
|
123
|
+
.command('squash [from] [to]')
|
|
124
|
+
.description('Squash versions: pmpt squash v2 v5 or pmpt squash --auto')
|
|
125
|
+
.option('--auto', 'Auto-remove empty snapshots (no file changes)')
|
|
126
|
+
.option('-p, --path <path>', 'Project path')
|
|
127
|
+
.action((from, to, opts) => cmdSquash(from, to, opts));
|
|
125
128
|
program
|
|
126
129
|
.command('export [path]')
|
|
127
130
|
.description('Export project history as a shareable .pmpt file')
|
package/dist/mcp.js
CHANGED
|
@@ -98,7 +98,10 @@ function formatDiffs(diffs) {
|
|
|
98
98
|
return lines.join('\n');
|
|
99
99
|
}
|
|
100
100
|
// ── Tools ───────────────────────────────────────────
|
|
101
|
-
server.tool('pmpt_save', 'Save a snapshot of .pmpt/docs/ files. Call after completing features, fixes, or milestones.
|
|
101
|
+
server.tool('pmpt_save', 'Save a snapshot of .pmpt/docs/ files. Call after completing features, fixes, or milestones. IMPORTANT: Always provide a summary describing what was accomplished — this gets recorded in the project development log.', {
|
|
102
|
+
projectPath: z.string().optional().describe('Project root path. Defaults to cwd.'),
|
|
103
|
+
summary: z.string().optional().describe('What was accomplished since the last save. This is recorded in pmpt.md as a development log entry. Examples: "Implemented user auth with JWT", "Fixed responsive layout on mobile", "Added search filtering by category".'),
|
|
104
|
+
}, async ({ projectPath, summary }) => {
|
|
102
105
|
try {
|
|
103
106
|
const pp = resolveProjectPath(projectPath);
|
|
104
107
|
assertInitialized(pp);
|
|
@@ -106,13 +109,37 @@ server.tool('pmpt_save', 'Save a snapshot of .pmpt/docs/ files. Call after compl
|
|
|
106
109
|
if (tracked.length === 0) {
|
|
107
110
|
return { content: [{ type: 'text', text: 'No files to save. Add .md files to .pmpt/docs/ first.' }] };
|
|
108
111
|
}
|
|
109
|
-
|
|
112
|
+
// Auto-update pmpt.md with summary before snapshot
|
|
113
|
+
if (summary) {
|
|
114
|
+
const docsDir = getDocsDir(pp);
|
|
115
|
+
const pmptMdPath = join(docsDir, 'pmpt.md');
|
|
116
|
+
if (existsSync(pmptMdPath)) {
|
|
117
|
+
let content = readFileSync(pmptMdPath, 'utf-8');
|
|
118
|
+
const snapshots = getAllSnapshots(pp);
|
|
119
|
+
const nextVersion = snapshots.length + 1;
|
|
120
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
121
|
+
const entry = `\n### v${nextVersion} — ${date}\n- ${summary}\n`;
|
|
122
|
+
const logIndex = content.indexOf('## Snapshot Log');
|
|
123
|
+
if (logIndex !== -1) {
|
|
124
|
+
const afterHeader = content.indexOf('\n', logIndex);
|
|
125
|
+
const nextSection = content.indexOf('\n## ', afterHeader + 1);
|
|
126
|
+
const insertPos = nextSection !== -1 ? nextSection : content.length;
|
|
127
|
+
content = content.slice(0, insertPos) + entry + content.slice(insertPos);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
content += `\n## Snapshot Log${entry}`;
|
|
131
|
+
}
|
|
132
|
+
writeFileSync(pmptMdPath, content, 'utf-8');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const entry = createFullSnapshot(pp, summary ? { note: summary } : undefined);
|
|
110
136
|
const changedCount = entry.changedFiles?.length ?? entry.files.length;
|
|
111
137
|
return {
|
|
112
138
|
content: [{
|
|
113
139
|
type: 'text',
|
|
114
140
|
text: [
|
|
115
141
|
`Snapshot v${entry.version} saved (${changedCount} changed, ${entry.files.length - changedCount} unchanged).`,
|
|
142
|
+
summary ? `Summary: ${summary}` : '',
|
|
116
143
|
'',
|
|
117
144
|
`Files: ${entry.files.join(', ')}`,
|
|
118
145
|
entry.changedFiles ? `Changed: ${entry.changedFiles.join(', ')}` : '',
|