pmpt-cli 1.14.5 → 1.14.7

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,8 +1,8 @@
1
1
  import * as p from '@clack/prompts';
2
- import { resolve } from 'path';
3
- import { existsSync, statSync } from 'fs';
2
+ import { resolve, join } from 'path';
3
+ import { existsSync, statSync, readFileSync, writeFileSync } from 'fs';
4
4
  import { isInitialized, getDocsDir } from '../lib/config.js';
5
- import { createFullSnapshot, getTrackedFiles } from '../lib/history.js';
5
+ import { createFullSnapshot, getTrackedFiles, getAllSnapshots } from '../lib/history.js';
6
6
  export async function cmdSave(fileOrPath) {
7
7
  const projectPath = fileOrPath && existsSync(fileOrPath) && statSync(fileOrPath).isDirectory()
8
8
  ? resolve(fileOrPath)
@@ -21,10 +21,46 @@ export async function cmdSave(fileOrPath) {
21
21
  p.outro('');
22
22
  return;
23
23
  }
24
+ // Ask for summary
25
+ const summary = await p.text({
26
+ message: 'What did you accomplish? (this is shown on your project page)',
27
+ placeholder: 'e.g. Added user auth with JWT, built login/signup pages',
28
+ });
29
+ if (p.isCancel(summary)) {
30
+ p.cancel('Save cancelled.');
31
+ process.exit(0);
32
+ }
33
+ const note = summary.trim() || undefined;
34
+ // Write summary to pmpt.md Snapshot Log before snapshot
35
+ if (note) {
36
+ const pmptMdPath = join(docsDir, 'pmpt.md');
37
+ if (existsSync(pmptMdPath)) {
38
+ let content = readFileSync(pmptMdPath, 'utf-8');
39
+ const snapshots = getAllSnapshots(projectPath);
40
+ const nextVersion = snapshots.length + 1;
41
+ const date = new Date().toISOString().slice(0, 10);
42
+ const noteLines = note.split(/(?:\.\s+|\n)/).filter(s => s.trim()).map(s => {
43
+ const trimmed = s.trim().replace(/\.?$/, '');
44
+ return `- ${trimmed}`;
45
+ });
46
+ const entry = `\n### v${nextVersion} — ${date}\n${noteLines.join('\n')}\n`;
47
+ const logIndex = content.indexOf('## Snapshot Log');
48
+ if (logIndex !== -1) {
49
+ const afterHeader = content.indexOf('\n', logIndex);
50
+ const nextSection = content.indexOf('\n## ', afterHeader + 1);
51
+ const insertPos = nextSection !== -1 ? nextSection : content.length;
52
+ content = content.slice(0, insertPos) + entry + content.slice(insertPos);
53
+ }
54
+ else {
55
+ content += `\n## Snapshot Log${entry}`;
56
+ }
57
+ writeFileSync(pmptMdPath, content, 'utf-8');
58
+ }
59
+ }
24
60
  const s = p.spinner();
25
61
  s.start(`Creating snapshot of ${files.length} file(s)...`);
26
62
  try {
27
- const entry = createFullSnapshot(projectPath);
63
+ const entry = createFullSnapshot(projectPath, { note });
28
64
  s.stop('Snapshot saved');
29
65
  let msg = `v${entry.version} saved`;
30
66
  if (entry.git) {
@@ -38,11 +74,8 @@ export async function cmdSave(fileOrPath) {
38
74
  msg += ` (${changedCount} changed, ${unchangedCount} skipped)`;
39
75
  }
40
76
  p.log.success(msg);
41
- // Warn if pmpt.md was not updated since last save
42
- if (entry.version > 1 && entry.changedFiles && !entry.changedFiles.includes('pmpt.md')) {
43
- p.log.message('');
44
- p.log.warn('pmpt.md has not been updated since the last save.');
45
- p.log.message(' Tip: Mark completed features and update the Snapshot Log before saving.');
77
+ if (note) {
78
+ p.log.info(`Summary: ${note}`);
46
79
  }
47
80
  p.log.message('');
48
81
  p.log.info('Files included:');
@@ -14,7 +14,7 @@ export function cmdWatch(path) {
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('');
17
- const watcher = startWatching(projectPath, (version, files, git) => {
17
+ const watcher = startWatching(projectPath, (version, files, git, note) => {
18
18
  let msg = `v${version} saved (${files.length} file(s))`;
19
19
  if (git) {
20
20
  msg += ` · ${git.commit}`;
@@ -22,6 +22,9 @@ export function cmdWatch(path) {
22
22
  msg += ' (uncommitted)';
23
23
  }
24
24
  p.log.success(msg);
25
+ if (note) {
26
+ p.log.info(` ${note}`);
27
+ }
25
28
  });
26
29
  process.on('SIGINT', () => {
27
30
  p.log.message('');
@@ -2,7 +2,7 @@ import chokidar from 'chokidar';
2
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
+ import { join, relative } from 'path';
6
6
  export function startWatching(projectPath, onSnapshot) {
7
7
  const config = loadConfig(projectPath);
8
8
  if (!config) {
@@ -19,11 +19,18 @@ export function startWatching(projectPath, onSnapshot) {
19
19
  },
20
20
  });
21
21
  const fileContents = new Map();
22
+ const pendingChanges = new Set();
22
23
  let debounceTimer = null;
23
24
  const saveSnapshot = () => {
24
- const entry = createFullSnapshot(projectPath);
25
+ // Build auto-note from changed file names
26
+ const changedNames = [...pendingChanges].map(p => relative(docsDir, p));
27
+ const note = changedNames.length > 0
28
+ ? `Updated ${changedNames.join(', ')}`
29
+ : undefined;
30
+ pendingChanges.clear();
31
+ const entry = createFullSnapshot(projectPath, { note });
25
32
  if (onSnapshot) {
26
- onSnapshot(entry.version, entry.files, entry.git);
33
+ onSnapshot(entry.version, entry.files, entry.git, note);
27
34
  }
28
35
  };
29
36
  // Debounced snapshot save (1 second)
@@ -37,6 +44,7 @@ export function startWatching(projectPath, onSnapshot) {
37
44
  try {
38
45
  const content = readFileSync(path, 'utf-8');
39
46
  fileContents.set(path, content);
47
+ pendingChanges.add(path);
40
48
  debouncedSave();
41
49
  }
42
50
  catch {
@@ -50,6 +58,7 @@ export function startWatching(projectPath, onSnapshot) {
50
58
  // Only snapshot if content actually changed
51
59
  if (oldContent !== newContent) {
52
60
  fileContents.set(path, newContent);
61
+ pendingChanges.add(path);
53
62
  debouncedSave();
54
63
  }
55
64
  }
package/dist/mcp.js CHANGED
@@ -98,9 +98,9 @@ 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. CRITICAL: Always provide a summary parameter — it becomes the version description shown on pmptwiki.com. Without a summary, the version appears empty on the project page. Write a concise description of what was accomplished (e.g. "Added user authentication with JWT").', {
101
+ server.tool('pmpt_save', 'Save a snapshot of .pmpt/docs/ files. Call after completing features, fixes, or milestones. CRITICAL: Always provide a detailed summary parameter — it becomes the version description shown publicly on pmptwiki.com. Without a summary, the version appears empty on the project page. Write a DETAILED summary (3-5 sentences) that explains: (1) WHAT was built or changed, (2) WHY it matters, (3) key technical decisions made. Think of it as a mini dev blog entry that helps others learn from your journey.', {
102
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".'),
103
+ summary: z.string().optional().describe('Detailed description of what was accomplished since the last save. Write 3-5 sentences that tell the story: what you built, why, and how. This is shown publicly on the project page. BAD example: "Added auth" — too vague. GOOD example: "Implemented user authentication using JWT with refresh token rotation. Chose JWT over session-based auth for stateless API compatibility. Added login/signup pages with form validation and error handling. Protected routes now redirect unauthenticated users to login."'),
104
104
  }, async ({ projectPath, summary }) => {
105
105
  try {
106
106
  const pp = resolveProjectPath(projectPath);
@@ -118,7 +118,11 @@ server.tool('pmpt_save', 'Save a snapshot of .pmpt/docs/ files. Call after compl
118
118
  const snapshots = getAllSnapshots(pp);
119
119
  const nextVersion = snapshots.length + 1;
120
120
  const date = new Date().toISOString().slice(0, 10);
121
- const entry = `\n### v${nextVersion} ${date}\n- ${summary}\n`;
121
+ const summaryLines = summary.split(/(?:\.\s+|\n)/).filter(s => s.trim()).map(s => {
122
+ const trimmed = s.trim().replace(/\.?$/, '');
123
+ return `- ${trimmed}`;
124
+ });
125
+ const entry = `\n### v${nextVersion} — ${date}\n${summaryLines.join('\n')}\n`;
122
126
  const logIndex = content.indexOf('## Snapshot Log');
123
127
  if (logIndex !== -1) {
124
128
  const afterHeader = content.indexOf('\n', logIndex);
@@ -476,11 +480,11 @@ server.tool('pmpt_read_context', 'Read project context to understand current sta
476
480
  return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
477
481
  }
478
482
  });
479
- server.tool('pmpt_update_doc', 'Update pmpt.md: check off completed features, add progress notes, or append content. Use this after completing work to keep the project document up to date.', {
483
+ server.tool('pmpt_update_doc', 'Update pmpt.md: check off completed features, add progress notes, or backfill missing version summaries. Use after completing work OR before publishing to fill in empty Snapshot Log entries. To backfill: use pmpt_diff to understand what changed, then set snapshotVersion="v2 — Short Title" and progressNote with DETAILED multi-sentence description. Write as if explaining to another developer what happened in this version and why.', {
480
484
  projectPath: z.string().optional().describe('Project root path. Defaults to cwd.'),
481
485
  completedFeatures: z.array(z.string()).optional().describe('Feature names to mark as done (matches against checkbox items in pmpt.md).'),
482
- progressNote: z.string().optional().describe('Progress note to append to the Snapshot Log section.'),
483
- snapshotVersion: z.string().optional().describe('Version label for the snapshot log entry (e.g. "v3 - Auth Complete"). Auto-generated if omitted.'),
486
+ progressNote: z.string().optional().describe('Detailed progress note (3-5 sentences) to append to the Snapshot Log. Explain what was done, why, and any key decisions. Each sentence becomes a bullet point. BAD: "Fixed bugs". GOOD: "Fixed authentication redirect loop caused by token expiry race condition. Added token refresh middleware that silently renews expired tokens. Users no longer get logged out unexpectedly during long sessions."'),
487
+ snapshotVersion: z.string().optional().describe('Version label for the snapshot log entry (e.g. "v3 Auth Complete"). Auto-generated if omitted.'),
484
488
  }, async ({ projectPath, completedFeatures, progressNote, snapshotVersion }) => {
485
489
  try {
486
490
  const pp = resolveProjectPath(projectPath);
@@ -507,7 +511,11 @@ server.tool('pmpt_update_doc', 'Update pmpt.md: check off completed features, ad
507
511
  if (progressNote) {
508
512
  const snapshots = getAllSnapshots(pp);
509
513
  const label = snapshotVersion || `v${snapshots.length} - Progress`;
510
- const entry = `\n### ${label}\n- ${progressNote}\n`;
514
+ const noteLines = progressNote.split(/(?:\.\s+|\n)/).filter(s => s.trim()).map(s => {
515
+ const trimmed = s.trim().replace(/\.?$/, '');
516
+ return `- ${trimmed}`;
517
+ });
518
+ const entry = `\n### ${label}\n${noteLines.join('\n')}\n`;
511
519
  const logIndex = content.indexOf('## Snapshot Log');
512
520
  if (logIndex !== -1) {
513
521
  // Find the end of the Snapshot Log header line
@@ -582,7 +590,7 @@ server.tool('pmpt_log_decision', 'Record an architectural or technical decision
582
590
  return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
583
591
  }
584
592
  });
585
- server.tool('pmpt_publish', 'Publish the project to pmptwiki.com. Non-interactive — just provide slug and optional metadata. BEFORE publishing, verify: (1) Run pmpt_history to check all snapshots have meaningful summaries empty versions look bad on the project page. (2) If any versions lack descriptions, run pmpt_save with a summary or update pmpt.md Snapshot Log section with ### vN Title entries. (3) Run pmpt_quality to check publish readiness. Note: user must have run `pmpt login` once before.', {
593
+ server.tool('pmpt_publish', 'Publish the project to pmptwiki.com. Non-interactive — just provide slug and optional metadata. MANDATORY pre-publish checklist: (1) Run pmpt_history if ANY version lacks a note/summary, you MUST fix it before publishing. (2) For each empty version, run pmpt_diff for that version to understand what changed, then use pmpt_update_doc with a DETAILED progressNote (3-5 sentences explaining what, why, and key decisions) and snapshotVersion. Write like a dev blogothers will read this to learn from your journey. (3) After backfilling all versions, run pmpt_save with a detailed summary. (4) Run pmpt_quality to verify readiness. DO NOT publish with empty or vague single-line versions — they display poorly on the project page. Note: user must have run `pmpt login` once before.', {
586
594
  projectPath: z.string().optional().describe('Project root path. Defaults to cwd.'),
587
595
  slug: z.string().describe('Project slug (3-50 chars, lowercase alphanumeric and hyphens).'),
588
596
  description: z.string().optional().describe('Project description (max 500 chars).'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmpt-cli",
3
- "version": "1.14.5",
3
+ "version": "1.14.7",
4
4
  "description": "Record and share your AI-driven product development journey",
5
5
  "type": "module",
6
6
  "bin": {