@orderful/droid 0.23.0 → 0.25.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 (53) hide show
  1. package/.eslintrc.json +6 -4
  2. package/AGENTS.md +58 -0
  3. package/CHANGELOG.md +44 -0
  4. package/README.md +11 -6
  5. package/dist/bin/droid.js +384 -170
  6. package/dist/commands/config.d.ts +15 -1
  7. package/dist/commands/config.d.ts.map +1 -1
  8. package/dist/commands/exec.d.ts +10 -0
  9. package/dist/commands/exec.d.ts.map +1 -0
  10. package/dist/commands/tui.d.ts.map +1 -1
  11. package/dist/index.js +171 -33
  12. package/dist/lib/migrations.d.ts.map +1 -1
  13. package/dist/lib/skills.d.ts.map +1 -1
  14. package/dist/tools/codex/TOOL.yaml +2 -2
  15. package/dist/tools/codex/agents/codex-document-processor.md +8 -4
  16. package/dist/tools/codex/commands/codex.md +18 -10
  17. package/dist/tools/codex/skills/droid-codex/SKILL.md +140 -67
  18. package/dist/tools/codex/skills/droid-codex/references/creating.md +13 -51
  19. package/dist/tools/codex/skills/droid-codex/references/decisions.md +15 -19
  20. package/dist/tools/codex/skills/droid-codex/references/loading.md +49 -13
  21. package/dist/tools/codex/skills/droid-codex/references/topics.md +14 -12
  22. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts +31 -0
  23. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +1 -0
  24. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
  25. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts +20 -0
  26. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +1 -0
  27. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
  28. package/dist/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
  29. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts +23 -0
  30. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +1 -0
  31. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
  32. package/package.json +1 -1
  33. package/src/bin/droid.ts +9 -0
  34. package/src/commands/config.ts +38 -4
  35. package/src/commands/exec.ts +96 -0
  36. package/src/commands/install.ts +1 -1
  37. package/src/commands/setup.ts +6 -6
  38. package/src/commands/tui.tsx +254 -175
  39. package/src/lib/migrations.ts +103 -10
  40. package/src/lib/quotes.ts +6 -6
  41. package/src/lib/skills.ts +168 -45
  42. package/src/tools/codex/TOOL.yaml +2 -2
  43. package/src/tools/codex/agents/codex-document-processor.md +8 -4
  44. package/src/tools/codex/commands/codex.md +18 -10
  45. package/src/tools/codex/skills/droid-codex/SKILL.md +140 -67
  46. package/src/tools/codex/skills/droid-codex/references/creating.md +13 -51
  47. package/src/tools/codex/skills/droid-codex/references/decisions.md +15 -19
  48. package/src/tools/codex/skills/droid-codex/references/loading.md +49 -13
  49. package/src/tools/codex/skills/droid-codex/references/topics.md +14 -12
  50. package/src/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
  51. package/src/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
  52. package/src/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
  53. 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
+ }