@pcoliveira90/pdd 0.3.1 → 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
@@ -22,13 +22,10 @@ Language versions: [Default README](README.md) | [Português (Brasil)](README.pt
22
22
  ## Quick Start
23
23
 
24
24
  ```bash
25
- # 1) Create a linked worktree (recommended and enforced for mutating flows)
26
- git worktree add ../pdd-worktrees/my-change -b feature/my-change
27
-
28
- # 2) Initialize PDD in the repository
25
+ # 1) Initialize PDD in the repository (if running in primary, PDD auto-creates a linked worktree)
29
26
  pdd init --here
30
27
 
31
- # 3) Run a fix workflow
28
+ # 2) Run a fix workflow
32
29
  pdd fix "login not saving incomeStatus"
33
30
  ```
34
31
 
package/README.md CHANGED
@@ -22,13 +22,10 @@ Language versions: [English](README.en.md) | [Português (Brasil)](README.pt-BR.
22
22
  ## Quick Start
23
23
 
24
24
  ```bash
25
- # 1) Create a linked worktree (recommended and enforced for mutating flows)
26
- git worktree add ../pdd-worktrees/my-change -b feature/my-change
27
-
28
- # 2) Initialize PDD in the repository
25
+ # 1) Initialize PDD in the repository (if running in primary, PDD auto-creates a linked worktree)
29
26
  pdd init --here
30
27
 
31
- # 3) Run a fix workflow
28
+ # 2) Run a fix workflow
32
29
  pdd fix "login not saving incomeStatus"
33
30
  ```
34
31
 
package/README.pt-BR.md CHANGED
@@ -22,13 +22,10 @@ Versoes por idioma: [README padrao](README.md) | [English](README.en.md)
22
22
  ## Inicio rapido
23
23
 
24
24
  ```bash
25
- # 1) Crie uma worktree vinculada (recomendado e exigido para fluxos mutaveis)
26
- git worktree add ../pdd-worktrees/minha-mudanca -b feature/minha-mudanca
27
-
28
- # 2) Inicialize o PDD no repositorio
25
+ # 1) Inicialize o PDD no repositorio (se estiver na principal, o PDD cria worktree vinculada automaticamente)
29
26
  pdd init --here
30
27
 
31
- # 3) Rode um fluxo de correcao
28
+ # 2) Rode um fluxo de correcao
32
29
  pdd fix "login nao salva incomeStatus"
33
30
  ```
34
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pcoliveira90/pdd",
3
- "version": "0.3.1",
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": {
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"');
@@ -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',