pmpt-cli 1.14.21 → 1.17.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/README.md +31 -4
- package/dist/commands/constraint.js +92 -0
- package/dist/commands/doctor.js +220 -0
- package/dist/commands/init.js +70 -10
- package/dist/commands/mcp-setup.js +73 -9
- package/dist/commands/plan.js +12 -2
- package/dist/index.js +30 -1
- package/dist/lib/harness.js +179 -0
- package/dist/lib/plan.js +48 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
Plan with 5 questions. Build with AI. Save every version. Share and reproduce.
|
|
12
12
|
|
|
13
|
+
Every session, every decision, every constraint — accumulated in one file your AI can always read.
|
|
14
|
+
|
|
13
15
|
[Quick Start](#quick-start) · [Commands](#commands) · [MCP Server](#mcp-server) · [Explore Projects](#explore-projects)
|
|
14
16
|
|
|
15
17
|
</div>
|
|
@@ -65,6 +67,7 @@ pmpt explore
|
|
|
65
67
|
| | Without pmpt | With pmpt |
|
|
66
68
|
|---|---|---|
|
|
67
69
|
| **Planning** | Stare at blank screen, write vague prompts | Answer 5 guided questions, get structured AI prompt |
|
|
70
|
+
| **Context** | Re-explain the same decisions every AI session | AI reads pmpt.md and knows why things are the way they are |
|
|
68
71
|
| **Tracking** | Lose track of what you built and how | Every version auto-saved with full history |
|
|
69
72
|
| **Sharing** | Share finished code only | Share the entire journey — others can reproduce it |
|
|
70
73
|
|
|
@@ -92,8 +95,13 @@ The generated prompt is **automatically copied to your clipboard**. Just paste i
|
|
|
92
95
|
|
|
93
96
|
| Command | Description |
|
|
94
97
|
|---------|-------------|
|
|
95
|
-
| `pmpt init` | Initialize project
|
|
98
|
+
| `pmpt init` | Initialize project — sets up `.pmpt/`, selects AI tool, creates entry points |
|
|
96
99
|
| `pmpt plan` | 5 questions → AI prompt (auto-copied to clipboard) |
|
|
100
|
+
| `pmpt plan --template` | Generate a fillable `answers.json` (Windows/PowerShell friendly) |
|
|
101
|
+
| `pmpt plan --answers-file <f>` | Run plan non-interactively from a JSON file |
|
|
102
|
+
| `pmpt constraint add "<rule>"` | Add an architecture rule to pmpt.md |
|
|
103
|
+
| `pmpt constraint list` | List all constraints |
|
|
104
|
+
| `pmpt constraint remove <n>` | Remove a constraint by index |
|
|
97
105
|
| `pmpt save` | Save current state as a snapshot |
|
|
98
106
|
| `pmpt watch` | Auto-detect file changes and save versions |
|
|
99
107
|
| `pmpt status` | Check project status, tracked files, and quality score |
|
|
@@ -213,18 +221,37 @@ All tools accept an optional `projectPath` parameter (defaults to cwd).
|
|
|
213
221
|
|
|
214
222
|
```
|
|
215
223
|
your-project/
|
|
224
|
+
├── CLAUDE.md # Claude Code entry point → points to .pmpt/index.md
|
|
225
|
+
├── .cursorrules # Cursor entry point → points to .pmpt/index.md
|
|
226
|
+
├── AGENTS.md # Codex entry point → points to .pmpt/index.md
|
|
216
227
|
└── .pmpt/
|
|
228
|
+
├── index.md # AI context map (entry point for all tools)
|
|
217
229
|
├── config.json # Project configuration
|
|
218
|
-
├── docs/
|
|
230
|
+
├── docs/
|
|
231
|
+
│ ├── pmpt.md # Single source of truth — architecture, constraints,
|
|
232
|
+
│ │ # decisions, lessons, progress, snapshot log
|
|
219
233
|
│ ├── plan.md # Product plan (features checklist)
|
|
220
|
-
│
|
|
221
|
-
│ └── pmpt.ai.md # AI-ready prompt (project context & instructions)
|
|
234
|
+
│ └── pmpt.ai.md # AI-ready prompt (paste into your AI tool)
|
|
222
235
|
└── .history/ # Auto-saved version history
|
|
223
236
|
├── v1-2024-02-20/
|
|
224
237
|
├── v2-2024-02-21/
|
|
225
238
|
└── ...
|
|
226
239
|
```
|
|
227
240
|
|
|
241
|
+
### pmpt.md — Single Source of Truth
|
|
242
|
+
|
|
243
|
+
`pmpt.md` is the living document that your AI reads at the start of every session. It accumulates everything the AI needs to understand your project:
|
|
244
|
+
|
|
245
|
+
| Section | What to record |
|
|
246
|
+
|---------|---------------|
|
|
247
|
+
| `## Architecture` | High-level structure — update as it evolves |
|
|
248
|
+
| `## Active Work` | What's currently being built (clear when done) |
|
|
249
|
+
| `## Decisions` | WHY, not just WHAT — include what led to each decision |
|
|
250
|
+
| `## Constraints` | Platform/library limitations and workarounds |
|
|
251
|
+
| `## Lessons` | Anti-patterns: what failed → root cause → fix applied |
|
|
252
|
+
|
|
253
|
+
The more you fill this in, the less you re-explain to AI every session.
|
|
254
|
+
|
|
228
255
|
---
|
|
229
256
|
|
|
230
257
|
## .pmpt File Format
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { isInitialized } from '../lib/config.js';
|
|
4
|
+
import { readConstraints, addConstraint, removeConstraint } from '../lib/harness.js';
|
|
5
|
+
export async function cmdConstraint(action, options = {}) {
|
|
6
|
+
const projectPath = resolve(process.cwd());
|
|
7
|
+
if (!isInitialized(projectPath)) {
|
|
8
|
+
p.intro('pmpt constraint');
|
|
9
|
+
p.log.error('Project not initialized. Run `pmpt init` first.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
if (action === 'list') {
|
|
13
|
+
const constraints = readConstraints(projectPath);
|
|
14
|
+
p.intro('pmpt constraint list');
|
|
15
|
+
if (constraints.length === 0) {
|
|
16
|
+
p.log.info('No constraints defined yet.');
|
|
17
|
+
p.log.message(` pmpt constraint add "<rule>"`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
p.log.message('');
|
|
21
|
+
constraints.forEach((rule, i) => {
|
|
22
|
+
p.log.message(` ${i + 1}. ${rule}`);
|
|
23
|
+
});
|
|
24
|
+
p.log.message('');
|
|
25
|
+
p.log.info(`${constraints.length} constraint(s) in .pmpt/docs/constraints.md`);
|
|
26
|
+
}
|
|
27
|
+
p.outro('');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (action === 'add') {
|
|
31
|
+
p.intro('pmpt constraint add');
|
|
32
|
+
let rule = options.rule;
|
|
33
|
+
if (!rule) {
|
|
34
|
+
const input = await p.text({
|
|
35
|
+
message: 'Enter constraint rule:',
|
|
36
|
+
placeholder: 'e.g., UI does not import Service directly',
|
|
37
|
+
validate: (v) => (!v ? 'Rule cannot be empty' : undefined),
|
|
38
|
+
});
|
|
39
|
+
if (p.isCancel(input)) {
|
|
40
|
+
p.cancel('Cancelled');
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
rule = input;
|
|
44
|
+
}
|
|
45
|
+
const ok = addConstraint(projectPath, rule);
|
|
46
|
+
if (!ok) {
|
|
47
|
+
p.log.error('Could not find ## Constraints section in .pmpt/docs/pmpt.md');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
p.log.success(`Added: ${rule}`);
|
|
51
|
+
p.log.info('File: .pmpt/docs/pmpt.md');
|
|
52
|
+
p.outro('');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (action === 'remove') {
|
|
56
|
+
p.intro('pmpt constraint remove');
|
|
57
|
+
const constraints = readConstraints(projectPath);
|
|
58
|
+
if (constraints.length === 0) {
|
|
59
|
+
p.log.warn('No constraints to remove.');
|
|
60
|
+
p.outro('');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let idx;
|
|
64
|
+
if (options.index) {
|
|
65
|
+
idx = parseInt(options.index, 10);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Interactive selection
|
|
69
|
+
const choice = await p.select({
|
|
70
|
+
message: 'Select constraint to remove:',
|
|
71
|
+
options: constraints.map((rule, i) => ({
|
|
72
|
+
value: String(i + 1),
|
|
73
|
+
label: `${i + 1}. ${rule}`,
|
|
74
|
+
})),
|
|
75
|
+
});
|
|
76
|
+
if (p.isCancel(choice)) {
|
|
77
|
+
p.cancel('Cancelled');
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
idx = parseInt(choice, 10);
|
|
81
|
+
}
|
|
82
|
+
const removed = removeConstraint(projectPath, idx);
|
|
83
|
+
if (removed === null) {
|
|
84
|
+
p.log.error(`No constraint at index ${idx}.`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
p.log.success(`Removed: ${removed}`);
|
|
88
|
+
}
|
|
89
|
+
p.outro('');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { detectPmptMcpPath, getWrapperStatus } from './mcp-setup.js';
|
|
7
|
+
function checkNodeVersion() {
|
|
8
|
+
const version = process.versions.node;
|
|
9
|
+
const major = parseInt(version.split('.')[0], 10);
|
|
10
|
+
if (major >= 18) {
|
|
11
|
+
return { label: 'Node.js', status: 'ok', detail: `v${version}` };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
label: 'Node.js',
|
|
15
|
+
status: 'fail',
|
|
16
|
+
detail: `v${version} (requires ≥18)`,
|
|
17
|
+
fix: 'Update Node.js: https://nodejs.org',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function checkNvm() {
|
|
21
|
+
const nvmDir = process.env.NVM_DIR || join(homedir(), '.nvm');
|
|
22
|
+
const isNvm = !!process.env.NVM_DIR || process.argv[1]?.includes('.nvm/');
|
|
23
|
+
if (!isNvm && !existsSync(nvmDir)) {
|
|
24
|
+
return { label: 'nvm', status: 'ok', detail: 'Not using nvm (no PATH issues)' };
|
|
25
|
+
}
|
|
26
|
+
// Check if default alias is set
|
|
27
|
+
let defaultAlias = '';
|
|
28
|
+
try {
|
|
29
|
+
const aliasFile = join(nvmDir, 'alias', 'default');
|
|
30
|
+
if (existsSync(aliasFile)) {
|
|
31
|
+
defaultAlias = readFileSync(aliasFile, 'utf-8').trim();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch { /* ignore */ }
|
|
35
|
+
if (defaultAlias) {
|
|
36
|
+
return {
|
|
37
|
+
label: 'nvm',
|
|
38
|
+
status: 'warn',
|
|
39
|
+
detail: `Active (default: ${defaultAlias}). MCP clients may not load nvm in non-interactive shells.`,
|
|
40
|
+
fix: 'Use "pmpt mcp-setup" to create a stable wrapper, or consider volta/fnm.',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
label: 'nvm',
|
|
45
|
+
status: 'warn',
|
|
46
|
+
detail: 'Active, no default alias set. MCP path may break across sessions.',
|
|
47
|
+
fix: 'Run "nvm alias default <version>" and "pmpt mcp-setup".',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function checkPmptMcpBinary() {
|
|
51
|
+
const path = detectPmptMcpPath();
|
|
52
|
+
if (path) {
|
|
53
|
+
return { label: 'pmpt-mcp binary', status: 'ok', detail: path };
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
label: 'pmpt-mcp binary',
|
|
57
|
+
status: 'fail',
|
|
58
|
+
detail: 'Not found in PATH or sibling directory',
|
|
59
|
+
fix: 'Run "npm install -g pmpt-cli" to install.',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function checkWrapper() {
|
|
63
|
+
const wrapper = getWrapperStatus();
|
|
64
|
+
if (!wrapper.exists) {
|
|
65
|
+
const isNvm = !!process.env.NVM_DIR || process.argv[1]?.includes('.nvm/');
|
|
66
|
+
if (isNvm) {
|
|
67
|
+
return {
|
|
68
|
+
label: 'Wrapper script',
|
|
69
|
+
status: 'warn',
|
|
70
|
+
detail: 'Not created (recommended for nvm users)',
|
|
71
|
+
fix: 'Run "pmpt mcp-setup" to create ~/.pmpt/bin/pmpt-mcp wrapper.',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { label: 'Wrapper script', status: 'ok', detail: 'Not needed (not using nvm)' };
|
|
75
|
+
}
|
|
76
|
+
if (wrapper.targetValid) {
|
|
77
|
+
return { label: 'Wrapper script', status: 'ok', detail: `OK → ${wrapper.targetPath}` };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
label: 'Wrapper script',
|
|
81
|
+
status: 'fail',
|
|
82
|
+
detail: `Stale — target binary missing: ${wrapper.targetPath}`,
|
|
83
|
+
fix: 'Run "pmpt mcp-setup" to update the wrapper after Node version change.',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function checkMcpClients() {
|
|
87
|
+
const results = [];
|
|
88
|
+
// Claude Code
|
|
89
|
+
try {
|
|
90
|
+
execSync('which claude', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
91
|
+
try {
|
|
92
|
+
const list = execSync('claude mcp list', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
93
|
+
if (list.includes('pmpt')) {
|
|
94
|
+
results.push({ label: 'Claude Code MCP', status: 'ok', detail: 'pmpt registered' });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
results.push({
|
|
98
|
+
label: 'Claude Code MCP',
|
|
99
|
+
status: 'warn',
|
|
100
|
+
detail: 'Claude Code found but pmpt not registered',
|
|
101
|
+
fix: 'Run "pmpt mcp-setup" and select Claude Code.',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
results.push({
|
|
107
|
+
label: 'Claude Code MCP',
|
|
108
|
+
status: 'warn',
|
|
109
|
+
detail: 'Claude Code found but "claude mcp list" failed',
|
|
110
|
+
fix: 'Make sure Claude Code is up to date.',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Claude Code not installed — skip
|
|
116
|
+
}
|
|
117
|
+
// Cursor
|
|
118
|
+
const cursorConfig = join(homedir(), '.cursor', 'mcp.json');
|
|
119
|
+
if (existsSync(join(homedir(), '.cursor'))) {
|
|
120
|
+
try {
|
|
121
|
+
if (existsSync(cursorConfig)) {
|
|
122
|
+
const content = JSON.parse(readFileSync(cursorConfig, 'utf-8'));
|
|
123
|
+
if (content?.mcpServers?.pmpt) {
|
|
124
|
+
const cmd = content.mcpServers.pmpt.command;
|
|
125
|
+
if (existsSync(cmd)) {
|
|
126
|
+
results.push({ label: 'Cursor MCP', status: 'ok', detail: `pmpt → ${cmd}` });
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
results.push({
|
|
130
|
+
label: 'Cursor MCP',
|
|
131
|
+
status: 'fail',
|
|
132
|
+
detail: `pmpt registered but binary missing: ${cmd}`,
|
|
133
|
+
fix: 'Run "pmpt mcp-setup" to update the path.',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
results.push({
|
|
139
|
+
label: 'Cursor MCP',
|
|
140
|
+
status: 'warn',
|
|
141
|
+
detail: 'Cursor found but pmpt not in mcp.json',
|
|
142
|
+
fix: 'Run "pmpt mcp-setup" and select Cursor.',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
results.push({ label: 'Cursor MCP', status: 'warn', detail: 'Could not parse ~/.cursor/mcp.json' });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Project .mcp.json
|
|
152
|
+
const projectMcp = join(process.cwd(), '.mcp.json');
|
|
153
|
+
if (existsSync(projectMcp)) {
|
|
154
|
+
try {
|
|
155
|
+
const content = JSON.parse(readFileSync(projectMcp, 'utf-8'));
|
|
156
|
+
if (content?.mcpServers?.pmpt) {
|
|
157
|
+
const cmd = content.mcpServers.pmpt.command;
|
|
158
|
+
if (existsSync(cmd)) {
|
|
159
|
+
results.push({ label: '.mcp.json', status: 'ok', detail: `pmpt → ${cmd}` });
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
results.push({
|
|
163
|
+
label: '.mcp.json',
|
|
164
|
+
status: 'fail',
|
|
165
|
+
detail: `pmpt registered but binary missing: ${cmd}`,
|
|
166
|
+
fix: 'Run "pmpt mcp-setup" to update the path.',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
results.push({ label: '.mcp.json', status: 'warn', detail: 'Could not parse .mcp.json' });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (results.length === 0) {
|
|
176
|
+
results.push({
|
|
177
|
+
label: 'MCP clients',
|
|
178
|
+
status: 'warn',
|
|
179
|
+
detail: 'No MCP client configuration found',
|
|
180
|
+
fix: 'Run "pmpt mcp-setup" to configure an AI tool.',
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return results;
|
|
184
|
+
}
|
|
185
|
+
const icons = { ok: '✓', warn: '⚠', fail: '✗' };
|
|
186
|
+
const colors = { ok: '\x1b[32m', warn: '\x1b[33m', fail: '\x1b[31m' };
|
|
187
|
+
const reset = '\x1b[0m';
|
|
188
|
+
export async function cmdDoctor() {
|
|
189
|
+
p.intro('pmpt doctor');
|
|
190
|
+
const s = p.spinner();
|
|
191
|
+
s.start('Running diagnostics...');
|
|
192
|
+
const checks = [
|
|
193
|
+
checkNodeVersion(),
|
|
194
|
+
checkNvm(),
|
|
195
|
+
checkPmptMcpBinary(),
|
|
196
|
+
checkWrapper(),
|
|
197
|
+
...checkMcpClients(),
|
|
198
|
+
];
|
|
199
|
+
s.stop('Diagnostics complete');
|
|
200
|
+
// Display results
|
|
201
|
+
const lines = [];
|
|
202
|
+
let hasIssues = false;
|
|
203
|
+
for (const check of checks) {
|
|
204
|
+
const icon = icons[check.status];
|
|
205
|
+
const color = colors[check.status];
|
|
206
|
+
lines.push(`${color}${icon}${reset} ${check.label}: ${check.detail}`);
|
|
207
|
+
if (check.fix) {
|
|
208
|
+
lines.push(` → ${check.fix}`);
|
|
209
|
+
hasIssues = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
p.note(lines.join('\n'), 'Diagnostic Results');
|
|
213
|
+
if (hasIssues) {
|
|
214
|
+
p.log.warn('Some issues found. Run the suggested fixes above.');
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
p.log.success('Everything looks good!');
|
|
218
|
+
}
|
|
219
|
+
p.outro('');
|
|
220
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -5,8 +5,9 @@ import { initializeProject, isInitialized, getDocsDir, ensurePmptClaudeMd, ensur
|
|
|
5
5
|
import { isGitRepo, getGitInfo, formatGitInfo, getCommitCount } from '../lib/git.js';
|
|
6
6
|
import { cmdPlan } from './plan.js';
|
|
7
7
|
import { scanProject, scanResultToAnswers } from '../lib/scanner.js';
|
|
8
|
-
import { savePlanDocuments, initPlanProgress, savePlanProgress } from '../lib/plan.js';
|
|
8
|
+
import { savePlanDocuments, initPlanProgress, savePlanProgress, generateAnswersTemplate } from '../lib/plan.js';
|
|
9
9
|
import { copyToClipboard } from '../lib/clipboard.js';
|
|
10
|
+
import { setupHarnessForTools } from '../lib/harness.js';
|
|
10
11
|
export async function cmdInit(path, options) {
|
|
11
12
|
p.intro('pmpt init');
|
|
12
13
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
@@ -100,14 +101,34 @@ export async function cmdInit(path, options) {
|
|
|
100
101
|
// Add pmpt MCP instructions to CLAUDE.md and register .mcp.json for Claude Code
|
|
101
102
|
ensurePmptClaudeMd(projectPath);
|
|
102
103
|
ensureMcpJson(projectPath);
|
|
104
|
+
// Ask which AI tool to set up harness for
|
|
105
|
+
const toolChoice = await p.select({
|
|
106
|
+
message: 'Which AI coding tool do you use?',
|
|
107
|
+
options: [
|
|
108
|
+
{ value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + .pmpt/index.md' },
|
|
109
|
+
{ value: 'cursor', label: 'Cursor', hint: '.cursorrules + .pmpt/index.md' },
|
|
110
|
+
{ value: 'codex', label: 'Codex', hint: 'AGENTS.md + .pmpt/index.md' },
|
|
111
|
+
{ value: 'all', label: 'All of the above', hint: 'Create all entry points' },
|
|
112
|
+
{ value: 'skip', label: 'Skip', hint: 'Set up later with `pmpt harness`' },
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
if (p.isCancel(toolChoice)) {
|
|
116
|
+
p.cancel('Cancelled');
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
if (toolChoice !== 'skip') {
|
|
120
|
+
setupHarnessForTools(projectPath, basename(projectPath), toolChoice);
|
|
121
|
+
}
|
|
103
122
|
// Build folder structure display
|
|
104
123
|
const notes = [
|
|
105
124
|
`Path: ${config.projectPath}`,
|
|
106
125
|
'',
|
|
107
126
|
'Folder structure:',
|
|
108
127
|
` .pmpt/`,
|
|
128
|
+
` ├── index.md AI entry point`,
|
|
109
129
|
` ├── config.json Config`,
|
|
110
|
-
` ├── docs
|
|
130
|
+
` ├── docs/`,
|
|
131
|
+
` │ └── pmpt.md Source of truth (architecture, constraints, decisions)`,
|
|
111
132
|
` └── .history/ Snapshots`,
|
|
112
133
|
];
|
|
113
134
|
if (config.repo) {
|
|
@@ -155,6 +176,7 @@ export async function cmdInit(path, options) {
|
|
|
155
176
|
options: [
|
|
156
177
|
{ value: 'auto', label: 'Auto-generate plan', hint: 'Recommended — instant AI prompt from project analysis' },
|
|
157
178
|
{ value: 'manual', label: 'Manual planning', hint: '5 questions interactive flow' },
|
|
179
|
+
{ value: 'file', label: 'Fill in answers file', hint: 'Windows/PowerShell friendly — edit a JSON file then run pmpt plan --answers-file' },
|
|
158
180
|
{ value: 'skip', label: 'Skip for now' },
|
|
159
181
|
],
|
|
160
182
|
});
|
|
@@ -162,6 +184,17 @@ export async function cmdInit(path, options) {
|
|
|
162
184
|
p.cancel('Cancelled');
|
|
163
185
|
process.exit(0);
|
|
164
186
|
}
|
|
187
|
+
if (scanChoice === 'file') {
|
|
188
|
+
const outFile = 'answers.json';
|
|
189
|
+
const template = generateAnswersTemplate();
|
|
190
|
+
writeFileSync(resolve(projectPath, outFile), JSON.stringify(template, null, 2), 'utf-8');
|
|
191
|
+
p.log.success(`Template created: ${outFile}`);
|
|
192
|
+
p.log.info('Next steps:');
|
|
193
|
+
p.log.message(` 1. Open and fill in ${outFile}`);
|
|
194
|
+
p.log.message(` 2. pmpt plan --answers-file ${outFile}`);
|
|
195
|
+
p.outro('Ready!');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
165
198
|
if (scanChoice === 'auto') {
|
|
166
199
|
// Ask for project description
|
|
167
200
|
const defaultDesc = scanResult.readmeDescription
|
|
@@ -236,23 +269,41 @@ export async function cmdInit(path, options) {
|
|
|
236
269
|
}
|
|
237
270
|
else {
|
|
238
271
|
ensureMinimalDocs(projectPath);
|
|
272
|
+
p.log.info('Tip: On Windows/PowerShell, use a template file to avoid paste issues:');
|
|
273
|
+
p.log.message(' pmpt plan --template # creates answers.json');
|
|
274
|
+
p.log.message(' pmpt plan --answers-file answers.json');
|
|
239
275
|
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
240
276
|
}
|
|
241
277
|
}
|
|
242
278
|
else {
|
|
243
279
|
// New/empty project — original flow
|
|
244
|
-
const startPlan = await p.
|
|
245
|
-
message: 'Start planning?
|
|
246
|
-
|
|
280
|
+
const startPlan = await p.select({
|
|
281
|
+
message: 'Start planning?',
|
|
282
|
+
options: [
|
|
283
|
+
{ value: 'yes', label: 'Yes — answer 5 questions now', hint: 'Interactive flow' },
|
|
284
|
+
{ value: 'file', label: 'Fill in answers file', hint: 'Windows/PowerShell friendly — edit a JSON file then run pmpt plan --answers-file' },
|
|
285
|
+
{ value: 'no', label: 'Skip for now' },
|
|
286
|
+
],
|
|
247
287
|
});
|
|
248
|
-
if (
|
|
249
|
-
p.log.message('');
|
|
250
|
-
await cmdPlan(projectPath);
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
288
|
+
if (p.isCancel(startPlan) || startPlan === 'no') {
|
|
253
289
|
ensureMinimalDocs(projectPath);
|
|
254
290
|
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
255
291
|
}
|
|
292
|
+
else if (startPlan === 'file') {
|
|
293
|
+
const outFile = 'answers.json';
|
|
294
|
+
const template = generateAnswersTemplate();
|
|
295
|
+
writeFileSync(resolve(projectPath, outFile), JSON.stringify(template, null, 2), 'utf-8');
|
|
296
|
+
ensureMinimalDocs(projectPath);
|
|
297
|
+
p.log.success(`Template created: ${outFile}`);
|
|
298
|
+
p.log.info('Next steps:');
|
|
299
|
+
p.log.message(` 1. Open and fill in ${outFile}`);
|
|
300
|
+
p.log.message(` 2. pmpt plan --answers-file ${outFile}`);
|
|
301
|
+
p.outro('Ready!');
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
p.log.message('');
|
|
305
|
+
await cmdPlan(projectPath);
|
|
306
|
+
}
|
|
256
307
|
}
|
|
257
308
|
}
|
|
258
309
|
catch (error) {
|
|
@@ -271,16 +322,25 @@ function ensureMinimalDocs(projectPath) {
|
|
|
271
322
|
const skeleton = [
|
|
272
323
|
`# ${name}`,
|
|
273
324
|
'',
|
|
325
|
+
'## Architecture',
|
|
326
|
+
'<!-- High-level structure. Update as it evolves. -->',
|
|
327
|
+
'',
|
|
328
|
+
'## Active Work',
|
|
329
|
+
'<!-- What\'s currently being built. Clear when done, move to Snapshot Log. -->',
|
|
330
|
+
'',
|
|
274
331
|
'## Progress',
|
|
275
332
|
'- Project initialized',
|
|
276
333
|
'',
|
|
277
334
|
'## Snapshot Log',
|
|
278
335
|
'',
|
|
279
336
|
'## Decisions',
|
|
337
|
+
'<!-- WHY, not just WHAT. Format: - [Decision] → [Reason] -->',
|
|
280
338
|
'',
|
|
281
339
|
'## Constraints',
|
|
340
|
+
'<!-- Platform/library limitations. Format: - [Tool]: limitation → workaround -->',
|
|
282
341
|
'',
|
|
283
342
|
'## Lessons',
|
|
343
|
+
'<!-- Anti-patterns. Format: - [What failed] → [Root cause] → [Fix applied] -->',
|
|
284
344
|
'',
|
|
285
345
|
].join('\n');
|
|
286
346
|
writeFileSync(pmptMdPath, skeleton, 'utf-8');
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
|
|
4
4
|
import { join, dirname } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
|
-
|
|
6
|
+
/** Stable wrapper path that survives nvm version switches */
|
|
7
|
+
const WRAPPER_DIR = join(homedir(), '.pmpt', 'bin');
|
|
8
|
+
const WRAPPER_PATH = join(WRAPPER_DIR, 'pmpt-mcp');
|
|
9
|
+
export function detectPmptMcpPath() {
|
|
7
10
|
// Strategy 1: sibling to the current pmpt binary (same bin directory)
|
|
8
11
|
const pmptBin = process.argv[1];
|
|
9
12
|
const siblingPath = join(dirname(pmptBin), 'pmpt-mcp');
|
|
@@ -24,6 +27,51 @@ function detectPmptMcpPath() {
|
|
|
24
27
|
}
|
|
25
28
|
return null;
|
|
26
29
|
}
|
|
30
|
+
/** Detect if nvm is in use */
|
|
31
|
+
function isNvmEnvironment() {
|
|
32
|
+
return !!process.env.NVM_DIR || process.argv[1]?.includes('.nvm/');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a wrapper script at ~/.pmpt/bin/pmpt-mcp that:
|
|
36
|
+
* 1. Loads nvm if available
|
|
37
|
+
* 2. Executes the real pmpt-mcp binary
|
|
38
|
+
* This makes the MCP path stable across Node version changes.
|
|
39
|
+
*/
|
|
40
|
+
function createWrapperScript(realBinaryPath) {
|
|
41
|
+
mkdirSync(WRAPPER_DIR, { recursive: true });
|
|
42
|
+
const nvmDir = process.env.NVM_DIR || join(homedir(), '.nvm');
|
|
43
|
+
const script = `#!/bin/bash
|
|
44
|
+
# pmpt-mcp wrapper — loads nvm then runs the real binary
|
|
45
|
+
# Auto-generated by pmpt mcp-setup. Re-run "pmpt mcp-setup" to update.
|
|
46
|
+
|
|
47
|
+
export NVM_DIR="${nvmDir}"
|
|
48
|
+
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
|
49
|
+
|
|
50
|
+
exec "${realBinaryPath}" "$@"
|
|
51
|
+
`;
|
|
52
|
+
writeFileSync(WRAPPER_PATH, script, { mode: 0o755 });
|
|
53
|
+
chmodSync(WRAPPER_PATH, 0o755);
|
|
54
|
+
return WRAPPER_PATH;
|
|
55
|
+
}
|
|
56
|
+
/** Check if wrapper exists and the target binary it points to is still valid */
|
|
57
|
+
export function getWrapperStatus() {
|
|
58
|
+
if (!existsSync(WRAPPER_PATH)) {
|
|
59
|
+
return { exists: false, targetValid: false, targetPath: null };
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const content = readFileSync(WRAPPER_PATH, 'utf-8');
|
|
63
|
+
const match = content.match(/exec "(.+?)"/);
|
|
64
|
+
const targetPath = match?.[1] || null;
|
|
65
|
+
return {
|
|
66
|
+
exists: true,
|
|
67
|
+
targetValid: targetPath ? existsSync(targetPath) : false,
|
|
68
|
+
targetPath,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { exists: true, targetValid: false, targetPath: null };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
27
75
|
function isCommandAvailable(cmd) {
|
|
28
76
|
try {
|
|
29
77
|
const which = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -55,14 +103,15 @@ function isJsonConfigured(configPath) {
|
|
|
55
103
|
}
|
|
56
104
|
}
|
|
57
105
|
function configureClaudeCode(mcpBinaryPath) {
|
|
58
|
-
// Remove existing entry first (ignore errors if not found)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
106
|
+
// Remove existing entry at any scope first (ignore errors if not found)
|
|
107
|
+
for (const scope of ['user', 'local', 'project']) {
|
|
108
|
+
try {
|
|
109
|
+
execSync(`claude mcp remove --scope ${scope} pmpt`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
110
|
+
}
|
|
111
|
+
catch { /* not configured at this scope */ }
|
|
64
112
|
}
|
|
65
|
-
|
|
113
|
+
// Register at user scope so it works across all projects
|
|
114
|
+
execSync(`claude mcp add --scope user --transport stdio pmpt -- "${mcpBinaryPath}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
66
115
|
}
|
|
67
116
|
function configureJsonFile(configPath, mcpBinaryPath) {
|
|
68
117
|
let config = {};
|
|
@@ -93,6 +142,21 @@ export async function cmdMcpSetup() {
|
|
|
93
142
|
let mcpPath = detectPmptMcpPath();
|
|
94
143
|
if (mcpPath) {
|
|
95
144
|
s.stop(`Found: ${mcpPath}`);
|
|
145
|
+
// If nvm environment, offer to create a stable wrapper
|
|
146
|
+
if (isNvmEnvironment()) {
|
|
147
|
+
p.log.warn('nvm detected — the binary path may break when you switch Node versions.');
|
|
148
|
+
const useWrapper = await p.confirm({
|
|
149
|
+
message: 'Create a stable wrapper at ~/.pmpt/bin/pmpt-mcp? (recommended for nvm users)',
|
|
150
|
+
initialValue: true,
|
|
151
|
+
});
|
|
152
|
+
if (!p.isCancel(useWrapper) && useWrapper) {
|
|
153
|
+
const wrapperPath = createWrapperScript(mcpPath);
|
|
154
|
+
p.log.success(`Wrapper created: ${wrapperPath}`);
|
|
155
|
+
p.log.info(`Points to: ${mcpPath}`);
|
|
156
|
+
p.log.info('After switching Node versions, run "pmpt mcp-setup" to update the wrapper.');
|
|
157
|
+
mcpPath = wrapperPath;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
96
160
|
}
|
|
97
161
|
else {
|
|
98
162
|
s.stop('Could not auto-detect pmpt-mcp path');
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
4
4
|
import { isInitialized, loadConfig } from '../lib/config.js';
|
|
5
5
|
import { copyToClipboard } from '../lib/clipboard.js';
|
|
6
6
|
import { cmdWatch } from './watch.js';
|
|
7
|
-
import { PLAN_QUESTIONS, getPlanProgress, initPlanProgress, savePlanProgress, savePlanDocuments, } from '../lib/plan.js';
|
|
7
|
+
import { PLAN_QUESTIONS, getPlanProgress, initPlanProgress, savePlanProgress, savePlanDocuments, generateAnswersTemplate, } from '../lib/plan.js';
|
|
8
8
|
function loadAnswersFromFile(projectPath, inputPath) {
|
|
9
9
|
const filePath = resolve(projectPath, inputPath);
|
|
10
10
|
if (!existsSync(filePath)) {
|
|
@@ -27,6 +27,16 @@ function loadAnswersFromFile(projectPath, inputPath) {
|
|
|
27
27
|
}
|
|
28
28
|
export async function cmdPlan(path, options) {
|
|
29
29
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
30
|
+
// Template generation (no init check needed)
|
|
31
|
+
if (options?.template) {
|
|
32
|
+
const outFile = resolve(projectPath, options.template);
|
|
33
|
+
const template = generateAnswersTemplate();
|
|
34
|
+
writeFileSync(outFile, JSON.stringify(template, null, 2), 'utf-8');
|
|
35
|
+
p.log.success(`Template created: ${outFile}`);
|
|
36
|
+
p.log.info('Fill in the fields and run:');
|
|
37
|
+
p.log.message(` pmpt plan --answers-file ${options.template}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
30
40
|
// Check initialization
|
|
31
41
|
if (!isInitialized(projectPath)) {
|
|
32
42
|
p.intro('pmpt plan');
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,8 @@ import { cmdRecover } from './commands/recover.js';
|
|
|
45
45
|
import { cmdDiff } from './commands/diff.js';
|
|
46
46
|
import { cmdInternalSeed } from './commands/internal-seed.js';
|
|
47
47
|
import { cmdMcpSetup } from './commands/mcp-setup.js';
|
|
48
|
+
import { cmdDoctor } from './commands/doctor.js';
|
|
49
|
+
import { cmdConstraint } from './commands/constraint.js';
|
|
48
50
|
import { trackCommand } from './lib/api.js';
|
|
49
51
|
import { checkForUpdates } from './lib/update-check.js';
|
|
50
52
|
import { createRequire } from 'module';
|
|
@@ -85,6 +87,7 @@ Examples:
|
|
|
85
87
|
$ pmpt graduate Graduate a project (Hall of Fame)
|
|
86
88
|
$ pmpt recover Recover damaged pmpt.md via AI
|
|
87
89
|
$ pmpt mcp-setup Configure MCP for AI tools
|
|
90
|
+
$ pmpt doctor (doc) Diagnose MCP & environment issues
|
|
88
91
|
$ pmpt feedback (fb) Share ideas or report bugs
|
|
89
92
|
|
|
90
93
|
Workflow:
|
|
@@ -146,7 +149,28 @@ program
|
|
|
146
149
|
.description('Quick product planning with 5 questions — auto-generate AI prompt')
|
|
147
150
|
.option('--reset', 'Restart plan from scratch')
|
|
148
151
|
.option('--answers-file <file>', 'Load plan answers from JSON file (non-interactive)')
|
|
149
|
-
.
|
|
152
|
+
.option('--template [file]', 'Generate a fillable answers template (default: answers.json)')
|
|
153
|
+
.action((path, options) => cmdPlan(path, {
|
|
154
|
+
...options,
|
|
155
|
+
template: options.template === true ? 'answers.json' : options.template,
|
|
156
|
+
}));
|
|
157
|
+
program
|
|
158
|
+
.command('constraint <action>')
|
|
159
|
+
.description('Manage architecture constraints — add, list, or remove rules')
|
|
160
|
+
.option('-r, --rule <rule>', 'Rule text (for add)')
|
|
161
|
+
.option('-i, --index <n>', 'Rule index (for remove)')
|
|
162
|
+
.addHelpText('after', `
|
|
163
|
+
Actions:
|
|
164
|
+
add Add a constraint rule
|
|
165
|
+
list List all constraints
|
|
166
|
+
remove Remove a constraint by index
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
pmpt constraint add "UI does not import Service directly"
|
|
170
|
+
pmpt constraint list
|
|
171
|
+
pmpt constraint remove 2
|
|
172
|
+
`)
|
|
173
|
+
.action((action, options) => cmdConstraint(action, options));
|
|
150
174
|
program
|
|
151
175
|
.command('logout')
|
|
152
176
|
.description('Clear saved GitHub authentication')
|
|
@@ -214,6 +238,11 @@ program
|
|
|
214
238
|
.command('mcp-setup')
|
|
215
239
|
.description('Configure pmpt MCP server for AI tools (Claude Code, Cursor, etc.)')
|
|
216
240
|
.action(cmdMcpSetup);
|
|
241
|
+
program
|
|
242
|
+
.command('doctor')
|
|
243
|
+
.alias('doc')
|
|
244
|
+
.description('Diagnose MCP connection, PATH issues, and environment health')
|
|
245
|
+
.action(cmdDoctor);
|
|
217
246
|
program
|
|
218
247
|
.command('feedback')
|
|
219
248
|
.alias('fb')
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { getConfigDir, getDocsDir } from './config.js';
|
|
4
|
+
// ─── index.md — lightweight map pointing to pmpt.md ───────────────
|
|
5
|
+
export function generateIndexMd(projectName) {
|
|
6
|
+
return `# ${projectName} — Project Context
|
|
7
|
+
|
|
8
|
+
> AI entry point. Read this first, then follow the links below.
|
|
9
|
+
|
|
10
|
+
## Map
|
|
11
|
+
|
|
12
|
+
| File | Purpose |
|
|
13
|
+
|------|---------|
|
|
14
|
+
| [.pmpt/docs/pmpt.md](.pmpt/docs/pmpt.md) | **Source of truth** — architecture, constraints, decisions, progress |
|
|
15
|
+
| [.pmpt/docs/pmpt.ai.md](.pmpt/docs/pmpt.ai.md) | Full AI prompt — paste into your AI tool to get started |
|
|
16
|
+
|
|
17
|
+
## Rules for AI
|
|
18
|
+
|
|
19
|
+
1. Read \`.pmpt/docs/pmpt.md\` before making any code changes
|
|
20
|
+
2. Follow every rule listed in the **## Constraints** section of pmpt.md
|
|
21
|
+
3. After milestones, call \`mcp__pmpt__pmpt_save\` (or \`pmpt save\` in terminal)
|
|
22
|
+
4. Update pmpt.md when architecture, constraints, or features change
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
// ─── Tool-specific entry points ────────────────────────────────────
|
|
26
|
+
export function generateCursorRules() {
|
|
27
|
+
return `# Project Rules
|
|
28
|
+
|
|
29
|
+
See \`.pmpt/index.md\` for the project context map.
|
|
30
|
+
|
|
31
|
+
The single source of truth is \`.pmpt/docs/pmpt.md\`.
|
|
32
|
+
Always check the **## Constraints** section before making code changes.
|
|
33
|
+
After completing milestones, run \`pmpt save\` in terminal.
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
export function generateAgentsMd(projectName) {
|
|
37
|
+
return `# ${projectName} — Agent Instructions
|
|
38
|
+
|
|
39
|
+
See \`.pmpt/index.md\` for the project context map.
|
|
40
|
+
|
|
41
|
+
The single source of truth is \`.pmpt/docs/pmpt.md\`.
|
|
42
|
+
Always check the **## Constraints** section before making code changes.
|
|
43
|
+
After completing milestones, run \`pmpt save\` in terminal.
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
// ─── Setup functions ───────────────────────────────────────────────
|
|
47
|
+
export function ensureIndexMd(projectPath, projectName) {
|
|
48
|
+
const configDir = getConfigDir(projectPath);
|
|
49
|
+
mkdirSync(configDir, { recursive: true });
|
|
50
|
+
const indexPath = join(configDir, 'index.md');
|
|
51
|
+
if (!existsSync(indexPath)) {
|
|
52
|
+
writeFileSync(indexPath, generateIndexMd(projectName), 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function ensureCursorRules(projectPath) {
|
|
56
|
+
const path = join(projectPath, '.cursorrules');
|
|
57
|
+
if (!existsSync(path)) {
|
|
58
|
+
writeFileSync(path, generateCursorRules(), 'utf-8');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function ensureAgentsMd(projectPath, projectName) {
|
|
62
|
+
const path = join(projectPath, 'AGENTS.md');
|
|
63
|
+
if (!existsSync(path)) {
|
|
64
|
+
writeFileSync(path, generateAgentsMd(projectName), 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function setupHarnessForTools(projectPath, projectName, tools) {
|
|
68
|
+
// index.md is always created — it's the lightweight map
|
|
69
|
+
ensureIndexMd(projectPath, projectName);
|
|
70
|
+
if (tools === 'cursor' || tools === 'all') {
|
|
71
|
+
ensureCursorRules(projectPath);
|
|
72
|
+
}
|
|
73
|
+
if (tools === 'codex' || tools === 'all') {
|
|
74
|
+
ensureAgentsMd(projectPath, projectName);
|
|
75
|
+
}
|
|
76
|
+
if (tools === 'claude' || tools === 'all') {
|
|
77
|
+
ensureClaudeMdIndexRef(projectPath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function ensureClaudeMdIndexRef(projectPath) {
|
|
81
|
+
const claudeMdPath = join(projectPath, 'CLAUDE.md');
|
|
82
|
+
const marker = '<!-- pmpt-index -->';
|
|
83
|
+
const section = `${marker}\nSee \`.pmpt/index.md\` for project context. Single source of truth: \`.pmpt/docs/pmpt.md\`.\n<!-- /pmpt-index -->`;
|
|
84
|
+
if (!existsSync(claudeMdPath))
|
|
85
|
+
return;
|
|
86
|
+
const content = readFileSync(claudeMdPath, 'utf-8');
|
|
87
|
+
if (content.includes(marker))
|
|
88
|
+
return;
|
|
89
|
+
const pmptMarker = '<!-- pmpt -->';
|
|
90
|
+
if (content.includes(pmptMarker)) {
|
|
91
|
+
writeFileSync(claudeMdPath, content.replace(pmptMarker, section + '\n\n' + pmptMarker), 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
writeFileSync(claudeMdPath, section + '\n\n' + content, 'utf-8');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// ─── Constraint management — reads/writes pmpt.md ## Constraints ──
|
|
98
|
+
export function readConstraints(projectPath) {
|
|
99
|
+
const pmptMdPath = join(getDocsDir(projectPath), 'pmpt.md');
|
|
100
|
+
if (!existsSync(pmptMdPath))
|
|
101
|
+
return [];
|
|
102
|
+
const lines = readFileSync(pmptMdPath, 'utf-8').split('\n');
|
|
103
|
+
const inSection = { value: false };
|
|
104
|
+
const rules = [];
|
|
105
|
+
for (const line of lines) {
|
|
106
|
+
if (line.trim() === '## Constraints') {
|
|
107
|
+
inSection.value = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (inSection.value && line.startsWith('## '))
|
|
111
|
+
break;
|
|
112
|
+
if (inSection.value && line.trimStart().startsWith('- ') && !line.includes('<!--')) {
|
|
113
|
+
rules.push(line.trimStart().slice(2).trim());
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return rules;
|
|
117
|
+
}
|
|
118
|
+
export function addConstraint(projectPath, rule) {
|
|
119
|
+
const pmptMdPath = join(getDocsDir(projectPath), 'pmpt.md');
|
|
120
|
+
if (!existsSync(pmptMdPath))
|
|
121
|
+
return false;
|
|
122
|
+
const lines = readFileSync(pmptMdPath, 'utf-8').split('\n');
|
|
123
|
+
let sectionEnd = -1;
|
|
124
|
+
let inSection = false;
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
if (lines[i].trim() === '## Constraints') {
|
|
127
|
+
inSection = true;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (inSection && lines[i].startsWith('## ')) {
|
|
131
|
+
sectionEnd = i;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const newLine = `- ${rule}`;
|
|
136
|
+
if (sectionEnd !== -1) {
|
|
137
|
+
// Insert before next section, after any existing content
|
|
138
|
+
lines.splice(sectionEnd, 0, newLine);
|
|
139
|
+
}
|
|
140
|
+
else if (inSection) {
|
|
141
|
+
lines.push(newLine);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
writeFileSync(pmptMdPath, lines.join('\n'), 'utf-8');
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
export function removeConstraint(projectPath, index) {
|
|
150
|
+
const pmptMdPath = join(getDocsDir(projectPath), 'pmpt.md');
|
|
151
|
+
if (!existsSync(pmptMdPath))
|
|
152
|
+
return null;
|
|
153
|
+
const lines = readFileSync(pmptMdPath, 'utf-8').split('\n');
|
|
154
|
+
let inSection = false;
|
|
155
|
+
let ruleCount = 0;
|
|
156
|
+
let removedRule = null;
|
|
157
|
+
const newLines = lines.filter((line) => {
|
|
158
|
+
if (line.trim() === '## Constraints') {
|
|
159
|
+
inSection = true;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
if (inSection && line.startsWith('## ')) {
|
|
163
|
+
inSection = false;
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (inSection && line.trimStart().startsWith('- ') && !line.includes('<!--')) {
|
|
167
|
+
ruleCount++;
|
|
168
|
+
if (ruleCount === index) {
|
|
169
|
+
removedRule = line.trimStart().slice(2).trim();
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
});
|
|
175
|
+
if (removedRule === null)
|
|
176
|
+
return null;
|
|
177
|
+
writeFileSync(pmptMdPath, newLines.join('\n'), 'utf-8');
|
|
178
|
+
return removedRule;
|
|
179
|
+
}
|
package/dist/lib/plan.js
CHANGED
|
@@ -118,17 +118,27 @@ After completing each feature above:
|
|
|
118
118
|
|
|
119
119
|
### What to Record in pmpt.md
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
- Bad: "Set minimum description length to 150 chars"
|
|
123
|
-
- Good: "Set minimum description length to 150 chars (50-char threshold caused 60% low-quality entries)"
|
|
121
|
+
pmpt.md is the **single source of truth** for this project. AI tools read it to understand context before every session. Keep it accurate.
|
|
124
122
|
|
|
125
|
-
**##
|
|
126
|
-
-
|
|
127
|
-
-
|
|
123
|
+
**## Architecture** — High-level structure. Update when the architecture changes.
|
|
124
|
+
- Example: \`Next.js (SSG) → Cloudflare Workers API → D1 database\`
|
|
125
|
+
- Include the WHY if the stack choice was non-obvious
|
|
128
126
|
|
|
129
|
-
**##
|
|
130
|
-
-
|
|
127
|
+
**## Active Work** — What's currently being built. One or two items max.
|
|
128
|
+
- Clear this section when done, then move to Snapshot Log
|
|
129
|
+
- Example: \`- Implementing user auth (started 2025-03-17)\`
|
|
130
|
+
|
|
131
|
+
**## Decisions** — Record WHY, not just WHAT. Include what led to the decision.
|
|
132
|
+
- Bad: "Switched to SQLite"
|
|
133
|
+
- Good: "Switched SQLite → Postgres: deploy target moved to serverless, needed connection pooling"
|
|
134
|
+
|
|
135
|
+
**## Constraints** — Platform or library limitations discovered during development.
|
|
136
|
+
- Format: \`- [Platform/Tool]: what doesn't work → workaround used\`
|
|
137
|
+
- Example: \`- Cloudflare Workers: no native fs access → use KV for file storage\`
|
|
138
|
+
|
|
139
|
+
**## Lessons** — Anti-patterns and "tried X, broke because Y" discoveries.
|
|
131
140
|
- Format: \`- [What failed] → [Root cause] → [Fix applied]\`
|
|
141
|
+
- Example: \`- JWT refresh on mobile broke → tokens expired before retry → added sliding expiry\`
|
|
132
142
|
`;
|
|
133
143
|
}
|
|
134
144
|
// Generate human-facing project document (pmpt.md)
|
|
@@ -153,24 +163,38 @@ ${contextSection}
|
|
|
153
163
|
## Features
|
|
154
164
|
${features}
|
|
155
165
|
${techSection}
|
|
166
|
+
## Architecture
|
|
167
|
+
<!-- High-level structure. Update as it evolves. -->
|
|
168
|
+
<!-- Example: "Next.js frontend → Express API → PostgreSQL" -->
|
|
169
|
+
|
|
170
|
+
## Active Work
|
|
171
|
+
<!-- What's currently being built. Clear when done, move to Snapshot Log. -->
|
|
172
|
+
|
|
156
173
|
## Progress
|
|
157
174
|
- [ ] Project setup
|
|
158
175
|
- [ ] Core features implementation
|
|
159
176
|
- [ ] Testing & polish
|
|
160
177
|
|
|
161
178
|
## Snapshot Log
|
|
162
|
-
### v1
|
|
179
|
+
### v1 — Initial Setup
|
|
163
180
|
- Project initialized with pmpt
|
|
164
181
|
|
|
165
182
|
## Decisions
|
|
183
|
+
<!-- WHY, not just WHAT. Include what led to the decision. -->
|
|
184
|
+
<!-- Format: - [Decision] → [Reason / data that led to it] -->
|
|
166
185
|
|
|
167
186
|
## Constraints
|
|
187
|
+
<!-- Platform or library limitations discovered during development. -->
|
|
188
|
+
<!-- Format: - [Platform/Tool]: what doesn't work → workaround used -->
|
|
168
189
|
|
|
169
190
|
## Lessons
|
|
191
|
+
<!-- Anti-patterns and "tried X, broke because Y" discoveries. -->
|
|
192
|
+
<!-- Format: - [What failed] → [Root cause] → [Fix applied] -->
|
|
170
193
|
|
|
171
194
|
---
|
|
172
|
-
*This document
|
|
173
|
-
*AI
|
|
195
|
+
*This document is the single source of truth for this project.*
|
|
196
|
+
*AI tools read this to understand context, constraints, and current state.*
|
|
197
|
+
*AI instructions are in \`pmpt.ai.md\` — paste that into your AI tool to get started.*
|
|
174
198
|
`;
|
|
175
199
|
}
|
|
176
200
|
// Generate plan document
|
|
@@ -200,6 +224,19 @@ ${techSection}
|
|
|
200
224
|
*Generated by pmpt plan*
|
|
201
225
|
`;
|
|
202
226
|
}
|
|
227
|
+
export function generateAnswersTemplate() {
|
|
228
|
+
const questions = {};
|
|
229
|
+
const fields = {};
|
|
230
|
+
for (const q of PLAN_QUESTIONS) {
|
|
231
|
+
questions[q.key] = `[${PLAN_QUESTIONS.indexOf(q) + 1}/${PLAN_QUESTIONS.length}] ${q.question}`;
|
|
232
|
+
fields[q.key] = '';
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
_help: 'Fill in the fields below, then run: pmpt plan --answers-file <this-file>',
|
|
236
|
+
_questions: questions,
|
|
237
|
+
...fields,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
203
240
|
export function getPlanProgress(projectPath) {
|
|
204
241
|
const planPath = join(getConfigDir(projectPath), PLAN_FILE);
|
|
205
242
|
if (!existsSync(planPath))
|