@pcoliveira90/pdd 0.3.0 → 0.4.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.en.md +52 -40
- package/README.md +59 -14
- package/README.pt-BR.md +66 -6
- package/bin/pdd-ai.js +1 -0
- package/bin/pdd.js +5 -24
- package/package.json +3 -3
- package/src/ai/run-fix-analysis.js +16 -3
- package/src/cli/doctor-command.js +30 -13
- package/src/cli/index.js +64 -22
- package/src/core/fix-runner.js +2 -3
- package/src/core/patch-generator.js +10 -11
- package/src/core/pr-manager.js +7 -7
- package/src/core/template-registry.js +1 -1
- package/src/core/validator.js +3 -3
- package/src/core/worktree-guard.js +42 -2
package/README.en.md
CHANGED
|
@@ -1,60 +1,72 @@
|
|
|
1
|
-
# PDD
|
|
2
|
-

|
|
3
|
-

|
|
4
|
-

|
|
5
|
-

|
|
6
|
-
> Ship safe changes in living systems.
|
|
1
|
+
# PDD - Patch-Driven Development
|
|
7
2
|
|
|
8
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@pcoliveira90/pdd)
|
|
4
|
+
[](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml)
|
|
5
|
+
[](https://github.com/pcoliveira90/pdd/blob/main/LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
> Safe changes in real systems.
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
PDD is a CLI-first framework for bugfix and feature work in existing codebases.
|
|
11
|
+
It standardizes how teams investigate, plan, validate, and document changes.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
Language versions: [Default README](README.md) | [Português (Brasil)](README.pt-BR.md)
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
- fixing bugs in production
|
|
18
|
-
- evolving legacy systems
|
|
19
|
-
- adding features without breaking things
|
|
15
|
+
## Why PDD
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
- Worktree-first execution for safer parallel development
|
|
18
|
+
- Structured change artifacts (`delta-spec`, `patch-plan`, `verification-report`)
|
|
19
|
+
- Consistent workflow for Cursor, Claude Code, and GitHub Copilot
|
|
20
|
+
- Built-in quality gates (`doctor`, validation, baseline CI checks)
|
|
22
21
|
|
|
23
|
-
##
|
|
22
|
+
## Quick Start
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
- applying minimal safe changes
|
|
29
|
-
- validating with evidence
|
|
24
|
+
```bash
|
|
25
|
+
# 1) Initialize PDD in the repository (if running in primary, PDD auto-creates a linked worktree)
|
|
26
|
+
pdd init --here
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
# 2) Run a fix workflow
|
|
29
|
+
pdd fix "login not saving incomeStatus"
|
|
30
|
+
```
|
|
32
31
|
|
|
33
|
-
## Core
|
|
32
|
+
## Core Commands
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
```bash
|
|
35
|
+
pdd init --here
|
|
36
|
+
pdd doctor
|
|
37
|
+
pdd status
|
|
38
|
+
pdd fix "bug description" [--dry-run] [--no-validate] [--open-pr]
|
|
39
|
+
pdd version
|
|
40
|
+
```
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
AI analysis command:
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
```bash
|
|
45
|
+
pdd-ai --provider=openai --task=analysis "bug description"
|
|
46
|
+
```
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
- Evidence before edit
|
|
43
|
-
- Minimal safe delta
|
|
44
|
-
- Root-cause over symptom patch
|
|
45
|
-
- Regression-aware
|
|
46
|
-
- Reuse existing patterns
|
|
47
|
-
- Verifiable outcomes
|
|
48
|
+
## Workflow Summary
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
1. Understand current behavior and root cause
|
|
51
|
+
2. Generate change artifacts under `changes/<change-id>/`
|
|
52
|
+
3. Validate tests/lint/build
|
|
53
|
+
4. Prepare PR artifacts and review in IDE
|
|
50
54
|
|
|
51
|
-
##
|
|
55
|
+
## IDE Alignment
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
examples/
|
|
57
|
+
PDD keeps equivalent intents across tools:
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
- Cursor: `.cursor/commands/pdd-*.md`
|
|
60
|
+
- Claude Code: `.claude/commands/pdd-*.md`
|
|
61
|
+
- GitHub Copilot: `.github/prompts/pdd-*.prompt.md`
|
|
57
62
|
|
|
58
|
-
##
|
|
63
|
+
## Documentation
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
- `docs/getting-started.md`
|
|
66
|
+
- `docs/installation-and-setup.md`
|
|
67
|
+
- `docs/fix-workflow.md`
|
|
68
|
+
- `docs/manifesto.md`
|
|
69
|
+
|
|
70
|
+
## Goal
|
|
71
|
+
|
|
72
|
+
Reliable execution engine for safe software changes.
|
package/README.md
CHANGED
|
@@ -1,26 +1,71 @@
|
|
|
1
|
-
# PDD
|
|
1
|
+
# PDD - Patch-Driven Development
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@pcoliveira90/pdd)
|
|
4
|
+
[](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml)
|
|
5
|
+
[](https://github.com/pcoliveira90/pdd/blob/main/LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
2
7
|
|
|
3
8
|
> Safe changes in real systems.
|
|
4
9
|
|
|
5
|
-
PDD is a framework
|
|
10
|
+
PDD is a CLI-first framework for bugfix and feature work in existing codebases.
|
|
11
|
+
It standardizes how teams investigate, plan, validate, and document changes.
|
|
12
|
+
|
|
13
|
+
Language versions: [English](README.en.md) | [Português (Brasil)](README.pt-BR.md)
|
|
14
|
+
|
|
15
|
+
## Why PDD
|
|
16
|
+
|
|
17
|
+
- Worktree-first execution for safer parallel development
|
|
18
|
+
- Structured change artifacts (`delta-spec`, `patch-plan`, `verification-report`)
|
|
19
|
+
- Consistent workflow for Cursor, Claude Code, and GitHub Copilot
|
|
20
|
+
- Built-in quality gates (`doctor`, validation, baseline CI checks)
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 1) Initialize PDD in the repository (if running in primary, PDD auto-creates a linked worktree)
|
|
26
|
+
pdd init --here
|
|
27
|
+
|
|
28
|
+
# 2) Run a fix workflow
|
|
29
|
+
pdd fix "login not saving incomeStatus"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Core Commands
|
|
6
33
|
|
|
7
|
-
|
|
34
|
+
```bash
|
|
35
|
+
pdd init --here
|
|
36
|
+
pdd doctor
|
|
37
|
+
pdd status
|
|
38
|
+
pdd fix "bug description" [--dry-run] [--no-validate] [--open-pr]
|
|
39
|
+
pdd version
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
AI analysis command:
|
|
8
43
|
|
|
9
44
|
```bash
|
|
10
|
-
pdd
|
|
11
|
-
pdd fix "bug"
|
|
12
|
-
pdd fix "bug" --dry-run
|
|
13
|
-
pdd fix "bug" --no-validate
|
|
14
|
-
pdd fix "bug" --open-pr
|
|
15
|
-
pdd-ai --provider=openai --task=analysis "bug"
|
|
45
|
+
pdd-ai --provider=openai --task=analysis "bug description"
|
|
16
46
|
```
|
|
17
47
|
|
|
18
|
-
##
|
|
48
|
+
## Workflow Summary
|
|
49
|
+
|
|
50
|
+
1. Understand current behavior and root cause
|
|
51
|
+
2. Generate change artifacts under `changes/<change-id>/`
|
|
52
|
+
3. Validate tests/lint/build
|
|
53
|
+
4. Prepare PR artifacts and review in IDE
|
|
54
|
+
|
|
55
|
+
## IDE Alignment
|
|
56
|
+
|
|
57
|
+
PDD keeps equivalent intents across tools:
|
|
58
|
+
|
|
59
|
+
- Cursor: `.cursor/commands/pdd-*.md`
|
|
60
|
+
- Claude Code: `.claude/commands/pdd-*.md`
|
|
61
|
+
- GitHub Copilot: `.github/prompts/pdd-*.prompt.md`
|
|
62
|
+
|
|
63
|
+
## Documentation
|
|
19
64
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
65
|
+
- `docs/getting-started.md`
|
|
66
|
+
- `docs/installation-and-setup.md`
|
|
67
|
+
- `docs/fix-workflow.md`
|
|
68
|
+
- `docs/manifesto.md`
|
|
24
69
|
|
|
25
70
|
## Goal
|
|
26
71
|
|
package/README.pt-BR.md
CHANGED
|
@@ -1,12 +1,72 @@
|
|
|
1
|
-
# PDD
|
|
1
|
+
# PDD - Patch-Driven Development
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@pcoliveira90/pdd)
|
|
4
|
+
[](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml)
|
|
5
|
+
[](https://github.com/pcoliveira90/pdd/blob/main/LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
> Entregue mudancas seguras em sistemas reais.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
PDD e um framework orientado a CLI para bugfix e evolucao de features em sistemas existentes.
|
|
11
|
+
Ele padroniza como o time investiga, planeja, valida e documenta mudancas.
|
|
12
|
+
|
|
13
|
+
Versoes por idioma: [README padrao](README.md) | [English](README.en.md)
|
|
14
|
+
|
|
15
|
+
## Por que usar PDD
|
|
16
|
+
|
|
17
|
+
- Execucao worktree-first para desenvolvimento paralelo mais seguro
|
|
18
|
+
- Artefatos estruturados de mudanca (`delta-spec`, `patch-plan`, `verification-report`)
|
|
19
|
+
- Fluxo consistente para Cursor, Claude Code e GitHub Copilot
|
|
20
|
+
- Quality gates nativos (`doctor`, validacao e checagens de CI)
|
|
21
|
+
|
|
22
|
+
## Inicio rapido
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 1) Inicialize o PDD no repositorio (se estiver na principal, o PDD cria worktree vinculada automaticamente)
|
|
26
|
+
pdd init --here
|
|
27
|
+
|
|
28
|
+
# 2) Rode um fluxo de correcao
|
|
29
|
+
pdd fix "login nao salva incomeStatus"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Comandos principais
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pdd init --here
|
|
36
|
+
pdd doctor
|
|
37
|
+
pdd status
|
|
38
|
+
pdd fix "descricao do bug" [--dry-run] [--no-validate] [--open-pr]
|
|
39
|
+
pdd version
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Comando de analise por IA:
|
|
8
43
|
|
|
9
44
|
```bash
|
|
10
|
-
pdd
|
|
11
|
-
pdd fix "bug"
|
|
45
|
+
pdd-ai --provider=openai --task=analysis "descricao do bug"
|
|
12
46
|
```
|
|
47
|
+
|
|
48
|
+
## Resumo do fluxo
|
|
49
|
+
|
|
50
|
+
1. Entender comportamento atual e causa raiz
|
|
51
|
+
2. Gerar artefatos em `changes/<change-id>/`
|
|
52
|
+
3. Validar testes/lint/build
|
|
53
|
+
4. Preparar artefatos de PR e revisar na IDE
|
|
54
|
+
|
|
55
|
+
## Alinhamento entre IDEs
|
|
56
|
+
|
|
57
|
+
O PDD mantem intencoes equivalentes entre ferramentas:
|
|
58
|
+
|
|
59
|
+
- Cursor: `.cursor/commands/pdd-*.md`
|
|
60
|
+
- Claude Code: `.claude/commands/pdd-*.md`
|
|
61
|
+
- GitHub Copilot: `.github/prompts/pdd-*.prompt.md`
|
|
62
|
+
|
|
63
|
+
## Documentacao
|
|
64
|
+
|
|
65
|
+
- `docs/getting-started.md`
|
|
66
|
+
- `docs/installation-and-setup.md`
|
|
67
|
+
- `docs/fix-workflow.md`
|
|
68
|
+
- `docs/manifesto.md`
|
|
69
|
+
|
|
70
|
+
## Objetivo
|
|
71
|
+
|
|
72
|
+
Motor de execucao confiavel para mudancas seguras em software real.
|
package/bin/pdd-ai.js
CHANGED
|
@@ -9,6 +9,7 @@ async function main() {
|
|
|
9
9
|
console.log('\n🤖 PDD AI Analysis');
|
|
10
10
|
console.log('----------------------');
|
|
11
11
|
console.log(`Provider: ${result.provider}`);
|
|
12
|
+
console.log(`Task: ${result.task}`);
|
|
12
13
|
console.log(`Model: ${result.model}`);
|
|
13
14
|
console.log(`Issue: ${result.issue}`);
|
|
14
15
|
console.log('\nResult:\n');
|
package/bin/pdd.js
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import path from 'path';
|
|
3
|
+
import { runCli } from '../src/cli/index.js';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const pddDir = path.join(targetDir, '.pdd');
|
|
11
|
-
|
|
12
|
-
if (fs.existsSync(pddDir)) {
|
|
13
|
-
console.log('PDD already initialized.');
|
|
14
|
-
process.exit(0);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
fs.mkdirSync(pddDir, { recursive: true });
|
|
18
|
-
fs.mkdirSync(path.join(pddDir, 'templates'));
|
|
19
|
-
fs.mkdirSync(path.join(pddDir, 'commands'));
|
|
20
|
-
fs.mkdirSync(path.join(pddDir, 'memory'));
|
|
21
|
-
|
|
22
|
-
fs.writeFileSync(path.join(pddDir, 'README.md'), 'PDD initialized');
|
|
23
|
-
|
|
24
|
-
console.log('✅ PDD initialized successfully.');
|
|
25
|
-
} else {
|
|
26
|
-
console.log('Usage: pdd init');
|
|
27
|
-
}
|
|
5
|
+
runCli(process.argv.slice(2)).catch(err => {
|
|
6
|
+
console.error(err);
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pcoliveira90/pdd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Patch-Driven Development CLI — safe, resilient and guided code changes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"start": "node bin/pdd-pro.js",
|
|
16
16
|
"dev": "node bin/pdd-pro.js",
|
|
17
|
-
"test": "
|
|
18
|
-
"lint": "
|
|
17
|
+
"test": "node --test",
|
|
18
|
+
"lint": "node ./scripts/lint-js.mjs",
|
|
19
19
|
"prepublishOnly": "npm pack"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
@@ -13,7 +13,14 @@ function extractArgValue(args, name, fallback = null) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function getIssueFromArgs(args) {
|
|
16
|
-
const filtered = args.filter(
|
|
16
|
+
const filtered = args.filter(
|
|
17
|
+
arg =>
|
|
18
|
+
!arg.startsWith('--provider') &&
|
|
19
|
+
!arg.startsWith('--model') &&
|
|
20
|
+
!arg.startsWith('--task') &&
|
|
21
|
+
arg !== 'fix' &&
|
|
22
|
+
arg !== '--ai'
|
|
23
|
+
);
|
|
17
24
|
return filtered.join(' ').trim();
|
|
18
25
|
}
|
|
19
26
|
|
|
@@ -138,10 +145,11 @@ export async function runAiFixAnalysis(argv = process.argv.slice(2)) {
|
|
|
138
145
|
const provider = extractArgValue(argv, '--provider', 'openai');
|
|
139
146
|
const providerConfig = getAiProviderConfig(provider);
|
|
140
147
|
const model = extractArgValue(argv, '--model', providerConfig.defaultModel);
|
|
148
|
+
const task = extractArgValue(argv, '--task', 'analysis');
|
|
141
149
|
const issue = getIssueFromArgs(argv);
|
|
142
150
|
|
|
143
151
|
if (!issue) {
|
|
144
|
-
throw new Error('Missing issue description. Example: pdd
|
|
152
|
+
throw new Error('Missing issue description. Example: pdd-ai --provider=openai --task=analysis "login not saving incomeStatus"');
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
const apiKey = process.env[providerConfig.envKey];
|
|
@@ -150,7 +158,11 @@ export async function runAiFixAnalysis(argv = process.argv.slice(2)) {
|
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
const baseUrl = resolveBaseUrl(providerConfig);
|
|
153
|
-
const prompt =
|
|
161
|
+
const prompt = [
|
|
162
|
+
`Task mode: ${task}`,
|
|
163
|
+
'',
|
|
164
|
+
buildBugfixPrompt({ issue })
|
|
165
|
+
].join('\n');
|
|
154
166
|
|
|
155
167
|
let raw;
|
|
156
168
|
if (provider === 'openai') {
|
|
@@ -167,6 +179,7 @@ export async function runAiFixAnalysis(argv = process.argv.slice(2)) {
|
|
|
167
179
|
|
|
168
180
|
return {
|
|
169
181
|
provider,
|
|
182
|
+
task,
|
|
170
183
|
model,
|
|
171
184
|
issue,
|
|
172
185
|
result: parsed
|
|
@@ -8,26 +8,42 @@ function exists(baseDir, relativePath) {
|
|
|
8
8
|
return fs.existsSync(path.join(baseDir, relativePath));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function existsAny(baseDir, relativePaths) {
|
|
12
|
+
return relativePaths.some(relativePath => exists(baseDir, relativePath));
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
function cursorAdapterInstalled(baseDir) {
|
|
12
|
-
return (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
return existsAny(baseDir, [
|
|
17
|
+
'.cursor/rules/pdd.mdc',
|
|
18
|
+
'.cursor/commands/pdd.md',
|
|
19
|
+
'.cursor/commands/pdd-recon.md',
|
|
20
|
+
'.cursor/commands/pdd-fix.md',
|
|
21
|
+
'.cursor/commands/pdd-feature.md',
|
|
22
|
+
'.cursor/commands/pdd-verify.md',
|
|
23
|
+
'.cursor/pdd.prompt.md'
|
|
24
|
+
]);
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
function claudeAdapterInstalled(baseDir) {
|
|
20
|
-
return (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
return existsAny(baseDir, [
|
|
29
|
+
'.claude/CLAUDE.md',
|
|
30
|
+
'.claude/commands/pdd.md',
|
|
31
|
+
'.claude/commands/pdd-recon.md',
|
|
32
|
+
'.claude/commands/pdd-fix.md',
|
|
33
|
+
'.claude/commands/pdd-feature.md',
|
|
34
|
+
'.claude/commands/pdd-verify.md'
|
|
35
|
+
]);
|
|
24
36
|
}
|
|
25
37
|
|
|
26
38
|
function copilotAdapterInstalled(baseDir) {
|
|
27
|
-
return (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
return existsAny(baseDir, [
|
|
40
|
+
'.github/copilot-instructions.md',
|
|
41
|
+
'.github/copilot/pdd.prompt.md',
|
|
42
|
+
'.github/prompts/pdd-recon.prompt.md',
|
|
43
|
+
'.github/prompts/pdd-fix.prompt.md',
|
|
44
|
+
'.github/prompts/pdd-feature.prompt.md',
|
|
45
|
+
'.github/prompts/pdd-verify.prompt.md'
|
|
46
|
+
]);
|
|
31
47
|
}
|
|
32
48
|
|
|
33
49
|
function readVersion(baseDir) {
|
|
@@ -90,6 +106,7 @@ export function runDoctor(baseDir = process.cwd(), argv = []) {
|
|
|
90
106
|
} else {
|
|
91
107
|
console.log('🎉 Templates up to date');
|
|
92
108
|
}
|
|
109
|
+
console.log('ℹ️ Note: CLI package version and template version are tracked separately.');
|
|
93
110
|
|
|
94
111
|
if (!adapters.claude && !adapters.cursor && !adapters.copilot) {
|
|
95
112
|
console.log('ℹ️ No IDE adapters installed');
|
package/src/cli/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { dirname, join } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
4
5
|
import { runValidation } from '../core/validator.js';
|
|
5
6
|
import { openPullRequest } from '../core/pr-manager.js';
|
|
6
7
|
import { generatePatchArtifacts } from '../core/patch-generator.js';
|
|
@@ -8,7 +9,7 @@ import { runInit } from './init-command.js';
|
|
|
8
9
|
import { runDoctor } from './doctor-command.js';
|
|
9
10
|
import { runStatus } from './status-command.js';
|
|
10
11
|
import { runResilientFixWorkflow } from '../core/fix-runner.js';
|
|
11
|
-
import {
|
|
12
|
+
import { createLinkedWorktree, detectWorktreeContext } from '../core/worktree-guard.js';
|
|
12
13
|
|
|
13
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
|
|
@@ -33,6 +34,44 @@ function parseFixArgs(argv) {
|
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
function maybeAutoRelocateToWorktree({
|
|
38
|
+
cwd,
|
|
39
|
+
argv,
|
|
40
|
+
commandName,
|
|
41
|
+
enabled
|
|
42
|
+
}) {
|
|
43
|
+
if (!enabled || argv.includes('--allow-main-worktree')) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const context = detectWorktreeContext(cwd);
|
|
48
|
+
if (!context.isGitRepo || !context.isPrimaryWorktree) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { worktreePath, branchName } = createLinkedWorktree({
|
|
53
|
+
baseDir: cwd,
|
|
54
|
+
commandName
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log(`🔀 Primary worktree detected. Auto-created linked worktree: ${worktreePath}`);
|
|
58
|
+
console.log(`🪴 Branch: ${branchName}`);
|
|
59
|
+
console.log('▶️ Continuing command in the new worktree...');
|
|
60
|
+
|
|
61
|
+
const result = spawnSync(
|
|
62
|
+
process.execPath,
|
|
63
|
+
[process.argv[1], ...argv],
|
|
64
|
+
{ cwd: worktreePath, stdio: 'inherit' }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (result.error) {
|
|
68
|
+
throw result.error;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
process.exitCode = typeof result.status === 'number' ? result.status : 1;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
36
75
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
37
76
|
const command = argv[0];
|
|
38
77
|
const cwd = process.cwd();
|
|
@@ -43,27 +82,27 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
43
82
|
}
|
|
44
83
|
|
|
45
84
|
if (command === 'init') {
|
|
46
|
-
const allowMainWorktree = argv.includes('--allow-main-worktree');
|
|
47
85
|
const mutatesCurrentRepo = argv.includes('--here') || argv.includes('--upgrade');
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
86
|
+
if (maybeAutoRelocateToWorktree({
|
|
87
|
+
cwd,
|
|
88
|
+
argv,
|
|
89
|
+
commandName: 'init',
|
|
90
|
+
enabled: mutatesCurrentRepo
|
|
91
|
+
})) {
|
|
92
|
+
return;
|
|
54
93
|
}
|
|
55
94
|
await runInit(argv);
|
|
56
95
|
return;
|
|
57
96
|
}
|
|
58
97
|
|
|
59
98
|
if (command === 'doctor') {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
99
|
+
if (maybeAutoRelocateToWorktree({
|
|
100
|
+
cwd,
|
|
101
|
+
argv,
|
|
102
|
+
commandName: 'doctor-fix',
|
|
103
|
+
enabled: argv.includes('--fix')
|
|
104
|
+
})) {
|
|
105
|
+
return;
|
|
67
106
|
}
|
|
68
107
|
runDoctor(cwd, argv);
|
|
69
108
|
return;
|
|
@@ -75,7 +114,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
75
114
|
}
|
|
76
115
|
|
|
77
116
|
if (command === 'fix') {
|
|
78
|
-
const { issue, openPr, dryRun, noValidate
|
|
117
|
+
const { issue, openPr, dryRun, noValidate } = parseFixArgs(argv);
|
|
79
118
|
|
|
80
119
|
if (!issue) {
|
|
81
120
|
console.error('❌ Missing issue description.');
|
|
@@ -83,11 +122,14 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
83
122
|
process.exit(1);
|
|
84
123
|
}
|
|
85
124
|
|
|
86
|
-
|
|
87
|
-
|
|
125
|
+
if (maybeAutoRelocateToWorktree({
|
|
126
|
+
cwd,
|
|
127
|
+
argv,
|
|
88
128
|
commandName: 'fix',
|
|
89
|
-
|
|
90
|
-
})
|
|
129
|
+
enabled: true
|
|
130
|
+
})) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
91
133
|
|
|
92
134
|
console.log('🔧 PDD Fix Workflow');
|
|
93
135
|
console.log(`Issue: ${issue}`);
|
|
@@ -143,8 +185,8 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
143
185
|
console.log(' pdd version (or: pdd --version, pdd -v) Show CLI version');
|
|
144
186
|
console.log('');
|
|
145
187
|
console.log('Worktree policy:');
|
|
146
|
-
console.log(' Mutating commands
|
|
147
|
-
console.log('
|
|
188
|
+
console.log(' Mutating commands auto-create and use a linked git worktree when needed.');
|
|
189
|
+
console.log(' Use --allow-main-worktree only if you intentionally want to run in primary.');
|
|
148
190
|
console.log('');
|
|
149
191
|
console.log('AI command (official binary):');
|
|
150
192
|
console.log(' pdd-ai [--provider=openai|claude|openrouter] [--task=analysis|build|test|review] [--model=<id>] "issue"');
|
package/src/core/fix-runner.js
CHANGED
|
@@ -75,11 +75,11 @@ export async function runResilientFixWorkflow({
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const changeId = `change-${Date.now()}`;
|
|
78
|
+
let phase = 'patch-generation';
|
|
78
79
|
setActiveChange(baseDir, changeId, 'in-progress');
|
|
79
80
|
|
|
80
81
|
try {
|
|
81
|
-
|
|
82
|
-
const patch = generatePatchArtifacts({ issue, baseDir });
|
|
82
|
+
const patch = generatePatchArtifacts({ issue, baseDir, changeId });
|
|
83
83
|
|
|
84
84
|
if (!noValidate) {
|
|
85
85
|
phase = 'validation';
|
|
@@ -113,7 +113,6 @@ export async function runResilientFixWorkflow({
|
|
|
113
113
|
files: patch.files
|
|
114
114
|
};
|
|
115
115
|
} catch (error) {
|
|
116
|
-
const phase = current.status === 'in-progress' ? current.lastPhase || 'unknown' : 'unknown';
|
|
117
116
|
const payload = buildFailurePayload({ issue, changeId, phase, error });
|
|
118
117
|
const artifacts = persistFailureArtifacts(baseDir, payload);
|
|
119
118
|
|
|
@@ -18,15 +18,14 @@ function slugify(value) {
|
|
|
18
18
|
.slice(0, 48);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function generatePatchArtifacts({ issue, baseDir = process.cwd() }) {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const changeDir = path.join(baseDir, 'changes', changeId);
|
|
21
|
+
export function generatePatchArtifacts({ issue, baseDir = process.cwd(), changeId = null }) {
|
|
22
|
+
const resolvedChangeId = changeId || `change-${Date.now()}-${slugify(issue || 'update')}`;
|
|
23
|
+
const changeDir = path.join(baseDir, 'changes', resolvedChangeId);
|
|
25
24
|
|
|
26
25
|
const files = [
|
|
27
|
-
path.join('changes',
|
|
28
|
-
path.join('changes',
|
|
29
|
-
path.join('changes',
|
|
26
|
+
path.join('changes', resolvedChangeId, 'delta-spec.md'),
|
|
27
|
+
path.join('changes', resolvedChangeId, 'patch-plan.md'),
|
|
28
|
+
path.join('changes', resolvedChangeId, 'verification-report.md')
|
|
30
29
|
];
|
|
31
30
|
|
|
32
31
|
writeFile(
|
|
@@ -34,7 +33,7 @@ export function generatePatchArtifacts({ issue, baseDir = process.cwd() }) {
|
|
|
34
33
|
`# Delta Spec
|
|
35
34
|
|
|
36
35
|
## Change ID
|
|
37
|
-
${
|
|
36
|
+
${resolvedChangeId}
|
|
38
37
|
|
|
39
38
|
## Issue
|
|
40
39
|
${issue}
|
|
@@ -71,7 +70,7 @@ bugfix | feature | refactor-safe | hotfix
|
|
|
71
70
|
`# Patch Plan
|
|
72
71
|
|
|
73
72
|
## Change ID
|
|
74
|
-
${
|
|
73
|
+
${resolvedChangeId}
|
|
75
74
|
|
|
76
75
|
## Issue
|
|
77
76
|
${issue}
|
|
@@ -98,7 +97,7 @@ ${issue}
|
|
|
98
97
|
`# Verification Report
|
|
99
98
|
|
|
100
99
|
## Change ID
|
|
101
|
-
${
|
|
100
|
+
${resolvedChangeId}
|
|
102
101
|
|
|
103
102
|
## Issue
|
|
104
103
|
${issue}
|
|
@@ -119,7 +118,7 @@ pending
|
|
|
119
118
|
);
|
|
120
119
|
|
|
121
120
|
return {
|
|
122
|
-
changeId,
|
|
121
|
+
changeId: resolvedChangeId,
|
|
123
122
|
changeDir,
|
|
124
123
|
files
|
|
125
124
|
};
|
package/src/core/pr-manager.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
|
|
5
|
+
function runGit(args, baseDir = process.cwd()) {
|
|
6
|
+
execFileSync('git', args, { stdio: 'inherit', cwd: baseDir });
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export async function openPullRequest({ issue, changeId, changeDir }) {
|
|
9
|
+
export async function openPullRequest({ issue, changeId, changeDir, baseDir = process.cwd() }) {
|
|
10
10
|
const branch = `pdd/${changeId}`;
|
|
11
11
|
const title = `fix: ${issue}`;
|
|
12
12
|
|
|
13
13
|
fs.writeFileSync(path.join(changeDir, 'pr-title.txt'), title);
|
|
14
14
|
fs.writeFileSync(path.join(changeDir, 'pr-body.md'), issue);
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
runGit(['checkout', '-b', branch], baseDir);
|
|
17
|
+
runGit(['add', '.'], baseDir);
|
|
18
|
+
runGit(['commit', '-m', title], baseDir);
|
|
19
19
|
|
|
20
20
|
console.log('PR ready (use IDE to open)');
|
|
21
21
|
}
|
|
@@ -171,7 +171,7 @@ Map the structure of the system.
|
|
|
171
171
|
## Hotspots
|
|
172
172
|
-
|
|
173
173
|
`,
|
|
174
|
-
'.pdd/version.json': JSON.stringify({ templateVersion:
|
|
174
|
+
'.pdd/version.json': JSON.stringify({ templateVersion: PDD_TEMPLATE_VERSION }, null, 2) + '\n'
|
|
175
175
|
};
|
|
176
176
|
|
|
177
177
|
export const IDE_ADAPTERS = {
|
package/src/core/validator.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
3
|
|
|
4
|
-
function runCommand(command) {
|
|
4
|
+
function runCommand(command, baseDir) {
|
|
5
5
|
console.log(`→ ${command}`);
|
|
6
|
-
execSync(command, { stdio: 'inherit' });
|
|
6
|
+
execSync(command, { stdio: 'inherit', cwd: baseDir });
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function runValidation(baseDir = process.cwd()) {
|
|
@@ -29,7 +29,7 @@ export function runValidation(baseDir = process.cwd()) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
try {
|
|
32
|
-
commands.forEach(runCommand);
|
|
32
|
+
commands.forEach(command => runCommand(command, baseDir));
|
|
33
33
|
} catch {
|
|
34
34
|
throw new Error('Validation failed');
|
|
35
35
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { execSync, execFileSync } from 'child_process';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
|
|
4
5
|
function runGit(command, baseDir) {
|
|
@@ -9,7 +10,7 @@ function normalize(p) {
|
|
|
9
10
|
return path.resolve(String(p || '')).toLowerCase();
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
function detectWorktreeContext(baseDir = process.cwd()) {
|
|
13
|
+
export function detectWorktreeContext(baseDir = process.cwd()) {
|
|
13
14
|
try {
|
|
14
15
|
const topLevel = runGit('git rev-parse --show-toplevel', baseDir);
|
|
15
16
|
const gitDir = runGit('git rev-parse --git-dir', baseDir);
|
|
@@ -31,6 +32,45 @@ function detectWorktreeContext(baseDir = process.cwd()) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
function slug(value) {
|
|
36
|
+
return String(value || '')
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
39
|
+
.replace(/^-+|-+$/g, '')
|
|
40
|
+
.slice(0, 40);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createLinkedWorktree({
|
|
44
|
+
baseDir = process.cwd(),
|
|
45
|
+
commandName = 'change'
|
|
46
|
+
}) {
|
|
47
|
+
const context = detectWorktreeContext(baseDir);
|
|
48
|
+
if (!context.isGitRepo) {
|
|
49
|
+
throw new Error('Current directory is not a git repository.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const topLevel = context.topLevel;
|
|
53
|
+
const repoName = slug(path.basename(topLevel)) || 'repo';
|
|
54
|
+
const commandSlug = slug(commandName) || 'change';
|
|
55
|
+
const stamp = Date.now();
|
|
56
|
+
const branchName = `feature/pdd-auto-${commandSlug}-${stamp}`;
|
|
57
|
+
|
|
58
|
+
const worktreesRoot = path.join(path.dirname(topLevel), 'pdd-worktrees');
|
|
59
|
+
fs.mkdirSync(worktreesRoot, { recursive: true });
|
|
60
|
+
const worktreePath = path.join(worktreesRoot, `${repoName}-${commandSlug}-${stamp}`);
|
|
61
|
+
|
|
62
|
+
execFileSync(
|
|
63
|
+
'git',
|
|
64
|
+
['worktree', 'add', '-b', branchName, worktreePath, 'HEAD'],
|
|
65
|
+
{ cwd: topLevel, stdio: 'pipe' }
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
worktreePath,
|
|
70
|
+
branchName
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
34
74
|
export function enforceLinkedWorktree({
|
|
35
75
|
baseDir = process.cwd(),
|
|
36
76
|
commandName = 'command',
|