pmpt-cli 1.15.0 → 1.18.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 +32 -4
- package/dist/commands/constraint.js +92 -0
- package/dist/commands/harness.js +59 -0
- package/dist/commands/init.js +31 -1
- package/dist/index.js +23 -0
- package/dist/lib/harness.js +230 -0
- package/dist/lib/plan.js +35 -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,14 @@ 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 |
|
|
99
|
+
| `pmpt harness` | Add harness to existing project — missing pmpt.md sections + index.md + tool entry points |
|
|
96
100
|
| `pmpt plan` | 5 questions → AI prompt (auto-copied to clipboard) |
|
|
101
|
+
| `pmpt plan --template` | Generate a fillable `answers.json` (Windows/PowerShell friendly) |
|
|
102
|
+
| `pmpt plan --answers-file <f>` | Run plan non-interactively from a JSON file |
|
|
103
|
+
| `pmpt constraint add "<rule>"` | Add an architecture rule to pmpt.md |
|
|
104
|
+
| `pmpt constraint list` | List all constraints |
|
|
105
|
+
| `pmpt constraint remove <n>` | Remove a constraint by index |
|
|
97
106
|
| `pmpt save` | Save current state as a snapshot |
|
|
98
107
|
| `pmpt watch` | Auto-detect file changes and save versions |
|
|
99
108
|
| `pmpt status` | Check project status, tracked files, and quality score |
|
|
@@ -213,18 +222,37 @@ All tools accept an optional `projectPath` parameter (defaults to cwd).
|
|
|
213
222
|
|
|
214
223
|
```
|
|
215
224
|
your-project/
|
|
225
|
+
├── CLAUDE.md # Claude Code entry point → points to .pmpt/index.md
|
|
226
|
+
├── .cursorrules # Cursor entry point → points to .pmpt/index.md
|
|
227
|
+
├── AGENTS.md # Codex entry point → points to .pmpt/index.md
|
|
216
228
|
└── .pmpt/
|
|
229
|
+
├── index.md # AI context map (entry point for all tools)
|
|
217
230
|
├── config.json # Project configuration
|
|
218
|
-
├── docs/
|
|
231
|
+
├── docs/
|
|
232
|
+
│ ├── pmpt.md # Single source of truth — architecture, constraints,
|
|
233
|
+
│ │ # decisions, lessons, progress, snapshot log
|
|
219
234
|
│ ├── plan.md # Product plan (features checklist)
|
|
220
|
-
│
|
|
221
|
-
│ └── pmpt.ai.md # AI-ready prompt (project context & instructions)
|
|
235
|
+
│ └── pmpt.ai.md # AI-ready prompt (paste into your AI tool)
|
|
222
236
|
└── .history/ # Auto-saved version history
|
|
223
237
|
├── v1-2024-02-20/
|
|
224
238
|
├── v2-2024-02-21/
|
|
225
239
|
└── ...
|
|
226
240
|
```
|
|
227
241
|
|
|
242
|
+
### pmpt.md — Single Source of Truth
|
|
243
|
+
|
|
244
|
+
`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:
|
|
245
|
+
|
|
246
|
+
| Section | What to record |
|
|
247
|
+
|---------|---------------|
|
|
248
|
+
| `## Architecture` | High-level structure — update as it evolves |
|
|
249
|
+
| `## Active Work` | What's currently being built (clear when done) |
|
|
250
|
+
| `## Decisions` | WHY, not just WHAT — include what led to each decision |
|
|
251
|
+
| `## Constraints` | Platform/library limitations and workarounds |
|
|
252
|
+
| `## Lessons` | Anti-patterns: what failed → root cause → fix applied |
|
|
253
|
+
|
|
254
|
+
The more you fill this in, the less you re-explain to AI every session.
|
|
255
|
+
|
|
228
256
|
---
|
|
229
257
|
|
|
230
258
|
## .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,59 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { resolve, basename } from 'path';
|
|
3
|
+
import { isInitialized } from '../lib/config.js';
|
|
4
|
+
import { addMissingSections, ensureIndexMd, ensureCursorRules, ensureAgentsMd, ensureClaudeMdRef, } from '../lib/harness.js';
|
|
5
|
+
export async function cmdHarness(path) {
|
|
6
|
+
const projectPath = path ? resolve(path) : process.cwd();
|
|
7
|
+
const projectName = basename(projectPath);
|
|
8
|
+
p.intro('pmpt harness');
|
|
9
|
+
if (!isInitialized(projectPath)) {
|
|
10
|
+
p.log.error('Project not initialized. Run `pmpt init` first.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
// 1. Add missing sections to pmpt.md
|
|
14
|
+
const added = addMissingSections(projectPath);
|
|
15
|
+
if (added.length > 0) {
|
|
16
|
+
p.log.success(`Added to pmpt.md: ${added.join(', ')}`);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
p.log.info('pmpt.md already has all harness sections.');
|
|
20
|
+
}
|
|
21
|
+
// 2. Create .pmpt/index.md
|
|
22
|
+
ensureIndexMd(projectPath, projectName);
|
|
23
|
+
p.log.success('Created: .pmpt/index.md');
|
|
24
|
+
// 3. Ask which AI tool
|
|
25
|
+
const toolChoice = await p.select({
|
|
26
|
+
message: 'Which AI coding tool do you use?',
|
|
27
|
+
options: [
|
|
28
|
+
{ value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md → .pmpt/index.md' },
|
|
29
|
+
{ value: 'cursor', label: 'Cursor', hint: '.cursorrules → .pmpt/index.md' },
|
|
30
|
+
{ value: 'codex', label: 'Codex', hint: 'AGENTS.md → .pmpt/index.md' },
|
|
31
|
+
{ value: 'all', label: 'All of the above' },
|
|
32
|
+
{ value: 'skip', label: 'Skip' },
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
if (p.isCancel(toolChoice) || toolChoice === 'skip') {
|
|
36
|
+
p.outro('Done. .pmpt/index.md created, pmpt.md updated.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const tool = toolChoice;
|
|
40
|
+
const created = [];
|
|
41
|
+
if (tool === 'claude' || tool === 'all') {
|
|
42
|
+
ensureClaudeMdRef(projectPath);
|
|
43
|
+
created.push('CLAUDE.md (updated)');
|
|
44
|
+
}
|
|
45
|
+
if (tool === 'cursor' || tool === 'all') {
|
|
46
|
+
ensureCursorRules(projectPath);
|
|
47
|
+
created.push('.cursorrules');
|
|
48
|
+
}
|
|
49
|
+
if (tool === 'codex' || tool === 'all') {
|
|
50
|
+
ensureAgentsMd(projectPath, projectName);
|
|
51
|
+
created.push('AGENTS.md');
|
|
52
|
+
}
|
|
53
|
+
if (created.length > 0) {
|
|
54
|
+
p.log.success(`Created: ${created.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
p.log.message('');
|
|
57
|
+
p.log.info('AI tools will now read .pmpt/index.md → .pmpt/docs/pmpt.md at session start.');
|
|
58
|
+
p.outro('Harness ready.');
|
|
59
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -7,6 +7,7 @@ import { cmdPlan } from './plan.js';
|
|
|
7
7
|
import { scanProject, scanResultToAnswers } from '../lib/scanner.js';
|
|
8
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) {
|
|
@@ -301,16 +322,25 @@ function ensureMinimalDocs(projectPath) {
|
|
|
301
322
|
const skeleton = [
|
|
302
323
|
`# ${name}`,
|
|
303
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
|
+
'',
|
|
304
331
|
'## Progress',
|
|
305
332
|
'- Project initialized',
|
|
306
333
|
'',
|
|
307
334
|
'## Snapshot Log',
|
|
308
335
|
'',
|
|
309
336
|
'## Decisions',
|
|
337
|
+
'<!-- WHY, not just WHAT. Format: - [Decision] → [Reason] -->',
|
|
310
338
|
'',
|
|
311
339
|
'## Constraints',
|
|
340
|
+
'<!-- Platform/library limitations. Format: - [Tool]: limitation → workaround -->',
|
|
312
341
|
'',
|
|
313
342
|
'## Lessons',
|
|
343
|
+
'<!-- Anti-patterns. Format: - [What failed] → [Root cause] → [Fix applied] -->',
|
|
314
344
|
'',
|
|
315
345
|
].join('\n');
|
|
316
346
|
writeFileSync(pmptMdPath, skeleton, 'utf-8');
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,8 @@ 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
48
|
import { cmdDoctor } from './commands/doctor.js';
|
|
49
|
+
import { cmdConstraint } from './commands/constraint.js';
|
|
50
|
+
import { cmdHarness } from './commands/harness.js';
|
|
49
51
|
import { trackCommand } from './lib/api.js';
|
|
50
52
|
import { checkForUpdates } from './lib/update-check.js';
|
|
51
53
|
import { createRequire } from 'module';
|
|
@@ -153,6 +155,27 @@ program
|
|
|
153
155
|
...options,
|
|
154
156
|
template: options.template === true ? 'answers.json' : options.template,
|
|
155
157
|
}));
|
|
158
|
+
program
|
|
159
|
+
.command('harness [path]')
|
|
160
|
+
.description('Set up AI context harness for existing projects — adds missing pmpt.md sections and creates tool entry points')
|
|
161
|
+
.action(cmdHarness);
|
|
162
|
+
program
|
|
163
|
+
.command('constraint <action>')
|
|
164
|
+
.description('Manage architecture constraints — add, list, or remove rules')
|
|
165
|
+
.option('-r, --rule <rule>', 'Rule text (for add)')
|
|
166
|
+
.option('-i, --index <n>', 'Rule index (for remove)')
|
|
167
|
+
.addHelpText('after', `
|
|
168
|
+
Actions:
|
|
169
|
+
add Add a constraint rule
|
|
170
|
+
list List all constraints
|
|
171
|
+
remove Remove a constraint by index
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
pmpt constraint add "UI does not import Service directly"
|
|
175
|
+
pmpt constraint list
|
|
176
|
+
pmpt constraint remove 2
|
|
177
|
+
`)
|
|
178
|
+
.action((action, options) => cmdConstraint(action, options));
|
|
156
179
|
program
|
|
157
180
|
.command('logout')
|
|
158
181
|
.description('Clear saved GitHub authentication')
|
|
@@ -0,0 +1,230 @@
|
|
|
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
|
+
export function ensureClaudeMdRef(projectPath) {
|
|
81
|
+
ensureClaudeMdIndexRef(projectPath);
|
|
82
|
+
}
|
|
83
|
+
function ensureClaudeMdIndexRef(projectPath) {
|
|
84
|
+
const claudeMdPath = join(projectPath, 'CLAUDE.md');
|
|
85
|
+
const marker = '<!-- pmpt-index -->';
|
|
86
|
+
const section = `${marker}\nSee \`.pmpt/index.md\` for project context. Single source of truth: \`.pmpt/docs/pmpt.md\`.\n<!-- /pmpt-index -->`;
|
|
87
|
+
if (!existsSync(claudeMdPath))
|
|
88
|
+
return;
|
|
89
|
+
const content = readFileSync(claudeMdPath, 'utf-8');
|
|
90
|
+
if (content.includes(marker))
|
|
91
|
+
return;
|
|
92
|
+
const pmptMarker = '<!-- pmpt -->';
|
|
93
|
+
if (content.includes(pmptMarker)) {
|
|
94
|
+
writeFileSync(claudeMdPath, content.replace(pmptMarker, section + '\n\n' + pmptMarker), 'utf-8');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
writeFileSync(claudeMdPath, section + '\n\n' + content, 'utf-8');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ─── pmpt.md section migration ────────────────────────────────────
|
|
101
|
+
const HARNESS_SECTIONS = [
|
|
102
|
+
{
|
|
103
|
+
heading: '## Architecture',
|
|
104
|
+
comment: '<!-- High-level structure. Update as it evolves. -->\n<!-- Example: "Next.js frontend → Cloudflare Workers API → D1 database" -->',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
heading: '## Active Work',
|
|
108
|
+
comment: '<!-- What\'s currently being built. Clear when done, move to Snapshot Log. -->',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
heading: '## Constraints',
|
|
112
|
+
comment: '<!-- Platform or library limitations discovered during development. -->\n<!-- Format: - [Platform/Tool]: what doesn\'t work → workaround used -->',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
heading: '## Lessons',
|
|
116
|
+
comment: '<!-- Anti-patterns and "tried X, broke because Y" discoveries. -->\n<!-- Format: - [What failed] → [Root cause] → [Fix applied] -->',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
/**
|
|
120
|
+
* Add missing harness sections to an existing pmpt.md.
|
|
121
|
+
* Returns list of section headings that were added.
|
|
122
|
+
*/
|
|
123
|
+
export function addMissingSections(projectPath) {
|
|
124
|
+
const pmptMdPath = join(getDocsDir(projectPath), 'pmpt.md');
|
|
125
|
+
if (!existsSync(pmptMdPath))
|
|
126
|
+
return [];
|
|
127
|
+
let content = readFileSync(pmptMdPath, 'utf-8');
|
|
128
|
+
const added = [];
|
|
129
|
+
for (const section of HARNESS_SECTIONS) {
|
|
130
|
+
if (content.includes(section.heading))
|
|
131
|
+
continue;
|
|
132
|
+
// Insert before ## Decisions if it exists, otherwise append
|
|
133
|
+
const insertBefore = '## Decisions';
|
|
134
|
+
const block = `${section.heading}\n${section.comment}\n`;
|
|
135
|
+
if (content.includes(insertBefore)) {
|
|
136
|
+
content = content.replace(insertBefore, block + '\n' + insertBefore);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
content = content.trimEnd() + '\n\n' + block;
|
|
140
|
+
}
|
|
141
|
+
added.push(section.heading);
|
|
142
|
+
}
|
|
143
|
+
if (added.length > 0) {
|
|
144
|
+
writeFileSync(pmptMdPath, content, 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
return added;
|
|
147
|
+
}
|
|
148
|
+
// ─── Constraint management — reads/writes pmpt.md ## Constraints ──
|
|
149
|
+
export function readConstraints(projectPath) {
|
|
150
|
+
const pmptMdPath = join(getDocsDir(projectPath), 'pmpt.md');
|
|
151
|
+
if (!existsSync(pmptMdPath))
|
|
152
|
+
return [];
|
|
153
|
+
const lines = readFileSync(pmptMdPath, 'utf-8').split('\n');
|
|
154
|
+
const inSection = { value: false };
|
|
155
|
+
const rules = [];
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
if (line.trim() === '## Constraints') {
|
|
158
|
+
inSection.value = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (inSection.value && line.startsWith('## '))
|
|
162
|
+
break;
|
|
163
|
+
if (inSection.value && line.trimStart().startsWith('- ') && !line.includes('<!--')) {
|
|
164
|
+
rules.push(line.trimStart().slice(2).trim());
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return rules;
|
|
168
|
+
}
|
|
169
|
+
export function addConstraint(projectPath, rule) {
|
|
170
|
+
const pmptMdPath = join(getDocsDir(projectPath), 'pmpt.md');
|
|
171
|
+
if (!existsSync(pmptMdPath))
|
|
172
|
+
return false;
|
|
173
|
+
const lines = readFileSync(pmptMdPath, 'utf-8').split('\n');
|
|
174
|
+
let sectionEnd = -1;
|
|
175
|
+
let inSection = false;
|
|
176
|
+
for (let i = 0; i < lines.length; i++) {
|
|
177
|
+
if (lines[i].trim() === '## Constraints') {
|
|
178
|
+
inSection = true;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (inSection && lines[i].startsWith('## ')) {
|
|
182
|
+
sectionEnd = i;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const newLine = `- ${rule}`;
|
|
187
|
+
if (sectionEnd !== -1) {
|
|
188
|
+
// Insert before next section, after any existing content
|
|
189
|
+
lines.splice(sectionEnd, 0, newLine);
|
|
190
|
+
}
|
|
191
|
+
else if (inSection) {
|
|
192
|
+
lines.push(newLine);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
writeFileSync(pmptMdPath, lines.join('\n'), 'utf-8');
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
export function removeConstraint(projectPath, index) {
|
|
201
|
+
const pmptMdPath = join(getDocsDir(projectPath), 'pmpt.md');
|
|
202
|
+
if (!existsSync(pmptMdPath))
|
|
203
|
+
return null;
|
|
204
|
+
const lines = readFileSync(pmptMdPath, 'utf-8').split('\n');
|
|
205
|
+
let inSection = false;
|
|
206
|
+
let ruleCount = 0;
|
|
207
|
+
let removedRule = null;
|
|
208
|
+
const newLines = lines.filter((line) => {
|
|
209
|
+
if (line.trim() === '## Constraints') {
|
|
210
|
+
inSection = true;
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (inSection && line.startsWith('## ')) {
|
|
214
|
+
inSection = false;
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if (inSection && line.trimStart().startsWith('- ') && !line.includes('<!--')) {
|
|
218
|
+
ruleCount++;
|
|
219
|
+
if (ruleCount === index) {
|
|
220
|
+
removedRule = line.trimStart().slice(2).trim();
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
});
|
|
226
|
+
if (removedRule === null)
|
|
227
|
+
return null;
|
|
228
|
+
writeFileSync(pmptMdPath, newLines.join('\n'), 'utf-8');
|
|
229
|
+
return removedRule;
|
|
230
|
+
}
|
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
|