@@ -82,6 +82,14 @@ describe('git + orchestration integration', () => {
8282 expect ( notes ) . not . toContain ( 'bravo' ) ;
8383 } ) ;
8484
85+ it ( 'generateReleaseNotes resolves the range itself when none is given' , async ( ) => {
86+ // No `range`/`from`/`to`: it must auto-resolve (untagged repo → full history)
87+ // rather than requiring an explicit range.
88+ const notes = await generateReleaseNotes ( { ai : false , cwd : untagged } ) ;
89+ expect ( notes ) . toContain ( '## Features' ) ;
90+ expect ( notes ) . toContain ( 'only commit' ) ;
91+ } ) ;
92+
8593 it ( 'generateReleaseNotes reports an empty range cleanly' , async ( ) => {
8694 const notes = await generateReleaseNotes ( { range : 'HEAD..HEAD' , ai : false , cwd : tagged } ) ;
8795 expect ( notes . trim ( ) ) . toBe ( '_No changes in `HEAD..HEAD`._' ) ;
@@ -116,6 +124,10 @@ describe('working-tree changes integration', () => {
116124
117125 // Untracked: a new file never added.
118126 writeFileSync ( join ( repo , 'untracked.txt' ) , 'brand new\n' ) ;
127+
128+ // Untracked but empty: git still reports it as a new file, so the diff must
129+ // surface it even with zero content.
130+ writeFileSync ( join ( repo , 'empty.txt' ) , '' ) ;
119131 } ) ;
120132
121133 afterAll ( ( ) => {
@@ -133,6 +145,7 @@ describe('working-tree changes integration', () => {
133145 expect ( wc . staged ) . toContain ( 'staged.txt' ) ;
134146 expect ( wc . unstaged ) . toContain ( 'tracked.txt' ) ;
135147 expect ( wc . untracked ) . toContain ( 'untracked.txt' ) ;
148+ expect ( wc . untracked ) . toContain ( 'empty.txt' ) ;
136149 expect ( wc . diff ) . toContain ( '### Staged changes' ) ;
137150 expect ( wc . diff ) . toContain ( '### Unstaged changes' ) ;
138151 expect ( wc . diff ) . toContain ( '### New (untracked) files' ) ;
@@ -146,6 +159,31 @@ describe('working-tree changes integration', () => {
146159 expect ( wc . untracked ) . toEqual ( [ ] ) ;
147160 } ) ;
148161
162+ it ( 'readWorkingChanges emits no sections when every requested category is empty' , async ( ) => {
163+ const clean = initRepo ( ) ;
164+ try {
165+ writeFileSync ( join ( clean , 'committed.txt' ) , 'x\n' ) ;
166+ git ( clean , 'add' , 'committed.txt' ) ;
167+ commit ( clean , 'feat: only commit' ) ;
168+ const wc = await readWorkingChanges ( {
169+ cwd : clean ,
170+ staged : true ,
171+ unstaged : true ,
172+ untracked : true ,
173+ } ) ;
174+ expect ( wc . isEmpty ) . toBe ( true ) ;
175+ expect ( wc . diff ) . toBe ( '' ) ;
176+ } finally {
177+ rmSync ( clean , { recursive : true , force : true } ) ;
178+ }
179+ } ) ;
180+
181+ it ( 'readWorkingChanges defaults cwd and returns empty when nothing is requested' , async ( ) => {
182+ // No options: cwd falls back to process.cwd() and no git category runs.
183+ const wc = await readWorkingChanges ( ) ;
184+ expect ( wc ) . toMatchObject ( { staged : [ ] , unstaged : [ ] , untracked : [ ] , isEmpty : true } ) ;
185+ } ) ;
186+
149187 it ( 'generateReleaseNotes (--no-ai) renders an Uncommitted changes section' , async ( ) => {
150188 const notes = await generateReleaseNotes ( {
151189 cwd : repo ,
0 commit comments