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.
Files changed (64) hide show
  1. package/dist/auto-work.d.ts +11 -0
  2. package/dist/auto-work.d.ts.map +1 -0
  3. package/dist/auto-work.js +22 -0
  4. package/dist/auto-work.js.map +1 -0
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +99 -0
  7. package/dist/cli.js.map +1 -1
  8. package/dist/comment.d.ts +29 -0
  9. package/dist/comment.d.ts.map +1 -0
  10. package/dist/comment.js +390 -0
  11. package/dist/comment.js.map +1 -0
  12. package/dist/git.d.ts +12 -0
  13. package/dist/git.d.ts.map +1 -1
  14. package/dist/git.js +57 -14
  15. package/dist/git.js.map +1 -1
  16. package/dist/harnesses/pi.d.ts +2 -0
  17. package/dist/harnesses/pi.d.ts.map +1 -1
  18. package/dist/harnesses/pi.js +92 -6
  19. package/dist/harnesses/pi.js.map +1 -1
  20. package/dist/install-ci.d.ts +7 -0
  21. package/dist/install-ci.d.ts.map +1 -0
  22. package/dist/install-ci.js +760 -0
  23. package/dist/install-ci.js.map +1 -0
  24. package/dist/orchestrator.d.ts +24 -4
  25. package/dist/orchestrator.d.ts.map +1 -1
  26. package/dist/orchestrator.js +252 -67
  27. package/dist/orchestrator.js.map +1 -1
  28. package/dist/prompts.d.ts.map +1 -1
  29. package/dist/prompts.js +1 -0
  30. package/dist/prompts.js.map +1 -1
  31. package/dist/providers/github-ci.d.ts +16 -0
  32. package/dist/providers/github-ci.d.ts.map +1 -0
  33. package/dist/providers/github-ci.js +733 -0
  34. package/dist/providers/github-ci.js.map +1 -0
  35. package/dist/providers/github.d.ts +21 -0
  36. package/dist/providers/github.d.ts.map +1 -1
  37. package/dist/providers/github.js +88 -3
  38. package/dist/providers/github.js.map +1 -1
  39. package/dist/providers/index.d.ts +1 -0
  40. package/dist/providers/index.d.ts.map +1 -1
  41. package/dist/providers/issue-provider.d.ts +26 -0
  42. package/dist/providers/issue-provider.d.ts.map +1 -1
  43. package/dist/task-manager.d.ts +4 -0
  44. package/dist/task-manager.d.ts.map +1 -1
  45. package/dist/task-manager.js +6 -0
  46. package/dist/task-manager.js.map +1 -1
  47. package/dist/types.d.ts +9 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js +2 -0
  50. package/dist/types.js.map +1 -1
  51. package/package.json +2 -1
  52. package/src/auto-work.ts +26 -0
  53. package/src/cli.ts +114 -0
  54. package/src/comment.ts +531 -0
  55. package/src/git.ts +58 -12
  56. package/src/harnesses/pi.ts +108 -6
  57. package/src/orchestrator.ts +287 -76
  58. package/src/prompts.ts +1 -0
  59. package/src/providers/github-ci.ts +840 -0
  60. package/src/providers/github.ts +118 -5
  61. package/src/providers/index.ts +1 -0
  62. package/src/providers/issue-provider.ts +25 -1
  63. package/src/task-manager.ts +7 -0
  64. package/src/types.ts +7 -0
@@ -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
  }
@@ -1,2 +1,3 @@
1
1
  export type {IssueProvider} from './issue-provider.js';
2
2
  export {GitHubProvider} from './github.js';
3
+ export type {AuthMode, InstallCIOptions} from './github-ci.js';
@@ -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
  }
@@ -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'};