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.
- package/dist/__tests__/task-manager.test.d.ts +2 -0
- package/dist/__tests__/task-manager.test.d.ts.map +1 -0
- package/dist/__tests__/task-manager.test.js +95 -0
- package/dist/__tests__/task-manager.test.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +147 -0
- package/dist/cli.js.map +1 -0
- package/dist/git.d.ts +60 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +138 -0
- package/dist/git.js.map +1 -0
- package/dist/harnesses/agent-harness.d.ts +19 -0
- package/dist/harnesses/agent-harness.d.ts.map +1 -0
- package/dist/harnesses/agent-harness.js +2 -0
- package/dist/harnesses/agent-harness.js.map +1 -0
- package/dist/harnesses/index.d.ts +3 -0
- package/dist/harnesses/index.d.ts.map +1 -0
- package/dist/harnesses/index.js +2 -0
- package/dist/harnesses/index.js.map +1 -0
- package/dist/harnesses/pi.d.ts +23 -0
- package/dist/harnesses/pi.d.ts.map +1 -0
- package/dist/harnesses/pi.js +63 -0
- package/dist/harnesses/pi.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +44 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +241 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/prompts.d.ts +12 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +112 -0
- package/dist/prompts.js.map +1 -0
- package/dist/providers/github.d.ts +34 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +135 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/issue-provider.d.ts +59 -0
- package/dist/providers/issue-provider.d.ts.map +1 -0
- package/dist/providers/issue-provider.js +2 -0
- package/dist/providers/issue-provider.js.map +1 -0
- package/dist/task-manager.d.ts +57 -0
- package/dist/task-manager.d.ts.map +1 -0
- package/dist/task-manager.js +158 -0
- package/dist/task-manager.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -0
- package/src/cli.ts +172 -0
- package/src/git.ts +148 -0
- package/src/harnesses/agent-harness.ts +15 -0
- package/src/harnesses/index.ts +2 -0
- package/src/harnesses/pi.ts +82 -0
- package/src/index.ts +13 -0
- package/src/orchestrator.ts +294 -0
- package/src/prompts.ts +114 -0
- package/src/providers/github.ts +180 -0
- package/src/providers/index.ts +2 -0
- package/src/providers/issue-provider.ts +59 -0
- package/src/task-manager.ts +190 -0
- 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 @@
|
|
|
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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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
});
|