@@ -11,6 +11,7 @@ import { FSStorageProvider, stripTeamCapabilitiesBlock, syncTeamCapabilities } f
1111import { clearResolveSquadCache , isRetiredProfileTeamRoot } from '@bradygaster/squad-sdk/resolution' ;
1212import {
1313 backfillCastingRegistryFromTeam ,
14+ detectHistoricalCastingSchema ,
1415 ensureCastingRegistryPair ,
1516 normalizeCastingJsonLineEndings as normalizeCastingJsonFiles ,
1617 preflightCastingRegistryPair ,
@@ -298,6 +299,18 @@ function stripRetiredProfileTeamRoot(squadDir: string, dest: string, dryRun: boo
298299 const teamRoot = record [ 'teamRoot' ] ;
299300 if ( typeof teamRoot !== 'string' || teamRoot . length === 0 || teamRoot === '.' ) return ;
300301 const target = path . resolve ( dest , teamRoot ) ;
302+ const comparable = ( value : string ) => process . platform === 'win32' ? value . toLowerCase ( ) : value ;
303+ if ( comparable ( target ) === comparable ( path . resolve ( dest ) ) ) {
304+ if ( dryRun ) {
305+ info ( 'Would normalize the absolute "teamRoot" setting to "." in .squad/config.json.' ) ;
306+ return ;
307+ }
308+ record [ 'teamRoot' ] = '.' ;
309+ storage . writeSync ( configPath , JSON . stringify ( record , null , 2 ) + '\n' ) ;
310+ clearResolveSquadCache ( ) ;
311+ info ( 'Normalized the absolute "teamRoot" setting to "." in .squad/config.json.' ) ;
312+ return ;
313+ }
301314 if ( ! isRetiredProfileTeamRoot ( target ) ) return ;
302315 if ( dryRun ) {
303316 info ( 'Would remove obsolete "teamRoot" setting from .squad/config.json.' ) ;
@@ -1157,7 +1170,12 @@ function runLegacySkillMigration(dest: string, filesUpdated: string[]): void {
11571170 }
11581171}
11591172
1160- async function runEnsureChecks ( dest : string , templatesDir : string , filesUpdated : string [ ] ) : Promise < void > {
1173+ async function runEnsureChecks (
1174+ dest : string ,
1175+ templatesDir : string ,
1176+ filesUpdated : string [ ] ,
1177+ options : { skipCasting ?: boolean } = { } ,
1178+ ) : Promise < void > {
11611179 const attrAdded = ensureGitattributes ( dest ) ;
11621180 if ( attrAdded . length > 0 ) {
11631181 success ( `ensured .gitattributes (${ attrAdded . length } rules added)` ) ;
@@ -1176,15 +1194,17 @@ async function runEnsureChecks(dest: string, templatesDir: string, filesUpdated:
11761194 filesUpdated . push ( ...dirsCreated ) ;
11771195 }
11781196
1179- const castingFiles = ensureCastingDefaults ( dest , templatesDir ) ;
1180- if ( castingFiles . length > 0 ) {
1181- success ( `scaffolded ${ castingFiles . length } default casting files` ) ;
1182- filesUpdated . push ( ...castingFiles ) ;
1183- }
1184- const normalizedCasting = normalizeCastingJsonLineEndings ( dest ) ;
1185- if ( normalizedCasting . length > 0 ) {
1186- success ( `normalized ${ normalizedCasting . length } casting JSON file(s) to LF` ) ;
1187- filesUpdated . push ( ...normalizedCasting ) ;
1197+ if ( ! options . skipCasting ) {
1198+ const castingFiles = ensureCastingDefaults ( dest , templatesDir ) ;
1199+ if ( castingFiles . length > 0 ) {
1200+ success ( `scaffolded ${ castingFiles . length } default casting files` ) ;
1201+ filesUpdated . push ( ...castingFiles ) ;
1202+ }
1203+ const normalizedCasting = normalizeCastingJsonLineEndings ( dest ) ;
1204+ if ( normalizedCasting . length > 0 ) {
1205+ success ( `normalized ${ normalizedCasting . length } casting JSON file(s) to LF` ) ;
1206+ filesUpdated . push ( ...normalizedCasting ) ;
1207+ }
11881208 }
11891209
11901210 const memoryFiles = ensureMemoryGovernanceUpgradeDefaults ( dest ) ;
@@ -1535,6 +1555,22 @@ function warnCastingRepairSkipped(headline: string, result: RepairResult): void
15351555 }
15361556}
15371557
1558+ function detectLegacyCastingFiles ( castingDir : string ) : ReturnType < typeof detectHistoricalCastingSchema > {
1559+ try {
1560+ const registry = JSON . parse ( storage . readSync ( path . join ( castingDir , 'registry.json' ) ) ?? '{}' ) as unknown ;
1561+ const history = JSON . parse ( storage . readSync ( path . join ( castingDir , 'history.json' ) ) ?? '{}' ) as unknown ;
1562+ return detectHistoricalCastingSchema ( registry , history ) ;
1563+ } catch {
1564+ return undefined ;
1565+ }
1566+ }
1567+
1568+ function reportCastingMigrationFailure ( message : string | undefined ) : void {
1569+ if ( ! message ) return ;
1570+ warn ( `Upgrade completed, but legacy casting still needs repair: ${ message } ` ) ;
1571+ info ( ' The original registry.json and history.json were left unchanged. See "Incomplete legacy provenance" in the upgrading guide, then run `squad upgrade --force` again.' ) ;
1572+ }
1573+
15381574/**
15391575 * Run the upgrade command
15401576 */
@@ -1573,7 +1609,10 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
15731609 // The repair reconciles a single Init Mode history snapshot; registries with real history keep failing closed.
15741610 const castingDir = path . join ( squadDirInfo . path , 'casting' ) ;
15751611 const dryRun = options . dryRun ?? false ;
1576- const repair = repairHandwrittenCastingRegistry ( castingDir , { dryRun } ) ;
1612+ const historicalSchema = detectLegacyCastingFiles ( castingDir ) ;
1613+ const repair : RepairResult = historicalSchema
1614+ ? { repaired : false , files : [ ] }
1615+ : repairHandwrittenCastingRegistry ( castingDir , { dryRun } ) ;
15771616 const handwrittenRepaired = repair . repaired ;
15781617 if ( handwrittenRepaired ) {
15791618 if ( dryRun ) {
@@ -1586,13 +1625,22 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
15861625 }
15871626 // Legacy registries still go through the strict preflight (and its specific errors); only the
15881627 // preflight's generic failure is enriched with the handwritten-repair reason and fix.
1589- const castingMigrated = handwrittenRepaired && dryRun
1590- ? false
1591- : withCastingRepairContext ( repair , ( ) => preflightCastingRegistryPair ( castingDir , dryRun ) ) ;
1628+ let castingMigrationFailure : string | undefined ;
1629+ let castingMigrated = false ;
1630+ try {
1631+ castingMigrated = handwrittenRepaired && dryRun
1632+ ? false
1633+ : withCastingRepairContext ( repair , ( ) => preflightCastingRegistryPair ( castingDir , dryRun ) ) ;
1634+ } catch ( error ) {
1635+ castingMigrationFailure = error instanceof Error ? error . message : String ( error ) ;
1636+ warn ( `Could not migrate legacy casting; continuing with the rest of the upgrade: ${ castingMigrationFailure } ` ) ;
1637+ }
15921638 if ( castingMigrated ) {
1593- info ( dryRun
1594- ? 'Would migrate legacy casting state to a managed registry/history pair.'
1595- : 'Migrated legacy casting state to a managed registry/history pair.' ) ;
1639+ if ( dryRun ) {
1640+ info ( 'Would migrate legacy casting state to a managed registry/history pair.' ) ;
1641+ } else {
1642+ warn ( 'Migrated legacy casting state to a managed registry/history pair; byte-exact originals were preserved in its legacy archive.' ) ;
1643+ }
15961644 }
15971645
15981646 // --dry-run: preview what upgrade would do without writing (#1052, #2297)
@@ -1639,6 +1687,7 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
16391687 warn ( `Could not plan client files: ${ err instanceof Error ? err . message : String ( err ) } ` ) ;
16401688 }
16411689 console . log ( `\nRun without --dry-run to apply.\n` ) ;
1690+ reportCastingMigrationFailure ( castingMigrationFailure ) ;
16421691 return { fromVersion : oldVersion , toVersion : cliVersion , filesUpdated : [ ] , migrationsRun : [ ] } ;
16431692 }
16441693
@@ -1661,7 +1710,7 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
16611710 // to render the <!-- squad:capabilities --> block, so a 0.9.x project (empty casting/) would
16621711 // otherwise get the block only on the SECOND upgrade (F4, upgrade matrix).
16631712 prepareBuiltinAgents ( dest , templatesDir , filesUpdated ) ;
1664- preflightCastingPair ( dest , templatesDir , filesUpdated ) ;
1713+ if ( ! castingMigrationFailure ) preflightCastingPair ( dest , templatesDir , filesUpdated ) ;
16651714
16661715 // Refresh squad.agent.md
16671716 const agentSrc = path . join ( templatesDir , 'squad.agent.md.template' ) ;
@@ -1675,8 +1724,9 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
16751724 }
16761725
16771726 // Run infrastructure ensure checks even when already current
1678- await runEnsureChecks ( dest , templatesDir , filesUpdated ) ;
1727+ await runEnsureChecks ( dest , templatesDir , filesUpdated , { skipCasting : ! ! castingMigrationFailure } ) ;
16791728 await refreshClients ( dest , squadDirInfo . path , options , filesUpdated ) ;
1729+ reportCastingMigrationFailure ( castingMigrationFailure ) ;
16801730
16811731 return {
16821732 fromVersion : oldVersion ,
@@ -1696,7 +1746,7 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
16961746
16971747 // See preflightCastingPair: the capabilities block needs the casting pair first (F4).
16981748 prepareBuiltinAgents ( dest , templatesDir , filesUpdated ) ;
1699- preflightCastingPair ( dest , templatesDir , filesUpdated ) ;
1749+ if ( ! castingMigrationFailure ) preflightCastingPair ( dest , templatesDir , filesUpdated ) ;
17001750
17011751 storage . mkdirSync ( path . dirname ( agentDest ) , { recursive : true } ) ;
17021752 writeAgentTemplate ( agentSrc , agentDest , cliVersion , mcpConfigMode , isGitHubForMcp , { squadDir : squadDirInfo . path } ) ;
@@ -1734,7 +1784,7 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
17341784 }
17351785
17361786 // Run infrastructure ensure checks
1737- await runEnsureChecks ( dest , templatesDir , filesUpdated ) ;
1787+ await runEnsureChecks ( dest , templatesDir , filesUpdated , { skipCasting : ! ! castingMigrationFailure } ) ;
17381788 await refreshClients ( dest , squadDirInfo . path , options , filesUpdated ) ;
17391789
17401790 console . log ( ) ;
@@ -1745,6 +1795,7 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
17451795 dim ( 'Preserves user state: team.md, decisions/, agents/*/history.md' ) ;
17461796 }
17471797 console . log ( ) ;
1798+ reportCastingMigrationFailure ( castingMigrationFailure ) ;
17481799
17491800 return {
17501801 fromVersion : fromLabel ,
0 commit comments