diff --git a/.changeset/fix-silent-agent-md-skip.md b/.changeset/fix-silent-agent-md-skip.md new file mode 100644 index 000000000..349139fcb --- /dev/null +++ b/.changeset/fix-silent-agent-md-skip.md @@ -0,0 +1,6 @@ +--- +'@bradygaster/squad-cli': patch +'@bradygaster/squad-sdk': patch +--- + +Warn when squad.agent.md template is missing during upgrade or init instead of silently skipping file creation. Adds `warnings` field to `InitResult` for structured error reporting. diff --git a/packages/squad-cli/src/cli/core/upgrade.ts b/packages/squad-cli/src/cli/core/upgrade.ts index 2d280f1e1..0e9dc1172 100644 --- a/packages/squad-cli/src/cli/core/upgrade.ts +++ b/packages/squad-cli/src/cli/core/upgrade.ts @@ -501,6 +501,8 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr stampVersion(agentDest, cliVersion); success('upgraded squad.agent.md'); filesUpdated.push('squad.agent.md'); + } else { + warn('squad.agent.md.template not found — squad.agent.md was not refreshed. Reinstall or repair the CLI to restore the missing template.'); } // Run infrastructure ensure checks even when already current diff --git a/packages/squad-sdk/src/config/init.ts b/packages/squad-sdk/src/config/init.ts index 2b957765c..8dd85cdef 100644 --- a/packages/squad-sdk/src/config/init.ts +++ b/packages/squad-sdk/src/config/init.ts @@ -133,6 +133,8 @@ export interface InitResult { createdFiles: string[]; /** List of skipped file paths (already existed) */ skippedFiles: string[]; + /** Warnings for degraded operations (e.g. missing templates) */ + warnings?: string[]; /** Configuration file path */ configPath: string; /** Agent directory paths */ @@ -625,6 +627,7 @@ export async function initSquad(options: InitOptions, storage: StorageProvider = const createdFiles: string[] = []; const skippedFiles: string[] = []; + const warnings: string[] = []; const agentDirs: string[] = []; // Validate inputs @@ -1035,6 +1038,8 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre agentContent = stampVersionInContent(agentContent, version); await storage.write(agentFile, agentContent); createdFiles.push(toRelativePath(agentFile)); + } else { + warnings.push(`squad.agent.md template not found (${join(templatesDir || '.squad/templates', 'squad.agent.md.template')}) — Copilot agent file was not created or not refreshed`); } } else { skippedFiles.push(toRelativePath(agentFile)); @@ -1187,6 +1192,7 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre return { createdFiles, skippedFiles, + warnings, configPath, agentDirs, agentFile, diff --git a/test/cli/upgrade.test.ts b/test/cli/upgrade.test.ts index 4094e9968..1ccd89a7c 100644 --- a/test/cli/upgrade.test.ts +++ b/test/cli/upgrade.test.ts @@ -115,6 +115,27 @@ describe('CLI: upgrade command', () => { expect(result.toVersion).toBe(currentVersion); }); + it('refreshes squad.agent.md when already at current version (happy-path regression)', async () => { + const agentPath = join(TEST_ROOT, '.github', 'agents', 'squad.agent.md'); + const currentVersion = getPackageVersion(); + + // Verify initial state — already at current version + const before = await readFile(agentPath, 'utf-8'); + expect(before).toContain(``); + + // Run upgrade (takes version-current path) + const result = await runUpgrade(TEST_ROOT); + + expect(result.fromVersion).toBe(currentVersion); + expect(result.toVersion).toBe(currentVersion); + // squad.agent.md should still be refreshed in the version-current path + expect(result.filesUpdated).toContain('squad.agent.md'); + + // File should still have the correct version stamp + const after = await readFile(agentPath, 'utf-8'); + expect(after).toContain(``); + }); + it('should preserve version stamp after manifest loop (issue #195)', async () => { const agentPath = join(TEST_ROOT, '.github', 'agents', 'squad.agent.md'); const currentVersion = getPackageVersion(); diff --git a/test/init-scaffolding.test.ts b/test/init-scaffolding.test.ts index a5d9d5816..c73411488 100644 --- a/test/init-scaffolding.test.ts +++ b/test/init-scaffolding.test.ts @@ -305,3 +305,76 @@ describe('doctor passes after init (#579)', () => { expect(registryCheck?.status).toBe('fail'); }); }); + +// ─── squad.agent.md template handling (#730) ─────────────────────────── + +describe('squad.agent.md template handling (#730)', () => { + beforeEach(async () => { + if (existsSync(TEST_ROOT)) { + await rm(TEST_ROOT, { recursive: true, force: true }); + } + await mkdir(TEST_ROOT, { recursive: true }); + }); + + afterEach(async () => { + if (existsSync(TEST_ROOT)) { + await rm(TEST_ROOT, { recursive: true, force: true }); + } + }); + + it('initSquad creates squad.agent.md when template exists (happy-path regression)', async () => { + const result = await initSquad(sdkOptions(TEST_ROOT)); + + // squad.agent.md should be in createdFiles + const agentEntry = result.createdFiles.find(f => f.includes('squad.agent.md')); + expect(agentEntry).toBeDefined(); + + // File should exist on disk + const agentPath = join(TEST_ROOT, '.github', 'agents', 'squad.agent.md'); + expect(existsSync(agentPath)).toBe(true); + + // No warnings should be emitted + expect(result.warnings).toEqual([]); + }); + + it('initSquad returns warning when squad.agent.md template is missing', async () => { + const { FSStorageProvider } = await import('@bradygaster/squad-sdk'); + const realStorage = new FSStorageProvider(); + + // Create a proxy storage that hides squad.agent.md.template + const maskedStorage = new Proxy(realStorage, { + get(target, prop, receiver) { + if (prop === 'existsSync') { + return (filePath: string) => { + if (filePath.endsWith('squad.agent.md.template')) { + return false; + } + return target.existsSync(filePath); + }; + } + if (prop === 'readSync') { + return (filePath: string) => { + if (filePath.endsWith('squad.agent.md.template')) { + return undefined; + } + return target.readSync(filePath); + }; + } + return Reflect.get(target, prop, receiver); + }, + }); + + const result = await initSquad(sdkOptions(TEST_ROOT), maskedStorage as typeof realStorage); + + // Warning should be present + expect(result.warnings.length).toBe(1); + expect(result.warnings[0]).toContain('squad.agent.md template not found'); + + // squad.agent.md should NOT be in createdFiles + const agentEntry = result.createdFiles.find(f => f.includes('squad.agent.md')); + expect(agentEntry).toBeUndefined(); + + // Other files should still be created + expect(result.createdFiles.length).toBeGreaterThan(0); + }); +});