@@ -6,7 +6,7 @@ import path from "node:path";
66import type { OperationDispatcher , OperationRequestContext } from "../operation-dispatcher.js" ;
77import { findPluginScript } from "./plugin-cache.js" ;
88import { DirectoryNotAllowedError , assertPathAllowed } from "../security.js" ;
9- import { assertRepoAllowed , resolveWorktreeDir } from "./symphony-utils.js" ;
9+ import { assertRepoAllowed , findFirstExisting , resolveWorktreeDir } from "./symphony-utils.js" ;
1010
1111type ParsedLearningPattern = {
1212 id : string ;
@@ -74,8 +74,14 @@ export function registerLearningsRoutes(
7474 return ;
7575 }
7676
77- const claudeWorkDir = path . join ( worktreeDir , ".claude" , "work" ) ;
78- const chatHistoryPath = path . join ( claudeWorkDir , chatFile ) ;
77+ const newLearningsWorkDir = path . join ( worktreeDir , ".closedloop-ai" , "work" ) ;
78+ const oldLearningsWorkDir = path . join ( worktreeDir , ".claude" , "work" ) ;
79+ // Per-file resolution: find chat history wherever it exists
80+ const chatHistoryPath = findFirstExisting (
81+ path . join ( newLearningsWorkDir , chatFile ) ,
82+ path . join ( oldLearningsWorkDir , chatFile )
83+ ) ?? path . join ( newLearningsWorkDir , chatFile ) ;
84+ const claudeWorkDir = chatHistoryPath . startsWith ( newLearningsWorkDir ) ? newLearningsWorkDir : oldLearningsWorkDir ;
7985
8086 try {
8187 assertPathAllowed ( claudeWorkDir , getAllowedDirectories ( ) ) ;
@@ -147,15 +153,12 @@ export function registerLearningsRoutes(
147153 }
148154
149155 const worktreeDir = resolveWorktreeDir ( expandedRepoPath , ticketId ) ;
150- const statusPath = path . join (
151- worktreeDir ,
152- ".claude" ,
153- "work" ,
154- ".learnings" ,
155- "processing-status.json"
156+ const statusPath = findFirstExisting (
157+ path . join ( worktreeDir , ".closedloop-ai" , "work" , ".learnings" , "processing-status.json" ) ,
158+ path . join ( worktreeDir , ".claude" , "work" , ".learnings" , "processing-status.json" )
156159 ) ;
157160
158- if ( ! existsSync ( statusPath ) ) {
161+ if ( ! statusPath ) {
159162 json ( context , 200 , { status : "none" } ) ;
160163 return ;
161164 }
@@ -201,7 +204,9 @@ export function registerLearningsRoutes(
201204 return ;
202205 }
203206
204- const claudeWorkDir = path . join ( worktreeDir , ".claude" , "work" ) ;
207+ const newProcWorkDir = path . join ( worktreeDir , ".closedloop-ai" , "work" ) ;
208+ // Always write to the new canonical path; reads may fall back to legacy.
209+ const claudeWorkDir = newProcWorkDir ;
205210 const learningsDir = path . join ( claudeWorkDir , ".learnings" ) ;
206211 const pendingDir = path . join ( learningsDir , "pending" ) ;
207212 const processingStatusPath = path . join ( learningsDir , "processing-status.json" ) ;
@@ -228,11 +233,23 @@ export function registerLearningsRoutes(
228233 return ;
229234 }
230235
231- if ( ! existsSync ( pendingDir ) ) {
236+ // Check both new and legacy locations for pending learnings
237+ const legacyPendingDir = path . join ( worktreeDir , ".claude" , "work" , ".learnings" , "pending" ) ;
238+ const effectivePendingDir = findFirstExisting ( pendingDir , legacyPendingDir ) ;
239+ if ( ! effectivePendingDir ) {
232240 json ( context , 200 , { status : "skipped" , reason : "No pending learnings directory" } ) ;
233241 return ;
234242 }
235243
244+ // If pending learnings are at legacy location, copy them to new location
245+ if ( effectivePendingDir === legacyPendingDir && ! existsSync ( pendingDir ) ) {
246+ await fs . mkdir ( pendingDir , { recursive : true } ) ;
247+ const legacyFiles = await fs . readdir ( legacyPendingDir ) . catch ( ( ) => [ ] ) ;
248+ for ( const file of legacyFiles ) {
249+ await fs . copyFile ( path . join ( legacyPendingDir , file ) , path . join ( pendingDir , file ) ) . catch ( ( ) => { } ) ;
250+ }
251+ }
252+
236253 const pendingFiles = await fs
237254 . readdir ( pendingDir )
238255 . then ( ( entries ) => entries . filter ( ( entry ) => entry . endsWith ( ".json" ) ) )
@@ -304,15 +321,12 @@ export function registerLearningsRoutes(
304321 }
305322
306323 const worktreeDir = resolveWorktreeDir ( expandedRepoPath , ticketId ) ;
307- const statusPath = path . join (
308- worktreeDir ,
309- ".claude" ,
310- "work" ,
311- ".learnings" ,
312- "chat-extraction-status.json"
324+ const statusPath = findFirstExisting (
325+ path . join ( worktreeDir , ".closedloop-ai" , "work" , ".learnings" , "chat-extraction-status.json" ) ,
326+ path . join ( worktreeDir , ".claude" , "work" , ".learnings" , "chat-extraction-status.json" )
313327 ) ;
314328
315- if ( ! existsSync ( statusPath ) ) {
329+ if ( ! statusPath ) {
316330 json ( context , 200 , { status : "none" , count : 0 } ) ;
317331 return ;
318332 }
@@ -372,7 +386,9 @@ export function registerLearningsRoutes(
372386 return ;
373387 }
374388
375- const claudeWorkDir = path . join ( worktreeDir , ".claude" , "work" ) ;
389+ const newRecordWorkDir = path . join ( worktreeDir , ".closedloop-ai" , "work" ) ;
390+ // Always write to the new canonical path; reads may fall back to legacy.
391+ const claudeWorkDir = newRecordWorkDir ;
376392 const learningsDir = path . join ( claudeWorkDir , ".learnings" ) ;
377393
378394 try {
0 commit comments