claude-company 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/.claude/agents/architect.md +69 -0
- package/.claude/agents/auditor.md +44 -0
- package/.claude/agents/developer.md +86 -0
- package/.claude/agents/devops-engineer.md +38 -0
- package/.claude/agents/docs-librarian.md +36 -0
- package/.claude/agents/ideation-strategist.md +54 -0
- package/.claude/agents/product-manager.md +54 -0
- package/.claude/agents/qa-engineer.md +58 -0
- package/.claude/agents/security-reviewer.md +44 -0
- package/.claude/agents/tech-lead.md +80 -0
- package/.claude/hooks/_common.py +209 -0
- package/.claude/hooks/gate_stamp.py +77 -0
- package/.claude/hooks/gates_detect.py +387 -0
- package/.claude/hooks/guard_commit.py +123 -0
- package/.claude/hooks/guard_frozen.py +135 -0
- package/.claude/hooks/guard_spec.py +95 -0
- package/.claude/hooks/guard_tests.py +124 -0
- package/.claude/hooks/no_slop.py +134 -0
- package/.claude/hooks/session_start.py +63 -0
- package/.claude/hooks/stop_gate.py +59 -0
- package/.claude/settings.json +70 -0
- package/.claude/skills/autopilot/SKILL.md +65 -0
- package/.claude/skills/brainstorm/SKILL.md +61 -0
- package/.claude/skills/company-init/SKILL.md +51 -0
- package/.claude/skills/cr/SKILL.md +44 -0
- package/.claude/skills/feature/SKILL.md +42 -0
- package/.claude/skills/gates/SKILL.md +33 -0
- package/.claude/skills/onboard/SKILL.md +52 -0
- package/.claude/skills/orchestrator/SKILL.md +84 -0
- package/.claude/skills/standup/SKILL.md +38 -0
- package/.mcp.json +1 -0
- package/LICENSE +21 -0
- package/ORCHESTRATOR.md +191 -0
- package/README.md +236 -0
- package/bin/claude-company.js +112 -0
- package/company/EXTENDING.md +58 -0
- package/company/GATES.md +59 -0
- package/company/GIT.md +119 -0
- package/company/IDEATION.md +102 -0
- package/company/LOOPS.md +108 -0
- package/company/METHOD.md +151 -0
- package/company/frozen-surfaces.json +17 -0
- package/company/gates.config +5 -0
- package/company/run-gates.sh +158 -0
- package/company/state/DECISIONS.md +9 -0
- package/company/state/RESUME.md +28 -0
- package/company/state/STATUS.md +30 -0
- package/company/state/WORRIES.md +12 -0
- package/company/state/adherence.log +1 -0
- package/company/templates/BRIEF-TEMPLATE.md +69 -0
- package/company/templates/CR-TEMPLATE.md +28 -0
- package/company/templates/MODULE-TEMPLATE.md +23 -0
- package/company/templates/OPTIONS-TEMPLATE.md +42 -0
- package/company/templates/REPORT-TEMPLATE.md +35 -0
- package/company/templates/SPEC-TEMPLATE.md +69 -0
- package/docs/customizing.md +88 -0
- package/docs/getting-started.md +99 -0
- package/docs/how-it-works.md +153 -0
- package/install +4 -0
- package/install.sh +331 -0
- package/lib/install-tui.js +1600 -0
- package/package.json +47 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# How it works
|
|
2
|
+
|
|
3
|
+
This page explains the method behind claude-company: why it is shaped like a company, what the gates check, and how the pieces keep each other honest. Read it when you want to understand the system well enough to explain it to someone else.
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<a href="#the-core-idea">Core Idea</a> •
|
|
7
|
+
<a href="#the-pipeline">Pipeline</a> •
|
|
8
|
+
<a href="#the-paperwork">Paperwork</a> •
|
|
9
|
+
<a href="#the-gates">Gates</a> •
|
|
10
|
+
<a href="#protected-files">Protected Files</a> •
|
|
11
|
+
<a href="#what-the-owner-keeps">Owner</a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
## The core idea
|
|
15
|
+
|
|
16
|
+
AI agents are capable builders and unreliable narrators. Left alone, an agent under pressure will report failing work as done, widen its own scope, and edit tests until they pass. Process documents help, but an instruction is something the model can skip.
|
|
17
|
+
|
|
18
|
+
claude-company treats that as an engineering problem, not a prompting problem. The rules that matter are enforced by hooks: scripts that run before every file edit and shell command, and block the ones that break the rules. The prose explains the why; the hook supplies the no.
|
|
19
|
+
|
|
20
|
+
| Layer | What lives there | Binding? |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Canon (`company/*.md`, `CLAUDE.md`) | The method, the conventions, the why | Advisory: agents read and follow it |
|
|
23
|
+
| Roles (`.claude/agents/`) | Who does what, and what each role may touch | Structural: tool access is restricted per role |
|
|
24
|
+
| Hooks (`.claude/hooks/`) | The rules that must hold | **Mechanical: the action is blocked** |
|
|
25
|
+
|
|
26
|
+
## The pipeline
|
|
27
|
+
|
|
28
|
+
A feature moves through separated roles, each checking the one before:
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
sequenceDiagram
|
|
32
|
+
actor You
|
|
33
|
+
participant CEO
|
|
34
|
+
participant PM as product-manager
|
|
35
|
+
participant TL as tech-lead
|
|
36
|
+
participant Dev as developers (parallel)
|
|
37
|
+
participant QA as qa-engineer
|
|
38
|
+
|
|
39
|
+
You->>CEO: build me X
|
|
40
|
+
CEO->>PM: dispatch: write the spec
|
|
41
|
+
PM-->>CEO: spec: options considered, FR ids, fallbacks
|
|
42
|
+
CEO->>TL: sealed brief, isolated worktree
|
|
43
|
+
TL->>Dev: task orders on disjoint directories
|
|
44
|
+
Dev-->>TL: code + evidence reports
|
|
45
|
+
Note over TL: fills the gaps between pieces
|
|
46
|
+
TL->>QA: drive the running app
|
|
47
|
+
QA-->>TL: screenshots: loaded/empty/error/after-action
|
|
48
|
+
TL-->>CEO: evidence report + gate ladder
|
|
49
|
+
Note over CEO: reruns gates, checks diffs,<br>judges screenshots itself
|
|
50
|
+
CEO-->>You: delivery + evidence + your decisions
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Why a company shape
|
|
54
|
+
|
|
55
|
+
The structure copies what makes real engineering organizations work: separated roles with separated incentives.
|
|
56
|
+
|
|
57
|
+
- **Thinkers and builders are different agents.** The product manager and architect explore options and write plans. Developers build. A builder never quietly redefines the plan, because it never owns the plan.
|
|
58
|
+
- **Producers never grade their own work.** Each layer catches what the layer below was too close to see: developers report, leads verify, QA captures, the CEO judges, an auditor rechecks the big merges.
|
|
59
|
+
- **Everyone owns their own directories.** Work orders name the exact directories an agent may touch. Two agents never share one directory, so parallel work cannot collide.
|
|
60
|
+
|
|
61
|
+
The hierarchy stays shallow on purpose: the CEO, then tech leads, then their developers and QA. Deeper pyramids add token cost and lose information at every handoff.
|
|
62
|
+
|
|
63
|
+
## The paperwork
|
|
64
|
+
|
|
65
|
+
The company runs on a few typed documents rather than long conversations:
|
|
66
|
+
|
|
67
|
+
| Artifact | Written by | Read by | What it carries |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| Options memo | strategists + CEO | you | Numbered ideas with reasoning, scored recommendation, the strongest rejected option |
|
|
70
|
+
| Spec | product-manager | CEO | Requirements with stable ids (FR-01, FR-02), acceptance criteria, options considered |
|
|
71
|
+
| Brief | CEO | one builder | Mission, owned directories, definition of done, a decided fallback per ambiguity |
|
|
72
|
+
| Report | every agent | its dispatcher | Facts only: the diff, gate output, screenshots, deviations |
|
|
73
|
+
|
|
74
|
+
Two design choices matter most:
|
|
75
|
+
|
|
76
|
+
- **Builders read the brief, never the spec.** The spec is rich and human-facing; the brief is the lean slice derived from it. The builder's context stays small and its instructions stay exact.
|
|
77
|
+
- **Ambiguity is handled once, in writing.** Every open question gets one decided fallback, so ten parallel agents make the same assumption instead of ten different ones. Questions only a human should answer wait in `company/state/DECISIONS.md` while the build proceeds on the fallback.
|
|
78
|
+
|
|
79
|
+
## The gates
|
|
80
|
+
|
|
81
|
+
A gate is a command that must exit successfully: your test suite, your linter, your build. Gates live in `company/gates.config`, and `company/run-gates.sh` runs the ladder and stamps the result with a fingerprint of your working tree.
|
|
82
|
+
|
|
83
|
+
The stamp is what gives gates teeth:
|
|
84
|
+
|
|
85
|
+
```mermaid
|
|
86
|
+
stateDiagram-v2
|
|
87
|
+
direction LR
|
|
88
|
+
[*] --> Unstamped
|
|
89
|
+
Unstamped --> Green: run gates, all pass
|
|
90
|
+
Unstamped --> Red: any gate fails
|
|
91
|
+
Red --> Green: fix the cause, rerun
|
|
92
|
+
Green --> Stale: any tracked file changes
|
|
93
|
+
Stale --> Green: rerun gates
|
|
94
|
+
Green --> [*]: git commit allowed
|
|
95
|
+
note right of Red
|
|
96
|
+
commit hook blocks
|
|
97
|
+
Red, Stale, Unstamped
|
|
98
|
+
end note
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Change one file after the gates ran and the stamp goes stale, so "it passed earlier" stops counting. Nobody, including the CEO, can commit past a red or stale stamp.
|
|
102
|
+
|
|
103
|
+
<details>
|
|
104
|
+
<summary><b>Two habits that keep gates meaningful</b></summary>
|
|
105
|
+
<br>
|
|
106
|
+
|
|
107
|
+
- **Test the negative space.** Where a table lists allowed actions, generate the complement and assert every non-listed action is rejected. Positive-only tests pass while a system silently allows everything.
|
|
108
|
+
- **Never trust a worktree's numbers.** Agents build in isolated git worktrees, where stale artifacts can mask integration failures. Verification reruns gates on the integrated result.
|
|
109
|
+
|
|
110
|
+
</details>
|
|
111
|
+
|
|
112
|
+
## Protected files
|
|
113
|
+
|
|
114
|
+
Some files hold the whole system up: database migrations that already shipped, the schema, lockfiles, anything with exactly one legitimate writer. These are listed in `company/frozen-surfaces.json`, and the hook blocks every edit to them.
|
|
115
|
+
|
|
116
|
+
When an agent genuinely needs a protected file changed, it files a change request (CR) instead:
|
|
117
|
+
|
|
118
|
+
```mermaid
|
|
119
|
+
flowchart LR
|
|
120
|
+
A[Agent needs a frozen<br>file changed] --> B[STOP: file a CR<br>what, why, exact diff,<br>blast radius]
|
|
121
|
+
B --> C{CEO arbitrates}
|
|
122
|
+
C -->|approve| D[CEO applies it in a<br>dedicated gated commit]
|
|
123
|
+
C -->|reject| E[Agent uses the CR's<br>stated workaround]
|
|
124
|
+
D --> F[Affected agents rebase]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The paperwork is the point: every change to a shared surface becomes visible and reviewed instead of silent.
|
|
128
|
+
|
|
129
|
+
## What the owner keeps
|
|
130
|
+
|
|
131
|
+
The escalation list is short and absolute. No agent decides:
|
|
132
|
+
|
|
133
|
+
| Decision | Why it is yours |
|
|
134
|
+
|---|---|
|
|
135
|
+
| Production deploys and migrations | Merge is integration; shipping is a button only you press |
|
|
136
|
+
| Anything involving money | Pricing, billing behavior, ledgers |
|
|
137
|
+
| Weakening a protection | The rules exist because someone will want around them |
|
|
138
|
+
| Scope beyond a brief | Growth is a decision, not a drift |
|
|
139
|
+
| Business policy | Agents run on tagged fallbacks until you answer |
|
|
140
|
+
|
|
141
|
+
One more rule catches design problems early: a gate that fails twice on the same cause stops the work and surfaces to you, because repeated failure means the plan is wrong, not the agent.
|
|
142
|
+
|
|
143
|
+
## Watching it enforce
|
|
144
|
+
|
|
145
|
+
Every hook block and every hotfix bypass appends one line to `company/state/adherence.log`:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
2026-07-06T14:51:57Z | guard_frozen | BLOCK | .env | always-frozen: .env
|
|
149
|
+
2026-07-06T14:51:58Z | guard_commit | BLOCK | git commit | gates.status is stale
|
|
150
|
+
2026-07-06T14:52:14Z | guard_commit | BYPASS | git commit | hotfix mode
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The log is the difference between a system that claims discipline and one that demonstrates it. Repeated blocks on the same agent or file are a signal worth reading: the work order was vague, or the design is fighting the rules.
|
package/install
ADDED
package/install.sh
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install.sh - drop-in installer for claude-company, a hierarchical SDLC
|
|
3
|
+
# system for Claude Code. Copies the company system into a target project,
|
|
4
|
+
# idempotently and non-destructively (user state and config always win).
|
|
5
|
+
#
|
|
6
|
+
# Usage: bash install.sh /path/to/target-project
|
|
7
|
+
#
|
|
8
|
+
# Works on macOS bash 3.2 (no associative arrays, no readarray).
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
# --- colors ---------------------------------------------------------------
|
|
12
|
+
if [ -t 1 ]; then
|
|
13
|
+
C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'
|
|
14
|
+
C_BLUE=$'\033[34m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m'
|
|
15
|
+
else
|
|
16
|
+
C_RED=''; C_GREEN=''; C_YELLOW=''; C_BLUE=''; C_BOLD=''; C_RESET=''
|
|
17
|
+
fi
|
|
18
|
+
info() { printf '%s\n' "${C_BLUE}${C_BOLD}==>${C_RESET} $*"; }
|
|
19
|
+
ok() { printf '%s\n' " ${C_GREEN}ok${C_RESET} $*"; }
|
|
20
|
+
skip() { printf '%s\n' " ${C_YELLOW}keep${C_RESET} $*"; }
|
|
21
|
+
warn() { printf '%s\n' "${C_YELLOW}warning:${C_RESET} $*" >&2; }
|
|
22
|
+
die() { printf '%s\n' "${C_RED}error:${C_RESET} $*" >&2; exit 1; }
|
|
23
|
+
|
|
24
|
+
usage() {
|
|
25
|
+
cat >&2 <<USAGE
|
|
26
|
+
Usage: bash install.sh /path/to/target-project
|
|
27
|
+
|
|
28
|
+
Installs the claude-company SDLC system into an existing project directory.
|
|
29
|
+
The target directory must already exist.
|
|
30
|
+
USAGE
|
|
31
|
+
exit 2
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# --- resolve source (this script's repo), independent of cwd --------------
|
|
35
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
36
|
+
SRC="$SCRIPT_DIR"
|
|
37
|
+
|
|
38
|
+
# --- validate arguments ---------------------------------------------------
|
|
39
|
+
[ "$#" -ge 1 ] || usage
|
|
40
|
+
TARGET_ARG="$1"
|
|
41
|
+
[ -n "$TARGET_ARG" ] || usage
|
|
42
|
+
[ -d "$TARGET_ARG" ] || die "target directory does not exist: $TARGET_ARG"
|
|
43
|
+
TARGET="$(cd "$TARGET_ARG" && pwd)"
|
|
44
|
+
|
|
45
|
+
# --- refuse to install into the claude-company repo itself ----------------
|
|
46
|
+
if [ "$TARGET" = "$SRC" ]; then
|
|
47
|
+
die "refusing to install into the claude-company repo itself ($SRC)"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
info "claude-company installer"
|
|
51
|
+
echo " source: $SRC"
|
|
52
|
+
echo " target: $TARGET"
|
|
53
|
+
echo
|
|
54
|
+
|
|
55
|
+
# --- copy helpers ---------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
# Overwrite a single file (ours - update in place).
|
|
58
|
+
copy_overwrite() {
|
|
59
|
+
local src="$1" dst="$2"
|
|
60
|
+
if [ ! -e "$src" ]; then warn "source missing, skipped: ${src#$SRC/}"; return 0; fi
|
|
61
|
+
mkdir -p "$(dirname "$dst")"
|
|
62
|
+
cp "$src" "$dst"
|
|
63
|
+
ok "${dst#$TARGET/}"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Copy a single file only if the destination is absent (user wins).
|
|
67
|
+
copy_if_absent() {
|
|
68
|
+
local src="$1" dst="$2"
|
|
69
|
+
if [ ! -e "$src" ]; then warn "source missing, skipped: ${src#$SRC/}"; return 0; fi
|
|
70
|
+
if [ -e "$dst" ]; then skip "${dst#$TARGET/} (kept existing)"; return 0; fi
|
|
71
|
+
mkdir -p "$(dirname "$dst")"
|
|
72
|
+
cp "$src" "$dst"
|
|
73
|
+
ok "${dst#$TARGET/}"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Walk a directory and overwrite every file at the target (ours in place).
|
|
77
|
+
# Skips python bytecode caches so we never ship __pycache__/*.pyc artifacts.
|
|
78
|
+
copy_tree_overwrite() {
|
|
79
|
+
local src="$1" dst="$2" f rel
|
|
80
|
+
if [ ! -d "$src" ]; then warn "source dir missing, skipped: ${src#$SRC/}/"; return 0; fi
|
|
81
|
+
find "$src" -type f -not -path '*/__pycache__/*' -not -name '*.pyc' -print | while IFS= read -r f; do
|
|
82
|
+
rel="${f#$src/}"
|
|
83
|
+
mkdir -p "$dst/$(dirname "$rel")"
|
|
84
|
+
cp "$f" "$dst/$rel"
|
|
85
|
+
done
|
|
86
|
+
ok "${dst#$TARGET/}/ (updated)"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Walk a directory and copy each file only if absent at target (user wins).
|
|
90
|
+
copy_tree_if_absent() {
|
|
91
|
+
local src="$1" dst="$2" f rel
|
|
92
|
+
if [ ! -d "$src" ]; then return 0; fi
|
|
93
|
+
find "$src" -type f -not -path '*/__pycache__/*' -not -name '*.pyc' -print | while IFS= read -r f; do
|
|
94
|
+
rel="${f#$src/}"
|
|
95
|
+
if [ ! -e "$dst/$rel" ]; then
|
|
96
|
+
mkdir -p "$dst/$(dirname "$rel")"
|
|
97
|
+
cp "$f" "$dst/$rel"
|
|
98
|
+
fi
|
|
99
|
+
done
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# --- 1. agents, hooks, skills (ours - update in place) --------------------
|
|
103
|
+
info "Installing agents, hooks, and skills"
|
|
104
|
+
copy_tree_overwrite "$SRC/.claude/agents" "$TARGET/.claude/agents"
|
|
105
|
+
copy_tree_overwrite "$SRC/.claude/hooks" "$TARGET/.claude/hooks"
|
|
106
|
+
copy_tree_overwrite "$SRC/.claude/skills" "$TARGET/.claude/skills"
|
|
107
|
+
|
|
108
|
+
# --- 2. canon docs and orchestrator (ours - update in place) --------------
|
|
109
|
+
info "Installing canon docs"
|
|
110
|
+
copy_overwrite "$SRC/ORCHESTRATOR.md" "$TARGET/ORCHESTRATOR.md"
|
|
111
|
+
copy_overwrite "$SRC/company/METHOD.md" "$TARGET/company/METHOD.md"
|
|
112
|
+
copy_overwrite "$SRC/company/GATES.md" "$TARGET/company/GATES.md"
|
|
113
|
+
copy_overwrite "$SRC/company/EXTENDING.md" "$TARGET/company/EXTENDING.md"
|
|
114
|
+
copy_overwrite "$SRC/company/IDEATION.md" "$TARGET/company/IDEATION.md"
|
|
115
|
+
copy_overwrite "$SRC/company/GIT.md" "$TARGET/company/GIT.md"
|
|
116
|
+
copy_overwrite "$SRC/company/LOOPS.md" "$TARGET/company/LOOPS.md"
|
|
117
|
+
copy_overwrite "$SRC/company/run-gates.sh" "$TARGET/company/run-gates.sh"
|
|
118
|
+
[ -f "$TARGET/company/run-gates.sh" ] && chmod +x "$TARGET/company/run-gates.sh"
|
|
119
|
+
copy_tree_overwrite "$SRC/company/templates" "$TARGET/company/templates"
|
|
120
|
+
|
|
121
|
+
# --- 3. user config and state (user wins if present) ----------------------
|
|
122
|
+
info "Installing project config and state (existing files preserved)"
|
|
123
|
+
copy_if_absent "$SRC/company/gates.config" "$TARGET/company/gates.config"
|
|
124
|
+
copy_if_absent "$SRC/company/frozen-surfaces.json" "$TARGET/company/frozen-surfaces.json"
|
|
125
|
+
|
|
126
|
+
# work directories - preserve any existing content
|
|
127
|
+
mkdir -p "$TARGET/company/specs" "$TARGET/company/briefs" "$TARGET/company/change-requests"
|
|
128
|
+
copy_tree_if_absent "$SRC/company/specs" "$TARGET/company/specs"
|
|
129
|
+
copy_tree_if_absent "$SRC/company/briefs" "$TARGET/company/briefs"
|
|
130
|
+
copy_tree_if_absent "$SRC/company/change-requests" "$TARGET/company/change-requests"
|
|
131
|
+
|
|
132
|
+
# --- 4. scaffold state stubs (only if absent) -----------------------------
|
|
133
|
+
info "Scaffolding company/state"
|
|
134
|
+
mkdir -p "$TARGET/company/state"
|
|
135
|
+
scaffold_stub() {
|
|
136
|
+
local dst="$1" header="$2"
|
|
137
|
+
if [ -e "$dst" ]; then skip "${dst#$TARGET/} (kept existing)"; return 0; fi
|
|
138
|
+
printf '%s\n' "$header" > "$dst"
|
|
139
|
+
ok "${dst#$TARGET/}"
|
|
140
|
+
}
|
|
141
|
+
scaffold_stub "$TARGET/company/state/STATUS.md" \
|
|
142
|
+
"# STATUS - maintained by the orchestrator. Red stays red until proven green."
|
|
143
|
+
scaffold_stub "$TARGET/company/state/RESUME.md" \
|
|
144
|
+
"# RESUME - maintained by the orchestrator. Where we are and what happens next."
|
|
145
|
+
scaffold_stub "$TARGET/company/state/WORRIES.md" \
|
|
146
|
+
"# WORRIES - maintained by the orchestrator. Open risks and unknowns, worst first."
|
|
147
|
+
scaffold_stub "$TARGET/company/state/DECISIONS.md" \
|
|
148
|
+
"# DECISIONS - maintained by the orchestrator. Durable choices and their rationale."
|
|
149
|
+
if [ ! -e "$TARGET/company/state/adherence.log" ]; then
|
|
150
|
+
touch "$TARGET/company/state/adherence.log"
|
|
151
|
+
ok "company/state/adherence.log"
|
|
152
|
+
else
|
|
153
|
+
skip "company/state/adherence.log (kept existing)"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# --- 5. settings.json (copy or deep-merge) --------------------------------
|
|
157
|
+
# Always route through the merger so the on-disk formatting is identical
|
|
158
|
+
# whether we started from nothing or an existing file (keeps re-runs a no-op).
|
|
159
|
+
info "Installing .claude/settings.json"
|
|
160
|
+
SETTINGS_SRC="$SRC/.claude/settings.json"
|
|
161
|
+
SETTINGS_DST="$TARGET/.claude/settings.json"
|
|
162
|
+
if [ ! -f "$SETTINGS_SRC" ]; then
|
|
163
|
+
warn "source .claude/settings.json missing - skipped"
|
|
164
|
+
else
|
|
165
|
+
[ -f "$SETTINGS_DST" ] && SETTINGS_VERB="merged" || SETTINGS_VERB="copied"
|
|
166
|
+
mkdir -p "$TARGET/.claude"
|
|
167
|
+
python3 - "$SETTINGS_SRC" "$SETTINGS_DST" <<'PY'
|
|
168
|
+
import json, sys
|
|
169
|
+
|
|
170
|
+
src_path, dst_path = sys.argv[1], sys.argv[2]
|
|
171
|
+
with open(src_path) as f:
|
|
172
|
+
ours = json.load(f)
|
|
173
|
+
try:
|
|
174
|
+
with open(dst_path) as f:
|
|
175
|
+
theirs = json.load(f)
|
|
176
|
+
except (FileNotFoundError, ValueError):
|
|
177
|
+
theirs = {}
|
|
178
|
+
|
|
179
|
+
# --- merge hooks: append our command entries unless already present -------
|
|
180
|
+
def commands_in_event(groups):
|
|
181
|
+
cmds = set()
|
|
182
|
+
for g in groups or []:
|
|
183
|
+
for h in (g.get("hooks") or []):
|
|
184
|
+
c = h.get("command")
|
|
185
|
+
if c is not None:
|
|
186
|
+
cmds.add(c)
|
|
187
|
+
return cmds
|
|
188
|
+
|
|
189
|
+
our_hooks = ours.get("hooks") or {}
|
|
190
|
+
their_hooks = theirs.setdefault("hooks", {}) if isinstance(theirs.get("hooks", {}), dict) else {}
|
|
191
|
+
if not isinstance(theirs.get("hooks"), dict):
|
|
192
|
+
theirs["hooks"] = {}
|
|
193
|
+
their_hooks = theirs["hooks"]
|
|
194
|
+
|
|
195
|
+
for event, our_groups in our_hooks.items():
|
|
196
|
+
existing_groups = their_hooks.get(event)
|
|
197
|
+
if not isinstance(existing_groups, list):
|
|
198
|
+
existing_groups = []
|
|
199
|
+
their_hooks[event] = existing_groups
|
|
200
|
+
have = commands_in_event(existing_groups)
|
|
201
|
+
for g in (our_groups or []):
|
|
202
|
+
new_hooks = []
|
|
203
|
+
for h in (g.get("hooks") or []):
|
|
204
|
+
c = h.get("command")
|
|
205
|
+
if c is not None and c in have:
|
|
206
|
+
continue # identical command already present
|
|
207
|
+
new_hooks.append(h)
|
|
208
|
+
if c is not None:
|
|
209
|
+
have.add(c)
|
|
210
|
+
if new_hooks:
|
|
211
|
+
ng = dict(g)
|
|
212
|
+
ng["hooks"] = new_hooks
|
|
213
|
+
existing_groups.append(ng)
|
|
214
|
+
|
|
215
|
+
# --- merge permissions.deny: union, preserving user's ---------------------
|
|
216
|
+
our_perms = ours.get("permissions") or {}
|
|
217
|
+
our_deny = our_perms.get("deny") or []
|
|
218
|
+
if our_deny:
|
|
219
|
+
their_perms = theirs.get("permissions")
|
|
220
|
+
if not isinstance(their_perms, dict):
|
|
221
|
+
their_perms = {}
|
|
222
|
+
theirs["permissions"] = their_perms
|
|
223
|
+
their_deny = their_perms.get("deny")
|
|
224
|
+
if not isinstance(their_deny, list):
|
|
225
|
+
their_deny = []
|
|
226
|
+
their_perms["deny"] = their_deny
|
|
227
|
+
for entry in our_deny:
|
|
228
|
+
if entry not in their_deny:
|
|
229
|
+
their_deny.append(entry)
|
|
230
|
+
|
|
231
|
+
with open(dst_path, "w") as f:
|
|
232
|
+
json.dump(theirs, f, indent=2, sort_keys=False)
|
|
233
|
+
f.write("\n")
|
|
234
|
+
PY
|
|
235
|
+
ok ".claude/settings.json ($SETTINGS_VERB)"
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# --- 6. .mcp.json (copy or merge the playwright server) -------------------
|
|
239
|
+
info "Installing .mcp.json"
|
|
240
|
+
MCP_SRC="$SRC/.mcp.json"
|
|
241
|
+
MCP_DST="$TARGET/.mcp.json"
|
|
242
|
+
if [ ! -f "$MCP_SRC" ]; then
|
|
243
|
+
warn "source .mcp.json missing - skipped"
|
|
244
|
+
else
|
|
245
|
+
[ -f "$MCP_DST" ] && MCP_VERB="merged" || MCP_VERB="copied"
|
|
246
|
+
python3 - "$MCP_SRC" "$MCP_DST" <<'PY'
|
|
247
|
+
import json, sys
|
|
248
|
+
src_path, dst_path = sys.argv[1], sys.argv[2]
|
|
249
|
+
with open(src_path) as f:
|
|
250
|
+
ours = json.load(f)
|
|
251
|
+
try:
|
|
252
|
+
with open(dst_path) as f:
|
|
253
|
+
theirs = json.load(f)
|
|
254
|
+
except (FileNotFoundError, ValueError):
|
|
255
|
+
theirs = {}
|
|
256
|
+
our_servers = ours.get("mcpServers") or {}
|
|
257
|
+
their_servers = theirs.get("mcpServers")
|
|
258
|
+
if not isinstance(their_servers, dict):
|
|
259
|
+
their_servers = {}
|
|
260
|
+
theirs["mcpServers"] = their_servers
|
|
261
|
+
for name, cfg in our_servers.items():
|
|
262
|
+
if name not in their_servers: # never overwrite a user's existing server
|
|
263
|
+
their_servers[name] = cfg
|
|
264
|
+
with open(dst_path, "w") as f:
|
|
265
|
+
json.dump(theirs, f, indent=2, sort_keys=False)
|
|
266
|
+
f.write("\n")
|
|
267
|
+
PY
|
|
268
|
+
ok ".mcp.json ($MCP_VERB)"
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
# --- 7. CLAUDE.md marked block (append or replace) ------------------------
|
|
272
|
+
info "Updating CLAUDE.md"
|
|
273
|
+
read -r -d '' CC_BLOCK <<'BLOCK' || true
|
|
274
|
+
<!-- claude-company:begin -->
|
|
275
|
+
## claude-company
|
|
276
|
+
|
|
277
|
+
This project runs **claude-company**, a hierarchical SDLC system for Claude Code.
|
|
278
|
+
|
|
279
|
+
- Main sessions act as CEO: drive the project through `/orchestrator` and `ORCHESTRATOR.md`.
|
|
280
|
+
- Subagents obey their brief plus `company/METHOD.md` - the brief is the contract.
|
|
281
|
+
- Gates are the definition of done and are hook-enforced. Red stays red until proven green.
|
|
282
|
+
- Run gates with `company/run-gates.sh`; configure them in `company/gates.config`.
|
|
283
|
+
- Frozen surfaces (`company/frozen-surfaces.json`) change only via a change request in `company/change-requests/`.
|
|
284
|
+
- Project state lives in `company/state/` (STATUS, RESUME, WORRIES, DECISIONS).
|
|
285
|
+
|
|
286
|
+
New to this project? Run `/company-init`. Adopting an existing codebase? Run `/onboard`. Then `/orchestrator`.
|
|
287
|
+
<!-- claude-company:end -->
|
|
288
|
+
BLOCK
|
|
289
|
+
|
|
290
|
+
CC_BLOCK="$CC_BLOCK" python3 - "$TARGET/CLAUDE.md" <<'PY'
|
|
291
|
+
import os, re, sys
|
|
292
|
+
path = sys.argv[1]
|
|
293
|
+
block = os.environ["CC_BLOCK"].rstrip("\n")
|
|
294
|
+
begin = "<!-- claude-company:begin -->"
|
|
295
|
+
end = "<!-- claude-company:end -->"
|
|
296
|
+
try:
|
|
297
|
+
with open(path) as f:
|
|
298
|
+
content = f.read()
|
|
299
|
+
except FileNotFoundError:
|
|
300
|
+
content = ""
|
|
301
|
+
if begin in content and end in content:
|
|
302
|
+
pattern = re.compile(re.escape(begin) + ".*?" + re.escape(end), re.DOTALL)
|
|
303
|
+
content = pattern.sub(lambda m: block, content)
|
|
304
|
+
else:
|
|
305
|
+
if content and not content.endswith("\n"):
|
|
306
|
+
content += "\n"
|
|
307
|
+
if content:
|
|
308
|
+
content += "\n"
|
|
309
|
+
content += block + "\n"
|
|
310
|
+
with open(path, "w") as f:
|
|
311
|
+
f.write(content)
|
|
312
|
+
PY
|
|
313
|
+
ok "CLAUDE.md (claude-company block up to date)"
|
|
314
|
+
|
|
315
|
+
# --- epilogue -------------------------------------------------------------
|
|
316
|
+
echo
|
|
317
|
+
info "claude-company installed"
|
|
318
|
+
cat <<EPILOGUE
|
|
319
|
+
|
|
320
|
+
Next steps:
|
|
321
|
+
1. cd "$TARGET"
|
|
322
|
+
2. claude
|
|
323
|
+
3. In Claude Code, run one of:
|
|
324
|
+
/company-init (new project - scaffold specs and gates)
|
|
325
|
+
/onboard (existing codebase - map what is already there)
|
|
326
|
+
4. Then start driving the work:
|
|
327
|
+
/orchestrator
|
|
328
|
+
|
|
329
|
+
Configure your gates in company/gates.config, then verify with:
|
|
330
|
+
bash company/run-gates.sh
|
|
331
|
+
EPILOGUE
|