diff --git a/.github/workflows/squad-docs.yml b/.github/workflows/squad-docs.yml
new file mode 100644
index 000000000..307d502c5
--- /dev/null
+++ b/.github/workflows/squad-docs.yml
@@ -0,0 +1,50 @@
+name: Squad Docs — Build & Deploy
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [preview]
+ paths:
+ - 'docs/**'
+ - '.github/workflows/squad-docs.yml'
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+
+ - name: Install build dependencies
+ run: npm install --no-save markdown-it markdown-it-anchor
+
+ - name: Build docs site
+ run: node docs/build.js --out _site --base /squad
+
+ - name: Upload Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: _site
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/squad-main-guard.yml b/.github/workflows/squad-main-guard.yml
index 9f7f65117..cc927f78a 100644
--- a/.github/workflows/squad-main-guard.yml
+++ b/.github/workflows/squad-main-guard.yml
@@ -36,16 +36,15 @@ jobs:
}
// Check each file against forbidden path rules
+ // Allow removals — deleting forbidden files from protected branches is fine
const forbidden = files
+ .filter(f => f.status !== 'removed')
.map(f => f.filename)
.filter(f => {
// .ai-team/** — ALL team state files, zero exceptions
if (f === '.ai-team' || f.startsWith('.ai-team/')) return true;
- // team-docs/** EXCEPT team-docs/blog/** — blog content is allowed
- if (f.startsWith('team-docs/')) {
- if (f.startsWith('team-docs/blog/')) return false;
- return true;
- }
+ // team-docs/** — ALL internal team docs, zero exceptions
+ if (f.startsWith('team-docs/')) return true;
return false;
});
@@ -60,7 +59,7 @@ jobs:
'',
'The following files must NOT be merged into `main`.',
'`.ai-team/` is runtime team state — it belongs on dev branches only.',
- '`team-docs/` internal content should stay on dev (only `team-docs/blog/` is allowed on main).',
+ '`team-docs/` is internal team content — it belongs on dev branches only.',
'',
'### Forbidden files found:',
'',
@@ -72,17 +71,17 @@ jobs:
'# Remove tracked .ai-team/ files (keeps local copies):',
'git rm --cached -r .ai-team/',
'',
- '# Remove tracked team-docs/ files (except blog/):',
+ '# Remove tracked team-docs/ files:',
'git rm --cached -r team-docs/',
- 'git checkout HEAD -- team-docs/blog/',
'',
'# Commit the removal and push:',
'git commit -m "chore: remove forbidden paths from PR"',
'git push',
'```',
'',
- '> ⚠️ `.ai-team/` is in `.gitignore` but files previously force-added with `git add -f`',
- '> remain tracked. `git rm --cached` untracks them without deleting your local copies.',
+ '> ⚠️ `.ai-team/` is committed on `dev` and feature branches by design.',
+ '> The guard workflow is the enforcement mechanism that keeps these files off `main` and `preview`.',
+ '> `git rm --cached` untracks them from this PR without deleting your local copies.',
];
core.setFailed(lines.join('\n'));
diff --git a/.gitignore b/.gitignore
index 712e81311..d1cfda57c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,11 +10,13 @@ Thumbs.db
# Node (if we add tooling later)
node_modules/
-# Squad team state (created at runtime, never committed)
-# ⚠️ NEVER force-add these files (git add -f .ai-team/).
-# Previously force-added files remain tracked even with this rule.
-# If tracked files leak in, remove them: git rm --cached -r .ai-team/
-.ai-team/
+# Built docs site (generated, not committed)
+_site/
+
+# Squad team state — committed on dev and feature branches,
+# blocked from main/preview by squad-main-guard.yml workflow.
+# ⚠️ NEVER merge .ai-team/ into main or preview.
+# The guard workflow will fail the PR if you try.
# Squad Squad templates (internal, not distributed)
.ai-team-templates/
diff --git a/.npmignore b/.npmignore
index 2099314bf..9aae519b2 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,6 +1,6 @@
# Squad Squad — team state and development files
# These are excluded from the distributed package.
-# The product ships: index.js, templates/, docs/, CHANGELOG.md, .github/agents/squad.agent.md
+# The product ships: index.js, templates/, CHANGELOG.md, .github/agents/squad.agent.md
# (also enforced by package.json "files" field)
# Squad Squad (the AI team that builds Squad)
@@ -10,13 +10,20 @@
# Internal team docs (proposals, sprint plans — never shipped)
team-docs/
+# Public docs (read on GitHub, not needed at runtime)
+docs/
+
# Tests
test/
# Git/repo config not needed at runtime
+.gitignore
.gitattributes
.github/workflows/
+# Repo contributor docs (not needed by users)
+CONTRIBUTING.md
+
# Editor / OS
.vscode/
.DS_Store
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e047ffdb1..389e93d83 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -62,8 +62,8 @@ Squad uses a three-tier branch structure to protect production and staging while
| Branch | Purpose | Protection | Files Allowed |
|--------|---------|------------|---------------|
-| **`dev`** | Development & integration | None | ✅ Everything |
-| **`feature/squad/{issue}-{slug}`** | Feature work | None — merge to dev | ✅ Everything |
+| **`dev`** | Development & integration | None | ✅ Everything (including `.ai-team/`) |
+| **`feature/squad/{issue}-{slug}`** | Feature work | None — merge to dev | ✅ Everything (including `.ai-team/`) |
| **`preview`** | Staging & release candidate | Guard checks for `.ai-team/`, `team-docs/` (except blog/) | ✅ Most files — see [Protected Files](#whats-protected) |
| **`main`** | Production & releases | Guard checks for `.ai-team/`, `team-docs/` (except blog/) | ✅ Most files — see [Protected Files](#whats-protected) |
@@ -97,15 +97,14 @@ git push origin squad/42-auth-refresh
### 🚫 CRITICAL: Files Blocked from `main` and `preview`
-These files are **runtime team state** and belong on development branches only:
+These files are **runtime team state** and live on `dev` and feature branches. They are committed to git and flow freely between `dev` and feature branches, but the **guard workflow** (`squad-main-guard.yml`) blocks them from reaching `main` or `preview`:
-| Path | Reason | Merged to main? |
-|------|--------|-----------------|
-| **`.ai-team/**`** | Agent charters, routing, decisions, history, casting registry | ❌ NEVER |
-| **`team-docs/` (except `team-docs/blog/`** | Internal team documentation, sprint plans, notes | ❌ NEVER |
-| **`team-docs/blog/**`** | Public blog content | ✅ YES — blog posts are public |
+| Path | Reason | Committed to `dev`? | Merged to `main`/`preview`? |
+|------|--------|---------------------|---------------------------|
+| **`.ai-team/**`** | Agent charters, routing, decisions, history, casting registry | ✅ YES | ❌ NEVER — guard blocks |
+| **`team-docs/**`** | Internal team documentation, sprint plans, notes | ✅ YES | ❌ NEVER — guard blocks |
-**Why?** `.ai-team/` contains persistent agent knowledge, routing rules, and decision history. It's internal infrastructure. If it leaks to `main`, you're shipping developer metadata as product. `team-docs/` is working notes — only blog content is publication-ready.
+**Why?** `.ai-team/` contains persistent agent knowledge, routing rules, and decision history. `team-docs/` contains internal proposals, sprint plans, and working notes. Both are internal infrastructure that belongs on development branches — not in production. The guard workflow is the enforcement mechanism, not `.gitignore`. `.ai-team/` is NOT in `.gitignore` — it's a normal part of the `dev` branch. The `.npmignore` file ensures both are excluded from the published npm package. Blog posts live in `docs/blog/` and flow freely to all branches.
### ✅ Files That Flow Freely
@@ -114,10 +113,9 @@ These files move between `dev` → `preview` → `main` with no restrictions:
- `index.js` — CLI entry point
- `squad.agent.md` — Squad coordinator
- `templates/` — Agent templates
-- `docs/` — Public documentation
+- `docs/` — Public documentation (including `docs/blog/`)
- `test/` — Test suite
- `.github/workflows/` — GitHub Actions workflows
-- `team-docs/blog/` — Blog posts
- `package.json` — Dependencies
- `README.md`, `LICENSE` — Project metadata
- `CHANGELOG.md` — Release history
@@ -171,19 +169,18 @@ If you accidentally (or intentionally) target `preview` or `main`, the **guard w
If the guard blocks your PR because it contains `.ai-team/` or `team-docs/` files:
```bash
-# Remove .ai-team/ from git (keeps local copies safe)
+# Remove .ai-team/ from this PR (keeps local copies and dev branch copies safe)
git rm --cached -r .ai-team/
-# Remove team-docs/ except blog/
+# Remove team-docs/ from this PR
git rm --cached -r team-docs/
-git checkout HEAD -- team-docs/blog/
# Commit and push
git commit -m "chore: remove internal team files from PR"
git push
```
-The workflow will re-run and pass. Your local `.ai-team/` and `team-docs/` files remain untouched.
+The workflow will re-run and pass. Your local `.ai-team/` and `team-docs/` files remain untouched, and they continue to exist on `dev` normally.
---
@@ -289,18 +286,18 @@ You don't need to add these yourself — the Lead will triage and label issues.
```
squad/
-├── .ai-team/ 🚫 BLOCKED from main/preview
-│ ├── agents/
-│ │ ├── {name}/charter.md 🚫 BLOCKED
-│ │ └── {name}/history.md 🚫 BLOCKED
-│ ├── team.md 🚫 BLOCKED
-│ ├── routing.md 🚫 BLOCKED
-│ ├── decisions.md 🚫 BLOCKED
+├── .ai-team/ ✅ Committed on dev & feature branches
+│ ├── agents/ 🚫 Guard blocks from main/preview
+│ │ ├── {name}/charter.md 🚫 Guard blocks from main/preview
+│ │ └── {name}/history.md 🚫 Guard blocks from main/preview
+│ ├── team.md 🚫 Guard blocks from main/preview
+│ ├── routing.md 🚫 Guard blocks from main/preview
+│ ├── decisions.md 🚫 Guard blocks from main/preview
│ └── ...
│
-├── team-docs/ 🚫 BLOCKED (except blog/)
-│ ├── sprint-plan.md 🚫 BLOCKED
-│ ├── roadmap.md 🚫 BLOCKED
+├── team-docs/ 🚫 Guard blocks from main/preview (except blog/)
+│ ├── sprint-plan.md 🚫 Guard blocks from main/preview
+│ ├── roadmap.md 🚫 Guard blocks from main/preview
│ └── blog/ ✅ ALLOWED (public content)
│ └── 001-launch.md ✅ ALLOWED
│
@@ -336,13 +333,13 @@ When you open a PR to `main` or `preview`, the workflow `.github/workflows/squad
1. **Fetches all files changed in your PR** (paginated for large PRs)
2. **Checks each file against forbidden path rules:**
- If filename starts with `.ai-team/` → BLOCKED
- - If filename starts with `team-docs/` AND not `team-docs/blog/` → BLOCKED
+ - If filename starts with `team-docs/` → BLOCKED
- Otherwise → ALLOWED
3. **Reports results:**
- ✅ **Pass:** "No forbidden paths found" — you're good to merge
- ❌ **Fail:** Lists forbidden files and shows `git rm --cached` fix
-The guard is **not a suggestion** — it's a hard stop. But it's easy to fix if it blocks you (see [Fixing a Blocked PR](#fixing-a-blocked-pr)).
+The guard is **not a suggestion** — it's a hard stop. This is the primary enforcement mechanism that keeps `.ai-team/` and internal `team-docs/` off `main` and `preview`. But it's easy to fix if it blocks you (see [Fixing a Blocked PR](#fixing-a-blocked-pr)).
---
@@ -350,12 +347,14 @@ The guard is **not a suggestion** — it's a hard stop. But it's easy to fix if
### Q: I accidentally committed `.ai-team/` to my feature branch. Do I have to delete it?
-**A:** No! `.ai-team/` is in `.gitignore`, so it won't push to GitHub unless you force-add it. Just keep your feature branch clean:
+**A:** Nope — `.ai-team/` files are **supposed** to be committed on `dev` and feature branches! They're part of the normal development workflow. The guard workflow (`squad-main-guard.yml`) prevents them from reaching `main` or `preview`. Just don't PR them to those branches.
+
+If you're creating a PR to `main` or `preview` and the guard blocks it, remove the files from that PR only:
```bash
-git rm --cached -r .ai-team/ # Untrack if force-added
-git checkout dev # Switch back to dev
-# Your local .ai-team/ remains untouched
+git rm --cached -r .ai-team/ # Untrack from this PR
+git commit -m "chore: remove .ai-team/ from release PR"
+git push
```
### Q: Can I PR to `main` directly?
@@ -364,11 +363,11 @@ git checkout dev # Switch back to dev
### Q: The guard blocked my PR. What now?
-**A:** Your PR contains `.ai-team/` or `team-docs/` files that shouldn't be in `main`/`preview`. Follow [Fixing a Blocked PR](#fixing-a-blocked-pr) — it's three `git rm --cached` commands and a push. The workflow will re-run and pass.
+**A:** Your PR targets `main` or `preview` and contains `.ai-team/` or `team-docs/` files. These files live on `dev` and feature branches but must not reach production. Follow [Fixing a Blocked PR](#fixing-a-blocked-pr) — it's three `git rm --cached` commands and a push. The workflow will re-run and pass.
### Q: I want to commit `team-docs/sprint-plan.md` — can I do that?
-**A:** Not to `main` or `preview` — it's internal. Commit it to `dev` and feature branches, and the guard will block it if you accidentally PR it to `main`. If it's public content (blog, guides, etc.), put it in `team-docs/blog/` and it flows freely.
+**A:** Not to `main` or `preview` — it's internal. Commit it to `dev` and feature branches, and the guard will block it if you accidentally PR it to `main`. If it's public content (blog, guides, etc.), put it in `docs/blog/` and it flows freely.
### Q: What if I disagree with the branch protection?
@@ -389,9 +388,9 @@ Welcome aboard. Make Squad better. 🚀
## Summary: What You Need to Know
1. **Clone from `dev`, create `squad/{issue}-{slug}` branch, PR back to `dev`**
-2. **Never commit `.ai-team/` or internal `team-docs/` to `main` or `preview` — the guard will block it**
+2. **`.ai-team/` files are committed on `dev` and feature branches — the guard workflow blocks them from `main`/`preview`**
3. **Run `npm test` before pushing**
4. **Follow conventional commits (feat:, fix:, docs:, etc.)**
-5. **If the guard blocks your PR, run `git rm --cached` and push again**
+5. **If the guard blocks your PR to `main`/`preview`, run `git rm --cached` and push again**
That's it. Happy contributing.
diff --git a/docs/assets/script.js b/docs/assets/script.js
new file mode 100644
index 000000000..39b6a597a
--- /dev/null
+++ b/docs/assets/script.js
@@ -0,0 +1,41 @@
+(function(){
+ var saved=localStorage.getItem("squad-theme");
+ if(saved) document.documentElement.setAttribute("data-theme",saved);
+ updateThemeBtn();
+})();
+function toggleTheme(){
+ var html=document.documentElement;
+ var c=html.getAttribute("data-theme");
+ var n=c==="dark"?"light":c==="light"?"auto":"dark";
+ html.setAttribute("data-theme",n);
+ localStorage.setItem("squad-theme",n);
+ updateThemeBtn();
+}
+function updateThemeBtn(){
+ var t=document.documentElement.getAttribute("data-theme");
+ var b=document.getElementById("theme-btn");
+ if(!b) return;
+ b.textContent=t==="dark"?"\u2600\uFE0F":t==="light"?"\uD83C\uDF19":"\uD83D\uDCBB";
+}
+function toggleSidebar(){
+ document.getElementById("sidebar").classList.toggle("open");
+}
+(function(){
+ var input=document.getElementById("search");
+ var results=document.getElementById("search-results");
+ function esc(s){ return s.replace(/&/g,"&").replace(//g,">"); }
+ input.addEventListener("input",function(){
+ var q=this.value.toLowerCase().trim();
+ if(q.length<2){ results.classList.remove("visible"); return; }
+ var matches=searchIndex.filter(function(e){
+ return e.title.toLowerCase().indexOf(q)!==-1 || e.preview.toLowerCase().indexOf(q)!==-1;
+ }).slice(0,10);
+ if(!matches.length){ results.innerHTML='