Skip to content

Commit 93c50c6

Browse files
authored
Merge pull request #185 from williamhallatt/squad/fix-template-install-path
fix(templates): install to .squad/templates/ not .squad-templates/
2 parents bf2cc88 + 7b5e2fd commit 93c50c6

3 files changed

Lines changed: 78 additions & 61 deletions

File tree

‎index.js‎

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function fatal(msg) {
1919
function 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') {
276276
function scrubEmailsFromDirectory(dirPath) {
277277
const EMAIL_PATTERN = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
278278
const NAME_WITH_EMAIL_PATTERN = /([a-zA-Z0-9_-]+)\s*\(([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-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
393393
function replaceAiTeamReferences(dirPath) {
394394
const updatedFiles = [];
395395
const replacements = [
396-
[/\.ai-team-templates\//g, '.squad-templates/'],
396+
[/\.ai-team-templates\//g, '.squad/templates/'],
397397
[/\.ai-team\//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 ---
613613
if (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
11631163
if (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) {
14861501
const 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)
16531668
const 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

16571674
if (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

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bradygaster/create-squad",
3-
"version": "0.5.4",
3+
"version": "0.5.5",
44
"description": "Add an AI agent team to any project",
55
"bin": {
66
"create-squad": "./index.js"

0 commit comments

Comments
 (0)