Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fix-silent-agent-md-skip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@bradygaster/squad-cli': patch
'@bradygaster/squad-sdk': patch
Comment thread
diberry marked this conversation as resolved.
Resolved
---

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.
2 changes: 2 additions & 0 deletions packages/squad-cli/src/cli/core/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions packages/squad-sdk/src/config/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -1187,6 +1192,7 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre
return {
createdFiles,
skippedFiles,
warnings,
configPath,
agentDirs,
agentFile,
Expand Down
21 changes: 21 additions & 0 deletions test/cli/upgrade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(`<!-- version: ${currentVersion} -->`);

// 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(`<!-- version: ${currentVersion} -->`);
});

it('should preserve version stamp after manifest loop (issue #195)', async () => {
const agentPath = join(TEST_ROOT, '.github', 'agents', 'squad.agent.md');
const currentVersion = getPackageVersion();
Expand Down
73 changes: 73 additions & 0 deletions test/init-scaffolding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Loading