@@ -19,7 +19,7 @@ function fatal(msg) {
1919function detectSquadDir ( dest ) {
2020 const squadDir = path . join ( dest , '.squad' ) ;
2121 const aiTeamDir = path . join ( dest , '.ai-team' ) ;
22-
22+
2323 if ( fs . existsSync ( squadDir ) ) {
2424 return { path : squadDir , name : '.squad' , isLegacy : false } ;
2525 }
@@ -67,7 +67,7 @@ if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
6767 console . log ( `Commands:` ) ;
6868 console . log ( ` ${ BOLD } (default)${ RESET } Initialize Squad (skip files that already exist)` ) ;
6969 console . log ( ` ${ BOLD } upgrade${ RESET } Update Squad-owned files to latest version` ) ;
70- console . log ( ` Overwrites: squad.agent.md, templates dir (.squad- templates/ or .ai-team-templates/)` ) ;
70+ console . log ( ` Overwrites: squad.agent.md, templates dir (.squad/ templates/ or .ai-team-templates/)` ) ;
7171 console . log ( ` Never touches: .squad/ or .ai-team/ (your team state)` ) ;
7272 console . log ( ` Flags: --migrate-directory (rename .ai-team/ → .squad/)` ) ;
7373 console . log ( ` ${ BOLD } copilot${ RESET } Add/remove the Copilot coding agent (@copilot)` ) ;
@@ -210,15 +210,15 @@ if (cmd === 'watch') {
210210 for ( const member of members ) {
211211 const role = member . role . toLowerCase ( ) ;
212212 if ( ( role . includes ( 'frontend' ) || role . includes ( 'ui' ) ) &&
213- ( issueText . includes ( 'ui' ) || issueText . includes ( 'frontend' ) || issueText . includes ( 'css' ) ) ) {
213+ ( issueText . includes ( 'ui' ) || issueText . includes ( 'frontend' ) || issueText . includes ( 'css' ) ) ) {
214214 assignedMember = member ; reason = 'frontend/UI domain' ; break ;
215215 }
216216 if ( ( role . includes ( 'backend' ) || role . includes ( 'api' ) || role . includes ( 'server' ) ) &&
217- ( issueText . includes ( 'api' ) || issueText . includes ( 'backend' ) || issueText . includes ( 'database' ) ) ) {
217+ ( issueText . includes ( 'api' ) || issueText . includes ( 'backend' ) || issueText . includes ( 'database' ) ) ) {
218218 assignedMember = member ; reason = 'backend/API domain' ; break ;
219219 }
220220 if ( ( role . includes ( 'test' ) || role . includes ( 'qa' ) ) &&
221- ( issueText . includes ( 'test' ) || issueText . includes ( 'bug' ) || issueText . includes ( 'fix' ) ) ) {
221+ ( issueText . includes ( 'test' ) || issueText . includes ( 'bug' ) || issueText . includes ( 'fix' ) ) ) {
222222 assignedMember = member ; reason = 'testing/QA domain' ; break ;
223223 }
224224 }
@@ -276,35 +276,35 @@ if (cmd === 'watch') {
276276function scrubEmailsFromDirectory ( dirPath ) {
277277 const EMAIL_PATTERN = / ( [ a - z A - Z 0 - 9 . _ % + - ] + @ [ a - z A - Z 0 - 9 . - ] + \. [ a - z A - Z ] { 2 , } ) / g;
278278 const NAME_WITH_EMAIL_PATTERN = / ( [ a - z A - Z 0 - 9 _ - ] + ) \s * \( ( [ a - z A - Z 0 - 9 . _ % + - ] + @ [ a - z A - Z 0 - 9 . - ] + \. [ a - z A - Z ] { 2 , } ) \) / g;
279-
279+
280280 const scrubbedFiles = [ ] ;
281281 const filesToScrub = [
282282 'team.md' ,
283283 'decisions.md' ,
284284 'routing.md' ,
285285 'ceremonies.md'
286286 ] ;
287-
287+
288288 // Scrub root-level files
289289 for ( const file of filesToScrub ) {
290290 const filePath = path . join ( dirPath , file ) ;
291291 if ( fs . existsSync ( filePath ) ) {
292292 try {
293293 let content = fs . readFileSync ( filePath , 'utf8' ) ;
294294 let modified = false ;
295-
295+
296296 // Replace "name (email)" → "name"
297297 const beforeNameEmail = content ;
298298 content = content . replace ( NAME_WITH_EMAIL_PATTERN , '$1' ) ;
299299 if ( content !== beforeNameEmail ) modified = true ;
300-
300+
301301 // Replace bare emails in identity contexts (but preserve in URLs and code examples)
302302 const lines = content . split ( '\n' ) ;
303303 const scrubbed = lines . map ( line => {
304304 // Skip lines that look like URLs, code blocks, or examples
305- if ( line . includes ( 'http://' ) || line . includes ( 'https://' ) ||
306- line . includes ( '```' ) || line . includes ( 'example.com' ) ||
307- line . trim ( ) . startsWith ( '//' ) || line . trim ( ) . startsWith ( '#' ) ) {
305+ if ( line . includes ( 'http://' ) || line . includes ( 'https://' ) ||
306+ line . includes ( '```' ) || line . includes ( 'example.com' ) ||
307+ line . trim ( ) . startsWith ( '//' ) || line . trim ( ) . startsWith ( '#' ) ) {
308308 return line ;
309309 }
310310 // Scrub emails from identity/attribution contexts
@@ -313,7 +313,7 @@ function scrubEmailsFromDirectory(dirPath) {
313313 if ( before !== after ) modified = true ;
314314 return after ;
315315 } ) ;
316-
316+
317317 if ( modified ) {
318318 fs . writeFileSync ( filePath , scrubbed . join ( '\n' ) ) ;
319319 scrubbedFiles . push ( path . relative ( dirPath , filePath ) ) ;
@@ -323,7 +323,7 @@ function scrubEmailsFromDirectory(dirPath) {
323323 }
324324 }
325325 }
326-
326+
327327 // Scrub agent history files
328328 const agentsDir = path . join ( dirPath , 'agents' ) ;
329329 if ( fs . existsSync ( agentsDir ) ) {
@@ -333,27 +333,27 @@ function scrubEmailsFromDirectory(dirPath) {
333333 if ( fs . existsSync ( historyPath ) ) {
334334 let content = fs . readFileSync ( historyPath , 'utf8' ) ;
335335 let modified = false ;
336-
336+
337337 // Replace "name (email)" → "name"
338338 const beforeNameEmail = content ;
339339 content = content . replace ( NAME_WITH_EMAIL_PATTERN , '$1' ) ;
340340 if ( content !== beforeNameEmail ) modified = true ;
341-
341+
342342 // Scrub bare emails carefully
343343 const lines = content . split ( '\n' ) ;
344344 const scrubbed = lines . map ( line => {
345345 // Skip URLs, code, examples
346- if ( line . includes ( 'http://' ) || line . includes ( 'https://' ) ||
347- line . includes ( '```' ) || line . includes ( 'example.com' ) ||
348- line . trim ( ) . startsWith ( '//' ) || line . trim ( ) . startsWith ( '#' ) ) {
346+ if ( line . includes ( 'http://' ) || line . includes ( 'https://' ) ||
347+ line . includes ( '```' ) || line . includes ( 'example.com' ) ||
348+ line . trim ( ) . startsWith ( '//' ) || line . trim ( ) . startsWith ( '#' ) ) {
349349 return line ;
350350 }
351351 const before = line ;
352352 const after = line . replace ( EMAIL_PATTERN , '[email scrubbed]' ) ;
353353 if ( before !== after ) modified = true ;
354354 return after ;
355355 } ) ;
356-
356+
357357 if ( modified ) {
358358 fs . writeFileSync ( historyPath , scrubbed . join ( '\n' ) ) ;
359359 scrubbedFiles . push ( path . relative ( dirPath , historyPath ) ) ;
@@ -364,7 +364,7 @@ function scrubEmailsFromDirectory(dirPath) {
364364 console . error ( `${ RED } ✗${ RESET } Failed to scrub agent histories: ${ err . message } ` ) ;
365365 }
366366 }
367-
367+
368368 // Scrub log files
369369 const logDir = path . join ( dirPath , 'log' ) ;
370370 if ( fs . existsSync ( logDir ) ) {
@@ -385,15 +385,15 @@ function scrubEmailsFromDirectory(dirPath) {
385385 console . error ( `${ RED } ✗${ RESET } Failed to scrub log files: ${ err . message } ` ) ;
386386 }
387387 }
388-
388+
389389 return scrubbedFiles ;
390390}
391391
392392// Replace legacy .ai-team/ path references inside .md and .json files
393393function replaceAiTeamReferences ( dirPath ) {
394394 const updatedFiles = [ ] ;
395395 const replacements = [
396- [ / \. a i - t e a m - t e m p l a t e s \/ / g, '.squad- templates/' ] ,
396+ [ / \. a i - t e a m - t e m p l a t e s \/ / g, '.squad/ templates/' ] ,
397397 [ / \. a i - t e a m \/ / g, '.squad/' ]
398398 ] ;
399399
@@ -429,14 +429,14 @@ function detectProjectType(dir) {
429429 if ( fs . existsSync ( path . join ( dir , 'package.json' ) ) ) return 'npm' ;
430430 if ( fs . existsSync ( path . join ( dir , 'go.mod' ) ) ) return 'go' ;
431431 if ( fs . existsSync ( path . join ( dir , 'requirements.txt' ) ) ||
432- fs . existsSync ( path . join ( dir , 'pyproject.toml' ) ) ) return 'python' ;
432+ fs . existsSync ( path . join ( dir , 'pyproject.toml' ) ) ) return 'python' ;
433433 if ( fs . existsSync ( path . join ( dir , 'pom.xml' ) ) ||
434- fs . existsSync ( path . join ( dir , 'build.gradle' ) ) ||
435- fs . existsSync ( path . join ( dir , 'build.gradle.kts' ) ) ) return 'java' ;
434+ fs . existsSync ( path . join ( dir , 'build.gradle' ) ) ||
435+ fs . existsSync ( path . join ( dir , 'build.gradle.kts' ) ) ) return 'java' ;
436436 try {
437437 const entries = fs . readdirSync ( dir ) ;
438438 if ( entries . some ( e => e . endsWith ( '.csproj' ) || e . endsWith ( '.sln' ) || e . endsWith ( '.slnx' ) || e . endsWith ( '.fsproj' ) || e . endsWith ( '.vbproj' ) ) ) return 'dotnet' ;
439- } catch { }
439+ } catch { }
440440 return 'unknown' ;
441441}
442442
@@ -612,14 +612,14 @@ function writeWorkflowFile(file, srcPath, destPath, projectType) {
612612// --- Email scrubbing subcommand ---
613613if ( cmd === 'scrub-emails' ) {
614614 const targetDir = process . argv [ 3 ] || path . join ( dest , '.ai-team' ) ;
615-
615+
616616 if ( ! fs . existsSync ( targetDir ) ) {
617617 fatal ( `Directory not found: ${ targetDir } ` ) ;
618618 }
619-
619+
620620 console . log ( `${ DIM } Scanning ${ path . relative ( dest , targetDir ) } for email addresses...${ RESET } ` ) ;
621621 const scrubbedFiles = scrubEmailsFromDirectory ( targetDir ) ;
622-
622+
623623 if ( scrubbedFiles . length === 0 ) {
624624 console . log ( `${ GREEN } ✓${ RESET } No email addresses found — all clean` ) ;
625625 } else {
@@ -1163,15 +1163,15 @@ const isMigrateDirectory = isUpgrade && process.argv.includes('--migrate-directo
11631163if ( isMigrateDirectory ) {
11641164 const aiTeamDir = path . join ( dest , '.ai-team' ) ;
11651165 const squadDir = path . join ( dest , '.squad' ) ;
1166-
1166+
11671167 if ( ! fs . existsSync ( aiTeamDir ) ) {
11681168 fatal ( 'No .ai-team/ directory found — nothing to migrate.' ) ;
11691169 }
1170-
1170+
11711171 if ( fs . existsSync ( squadDir ) ) {
11721172 fatal ( '.squad/ directory already exists — migration appears to be complete.' ) ;
11731173 }
1174-
1174+
11751175 // Safe rename that falls back to copy+delete on Windows EPERM/EACCES
11761176 function safeRename ( source , target ) {
11771177 try {
@@ -1187,12 +1187,12 @@ if (isMigrateDirectory) {
11871187 }
11881188
11891189 console . log ( `${ DIM } Migrating .ai-team/ → .squad/...${ RESET } ` ) ;
1190-
1190+
11911191 try {
11921192 // Rename directory
11931193 safeRename ( aiTeamDir , squadDir ) ;
11941194 console . log ( `${ GREEN } ✓${ RESET } Renamed .ai-team/ → .squad/` ) ;
1195-
1195+
11961196 // Update .gitattributes
11971197 const gitattributes = path . join ( dest , '.gitattributes' ) ;
11981198 if ( fs . existsSync ( gitattributes ) ) {
@@ -1203,7 +1203,7 @@ if (isMigrateDirectory) {
12031203 console . log ( `${ GREEN } ✓${ RESET } Updated .gitattributes` ) ;
12041204 }
12051205 }
1206-
1206+
12071207 // Update .gitignore if it exists
12081208 const gitignore = path . join ( dest , '.gitignore' ) ;
12091209 if ( fs . existsSync ( gitignore ) ) {
@@ -1214,7 +1214,7 @@ if (isMigrateDirectory) {
12141214 console . log ( `${ GREEN } ✓${ RESET } Updated .gitignore` ) ;
12151215 }
12161216 }
1217-
1217+
12181218 // Scrub email addresses from migrated files
12191219 console . log ( `${ DIM } Scrubbing email addresses from .squad/ files...${ RESET } ` ) ;
12201220 const scrubbedFiles = scrubEmailsFromDirectory ( squadDir ) ;
@@ -1233,12 +1233,12 @@ if (isMigrateDirectory) {
12331233 console . log ( `${ GREEN } ✓${ RESET } No .ai-team/ references found` ) ;
12341234 }
12351235
1236- // Rename .ai-team-templates/ → .squad- templates/ if it exists
1236+ // Rename .ai-team-templates/ → .squad/ templates/ if it exists
12371237 const aiTeamTemplatesDir = path . join ( dest , '.ai-team-templates' ) ;
1238- const squadTemplatesDir = path . join ( dest , '.squad- templates' ) ;
1238+ const squadTemplatesDir = path . join ( dest , '.squad' , ' templates') ;
12391239 if ( fs . existsSync ( aiTeamTemplatesDir ) ) {
12401240 safeRename ( aiTeamTemplatesDir , squadTemplatesDir ) ;
1241- console . log ( `${ GREEN } ✓${ RESET } Renamed .ai-team-templates/ → .squad- templates/` ) ;
1241+ console . log ( `${ GREEN } ✓${ RESET } Renamed .ai-team-templates/ → .squad/ templates/` ) ;
12421242 }
12431243
12441244 console . log ( ) ;
@@ -1247,7 +1247,7 @@ if (isMigrateDirectory) {
12471247 console . log ( ` git add -A` ) ;
12481248 console . log ( ` git commit -m "chore: migrate .ai-team/ → .squad/"` ) ;
12491249 console . log ( ) ;
1250-
1250+
12511251 } catch ( err ) {
12521252 fatal ( `Migration failed: ${ err . message } ` ) ;
12531253 }
@@ -1342,6 +1342,21 @@ const migrations = [
13421342 console . log ( `${ GREEN } ✓${ RESET } Removed squad-main-guard.yml — .squad/ files can now flow freely to all branches` ) ;
13431343 }
13441344 }
1345+ } ,
1346+ {
1347+ version : '0.5.5' ,
1348+ description : 'Move .squad-templates/ into .squad/templates/' ,
1349+ run ( dest , squadDir ) {
1350+ const oldPath = path . join ( dest , '.squad-templates' ) ;
1351+ const newPath = path . join ( squadDir || path . join ( dest , '.squad' ) , 'templates' ) ;
1352+ if ( fs . existsSync ( oldPath ) && ! fs . existsSync ( newPath ) ) {
1353+ safeRename ( oldPath , newPath ) ;
1354+ console . log ( `${ GREEN } ✓${ RESET } Moved .squad-templates/ → .squad/templates/` ) ;
1355+ } else if ( fs . existsSync ( oldPath ) && fs . existsSync ( newPath ) ) {
1356+ fs . rmSync ( oldPath , { recursive : true } ) ;
1357+ console . log ( `${ GREEN } ✓${ RESET } Removed redundant .squad-templates/ (already have .squad/templates/)` ) ;
1358+ }
1359+ }
13451360 }
13461361] ;
13471362
@@ -1486,7 +1501,7 @@ if (isUpgrade) {
14861501const squadInfo = ( ( ) => {
14871502 const squadDir = path . join ( dest , '.squad' ) ;
14881503 const aiTeamDir = path . join ( dest , '.ai-team' ) ;
1489-
1504+
14901505 if ( fs . existsSync ( squadDir ) ) {
14911506 return { path : squadDir , name : '.squad' , isLegacy : false } ;
14921507 }
@@ -1651,20 +1666,22 @@ if (missing.length) {
16511666
16521667// Copy templates (Squad-owned — overwrite on upgrade)
16531668const templatesSrc = path . join ( root , 'templates' ) ;
1654- const templatesDestName = squadInfo . isLegacy ? '.ai-team-templates' : '.squad-templates' ;
1655- const templatesDest = path . join ( dest , templatesDestName ) ;
1669+ const templatesDest = squadInfo . isLegacy
1670+ ? path . join ( dest , '.ai-team-templates' )
1671+ : path . join ( dest , '.squad' , 'templates' ) ;
1672+ const templatesDestDisplay = squadInfo . isLegacy ? '.ai-team-templates/' : '.squad/templates/' ;
16561673
16571674if ( isUpgrade ) {
16581675 copyRecursive ( templatesSrc , templatesDest ) ;
1659- console . log ( `${ GREEN } ✓${ RESET } ${ BOLD } upgraded${ RESET } ${ templatesDestName } / ` ) ;
1676+ console . log ( `${ GREEN } ✓${ RESET } ${ BOLD } upgraded${ RESET } ${ templatesDestDisplay } ` ) ;
16601677
16611678 // Run migrations applicable for this version jump
16621679 runMigrations ( dest , oldVersion || '0.0.0' , squadInfo . path ) ;
16631680} else if ( fs . existsSync ( templatesDest ) ) {
1664- console . log ( `${ DIM } ${ templatesDestName } / already exists — skipping (run 'upgrade' to update)${ RESET } ` ) ;
1681+ console . log ( `${ DIM } ${ templatesDestDisplay } already exists — skipping (run 'upgrade' to update)${ RESET } ` ) ;
16651682} else {
16661683 copyRecursive ( templatesSrc , templatesDest ) ;
1667- console . log ( `${ GREEN } ✓${ RESET } ${ templatesDestName } / ` ) ;
1684+ console . log ( `${ GREEN } ✓${ RESET } ${ templatesDestDisplay } ` ) ;
16681685}
16691686
16701687// Copy workflow templates (Squad-owned — overwrite on upgrade)
@@ -1709,7 +1726,7 @@ if (isUpgrade) {
17091726 } else {
17101727 console . log ( `${ GREEN } ✓${ RESET } No email addresses found` ) ;
17111728 }
1712-
1729+
17131730 console . log ( `\n${ DIM } ${ squadInfo . name } / untouched — your team state is safe${ RESET } ` ) ;
17141731
17151732 // Hint about new features available after upgrade
0 commit comments