@pcoliveira90/pdd 0.2.5 → 0.3.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/LICENSE +20 -20
- package/README.en.md +60 -60
- package/README.md +27 -26
- package/README.pt-BR.md +12 -12
- package/bin/pdd-ai.js +23 -23
- package/bin/pdd-pro.js +7 -7
- package/bin/pdd.js +27 -27
- package/package.json +43 -42
- package/src/ai/analyze-change.js +41 -41
- package/src/ai/engine.js +34 -34
- package/src/ai/run-fix-analysis.js +174 -174
- package/src/cli/doctor-command.js +123 -101
- package/src/cli/doctor-fix.js +51 -51
- package/src/cli/index.js +164 -130
- package/src/cli/init-command.js +270 -249
- package/src/cli/status-command.js +33 -33
- package/src/core/fix-runner.js +135 -135
- package/src/core/ide-detector.js +94 -94
- package/src/core/patch-generator.js +126 -126
- package/src/core/pr-manager.js +21 -21
- package/src/core/project-review-agent.js +301 -0
- package/src/core/remediation-advisor.js +91 -91
- package/src/core/state-manager.js +71 -71
- package/src/core/template-registry.js +446 -320
- package/src/core/template-upgrade.js +68 -68
- package/src/core/validator.js +38 -38
- package/src/core/worktree-guard.js +54 -0
package/src/core/fix-runner.js
CHANGED
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import {
|
|
4
|
-
readProjectState,
|
|
5
|
-
setActiveChange,
|
|
6
|
-
clearActiveChange,
|
|
7
|
-
writeProjectState
|
|
8
|
-
} from './state-manager.js';
|
|
9
|
-
|
|
10
|
-
function ensureDir(filePath) {
|
|
11
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function appendText(filePath, content) {
|
|
15
|
-
ensureDir(filePath);
|
|
16
|
-
fs.appendFileSync(filePath, content, 'utf-8');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function writeJson(filePath, value) {
|
|
20
|
-
ensureDir(filePath);
|
|
21
|
-
fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf-8');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function nowIso() {
|
|
25
|
-
return new Date().toISOString();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function buildFailurePayload({ issue, changeId, phase, error }) {
|
|
29
|
-
return {
|
|
30
|
-
issue,
|
|
31
|
-
changeId,
|
|
32
|
-
phase,
|
|
33
|
-
error: error?.message || String(error),
|
|
34
|
-
failedAt: nowIso()
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function persistFailureArtifacts(baseDir, payload) {
|
|
39
|
-
const changeDir = path.join(baseDir, 'changes', payload.changeId);
|
|
40
|
-
const failureReportPath = path.join(changeDir, 'failure-report.json');
|
|
41
|
-
const verificationReportPath = path.join(changeDir, 'verification-report.md');
|
|
42
|
-
|
|
43
|
-
writeJson(failureReportPath, payload);
|
|
44
|
-
appendText(
|
|
45
|
-
verificationReportPath,
|
|
46
|
-
`\n## Failure\n- phase: ${payload.phase}\n- error: ${payload.error}\n- failedAt: ${payload.failedAt}\n`
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
failureReportPath,
|
|
51
|
-
verificationReportPath
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function runResilientFixWorkflow({
|
|
56
|
-
baseDir = process.cwd(),
|
|
57
|
-
issue,
|
|
58
|
-
dryRun = false,
|
|
59
|
-
noValidate = false,
|
|
60
|
-
openPr = false,
|
|
61
|
-
generatePatchArtifacts,
|
|
62
|
-
runValidation,
|
|
63
|
-
openPullRequest
|
|
64
|
-
}) {
|
|
65
|
-
const current = readProjectState(baseDir);
|
|
66
|
-
if (current.status === 'in-progress' && current.activeChange) {
|
|
67
|
-
throw new Error(`Another change is already in progress: ${current.activeChange}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (dryRun) {
|
|
71
|
-
return {
|
|
72
|
-
status: 'dry-run',
|
|
73
|
-
issue
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const changeId = `change-${Date.now()}`;
|
|
78
|
-
setActiveChange(baseDir, changeId, 'in-progress');
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
let phase = 'patch-generation';
|
|
82
|
-
const patch = generatePatchArtifacts({ issue, baseDir });
|
|
83
|
-
|
|
84
|
-
if (!noValidate) {
|
|
85
|
-
phase = 'validation';
|
|
86
|
-
runValidation(baseDir);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (openPr) {
|
|
90
|
-
phase = 'pr-preparation';
|
|
91
|
-
await openPullRequest({
|
|
92
|
-
issue,
|
|
93
|
-
changeId: patch.changeId,
|
|
94
|
-
changeDir: patch.changeDir,
|
|
95
|
-
baseDir
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
clearActiveChange(baseDir, 'completed');
|
|
100
|
-
writeProjectState(baseDir, {
|
|
101
|
-
activeChange: null,
|
|
102
|
-
lastChange: patch.changeId,
|
|
103
|
-
status: 'completed',
|
|
104
|
-
lastResult: 'success',
|
|
105
|
-
lastIssue: issue,
|
|
106
|
-
lastPhase: 'completed',
|
|
107
|
-
lastError: null
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
status: 'completed',
|
|
112
|
-
changeId: patch.changeId,
|
|
113
|
-
files: patch.files
|
|
114
|
-
};
|
|
115
|
-
} catch (error) {
|
|
116
|
-
const phase = current.status === 'in-progress' ? current.lastPhase || 'unknown' : 'unknown';
|
|
117
|
-
const payload = buildFailurePayload({ issue, changeId, phase, error });
|
|
118
|
-
const artifacts = persistFailureArtifacts(baseDir, payload);
|
|
119
|
-
|
|
120
|
-
writeProjectState(baseDir, {
|
|
121
|
-
activeChange: changeId,
|
|
122
|
-
lastChange: changeId,
|
|
123
|
-
status: 'failed',
|
|
124
|
-
lastResult: 'failed',
|
|
125
|
-
lastIssue: issue,
|
|
126
|
-
lastPhase: phase,
|
|
127
|
-
lastError: payload.error,
|
|
128
|
-
lastFailureReport: artifacts.failureReportPath
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const wrapped = new Error(`Fix failed during ${phase}: ${payload.error}`);
|
|
132
|
-
wrapped.cause = error;
|
|
133
|
-
throw wrapped;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import {
|
|
4
|
+
readProjectState,
|
|
5
|
+
setActiveChange,
|
|
6
|
+
clearActiveChange,
|
|
7
|
+
writeProjectState
|
|
8
|
+
} from './state-manager.js';
|
|
9
|
+
|
|
10
|
+
function ensureDir(filePath) {
|
|
11
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function appendText(filePath, content) {
|
|
15
|
+
ensureDir(filePath);
|
|
16
|
+
fs.appendFileSync(filePath, content, 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function writeJson(filePath, value) {
|
|
20
|
+
ensureDir(filePath);
|
|
21
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function nowIso() {
|
|
25
|
+
return new Date().toISOString();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildFailurePayload({ issue, changeId, phase, error }) {
|
|
29
|
+
return {
|
|
30
|
+
issue,
|
|
31
|
+
changeId,
|
|
32
|
+
phase,
|
|
33
|
+
error: error?.message || String(error),
|
|
34
|
+
failedAt: nowIso()
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function persistFailureArtifacts(baseDir, payload) {
|
|
39
|
+
const changeDir = path.join(baseDir, 'changes', payload.changeId);
|
|
40
|
+
const failureReportPath = path.join(changeDir, 'failure-report.json');
|
|
41
|
+
const verificationReportPath = path.join(changeDir, 'verification-report.md');
|
|
42
|
+
|
|
43
|
+
writeJson(failureReportPath, payload);
|
|
44
|
+
appendText(
|
|
45
|
+
verificationReportPath,
|
|
46
|
+
`\n## Failure\n- phase: ${payload.phase}\n- error: ${payload.error}\n- failedAt: ${payload.failedAt}\n`
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
failureReportPath,
|
|
51
|
+
verificationReportPath
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function runResilientFixWorkflow({
|
|
56
|
+
baseDir = process.cwd(),
|
|
57
|
+
issue,
|
|
58
|
+
dryRun = false,
|
|
59
|
+
noValidate = false,
|
|
60
|
+
openPr = false,
|
|
61
|
+
generatePatchArtifacts,
|
|
62
|
+
runValidation,
|
|
63
|
+
openPullRequest
|
|
64
|
+
}) {
|
|
65
|
+
const current = readProjectState(baseDir);
|
|
66
|
+
if (current.status === 'in-progress' && current.activeChange) {
|
|
67
|
+
throw new Error(`Another change is already in progress: ${current.activeChange}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (dryRun) {
|
|
71
|
+
return {
|
|
72
|
+
status: 'dry-run',
|
|
73
|
+
issue
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const changeId = `change-${Date.now()}`;
|
|
78
|
+
setActiveChange(baseDir, changeId, 'in-progress');
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
let phase = 'patch-generation';
|
|
82
|
+
const patch = generatePatchArtifacts({ issue, baseDir });
|
|
83
|
+
|
|
84
|
+
if (!noValidate) {
|
|
85
|
+
phase = 'validation';
|
|
86
|
+
runValidation(baseDir);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (openPr) {
|
|
90
|
+
phase = 'pr-preparation';
|
|
91
|
+
await openPullRequest({
|
|
92
|
+
issue,
|
|
93
|
+
changeId: patch.changeId,
|
|
94
|
+
changeDir: patch.changeDir,
|
|
95
|
+
baseDir
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
clearActiveChange(baseDir, 'completed');
|
|
100
|
+
writeProjectState(baseDir, {
|
|
101
|
+
activeChange: null,
|
|
102
|
+
lastChange: patch.changeId,
|
|
103
|
+
status: 'completed',
|
|
104
|
+
lastResult: 'success',
|
|
105
|
+
lastIssue: issue,
|
|
106
|
+
lastPhase: 'completed',
|
|
107
|
+
lastError: null
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
status: 'completed',
|
|
112
|
+
changeId: patch.changeId,
|
|
113
|
+
files: patch.files
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const phase = current.status === 'in-progress' ? current.lastPhase || 'unknown' : 'unknown';
|
|
117
|
+
const payload = buildFailurePayload({ issue, changeId, phase, error });
|
|
118
|
+
const artifacts = persistFailureArtifacts(baseDir, payload);
|
|
119
|
+
|
|
120
|
+
writeProjectState(baseDir, {
|
|
121
|
+
activeChange: changeId,
|
|
122
|
+
lastChange: changeId,
|
|
123
|
+
status: 'failed',
|
|
124
|
+
lastResult: 'failed',
|
|
125
|
+
lastIssue: issue,
|
|
126
|
+
lastPhase: phase,
|
|
127
|
+
lastError: payload.error,
|
|
128
|
+
lastFailureReport: artifacts.failureReportPath
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const wrapped = new Error(`Fix failed during ${phase}: ${payload.error}`);
|
|
132
|
+
wrapped.cause = error;
|
|
133
|
+
throw wrapped;
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/core/ide-detector.js
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
/** Stable order for prompts and numbered selection */
|
|
6
|
-
export const IDE_ORDER = ['cursor', 'claude', 'copilot'];
|
|
7
|
-
|
|
8
|
-
export const IDE_LABELS = {
|
|
9
|
-
cursor: 'Cursor',
|
|
10
|
-
claude: 'Claude (desktop / Claude Code)',
|
|
11
|
-
copilot: 'GitHub Copilot (VS Code)'
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function exists(p) {
|
|
15
|
-
return Boolean(p && fs.existsSync(p));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function vscodeDetected() {
|
|
19
|
-
const platform = process.platform;
|
|
20
|
-
const home = os.homedir();
|
|
21
|
-
|
|
22
|
-
if (platform === 'win32') {
|
|
23
|
-
const local = process.env.LOCALAPPDATA;
|
|
24
|
-
const pf = process.env.PROGRAMFILES;
|
|
25
|
-
const pf86 = process.env['PROGRAMFILES(X86)'];
|
|
26
|
-
return (
|
|
27
|
-
exists(local && path.join(local, 'Programs', 'Microsoft VS Code', 'Code.exe')) ||
|
|
28
|
-
exists(pf && path.join(pf, 'Microsoft VS Code', 'Code.exe')) ||
|
|
29
|
-
exists(pf86 && path.join(pf86, 'Microsoft VS Code', 'Code.exe')) ||
|
|
30
|
-
exists(path.join(home, '.vscode'))
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (platform === 'darwin') {
|
|
35
|
-
return (
|
|
36
|
-
exists('/Applications/Visual Studio Code.app') ||
|
|
37
|
-
exists(path.join(home, '.vscode'))
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
exists('/usr/share/code/code') ||
|
|
43
|
-
exists('/usr/bin/code') ||
|
|
44
|
-
exists('/snap/bin/code') ||
|
|
45
|
-
exists(path.join(home, '.vscode'))
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Heuristic detection of IDE-related tooling (best-effort, no guarantees).
|
|
51
|
-
* @returns {{ cursor: boolean, claude: boolean, copilot: boolean }}
|
|
52
|
-
*/
|
|
53
|
-
export function detectIdePresence() {
|
|
54
|
-
const platform = process.platform;
|
|
55
|
-
const home = os.homedir();
|
|
56
|
-
const local = process.env.LOCALAPPDATA;
|
|
57
|
-
|
|
58
|
-
const presence = {
|
|
59
|
-
cursor: false,
|
|
60
|
-
claude: false,
|
|
61
|
-
copilot: false
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
if (platform === 'win32') {
|
|
65
|
-
presence.cursor =
|
|
66
|
-
exists(local && path.join(local, 'Programs', 'cursor', 'Cursor.exe')) ||
|
|
67
|
-
exists(path.join(home, '.cursor'));
|
|
68
|
-
|
|
69
|
-
presence.claude =
|
|
70
|
-
exists(local && path.join(local, 'Programs', 'Claude', 'Claude.exe')) ||
|
|
71
|
-
exists(path.join(home, '.claude'));
|
|
72
|
-
} else if (platform === 'darwin') {
|
|
73
|
-
presence.cursor =
|
|
74
|
-
exists('/Applications/Cursor.app') || exists(path.join(home, '.cursor'));
|
|
75
|
-
|
|
76
|
-
presence.claude =
|
|
77
|
-
exists('/Applications/Claude.app') || exists(path.join(home, '.claude'));
|
|
78
|
-
} else {
|
|
79
|
-
presence.cursor =
|
|
80
|
-
exists(path.join(home, '.cursor')) ||
|
|
81
|
-
exists('/opt/Cursor') ||
|
|
82
|
-
exists('/usr/share/cursor');
|
|
83
|
-
|
|
84
|
-
presence.claude = exists(path.join(home, '.claude'));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
presence.copilot = vscodeDetected();
|
|
88
|
-
|
|
89
|
-
return presence;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function keysWhereDetected(presence) {
|
|
93
|
-
return IDE_ORDER.filter(k => presence[k]);
|
|
94
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/** Stable order for prompts and numbered selection */
|
|
6
|
+
export const IDE_ORDER = ['cursor', 'claude', 'copilot'];
|
|
7
|
+
|
|
8
|
+
export const IDE_LABELS = {
|
|
9
|
+
cursor: 'Cursor',
|
|
10
|
+
claude: 'Claude (desktop / Claude Code)',
|
|
11
|
+
copilot: 'GitHub Copilot (VS Code)'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function exists(p) {
|
|
15
|
+
return Boolean(p && fs.existsSync(p));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function vscodeDetected() {
|
|
19
|
+
const platform = process.platform;
|
|
20
|
+
const home = os.homedir();
|
|
21
|
+
|
|
22
|
+
if (platform === 'win32') {
|
|
23
|
+
const local = process.env.LOCALAPPDATA;
|
|
24
|
+
const pf = process.env.PROGRAMFILES;
|
|
25
|
+
const pf86 = process.env['PROGRAMFILES(X86)'];
|
|
26
|
+
return (
|
|
27
|
+
exists(local && path.join(local, 'Programs', 'Microsoft VS Code', 'Code.exe')) ||
|
|
28
|
+
exists(pf && path.join(pf, 'Microsoft VS Code', 'Code.exe')) ||
|
|
29
|
+
exists(pf86 && path.join(pf86, 'Microsoft VS Code', 'Code.exe')) ||
|
|
30
|
+
exists(path.join(home, '.vscode'))
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (platform === 'darwin') {
|
|
35
|
+
return (
|
|
36
|
+
exists('/Applications/Visual Studio Code.app') ||
|
|
37
|
+
exists(path.join(home, '.vscode'))
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
exists('/usr/share/code/code') ||
|
|
43
|
+
exists('/usr/bin/code') ||
|
|
44
|
+
exists('/snap/bin/code') ||
|
|
45
|
+
exists(path.join(home, '.vscode'))
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Heuristic detection of IDE-related tooling (best-effort, no guarantees).
|
|
51
|
+
* @returns {{ cursor: boolean, claude: boolean, copilot: boolean }}
|
|
52
|
+
*/
|
|
53
|
+
export function detectIdePresence() {
|
|
54
|
+
const platform = process.platform;
|
|
55
|
+
const home = os.homedir();
|
|
56
|
+
const local = process.env.LOCALAPPDATA;
|
|
57
|
+
|
|
58
|
+
const presence = {
|
|
59
|
+
cursor: false,
|
|
60
|
+
claude: false,
|
|
61
|
+
copilot: false
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (platform === 'win32') {
|
|
65
|
+
presence.cursor =
|
|
66
|
+
exists(local && path.join(local, 'Programs', 'cursor', 'Cursor.exe')) ||
|
|
67
|
+
exists(path.join(home, '.cursor'));
|
|
68
|
+
|
|
69
|
+
presence.claude =
|
|
70
|
+
exists(local && path.join(local, 'Programs', 'Claude', 'Claude.exe')) ||
|
|
71
|
+
exists(path.join(home, '.claude'));
|
|
72
|
+
} else if (platform === 'darwin') {
|
|
73
|
+
presence.cursor =
|
|
74
|
+
exists('/Applications/Cursor.app') || exists(path.join(home, '.cursor'));
|
|
75
|
+
|
|
76
|
+
presence.claude =
|
|
77
|
+
exists('/Applications/Claude.app') || exists(path.join(home, '.claude'));
|
|
78
|
+
} else {
|
|
79
|
+
presence.cursor =
|
|
80
|
+
exists(path.join(home, '.cursor')) ||
|
|
81
|
+
exists('/opt/Cursor') ||
|
|
82
|
+
exists('/usr/share/cursor');
|
|
83
|
+
|
|
84
|
+
presence.claude = exists(path.join(home, '.claude'));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
presence.copilot = vscodeDetected();
|
|
88
|
+
|
|
89
|
+
return presence;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function keysWhereDetected(presence) {
|
|
93
|
+
return IDE_ORDER.filter(k => presence[k]);
|
|
94
|
+
}
|