@pcoliveira90/pdd 0.2.6 â 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 -270
- 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 -301
- 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
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
import { buildBugfixPrompt } from './analyze-change.js';
|
|
2
|
-
import { getAiProviderConfig } from './engine.js';
|
|
3
|
-
|
|
4
|
-
function extractArgValue(args, name, fallback = null) {
|
|
5
|
-
const prefix = `${name}=`;
|
|
6
|
-
const direct = args.find(arg => arg.startsWith(prefix));
|
|
7
|
-
if (direct) return direct.slice(prefix.length);
|
|
8
|
-
|
|
9
|
-
const index = args.findIndex(arg => arg === name);
|
|
10
|
-
if (index >= 0 && args[index + 1]) return args[index + 1];
|
|
11
|
-
|
|
12
|
-
return fallback;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function getIssueFromArgs(args) {
|
|
16
|
-
const filtered = args.filter(arg => !arg.startsWith('--provider') && !arg.startsWith('--model') && arg !== 'fix' && arg !== '--ai');
|
|
17
|
-
return filtered.join(' ').trim();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function buildStructuredInstruction() {
|
|
21
|
-
return [
|
|
22
|
-
'Return valid JSON only.',
|
|
23
|
-
'Use exactly these keys:',
|
|
24
|
-
'{',
|
|
25
|
-
' "root_cause_hypothesis": string,',
|
|
26
|
-
' "impacted_areas": string[],',
|
|
27
|
-
' "minimal_safe_delta": string,',
|
|
28
|
-
' "regression_risks": string[],',
|
|
29
|
-
' "validation_plan": string[]',
|
|
30
|
-
'}'
|
|
31
|
-
].join('\n');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function callOpenAI({ prompt, model, apiKey, baseUrl }) {
|
|
35
|
-
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
36
|
-
method: 'POST',
|
|
37
|
-
headers: {
|
|
38
|
-
'Content-Type': 'application/json',
|
|
39
|
-
Authorization: `Bearer ${apiKey}`
|
|
40
|
-
},
|
|
41
|
-
body: JSON.stringify({
|
|
42
|
-
model,
|
|
43
|
-
temperature: 0.2,
|
|
44
|
-
messages: [
|
|
45
|
-
{ role: 'system', content: buildStructuredInstruction() },
|
|
46
|
-
{ role: 'user', content: prompt }
|
|
47
|
-
]
|
|
48
|
-
})
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (!response.ok) {
|
|
52
|
-
const text = await response.text();
|
|
53
|
-
throw new Error(`OpenAI request failed (${response.status}): ${text}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const data = await response.json();
|
|
57
|
-
return data.choices?.[0]?.message?.content ?? '';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function callClaude({ prompt, model, apiKey, baseUrl }) {
|
|
61
|
-
const response = await fetch(`${baseUrl}/messages`, {
|
|
62
|
-
method: 'POST',
|
|
63
|
-
headers: {
|
|
64
|
-
'Content-Type': 'application/json',
|
|
65
|
-
'x-api-key': apiKey,
|
|
66
|
-
'anthropic-version': '2023-06-01'
|
|
67
|
-
},
|
|
68
|
-
body: JSON.stringify({
|
|
69
|
-
model,
|
|
70
|
-
max_tokens: 1200,
|
|
71
|
-
temperature: 0.2,
|
|
72
|
-
system: buildStructuredInstruction(),
|
|
73
|
-
messages: [
|
|
74
|
-
{ role: 'user', content: prompt }
|
|
75
|
-
]
|
|
76
|
-
})
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
if (!response.ok) {
|
|
80
|
-
const text = await response.text();
|
|
81
|
-
throw new Error(`Claude request failed (${response.status}): ${text}`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const data = await response.json();
|
|
85
|
-
return data.content?.[0]?.text ?? '';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function callOpenRouter({ prompt, model, apiKey, baseUrl }) {
|
|
89
|
-
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
headers: {
|
|
92
|
-
'Content-Type': 'application/json',
|
|
93
|
-
Authorization: `Bearer ${apiKey}`,
|
|
94
|
-
'HTTP-Referer': 'https://github.com/pcoliveira90/pdd',
|
|
95
|
-
'X-Title': 'PDD CLI'
|
|
96
|
-
},
|
|
97
|
-
body: JSON.stringify({
|
|
98
|
-
model,
|
|
99
|
-
temperature: 0.2,
|
|
100
|
-
messages: [
|
|
101
|
-
{ role: 'system', content: buildStructuredInstruction() },
|
|
102
|
-
{ role: 'user', content: prompt }
|
|
103
|
-
]
|
|
104
|
-
})
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
if (!response.ok) {
|
|
108
|
-
const text = await response.text();
|
|
109
|
-
throw new Error(`OpenRouter request failed (${response.status}): ${text}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const data = await response.json();
|
|
113
|
-
return data.choices?.[0]?.message?.content ?? '';
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function resolveBaseUrl(providerConfig) {
|
|
117
|
-
const envValue = process.env[providerConfig.baseUrlEnv];
|
|
118
|
-
if (envValue) return envValue;
|
|
119
|
-
|
|
120
|
-
if (providerConfig.name === 'openai') return 'https://api.openai.com/v1';
|
|
121
|
-
if (providerConfig.name === 'claude') return 'https://api.anthropic.com/v1';
|
|
122
|
-
if (providerConfig.name === 'openrouter') return 'https://openrouter.ai/api/v1';
|
|
123
|
-
|
|
124
|
-
throw new Error(`Missing base URL for provider: ${providerConfig.name}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function parseJsonSafely(text) {
|
|
128
|
-
try {
|
|
129
|
-
return JSON.parse(text);
|
|
130
|
-
} catch {
|
|
131
|
-
return {
|
|
132
|
-
raw_output: text
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export async function runAiFixAnalysis(argv = process.argv.slice(2)) {
|
|
138
|
-
const provider = extractArgValue(argv, '--provider', 'openai');
|
|
139
|
-
const providerConfig = getAiProviderConfig(provider);
|
|
140
|
-
const model = extractArgValue(argv, '--model', providerConfig.defaultModel);
|
|
141
|
-
const issue = getIssueFromArgs(argv);
|
|
142
|
-
|
|
143
|
-
if (!issue) {
|
|
144
|
-
throw new Error('Missing issue description. Example: pdd fix --ai "login not saving incomeStatus"');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const apiKey = process.env[providerConfig.envKey];
|
|
148
|
-
if (!apiKey) {
|
|
149
|
-
throw new Error(`Missing API key. Set ${providerConfig.envKey}.`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const baseUrl = resolveBaseUrl(providerConfig);
|
|
153
|
-
const prompt = buildBugfixPrompt({ issue });
|
|
154
|
-
|
|
155
|
-
let raw;
|
|
156
|
-
if (provider === 'openai') {
|
|
157
|
-
raw = await callOpenAI({ prompt, model, apiKey, baseUrl });
|
|
158
|
-
} else if (provider === 'claude') {
|
|
159
|
-
raw = await callClaude({ prompt, model, apiKey, baseUrl });
|
|
160
|
-
} else if (provider === 'openrouter') {
|
|
161
|
-
raw = await callOpenRouter({ prompt, model, apiKey, baseUrl });
|
|
162
|
-
} else {
|
|
163
|
-
throw new Error(`Unsupported provider: ${provider}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const parsed = parseJsonSafely(raw);
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
provider,
|
|
170
|
-
model,
|
|
171
|
-
issue,
|
|
172
|
-
result: parsed
|
|
173
|
-
};
|
|
174
|
-
}
|
|
1
|
+
import { buildBugfixPrompt } from './analyze-change.js';
|
|
2
|
+
import { getAiProviderConfig } from './engine.js';
|
|
3
|
+
|
|
4
|
+
function extractArgValue(args, name, fallback = null) {
|
|
5
|
+
const prefix = `${name}=`;
|
|
6
|
+
const direct = args.find(arg => arg.startsWith(prefix));
|
|
7
|
+
if (direct) return direct.slice(prefix.length);
|
|
8
|
+
|
|
9
|
+
const index = args.findIndex(arg => arg === name);
|
|
10
|
+
if (index >= 0 && args[index + 1]) return args[index + 1];
|
|
11
|
+
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getIssueFromArgs(args) {
|
|
16
|
+
const filtered = args.filter(arg => !arg.startsWith('--provider') && !arg.startsWith('--model') && arg !== 'fix' && arg !== '--ai');
|
|
17
|
+
return filtered.join(' ').trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildStructuredInstruction() {
|
|
21
|
+
return [
|
|
22
|
+
'Return valid JSON only.',
|
|
23
|
+
'Use exactly these keys:',
|
|
24
|
+
'{',
|
|
25
|
+
' "root_cause_hypothesis": string,',
|
|
26
|
+
' "impacted_areas": string[],',
|
|
27
|
+
' "minimal_safe_delta": string,',
|
|
28
|
+
' "regression_risks": string[],',
|
|
29
|
+
' "validation_plan": string[]',
|
|
30
|
+
'}'
|
|
31
|
+
].join('\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function callOpenAI({ prompt, model, apiKey, baseUrl }) {
|
|
35
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
Authorization: `Bearer ${apiKey}`
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
model,
|
|
43
|
+
temperature: 0.2,
|
|
44
|
+
messages: [
|
|
45
|
+
{ role: 'system', content: buildStructuredInstruction() },
|
|
46
|
+
{ role: 'user', content: prompt }
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const text = await response.text();
|
|
53
|
+
throw new Error(`OpenAI request failed (${response.status}): ${text}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
return data.choices?.[0]?.message?.content ?? '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function callClaude({ prompt, model, apiKey, baseUrl }) {
|
|
61
|
+
const response = await fetch(`${baseUrl}/messages`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
'x-api-key': apiKey,
|
|
66
|
+
'anthropic-version': '2023-06-01'
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
model,
|
|
70
|
+
max_tokens: 1200,
|
|
71
|
+
temperature: 0.2,
|
|
72
|
+
system: buildStructuredInstruction(),
|
|
73
|
+
messages: [
|
|
74
|
+
{ role: 'user', content: prompt }
|
|
75
|
+
]
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
throw new Error(`Claude request failed (${response.status}): ${text}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
return data.content?.[0]?.text ?? '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function callOpenRouter({ prompt, model, apiKey, baseUrl }) {
|
|
89
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: {
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
Authorization: `Bearer ${apiKey}`,
|
|
94
|
+
'HTTP-Referer': 'https://github.com/pcoliveira90/pdd',
|
|
95
|
+
'X-Title': 'PDD CLI'
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
model,
|
|
99
|
+
temperature: 0.2,
|
|
100
|
+
messages: [
|
|
101
|
+
{ role: 'system', content: buildStructuredInstruction() },
|
|
102
|
+
{ role: 'user', content: prompt }
|
|
103
|
+
]
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const text = await response.text();
|
|
109
|
+
throw new Error(`OpenRouter request failed (${response.status}): ${text}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
return data.choices?.[0]?.message?.content ?? '';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function resolveBaseUrl(providerConfig) {
|
|
117
|
+
const envValue = process.env[providerConfig.baseUrlEnv];
|
|
118
|
+
if (envValue) return envValue;
|
|
119
|
+
|
|
120
|
+
if (providerConfig.name === 'openai') return 'https://api.openai.com/v1';
|
|
121
|
+
if (providerConfig.name === 'claude') return 'https://api.anthropic.com/v1';
|
|
122
|
+
if (providerConfig.name === 'openrouter') return 'https://openrouter.ai/api/v1';
|
|
123
|
+
|
|
124
|
+
throw new Error(`Missing base URL for provider: ${providerConfig.name}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseJsonSafely(text) {
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(text);
|
|
130
|
+
} catch {
|
|
131
|
+
return {
|
|
132
|
+
raw_output: text
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function runAiFixAnalysis(argv = process.argv.slice(2)) {
|
|
138
|
+
const provider = extractArgValue(argv, '--provider', 'openai');
|
|
139
|
+
const providerConfig = getAiProviderConfig(provider);
|
|
140
|
+
const model = extractArgValue(argv, '--model', providerConfig.defaultModel);
|
|
141
|
+
const issue = getIssueFromArgs(argv);
|
|
142
|
+
|
|
143
|
+
if (!issue) {
|
|
144
|
+
throw new Error('Missing issue description. Example: pdd fix --ai "login not saving incomeStatus"');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const apiKey = process.env[providerConfig.envKey];
|
|
148
|
+
if (!apiKey) {
|
|
149
|
+
throw new Error(`Missing API key. Set ${providerConfig.envKey}.`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const baseUrl = resolveBaseUrl(providerConfig);
|
|
153
|
+
const prompt = buildBugfixPrompt({ issue });
|
|
154
|
+
|
|
155
|
+
let raw;
|
|
156
|
+
if (provider === 'openai') {
|
|
157
|
+
raw = await callOpenAI({ prompt, model, apiKey, baseUrl });
|
|
158
|
+
} else if (provider === 'claude') {
|
|
159
|
+
raw = await callClaude({ prompt, model, apiKey, baseUrl });
|
|
160
|
+
} else if (provider === 'openrouter') {
|
|
161
|
+
raw = await callOpenRouter({ prompt, model, apiKey, baseUrl });
|
|
162
|
+
} else {
|
|
163
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const parsed = parseJsonSafely(raw);
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
provider,
|
|
170
|
+
model,
|
|
171
|
+
issue,
|
|
172
|
+
result: parsed
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -1,101 +1,123 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { PDD_TEMPLATE_VERSION } from '../core/template-registry.js';
|
|
4
|
-
import { runDoctorFix } from './doctor-fix.js';
|
|
5
|
-
import { buildDoctorRemediationPlan, printDoctorRemediationPlan } from '../core/remediation-advisor.js';
|
|
6
|
-
|
|
7
|
-
function exists(baseDir, relativePath) {
|
|
8
|
-
return fs.existsSync(path.join(baseDir, relativePath));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
console.log(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { PDD_TEMPLATE_VERSION } from '../core/template-registry.js';
|
|
4
|
+
import { runDoctorFix } from './doctor-fix.js';
|
|
5
|
+
import { buildDoctorRemediationPlan, printDoctorRemediationPlan } from '../core/remediation-advisor.js';
|
|
6
|
+
|
|
7
|
+
function exists(baseDir, relativePath) {
|
|
8
|
+
return fs.existsSync(path.join(baseDir, relativePath));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
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
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function claudeAdapterInstalled(baseDir) {
|
|
20
|
+
return (
|
|
21
|
+
exists(baseDir, '.claude/commands/pdd.md') ||
|
|
22
|
+
exists(baseDir, '.claude/CLAUDE.md')
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function copilotAdapterInstalled(baseDir) {
|
|
27
|
+
return (
|
|
28
|
+
exists(baseDir, '.github/copilot/pdd.prompt.md') ||
|
|
29
|
+
exists(baseDir, '.github/copilot-instructions.md')
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readVersion(baseDir) {
|
|
34
|
+
const file = path.join(baseDir, '.pdd/version.json');
|
|
35
|
+
if (!fs.existsSync(file)) return null;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8')).templateVersion;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function print(label, ok, detail = '') {
|
|
45
|
+
const icon = ok ? 'â
' : 'â ī¸';
|
|
46
|
+
console.log(`${icon} ${label}${detail ? ` â ${detail}` : ''}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function runDoctor(baseDir = process.cwd(), argv = []) {
|
|
50
|
+
const shouldFix = argv.includes('--fix');
|
|
51
|
+
|
|
52
|
+
console.log('đŠē PDD Doctor\n');
|
|
53
|
+
|
|
54
|
+
const coreChecks = {
|
|
55
|
+
constitution: exists(baseDir, '.pdd/constitution.md'),
|
|
56
|
+
delta: exists(baseDir, '.pdd/templates/delta-spec.md'),
|
|
57
|
+
patch: exists(baseDir, '.pdd/templates/patch-plan.md'),
|
|
58
|
+
verification: exists(baseDir, '.pdd/templates/verification-report.md'),
|
|
59
|
+
memory: exists(baseDir, '.pdd/memory/system-map.md')
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const adapters = {
|
|
63
|
+
claude: claudeAdapterInstalled(baseDir),
|
|
64
|
+
cursor: cursorAdapterInstalled(baseDir),
|
|
65
|
+
copilot: copilotAdapterInstalled(baseDir)
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const installedVersion = readVersion(baseDir);
|
|
69
|
+
|
|
70
|
+
// Raw checks
|
|
71
|
+
print('Core constitution', coreChecks.constitution);
|
|
72
|
+
print('Delta spec', coreChecks.delta);
|
|
73
|
+
print('Patch plan', coreChecks.patch);
|
|
74
|
+
print('Verification report', coreChecks.verification);
|
|
75
|
+
print('System map', coreChecks.memory);
|
|
76
|
+
|
|
77
|
+
console.log('');
|
|
78
|
+
|
|
79
|
+
print('Claude adapter', adapters.claude, adapters.claude ? 'installed' : 'missing');
|
|
80
|
+
print('Cursor adapter', adapters.cursor, adapters.cursor ? 'installed' : 'missing');
|
|
81
|
+
print('Copilot adapter', adapters.copilot, adapters.copilot ? 'installed' : 'missing');
|
|
82
|
+
|
|
83
|
+
console.log('');
|
|
84
|
+
|
|
85
|
+
if (!installedVersion) {
|
|
86
|
+
console.log('â ī¸ No template version detected. Run `pdd init --here --force`.');
|
|
87
|
+
} else if (installedVersion !== PDD_TEMPLATE_VERSION) {
|
|
88
|
+
console.log(`â ī¸ Templates outdated (${installedVersion} â ${PDD_TEMPLATE_VERSION})`);
|
|
89
|
+
console.log('đ Run: pdd init --here --upgrade');
|
|
90
|
+
} else {
|
|
91
|
+
console.log('đ Templates up to date');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!adapters.claude && !adapters.cursor && !adapters.copilot) {
|
|
95
|
+
console.log('âšī¸ No IDE adapters installed');
|
|
96
|
+
console.log('đ Run: pdd init --here --ide=claude (or cursor/copilot)');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Guided remediation
|
|
100
|
+
const plan = buildDoctorRemediationPlan({
|
|
101
|
+
coreChecks,
|
|
102
|
+
adapters,
|
|
103
|
+
installedVersion,
|
|
104
|
+
currentVersion: PDD_TEMPLATE_VERSION
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
printDoctorRemediationPlan(plan);
|
|
108
|
+
|
|
109
|
+
if (shouldFix) {
|
|
110
|
+
console.log('');
|
|
111
|
+
const result = runDoctorFix(baseDir);
|
|
112
|
+
|
|
113
|
+
if (result.changed) {
|
|
114
|
+
console.log('đ§ Auto-repair applied:');
|
|
115
|
+
result.repaired.forEach(file => console.log(`- fixed: ${file}`));
|
|
116
|
+
if (result.installedVersion && result.installedVersion !== result.currentVersion) {
|
|
117
|
+
console.log(`âšī¸ Version updated: ${result.installedVersion} â ${result.currentVersion}`);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
console.log('â
No fixes needed');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|