@@ -46,6 +46,16 @@ import {
4646 runContextDocGeneration ,
4747} from "./contextDocBuilder" ;
4848
49+ function createDeferred < T > ( ) {
50+ let resolve ! : ( value : T | PromiseLike < T > ) => void ;
51+ let reject ! : ( reason ?: unknown ) => void ;
52+ const promise = new Promise < T > ( ( res , rej ) => {
53+ resolve = res ;
54+ reject = rej ;
55+ } ) ;
56+ return { promise, resolve, reject } ;
57+ }
58+
4959function createLogger ( ) {
5060 return {
5161 debug : ( ) => { } ,
@@ -221,4 +231,99 @@ describe("contextDocService", () => {
221231 expect ( service . getDocPath ( "prd_ade" ) ) . toBe ( path . join ( projectRoot , "prd_ade.md" ) ) ;
222232 expect ( resolveContextDocPath ) . toHaveBeenCalledWith ( projectRoot , "prd_ade" ) ;
223233 } ) ;
234+
235+ it ( "persists active auto-refresh status as pending/running with trigger metadata" , async ( ) => {
236+ const { service } = await createFixture ( ) ;
237+ const deferred = createDeferred < Awaited < ReturnType < typeof runContextDocGeneration > > > ( ) ;
238+ vi . mocked ( runContextDocGeneration ) . mockReturnValueOnce ( deferred . promise as ReturnType < typeof runContextDocGeneration > ) ;
239+
240+ await service . savePrefs ( {
241+ provider : "unified" ,
242+ modelId : "gpt-5" ,
243+ reasoningEffort : "medium" ,
244+ events : { onPrLand : true } ,
245+ } ) ;
246+
247+ const refreshPromise = service . maybeAutoRefreshDocs ( {
248+ event : "pr_land" ,
249+ reason : "prs_land:123" ,
250+ } ) ;
251+
252+ const duringRun = service . getStatus ( ) . generation ;
253+ expect ( [ "pending" , "running" ] ) . toContain ( duringRun . state ) ;
254+ expect ( duringRun . source ) . toBe ( "auto" ) ;
255+ expect ( duringRun . event ) . toBe ( "pr_land" ) ;
256+ expect ( duringRun . reason ) . toBe ( "prs_land:123" ) ;
257+ expect ( duringRun . provider ) . toBe ( "unified" ) ;
258+ expect ( duringRun . modelId ) . toBe ( "gpt-5" ) ;
259+ expect ( duringRun . reasoningEffort ) . toBe ( "medium" ) ;
260+
261+ deferred . resolve ( {
262+ provider : "unified" ,
263+ generatedAt : "2026-03-05T12:01:00.000Z" ,
264+ prdPath : "/tmp/PRD.ade.md" ,
265+ architecturePath : "/tmp/ARCHITECTURE.ade.md" ,
266+ usedFallbackPath : false ,
267+ warnings : [ ] ,
268+ outputPreview : "generated" ,
269+ } ) ;
270+
271+ await expect ( refreshPromise ) . resolves . toMatchObject ( {
272+ provider : "unified" ,
273+ generatedAt : "2026-03-05T12:01:00.000Z" ,
274+ } ) ;
275+
276+ expect ( service . getStatus ( ) . generation ) . toMatchObject ( {
277+ state : "succeeded" ,
278+ source : "auto" ,
279+ event : "pr_land" ,
280+ reason : "prs_land:123" ,
281+ provider : "unified" ,
282+ modelId : "gpt-5" ,
283+ reasoningEffort : "medium" ,
284+ finishedAt : "2026-03-05T12:01:00.000Z" ,
285+ } ) ;
286+ } ) ;
287+
288+ it ( "records manual generation metadata on completion" , async ( ) => {
289+ const { service } = await createFixture ( ) ;
290+
291+ await service . generateDocs ( {
292+ provider : "codex" ,
293+ modelId : "gpt-5-codex" ,
294+ reasoningEffort : "high" ,
295+ events : { onPrCreate : true } ,
296+ } ) ;
297+
298+ expect ( service . getStatus ( ) . generation ) . toMatchObject ( {
299+ state : "succeeded" ,
300+ source : "manual" ,
301+ event : null ,
302+ reason : "manual_generate" ,
303+ provider : "codex" ,
304+ modelId : "gpt-5-codex" ,
305+ reasoningEffort : "high" ,
306+ finishedAt : "2026-03-05T12:00:00.000Z" ,
307+ } ) ;
308+ } ) ;
309+
310+ it ( "maps legacy idle generation records with a finish time to succeeded" , async ( ) => {
311+ const { db, service } = await createFixture ( ) ;
312+
313+ db . setJson ( "context:docs:generationStatus.v1" , {
314+ state : "idle" ,
315+ finishedAt : "2026-03-05T09:30:00.000Z" ,
316+ source : "auto" ,
317+ event : "pr_create" ,
318+ reason : "legacy_run" ,
319+ } ) ;
320+
321+ expect ( service . getStatus ( ) . generation ) . toMatchObject ( {
322+ state : "succeeded" ,
323+ source : "auto" ,
324+ event : "pr_create" ,
325+ reason : "legacy_run" ,
326+ finishedAt : "2026-03-05T09:30:00.000Z" ,
327+ } ) ;
328+ } ) ;
224329} ) ;
0 commit comments