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
78 changes: 78 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Contributing

Thanks for helping. This project is a set of persona profiles plus a Bash CLI, so contributing is mostly writing Markdown carefully and running two commands before you push.

## How to customize a persona

Two supported paths, and no third:

**Open a pull request** when the change makes the persona better for everyone. Sharper domain expertise, a communication rule that improves every answer, a fixed factual error.

**Fork the repo** when the change is specific to how you or your team work. Your fork is yours. Pull from upstream when you want the improvements.

There is deliberately no per-user override layer. It was on the roadmap and was retired. An override directory means two competing definitions of a persona and no reliable way to tell which one produced a given answer. One source of truth is worth more than the convenience of a local tweak.

Never hand-edit anything under `~/.claude/`. Those are installed copies, and the next `claude-team sync` overwrites them.

## The one rule that matters

`profiles/<name>.md` is the only source of truth for a persona.

Two files are generated from it and must never be edited by hand:

| Generated file | Serves | Contains |
|---|---|---|
| `agents/<name>.md` | delegation, "have robin review this diff" | the profile minus `## Greeting` |
| `commands/<name>.md` | the `/<name>` slash command | the profile minus `## Greeting` and minus `## Required Interactive Behaviors`, with the greeting line as a trailer |

`scripts/generate-agents.sh` writes both. `claude-team sync` runs the generator and then installs all three copies. The test suite fails if either generated file drifts from its profile, so a profile edit without a regeneration will not pass CI.

## Adding or changing a persona

1. Edit `profiles/<name>.md`, or copy an existing profile as a starting point.
2. Keep the title in the form `# Name — Role`. Both the CLI and the generator split it at the first em dash, so the role may contain one but the separator must be there.
3. **Write a `## Greeting` section.** One line, the sentence the persona says when someone runs `/<name>`. The generator aborts without it rather than shipping a slash command that ends in a bare separator. This is the most common thing to forget.
4. Optionally add a model tier in `profiles/tiers.conf`. Without one the persona still works and uses the default.
5. Regenerate and test:

```bash
bash scripts/generate-agents.sh
bash tests/run.sh
```

6. Commit the regenerated `agents/` and `commands/` files alongside the profile. They are committed artifacts, not build output.

Coordinator profiles (`profiles/coordinator.md`, `profiles/coordinator-prod.md`) are exempt. The generator skips them, and they need no `## Greeting`.

## Before you push

Both of these run in CI, so running them locally saves a round trip:

```bash
bash tests/run.sh
shellcheck bin/claude-team bin/team-session-start install.sh scripts/generate-agents.sh tests/run.sh
```

The suite must be fully green. Shellcheck must be silent; it reads `.shellcheckrc` from the repo root, which documents the three disables and why each exists.

The suite runs on Linux and macOS. macOS ships Bash 3.2 and every entry point enforces a Bash 4+ floor, so use current Bash locally if you are on a Mac.

## House style

**No emdashes in prose.** Restructure with commas, colons, semicolons, parentheses, or separate sentences. Emdashes are fine as delimiters in structured lists, such as the `# Name — Role` title or a glossary entry.

**The six coding personas follow [WRITING.md](WRITING.md).** Akira, Sasha, Robin, Alex, Morgan, and Jordan carry a plain technical English standard adapted from the plain-language principles of ASD-STE100. If you are editing one of those profiles, read that file first. The other eleven personas are deliberately exempt, because a word-count rule would strip the craft out of narrative and marketing work.

## Changing the CLI

`bin/claude-team` is a single Bash file with `set -euo pipefail`. A few conventions worth knowing before you edit it:

- **Writes to shared files take the lock.** `~/.claude/branches/INDEX.md` and `~/.claude/CLAUDE.md` are shared across every session on the machine. Both appends and rewrites must serialize: an append that lands between a rewriter's read and its rename is erased by that rename.
- **Temp files are created beside their destination**, never in `TMPDIR`. A cross-device rename degrades to copy plus unlink, during which a reader can observe a half-written file.
- **`awk > tmp && mv` hides failures.** `set -e` exempts a failing command in an `&&` list, so check the result explicitly.
- **Quote `"done"`** when passing it as an argument, or shellcheck reads it as a loop terminator.
- **A test that cannot fail is not a test.** Before trusting a new one, break the thing it guards and watch it go red.

## Reporting a problem

Open an issue with the command you ran, what you expected, what happened, and your OS and Bash version (`bash --version`).
25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,19 @@ Your existing `~/.claude/CLAUDE.md` content is preserved. The team member profil

---

## Customizing the Team

There are two supported ways to change how a persona behaves, and both keep one source of truth:

- **Open a pull request.** If the change makes the persona better for everyone, send it upstream. See [CONTRIBUTING.md](CONTRIBUTING.md).
- **Fork the repo.** If the change is specific to how you or your team work, fork and own your copy. `git pull` from upstream when you want the improvements.

There is deliberately no third option. A per-user override layer was on the roadmap and was retired: it would mean two competing definitions of a persona, and no reliable way to tell whether Akira is behaving like upstream Akira or like your local edit. One source of truth is worth more than the convenience.

Practically: edit `profiles/<name>.md` in your clone, run `claude-team sync`, and never hand-edit anything under `~/.claude/`.

---

## Adding Your Own Team Members

1. Create a new profile file in `profiles/`:
Expand All @@ -776,15 +789,17 @@ cp profiles/robin.md profiles/yourname.md

2. Edit it to define the persona, expertise, security focus, and communication style.

3. Optionally add a model tier in `profiles/tiers.conf`.
3. Keep the `## Greeting` section, and write one line for your persona. It is the sentence they say when someone runs `/yourname`. This is required: `scripts/generate-agents.sh` refuses to run without it, so a missing greeting stops the install rather than shipping a broken slash command.

4. Optionally add a model tier in `profiles/tiers.conf`.

4. Install it everywhere:
5. Install it everywhere:

```bash
claude-team sync
```

5. Activate it:
6. Activate it:

```bash
claude-team use yourname
Expand Down Expand Up @@ -898,7 +913,7 @@ The `0.4` through `0.7` numbers used during development are retired. v1 is the f
- `claude-team sync` propagates a profile edit to all three installed copies
- `claude-team install-hook` registers session context on the clone install path
- Plain technical English standard for the six coding specialists ([WRITING.md](WRITING.md))
- 135-test suite covering the CLI commands, both coordinator modes, and the install path
- 170-test suite covering the CLI commands, both coordinator modes, and the install path

### v1.0

Expand All @@ -915,6 +930,4 @@ The `0.4` through `0.7` numbers used during development are retired. v1 is the f

If any of these would change how you use the tool, [open an issue](https://github.com/code-katz/claude-team-cli/issues).

- **Local profile overrides:** `~/.claude/team/local/` for per-user customizations without forking the repo. This is the next thing we want to build. `claude-team sync` made persona edits stick across all three copies, but the edit still lives in tracked files that the next `git pull` overwrites. Overrides are what turn "customize a persona" into something you only have to do once.
- **Team-scoped profiles:** `claude-team init` creates a `.claude-team/` config in a project repo, shared across the dev team
- **Session handoff briefing:** when switching team members mid-task, the coordinator generates a structured briefing so the incoming specialist doesn't start cold
15 changes: 5 additions & 10 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ v1 delivered the roster and the coordinator. v2 closes the gaps that made the te
- [x] `claude-team install-hook` — the SessionStart hook now registers on the `install.sh` path, not only the plugin path
- [x] Plain technical English standard for the six coding specialists, documented in [WRITING.md](WRITING.md)
- [x] Slash commands generated from profiles, so a new persona means writing one file instead of three
- [x] 136-test suite covering the CLI commands, both coordinator modes, and the install path
- [x] 170-test suite covering the CLI commands, both coordinator modes, and the install path

### Shipped — v1.0

Expand All @@ -40,7 +40,9 @@ v1 delivered the roster and the coordinator. v2 closes the gaps that made the te

### Near-Term

**Local profile overrides** is the highest-value unbuilt item on this list. Details in the section below.
**Local profile overrides are retired, not deferred.** They were the top item here through v2. Building them would create a second source of persona truth competing with the repo, and the confusion between core version and local customization costs more than the convenience buys. Team-scoped profiles (`claude-team init` writing `.claude-team/`) are retired for the same reason at project scope.

Customizing a persona has two supported paths, both of which keep one source of truth: open a pull request so everyone gets the improvement, or fork and own your copy. See [CONTRIBUTING.md](CONTRIBUTING.md).

Slash command generation shipped in v2.0 and is no longer queued. `scripts/generate-agents.sh` now writes both the delegation subagent and the `/name` slash command from the profile, so adding a persona means writing one profile.

Expand All @@ -50,14 +52,6 @@ Slash command generation shipped in v2.0 and is no longer queued. `scripts/gener

These are directions we're exploring. If any of them would change how you use the tool, [open an issue](https://github.com/code-katz/claude-team-cli/issues). Real usage feedback shapes what gets built next.

**Local profile overrides (top priority)**
A `~/.claude/team/local/` directory for per-user customizations without forking the repo. Override any persona's behavior for your specific workflow without touching the upstream source.

This moved from "exploring" to "next thing we want to build" during v2. `claude-team sync` fixed the symptom, which was an edit landing in one installed copy out of three. It did not fix the cause: the only place to customize a persona is a tracked file that the next `git pull` overwrites. Until overrides exist, "make Akira work the way my team works" is something you have to redo after every update. Customizing the team is the whole point of the product, so this is the gap worth closing first.

**Team-scoped profiles**
`claude-team init` creates a `.claude-team/` config at the project root, so team conventions are shared across developers on the same repo. Consistent team member behaviors without everyone managing their own `~/.claude/team/` independently.

**Session handoff briefing**
When switching team members mid-task, the coordinator generates a structured briefing so the incoming specialist doesn't start cold: decisions made this session, open questions, and a direct question addressed to the new team member by name.

Expand All @@ -69,3 +63,4 @@ When switching team members mid-task, the coordinator generates a structured bri
|---|---|---|
| 2026-04-09 | snapshot | Initial ROADMAP.md — v0.6 shipping, aspirational backlog identified |
| 2026-07-29 | snapshot | Renumbered to v2.0 current, v1.0 shipped; pre-release `0.x` numbers retired. Corrected test count to 135. Local profile overrides promoted to top priority. |
| 2026-07-29 | decision | Local profile overrides and team-scoped profiles retired. Both create a second source of persona truth competing with the repo. Supported paths are a pull request or a fork, documented in CONTRIBUTING.md. |
14 changes: 14 additions & 0 deletions bin/claude-team
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ yellow() { printf '\033[33m%s\033[0m' "$*"; }

die() { echo "$(red "error:") $*" >&2; exit 1; }

# Claude Code registers subagents and hooks when a session starts, and reads
# slash commands on demand. So a sync activates the slash commands immediately
# and leaves the other two waiting for the next session. Saying so is the whole
# point: a green checkmark on all three reads as "all three are live now".
say_session_scope() {
echo ""
echo "$(dim "Live now: slash commands (/akira, /robin, ...). No restart needed.")"
echo "$(dim "Next session: subagents and the SessionStart hook. Claude Code registers those at startup.")"
}

lowercase() { printf '%s\n' "${1,,}"; }
capitalize() { printf '%s\n' "${1^}"; }

Expand Down Expand Up @@ -1158,6 +1168,7 @@ Make sure you are running this from the claude-team-cli repo."
mkdir -p "$commands_dst"
cp "$repo_dir/commands"/*.md "$commands_dst/"
echo "$(green "✓") Slash commands synced to $(dim "$commands_dst")"
say_session_scope
echo ""
echo "$(dim "Edit profiles in $repo_dir/profiles, then rerun: claude-team sync")"
echo ""
Expand Down Expand Up @@ -1230,6 +1241,9 @@ os.replace(tmp, settings_path)
PY

echo "$(green "✓") SessionStart hook registered: $(dim "$settings")"
# A SessionStart hook cannot fire in the session that registers it, so a bare
# checkmark here reads as "it ran" when nothing has run yet.
echo "$(dim " Runs from your next Claude Code session onward, not this one.")"
}

cmd_help() {
Expand Down
7 changes: 6 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ case "$(echo "$coord_answer" | tr '[:upper:]' '[:lower:]')" in
esac

echo ""
echo "$(bold "Done!") Your Claude dev team is ready."
echo "$(bold "Done!") Your Claude dev team is installed."
echo ""
# "ready" overstated it: the subagents, the SessionStart hook, and the
# coordinator all wait for a new session. Only the slash commands are live.
echo "$(yellow "→") $(bold "Start a new Claude Code session") to activate the subagents, the"
echo " SessionStart hook, and the coordinator. Slash commands work right now."
echo ""
echo "Quick start:"
echo " claude-team list $(dim "# see your team")"
Expand Down
20 changes: 20 additions & 0 deletions tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -691,10 +691,30 @@ assert_file_has "sync regenerates the subagent from the edited profile" \
assert_file_has "sync regenerates the slash command from the edited profile" \
"$SYNC_HOME/.claude/commands/robin.md" "SYNC-PROFILE-MARKER"
assert_file_has "sync installs tiers.conf" "$SYNC_HOME/.claude/team/tiers.conf" "akira"
# Three green checkmarks read as "all three are live now". Only the slash
# commands are: Claude Code registers subagents and hooks at session start.
out=$(HOME="$SYNC_HOME" bash "$SYNC_REPO/bin/claude-team" sync 2>&1)
assert_contains "sync says slash commands are live now" "No restart needed" "$out"
assert_contains "sync says subagents wait for a new session" "Next session" "$out"
rm -rf "$SYNC_REPO" "$SYNC_HOME"

echo ""

# Messaging accuracy. Nothing asserted any of these strings before, so a
# checkmark could claim a capability that is not live yet and CI stayed green.
echo "session-scope messaging"

out=$(run_cmd install-hook 2>&1)
assert_contains "install-hook says the hook starts next session" "next Claude Code session" "$out"
out=$(run_cmd install-commands 2>&1)
assert_contains "install-commands still promises immediacy" "No new session required" "$out"
out=$(run_cmd use robin 2>&1)
assert_contains "use still asks for a new session" "Start a new Claude Code session" "$out"
run_cmd reset >/dev/null 2>&1
assert_file_has "installer names the restart requirement" "$REPO_DIR/install.sh" "Start a new Claude Code session"
assert_file_lacks "installer no longer claims the team is ready" "$REPO_DIR/install.sh" "dev team is ready"
echo ""

# install.sh end to end into an isolated HOME: files land where the CLI
# expects them, and the coordinator prompt delegates to the CLI code path
echo "install.sh"
Expand Down
Loading