@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 CHANGED
@@ -1,60 +1,72 @@
1
- # PDD Patch-Driven Development
2
- ![npm](https://img.shields.io/npm/v/@pcoliveira90/pdd)
3
- ![license](https://img.shields.io/github/license/pcoliveira90/pdd)
4
- ![stars](https://img.shields.io/github/stars/pcoliveira90/pdd)
5
- ![issues](https://img.shields.io/github/issues/pcoliveira90/pdd)
6
- > Ship safe changes in living systems.
1
+ # PDD - Patch-Driven Development
7
2
 
8
- PDD is an open-source framework focused on **bugfixes and incremental feature development in existing systems**.
3
+ [![npm version](https://img.shields.io/npm/v/@pcoliveira90/pdd)](https://www.npmjs.com/package/@pcoliveira90/pdd)
4
+ [![CLI Self Validation](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml/badge.svg)](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml)
5
+ [![License: MIT](https://img.shields.io/github/license/pcoliveira90/pdd)](https://github.com/pcoliveira90/pdd/blob/main/LICENSE)
6
+ [![Node >=18](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
9
7
 
10
- ---
8
+ > Safe changes in real systems.
11
9
 
12
- ## The Problem
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
- Most development is not greenfield.
13
+ Language versions: [Default README](README.md) | [Português (Brasil)](README.pt-BR.md)
15
14
 
16
- It is:
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
- ## The Solution: PDD
22
+ ## Quick Start
24
23
 
25
- PDD focuses on:
26
- - understanding existing systems
27
- - identifying root causes
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 Flow
32
+ ## Core Commands
34
33
 
35
- Issue → Recon → Delta Spec → Patch Plan → Change → Verify
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
- ## Principles
44
+ ```bash
45
+ pdd-ai --provider=openai --task=analysis "bug description"
46
+ ```
40
47
 
41
- - Change-first
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
- ## Structure
55
+ ## IDE Alignment
52
56
 
53
- .pdd/
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
- ## Vision
63
+ ## Documentation
59
64
 
60
- Make AI-assisted development safe for real-world systems.
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 Patch-Driven Development
1
+ # PDD - Patch-Driven Development
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@pcoliveira90/pdd)](https://www.npmjs.com/package/@pcoliveira90/pdd)
4
+ [![CLI Self Validation](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml/badge.svg)](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml)
5
+ [![License: MIT](https://img.shields.io/github/license/pcoliveira90/pdd)](https://github.com/pcoliveira90/pdd/blob/main/LICENSE)
6
+ [![Node >=18](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
2
7
 
3
8
  > Safe changes in real systems.
4
9
 
5
- PDD is a framework focused on executing safe changes in existing systems.
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
- ## CLI
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 init
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
- ## Flow
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
- 1. describe issue
21
- 2. generate artifacts
22
- 3. validate
23
- 4. prepare PR (IDE handles opening)
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 Patch-Driven Development
1
+ # PDD - Patch-Driven Development
2
2
 
3
- > Entregue mudanças seguras em sistemas existentes.
3
+ [![npm version](https://img.shields.io/npm/v/@pcoliveira90/pdd)](https://www.npmjs.com/package/@pcoliveira90/pdd)
4
+ [![CLI Self Validation](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml/badge.svg)](https://github.com/pcoliveira90/pdd/actions/workflows/cli-self-validation.yml)
5
+ [![License: MIT](https://img.shields.io/github/license/pcoliveira90/pdd)](https://github.com/pcoliveira90/pdd/blob/main/LICENSE)
6
+ [![Node >=18](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
4
7
 
5
- PDD é um framework focado em aplicar mudanças seguras em sistemas reais.
8
+ > Entregue mudancas seguras em sistemas reais.
6
9
 
7
- ## CLI
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 init
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 fs from 'fs';
4
- import path from 'path';
3
+ import { runCli } from '../src/cli/index.js';
5
4
 
6
- const command = process.argv[2];
7
-
8
- if (command === 'init') {
9
- const targetDir = process.cwd();
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.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": "echo \"No tests yet\"",
18
- "lint": "echo \"No lint configured\"",
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(arg => !arg.startsWith('--provider') && !arg.startsWith('--model') && arg !== 'fix' && arg !== '--ai');
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 fix --ai "login not saving incomeStatus"');
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 = buildBugfixPrompt({ issue });
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
- exists(baseDir, '.cursor/rules/pdd.mdc') ||
14
- exists(baseDir, '.cursor/commands/pdd.md') ||
15
- exists(baseDir, '.cursor/pdd.prompt.md')
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
- exists(baseDir, '.claude/commands/pdd.md') ||
22
- exists(baseDir, '.claude/CLAUDE.md')
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
- exists(baseDir, '.github/copilot/pdd.prompt.md') ||
29
- exists(baseDir, '.github/copilot-instructions.md')
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 { enforceLinkedWorktree } from '../core/worktree-guard.js';
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 (mutatesCurrentRepo) {
49
- enforceLinkedWorktree({
50
- baseDir: cwd,
51
- commandName: 'init',
52
- allowMainWorktree
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
- const allowMainWorktree = argv.includes('--allow-main-worktree');
61
- if (argv.includes('--fix')) {
62
- enforceLinkedWorktree({
63
- baseDir: cwd,
64
- commandName: 'doctor --fix',
65
- allowMainWorktree
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, allowMainWorktree } = parseFixArgs(argv);
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
- enforceLinkedWorktree({
87
- baseDir: cwd,
125
+ if (maybeAutoRelocateToWorktree({
126
+ cwd,
127
+ argv,
88
128
  commandName: 'fix',
89
- allowMainWorktree
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 require linked git worktree by default.');
147
- console.log(' Override intentionally with --allow-main-worktree.');
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"');
@@ -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
- let phase = 'patch-generation';
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 timestamp = Date.now();
23
- const changeId = `change-${timestamp}-${slugify(issue || 'update')}`;
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', changeId, 'delta-spec.md'),
28
- path.join('changes', changeId, 'patch-plan.md'),
29
- path.join('changes', changeId, 'verification-report.md')
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
- ${changeId}
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
- ${changeId}
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
- ${changeId}
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
  };
@@ -1,21 +1,21 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { execSync } from 'child_process';
3
+ import { execFileSync } from 'child_process';
4
4
 
5
- function exec(command) {
6
- execSync(command, { stdio: 'inherit' });
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
- exec(`git checkout -b ${branch}`);
17
- exec('git add .');
18
- exec(`git commit -m "${title}"`);
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: '0.2.3' }, null, 2) + '\n'
174
+ '.pdd/version.json': JSON.stringify({ templateVersion: PDD_TEMPLATE_VERSION }, null, 2) + '\n'
175
175
  };
176
176
 
177
177
  export const IDE_ADAPTERS = {
@@ -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 { execSync } from 'child_process';
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',