Skip to content

Commit 2b91f69

Browse files
authored
Merge pull request #198 from jsturtevant/fix-consult-mode
feat: wire up consult mode CLI + fix global squad resolution (#196)
2 parents 2fe5839 + 4513eb6 commit 2b91f69

13 files changed

Lines changed: 485 additions & 16 deletions

File tree

‎docs/features/consult-mode.md‎

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Consult Mode
2+
3+
> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases.
4+
5+
6+
Consult mode lets you bring your personal squad to projects you don't own — OSS contributions, client work, temporary collaborations — without leaving any trace. Your team consults, does the work, learns things, and returns home with only the generic learnings you approve.
7+
8+
---
9+
10+
## The Problem
11+
12+
You have a personal squad at your global path (e.g., `~/.config/squad/.squad` on Linux) with agents, skills, and decisions refined over time. When you contribute to someone else's project, you face a dilemma:
13+
14+
- **Pollute the project?** Running `squad init` creates a `.squad/` folder they didn't ask for
15+
- **Pollute your squad?** Project-specific knowledge bleeds into your global squad
16+
- **Work without your team?** Lose the productivity benefits you've built up
17+
18+
---
19+
20+
## The Solution
21+
22+
Your team **consults** on a project. They bring their expertise, do the work, and learn things. When done, they extract what's reusable and return home. The project never knows Squad was there.
23+
24+
| Aspect | Normal Mode | Consult Mode |
25+
|--------|-------------|--------------|
26+
| Squad location | `.squad/` in project | **Copy** of personal squad into project `.squad/` |
27+
| Git visibility | Committed or `.gitignore` | Invisible via `.git/info/exclude` |
28+
| Writes go to | Project `.squad/` | Project `.squad/` (isolated copy) |
29+
| After session | Stays in project | Extract generic learnings → personal squad, discard rest |
30+
31+
---
32+
33+
## Quick Start
34+
35+
### OSS Contribution
36+
37+
```bash
38+
cd ~/projects/kubernetes-dashboard
39+
squad consult # Enter consult mode
40+
# ... do your work with your squad ...
41+
squad extract # Review and extract generic learnings
42+
squad extract --clean --yes # Clean up after extraction
43+
```
44+
45+
### Client Work
46+
47+
```bash
48+
cd ~/client-projects/acme-corp
49+
squad consult # Enter consult mode
50+
# ... work on the project ...
51+
squad extract --dry-run # Preview what would be extracted
52+
squad extract --clean # Extract and clean up (prompts for confirmation)
53+
```
54+
55+
### Check Status
56+
57+
```bash
58+
squad consult --status # See if consult mode is active
59+
squad consult --check # Dry-run: show what would happen
60+
```
61+
62+
---
63+
64+
## Command Reference
65+
66+
### `squad consult`
67+
68+
Enter consult mode with your personal squad.
69+
70+
```bash
71+
squad consult # Enter consult mode
72+
squad consult --status # Check current consult mode status
73+
squad consult --check # Dry-run: show what would happen without creating files
74+
```
75+
76+
**What happens:**
77+
78+
1. Copies your personal squad into the project's `.squad/` directory
79+
2. Adds `.squad/` and `.github/agents/squad.agent.md` to `.git/info/exclude`
80+
3. Patches the Scribe charter with extraction instructions
81+
4. Creates a staging area at `.squad/extract/` for generic learnings
82+
83+
**Created structure:**
84+
85+
```
86+
.squad/ # Full copy of personal squad
87+
├── config.json # { "consult": true, "sourceSquad": "...", ... }
88+
├── agents/ # Copied from personal squad
89+
├── skills/ # Copied from personal squad
90+
├── decisions.md # Copied from personal squad
91+
├── scribe-charter.md # Patched with consult mode extraction instructions
92+
├── sessions/ # Local session history
93+
└── extract/ # Staging area for generic learnings
94+
95+
.github/agents/
96+
└── squad.agent.md # Points to local .squad/ (also excluded from git)
97+
```
98+
99+
**Requirements:**
100+
101+
- You must have a personal squad configured
102+
- The project must not already have a committed `.squad/` folder
103+
104+
---
105+
106+
### `squad extract`
107+
108+
Extract generic learnings from a consult session back to your personal squad.
109+
110+
```bash
111+
squad extract # Review and extract generic learnings
112+
squad extract --dry-run # Preview what would be extracted (no changes)
113+
squad extract --clean # Also delete project .squad/ after (prompts for confirmation)
114+
squad extract --clean --yes # Delete without confirmation
115+
squad extract --accept-risks # Allow extraction despite license risks
116+
```
117+
118+
**What happens:**
119+
120+
1. Reads the project's LICENSE file
121+
2. Loads staged learnings from `.squad/extract/`
122+
3. Presents an interactive selection UI
123+
4. Merges selected items to your personal squad
124+
5. Logs the consultation to `<personal-squad>/consultations/{project}.md`
125+
6. Optionally cleans up the project `.squad/` directory
126+
127+
**Example output:**
128+
129+
```
130+
📤 Learnings staged for extraction:
131+
132+
⚠️ License: MIT (safe to extract)
133+
134+
Found 3 learning(s) in .squad/extract/:
135+
[1] use-async-await.md
136+
[2] validate-inputs.md
137+
[3] prefer-composition.md
138+
139+
Select learnings to extract (space to toggle, enter to confirm):
140+
❯ ◉ use-async-await.md
141+
◉ validate-inputs.md
142+
◉ prefer-composition.md
143+
144+
Extract 3 learning(s)? [Y/n]
145+
```
146+
147+
---
148+
149+
## Learning Classification
150+
151+
During your consult session, the **Scribe** automatically classifies decisions as they're made:
152+
153+
### Generic (applies to any project)
154+
155+
Copied to `.squad/extract/` for later extraction:
156+
157+
- "Always use async/await instead of callbacks"
158+
- "Validate inputs at API boundaries"
159+
- "Prefer composition over inheritance"
160+
- Best practices, coding standards, patterns that work anywhere
161+
162+
### Project-specific (only applies here)
163+
164+
Kept in local `decisions.md` only — not extracted:
165+
166+
- References to specific file paths in the project
167+
- Project-specific config, APIs, or schemas
168+
- Decisions that mention "this project" or "this codebase"
169+
170+
**You always have final say.** The Scribe proposes by writing to `extract/`, you approve or reject via `squad extract`. No extraction happens without your explicit confirmation.
171+
172+
---
173+
174+
## License Handling
175+
176+
### Permissive Licenses (Safe)
177+
178+
MIT, Apache, BSD, ISC — proceed normally:
179+
180+
```
181+
⚠️ License: MIT (safe to extract)
182+
```
183+
184+
### Copyleft Licenses (Blocked)
185+
186+
GPL, AGPL, LGPL — extraction is blocked by default:
187+
188+
```
189+
🚫 License: GPL-3.0 (copyleft)
190+
Extraction blocked. Patterns from copyleft projects may carry
191+
license obligations that affect your future work.
192+
193+
See: https://squad.dev/docs/license-risk
194+
195+
To proceed anyway: squad extract --accept-risks
196+
```
197+
198+
To override:
199+
200+
```bash
201+
squad extract --accept-risks
202+
```
203+
204+
---
205+
206+
## Technical Notes
207+
208+
### Git Invisibility
209+
210+
Consult mode uses `.git/info/exclude` to hide Squad files:
211+
212+
- Same syntax as `.gitignore`
213+
- Lives inside `.git/`, so it's never committed
214+
- Project owners never see it
215+
- `git status` shows nothing Squad-related
216+
217+
### Why Copy Instead of Reference?
218+
219+
Your personal squad is **copied** into the project rather than referenced:
220+
221+
- Changes during the session don't pollute your personal squad
222+
- Session-specific decisions stay isolated until explicitly extracted
223+
- Works offline (no dependency on external path)
224+
- Clean separation between "consulting" and "bringing home"
225+
226+
### Consultation Log
227+
228+
All consultations are tracked in your personal squad at `consultations/{project}.md`:
229+
230+
```markdown
231+
# kubernetes-dashboard
232+
233+
**First consulted:** 2026-02-27
234+
**Last session:** 2026-03-15
235+
**License:** Apache-2.0
236+
237+
## Sessions
238+
239+
### 2026-02-27
240+
- use-async-await.md: "### Always use async/await..."
241+
- validate-inputs.md: "### Validate inputs at API..."
242+
243+
### 2026-03-15
244+
- prefer-composition.md: "### Prefer composition over..."
245+
```
246+
247+
---
248+
249+
## Tips
250+
251+
- Run `squad consult --check` before entering consult mode to preview what will happen
252+
- Use `squad extract --dry-run` to review staged learnings without committing
253+
- The `--clean` flag is convenient for OSS drive-by contributions where you won't return
254+
- Consult mode errors out if the project already has a committed `.squad/` — use normal mode instead
255+
- Your personal squad is never modified during the session — only via explicit `squad extract`
256+
257+
---
258+
259+
## Next Steps
260+
261+
- **Set up a personal squad:** See [Your Personal Squad](../guide/personal-squad.md) for initial setup with `squad init --global`
262+
- **Learn about sharing:** See [Export & Import](./export-import.md) for portable team snapshots
263+
- **Upstream inheritance:** See [Upstream Inheritance](./upstream-inheritance.md) for knowledge sharing across teams

‎docs/guide/personal-squad.md‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,14 @@ The more projects you connect, the richer these reviews become. Your Lead builds
205205

206206
You join an open-source project. New repo, unfamiliar code. But your agents already know *you*.
207207

208+
For projects you don't own — OSS contributions, client work, temporary collaborations — use **consult mode**. Your team consults invisibly, and the project never knows Squad was there:
209+
208210
```bash
209211
cd ~/projects/new-oss-contribution
210-
squad init
212+
squad consult
211213
```
212214

213-
Your personal squad connects. The agents don't know the codebase yet — they'll learn it. But they already know your preferences:
215+
Your personal squad is copied into the project's `.squad/` directory, hidden via `.git/info/exclude`. The agents don't know the codebase yet — they'll learn it. But they already know your preferences:
214216

215217
- How you like code structured
216218
- What testing patterns you follow
@@ -223,8 +225,16 @@ Your personal squad connects. The agents don't know the codebase yet — they'll
223225

224226
Your agents explore the repo with your familiar voice. They ask the questions you'd ask. The codebase is new — but your relationship with your team isn't.
225227

228+
When you're done, extract the generic learnings back to your personal squad and clean up:
229+
230+
```bash
231+
squad extract --clean
232+
```
233+
226234
It's not a cold start. It's your team meeting a new project.
227235

236+
> 📖 **Full guide:** [Consult Mode](../features/consult-mode.md) — invisible consulting, learning extraction, license handling.
237+
228238
---
229239

230240
## 7. Use Case: Automating Your Personal Workflow
@@ -279,11 +289,11 @@ What works well today:
279289
- Shared team identity across projects
280290
- Skills that accumulate and carry over
281291
- Consistent agent behavior everywhere you work
292+
- **Consult mode** — bring your team to projects you don't own, invisibly ([docs](../features/consult-mode.md))
282293

283294
What's still rough:
284295
- No sync mechanism between machines yet — `~/.squad/` is local to your machine
285296
- Project keys aren't used for anything yet (that `null` in config.json)
286-
- The resolution system is simple — no conflict handling if team identity diverges
287297
- No UI for browsing your global skills or agent histories (it's files for now)
288298

289299
We're building in the open. If something feels off, [open an issue](https://github.com/bradygaster/squad/issues). If something feels right, we want to hear about that too.

‎packages/squad-cli/src/cli-entry.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ async function main(): Promise<void> {
7171
console.log(` Usage: nap [--deep] [--dry-run]`);
7272
console.log(` Flags: --deep (thorough cleanup), --dry-run (preview only)`);
7373
console.log(` ${BOLD}doctor${RESET} Validate squad setup (check files, config, health)`);
74+
console.log(` ${BOLD}consult${RESET} Enter consult mode with your personal squad`);
75+
console.log(` Flags: --status, --check`);
76+
console.log(` ${BOLD}extract${RESET} Extract learnings from consult mode session`);
77+
console.log(` Flags: --dry-run, --clean, --yes, --accept-risks`);
7478
console.log(` ${BOLD}help${RESET} Show this help message`);
7579
console.log(`\nFlags:`);
7680
console.log(` ${BOLD}--version, -v${RESET} Print version`);
@@ -265,6 +269,18 @@ async function main(): Promise<void> {
265269
return;
266270
}
267271

272+
if (cmd === 'consult') {
273+
const { runConsult } = await import('./cli/commands/consult.js');
274+
await runConsult(process.cwd(), args.slice(1));
275+
return;
276+
}
277+
278+
if (cmd === 'extract') {
279+
const { runExtract } = await import('./cli/commands/extract.js');
280+
await runExtract(process.cwd(), args.slice(1));
281+
return;
282+
}
283+
268284
// Unknown command
269285
fatal(`Unknown command: ${cmd}\n Run 'squad help' for usage information.`);
270286
}

‎packages/squad-cli/src/cli/shell/index.ts‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { SquadClient } from '@bradygaster/squad-sdk/client';
2121
import type { SquadSession } from '@bradygaster/squad-sdk/client';
2222
import type { SquadPermissionHandler } from '@bradygaster/squad-sdk/client';
2323
import type { ShellMessage } from './types.js';
24-
import { initSquadTelemetry, TIMEOUTS, StreamingPipeline, recordAgentSpawn, recordAgentDuration, recordAgentError, recordAgentDestroy, RuntimeEventBus } from '@bradygaster/squad-sdk';
24+
import { initSquadTelemetry, TIMEOUTS, StreamingPipeline, recordAgentSpawn, recordAgentDuration, recordAgentError, recordAgentDestroy, RuntimeEventBus, resolveSquad, resolveGlobalSquadPath } from '@bradygaster/squad-sdk';
2525
import type { UsageEvent } from '@bradygaster/squad-sdk';
2626
import { enableShellMetrics, recordShellSessionDuration, recordAgentResponseLatency, recordShellError } from './shell-metrics.js';
2727
import { buildCoordinatorPrompt, buildInitModePrompt, parseCoordinatorResponse, hasRosterEntries } from './coordinator.js';
@@ -164,7 +164,24 @@ export async function runShell(): Promise<void> {
164164

165165
const registry = new SessionRegistry();
166166
const renderer = new ShellRenderer();
167-
const teamRoot = process.cwd();
167+
168+
// Resolve teamRoot: local .squad/ → global squad → cwd (init mode)
169+
const teamRoot = (() => {
170+
const cwd = process.cwd();
171+
// 1. Walk up from cwd looking for a local .squad/
172+
const localSquad = resolveSquad(cwd);
173+
if (localSquad) {
174+
return pathResolve(localSquad, '..');
175+
}
176+
// 2. Fall back to global (personal) squad path
177+
const globalPath = resolveGlobalSquadPath();
178+
const globalSquadDir = join(globalPath, '.squad');
179+
if (existsSync(globalSquadDir)) {
180+
return globalPath;
181+
}
182+
// 3. No squad found — use cwd (triggers init mode)
183+
return cwd;
184+
})();
168185

169186
// Session persistence — create or resume a previous session
170187
// Skip resume on first run (no team.md or .first-run marker present)

‎test/acceptance/acceptance.test.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,7 @@ runFeature(join(featuresDir, 'doctor-extended.feature'), registry);
3939
runFeature(join(featuresDir, 'help-comprehensive.feature'), registry);
4040
runFeature(join(featuresDir, 'error-paths.feature'), registry);
4141
runFeature(join(featuresDir, 'exit-codes.feature'), registry);
42+
43+
// Consult and extract command tests
44+
runFeature(join(featuresDir, 'consult-command.feature'), registry);
45+
runFeature(join(featuresDir, 'extract-command.feature'), registry);

0 commit comments

Comments
 (0)