Skip to content

test(cli): add init→upgrade→doctor parity test suite - #840

Merged
tamirdresher merged 3 commits into
devfrom
squad/822-init-upgrade-parity-tests
Apr 5, 2026
Merged

tamirdresher merged 3 commits into
devfrom
squad/822-init-upgrade-parity-tests

Conversation

@diberry

@diberry diberry commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a parity test suite that verifies init and upgrade produce equivalent scaffolding. This guards against the class of drift discovered when casting was added to init but upgrade wasn't updated (Brady's insiders install).

Tests (7)

# Test What it verifies
1 ENSURE_DIRECTORIES matches init dirs Every dir init creates is also in ENSURE_DIRECTORIES
2 Upgrade scaffolds casting defaults ensureCastingDefaults creates registry/policy/history.json
3 Upgrade doesn't overwrite existing files force:false preserves user state (team.md, casting)
4 Both paths create .squad/agents/ Agents directory is ensured by both code paths
5 Both paths create .gitattributes merge=union rules installed by init and upgrade
6 ensureDirectories is idempotent Second call returns empty array
7 ensureGitattributes is idempotent Rules not duplicated on repeated calls

Test-only PR — no product code changes

Closes #822
Related: #817, #823

Copilot AI review requested due to automatic review settings April 4, 2026 23:33
@github-actions

github-actions Bot commented Apr 4, 2026 •

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #840

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 2
Files added 2
Files modified 0
Files deleted 0
Modules touched 2

🎯 Risk Factors

  • 2 files changed (≤5 → LOW)
  • 2 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • .changeset/init-upgrade-parity-tests.md
tests (1 file)
  • test/cli/init-upgrade-parity.test.ts

This report is generated automatically for every PR. See #733 for details.

@github-actions

github-actions Bot commented Apr 4, 2026 •

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 88e95f1

⚠️ 4 item(s) to address before review

Status Check Details
❌ Single commit 3 commits — consider squashing before review
✅ Not in draft Ready for review
✅ Branch up to date Up to date with dev
❌ Copilot review No Copilot review yet — it may still be processing
✅ Changeset present Changeset file found
✅ Scope clean No .squad/ or docs/proposals/ files
✅ No merge conflicts No merge conflicts
❌ Copilot threads resolved 4 unresolved Copilot thread(s) — fix and resolve before merging
❌ CI passing 16 check(s) still running

This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Vitest parity test suite to prevent drift between squad init and squad upgrade scaffolding, guarding against regressions that would later surface in squad doctor.

Changes:

  • Introduces init-upgrade-parity.test.ts covering directory scaffolding, casting defaults, non-overwrite behavior, and idempotency of ensure helpers.
  • Verifies .gitattributes merge=union rules and repeated ensure calls don’t duplicate entries.

Comment on lines +95 to +101
// Track which dirs init missed so drift is visible in test output.
// Ideally created.length === 0 (init scaffolds everything upgrade expects).
if (created.length > 0) {
console.warn(
`[parity drift] init missed ${created.length} dirs that upgrade ensures: ${created.join(', ')}`,
);
}

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test logs parity drift via console.warn but never fails when drift is detected (created.length > 0). This means init/upgrade directory drift will not be caught by CI. Convert this into an assertion (e.g., expect created to be empty) so the contract is enforced.

Suggested change
// Track which dirs init missed so drift is visible in test output.
// Ideally created.length === 0 (init scaffolds everything upgrade expects).
if (created.length > 0) {
console.warn(
`[parity drift] init missed ${created.length} dirs that upgrade ensures: ${created.join(', ')}`,
);
}
// Enforce parity: init should already scaffold everything upgrade ensures.
expect(
created,
`[parity drift] init missed ${created.length} dirs that upgrade ensures: ${created.join(', ')}`,
).toEqual([]);

Copilot uses AI. Check for mistakes.
'.squad/sessions',
'.squad/decisions/inbox',
'.squad/casting',
'.squad/agents',

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INIT_INFRASTRUCTURE_DIRS claims to be derived from what runInit scaffolds, but it omits at least .squad/plugins/ (see existing init integration tests). If parity is meant to prevent init/upgrade drift, include the full set of init-created infrastructure dirs here (or derive them programmatically) so missing upgrade coverage is actually detected.

Suggested change
'.squad/agents',
'.squad/agents',
'.squad/plugins',

Copilot uses AI. Check for mistakes.
Comment on lines +107 to +128
it('upgrade scaffolds casting directory and init creates casting files', async () => {
await runInit(TEST_ROOT);

const castingDir = join(TEST_ROOT, '.squad', 'casting');
expect(existsSync(castingDir)).toBe(true);

// Init should have created the three casting JSON files
for (const file of CASTING_FILES) {
const filePath = join(castingDir, file);
expect(existsSync(filePath)).toBe(true);

const content = await readFile(filePath, 'utf-8');
expect(() => JSON.parse(content)).not.toThrow();
}

// After upgrade on an already-init'd directory, casting files survive
await runUpgrade(TEST_ROOT);

for (const file of CASTING_FILES) {
expect(existsSync(join(castingDir, file))).toBe(true);
}
});

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test name/comment says it verifies upgrade scaffolds casting defaults, but it only checks that init created the files and that upgrade preserves them. To validate upgrade scaffolding behavior, delete .squad/casting/ (or the JSON files) after init, run runUpgrade, and assert the directory/files are recreated with valid JSON.

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +162
const sentinel = '<!-- USER_SENTINEL -->\n';
if (existsSync(teamPath)) {
await writeFile(teamPath, sentinel);
}

// Write a sentinel casting file
const registryPath = join(TEST_ROOT, '.squad', 'casting', 'registry.json');
const customRegistry = JSON.stringify({ agents: { sentinel: true } });
if (existsSync(registryPath)) {
await writeFile(registryPath, customRegistry);
}

await runUpgrade(TEST_ROOT);

// team.md should still have our sentinel
if (existsSync(teamPath)) {
const teamContent = await readFile(teamPath, 'utf-8');
expect(teamContent).toContain('USER_SENTINEL');
}

// casting/registry.json should still have our custom content
if (existsSync(registryPath)) {
const regContent = await readFile(registryPath, 'utf-8');
expect(regContent).toContain('sentinel');
}

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertions in this test are conditional on team.md and casting/registry.json already existing, so the test can pass without actually verifying upgrade's non-overwrite behavior. Since runInit is expected to create these, assert they exist after init (and fail if they don't) before writing/validating sentinel content.

Suggested change
const sentinel = '<!-- USER_SENTINEL -->\n';
if (existsSync(teamPath)) {
await writeFile(teamPath, sentinel);
}
// Write a sentinel casting file
const registryPath = join(TEST_ROOT, '.squad', 'casting', 'registry.json');
const customRegistry = JSON.stringify({ agents: { sentinel: true } });
if (existsSync(registryPath)) {
await writeFile(registryPath, customRegistry);
}
await runUpgrade(TEST_ROOT);
// team.md should still have our sentinel
if (existsSync(teamPath)) {
const teamContent = await readFile(teamPath, 'utf-8');
expect(teamContent).toContain('USER_SENTINEL');
}
// casting/registry.json should still have our custom content
if (existsSync(registryPath)) {
const regContent = await readFile(registryPath, 'utf-8');
expect(regContent).toContain('sentinel');
}
const registryPath = join(TEST_ROOT, '.squad', 'casting', 'registry.json');
const sentinel = '<!-- USER_SENTINEL -->\n';
const customRegistry = JSON.stringify({ agents: { sentinel: true } });
expect(existsSync(teamPath)).toBe(true);
expect(existsSync(registryPath)).toBe(true);
await writeFile(teamPath, sentinel);
// Write a sentinel casting file
await writeFile(registryPath, customRegistry);
await runUpgrade(TEST_ROOT);
// team.md should still have our sentinel
const teamContent = await readFile(teamPath, 'utf-8');
expect(teamContent).toContain('USER_SENTINEL');
// casting/registry.json should still have our custom content
const regContent = await readFile(registryPath, 'utf-8');
expect(regContent).toContain('sentinel');

Copilot uses AI. Check for mistakes.
Verifies init and upgrade produce equivalent scaffolding.
Tests ENSURE_DIRECTORIES coverage, casting defaults, and .gitattributes.

Closes #822
Related: #817, #823

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@diberry
diberry force-pushed the squad/822-init-upgrade-parity-tests branch from 95cf3ce to 76fce2a Compare April 4, 2026 23:41

@tamirdresher tamirdresher left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — Smart parity tests guarding init/upgrade drift. Sentinel no-overwrite test is clever.

@tamirdresher
tamirdresher merged commit bc02ecb into dev Apr 5, 2026
18 checks passed
@tamirdresher
tamirdresher deleted the squad/822-init-upgrade-parity-tests branch April 5, 2026 06:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(cli): add init→upgrade→doctor parity test suite

3 participants