@yemi33/squad 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ # Work Item: {{item_name}}
2
+
3
+ > Agent: {{agent_name}} ({{agent_role}}) | ID: {{item_id}} | Priority: {{item_priority}} | Type: {{work_type}}
4
+
5
+ ## Context
6
+
7
+ Repository ID: from `.squad/config.json` under `project.repositoryId`
8
+ Repo: {{repo_name}} | Org: {{ado_org}} | Project: {{ado_project}}
9
+ Team root: {{team_root}}
10
+
11
+ {{scope_section}}
12
+
13
+ ## Task Description
14
+
15
+ {{item_description}}
16
+
17
+ {{additional_context}}
18
+
19
+ ## Branch Naming Convention
20
+ Branch format: `feat/{{item_id}}-<short-description>`
21
+ Keep branch names lowercase, use hyphens, max 60 chars.
22
+
23
+ ## Steps
24
+
25
+ 1. **Understand the task** — read the description carefully, explore relevant code
26
+ 2. **Navigate** to the correct project directory: `{{project_path}}`
27
+ 3. **Create a worktree** for your changes:
28
+ ```bash
29
+ cd {{project_path}}
30
+ git worktree add ../worktrees/feat-{{item_id}} -b feat/{{item_id}}-<short-desc> {{main_branch}}
31
+ cd ../worktrees/feat-{{item_id}}
32
+ ```
33
+ 4. **Implement** the changes
34
+ 5. **Build and verify** — ensure the build passes. If it fails, fix and retry (up to 3 times)
35
+ 6. **Commit and push**:
36
+ ```bash
37
+ git add -A
38
+ git commit -m "feat({{item_id}}): <description>"
39
+ git push -u origin feat/{{item_id}}-<short-desc>
40
+ ```
41
+ 7. **Create a PR:**
42
+ {{pr_create_instructions}}
43
+ - sourceRefName: `refs/heads/feat/{{item_id}}-<short-desc>`
44
+ - targetRefName: `refs/heads/{{main_branch}}`
45
+ - title: `feat({{item_id}}): <description>`
46
+ 8. **Post implementation notes** as a PR thread comment:
47
+ {{pr_comment_instructions}}
48
+ 9. **Add PR to tracker** — append to `{{project_path}}/.squad/pull-requests.json`:
49
+ ```json
50
+ { "id": "PR-<number>", "title": "...", "agent": "{{agent_name}}", "branch": "...", "reviewStatus": "pending", "status": "active", "created": "<date>", "url": "<pr-url>", "prdItems": ["{{item_id}}"] }
51
+ ```
52
+ 10. **Clean up worktree**:
53
+ ```bash
54
+ cd {{project_path}}
55
+ git worktree remove ../worktrees/feat-{{item_id}} --force
56
+ ```
57
+
58
+ ## After Completion
59
+
60
+ Write your findings to: `{{team_root}}/notes/inbox/{{agent_id}}-{{item_id}}-{{date}}.md`
61
+
62
+ **Note:** Do NOT write to `agents/*/status.json` — the engine manages your status automatically.
63
+
64
+ ## Rules
65
+ - NEVER checkout branches in the main working tree — use worktrees
66
+ - Use the repo host's MCP tools for PR creation — check available MCP tools before starting
67
+ - Use PowerShell for build commands on Windows if applicable
68
+ - If you discover a repeatable workflow, save it as a skill: squad-wide at `{{team_root}}/skills/<name>.md`, or project-specific at `<project>/.claude/skills/<name>.md` (requires PR)
69
+
70
+ ## Handling Merge Conflicts
71
+ If you encounter merge conflicts during push or PR creation:
72
+ 1. Resolve conflicts in the worktree, preferring your changes
73
+ 2. Commit the resolution
74
+ 3. Push again
package/routing.md ADDED
@@ -0,0 +1,29 @@
1
+ # Work Routing
2
+
3
+ How the engine decides who handles what. Parsed by engine.js — keep the table format exact.
4
+
5
+ ## Routing Table
6
+
7
+ <!-- FORMAT: | work_type | preferred_agent | fallback | -->
8
+ | Work Type | Preferred | Fallback |
9
+ |-----------|-----------|----------|
10
+ | implement | dallas | ralph |
11
+ | implement:large | rebecca | dallas |
12
+ | review | ripley | lambert |
13
+ | fix | _author_ | dallas |
14
+ | plan-to-prd | lambert | rebecca |
15
+ | explore | ripley | rebecca |
16
+ | test | dallas | ralph |
17
+
18
+ Notes:
19
+ - `_author_` means route to the PR author
20
+ - `implement:large` is for items with `estimated_complexity: "large"`
21
+ - Engine falls back to any idle agent if both preferred and fallback are busy
22
+
23
+ ## Rules
24
+
25
+ 1. **Eager by default** — spawn all agents who can start work, not one at a time
26
+ 2. **No self-review** — author cannot review their own PR
27
+ 3. **Exploration gates implementation** — when exploring, finish before implementing
28
+ 4. **Implementation informs PRD** — Lambert reads build summaries before writing PRD
29
+ 5. **All rules in `notes.md` apply** — engine injects them into every playbook
@@ -0,0 +1,72 @@
1
+ # Squad Skills
2
+
3
+ Agent-discovered reusable workflows, written as Claude Code-compatible skills. Any agent can create a skill when it discovers a repeatable pattern. All agents see the skill index in their system prompt and can invoke them.
4
+
5
+ ## Format
6
+
7
+ Each skill is a markdown file with Claude Code skill frontmatter:
8
+
9
+ ```markdown
10
+ ---
11
+ name: short-descriptive-name
12
+ description: What this skill does and when to use it
13
+ allowed-tools: Bash, Read, Edit, Write, Glob, Grep
14
+ argument-hint: "[optional] [parameter] [hints]"
15
+ trigger: when should an agent use this skill
16
+ author: agent-name
17
+ created: YYYY-MM-DD
18
+ project: project-name or "any"
19
+ ---
20
+
21
+ # Skill: Short Title
22
+
23
+ ## When to Use
24
+ <describe the situation that triggers this workflow>
25
+
26
+ ## Steps
27
+ 1. First step
28
+ 2. Second step
29
+ 3. ...
30
+
31
+ ## Notes
32
+ <gotchas, variations, things to watch out for>
33
+ ```
34
+
35
+ ## Key Fields
36
+
37
+ - `name` — unique identifier (lowercase with hyphens)
38
+ - `description` — plain English description (used by Claude Code for skill discovery)
39
+ - `allowed-tools` — which tools the skill may use (Claude Code permission scoping)
40
+ - `trigger` — when an agent should invoke this skill (injected into prompt index)
41
+ - `author` — which agent created it
42
+ - `project` — which project it applies to, or "any"
43
+
44
+ ## Two Skill Locations
45
+
46
+ ### Squad-wide skills (`~/.squad/skills/`)
47
+ - Shared across all agents and all projects
48
+ - No PR required — agents write directly
49
+ - Best for: cross-project workflows, team conventions, tool patterns
50
+
51
+ ### Project-specific skills (`<project>/.claude/skills/`)
52
+ - Scoped to a single repo, available to anyone working in that repo
53
+ - **Requires a PR** since it modifies the repo (worktree + branch + PR)
54
+ - Best for: repo-specific build steps, test patterns, deployment workflows
55
+ - Automatically available in Claude Code sessions within that project
56
+
57
+ ## How It Works
58
+
59
+ 1. Agent discovers a repeatable pattern during a task
60
+ 2. Agent writes a skill file to the appropriate location:
61
+ - Squad-wide: `~/.squad/skills/<name>.md` (direct write)
62
+ - Project-specific: `<project>/.claude/skills/<name>.md` (via PR)
63
+ 3. Engine detects new skills and adds them to the index
64
+ 4. Index is injected into every agent's system prompt
65
+ 5. Future agents see "Available Skills" and follow them when the trigger matches
66
+ 6. Skills are also compatible with Claude Code's `/skill-name` invocation
67
+
68
+ ## Compatibility
69
+
70
+ Skills use the same frontmatter format as Claude Code skills (`~/.claude/skills/`).
71
+ - Squad-wide skills can be copied to `~/.claude/skills/` for personal use
72
+ - Project-specific skills are already in the Claude Code discovery path
@@ -0,0 +1,18 @@
1
+ ---
2
+ created: 2026-03-13
3
+ author: Dallas
4
+ name: ado-pr-status-fetch
5
+ description: Fetch ADO PR status when az CLI is broken, using git credential manager + curl
6
+ allowed-tools: Bash
7
+ trigger: when az CLI fails with DLL/import errors and you need to query ADO REST API
8
+ scope: squad
9
+ project: any
10
+ ---
11
+
12
+ # Fetch ADO PR Status via Git Credential Manager
13
+
14
+ Use this when `az repos pr show` fails due to broken az CLI (win32file DLL error).
15
+
16
+ ## Steps
17
+
18
+ 1. Get bearer token from git credential manager:
package/squad.js ADDED
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Squad Init — Link a project to the central squad
4
+ *
5
+ * Usage:
6
+ * node squad.js <project-dir> Add a project interactively
7
+ * node squad.js <project-dir> --remove Remove a project
8
+ * node squad.js --list List linked projects
9
+ *
10
+ * This adds the project to ~/.squad/config.json's projects array.
11
+ * The squad engine and dashboard run centrally from ~/.squad/.
12
+ * Each project just needs its own work-items.json and pull-requests.json.
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const readline = require('readline');
18
+ const { execSync } = require('child_process');
19
+
20
+ const SQUAD_HOME = __dirname;
21
+ const CONFIG_PATH = path.join(SQUAD_HOME, 'config.json');
22
+
23
+ function loadConfig() {
24
+ try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); } catch {
25
+ return { projects: [], engine: {}, claude: {}, agents: {} };
26
+ }
27
+ }
28
+
29
+ function saveConfig(config) {
30
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
31
+ }
32
+
33
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
34
+ function ask(q, def) {
35
+ return new Promise(resolve => {
36
+ rl.question(` ${q}${def ? ` [${def}]` : ''}: `, ans => resolve(ans.trim() || def || ''));
37
+ });
38
+ }
39
+
40
+ function autoDiscover(targetDir) {
41
+ const result = { _found: [] };
42
+
43
+ // 1. Detect main branch from git
44
+ try {
45
+ const head = execSync('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || git symbolic-ref HEAD', { cwd: targetDir, encoding: 'utf8', timeout: 5000 }).trim();
46
+ const branch = head.replace('refs/remotes/origin/', '').replace('refs/heads/', '');
47
+ if (branch) { result.mainBranch = branch; result._found.push('main branch'); }
48
+ } catch {}
49
+
50
+ // 2. Detect repo host, org, project, repo name from git remote URL
51
+ try {
52
+ const remoteUrl = execSync('git remote get-url origin', { cwd: targetDir, encoding: 'utf8', timeout: 5000 }).trim();
53
+ if (remoteUrl.includes('github.com')) {
54
+ result.repoHost = 'github';
55
+ // https://github.com/org/repo.git or git@github.com:org/repo.git
56
+ const m = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
57
+ if (m) { result.org = m[1]; result.repoName = m[2]; }
58
+ result._found.push('GitHub remote');
59
+ } else if (remoteUrl.includes('visualstudio.com') || remoteUrl.includes('dev.azure.com')) {
60
+ result.repoHost = 'ado';
61
+ // https://org.visualstudio.com/project/_git/repo or https://dev.azure.com/org/project/_git/repo
62
+ const m1 = remoteUrl.match(/https:\/\/([^.]+)\.visualstudio\.com[^/]*\/([^/]+)\/_git\/([^/\s]+)/);
63
+ const m2 = remoteUrl.match(/https:\/\/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/\s]+)/);
64
+ const m = m1 || m2;
65
+ if (m) { result.org = m[1]; result.project = m[2]; result.repoName = m[3]; }
66
+ result._found.push('Azure DevOps remote');
67
+ }
68
+ } catch {}
69
+
70
+ // 3. Read description from CLAUDE.md first line or README.md first paragraph
71
+ try {
72
+ const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
73
+ if (fs.existsSync(claudeMdPath)) {
74
+ const content = fs.readFileSync(claudeMdPath, 'utf8');
75
+ // Look for a description-like first line or paragraph (skip headings)
76
+ const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
77
+ if (lines[0] && lines[0].length < 200) {
78
+ result.description = lines[0].trim();
79
+ result._found.push('description from CLAUDE.md');
80
+ }
81
+ }
82
+ } catch {}
83
+ if (!result.description) {
84
+ try {
85
+ const readmePath = path.join(targetDir, 'README.md');
86
+ if (fs.existsSync(readmePath)) {
87
+ const content = fs.readFileSync(readmePath, 'utf8').slice(0, 2000);
88
+ const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#') && !l.startsWith('!'));
89
+ if (lines[0] && lines[0].length < 200) {
90
+ result.description = lines[0].trim();
91
+ result._found.push('description from README.md');
92
+ }
93
+ }
94
+ } catch {}
95
+ }
96
+
97
+ // 4. Detect project name
98
+ try {
99
+ const pkgPath = path.join(targetDir, 'package.json');
100
+ if (fs.existsSync(pkgPath)) {
101
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
102
+ if (pkg.name) { result.name = pkg.name.replace(/^@[^/]+\//, ''); result._found.push('name from package.json'); }
103
+ }
104
+ } catch {}
105
+
106
+ return result;
107
+ }
108
+
109
+ async function addProject(targetDir) {
110
+ const target = path.resolve(targetDir);
111
+ if (!fs.existsSync(target)) {
112
+ console.log(` Error: Directory not found: ${target}`);
113
+ process.exit(1);
114
+ }
115
+
116
+ const config = loadConfig();
117
+ if (!config.projects) config.projects = [];
118
+
119
+ // Check if already linked
120
+ const existing = config.projects.find(p => path.resolve(p.localPath) === target);
121
+ if (existing) {
122
+ console.log(` "${existing.name}" is already linked at ${target}`);
123
+ const update = await ask('Update its configuration? (y/N)', 'N');
124
+ if (update.toLowerCase() !== 'y') { rl.close(); return; }
125
+ config.projects = config.projects.filter(p => path.resolve(p.localPath) !== target);
126
+ }
127
+
128
+ console.log('\n Link Project to Squad');
129
+ console.log(' ─────────────────────────────');
130
+ console.log(` Squad home: ${SQUAD_HOME}`);
131
+ console.log(` Project: ${target}\n`);
132
+
133
+ // Auto-discover what we can from the repo
134
+ const detected = autoDiscover(target);
135
+ if (detected._found.length > 0) {
136
+ console.log(` Auto-detected: ${detected._found.join(', ')}\n`);
137
+ }
138
+
139
+ const name = await ask('Project name', detected.name || path.basename(target));
140
+ const description = await ask('Description (what this repo contains/does)', detected.description || '');
141
+ const repoHost = await ask('Repo host (ado/github)', detected.repoHost || 'ado');
142
+ const adoOrg = await ask('Organization', detected.org || '');
143
+ const adoProject = await ask('Project', detected.project || '');
144
+ const repoName = await ask('Repo name', detected.repoName || name);
145
+ const repoId = await ask('Repository ID (GUID, optional)', '');
146
+ const mainBranch = await ask('Main branch', detected.mainBranch || 'main');
147
+
148
+ rl.close();
149
+
150
+ const prUrlBase = repoHost === 'github'
151
+ ? (adoOrg && repoName ? `https://github.com/${adoOrg}/${repoName}/pull/` : '')
152
+ : (adoOrg && adoProject && repoName
153
+ ? `https://${adoOrg}.visualstudio.com/DefaultCollection/${adoProject}/_git/${repoName}/pullrequest/`
154
+ : '');
155
+
156
+ const project = {
157
+ name,
158
+ description,
159
+ localPath: target.replace(/\\/g, '/'),
160
+ repoHost,
161
+ repositoryId: repoId,
162
+ adoOrg,
163
+ adoProject,
164
+ repoName,
165
+ mainBranch,
166
+ prUrlBase,
167
+ workSources: {
168
+ prd: {
169
+ enabled: true,
170
+ path: 'docs/prd-gaps.json',
171
+ itemFilter: { status: ['missing', 'planned'] },
172
+ cooldownMinutes: 30
173
+ },
174
+ pullRequests: {
175
+ enabled: true,
176
+ path: '.squad/pull-requests.json',
177
+ cooldownMinutes: 30
178
+ },
179
+ workItems: {
180
+ enabled: true,
181
+ path: '.squad/work-items.json',
182
+ cooldownMinutes: 0
183
+ }
184
+ }
185
+ };
186
+
187
+ config.projects.push(project);
188
+ saveConfig(config);
189
+
190
+ // Create project-local state files if needed
191
+ const squadDir = path.join(target, '.squad');
192
+ if (!fs.existsSync(squadDir)) fs.mkdirSync(squadDir, { recursive: true });
193
+
194
+ const stateFiles = {
195
+ 'pull-requests.json': '[]',
196
+ 'work-items.json': '[]',
197
+ };
198
+ for (const [f, content] of Object.entries(stateFiles)) {
199
+ const fp = path.join(squadDir, f);
200
+ if (!fs.existsSync(fp)) fs.writeFileSync(fp, content);
201
+ }
202
+
203
+ console.log(`\n Linked "${name}" (${target})`);
204
+ console.log(` Total projects: ${config.projects.length}`);
205
+ console.log(`\n Project state files created in ${squadDir}`);
206
+ console.log(`\n Start the squad from anywhere:`);
207
+ console.log(` node ${SQUAD_HOME}/engine.js # Engine`);
208
+ console.log(` node ${SQUAD_HOME}/dashboard.js # Dashboard`);
209
+ console.log(` node ${SQUAD_HOME}/engine.js status # Status\n`);
210
+ }
211
+
212
+ function removeProject(targetDir) {
213
+ const target = path.resolve(targetDir);
214
+ const config = loadConfig();
215
+ const before = (config.projects || []).length;
216
+ config.projects = (config.projects || []).filter(p => path.resolve(p.localPath) !== target);
217
+ const after = config.projects.length;
218
+
219
+ if (before === after) {
220
+ console.log(` No project linked at ${target}`);
221
+ } else {
222
+ saveConfig(config);
223
+ console.log(` Removed project at ${target}`);
224
+ console.log(` Remaining projects: ${config.projects.length}`);
225
+ }
226
+ }
227
+
228
+ function listProjects() {
229
+ const config = loadConfig();
230
+ const projects = config.projects || [];
231
+ console.log(`\n Squad Projects (${projects.length})\n`);
232
+ if (projects.length === 0) {
233
+ console.log(' No projects linked. Run: node squad.js <project-dir>\n');
234
+ return;
235
+ }
236
+ for (const p of projects) {
237
+ const exists = fs.existsSync(p.localPath);
238
+ console.log(` ${p.name}`);
239
+ if (p.description) console.log(` Desc: ${p.description}`);
240
+ console.log(` Path: ${p.localPath} ${exists ? '' : '(NOT FOUND)'}`);
241
+ console.log(` Repo: ${p.adoOrg}/${p.adoProject}/${p.repoName} (${p.repoHost || 'ado'})`);
242
+ console.log(` ID: ${p.repositoryId || 'none'}`);
243
+ console.log('');
244
+ }
245
+ }
246
+
247
+ // ─── CLI ─────────────────────────────────────────────────────────────────────
248
+
249
+ const [cmd, ...rest] = process.argv.slice(2);
250
+
251
+ function initSquad() {
252
+ const config = loadConfig();
253
+ if (!config.projects) config.projects = [];
254
+ if (!config.engine) config.engine = { tickInterval: 60000, staleThreshold: 1800000, maxConcurrent: 3, inboxConsolidateThreshold: 5, agentTimeout: 600000, maxTurns: 100 };
255
+ if (!config.claude) config.claude = { binary: 'claude', outputFormat: 'json', allowedTools: 'Edit,Write,Read,Bash,Glob,Grep,Agent,WebFetch,WebSearch' };
256
+ if (!config.agents || Object.keys(config.agents).length === 0) {
257
+ config.agents = {
258
+ ripley: { name: 'Ripley', emoji: '🏗️', role: 'Lead / Explorer', skills: ['architecture', 'codebase-exploration', 'design-review'] },
259
+ dallas: { name: 'Dallas', emoji: '🔧', role: 'Engineer', skills: ['implementation', 'typescript', 'docker', 'testing'] },
260
+ lambert: { name: 'Lambert', emoji: '📊', role: 'Analyst', skills: ['gap-analysis', 'requirements', 'documentation'] },
261
+ rebecca: { name: 'Rebecca', emoji: '🧠', role: 'Architect', skills: ['system-design', 'api-design', 'scalability', 'implementation'] },
262
+ ralph: { name: 'Ralph', emoji: '⚙️', role: 'Engineer', skills: ['implementation', 'bug-fixes', 'testing', 'scaffolding'] },
263
+ };
264
+ }
265
+ saveConfig(config);
266
+ console.log(`\n Squad initialized at ${SQUAD_HOME}`);
267
+ console.log(` Projects: ${config.projects.length}`);
268
+ console.log(`\n Add a project:`);
269
+ console.log(` node squad add <project-dir>\n`);
270
+ }
271
+
272
+ const commands = {
273
+ init: () => initSquad(),
274
+ add: () => {
275
+ const dir = rest[0];
276
+ if (!dir) { console.log('Usage: node squad add <project-dir>'); process.exit(1); }
277
+ addProject(dir).catch(e => { console.error(e); process.exit(1); });
278
+ },
279
+ remove: () => {
280
+ const dir = rest[0];
281
+ if (!dir) { console.log('Usage: node squad remove <project-dir>'); process.exit(1); }
282
+ removeProject(dir);
283
+ },
284
+ list: () => listProjects(),
285
+ };
286
+
287
+ if (cmd && commands[cmd]) {
288
+ commands[cmd]();
289
+ } else {
290
+ console.log('\n Squad — Central AI dev team manager\n');
291
+ console.log(' Usage: node squad <command>\n');
292
+ console.log(' Commands:');
293
+ console.log(' init Initialize squad (no projects)');
294
+ console.log(' add <project-dir> Link a project');
295
+ console.log(' remove <project-dir> Unlink a project');
296
+ console.log(' list List linked projects\n');
297
+ console.log(' After init, also use:');
298
+ console.log(' node ~/.squad/engine.js Start engine');
299
+ console.log(' node ~/.squad/dashboard.js Start dashboard\n');
300
+ }
package/team.md ADDED
@@ -0,0 +1,19 @@
1
+ # Squad Team
2
+
3
+ Roster defined in `config.json`. See individual charters for details.
4
+
5
+ | Name | Role | Charter |
6
+ |------|------|---------|
7
+ | Ripley | Lead / Explorer | `agents/ripley/charter.md` |
8
+ | Dallas | Engineer | `agents/dallas/charter.md` |
9
+ | Lambert | Analyst | `agents/lambert/charter.md` |
10
+ | Rebecca | Architect | `agents/rebecca/charter.md` |
11
+ | Ralph | Engineer | `agents/ralph/charter.md` |
12
+
13
+ ## Project Context
14
+
15
+ Project details (name, stack, repo host, etc.) are configured per-project in `config.json` and each project's `.squad/` folder. Run `node squad.js list` to see linked projects.
16
+
17
+ ## Rules
18
+
19
+ See `notes.md` for all team rules and constraints.