Skip to content
Closed
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/agent-file-silent-deletion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@bradygaster/squad-cli': patch
'@bradygaster/squad-sdk': patch
---

Prevent squad.agent.md silent deletion — 3 code path fixes
12 changes: 0 additions & 12 deletions .changeset/fix-nap-archival-budget.md

This file was deleted.

4 changes: 2 additions & 2 deletions packages/squad-cli/src/cli/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,8 @@ function checkSquadAgentMd(cwd: string): DoctorCheck {
if (content.trim().length === 0) {
return {
name: '.github/agents/squad.agent.md',
status: 'warn',
message: "file is empty — run 'squad upgrade' to restore it",
status: 'fail',
message: "file is empty — Squad will not be discovered by Copilot. Run 'squad upgrade' to restore it",
};
}
} catch {
Expand Down
13 changes: 4 additions & 9 deletions packages/squad-cli/src/cli/core/nap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,22 +386,17 @@ function archiveDecisions(squadDir: string, dryRun: boolean): NapAction | null {
dated.sort((a, b) => a.daysAgo! - b.daysAgo!);

// Keep the most recent dated entries that fit under the threshold
// along with all undated entries and the header.
// Account for separator newlines added during reassembly:
// recentContent = header + '\n' + entries.join('\n') + '\n'
// Each entry contributes +1 byte for its join separator (overestimates
// by 1 byte for the last entry, which is a safe margin).
// along with all undated entries and the header
const headerEnd = entries.length > 0 ? entries[0]!.start : lines.length;
const headerSize = Buffer.byteLength(lines.slice(0, headerEnd).join('\n'), 'utf8');
const reassemblyOverhead = 2; // '\n' after header + trailing '\n'
const undatedSize = undated.reduce(
(sum, e) => sum + Buffer.byteLength(lines.slice(e.start, e.end).join('\n'), 'utf8') + 1, 0,
(sum, e) => sum + Buffer.byteLength(lines.slice(e.start, e.end).join('\n'), 'utf8'), 0,
);
let budget = DECISION_THRESHOLD - headerSize - reassemblyOverhead - undatedSize;
let budget = DECISION_THRESHOLD - headerSize - undatedSize;

const keptDated: typeof entries = [];
for (const e of dated) {
const entrySize = Buffer.byteLength(lines.slice(e.start, e.end).join('\n'), 'utf8') + 1;
const entrySize = Buffer.byteLength(lines.slice(e.start, e.end).join('\n'), 'utf8');
if (budget >= entrySize) {
budget -= entrySize;
keptDated.push(e);
Expand Down
31 changes: 31 additions & 0 deletions packages/squad-cli/src/cli/core/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface UpdateInfo {
toVersion: string;
filesUpdated: string[];
migrationsRun: string[];
warnings?: string[];
}

/**
Expand Down Expand Up @@ -448,6 +449,7 @@ function runEnsureChecks(dest: string, templatesDir: string, filesUpdated: strin
export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Promise<UpdateInfo> {
const cliVersion = getPackageVersion();
const filesUpdated: string[] = [];
const warnings: string[] = [];

// Detect squad directory
const squadDirInfo = detectSquadDir(dest);
Expand Down Expand Up @@ -495,12 +497,39 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr

// Refresh squad.agent.md
const agentSrc = path.join(templatesDir, 'squad.agent.md.template');
const existingAgentContent = storage.existsSync(agentDest) ? (storage.readSync(agentDest) ?? '') : '';
const agentFileIsEmpty = !storage.existsSync(agentDest) || existingAgentContent.trim().length < 100;

if (storage.existsSync(agentSrc)) {
storage.mkdirSync(path.dirname(agentDest), { recursive: true });
storage.copySync(agentSrc, agentDest);
stampVersion(agentDest, cliVersion);
success('upgraded squad.agent.md');
filesUpdated.push('squad.agent.md');
} else if (agentFileIsEmpty) {
// Template missing and file is empty/minimal — generate fallback
storage.mkdirSync(path.dirname(agentDest), { recursive: true });
const fallback = [
'# Squad Agent',
'',
'This is the Squad coordinator agent file.',
'It enables GitHub Copilot to discover and route work to your Squad team.',
'',
'This file was auto-generated because the original template was unavailable.',
'Run `squad upgrade` again once the CLI is fully installed to get the complete version.',
'',
`<!-- version: ${cliVersion} -->`,
'',
].join('\n');
storage.writeSync(agentDest, fallback);
warn('squad.agent.md.template not found — generated fallback agent file');
warnings.push('squad.agent.md.template not found — generated fallback agent file');
filesUpdated.push('squad.agent.md');
} else {
// Template missing but file has content — keep existing, warn, still report
warn('squad.agent.md.template not found — keeping existing squad.agent.md');
warnings.push('squad.agent.md.template not found — could not refresh agent file');
filesUpdated.push('squad.agent.md');
}

// Run infrastructure ensure checks even when already current
Expand All @@ -511,6 +540,7 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
toVersion: cliVersion,
filesUpdated,
migrationsRun: migrationsApplied,
warnings,
};
}

Expand Down Expand Up @@ -603,5 +633,6 @@ export async function runUpgrade(dest: string, options: UpgradeOptions = {}): Pr
toVersion: cliVersion,
filesUpdated,
migrationsRun: migrationsApplied,
warnings,
};
}
22 changes: 22 additions & 0 deletions packages/squad-sdk/src/config/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ export interface InitResult {
agentFile: string;
/** Path to .squad/ directory */
squadDir: string;
/** Warnings generated during init */
warnings?: string[];
}

// ============================================================================
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,24 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre
agentContent = stampVersionInContent(agentContent, version);
await storage.write(agentFile, agentContent);
createdFiles.push(toRelativePath(agentFile));
} else {
// Template missing — generate fallback so Copilot can still discover Squad
await storage.mkdir(dirname(agentFile), { recursive: true });
const fallback = [
'# Squad Agent',
'',
'This is the Squad coordinator agent file.',
'It enables GitHub Copilot to discover and route work to your Squad team.',
'',
'This file was auto-generated because the original template was unavailable.',
'Run `squad upgrade` once the CLI is fully installed to get the complete version.',
'',
`<!-- version: ${version} -->`,
'',
].join('\n');
await storage.write(agentFile, fallback);
createdFiles.push(toRelativePath(agentFile));
warnings.push('squad.agent.md.template not found — generated fallback agent file');
}
} else {
skippedFiles.push(toRelativePath(agentFile));
Expand Down Expand Up @@ -1191,6 +1212,7 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre
agentDirs,
agentFile,
squadDir,
warnings,
};
}

Expand Down
Loading
Loading