whitesmith 0.0.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.
Files changed (70) hide show
  1. package/dist/__tests__/task-manager.test.d.ts +2 -0
  2. package/dist/__tests__/task-manager.test.d.ts.map +1 -0
  3. package/dist/__tests__/task-manager.test.js +95 -0
  4. package/dist/__tests__/task-manager.test.js.map +1 -0
  5. package/dist/cli.d.ts +5 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +147 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/git.d.ts +60 -0
  10. package/dist/git.d.ts.map +1 -0
  11. package/dist/git.js +138 -0
  12. package/dist/git.js.map +1 -0
  13. package/dist/harnesses/agent-harness.d.ts +19 -0
  14. package/dist/harnesses/agent-harness.d.ts.map +1 -0
  15. package/dist/harnesses/agent-harness.js +2 -0
  16. package/dist/harnesses/agent-harness.js.map +1 -0
  17. package/dist/harnesses/index.d.ts +3 -0
  18. package/dist/harnesses/index.d.ts.map +1 -0
  19. package/dist/harnesses/index.js +2 -0
  20. package/dist/harnesses/index.js.map +1 -0
  21. package/dist/harnesses/pi.d.ts +23 -0
  22. package/dist/harnesses/pi.d.ts.map +1 -0
  23. package/dist/harnesses/pi.js +63 -0
  24. package/dist/harnesses/pi.js.map +1 -0
  25. package/dist/index.d.ts +11 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +8 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/orchestrator.d.ts +44 -0
  30. package/dist/orchestrator.d.ts.map +1 -0
  31. package/dist/orchestrator.js +241 -0
  32. package/dist/orchestrator.js.map +1 -0
  33. package/dist/prompts.d.ts +12 -0
  34. package/dist/prompts.d.ts.map +1 -0
  35. package/dist/prompts.js +112 -0
  36. package/dist/prompts.js.map +1 -0
  37. package/dist/providers/github.d.ts +34 -0
  38. package/dist/providers/github.d.ts.map +1 -0
  39. package/dist/providers/github.js +135 -0
  40. package/dist/providers/github.js.map +1 -0
  41. package/dist/providers/index.d.ts +3 -0
  42. package/dist/providers/index.d.ts.map +1 -0
  43. package/dist/providers/index.js +2 -0
  44. package/dist/providers/index.js.map +1 -0
  45. package/dist/providers/issue-provider.d.ts +59 -0
  46. package/dist/providers/issue-provider.d.ts.map +1 -0
  47. package/dist/providers/issue-provider.js +2 -0
  48. package/dist/providers/issue-provider.js.map +1 -0
  49. package/dist/task-manager.d.ts +57 -0
  50. package/dist/task-manager.d.ts.map +1 -0
  51. package/dist/task-manager.js +158 -0
  52. package/dist/task-manager.js.map +1 -0
  53. package/dist/types.d.ts +92 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +14 -0
  56. package/dist/types.js.map +1 -0
  57. package/package.json +46 -0
  58. package/src/cli.ts +172 -0
  59. package/src/git.ts +148 -0
  60. package/src/harnesses/agent-harness.ts +15 -0
  61. package/src/harnesses/index.ts +2 -0
  62. package/src/harnesses/pi.ts +82 -0
  63. package/src/index.ts +13 -0
  64. package/src/orchestrator.ts +294 -0
  65. package/src/prompts.ts +114 -0
  66. package/src/providers/github.ts +180 -0
  67. package/src/providers/index.ts +2 -0
  68. package/src/providers/issue-provider.ts +59 -0
  69. package/src/task-manager.ts +190 -0
  70. package/src/types.ts +88 -0
@@ -0,0 +1,59 @@
1
+ import type { Issue } from '../types.js';
2
+ /**
3
+ * Interface for issue sources.
4
+ * Implementations can back this with GitHub Issues, GitLab, Jira, etc.
5
+ */
6
+ export interface IssueProvider {
7
+ /**
8
+ * List open issues, optionally filtered by labels
9
+ */
10
+ listIssues(options?: {
11
+ labels?: string[];
12
+ noLabels?: string[];
13
+ }): Promise<Issue[]>;
14
+ /**
15
+ * Get a single issue by number
16
+ */
17
+ getIssue(number: number): Promise<Issue>;
18
+ /**
19
+ * Add a label to an issue
20
+ */
21
+ addLabel(number: number, label: string): Promise<void>;
22
+ /**
23
+ * Remove a label from an issue
24
+ */
25
+ removeLabel(number: number, label: string): Promise<void>;
26
+ /**
27
+ * Post a comment on an issue
28
+ */
29
+ comment(number: number, body: string): Promise<void>;
30
+ /**
31
+ * Close an issue
32
+ */
33
+ closeIssue(number: number): Promise<void>;
34
+ /**
35
+ * Create a pull request and return its URL
36
+ */
37
+ createPR(options: {
38
+ head: string;
39
+ base: string;
40
+ title: string;
41
+ body: string;
42
+ }): Promise<string>;
43
+ /**
44
+ * Check if a remote branch exists
45
+ */
46
+ remoteBranchExists(branch: string): Promise<boolean>;
47
+ /**
48
+ * Check if there's an open or merged PR for a given head branch
49
+ */
50
+ getPRForBranch(branch: string): Promise<{
51
+ state: 'open' | 'merged' | 'closed';
52
+ url: string;
53
+ } | null>;
54
+ /**
55
+ * Ensure required labels exist in the repo (create if missing)
56
+ */
57
+ ensureLabels(labels: string[]): Promise<void>;
58
+ }
59
+ //# sourceMappingURL=issue-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-provider.d.ts","sourceRoot":"","sources":["../../src/providers/issue-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,aAAa,CAAC;AAEvC;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B;;OAEG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAC,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAEjF;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAEzC;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvD;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErD;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1C;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9F;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAErD;;OAEG;IACH,cAAc,CACb,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC,CAAC;IAEtE;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=issue-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-provider.js","sourceRoot":"","sources":["../../src/providers/issue-provider.ts"],"names":[],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import type { Task, TaskFrontmatter } from './types.js';
2
+ /**
3
+ * Manages task files in the tasks/ directory.
4
+ *
5
+ * Task files live at: tasks/<issue-number>/<seq>-<slug>.md
6
+ * Each file has YAML frontmatter with id, issue, title, depends_on.
7
+ */
8
+ export declare class TaskManager {
9
+ private workDir;
10
+ constructor(workDir: string);
11
+ /**
12
+ * Get the tasks directory path
13
+ */
14
+ getTasksDir(): string;
15
+ /**
16
+ * Get the directory for a specific issue's tasks
17
+ */
18
+ getIssueTasksDir(issueNumber: number): string;
19
+ /**
20
+ * List all pending tasks for an issue (files in tasks/<issue>/)
21
+ */
22
+ listTasks(issueNumber: number): Task[];
23
+ /**
24
+ * List all pending tasks across all issues
25
+ */
26
+ listAllTasks(): Task[];
27
+ /**
28
+ * Check if an issue has any remaining (pending) tasks
29
+ */
30
+ hasRemainingTasks(issueNumber: number): boolean;
31
+ /**
32
+ * Get issue numbers that have task files
33
+ */
34
+ getIssuesWithTasks(): number[];
35
+ /**
36
+ * Read and parse a single task file
37
+ */
38
+ readTask(filePath: string): Task;
39
+ /**
40
+ * Write a task file
41
+ */
42
+ writeTask(issueNumber: number, seq: number, slug: string, frontmatter: TaskFrontmatter, body: string): string;
43
+ /**
44
+ * Delete a task file (called when task is implemented)
45
+ */
46
+ deleteTask(taskFilePath: string): void;
47
+ /**
48
+ * Check if all dependencies of a task are satisfied
49
+ * (i.e. their task files have been deleted from the tasks directory)
50
+ */
51
+ areDependenciesSatisfied(task: Task): boolean;
52
+ /**
53
+ * Parse YAML frontmatter from a markdown file
54
+ */
55
+ private parseFrontmatter;
56
+ }
57
+ //# sourceMappingURL=task-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-manager.d.ts","sourceRoot":"","sources":["../src/task-manager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,IAAI,EAAE,eAAe,EAAC,MAAM,YAAY,CAAC;AAItD;;;;;GAKG;AACH,qBAAa,WAAW;IACvB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAI3B;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAI7C;;OAEG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE;IAYtC;;OAEG;IACH,YAAY,IAAI,IAAI,EAAE;IAoBtB;;OAEG;IACH,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAI/C;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAc9B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAehC;;OAEG;IACH,SAAS,CACR,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,eAAe,EAC5B,IAAI,EAAE,MAAM,GACV,MAAM;IAcT;;OAEG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAiBtC;;;OAGG;IACH,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAU7C;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAexB"}
@@ -0,0 +1,158 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
4
+ const TASKS_DIR = 'tasks';
5
+ /**
6
+ * Manages task files in the tasks/ directory.
7
+ *
8
+ * Task files live at: tasks/<issue-number>/<seq>-<slug>.md
9
+ * Each file has YAML frontmatter with id, issue, title, depends_on.
10
+ */
11
+ export class TaskManager {
12
+ workDir;
13
+ constructor(workDir) {
14
+ this.workDir = workDir;
15
+ }
16
+ /**
17
+ * Get the tasks directory path
18
+ */
19
+ getTasksDir() {
20
+ return path.join(this.workDir, TASKS_DIR);
21
+ }
22
+ /**
23
+ * Get the directory for a specific issue's tasks
24
+ */
25
+ getIssueTasksDir(issueNumber) {
26
+ return path.join(this.workDir, TASKS_DIR, String(issueNumber));
27
+ }
28
+ /**
29
+ * List all pending tasks for an issue (files in tasks/<issue>/)
30
+ */
31
+ listTasks(issueNumber) {
32
+ const dir = this.getIssueTasksDir(issueNumber);
33
+ if (!fs.existsSync(dir))
34
+ return [];
35
+ const files = fs
36
+ .readdirSync(dir)
37
+ .filter((f) => f.endsWith('.md'))
38
+ .sort();
39
+ return files.map((f) => this.readTask(path.join(dir, f)));
40
+ }
41
+ /**
42
+ * List all pending tasks across all issues
43
+ */
44
+ listAllTasks() {
45
+ const tasksDir = this.getTasksDir();
46
+ if (!fs.existsSync(tasksDir))
47
+ return [];
48
+ const issueDirs = fs
49
+ .readdirSync(tasksDir)
50
+ .filter((d) => {
51
+ const fullPath = path.join(tasksDir, d);
52
+ return fs.statSync(fullPath).isDirectory() && /^\d+$/.test(d);
53
+ })
54
+ .sort();
55
+ const tasks = [];
56
+ for (const issueDir of issueDirs) {
57
+ const issueNumber = parseInt(issueDir, 10);
58
+ tasks.push(...this.listTasks(issueNumber));
59
+ }
60
+ return tasks;
61
+ }
62
+ /**
63
+ * Check if an issue has any remaining (pending) tasks
64
+ */
65
+ hasRemainingTasks(issueNumber) {
66
+ return this.listTasks(issueNumber).length > 0;
67
+ }
68
+ /**
69
+ * Get issue numbers that have task files
70
+ */
71
+ getIssuesWithTasks() {
72
+ const tasksDir = this.getTasksDir();
73
+ if (!fs.existsSync(tasksDir))
74
+ return [];
75
+ return fs
76
+ .readdirSync(tasksDir)
77
+ .filter((d) => {
78
+ const fullPath = path.join(tasksDir, d);
79
+ return fs.statSync(fullPath).isDirectory() && /^\d+$/.test(d);
80
+ })
81
+ .map((d) => parseInt(d, 10))
82
+ .sort((a, b) => a - b);
83
+ }
84
+ /**
85
+ * Read and parse a single task file
86
+ */
87
+ readTask(filePath) {
88
+ const content = fs.readFileSync(filePath, 'utf-8');
89
+ const frontmatter = this.parseFrontmatter(content);
90
+ const relativePath = path.relative(this.workDir, filePath);
91
+ return {
92
+ id: frontmatter.id,
93
+ issue: frontmatter.issue,
94
+ title: frontmatter.title,
95
+ dependsOn: frontmatter.depends_on || [],
96
+ content,
97
+ filePath: relativePath,
98
+ };
99
+ }
100
+ /**
101
+ * Write a task file
102
+ */
103
+ writeTask(issueNumber, seq, slug, frontmatter, body) {
104
+ const dir = this.getIssueTasksDir(issueNumber);
105
+ fs.mkdirSync(dir, { recursive: true });
106
+ const seqStr = String(seq).padStart(3, '0');
107
+ const fileName = `${seqStr}-${slug}.md`;
108
+ const filePath = path.join(dir, fileName);
109
+ const content = `---\n${stringifyYaml(frontmatter).trim()}\n---\n\n${body}`;
110
+ fs.writeFileSync(filePath, content, 'utf-8');
111
+ return path.relative(this.workDir, filePath);
112
+ }
113
+ /**
114
+ * Delete a task file (called when task is implemented)
115
+ */
116
+ deleteTask(taskFilePath) {
117
+ const fullPath = path.resolve(this.workDir, taskFilePath);
118
+ if (fs.existsSync(fullPath)) {
119
+ fs.unlinkSync(fullPath);
120
+ }
121
+ // Clean up empty issue directory
122
+ const dir = path.dirname(fullPath);
123
+ if (fs.existsSync(dir)) {
124
+ const remaining = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
125
+ if (remaining.length === 0) {
126
+ // Remove directory if no more task files
127
+ fs.rmSync(dir, { recursive: true, force: true });
128
+ }
129
+ }
130
+ }
131
+ /**
132
+ * Check if all dependencies of a task are satisfied
133
+ * (i.e. their task files have been deleted from the tasks directory)
134
+ */
135
+ areDependenciesSatisfied(task) {
136
+ if (task.dependsOn.length === 0)
137
+ return true;
138
+ const allTasks = this.listAllTasks();
139
+ const pendingIds = new Set(allTasks.map((t) => t.id));
140
+ // A dependency is satisfied if its task file no longer exists (deleted = completed)
141
+ return task.dependsOn.every((depId) => !pendingIds.has(depId));
142
+ }
143
+ /**
144
+ * Parse YAML frontmatter from a markdown file
145
+ */
146
+ parseFrontmatter(content) {
147
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
148
+ if (!match) {
149
+ throw new Error('Task file is missing YAML frontmatter');
150
+ }
151
+ const parsed = parseYaml(match[1]);
152
+ if (!parsed.id || parsed.issue === undefined || !parsed.title) {
153
+ throw new Error(`Task frontmatter is missing required fields (id, issue, title). Got: ${JSON.stringify(parsed)}`);
154
+ }
155
+ return parsed;
156
+ }
157
+ }
158
+ //# sourceMappingURL=task-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-manager.js","sourceRoot":"","sources":["../src/task-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * A GitHub issue (or equivalent) created by a human.
3
+ * Represents a problem, feature request, or improvement.
4
+ */
5
+ export interface Issue {
6
+ /** Issue number (e.g. 42) */
7
+ number: number;
8
+ /** Issue title */
9
+ title: string;
10
+ /** Issue body/description (markdown) */
11
+ body: string;
12
+ /** Current labels on the issue */
13
+ labels: string[];
14
+ /** Issue URL */
15
+ url: string;
16
+ }
17
+ /**
18
+ * A task generated from an issue.
19
+ * Each task represents a single PR's worth of work.
20
+ */
21
+ export interface Task {
22
+ /** Unique task ID: "<issue>-<seq>" e.g. "42-001" */
23
+ id: string;
24
+ /** Parent issue number */
25
+ issue: number;
26
+ /** Human-readable title */
27
+ title: string;
28
+ /** Task IDs this depends on (must be completed first) */
29
+ dependsOn: string[];
30
+ /** Full markdown content of the task file (including frontmatter) */
31
+ content: string;
32
+ /** File path relative to repo root */
33
+ filePath: string;
34
+ }
35
+ /**
36
+ * Parsed frontmatter from a task file
37
+ */
38
+ export interface TaskFrontmatter {
39
+ id: string;
40
+ issue: number;
41
+ title: string;
42
+ depends_on?: string[];
43
+ }
44
+ /**
45
+ * Labels used by whitesmith to track issue state
46
+ */
47
+ export declare const LABELS: {
48
+ /** Agent is generating tasks for this issue */
49
+ readonly INVESTIGATING: "whitesmith:investigating";
50
+ /** A PR with generated tasks has been opened */
51
+ readonly TASKS_PROPOSED: "whitesmith:tasks-proposed";
52
+ /** Task PR has been merged — tasks are on main */
53
+ readonly TASKS_ACCEPTED: "whitesmith:tasks-accepted";
54
+ /** All tasks for this issue have been completed */
55
+ readonly COMPLETED: "whitesmith:completed";
56
+ };
57
+ /**
58
+ * Configuration for whitesmith
59
+ */
60
+ export interface DevPulseConfig {
61
+ /** Command to run the agent harness */
62
+ agentCmd: string;
63
+ /** Maximum iterations per run */
64
+ maxIterations: number;
65
+ /** Working directory (the repo) */
66
+ workDir: string;
67
+ /** Skip pushing branches and creating PRs */
68
+ noPush: boolean;
69
+ /** Skip sleep between iterations (for testing) */
70
+ noSleep: boolean;
71
+ /** Log file path */
72
+ logFile?: string;
73
+ /** GitHub repo in "owner/repo" format (auto-detected if not set) */
74
+ repo?: string;
75
+ }
76
+ /**
77
+ * What the orchestrator should do next
78
+ */
79
+ export type Action = {
80
+ type: 'reconcile';
81
+ issue: Issue;
82
+ } | {
83
+ type: 'investigate';
84
+ issue: Issue;
85
+ } | {
86
+ type: 'implement';
87
+ task: Task;
88
+ issue: Issue;
89
+ } | {
90
+ type: 'idle';
91
+ };
92
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,KAAK;IACrB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gBAAgB;IAChB,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;GAGG;AACH,MAAM,WAAW,IAAI;IACpB,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,MAAM;IAClB,+CAA+C;;IAE/C,gDAAgD;;IAEhD,kDAAkD;;IAElD,mDAAmD;;CAE1C,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,MAAM,MAAM,GACf;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAC,GACjC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAC,GACnC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAC,GAC7C;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Labels used by whitesmith to track issue state
3
+ */
4
+ export const LABELS = {
5
+ /** Agent is generating tasks for this issue */
6
+ INVESTIGATING: 'whitesmith:investigating',
7
+ /** A PR with generated tasks has been opened */
8
+ TASKS_PROPOSED: 'whitesmith:tasks-proposed',
9
+ /** Task PR has been merged — tasks are on main */
10
+ TASKS_ACCEPTED: 'whitesmith:tasks-accepted',
11
+ /** All tasks for this issue have been completed */
12
+ COMPLETED: 'whitesmith:completed',
13
+ };
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA8CA;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACrB,+CAA+C;IAC/C,aAAa,EAAE,0BAA0B;IACzC,gDAAgD;IAChD,cAAc,EAAE,2BAA2B;IAC3C,kDAAkD;IAClD,cAAc,EAAE,2BAA2B;IAC3C,mDAAmD;IACnD,SAAS,EAAE,sBAAsB;CACxB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "whitesmith",
3
+ "version": "0.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "bin": {
9
+ "whitesmith": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src"
20
+ ],
21
+ "dependencies": {
22
+ "commander": "^14.0.3",
23
+ "simple-git": "^3.33.0",
24
+ "yaml": "^2.7.1"
25
+ },
26
+ "devDependencies": {
27
+ "@changesets/cli": "^2.29.8",
28
+ "@types/node": "^25.2.0",
29
+ "as-soon": "^0.1.5",
30
+ "cross-env": "^10.1.0",
31
+ "prettier": "^3.8.0",
32
+ "tsx": "^4.21.0",
33
+ "typescript": "^5.3.3",
34
+ "vitest": "^4.0.18"
35
+ },
36
+ "scripts": {
37
+ "prepublishOnly": "pnpm format:check && pnpm build",
38
+ "release": "pnpm prepublishOnly && git push --all && pnpm changeset publish && git push --tags",
39
+ "format": "prettier --write .",
40
+ "format:check": "prettier --check .",
41
+ "build": "tsc",
42
+ "dev": "as-soon -w src pnpm build",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest"
45
+ }
46
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {Command} from 'commander';
4
+ import * as path from 'node:path';
5
+ import * as fs from 'node:fs';
6
+ import type {DevPulseConfig} from './types.js';
7
+ import {LABELS} from './types.js';
8
+ import {Orchestrator} from './orchestrator.js';
9
+ import {GitHubProvider} from './providers/github.js';
10
+ import {PiHarness} from './harnesses/pi.js';
11
+ import {TaskManager} from './task-manager.js';
12
+ import pkg from '../package.json' with {type: 'json'};
13
+
14
+ const DEFAULT_AGENT_CMD = 'pi';
15
+ const DEFAULT_MAX_ITERATIONS = 10;
16
+
17
+ function createOrchestrator(config: DevPulseConfig): Orchestrator {
18
+ const issues = new GitHubProvider(config.workDir, config.repo);
19
+ const agent = new PiHarness(config.agentCmd);
20
+ return new Orchestrator(config, issues, agent);
21
+ }
22
+
23
+ const packageName = pkg.name;
24
+ const binName = typeof pkg.bin === 'string' ? pkg.bin : Object.keys(pkg.bin)[0];
25
+
26
+ export function buildCli(): Command {
27
+ const program = new Command();
28
+
29
+ program.name(binName).description('AI-powered issue-to-PR pipeline').version('0.0.0');
30
+
31
+ // --- run ---
32
+ program
33
+ .command('run')
34
+ .description('Run the main whitesmith loop: investigate issues, implement tasks')
35
+ .argument('[work_dir]', 'Working directory', '.')
36
+ .option('--agent-cmd <cmd>', 'Agent harness command', DEFAULT_AGENT_CMD)
37
+ .option('--max-iterations <n>', 'Max iterations', String(DEFAULT_MAX_ITERATIONS))
38
+ .option('--repo <owner/repo>', 'GitHub repo (auto-detected if omitted)')
39
+ .option('--log-file <path>', 'Log agent output to file')
40
+ .option('--no-push', 'Skip pushing and PR creation')
41
+ .option('--no-sleep', 'Skip sleep between iterations')
42
+ .action(async (workDir: string, opts) => {
43
+ const config: DevPulseConfig = {
44
+ agentCmd: opts.agentCmd,
45
+ maxIterations: parseInt(opts.maxIterations, 10),
46
+ workDir: path.resolve(workDir),
47
+ noPush: opts.push === false,
48
+ noSleep: opts.sleep === false,
49
+ logFile: opts.logFile,
50
+ repo: opts.repo,
51
+ };
52
+
53
+ if (!fs.existsSync(config.workDir)) {
54
+ console.error(`ERROR: Directory '${config.workDir}' does not exist`);
55
+ process.exit(1);
56
+ }
57
+
58
+ process.chdir(config.workDir);
59
+ const orchestrator = createOrchestrator(config);
60
+
61
+ try {
62
+ await orchestrator.run();
63
+ } catch (error) {
64
+ console.error('ERROR:', error instanceof Error ? error.message : error);
65
+ process.exit(1);
66
+ }
67
+ });
68
+
69
+ // --- status ---
70
+ program
71
+ .command('status')
72
+ .description('Show current status of issues and tasks')
73
+ .argument('[work_dir]', 'Working directory', '.')
74
+ .option('--repo <owner/repo>', 'GitHub repo')
75
+ .action(async (workDir: string, opts) => {
76
+ const resolvedDir = path.resolve(workDir);
77
+ const issues = new GitHubProvider(resolvedDir, opts.repo);
78
+ const taskMgr = new TaskManager(resolvedDir);
79
+
80
+ console.log('=== whitesmith status ===\n');
81
+
82
+ // Show issues by state
83
+ for (const [name, label] of Object.entries(LABELS)) {
84
+ const list = await issues.listIssues({labels: [label]});
85
+ if (list.length > 0) {
86
+ console.log(`${name} (${label}):`);
87
+ for (const issue of list) {
88
+ console.log(` #${issue.number} - ${issue.title}`);
89
+ }
90
+ console.log('');
91
+ }
92
+ }
93
+
94
+ // Show new issues (no whitesmith labels)
95
+ const allLabels = Object.values(LABELS);
96
+ const newIssues = await issues.listIssues({noLabels: allLabels});
97
+ if (newIssues.length > 0) {
98
+ console.log('NEW (no label):');
99
+ for (const issue of newIssues) {
100
+ console.log(` #${issue.number} - ${issue.title}`);
101
+ }
102
+ console.log('');
103
+ }
104
+
105
+ // Show pending tasks
106
+ const allTasks = taskMgr.listAllTasks();
107
+ if (allTasks.length > 0) {
108
+ console.log('PENDING TASKS:');
109
+ for (const task of allTasks) {
110
+ const deps = task.dependsOn.length > 0 ? ` (depends: ${task.dependsOn.join(', ')})` : '';
111
+ console.log(` ${task.id} - ${task.title}${deps}`);
112
+ }
113
+ console.log('');
114
+ }
115
+ });
116
+
117
+ // --- reconcile ---
118
+ program
119
+ .command('reconcile')
120
+ .description('Check for completed issues and close them (no AI needed)')
121
+ .argument('[work_dir]', 'Working directory', '.')
122
+ .option('--repo <owner/repo>', 'GitHub repo')
123
+ .action(async (workDir: string, opts) => {
124
+ const resolvedDir = path.resolve(workDir);
125
+ const issues = new GitHubProvider(resolvedDir, opts.repo);
126
+ const taskMgr = new TaskManager(resolvedDir);
127
+
128
+ console.log('=== whitesmith reconcile ===\n');
129
+
130
+ // Also handle tasks-proposed → tasks-accepted transition
131
+ // When a PR is merged, the tasks land on main, so if we see tasks on disk
132
+ // for an issue labeled tasks-proposed, it means the PR was merged
133
+ const proposedIssues = await issues.listIssues({labels: [LABELS.TASKS_PROPOSED]});
134
+ for (const issue of proposedIssues) {
135
+ if (taskMgr.hasRemainingTasks(issue.number)) {
136
+ // Tasks exist on main = PR was merged
137
+ console.log(`Issue #${issue.number}: tasks PR merged, marking as accepted`);
138
+ await issues.removeLabel(issue.number, LABELS.TASKS_PROPOSED);
139
+ await issues.addLabel(issue.number, LABELS.TASKS_ACCEPTED);
140
+ }
141
+ }
142
+
143
+ // Check accepted issues for completion
144
+ const acceptedIssues = await issues.listIssues({labels: [LABELS.TASKS_ACCEPTED]});
145
+ for (const issue of acceptedIssues) {
146
+ if (!taskMgr.hasRemainingTasks(issue.number)) {
147
+ console.log(`Issue #${issue.number}: all tasks done, closing`);
148
+ await issues.addLabel(issue.number, LABELS.COMPLETED);
149
+ await issues.removeLabel(issue.number, LABELS.TASKS_ACCEPTED);
150
+ await issues.comment(
151
+ issue.number,
152
+ '✅ All tasks for this issue have been implemented and merged. Closing.',
153
+ );
154
+ await issues.closeIssue(issue.number);
155
+ }
156
+ }
157
+
158
+ console.log('Reconcile complete.');
159
+ });
160
+
161
+ return program;
162
+ }
163
+
164
+ export async function main(args: string[] = process.argv): Promise<void> {
165
+ const program = buildCli();
166
+ await program.parseAsync(args);
167
+ }
168
+
169
+ main().catch((err) => {
170
+ console.error(err);
171
+ process.exit(1);
172
+ });