@orderful/droid 0.24.0 → 0.25.1
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/.eslintrc.json +6 -4
- package/AGENTS.md +58 -0
- package/CHANGELOG.md +35 -0
- package/README.md +11 -6
- package/dist/bin/droid.js +384 -170
- package/dist/commands/config.d.ts +15 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/exec.d.ts +10 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/index.js +171 -33
- package/dist/lib/migrations.d.ts.map +1 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/tools/codex/TOOL.yaml +1 -1
- package/dist/tools/codex/skills/droid-codex/SKILL.md +92 -72
- package/dist/tools/codex/skills/droid-codex/references/creating.md +13 -51
- package/dist/tools/codex/skills/droid-codex/references/decisions.md +15 -19
- package/dist/tools/codex/skills/droid-codex/references/topics.md +14 -12
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts +31 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts +20 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts +23 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
- package/package.json +1 -1
- package/src/bin/droid.ts +9 -0
- package/src/commands/config.ts +38 -4
- package/src/commands/exec.ts +96 -0
- package/src/commands/install.ts +1 -1
- package/src/commands/setup.ts +6 -6
- package/src/commands/tui.tsx +254 -175
- package/src/lib/migrations.ts +103 -10
- package/src/lib/quotes.ts +6 -6
- package/src/lib/skills.ts +168 -45
- package/src/tools/codex/TOOL.yaml +1 -1
- package/src/tools/codex/skills/droid-codex/SKILL.md +92 -72
- package/src/tools/codex/skills/droid-codex/references/creating.md +13 -51
- package/src/tools/codex/skills/droid-codex/references/decisions.md +15 -19
- package/src/tools/codex/skills/droid-codex/references/topics.md +14 -12
- package/src/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* codex git-finish-write
|
|
4
|
+
*
|
|
5
|
+
* Completes a write operation by committing, pushing, creating a PR,
|
|
6
|
+
* and returning to main.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* droid config codex | droid exec droid-codex git-finish-write --config - \
|
|
10
|
+
* --message "feat: add new topic" \
|
|
11
|
+
* --pr-title "New topic: caching" \
|
|
12
|
+
* --pr-body "Added exploration of caching patterns"
|
|
13
|
+
*
|
|
14
|
+
* Options:
|
|
15
|
+
* --config <json> Config with codex_repo path (required)
|
|
16
|
+
* --message <text> Commit message (required)
|
|
17
|
+
* --pr-title <text> PR title (required)
|
|
18
|
+
* --pr-body <text> PR body (optional, defaults to commit message)
|
|
19
|
+
*
|
|
20
|
+
* What it does:
|
|
21
|
+
* 1. Stage all changes
|
|
22
|
+
* 2. Commit with message
|
|
23
|
+
* 3. Push branch to origin
|
|
24
|
+
* 4. Create PR via gh CLI
|
|
25
|
+
* 5. Return to main branch
|
|
26
|
+
*
|
|
27
|
+
* Output (JSON):
|
|
28
|
+
* { "success": true, "pr_url": "https://github.com/...", "branch": "codex/topic-caching" }
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { execSync } from 'child_process';
|
|
32
|
+
import { readFileSync, existsSync } from 'fs';
|
|
33
|
+
|
|
34
|
+
interface Config {
|
|
35
|
+
codex_repo: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Result {
|
|
39
|
+
success: boolean;
|
|
40
|
+
pr_url?: string;
|
|
41
|
+
branch?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseArgs(args: string[]): {
|
|
46
|
+
config: Config | null;
|
|
47
|
+
message: string | null;
|
|
48
|
+
prTitle: string | null;
|
|
49
|
+
prBody: string | null;
|
|
50
|
+
} {
|
|
51
|
+
let config: Config | null = null;
|
|
52
|
+
let message: string | null = null;
|
|
53
|
+
let prTitle: string | null = null;
|
|
54
|
+
let prBody: string | null = null;
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
const arg = args[i];
|
|
58
|
+
if (arg === '--config' && args[i + 1]) {
|
|
59
|
+
const configArg = args[++i];
|
|
60
|
+
if (configArg === '-') {
|
|
61
|
+
const stdin = readFileSync(0, 'utf-8').trim();
|
|
62
|
+
config = JSON.parse(stdin) as Config;
|
|
63
|
+
} else {
|
|
64
|
+
config = JSON.parse(configArg) as Config;
|
|
65
|
+
}
|
|
66
|
+
} else if (arg === '--message' && args[i + 1]) {
|
|
67
|
+
message = args[++i];
|
|
68
|
+
} else if (arg === '--pr-title' && args[i + 1]) {
|
|
69
|
+
prTitle = args[++i];
|
|
70
|
+
} else if (arg === '--pr-body' && args[i + 1]) {
|
|
71
|
+
prBody = args[++i];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { config, message, prTitle, prBody };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function expandPath(p: string): string {
|
|
79
|
+
if (p.startsWith('~/')) {
|
|
80
|
+
return p.replace('~', process.env.HOME || '');
|
|
81
|
+
}
|
|
82
|
+
return p;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function run(cmd: string, cwd: string): { ok: boolean; output: string } {
|
|
86
|
+
try {
|
|
87
|
+
const output = execSync(cmd, {
|
|
88
|
+
cwd,
|
|
89
|
+
encoding: 'utf-8',
|
|
90
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
91
|
+
});
|
|
92
|
+
return { ok: true, output: output.trim() };
|
|
93
|
+
} catch (err: unknown) {
|
|
94
|
+
const error = err as { stderr?: string; stdout?: string; message?: string };
|
|
95
|
+
return { ok: false, output: error.stderr || error.stdout || error.message || 'Unknown error' };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function gitFinishWrite(
|
|
100
|
+
repoPath: string,
|
|
101
|
+
commitMessage: string,
|
|
102
|
+
prTitle: string,
|
|
103
|
+
prBody: string
|
|
104
|
+
): Result {
|
|
105
|
+
const cwd = expandPath(repoPath);
|
|
106
|
+
|
|
107
|
+
// Verify repo exists
|
|
108
|
+
if (!existsSync(cwd)) {
|
|
109
|
+
return { success: false, error: `Codex repo not found at ${cwd}` };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get current branch
|
|
113
|
+
const branchResult = run('git branch --show-current', cwd);
|
|
114
|
+
if (!branchResult.ok) {
|
|
115
|
+
return { success: false, error: `Failed to get current branch: ${branchResult.output}` };
|
|
116
|
+
}
|
|
117
|
+
const branch = branchResult.output;
|
|
118
|
+
|
|
119
|
+
// Don't allow commits directly to main
|
|
120
|
+
if (branch === 'main') {
|
|
121
|
+
return { success: false, error: 'Cannot commit directly to main. Use git-start-write first to create a branch.' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 1. Stage all changes
|
|
125
|
+
const add = run('git add -A', cwd);
|
|
126
|
+
if (!add.ok) {
|
|
127
|
+
return { success: false, error: `Failed to stage changes: ${add.output}` };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check if there are changes to commit
|
|
131
|
+
const status = run('git status --porcelain', cwd);
|
|
132
|
+
if (status.ok && status.output.length === 0) {
|
|
133
|
+
return { success: false, error: 'No changes to commit' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 2. Commit (use heredoc-style to avoid injection)
|
|
137
|
+
// Write commit message to a temp approach using -F -
|
|
138
|
+
const commit = run(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, cwd);
|
|
139
|
+
if (!commit.ok) {
|
|
140
|
+
return { success: false, error: `Failed to commit: ${commit.output}` };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 3. Push to origin
|
|
144
|
+
const push = run(`git push -u origin ${branch}`, cwd);
|
|
145
|
+
if (!push.ok) {
|
|
146
|
+
// Try force push if branch exists with different history
|
|
147
|
+
const forcePush = run(`git push -u origin ${branch} --force-with-lease`, cwd);
|
|
148
|
+
if (!forcePush.ok) {
|
|
149
|
+
return { success: false, error: `Failed to push: ${forcePush.output}` };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 4. Create PR via gh CLI
|
|
154
|
+
const escapedTitle = prTitle.replace(/"/g, '\\"');
|
|
155
|
+
const escapedBody = prBody.replace(/"/g, '\\"');
|
|
156
|
+
const prCreate = run(
|
|
157
|
+
`gh pr create --title "${escapedTitle}" --body "${escapedBody}" 2>&1 || gh pr view --json url -q .url`,
|
|
158
|
+
cwd
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
let prUrl = '';
|
|
162
|
+
if (prCreate.ok) {
|
|
163
|
+
// Output might be the PR URL directly or JSON
|
|
164
|
+
const output = prCreate.output;
|
|
165
|
+
if (output.startsWith('https://')) {
|
|
166
|
+
prUrl = output.split('\n')[0]; // First line is the URL
|
|
167
|
+
} else {
|
|
168
|
+
// Try to extract URL from output
|
|
169
|
+
const urlMatch = output.match(/https:\/\/github\.com\/[^\s]+/);
|
|
170
|
+
if (urlMatch) {
|
|
171
|
+
prUrl = urlMatch[0];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 5. Return to main
|
|
177
|
+
const checkoutMain = run('git checkout main', cwd);
|
|
178
|
+
if (!checkoutMain.ok) {
|
|
179
|
+
// Non-fatal - PR was created, just warn
|
|
180
|
+
console.error(`Warning: Failed to return to main: ${checkoutMain.output}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
success: true,
|
|
185
|
+
pr_url: prUrl || 'PR created (check GitHub)',
|
|
186
|
+
branch,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Main
|
|
191
|
+
const args = process.argv.slice(2);
|
|
192
|
+
const { config, message, prTitle, prBody } = parseArgs(args);
|
|
193
|
+
|
|
194
|
+
if (!config) {
|
|
195
|
+
console.log(JSON.stringify({
|
|
196
|
+
success: false,
|
|
197
|
+
error: 'Missing --config. Usage: droid config codex | droid exec droid-codex git-finish-write --config - --message "..." --pr-title "..."',
|
|
198
|
+
}));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!config.codex_repo) {
|
|
203
|
+
console.log(JSON.stringify({
|
|
204
|
+
success: false,
|
|
205
|
+
error: 'Missing codex_repo in config',
|
|
206
|
+
}));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!message) {
|
|
211
|
+
console.log(JSON.stringify({
|
|
212
|
+
success: false,
|
|
213
|
+
error: 'Missing --message argument',
|
|
214
|
+
}));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!prTitle) {
|
|
219
|
+
console.log(JSON.stringify({
|
|
220
|
+
success: false,
|
|
221
|
+
error: 'Missing --pr-title argument',
|
|
222
|
+
}));
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const result = gitFinishWrite(
|
|
227
|
+
config.codex_repo,
|
|
228
|
+
message,
|
|
229
|
+
prTitle,
|
|
230
|
+
prBody || message
|
|
231
|
+
);
|
|
232
|
+
console.log(JSON.stringify(result, null, 2));
|
|
233
|
+
|
|
234
|
+
if (!result.success) {
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* codex git-preamble
|
|
4
|
+
*
|
|
5
|
+
* Ensures the codex repo is on a clean main branch with latest changes.
|
|
6
|
+
* Run this before ANY codex operation (read or write).
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* droid config codex | droid exec droid-codex git-preamble --config -
|
|
10
|
+
*
|
|
11
|
+
* What it does:
|
|
12
|
+
* 1. Checkout main (abort any stuck merge/rebase)
|
|
13
|
+
* 2. Stash any uncommitted changes
|
|
14
|
+
* 3. Pull latest with rebase
|
|
15
|
+
*
|
|
16
|
+
* Output (JSON):
|
|
17
|
+
* { "success": true, "branch": "main", "stashed": false }
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=git-preamble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-preamble.d.ts","sourceRoot":"","sources":["../../../../../../src/tools/codex/skills/droid-codex/scripts/git-preamble.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* codex git-preamble
|
|
4
|
+
*
|
|
5
|
+
* Ensures the codex repo is on a clean main branch with latest changes.
|
|
6
|
+
* Run this before ANY codex operation (read or write).
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* droid config codex | droid exec droid-codex git-preamble --config -
|
|
10
|
+
*
|
|
11
|
+
* What it does:
|
|
12
|
+
* 1. Checkout main (abort any stuck merge/rebase)
|
|
13
|
+
* 2. Stash any uncommitted changes
|
|
14
|
+
* 3. Pull latest with rebase
|
|
15
|
+
*
|
|
16
|
+
* Output (JSON):
|
|
17
|
+
* { "success": true, "branch": "main", "stashed": false }
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execSync } from 'child_process';
|
|
21
|
+
import { readFileSync, existsSync } from 'fs';
|
|
22
|
+
|
|
23
|
+
interface Config {
|
|
24
|
+
codex_repo: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Result {
|
|
28
|
+
success: boolean;
|
|
29
|
+
branch?: string;
|
|
30
|
+
stashed?: boolean;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseArgs(args: string[]): { config: Config | null } {
|
|
35
|
+
let config: Config | null = null;
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < args.length; i++) {
|
|
38
|
+
const arg = args[i];
|
|
39
|
+
if (arg === '--config' && args[i + 1]) {
|
|
40
|
+
const configArg = args[++i];
|
|
41
|
+
if (configArg === '-') {
|
|
42
|
+
const stdin = readFileSync(0, 'utf-8').trim();
|
|
43
|
+
config = JSON.parse(stdin) as Config;
|
|
44
|
+
} else {
|
|
45
|
+
config = JSON.parse(configArg) as Config;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { config };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function expandPath(p: string): string {
|
|
54
|
+
if (p.startsWith('~/')) {
|
|
55
|
+
return p.replace('~', process.env.HOME || '');
|
|
56
|
+
}
|
|
57
|
+
return p;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function run(cmd: string, cwd: string): { ok: boolean; output: string } {
|
|
61
|
+
try {
|
|
62
|
+
const output = execSync(cmd, {
|
|
63
|
+
cwd,
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
});
|
|
67
|
+
return { ok: true, output: output.trim() };
|
|
68
|
+
} catch (err: unknown) {
|
|
69
|
+
const error = err as { stderr?: string; message?: string };
|
|
70
|
+
return { ok: false, output: error.stderr || error.message || 'Unknown error' };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function gitPreamble(repoPath: string): Result {
|
|
75
|
+
const cwd = expandPath(repoPath);
|
|
76
|
+
|
|
77
|
+
// Verify repo exists
|
|
78
|
+
if (!existsSync(cwd)) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
error: `Codex repo not found at ${cwd}. Run: git clone git@github.com:orderful/orderful-codex.git ${cwd}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 1. Abort any stuck operations (silent failures are fine)
|
|
86
|
+
run('git merge --abort', cwd);
|
|
87
|
+
run('git rebase --abort', cwd);
|
|
88
|
+
run('git cherry-pick --abort', cwd);
|
|
89
|
+
|
|
90
|
+
// 2. Checkout main
|
|
91
|
+
const checkout = run('git checkout main', cwd);
|
|
92
|
+
if (!checkout.ok && !checkout.output.includes('Already on')) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: `Failed to checkout main: ${checkout.output}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 3. Stash any uncommitted changes
|
|
100
|
+
const statusCheck = run('git status --porcelain', cwd);
|
|
101
|
+
let stashed = false;
|
|
102
|
+
if (statusCheck.ok && statusCheck.output.length > 0) {
|
|
103
|
+
const stash = run('git stash push -m "codex-auto-stash"', cwd);
|
|
104
|
+
stashed = stash.ok && !stash.output.includes('No local changes');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 4. Pull latest
|
|
108
|
+
const pull = run('git pull --rebase', cwd);
|
|
109
|
+
if (!pull.ok) {
|
|
110
|
+
// Try to recover - maybe there's a conflict
|
|
111
|
+
run('git rebase --abort', cwd);
|
|
112
|
+
const retryPull = run('git pull --rebase', cwd);
|
|
113
|
+
if (!retryPull.ok) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: `Failed to pull latest: ${retryPull.output}. The codex repo may have conflicts that need manual resolution.`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get current branch to confirm
|
|
122
|
+
const branch = run('git branch --show-current', cwd);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
branch: branch.ok ? branch.output : 'main',
|
|
127
|
+
stashed,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Main
|
|
132
|
+
const args = process.argv.slice(2);
|
|
133
|
+
const { config } = parseArgs(args);
|
|
134
|
+
|
|
135
|
+
if (!config) {
|
|
136
|
+
console.log(JSON.stringify({
|
|
137
|
+
success: false,
|
|
138
|
+
error: 'Missing --config. Usage: droid config codex | droid exec droid-codex git-preamble --config -',
|
|
139
|
+
}));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!config.codex_repo) {
|
|
144
|
+
console.log(JSON.stringify({
|
|
145
|
+
success: false,
|
|
146
|
+
error: 'Missing codex_repo in config. Run: droid config codex --set codex_repo=~/path/to/codex',
|
|
147
|
+
}));
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const result = gitPreamble(config.codex_repo);
|
|
152
|
+
console.log(JSON.stringify(result, null, 2));
|
|
153
|
+
|
|
154
|
+
if (!result.success) {
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|