whitesmith 0.0.1 → 0.0.2
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/dist/auto-work.d.ts +11 -0
- package/dist/auto-work.d.ts.map +1 -0
- package/dist/auto-work.js +22 -0
- package/dist/auto-work.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -1
- package/dist/comment.d.ts +29 -0
- package/dist/comment.d.ts.map +1 -0
- package/dist/comment.js +390 -0
- package/dist/comment.js.map +1 -0
- package/dist/git.d.ts +12 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +57 -14
- package/dist/git.js.map +1 -1
- package/dist/harnesses/pi.d.ts +2 -0
- package/dist/harnesses/pi.d.ts.map +1 -1
- package/dist/harnesses/pi.js +92 -6
- package/dist/harnesses/pi.js.map +1 -1
- package/dist/install-ci.d.ts +7 -0
- package/dist/install-ci.d.ts.map +1 -0
- package/dist/install-ci.js +760 -0
- package/dist/install-ci.js.map +1 -0
- package/dist/orchestrator.d.ts +24 -4
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +252 -67
- package/dist/orchestrator.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +1 -0
- package/dist/prompts.js.map +1 -1
- package/dist/providers/github-ci.d.ts +16 -0
- package/dist/providers/github-ci.d.ts.map +1 -0
- package/dist/providers/github-ci.js +733 -0
- package/dist/providers/github-ci.js.map +1 -0
- package/dist/providers/github.d.ts +21 -0
- package/dist/providers/github.d.ts.map +1 -1
- package/dist/providers/github.js +88 -3
- package/dist/providers/github.js.map +1 -1
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/issue-provider.d.ts +26 -0
- package/dist/providers/issue-provider.d.ts.map +1 -1
- package/dist/task-manager.d.ts +4 -0
- package/dist/task-manager.d.ts.map +1 -1
- package/dist/task-manager.js +6 -0
- package/dist/task-manager.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -1
- package/src/auto-work.ts +26 -0
- package/src/cli.ts +114 -0
- package/src/comment.ts +531 -0
- package/src/git.ts +58 -12
- package/src/harnesses/pi.ts +108 -6
- package/src/orchestrator.ts +287 -76
- package/src/prompts.ts +1 -0
- package/src/providers/github-ci.ts +840 -0
- package/src/providers/github.ts +118 -5
- package/src/providers/index.ts +1 -0
- package/src/providers/issue-provider.ts +25 -1
- package/src/task-manager.ts +7 -0
- package/src/types.ts +7 -0
package/src/providers/github.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {exec} from 'node:child_process';
|
|
1
|
+
import {exec, execSync} from 'node:child_process';
|
|
2
2
|
import {promisify} from 'node:util';
|
|
3
3
|
import type {IssueProvider} from './issue-provider.js';
|
|
4
4
|
import type {Issue} from '../types.js';
|
|
5
|
+
import {installGitHubCI, type InstallCIOptions, type AuthMode} from './github-ci.js';
|
|
5
6
|
|
|
6
7
|
const execAsync = promisify(exec);
|
|
7
8
|
|
|
@@ -150,17 +151,82 @@ export class GitHubProvider implements IssueProvider {
|
|
|
150
151
|
|
|
151
152
|
async getPRForBranch(
|
|
152
153
|
branch: string,
|
|
153
|
-
): Promise<{state: 'open' | 'merged' | 'closed'; url: string} | null> {
|
|
154
|
+
): Promise<{state: 'open' | 'merged' | 'closed'; url: string; number: number} | null> {
|
|
154
155
|
try {
|
|
155
156
|
const raw = await this.gh(
|
|
156
|
-
`pr list --head "${branch}" --state all --json state,url --limit 1`,
|
|
157
|
+
`pr list --head "${branch}" --state all --json state,url,number --limit 1`,
|
|
157
158
|
);
|
|
158
159
|
if (!raw || raw === '[]') return null;
|
|
159
|
-
const prs = JSON.parse(raw) as Array<{state: string; url: string}>;
|
|
160
|
+
const prs = JSON.parse(raw) as Array<{state: string; url: string; number: number}>;
|
|
160
161
|
if (prs.length === 0) return null;
|
|
161
162
|
const pr = prs[0];
|
|
162
163
|
const state = pr.state.toLowerCase() as 'open' | 'merged' | 'closed';
|
|
163
|
-
return {state, url: pr.url};
|
|
164
|
+
return {state, url: pr.url, number: pr.number};
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async mergePR(number: number): Promise<void> {
|
|
171
|
+
await this.gh(`pr merge ${number} --merge --delete-branch`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async listPRsByBranchPrefix(
|
|
175
|
+
prefix: string,
|
|
176
|
+
): Promise<Array<{branch: string; number: number; title: string; state: string; url: string}>> {
|
|
177
|
+
try {
|
|
178
|
+
const raw = await this.gh(
|
|
179
|
+
`pr list --state all --json headRefName,number,title,state,url --limit 100`,
|
|
180
|
+
);
|
|
181
|
+
if (!raw || raw === '[]') return [];
|
|
182
|
+
const prs = JSON.parse(raw) as Array<{
|
|
183
|
+
headRefName: string;
|
|
184
|
+
number: number;
|
|
185
|
+
title: string;
|
|
186
|
+
state: string;
|
|
187
|
+
url: string;
|
|
188
|
+
}>;
|
|
189
|
+
return prs
|
|
190
|
+
.filter((pr) => pr.headRefName.startsWith(prefix))
|
|
191
|
+
.map((pr) => ({
|
|
192
|
+
branch: pr.headRefName,
|
|
193
|
+
number: pr.number,
|
|
194
|
+
title: pr.title,
|
|
195
|
+
state: pr.state.toLowerCase(),
|
|
196
|
+
url: pr.url,
|
|
197
|
+
}));
|
|
198
|
+
} catch {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async getPR(number: number): Promise<{
|
|
204
|
+
branch: string;
|
|
205
|
+
number: number;
|
|
206
|
+
title: string;
|
|
207
|
+
state: string;
|
|
208
|
+
url: string;
|
|
209
|
+
body: string;
|
|
210
|
+
} | null> {
|
|
211
|
+
try {
|
|
212
|
+
const raw = await this.gh(`pr view ${number} --json headRefName,number,title,state,url,body`);
|
|
213
|
+
if (!raw) return null;
|
|
214
|
+
const pr = JSON.parse(raw) as {
|
|
215
|
+
headRefName: string;
|
|
216
|
+
number: number;
|
|
217
|
+
title: string;
|
|
218
|
+
state: string;
|
|
219
|
+
url: string;
|
|
220
|
+
body: string;
|
|
221
|
+
};
|
|
222
|
+
return {
|
|
223
|
+
branch: pr.headRefName,
|
|
224
|
+
number: pr.number,
|
|
225
|
+
title: pr.title,
|
|
226
|
+
state: pr.state.toLowerCase(),
|
|
227
|
+
url: pr.url,
|
|
228
|
+
body: pr.body || '',
|
|
229
|
+
};
|
|
164
230
|
} catch {
|
|
165
231
|
return null;
|
|
166
232
|
}
|
|
@@ -177,4 +243,51 @@ export class GitHubProvider implements IssueProvider {
|
|
|
177
243
|
}
|
|
178
244
|
}
|
|
179
245
|
}
|
|
246
|
+
|
|
247
|
+
// ─── CI Installation ────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
async installCI(options: InstallCIOptions): Promise<void> {
|
|
250
|
+
const repo = this.repo || this.detectRepo();
|
|
251
|
+
const ghAvailable = this.ghIsAvailable();
|
|
252
|
+
|
|
253
|
+
await installGitHubCI(
|
|
254
|
+
{
|
|
255
|
+
workDir: this.workDir,
|
|
256
|
+
repo,
|
|
257
|
+
ghAvailable,
|
|
258
|
+
setSecret: async (name: string, value: string) => {
|
|
259
|
+
const targetRepo = this.repo || repo;
|
|
260
|
+
if (!targetRepo) throw new Error('No repo configured');
|
|
261
|
+
execSync(`gh secret set ${name} --repo "${targetRepo}"`, {
|
|
262
|
+
input: value,
|
|
263
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
options,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private detectRepo(): string | undefined {
|
|
272
|
+
try {
|
|
273
|
+
const url = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', {
|
|
274
|
+
cwd: this.workDir,
|
|
275
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
276
|
+
})
|
|
277
|
+
.toString()
|
|
278
|
+
.trim();
|
|
279
|
+
return url || undefined;
|
|
280
|
+
} catch {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private ghIsAvailable(): boolean {
|
|
286
|
+
try {
|
|
287
|
+
execSync('gh auth status', {stdio: ['pipe', 'pipe', 'pipe']});
|
|
288
|
+
return true;
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
180
293
|
}
|
package/src/providers/index.ts
CHANGED
|
@@ -50,10 +50,34 @@ export interface IssueProvider {
|
|
|
50
50
|
*/
|
|
51
51
|
getPRForBranch(
|
|
52
52
|
branch: string,
|
|
53
|
-
): Promise<{state: 'open' | 'merged' | 'closed'; url: string} | null>;
|
|
53
|
+
): Promise<{state: 'open' | 'merged' | 'closed'; url: string; number: number} | null>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Merge a pull request by number
|
|
57
|
+
*/
|
|
58
|
+
mergePR(number: number): Promise<void>;
|
|
54
59
|
|
|
55
60
|
/**
|
|
56
61
|
* Ensure required labels exist in the repo (create if missing)
|
|
57
62
|
*/
|
|
58
63
|
ensureLabels(labels: string[]): Promise<void>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* List PRs whose head branch matches a prefix
|
|
67
|
+
*/
|
|
68
|
+
listPRsByBranchPrefix(
|
|
69
|
+
prefix: string,
|
|
70
|
+
): Promise<Array<{branch: string; number: number; title: string; state: string; url: string}>>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get PR details by number (branch, state, body, etc.)
|
|
74
|
+
*/
|
|
75
|
+
getPR(number: number): Promise<{
|
|
76
|
+
branch: string;
|
|
77
|
+
number: number;
|
|
78
|
+
title: string;
|
|
79
|
+
state: string;
|
|
80
|
+
url: string;
|
|
81
|
+
body: string;
|
|
82
|
+
} | null>;
|
|
59
83
|
}
|
package/src/task-manager.ts
CHANGED
|
@@ -70,6 +70,13 @@ export class TaskManager {
|
|
|
70
70
|
return tasks;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Check if a task file exists (by its repo-relative path)
|
|
75
|
+
*/
|
|
76
|
+
taskFileExists(filePath: string): boolean {
|
|
77
|
+
return fs.existsSync(path.resolve(this.workDir, filePath));
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
/**
|
|
74
81
|
* Check if an issue has any remaining (pending) tasks
|
|
75
82
|
*/
|
package/src/types.ts
CHANGED
|
@@ -56,6 +56,8 @@ export const LABELS = {
|
|
|
56
56
|
TASKS_ACCEPTED: 'whitesmith:tasks-accepted',
|
|
57
57
|
/** All tasks for this issue have been completed */
|
|
58
58
|
COMPLETED: 'whitesmith:completed',
|
|
59
|
+
/** Auto-work mode: auto-approve task PRs */
|
|
60
|
+
AUTO_WORK: 'whitesmith:auto-work',
|
|
59
61
|
} as const;
|
|
60
62
|
|
|
61
63
|
/**
|
|
@@ -76,6 +78,10 @@ export interface DevPulseConfig {
|
|
|
76
78
|
noPush: boolean;
|
|
77
79
|
/** Skip sleep between iterations (for testing) */
|
|
78
80
|
noSleep: boolean;
|
|
81
|
+
/** Print what would be done without executing it */
|
|
82
|
+
dryRun: boolean;
|
|
83
|
+
/** Enable auto-work mode (auto-approve task PRs) */
|
|
84
|
+
autoWork: boolean;
|
|
79
85
|
/** Log file path */
|
|
80
86
|
logFile?: string;
|
|
81
87
|
/** GitHub repo in "owner/repo" format (auto-detected if not set) */
|
|
@@ -87,6 +93,7 @@ export interface DevPulseConfig {
|
|
|
87
93
|
*/
|
|
88
94
|
export type Action =
|
|
89
95
|
| {type: 'reconcile'; issue: Issue}
|
|
96
|
+
| {type: 'auto-approve'; issue: Issue}
|
|
90
97
|
| {type: 'investigate'; issue: Issue}
|
|
91
98
|
| {type: 'implement'; task: Task; issue: Issue}
|
|
92
99
|
| {type: 'idle'};
|