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
4 changes: 3 additions & 1 deletion .github/agents/squad.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a

**⚠️ CRITICAL RULE: Every agent interaction MUST use the `task` tool to spawn a real agent. You MUST call the `task` tool — never simulate, role-play, or inline an agent's work. If you did not call the `task` tool, the agent was NOT spawned. No exceptions.**

**On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Pass the team root into every spawn prompt as `TEAM_ROOT` and the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work.
**On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Pass the team root into every spawn prompt as `TEAM_ROOT` and the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted.

**⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing).

Expand Down Expand Up @@ -641,6 +641,8 @@ prompt: |

Read .squad/agents/{name}/history.md (your project knowledge).
Read .squad/decisions.md (team decisions to respect).
If .squad/identity/wisdom.md exists, read it before starting work.
If .squad/identity/now.md exists, read it at spawn time.
If .squad/skills/ has relevant SKILL.md files, read them before working.

{only if MCP tools detected — omit entirely if none:}
Expand Down
67 changes: 66 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,15 @@ if (isMigrateDirectory) {
}
}

// Scrub email addresses from migrated files
console.log(`${DIM}Scrubbing email addresses from .squad/ files...${RESET}`);
const scrubbedFiles = scrubEmailsFromDirectory(squadDir);
if (scrubbedFiles.length > 0) {
console.log(`${GREEN}✓${RESET} Scrubbed email addresses from ${scrubbedFiles.length} file(s)`);
} else {
console.log(`${GREEN}✓${RESET} No email addresses found`);
}

console.log();
console.log(`${BOLD}Migration complete.${RESET}`);
console.log(`${DIM}Commit the change:${RESET}`);
Expand Down Expand Up @@ -1039,18 +1048,20 @@ if (squadInfo.isLegacy) {
showDeprecationWarning();
}

// Pre-create drop-box, orchestration-log, casting, skills, and plugins directories (additive-only)
// Pre-create drop-box, orchestration-log, casting, skills, plugins, and identity directories (additive-only)
const inboxDir = path.join(squadInfo.path, 'decisions', 'inbox');
const orchLogDir = path.join(squadInfo.path, 'orchestration-log');
const castingDir = path.join(squadInfo.path, 'casting');
const skillsDir = path.join(squadInfo.path, 'skills');
const pluginsDir = path.join(squadInfo.path, 'plugins');
const identityDir = path.join(squadInfo.path, 'identity');
try {
fs.mkdirSync(inboxDir, { recursive: true });
fs.mkdirSync(orchLogDir, { recursive: true });
fs.mkdirSync(castingDir, { recursive: true });
fs.mkdirSync(skillsDir, { recursive: true });
fs.mkdirSync(pluginsDir, { recursive: true });
fs.mkdirSync(identityDir, { recursive: true });
} catch (err) {
fatal(`Failed to create ${squadInfo.name}/ directories: ${err.message}`);
}
Expand All @@ -1064,6 +1075,51 @@ if (!isUpgrade) {
}
}

// Scaffold identity files (now.md, wisdom.md) — both init and upgrade
const nowMdPath = path.join(identityDir, 'now.md');
const wisdomMdPath = path.join(identityDir, 'wisdom.md');

if (!fs.existsSync(nowMdPath)) {
const nowTemplate = `---
updated_at: ${new Date().toISOString()}
focus_area: Initial setup
active_issues: []
---

# What We're Focused On

Getting started. Updated by coordinator at session start.
`;
fs.mkdirSync(identityDir, { recursive: true });
fs.writeFileSync(nowMdPath, nowTemplate);
console.log(`${GREEN}✓${RESET} ${squadInfo.name}/identity/now.md`);
} else if (isUpgrade) {
console.log(`${DIM}identity/now.md already exists — skipping${RESET}`);
}

if (!fs.existsSync(wisdomMdPath)) {
const wisdomTemplate = `---
last_updated: ${new Date().toISOString()}
---

# Team Wisdom

Reusable patterns and heuristics learned through work. NOT transcripts — each entry is a distilled, actionable insight.

## Patterns

<!-- Append entries below. Format: **Pattern:** description. **Context:** when it applies. -->

## Anti-Patterns

<!-- Things we tried that didn't work. **Avoid:** description. **Why:** reason. -->
`;
fs.mkdirSync(identityDir, { recursive: true });
fs.writeFileSync(wisdomMdPath, wisdomTemplate);
console.log(`${GREEN}✓${RESET} ${squadInfo.name}/identity/wisdom.md`);
} else if (isUpgrade) {
console.log(`${DIM}identity/wisdom.md already exists — skipping${RESET}`);
}

// Create sample MCP config (skip if .copilot/mcp-config.json already exists)
if (!isUpgrade) {
Expand Down Expand Up @@ -1189,6 +1245,15 @@ if (fs.existsSync(workflowsSrc) && fs.statSync(workflowsSrc).isDirectory()) {
}

if (isUpgrade) {
// Scrub email addresses from existing squad directory
console.log(`${DIM}Scrubbing email addresses from ${squadInfo.name}/ files...${RESET}`);
const scrubResult = scrubEmailsFromDirectory(squadInfo.path);
if (scrubResult.filesModified > 0) {
console.log(`${GREEN}✓${RESET} Scrubbed ${scrubResult.replacements} email address(es) from ${scrubResult.filesModified} file(s)`);
} else {
console.log(`${GREEN}✓${RESET} No email addresses found (scanned ${scrubResult.filesScanned} files)`);
}

console.log(`\n${DIM}${squadInfo.name}/ untouched — your team state is safe${RESET}`);

// Hint about new features available after upgrade
Expand Down
9 changes: 9 additions & 0 deletions templates/identity/now.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
updated_at: {timestamp}
focus_area: {brief description}
active_issues: []
---

# What We're Focused On

{Narrative description of current focus — 1-3 sentences. Updated by coordinator at session start.}
15 changes: 15 additions & 0 deletions templates/identity/wisdom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
last_updated: {timestamp}
---

# Team Wisdom

Reusable patterns and heuristics learned through work. NOT transcripts — each entry is a distilled, actionable insight.

## Patterns

<!-- Append entries below. Format: **Pattern:** description. **Context:** when it applies. -->

## Anti-Patterns

<!-- Things we tried that didn't work. **Avoid:** description. **Why:** reason. -->
64 changes: 64 additions & 0 deletions test/init-flow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,67 @@ describe('Init Mode prompt structure (#66)', () => {
});
});
});

describe('Identity layer files (now.md, wisdom.md) — #107', () => {
let tmpDir;

beforeEach(() => {
tmpDir = makeTempDir();
});

afterEach(() => {
cleanDir(tmpDir);
});

it('init creates .squad/identity/now.md', () => {
initSquad(tmpDir);
const nowPath = path.join(tmpDir, '.squad', 'identity', 'now.md');
assert.ok(fs.existsSync(nowPath), '.squad/identity/now.md should exist after init');

const content = fs.readFileSync(nowPath, 'utf8');
assert.ok(content.includes('updated_at:'), 'now.md should contain updated_at field');
assert.ok(content.includes('focus_area:'), 'now.md should contain focus_area field');
assert.ok(content.includes('What We\'re Focused On'), 'now.md should contain heading');
});

it('init creates .squad/identity/wisdom.md', () => {
initSquad(tmpDir);
const wisdomPath = path.join(tmpDir, '.squad', 'identity', 'wisdom.md');
assert.ok(fs.existsSync(wisdomPath), '.squad/identity/wisdom.md should exist after init');

const content = fs.readFileSync(wisdomPath, 'utf8');
assert.ok(content.includes('last_updated:'), 'wisdom.md should contain last_updated field');
assert.ok(content.includes('Team Wisdom'), 'wisdom.md should contain heading');
assert.ok(content.includes('## Patterns'), 'wisdom.md should contain Patterns section');
assert.ok(content.includes('## Anti-Patterns'), 'wisdom.md should contain Anti-Patterns section');
});

it('upgrade creates missing identity files', () => {
// First init
initSquad(tmpDir);

// Modify squad.agent.md to simulate an old version
const agentPath = path.join(tmpDir, '.github', 'agents', 'squad.agent.md');
let agentContent = fs.readFileSync(agentPath, 'utf8');
agentContent = agentContent.replace(/<!-- version: [^>]+ -->/, '<!-- version: 0.4.0 -->');
agentContent = agentContent.replace(/- \*\*Version:\*\* [0-9.]+(?:-[a-z]+)?/, '- **Version:** 0.4.0');
fs.writeFileSync(agentPath, agentContent);

// Delete identity files to simulate old installation
const identityDir = path.join(tmpDir, '.squad', 'identity');
fs.rmSync(identityDir, { recursive: true, force: true });

// Run upgrade
const result = runSquad(['upgrade'], tmpDir);
assert.equal(result.exitCode, 0, `upgrade should succeed: ${result.stdout}`);

// Check files were created - upgrade creates them because directory creation happens every time
const nowPath = path.join(tmpDir, '.squad', 'identity', 'now.md');
const wisdomPath = path.join(tmpDir, '.squad', 'identity', 'wisdom.md');

// The directory is created, and files are scaffolded if missing
assert.ok(fs.existsSync(identityDir), 'upgrade should create identity directory');
assert.ok(fs.existsSync(nowPath), 'upgrade should create missing now.md');
assert.ok(fs.existsSync(wisdomPath), 'upgrade should create missing wisdom.md');
});
});
Loading