@orderful/droid 0.48.0 → 0.51.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 (100) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/CHANGELOG.md +34 -0
  4. package/bun.lock +137 -3
  5. package/dist/bin/droid.js +355 -90
  6. package/dist/commands/pack.d.ts +5 -0
  7. package/dist/commands/pack.d.ts.map +1 -0
  8. package/dist/index.js +11 -0
  9. package/dist/lib/pack.d.ts +31 -0
  10. package/dist/lib/pack.d.ts.map +1 -0
  11. package/dist/lib/types.d.ts +17 -0
  12. package/dist/lib/types.d.ts.map +1 -1
  13. package/dist/tools/brain/.claude-plugin/plugin.json +1 -1
  14. package/dist/tools/brain/TOOL.yaml +3 -1
  15. package/dist/tools/brain/skills/brain/SKILL.md +4 -0
  16. package/dist/tools/brain/skills/brain/references/workflows.md +21 -7
  17. package/dist/tools/coach/TOOL.yaml +4 -0
  18. package/dist/tools/code-review/.claude-plugin/plugin.json +3 -2
  19. package/dist/tools/code-review/TOOL.yaml +4 -1
  20. package/dist/tools/code-review/agents/codex-context-researcher.md +99 -0
  21. package/dist/tools/code-review/skills/code-review/SKILL.md +20 -1
  22. package/dist/tools/codex/.claude-plugin/plugin.json +1 -1
  23. package/dist/tools/codex/TOOL.yaml +3 -1
  24. package/dist/tools/codex/skills/codex/SKILL.md +5 -1
  25. package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.d.ts +61 -0
  26. package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.d.ts.map +1 -0
  27. package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.ts +402 -0
  28. package/dist/tools/comments/TOOL.yaml +2 -0
  29. package/dist/tools/droid/.claude-plugin/plugin.json +1 -1
  30. package/dist/tools/droid/TOOL.yaml +3 -1
  31. package/dist/tools/droid/skills/droid/SKILL.md +48 -2
  32. package/dist/tools/droid/skills/droid/references/new-tool-workflow.md +234 -0
  33. package/dist/tools/edi-schema/TOOL.yaml +2 -0
  34. package/dist/tools/excalidraw/TOOL.yaml +2 -0
  35. package/dist/tools/meeting/TOOL.yaml +2 -0
  36. package/dist/tools/pii/TOOL.yaml +2 -0
  37. package/dist/tools/plan/.claude-plugin/plugin.json +1 -1
  38. package/dist/tools/plan/TOOL.yaml +5 -1
  39. package/dist/tools/plan/commands/plan.md +3 -2
  40. package/dist/tools/plan/skills/plan/SKILL.md +31 -10
  41. package/dist/tools/plan/skills/plan/references/workflows.md +44 -14
  42. package/dist/tools/project/.claude-plugin/plugin.json +1 -1
  43. package/dist/tools/project/TOOL.yaml +7 -1
  44. package/dist/tools/project/skills/project/SKILL.md +32 -1
  45. package/dist/tools/project/skills/project/references/loading.md +1 -0
  46. package/dist/tools/project/skills/project/references/pulling.md +57 -0
  47. package/dist/tools/project/skills/project/references/pushing.md +79 -0
  48. package/dist/tools/release/TOOL.yaml +2 -0
  49. package/dist/tools/share/TOOL.yaml +2 -0
  50. package/dist/tools/status-update/TOOL.yaml +4 -0
  51. package/dist/tools/tech-design/TOOL.yaml +2 -0
  52. package/dist/tools/wrapup/TOOL.yaml +2 -0
  53. package/package.json +3 -1
  54. package/scripts/build.ts +3 -2
  55. package/src/bin/droid.ts +9 -0
  56. package/src/commands/pack.ts +77 -0
  57. package/src/lib/pack.test.ts +85 -0
  58. package/src/lib/pack.ts +293 -0
  59. package/src/lib/types.ts +19 -0
  60. package/src/tools/brain/.claude-plugin/plugin.json +1 -1
  61. package/src/tools/brain/TOOL.yaml +3 -1
  62. package/src/tools/brain/skills/brain/SKILL.md +4 -0
  63. package/src/tools/brain/skills/brain/references/workflows.md +21 -7
  64. package/src/tools/coach/TOOL.yaml +4 -0
  65. package/src/tools/code-review/.claude-plugin/plugin.json +3 -2
  66. package/src/tools/code-review/TOOL.yaml +4 -1
  67. package/src/tools/code-review/agents/codex-context-researcher.md +99 -0
  68. package/src/tools/code-review/skills/code-review/SKILL.md +20 -1
  69. package/src/tools/codex/.claude-plugin/plugin.json +1 -1
  70. package/src/tools/codex/TOOL.yaml +3 -1
  71. package/src/tools/codex/skills/codex/SKILL.md +5 -1
  72. package/src/tools/codex/skills/codex/scripts/normalize-frontmatter.test.ts +331 -0
  73. package/src/tools/codex/skills/codex/scripts/normalize-frontmatter.ts +402 -0
  74. package/src/tools/comments/TOOL.yaml +2 -0
  75. package/src/tools/droid/.claude-plugin/plugin.json +1 -1
  76. package/src/tools/droid/TOOL.yaml +3 -1
  77. package/src/tools/droid/skills/droid/SKILL.md +48 -2
  78. package/src/tools/droid/skills/droid/references/new-tool-workflow.md +234 -0
  79. package/src/tools/edi-schema/TOOL.yaml +2 -0
  80. package/src/tools/excalidraw/TOOL.yaml +2 -0
  81. package/src/tools/meeting/TOOL.yaml +2 -0
  82. package/src/tools/pii/TOOL.yaml +2 -0
  83. package/src/tools/plan/.claude-plugin/plugin.json +1 -1
  84. package/src/tools/plan/TOOL.yaml +5 -1
  85. package/src/tools/plan/commands/plan.md +3 -2
  86. package/src/tools/plan/skills/plan/SKILL.md +31 -10
  87. package/src/tools/plan/skills/plan/references/workflows.md +44 -14
  88. package/src/tools/project/.claude-plugin/plugin.json +1 -1
  89. package/src/tools/project/TOOL.yaml +7 -1
  90. package/src/tools/project/skills/project/SKILL.md +32 -1
  91. package/src/tools/project/skills/project/references/loading.md +1 -0
  92. package/src/tools/project/skills/project/references/pulling.md +57 -0
  93. package/src/tools/project/skills/project/references/pushing.md +79 -0
  94. package/src/tools/release/TOOL.yaml +2 -0
  95. package/src/tools/share/TOOL.yaml +2 -0
  96. package/src/tools/status-update/TOOL.yaml +4 -0
  97. package/src/tools/tech-design/TOOL.yaml +2 -0
  98. package/src/tools/wrapup/TOOL.yaml +2 -0
  99. package/dist/tools/codex/skills/codex/scripts/git-scripts.test.ts +0 -364
  100. package/dist/tools/pii/skills/pii/scripts/presidio.test.ts +0 -444
@@ -0,0 +1,79 @@
1
+ # Pushing to Codex
2
+
3
+ **Trigger:** `/project push [codex:{name}]`
4
+
5
+ Push team-relevant content from a local project back to the shared codex.
6
+
7
+ ## What Gets Shared
8
+
9
+ Not everything in a project is codex-worthy. Extract only team-relevant content:
10
+
11
+ | Share | Don't Share |
12
+ |-------|-------------|
13
+ | Architecture decisions with rationale | Personal work notes |
14
+ | New patterns discovered during implementation | In-progress tasks |
15
+ | Updated technical constraints | Session logs or worklogs |
16
+ | Status changes (shipped, blocked, pivoted) | Brain docs or research drafts |
17
+ | Key file paths or API changes | Local configuration |
18
+
19
+ ## Procedure
20
+
21
+ 1. **Resolve codex project name:**
22
+ - If `codex:{name}` provided → use it, save to PROJECT.md frontmatter as `codex_project: {name}`
23
+ - If not provided → read `codex_project` from PROJECT.md frontmatter
24
+ - If neither → ask user which codex project to update, save to frontmatter
25
+ - Codex project must already exist. If it doesn't, suggest `/codex new project {name}` first.
26
+
27
+ 2. **Identify the local project:**
28
+ - If in a loaded project session → use that project
29
+ - If no project loaded → search and load first
30
+
31
+ 3. **Read PROJECT.md** and companion docs (DECISIONS.md, WORKLOG.md if they exist)
32
+
33
+ 4. **Load codex entry:**
34
+ - Run `droid config --get tools.codex` and get `codex_repo` from the JSON output
35
+ - Sync: `git -C {codex_repo} pull --rebase --quiet`
36
+ - Read existing codex project docs
37
+
38
+ 5. **Extract and categorise shareable content:**
39
+ - **Decisions:** New entries from DECISIONS.md or inline decisions in PROJECT.md
40
+ - **Architecture:** Changes to technical approach, new patterns, updated diagrams
41
+ - **Status:** Phase changes, milestones hit, scope changes
42
+ - **Constraints:** Newly discovered technical constraints or gotchas
43
+
44
+ 6. **Propose codex updates** (never auto-apply):
45
+ - For each item, show:
46
+ - What will be added/changed in codex
47
+ - Which codex file it targets (DECISIONS.md, TECH-DESIGN.md, PRD.md)
48
+ - A diff preview
49
+ - Let user accept, reject, or edit each item
50
+
51
+ 7. **After approval:**
52
+ - Use codex git scripts for the write operation:
53
+ ```bash
54
+ # Start write
55
+ droid config --get tools.codex | droid exec codex git-start-write --config - \
56
+ --branch codex/sync-{project-name}
57
+
58
+ # Make changes to codex files
59
+
60
+ # Normalize frontmatter
61
+ droid config --get tools.codex | droid exec codex normalize-frontmatter --config -
62
+
63
+ # Finish write (commit + PR)
64
+ droid config --get tools.codex | droid exec codex git-finish-write --config - \
65
+ --message "sync({project}): {summary}" \
66
+ --pr-title "sync({project}): Update from local project" \
67
+ --pr-body "{description of changes}"
68
+ ```
69
+ - Update local PROJECT.md sync marker:
70
+ ```
71
+ **Last pushed:** {date} to codex:{name}
72
+ ```
73
+
74
+ ## Key Principles
75
+
76
+ - **Curated, not automated** — user explicitly reviews what gets shared
77
+ - **PR-based** — codex changes go through PR review, not direct commits
78
+ - **Additive** — adds to codex content, doesn't overwrite others' contributions
79
+ - **Idempotent** — running `/project push` twice proposes only new/changed content
@@ -2,6 +2,8 @@ name: release
2
2
  description: "Release ceremony automation — create release PRs, check status, notify Slack."
3
3
  version: 0.2.2
4
4
  status: alpha
5
+ audience:
6
+ - engineering
5
7
 
6
8
  includes:
7
9
  skills:
@@ -2,6 +2,8 @@ name: share
2
2
  description: "Share content to external platforms. Publish docs to Confluence or post summaries to Slack."
3
3
  version: 0.1.1
4
4
  status: beta
5
+ audience:
6
+ - all
5
7
 
6
8
  includes:
7
9
  skills:
@@ -2,6 +2,10 @@ name: status-update
2
2
  description: "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal."
3
3
  version: 0.2.2
4
4
  status: beta
5
+ audience:
6
+ - engineering
7
+ - product
8
+ - design
5
9
 
6
10
  includes:
7
11
  skills:
@@ -2,6 +2,8 @@ name: tech-design
2
2
  description: "Technical design authoring tool for engineers. Create structured tech design docs with /tech-design start, iterate in brain, publish to codex. Three-document approach: research doc (codebase discoveries) + thought doc (design workspace) + roll-up (clean summary for review)."
3
3
  version: 0.3.1
4
4
  status: beta
5
+ audience:
6
+ - engineering
5
7
 
6
8
  includes:
7
9
  skills:
@@ -2,6 +2,8 @@ name: wrapup
2
2
  description: "Session wrap-up that captures decisions, learnings, and open items to persistent docs."
3
3
  version: 0.1.5
4
4
  status: alpha
5
+ audience:
6
+ - all
5
7
 
6
8
  includes:
7
9
  skills:
@@ -1,364 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
2
- import { execSync, spawnSync } from 'child_process';
3
- import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync } from 'fs';
4
- import { join } from 'path';
5
- import { tmpdir } from 'os';
6
-
7
- /**
8
- * Integration tests for codex git scripts.
9
- *
10
- * These tests create real git repos and run the actual scripts,
11
- * validating both the JSON output and the resulting git state.
12
- */
13
-
14
- const SCRIPTS_DIR = __dirname;
15
-
16
- interface ScriptResult {
17
- success: boolean;
18
- branch?: string;
19
- stashed?: boolean;
20
- from?: string;
21
- pr_url?: string;
22
- error?: string;
23
- }
24
-
25
- function runScript(
26
- scriptName: string,
27
- args: string[],
28
- config: Record<string, unknown>
29
- ): ScriptResult {
30
- const scriptPath = join(SCRIPTS_DIR, `${scriptName}.ts`);
31
- const configJson = JSON.stringify(config);
32
-
33
- const result = spawnSync('bun', ['run', scriptPath, '--config', configJson, ...args], {
34
- encoding: 'utf-8',
35
- cwd: process.cwd(),
36
- });
37
-
38
- if (result.error) {
39
- return { success: false, error: result.error.message };
40
- }
41
-
42
- try {
43
- return JSON.parse(result.stdout.trim());
44
- } catch {
45
- return {
46
- success: false,
47
- error: `Failed to parse output: ${result.stdout}\nStderr: ${result.stderr}`,
48
- };
49
- }
50
- }
51
-
52
- function git(cwd: string, ...args: string[]): string {
53
- // Quote arguments that contain spaces
54
- const quotedArgs = args.map(arg => (arg.includes(' ') ? `"${arg}"` : arg));
55
- return execSync(`git ${quotedArgs.join(' ')}`, { cwd, encoding: 'utf-8' }).trim();
56
- }
57
-
58
- function createTestRepo(): { repo: string; remote: string } {
59
- // Create temp directories for repo and "remote"
60
- const baseDir = mkdtempSync(join(tmpdir(), 'codex-test-'));
61
- const remoteDir = join(baseDir, 'remote.git');
62
- const repoDir = join(baseDir, 'repo');
63
-
64
- // Create bare "remote" repo
65
- mkdirSync(remoteDir);
66
- git(remoteDir, 'init', '--bare');
67
-
68
- // Create working repo
69
- mkdirSync(repoDir);
70
- git(repoDir, 'init');
71
- git(repoDir, 'config', 'user.email', 'test@test.com');
72
- git(repoDir, 'config', 'user.name', 'Test User');
73
-
74
- // Create initial commit
75
- writeFileSync(join(repoDir, 'README.md'), '# Test Repo\n');
76
- git(repoDir, 'add', '.');
77
- git(repoDir, 'commit', '-m', 'Initial commit');
78
-
79
- // Rename to main if needed
80
- try {
81
- git(repoDir, 'branch', '-M', 'main');
82
- } catch {
83
- // Already on main
84
- }
85
-
86
- // Add remote and push
87
- git(repoDir, 'remote', 'add', 'origin', remoteDir);
88
- git(repoDir, 'push', '-u', 'origin', 'main');
89
-
90
- return { repo: repoDir, remote: remoteDir };
91
- }
92
-
93
- function createTestRepoSimple(): string {
94
- const { repo } = createTestRepo();
95
- return repo;
96
- }
97
-
98
- function cleanupTestRepo(path: string): void {
99
- try {
100
- rmSync(path, { recursive: true, force: true });
101
- } catch {
102
- // Ignore cleanup errors
103
- }
104
- }
105
-
106
- describe('git-preamble', () => {
107
- let testRepo: string;
108
- let baseDir: string;
109
-
110
- beforeEach(() => {
111
- const { repo } = createTestRepo();
112
- testRepo = repo;
113
- // baseDir is the parent of repo
114
- baseDir = join(testRepo, '..');
115
- });
116
-
117
- afterEach(() => {
118
- cleanupTestRepo(baseDir);
119
- });
120
-
121
- it('succeeds on clean main branch', () => {
122
- const result = runScript('git-preamble', [], { codex_repo: testRepo });
123
-
124
- expect(result.success).toBe(true);
125
- expect(result.branch).toBe('main');
126
- expect(result.stashed).toBe(false);
127
- });
128
-
129
- it('returns to main from feature branch', () => {
130
- // Create and checkout a feature branch
131
- git(testRepo, 'checkout', '-b', 'feature/test');
132
-
133
- const result = runScript('git-preamble', [], { codex_repo: testRepo });
134
-
135
- expect(result.success).toBe(true);
136
- expect(result.branch).toBe('main');
137
-
138
- // Verify we're actually on main
139
- const currentBranch = git(testRepo, 'branch', '--show-current');
140
- expect(currentBranch).toBe('main');
141
- });
142
-
143
- it('stashes uncommitted changes', () => {
144
- // Create uncommitted changes
145
- writeFileSync(join(testRepo, 'dirty.txt'), 'uncommitted');
146
- git(testRepo, 'add', 'dirty.txt');
147
-
148
- const result = runScript('git-preamble', [], { codex_repo: testRepo });
149
-
150
- expect(result.success).toBe(true);
151
- expect(result.stashed).toBe(true);
152
-
153
- // Verify the file is no longer in working directory
154
- const status = git(testRepo, 'status', '--porcelain');
155
- expect(status).toBe('');
156
- });
157
-
158
- it('fails with missing repo', () => {
159
- const result = runScript('git-preamble', [], { codex_repo: '/nonexistent/path' });
160
-
161
- expect(result.success).toBe(false);
162
- expect(result.error).toContain('not found');
163
- });
164
-
165
- it('aborts stuck rebase', () => {
166
- // Create a situation with a stuck rebase (simulate by creating the file)
167
- mkdirSync(join(testRepo, '.git', 'rebase-merge'), { recursive: true });
168
- writeFileSync(join(testRepo, '.git', 'rebase-merge', 'git-rebase-todo'), '');
169
-
170
- const result = runScript('git-preamble', [], { codex_repo: testRepo });
171
-
172
- // Should succeed after aborting the rebase
173
- expect(result.success).toBe(true);
174
- });
175
- });
176
-
177
- describe('git-start-write', () => {
178
- let testRepo: string;
179
- let baseDir: string;
180
-
181
- beforeEach(() => {
182
- const { repo } = createTestRepo();
183
- testRepo = repo;
184
- baseDir = join(testRepo, '..');
185
- });
186
-
187
- afterEach(() => {
188
- cleanupTestRepo(baseDir);
189
- });
190
-
191
- it('creates a new branch from main', () => {
192
- const result = runScript('git-start-write', ['--branch', 'codex/test-branch'], {
193
- codex_repo: testRepo,
194
- });
195
-
196
- expect(result.success).toBe(true);
197
- expect(result.branch).toBe('codex/test-branch');
198
- expect(result.from).toBe('main');
199
-
200
- // Verify we're on the new branch
201
- const currentBranch = git(testRepo, 'branch', '--show-current');
202
- expect(currentBranch).toBe('codex/test-branch');
203
- });
204
-
205
- it('resets existing branch to main', () => {
206
- // Create branch with a commit
207
- git(testRepo, 'checkout', '-b', 'codex/existing');
208
- writeFileSync(join(testRepo, 'old-file.txt'), 'old content');
209
- git(testRepo, 'add', '.');
210
- git(testRepo, 'commit', '-m', 'Old commit');
211
- git(testRepo, 'checkout', 'main');
212
-
213
- // Start write should reset it
214
- const result = runScript('git-start-write', ['--branch', 'codex/existing'], {
215
- codex_repo: testRepo,
216
- });
217
-
218
- expect(result.success).toBe(true);
219
-
220
- // The old file should not exist (branch was reset)
221
- expect(existsSync(join(testRepo, 'old-file.txt'))).toBe(false);
222
- });
223
-
224
- it('fails without branch argument', () => {
225
- const result = runScript('git-start-write', [], { codex_repo: testRepo });
226
-
227
- expect(result.success).toBe(false);
228
- expect(result.error).toContain('--branch');
229
- });
230
-
231
- it('runs preamble first (returns from feature branch)', () => {
232
- // Start on a different branch
233
- git(testRepo, 'checkout', '-b', 'feature/other');
234
-
235
- const result = runScript('git-start-write', ['--branch', 'codex/new'], {
236
- codex_repo: testRepo,
237
- });
238
-
239
- expect(result.success).toBe(true);
240
- expect(result.branch).toBe('codex/new');
241
- });
242
- });
243
-
244
- describe('git-finish-write', () => {
245
- let testRepo: string;
246
- let baseDir: string;
247
-
248
- beforeEach(() => {
249
- const { repo } = createTestRepo();
250
- testRepo = repo;
251
- baseDir = join(testRepo, '..');
252
- // Start on a feature branch (simulating after git-start-write)
253
- git(testRepo, 'checkout', '-b', 'codex/test-feature');
254
- });
255
-
256
- afterEach(() => {
257
- cleanupTestRepo(baseDir);
258
- });
259
-
260
- it('commits changes and returns to main', () => {
261
- // Create a change
262
- writeFileSync(join(testRepo, 'new-file.md'), '# New Content\n');
263
-
264
- const result = runScript(
265
- 'git-finish-write',
266
- ['--message', 'test: add new file', '--pr-title', 'Test PR', '--pr-body', 'Test body'],
267
- { codex_repo: testRepo }
268
- );
269
-
270
- // Note: PR creation will fail (no remote), but commit should succeed
271
- // and we should return to main
272
- const currentBranch = git(testRepo, 'branch', '--show-current');
273
- expect(currentBranch).toBe('main');
274
-
275
- // Verify the commit exists on the feature branch
276
- const log = git(testRepo, 'log', 'codex/test-feature', '--oneline', '-1');
277
- expect(log).toContain('test: add new file');
278
- });
279
-
280
- it('fails when on main branch', () => {
281
- git(testRepo, 'checkout', 'main');
282
-
283
- const result = runScript(
284
- 'git-finish-write',
285
- ['--message', 'bad commit', '--pr-title', 'Bad PR'],
286
- { codex_repo: testRepo }
287
- );
288
-
289
- expect(result.success).toBe(false);
290
- expect(result.error).toContain('Cannot commit directly to main');
291
- });
292
-
293
- it('fails with no changes to commit', () => {
294
- const result = runScript(
295
- 'git-finish-write',
296
- ['--message', 'empty commit', '--pr-title', 'Empty PR'],
297
- { codex_repo: testRepo }
298
- );
299
-
300
- expect(result.success).toBe(false);
301
- expect(result.error).toContain('No changes to commit');
302
- });
303
-
304
- it('fails without required arguments', () => {
305
- const noMessage = runScript('git-finish-write', ['--pr-title', 'Test'], {
306
- codex_repo: testRepo,
307
- });
308
- expect(noMessage.success).toBe(false);
309
- expect(noMessage.error).toContain('--message');
310
-
311
- const noTitle = runScript('git-finish-write', ['--message', 'Test'], {
312
- codex_repo: testRepo,
313
- });
314
- expect(noTitle.success).toBe(false);
315
- expect(noTitle.error).toContain('--pr-title');
316
- });
317
- });
318
-
319
- describe('full workflow', () => {
320
- let testRepo: string;
321
- let baseDir: string;
322
-
323
- beforeEach(() => {
324
- const { repo } = createTestRepo();
325
- testRepo = repo;
326
- baseDir = join(testRepo, '..');
327
- });
328
-
329
- afterEach(() => {
330
- cleanupTestRepo(baseDir);
331
- });
332
-
333
- it('preamble -> start-write -> make changes -> finish-write', () => {
334
- // 1. Preamble
335
- const preamble = runScript('git-preamble', [], { codex_repo: testRepo });
336
- expect(preamble.success).toBe(true);
337
-
338
- // 2. Start write
339
- const start = runScript('git-start-write', ['--branch', 'codex/full-test'], {
340
- codex_repo: testRepo,
341
- });
342
- expect(start.success).toBe(true);
343
- expect(start.branch).toBe('codex/full-test');
344
-
345
- // 3. Make changes (create directory first)
346
- mkdirSync(join(testRepo, 'topics'), { recursive: true });
347
- writeFileSync(join(testRepo, 'topics', 'test-topic.md'), '# Test Topic\n');
348
-
349
- // 4. Finish write (PR will fail but commit should work)
350
- const finish = runScript(
351
- 'git-finish-write',
352
- ['--message', 'topic: add test-topic', '--pr-title', 'Topic: test-topic'],
353
- { codex_repo: testRepo }
354
- );
355
-
356
- // Should return to main regardless of PR status
357
- const currentBranch = git(testRepo, 'branch', '--show-current');
358
- expect(currentBranch).toBe('main');
359
-
360
- // Verify commit exists on feature branch
361
- const commits = git(testRepo, 'log', 'codex/full-test', '--oneline');
362
- expect(commits).toContain('topic: add test-topic');
363
- });
364
- });