@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.
- package/LICENSE +21 -0
- package/README.md +483 -0
- package/TODO.md +60 -0
- package/agents/dallas/charter.md +55 -0
- package/agents/lambert/charter.md +66 -0
- package/agents/ralph/charter.md +44 -0
- package/agents/rebecca/charter.md +56 -0
- package/agents/ripley/charter.md +46 -0
- package/bin/squad.js +164 -0
- package/config.template.json +53 -0
- package/dashboard.html +1680 -0
- package/dashboard.js +886 -0
- package/docs/auto-discovery.md +414 -0
- package/docs/blog-first-successful-dispatch.md +127 -0
- package/docs/self-improvement.md +277 -0
- package/engine/ado-mcp-wrapper.js +49 -0
- package/engine/spawn-agent.js +98 -0
- package/engine.js +3416 -0
- package/package.json +46 -0
- package/playbooks/build-and-test.md +155 -0
- package/playbooks/explore.md +63 -0
- package/playbooks/fix.md +57 -0
- package/playbooks/implement.md +84 -0
- package/playbooks/plan-to-prd.md +74 -0
- package/playbooks/review.md +68 -0
- package/playbooks/test.md +75 -0
- package/playbooks/work-item.md +74 -0
- package/routing.md +29 -0
- package/skills/README.md +72 -0
- package/skills/ado-pr-status-fetch.md +18 -0
- package/squad.js +300 -0
- package/team.md +19 -0
|
@@ -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
|
package/skills/README.md
ADDED
|
@@ -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.
|