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
16 changes: 16 additions & 0 deletions .squad/agents/eecom/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,19 @@ Reviewed and merged PR #486 (two-layer signal handling + 22 tests). Improves gra
### Session 2 Summary (2026-03-22)

Executed 3 tasks across 2 waves: economy mode (#500, PR #504), node:sqlite fix (#502, PR #506), rate limit UX (#464, PR #505). All PRs merged to dev.


### Personal Squad Init via npx (#576) (2026-03-23)

**Context:** `init --global` (used via npx to set up personal squad) created a full `.squad/` structure at `~/.config/squad/` but never created the `personal-squad/` subdirectory. `resolvePersonalSquadDir()` looks for `personal-squad/`, so subsequent repo-level `init` couldn't discover the user's personal agents.

**Root cause:** Two separate concepts - `init --global` scaffolds a full squad, `personal init` creates `personal-squad/`. The `--global` flag never bridged between them.

**Fix:**
1. `resolution.ts` - Added `ensurePersonalSquadDir()` idempotent helper to SDK.
2. `cli-entry.ts` - `init --global` now suppresses workflows and passes `isGlobal` flag.
3. `init.ts` - After global init, calls `ensurePersonalSquadDir()`. After repo init, detects personal squad.
4. `personal.ts` - Refactored to reuse `ensurePersonalSquadDir()`.
5. `resolution.test.ts` - Added 3 tests.

**Pattern:** `resolveGlobalSquadPath()` returns the container; `ensurePersonalSquadDir()` creates the subdirectory the rest of the system looks for.
20 changes: 20 additions & 0 deletions .squad/agents/fido/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,23 @@ Pattern: Tests follow existing `test/cli/init.test.ts` and `test/cli/doctor.test

Commit: 7660a27 on branch squad/579-init-scaffolding-hardening.

### Personal Squad Init Discovery Tests (#576)

**Task:** Write tests for personal squad discovery and init flows (Issue #576 — npx init --global not discovering personal squad).

**Test file:** `test/personal-squad-init.test.ts` — 35 tests, 10 describe blocks, all passing.

**Coverage areas:**
1. `resolveGlobalSquadPath()` — platform-specific path resolution (Windows APPDATA, Linux XDG_CONFIG_HOME, consistency)
2. `resolvePersonalSquadDir()` — kill-switch (SQUAD_NO_PERSONAL), directory existence, npx-agnostic discovery
3. `personalInit` contract — directory structure creation, config.json shape, idempotency
4. `resolveSquadPaths()` — personalDir field inclusion, null when disabled
5. Edge: empty personal-squad dir (exists but no agents/)
6. Edge: partial state (agent dirs without charter.md, missing Role metadata defaults to "personal", stray files skipped)
7. `mergeSessionCast()` — project-wins precedence, case-insensitive collision, empty inputs
8. `ensureSquadPathTriple()` — personal dir in allowed roots, null personalDir graceful handling
9. Charter metadata parsing edge cases (whitespace trimming, sourceDir correctness, multi-agent discovery)

**Key finding:** `resolvePersonalSquadDir()` is install-method-agnostic — it resolves from env vars and `os.homedir()`, never from `process.argv`. The npx issue (#576) is therefore NOT in path resolution but likely in the CLI command wiring or the `--global` flag routing. Tests confirm the SDK layer works correctly.

**Commit:** c307187 on branch squad/576-personal-squad-init-npx
6 changes: 4 additions & 2 deletions packages/squad-cli/src/cli-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,11 +271,13 @@ async function main(): Promise<void> {
return;
}

const dest = hasGlobal ? (await lazySquadSdk()).resolveGlobalSquadPath() : process.cwd();
const sdkMod = hasGlobal ? await lazySquadSdk() : null;
const dest = hasGlobal ? sdkMod!.resolveGlobalSquadPath() : process.cwd();
const noWorkflows = args.includes('--no-workflows');
const sdk = args.includes('--sdk');
const roles = args.includes('--roles');
runInit(dest, { includeWorkflows: !noWorkflows, sdk, roles }).catch(err => {
// Global init: suppress workflows (no GitHub CI in ~/.config/squad/) and bootstrap personal squad
runInit(dest, { includeWorkflows: !noWorkflows && !hasGlobal, sdk, roles, isGlobal: hasGlobal }).catch(err => {
fatal(err.message);
});
return;
Expand Down
16 changes: 3 additions & 13 deletions packages/squad-cli/src/cli/commands/personal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import fs from 'node:fs';
import path from 'node:path';
import { resolveGlobalSquadPath, resolvePersonalSquadDir } from '@bradygaster/squad-sdk/resolution';
import { resolveGlobalSquadPath, resolvePersonalSquadDir, ensurePersonalSquadDir } from '@bradygaster/squad-sdk/resolution';
import { resolvePersonalAgents } from '@bradygaster/squad-sdk/agents/personal';
import { success, warn, info, BOLD, RESET, DIM } from '../core/output.js';
import { fatal } from '../core/errors.js';
Expand Down Expand Up @@ -68,20 +68,10 @@ async function personalInit(): Promise<void> {
return;
}

// Create directory structure
const agentsDir = path.join(personalDir, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });

// Create config.json
const config = {
defaultModel: 'auto',
ghostProtocol: true,
};
const configPath = path.join(personalDir, 'config.json');
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
const created = ensurePersonalSquadDir();

success('Personal squad initialized');
info(` Path: ${personalDir}`);
info(` Path: ${created}`);
info(` Add agents with: squad personal add <name> --role <role>`);
}

Expand Down
20 changes: 19 additions & 1 deletion packages/squad-cli/src/cli/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { success, BOLD, RESET, YELLOW, GREEN, DIM } from './output.js';
import { fatal } from './errors.js';
import { detectProjectType } from './project-type.js';
import { getPackageVersion, stampVersion } from './version.js';
import { initSquad as sdkInitSquad, cleanupOrphanInitPrompt, type InitOptions } from '@bradygaster/squad-sdk';
import { initSquad as sdkInitSquad, cleanupOrphanInitPrompt, ensurePersonalSquadDir, resolvePersonalSquadDir, type InitOptions } from '@bradygaster/squad-sdk';

const CYAN = '\x1b[36m';

Expand Down Expand Up @@ -104,6 +104,8 @@ export interface RunInitOptions {
sdk?: boolean;
/** If true, use built-in base roles instead of fictional universe casting (default: false) */
roles?: boolean;
/** If true, this is a global (personal squad) init — bootstrap personal-squad/ dir */
isGlobal?: boolean;
}

/**
Expand Down Expand Up @@ -296,6 +298,22 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi
console.log(`${GREEN}${BOLD}Your team is ready.${RESET} Run ${CYAN}${BOLD}squad${RESET} to start.`);
console.log();

// ── Personal squad bridge ───────────────────────────────────────────
if (options.isGlobal) {
// Global init: ensure personal-squad/ directory exists alongside .squad/
const personalDir = ensurePersonalSquadDir();
console.log(`${GREEN}${BOLD}✓${RESET} Personal squad initialized at ${DIM}${personalDir}${RESET}`);
console.log(`${DIM} Add agents with: squad personal add <name> --role <role>${RESET}`);
console.log();
} else {
// Repo init: inform user if personal squad is available
const personalDir = resolvePersonalSquadDir();
if (personalDir) {
console.log(`${GREEN}${BOLD}✓${RESET} Personal squad detected — your personal agents will be available here.`);
console.log();
}
}

if (squadInfo.isLegacy) {
showDeprecationWarning();
}
Expand Down
Loading
Loading