claude-company 0.2.0 → 0.2.4

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.
@@ -43,6 +43,26 @@ def load_manifest(root):
43
43
  return roles
44
44
 
45
45
 
46
+ def load_builtins(root):
47
+ """Return {type: model} from company/models.json `builtins`, or None.
48
+
49
+ Mirrors load_manifest and fails open (None) on a missing/malformed/
50
+ unreadable file or section. Keys starting with '$' (e.g. "$comment") are
51
+ dropped so they are never treated as a spawnable type, and only string
52
+ model values are kept.
53
+ """
54
+ cfg = c.read_json_file(os.path.join(root, "company", "models.json"))
55
+ if not isinstance(cfg, dict):
56
+ return None
57
+ builtins = cfg.get("builtins")
58
+ if not isinstance(builtins, dict):
59
+ return None
60
+ return {
61
+ k: v for k, v in builtins.items()
62
+ if not k.startswith("$") and isinstance(v, str)
63
+ }
64
+
65
+
46
66
  def is_hotfix(root):
47
67
  task = c.active_task(root)
48
68
  return isinstance(task, dict) and task.get("type") == "hotfix"
@@ -67,15 +87,53 @@ def new_text(tool_name, tool_input):
67
87
  return "\n".join(parts)
68
88
 
69
89
 
70
- def handle_spawn(root, tool_input, roles):
90
+ def handle_spawn(root, tool_input, roles, builtins):
71
91
  role = None
72
92
  for field in SPAWN_TYPE_FIELDS:
73
93
  val = tool_input.get(field)
74
94
  if val:
75
95
  role = val
76
96
  break
77
- if role is None or role not in roles:
78
- return # unknown role -> allow
97
+ if role is None:
98
+ return # no spawn type -> allow
99
+ if role in roles:
100
+ # OQ-MRA-02 assumption: roles governs on any collision - a type present
101
+ # in both `roles` and `builtins` is decided here, by the roles branch.
102
+ handle_role_spawn(root, tool_input, roles, role)
103
+ return
104
+ # Not a manifest role. A built-in agent type has no .claude/agents
105
+ # frontmatter to inherit from, so a bare spawn would silently take the
106
+ # SESSION model - require an explicit matching override.
107
+ if builtins is None:
108
+ return # no builtins section -> builtin enforcement inert (fail open)
109
+ if role not in builtins:
110
+ return # unknown type -> allow
111
+ pin = builtins[role]
112
+ override = tool_input.get("model")
113
+ if override == pin:
114
+ return # explicit matching override -> allow
115
+ if is_hotfix(root):
116
+ c.log_bypass(root, HOOK, "spawn " + role, "hotfix mode")
117
+ return
118
+ if not override:
119
+ c.block(
120
+ root, HOOK, "spawn " + role,
121
+ "builtin spawn no override (pin {})".format(pin),
122
+ "BLOCKED: spawning built-in '{}' with no model override would "
123
+ "inherit the session model - built-in agent types have no "
124
+ "company/models.json frontmatter pin to fall back on.\n"
125
+ "Fix: pass model: '{}'".format(role, pin),
126
+ )
127
+ c.block(
128
+ root, HOOK, "spawn " + role,
129
+ "builtin spawn override {} != pin {}".format(override, pin),
130
+ "BLOCKED: spawning built-in '{}' with model '{}' contradicts "
131
+ "company/models.json (built-in pin: '{}').\n"
132
+ "Fix: pass model: '{}'".format(role, override, pin, pin),
133
+ )
134
+
135
+
136
+ def handle_role_spawn(root, tool_input, roles, role):
79
137
  override = tool_input.get("model")
80
138
  if not override:
81
139
  return # no override -> role inherits the manifest model, allow
@@ -124,8 +182,53 @@ def handle_frontmatter_edit(root, tool_name, tool_input, roles):
124
182
  )
125
183
 
126
184
 
185
+ def check_spawn_wiring(root):
186
+ """Assert .claude/settings.json wires guard_models on the Task spawn tool.
187
+
188
+ Passes when some PreToolUse group has a matcher covering the Task tool -
189
+ "Task" is one of its "|"-separated alternatives (the shipped matcher is
190
+ "Task|Agent") - and one of that group's hooks runs a command referencing
191
+ guard_models.py. Only the project settings.json counts; settings.local.json
192
+ is ignored. Returns (ok, fixit_message).
193
+ """
194
+ fixit = (
195
+ "spawn enforcement is not wired: .claude/settings.json has no "
196
+ "PreToolUse matcher covering the Task spawn tool that runs "
197
+ "guard_models.py - re-run `claude-company install` or `update` to "
198
+ "re-add it."
199
+ )
200
+ settings = c.read_json_file(
201
+ os.path.join(root, ".claude", "settings.json")
202
+ )
203
+ if not isinstance(settings, dict):
204
+ return False, fixit
205
+ hooks = settings.get("hooks")
206
+ if not isinstance(hooks, dict):
207
+ return False, fixit
208
+ groups = hooks.get("PreToolUse")
209
+ if not isinstance(groups, list):
210
+ return False, fixit
211
+ for group in groups:
212
+ if not isinstance(group, dict):
213
+ continue
214
+ matcher = group.get("matcher") or ""
215
+ if "Task" not in matcher.split("|"):
216
+ continue
217
+ for h in group.get("hooks") or []:
218
+ if isinstance(h, dict) and "guard_models.py" in (
219
+ h.get("command") or ""
220
+ ):
221
+ return True, ""
222
+ return False, fixit
223
+
224
+
127
225
  def run_check(root):
128
- """Compare every agent frontmatter against the manifest. Exit 0/1."""
226
+ """Compare every agent frontmatter against the manifest. Exit 0/1.
227
+
228
+ FR-MRA-06: this frontmatter/agent-file comparison is based ONLY on `roles`.
229
+ Built-in types live in the separate `builtins` section and never demand a
230
+ .claude/agents/<type>.md file, so their absence is never a mismatch here.
231
+ """
129
232
  roles = load_manifest(root)
130
233
  if roles is None:
131
234
  print("company/models.json missing or unreadable")
@@ -178,6 +281,19 @@ def run_check(root):
178
281
  print("\nmismatches:")
179
282
  for m in mismatches:
180
283
  print(" - " + m)
284
+
285
+ # LOUD wiring assertion (FR-MRA-07), gated on builtins presence.
286
+ # OQ-MRA-04 assumption: guard_models asserts only its OWN enforcement
287
+ # wiring. Old manifests without a `builtins` section (load_builtins ->
288
+ # None) skip this entirely and behave exactly as before.
289
+ wiring_ok = True
290
+ if load_builtins(root) is not None:
291
+ wiring_ok, fixit = check_spawn_wiring(root)
292
+ if not wiring_ok:
293
+ print("\nspawn wiring:")
294
+ print(" - " + fixit)
295
+
296
+ if mismatches or not wiring_ok:
181
297
  return 1
182
298
  print("\nall agent models agree with company/models.json")
183
299
  return 0
@@ -206,7 +322,7 @@ def main():
206
322
  if roles is None:
207
323
  sys.exit(0) # no manifest -> fail open
208
324
  if tool_name in ("Task", "Agent"):
209
- handle_spawn(root, tool_input, roles)
325
+ handle_spawn(root, tool_input, roles, load_builtins(root))
210
326
  elif tool_name in ("Edit", "Write", "MultiEdit"):
211
327
  handle_frontmatter_edit(root, tool_name, tool_input, roles)
212
328
  except SystemExit:
@@ -1,4 +1,7 @@
1
1
  {
2
+ "env": {
3
+ "CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH": "2"
4
+ },
2
5
  "permissions": {
3
6
  "deny": [
4
7
  "Read(./.env)",
package/ORCHESTRATOR.md CHANGED
@@ -178,6 +178,11 @@ building agents into isolated worktrees:
178
178
  git worktree add .claude/worktrees/<task-slug> -b task/<task-slug>
179
179
  ```
180
180
 
181
+ The Workflow tool is FORBIDDEN by default: its internal `agent()` spawns fire no
182
+ PreToolUse events, so `guard_models` cannot pin their model - permit it only
183
+ with explicit owner authorization and a `model` pin in every `agent()` call,
184
+ including all early stages (see `company/METHOD.md`).
185
+
181
186
  Skeleton for a tech-lead (adapt for direct developer dispatch on `quick`):
182
187
 
183
188
  ```
package/company/GATES.md CHANGED
@@ -65,7 +65,9 @@ These rungs are wired for this repo; a project inherits them and adds its own.
65
65
  - **G7 models (`python3 .claude/hooks/guard_models.py --check`).** The model
66
66
  routing manifest and agent frontmatter must agree: spawn overrides and
67
67
  per-agent model declarations match `models.json`, so no agent silently runs
68
- on the wrong model. (Shipped in wave 1.)
68
+ on the wrong model. It also asserts the Task|Agent spawn hook is wired into
69
+ `.claude/settings.json`, so the enforcement cannot ship as code without teeth.
70
+ (Shipped in wave 1.)
69
71
  - **G8 audit - the last rung, network-bound.** A dependency and CVE scan: no
70
72
  known-vulnerable dependency ships. It runs last because it reaches the
71
73
  network; keep it blocking anyway.
package/company/METHOD.md CHANGED
@@ -163,6 +163,16 @@ All under `company/state/`, all owned by the CEO:
163
163
  6. Business-policy open questions. Agents run on tagged fallbacks; the owner
164
164
  answers the question.
165
165
 
166
+ ## The Workflow tool is outside enforcement
167
+
168
+ The Workflow tool runs its own internal `agent()` spawns that fire NO PreToolUse
169
+ events - the hooks never see them, so `guard_models` cannot pin their model and
170
+ they inherit the main-loop model. It is therefore FORBIDDEN by default in
171
+ company projects. It is permitted only with explicit owner authorization AND a
172
+ `model` pin in EVERY `agent()` call, including all early-stage agents: resuming
173
+ a dead workflow re-runs the incomplete early-stage agents live, so partial
174
+ pinning does not hold.
175
+
166
176
  ## Writing discipline
167
177
 
168
178
  All writing in this repository stays hook-clean: straight quotes, ' - ' rather
@@ -7,6 +7,13 @@
7
7
  "security-reviewer": "opus", "devops-engineer": "opus",
8
8
  "docs-librarian": "opus", "ideation-strategist": "opus"
9
9
  },
10
+ "builtins": {
11
+ "$comment": "Built-in agent types (Explore, general-purpose, Plan, claude) have no .claude/agents frontmatter pin, so a bare spawn silently inherits the SESSION model - guard_models therefore requires an explicit matching model override for these. The Workflow tool is hook-invisible (its agent() spawns fire no PreToolUse events) and is forbidden by default - see company/METHOD.md.",
12
+ "Explore": "opus",
13
+ "general-purpose": "opus",
14
+ "Plan": "opus",
15
+ "claude": "opus"
16
+ },
10
17
  "pricing": {
11
18
  "$comment": "OQ-W1-01 estimate only, NOT billing truth - used by .claude/hooks/cost_capture.py for rough spend visibility in company/state/costs.log and /standup. USD per million tokens (MTok). cache_write is 1.25x input, cache_read is 0.1x input.",
12
19
  "opus": {"input": 15, "output": 75, "cache_write": 18.75, "cache_read": 1.5}
@@ -13,4 +13,10 @@ fallbacks until a row lands here._
13
13
  | 5 | 2026-07-10 | Enforcement design direction | Owner REJECTED budgets/role bans twice; approved principle-derived gates (mechanism 5 + written-record consistency) and demanded low-token injection + GitHub tracking gate | all future gate design |
14
14
  | 6 | 2026-07-15 | cli-update delivery (update subcommand + provenance manifest, PR #57, issues #54-#56) | ACCEPTED - owner: "test it, the merge it to main"; CEO re-tested end-to-end (edit kept, .new written, deleted file restored, config untouched), merged 7726c99, integrated main verified (213 hooks + 40 CLI + 56 engine green), witnesses W-014..W-016 | CLI, installer, frozen registry |
15
15
  | 7 | 2026-07-15 | cli-self-update delivery (update refreshes the CLI first via npx handoff, PR #60, issue #59) | ACCEPTED - owner: "push to main, then to npm"; merged a09b463, integrated main verified (213 + 57 green), witnesses W-017..W-018 | CLI driver |
16
- | 8 | 2026-07-15 | Release v0.2.0 to npm (target: the release/0.2.0 merge commit; notes: PR #57/#60 bodies) | Owner ORDERED the publish ("push changes to npm", "push to main, then to npm"). Semver: minor (two additive features). Publish executes with AUTHORIZED=1 once npm auth exists; whoami was 401 at prep time | npm registry |
16
+ | 8 | 2026-07-15 | Release v0.2.0 to npm (tag v0.2.0 at 5913374; notes: PR #57/#60 bodies) | PUBLISHED 2026-07-15 by the OWNER manually from the clean tag clone (link-based npm 2FA - publishes are always owner-manual or git-CI, never agent-run). Registry verified: dist-tags.latest 0.2.0. Semver minor | npm registry |
17
+ | 9 | 2026-07-15 | DevMesh migration (owner: "use claude-company on this WITHOUT breaking custom changes... preserve DevMesh specific context") | DELIVERED - umbrella root git-initialized (machinery only), primitive company ported to company/, all customizations preserved, stale docs archived (nothing deleted). Verification: invariants gate PASS, 6 frozen surfaces probe-blocked. Found+fixed upstream #67/#68 | DevMesh polyrepo |
18
+ | 10 | 2026-07-15 | Release v0.2.1 to npm (patch: #64 enforcer arming, #67 merger fix + field-heal, #68 record-leak) | PREPPED by CEO (bump PR + tag on merge); publish is the owner's button per standing rule (link-based 2FA). SUPERSEDED by v0.2.2 (#12) before publish - registry never received 0.2.1 | npm registry |
19
+ | 11 | 2026-07-22 | model-routing-arming delivery (builtins enforcement + wiring gate + migration + Workflow doctrine, PR #77, issues #74-#76) | ACCEPTED - owner: "merge everything and give command to push to npm"; CEO merged cd07fb6, integrated main verified (hooks 222 + npm 61 + engine 111 green, gates stamped), live enforcement certified (contradict/bare exit 2, match 0, dormancy probe red), witnesses W-026/W-027/W-028 | hooks, models.json, installer, doctrine |
20
+ | 12 | 2026-07-22 | Release v0.2.2 to npm (rolls up unpublished 0.2.1 + #77 model-routing arming) | PREPPED by CEO; owner pushed tag v0.2.2 (at ad3dda8) but publish SUPERSEDED by v0.2.3 (#14) which adds #83 - registry goes 0.2.0 -> 0.2.3 directly | npm registry |
21
+ | 13 | 2026-07-23 | spawn-depth-shipping delivery (CC 2.1.21 depth-1 flattening; template env + installer env merge + guards, PR #83, issues #79-#82) | ACCEPTED - owner: "merge it"; merged 6061814, CEO independent verification (224/61/123 green, merge probes, byte-identity), witness W-029 | template settings, installer, tests |
22
+ | 14 | 2026-07-23 | Release v0.2.3 to npm (v0.2.2 content + #83 spawn-depth) | PREPPED by CEO per owner direction ("change to v0.2.3"); owner tags v0.2.3 at the release-PR merge commit and publishes from a clean tag clone (link-based 2FA, owner-manual); notes: PR #77/#83 bodies | npm registry |
@@ -14,6 +14,94 @@ witnesses/models/tests/audit. Owner acceptance recorded (DECISIONS #3).
14
14
 
15
15
  ## 2. Next actions, in order
16
16
 
17
+ 00000. test-infra-fixes SHIPPED 2026-07-26 (PR #85 merged 65bd070).
18
+ Self-authored by the CEO in the main checkout, auditor verdict SHIP,
19
+ CI 9/9 green across ubuntu+macos x node 18/20/22. Integrated main
20
+ re-verified by the CEO: hooks 224, npm test 61/0, install 96/0, all
21
+ stamped. Ubuntu job log confirms `Ran 224 tests ... OK` - the 121
22
+ previously-invisible tests passed on their first ever CI run.
23
+ Two defects, both found while scoping the multi-session engagement:
24
+ (a) tests/hooks/run_tests.sh exec'd test_hooks.py directly, so CI ran
25
+ 103 of 224 hook tests - the other 121, including ALL of
26
+ test_guard_provenance.py, never ran in CI. Now unittest discover
27
+ with -v (the -v preserves the per-test CI log lines that
28
+ unittest.main(verbosity=2) used to give).
29
+ (b) npm test was RED ON MAIN: tests/cli/test_cli.sh piped
30
+ `npm pack --dry-run --json` into JSON.parse, and npm 10.5.0 writes
31
+ the prepare lifecycle banner to STDOUT, so all 10 pack-manifest
32
+ assertions were dead (PASS 51 / FAIL 10). --silent fixes it;
33
+ --ignore-scripts does NOT. Auditor proved the packed file list is
34
+ byte-identical across npm 9/10/11 (91 files).
35
+ RELEASE FACTS CORRECTED 2026-07-26: v0.2.3 is ALREADY PUBLISHED on npm
36
+ (2026-07-25T16:17Z). Prior RESUME/STATUS claims that the registry sat
37
+ at 0.2.0 awaiting an owner tag were STALE - the owner had already
38
+ tagged and published. Registry versions: 0.1.0, 0.1.1, 0.2.0, 0.2.3.
39
+ Nothing needs publishing for the test-infra work: tests/ is not in the
40
+ pack list and company/briefs/** is excluded, so the tarball is
41
+ unchanged by it.
42
+ NEW P2 WORRY - the pack list excludes company/specs|briefs|
43
+ change-requests but NOT company/state, so company/state/{RESUME,STATUS,
44
+ WORRIES,DECISIONS}.md ship into every install. Verified against the
45
+ live 0.2.3 tarball: those four ARE in it. The runtime files
46
+ (costs.log, provenance-ledger.json, active-task.json, .cost-cursor.json,
47
+ gates.status) are untracked, so a CLEAN-CLONE publish leaves them out -
48
+ but publishing from a dirty working checkout WOULD leak them. Always
49
+ publish from a clean clone at the tag.
50
+ Next: multi-session-tasks (feature) - plan approved by the owner
51
+ 2026-07-26 at ~/.claude/plans/flickering-painting-pony.md, core-only
52
+ scope. active-task.json becomes N entries; the provenance ledger stops
53
+ wiping itself on slug change. Phase 0 spec via product-manager first.
54
+ NEW P1 WORRY, directly in scope for that engagement: dispatched
55
+ worktree agents CANNOT COMMIT - guard_commit judges them to be on main
56
+ because the harness pins payload cwd to the main checkout. That turns
57
+ every delegated build into self-authored work needing an audit.
58
+
59
+ 0000. spawn-depth-shipping SHIPPED 2026-07-23 (PR #83 merged 6061814,
60
+ closes #79-#82; witness W-029; brief archived pending next chore
61
+ pass - it is tracked at company/briefs/brief-spawn-depth-shipping.md
62
+ on main, move to shipped/ with the next closeout PR). CC 2.1.21
63
+ defaulted subagent spawn depth to 1 (flattens CEO->lead->dev);
64
+ template now ships env CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH="2" and
65
+ install/update merge env additively (user values win, non-dict env
66
+ replaced-safe, heredocs byte-identical 3587 chars). CEO independent
67
+ verification: 224/61/123 all green, own merge probes (pin "3"
68
+ survives, bare heals to "2"), risk low. This session armed via
69
+ settings.local.json; memory file spawn-depth-env-required.md saved.
70
+ guard_tests test_scope papercut hit a THIRD time (see WORRIES P2).
71
+ NOTE: not on npm until the release AFTER v0.2.2 - owner still owes
72
+ the v0.2.2 tag+publish (DECISIONS #12), then a v0.2.3 can carry
73
+ spawn-depth.
74
+ 000. model-routing-arming SHIPPED 2026-07-22 (PR #77 merged cd07fb6,
75
+ closes #74-#76; acceptance DECISIONS #11; witnesses W-026 wiring
76
+ assertion / W-027 merge byte-identity / W-028 bare-builtin block).
77
+ Integrated main verified by CEO: hooks 222 OK, npm 61, update
78
+ engine 111, --check 0, trace 21/21 after citation fix, live
79
+ certification (contradict/bare exit 2, match 0; dormancy probe:
80
+ Task|Agent group stripped -> --check exit 1 + fix-it). Worktree
81
+ and task branch removed; brief+spec archived to shipped/.
82
+ RELEASE 0.2.2 MERGED (PR #78, merge commit ad3dda8; task
83
+ release-0.2.2-closeout closed; audits recorded approved after the
84
+ verdict-parser workaround - see WORRIES P2). Integrated main green
85
+ + stamped (223/61/111), registry sealed 27/27. AWAITING OWNER
86
+ BUTTONS ONLY: tag v0.2.2 at ad3dda8, push tag, npm publish from a
87
+ clean tag clone (memory: npm-publish-owner-only; registry still on
88
+ 0.2.0 - both 0.2.1 and 0.2.2 unpublished until then). Next chore
89
+ pass: archive brief-release-0.2.2-closeout.md to shipped/, file
90
+ the guard_provenance verdict-parser issue and the guard_tests
91
+ test_scope-from-main issue from WORRIES. Near-miss worth
92
+ remembering: a .format arg mismatch made the wrong-override path
93
+ fail OPEN until the live replay caught it - pinned by
94
+ test_builtin_spawn_wrong_override_blocked.
95
+ 00. provenance-shipping SHIPPED 2026-07-15 (PR #65, d624cc3, closes #64;
96
+ witnesses W-019/W-020, 20/20): fresh installs now ship
97
+ company/provenance.json (enforcer armed by default); update NEVER
98
+ auto-arms, prints one notice line. Owner field audit found the gap.
99
+ PENDING OWNER BUTTON: 0.2.1 patch release to npm (0.2.0 on the
100
+ registry still has the gap - fresh 0.2.0 installs stay dormant).
101
+ Same flow as 0.2.0: bump PR, tag, owner publishes from clean tag
102
+ clone (memory: npm-publish-owner-only). Field guidance given: owner's
103
+ research repo stays disarmed until they create provenance.json after
104
+ Jul 28; gitignore data/ to stop stop_gate churn from training jobs.
17
105
  0. cli-update SHIPPED 2026-07-15: PR #57 merged (7726c99, closes #54-#56),
18
106
  owner acceptance DECISIONS #6, witnesses W-014..W-016 recorded (16/16),
19
107
  spec+brief archived to shipped/, worktree+branch removed, integrated main
@@ -29,14 +117,17 @@ witnesses/models/tests/audit. Owner acceptance recorded (DECISIONS #3).
29
117
  0c. cli-self-update SHIPPED 2026-07-15: PR #60 merged (a09b463, closes
30
118
  #59), acceptance DECISIONS #7, witnesses W-017..W-018 (18/18),
31
119
  integrated main 213+57 green. Worktree+branch removed.
32
- 0d. ACTIVE: npm-release-0.2.0 (owner ORDERED publish - DECISIONS #8, do
33
- not re-ask). On release/0.2.0 branch: version 0.1.1 -> 0.2.0,
34
- spec/brief archived, board rows. Remaining: commit branch -> PR -> CI
35
- -> merge -> `git tag v0.2.0 <merge commit> && git push origin v0.2.0`
36
- -> `AUTHORIZED=1 npm publish` (prepublishOnly runs CLI+TUI+install
37
- suites). ONLY blocker: npm whoami 401 - owner must run `npm login`
38
- interactively. active-task.json cleared at release close (test_scope
39
- was reset with the clear).
120
+ 0d. v0.2.0 PUBLISHED to npm 2026-07-15 (DECISIONS #8): tag v0.2.0 at
121
+ 5913374, owner published manually from the clean tag clone. FIELD
122
+ LESSON (memory npm-publish-owner-only.md): owner npm 2FA is
123
+ LINK-BASED - no OTP codes exist; publishes are ALWAYS owner-manual or
124
+ git-CI, agent only prepares (bump PR, tag, clean tag clone, verified
125
+ placeholder gates.config). NEVER publish from the working checkout -
126
+ local gates.config wiring fails the placeholder test. Witnesses
127
+ W-017/18 landed via PR #62 (fab01e1). Candidate next: publish via CI
128
+ workflow (owner hinted "or through git ci"), #36, #37, tarball ships
129
+ repo board state (P3 hygiene, install never copies it), roadmap
130
+ #1-#11.
40
131
  1. delegation-enforcement SHIPPED 2026-07-10 (PR #49, f9e5dae, closes
41
132
  #42-#47; acceptance DECISIONS #4). Close-out PR in flight if session
42
133
  died mid-close: witnesses W-011..W-013 recorded, brief archived to
@@ -3,13 +3,22 @@
3
3
  _Maintained by the CEO. Updated after every dispatch, merge, and CR decision._
4
4
  _Red stays red until proven green. Never average a status._
5
5
 
6
- _Last updated: 2026-07-15 - cli-update SHIPPED (PR #57 merged 7726c99, acceptance DECISIONS #6)._
6
+ _Last updated: 2026-07-22 - model-routing-arming SHIPPED (PR #77 merged cd07fb6, acceptance DECISIONS #11); release 0.2.2 prepped._
7
7
 
8
8
  ## Active tasks
9
9
 
10
10
  | Task | Class | Lead/Agent | State | Gates | Notes |
11
11
  |---|---|---|---|---|---|
12
- | npm-release-0.2.0 | release | CEO | IN FLIGHT | integrated main green (213+57) | v0.2.0 bump + close-out on release/0.2.0 branch -> PR -> merge -> tag v0.2.0 -> AUTHORIZED=1 npm publish. ONLY blocker: npm whoami 401 - owner must `npm login`. Release record DECISIONS #8. |
12
+ | test-infra-fixes | quick | CEO (self, audited) | SHIPPED 2026-07-26 (PR #85 merged 65bd070) | 224 hooks + 61 CLI + 96 install green on integrated main and stamped; CI 9/9 across ubuntu+macos x node 18/20/22; ubuntu log confirms "Ran 224 tests OK" | Two one-line test-infra defects. (1) CI ran 103 of 224 hook tests - run_tests.sh exec'd test_hooks.py directly, so 121 tests incl. all of test_guard_provenance never ran in CI. (2) npm test was RED on main - pack-manifest JSON parse broken by the npm 10.5.0 prepare banner on stdout, 10 dead assertions. Blocks everything downstream: a red local gate means no stamp, no commit. |
13
+ | multi-session-tasks | feature | not yet dispatched | SPEC READY - awaiting tracking issues then dispatch | - | active-task.json becomes N entries in one file so parallel sessions in one checkout stop overwriting each other; provenance ledger stops wiping itself on slug change. Plan approved by owner 2026-07-26, core-only scope. Phase 0 done: company/specs/spec-multi-session-tasks.md, 31 FRs / 11 BRs / 10 OQs, all fallbacks decided. PM caught a real hole in the approved plan: "ALL over non-hotfix entries" is vacuously TRUE on an empty list, which would have silently flipped guard_spec from block to ALLOW when no task is active - FR-MST-05 orders the empty check first. |
14
+ | spawn-depth-shipping | quick | developer | SHIPPED 2026-07-23 (PR #83 merged 6061814, closes #79-#82) | 224 hooks + 61 CLI + 123 engine green (dev AND CEO reruns); integrated main stamped | Template ships env CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=2; install/update merge env additively (user pin survives - proven); witness W-029. NOT yet on npm - registry still 0.2.0; owner has v0.2.2 tag+publish pending, and this lands in the NEXT release after that. |
15
+ | release-0.2.3 | release | CEO | PUBLISHED 2026-07-25 - CLOSED | integrated main green + stamped; CI 9/9 | Registry latest = 0.2.3 (published 2026-07-25T16:17Z); tags v0.2.0-v0.2.3 all on origin. The earlier "AWAITING OWNER TAG" state was stale bookkeeping - verified 2026-07-26 against `npm view claude-company versions`. Published tarball carries company/state/{RESUME,STATUS,WORRIES,DECISIONS}.md but NOT the runtime state files. DECISIONS #12-#14. |
16
+
17
+ ## Shipped (recent)
18
+
19
+ | Task | Date | Evidence |
20
+ |---|---|---|
21
+ | model-routing-arming | 2026-07-22 | PR #77 merged cd07fb6 (closes #74-#76); acceptance DECISIONS #11; witnesses W-026/W-027/W-028; live certification: builtin contradict/bare spawn exit 2, match 0, dormancy probe turns --check red; migration: builtins merge lands on install AND update automatically. |
13
22
 
14
23
  ## Wave position (programs only)
15
24
 
@@ -18,7 +27,12 @@ _Last updated: 2026-07-15 - cli-update SHIPPED (PR #57 merged 7726c99, acceptanc
18
27
  | adoption program 1-3 | all | SHIPPED 2026-07-10 | PRs 27/33/34/32/35 merged; acceptance DECISIONS #3 |
19
28
  | follow-up pair | docs-sync + adr-hardening | SHIPPED 2026-07-10 | PRs 38/39/40/41 merged; #28 + #31 closed; witnesses 10/10 on main |
20
29
  | cli-update | single workstream | SHIPPED 2026-07-15 | PR #57 merged (7726c99, closes #54-#56); integrated main 213+40+56 green; witnesses W-014..W-016; acceptance DECISIONS #6 |
21
- | cli-self-update | single workstream | SHIPPED 2026-07-15 | PR #60 merged (a09b463, closes #59); integrated main 213+57 green; witnesses W-017..W-018; acceptance DECISIONS #7 |
30
+ | cli-self-update | single workstream | SHIPPED 2026-07-15 | PR #60 merged (a09b463, closes #59); integrated main 213+57 green; witnesses W-017..W-018 (landed via PR #62); acceptance DECISIONS #7 |
31
+ | v0.2.0 npm release | release | PUBLISHED 2026-07-15 | PR #61 merged, tag v0.2.0 (5913374), owner published manually (link-based 2FA); registry latest=0.2.0; DECISIONS #8 |
32
+ | provenance-shipping | quick fix | SHIPPED 2026-07-15 | PR #65 merged (d624cc3, closes #64); fresh installs arm the delegation enforcer, update never auto-arms; witnesses W-019/W-020; NOTE: 0.2.0 on npm still has the gap - 0.2.1 patch release recommended (owner button) |
33
+ | settings-merger-fix | quick fix | SHIPPED 2026-07-15 | PR #69 merged (f17c3c4, closes #67); dedup per (matcher, command) in both engines; update HEALS broken field installs (hand-proven); witnesses W-021/W-022. DevMesh hand-patched meanwhile (its c1ecbf7) |
34
+ | pack-leak-fix | quick fix | SHIPPED 2026-07-15 | PR #71 merged (d714892, closes #68); record trees scaffold empty + tarball negations; seeded negative test; witnesses W-023/W-024 |
35
+ | devmesh-migration | consulting | DELIVERED 2026-07-15 | DevMesh polyrepo migrated to claude-company (its commits b3f0a47/7e08247/c1ecbf7): docs/team ported to company/, frozen registry live (6 surfaces probe-verified), make-gates mirrored, custom agents/skills/memory preserved, 2 upstream bugs found (#67/#68 - both now fixed) |
22
36
 
23
37
  ## Open CRs
24
38
 
@@ -9,14 +9,21 @@ wave/merge; P3 is polish._
9
9
 
10
10
  | P | Worry | What (one line) | CEO logic (one line) |
11
11
  |---|---|---|---|
12
+ | P1 | dispatched worktree agents cannot commit at all | guard_commit.git_cwd prefers payload cwd, which the harness pins to the MAIN checkout; a developer in .claude/worktrees/<slug> on task/<slug> is judged to be on main and blocked as "commit on protected branch". Reproduced 2026-07-26 on task/ci-test-discovery; the agent correctly refused to evade and left its work staged | The #26 git_cwd fix only helps when the session cwd really is the worktree, which is never true for a dispatched subagent. Today's workaround is the CEO committing from the main checkout on the task branch, which converts every delegated build into self-authored work needing an audit - i.e. it silently defeats the delegation economics. Candidate fix: resolve the branch from the edited file's worktree, or accept a worktree path as proof of branch. Fold into the multi-session engagement |
13
+ | P2 | CI npm may hide what local npm catches | tests/cli/test_cli.sh pack-manifest assertions were fully dead locally on npm 10.5.0 (prepare banner on stdout corrupts the JSON) while CI stayed green - 10 assertions were failing on main and CI never said so | Fixed via --silent in test-infra-fixes; the general lesson is that a suite passing in CI is not evidence it passes for a contributor. Candidate: pin or report the npm version in the CI log |
12
14
  | P2 | gates.config template-vs-local tension | On the product repo itself, tracked gates.config must stay placeholder (ships to installs) so stop_gate/guard_commit need uncommitted local wiring - a papercut every session | Consider gates.local.config override in run-gates.sh; candidate issue after wave 2 |
13
15
  | P3 | costs.log accuracy limits | Multi-model stop intervals book tokens to the last model; sub-half-cent stops log est=$0.00 | Known limits from wave-1 report; revisit if spend reporting starts driving decisions |
14
16
  | P2 | staging stales a provenance audit | work_hash includes diff --cached, so git add AFTER the auditor pass stales the audit even with identical content | Auditor worry #2: doc the order (stage, then audit, then commit) in ORCHESTRATOR step 7 wording next doctrine touch; operational for now |
15
17
  | P3 | git -C evades guard_commit subcmd parse | guard_commit.git_subcmd treats -C <dir> as the subcommand token, so `git -C x commit` bypasses commit gates (pre-existing, Mode C inherits) | Anti-adversary, out of doctrine scope; candidate issue against guard_commit |
16
18
  | P3 | Mode D lacks worktree-cwd exemption | Stop close-gate block message reads wrong if a worktree session ever gets an active-task.json (moot today - file is untracked) | Auditor worry #3; per-spec (mirrors stop_gate); revisit only if worktree sessions gain task state |
17
19
  | P2 | session_start 60-line cap can hide digest | Saturated RESUME+STATUS could truncate the active-task + execution digest lines | Lead worry P2, inherited behavior; candidate tail-reservation tweak in session_start |
20
+ | P1 | provenance audit verdict parser is substring-naive | guard_provenance.py:487 records the negative verdict if that literal phrase appears ANYWHERE in the audit payload - including the CEO's own dispatch prompt. Hit 3x on release-0.2.2, then a 4th time on test-infra-fixes where it BLOCKED the commit twice against two passing audits | Raised to P1: it now costs real dispatches, and the trap is near-inevitable because naming the verdict vocabulary is the natural way to prompt an auditor. PROVEN WORKAROUND: give the auditor a vocabulary that cannot poison the record (SHIP / SHIP-WITH-FIXES / HALT) and forbid it from emitting the negative token. Real fix: anchor the parse to the response's verdict line. Fold the vocabulary change into .claude/agents/auditor.md so no CEO has to remember it |
21
+ | P2 | SendMessage-resumed audits record no provenance | Only Task/Agent spawns fire the PostToolUse event that writes an audit into the ledger. Continuing an auditor via SendMessage produces a real audit that the ledger never sees, so the commit stays blocked and the reason looks wrong | Found on test-infra-fixes after two resumed rounds counted for nothing. Doctrine fix is cheap: a re-audit must be a fresh dispatch, never a resume. Candidate: say so in ORCHESTRATOR step 7 |
22
+ | P2 | guard_tests resolves test_scope from main checkout | Worktree builders cannot commit brief-granted test edits until the CEO sets test_scope in MAIN's active-task.json - has now blocked two workstreams (cli-update, model-routing-arming) | Candidate fix: resolve test_scope from the target worktree, or dispatch protocol sets it whenever a brief grants test dirs; follow-up brief |
23
+ | P3 | models.json merge is now a THIRD duplicated heredoc | install.sh/update.sh byte-identical coupling grew (settings, CLAUDE block, now models.json builtins) - divergence risk compounds | Same root cause as existing heredoc row; the byte-identity witness candidate (lead proposal 3) mitigates; fold into any future extraction task |
18
24
  | P2 | update.sh duplicates install.sh merge heredocs | The three merges (settings/mcp/CLAUDE block) + CC_BLOCK are copied verbatim; a future install.sh merge change silently diverges update | cli-update lead worry; candidate fixture test asserting install and update produce identical merges; extraction forbidden this pass by additive-only brief |
19
25
  | P3 | update spawns python3 per file (~3x/file) | ~180 spawns for 61 files - seconds today, slow at scale | Candidate manifest.py hashtree batch subcommand; not blocking |
20
26
  | P3 | manifest emission fails open at install | lib/ or package.json missing -> no manifest, update runs bootstrap-safe forever (never clean "updated") | By design (safe); revisit only if field reports show chronic manifest-less installs |
21
27
  | P3 | extensionless files count as source | guard_spec.is_source flags any extensionless file outside company/.claude/docs/.github (e.g. Makefile, a bare "report") - a non-code agent writing one hits the execution gate | Found in owner-requested gate probe 2026-07-15; md/json/state are proven exempt; candidate: extensionless allowlist if it ever bites |
22
28
  | P3 | guard_tests gates md inside tests/ | Any write under a tests/ segment is blocked without test_scope regardless of extension, incl. tests/**/*.md (docs-librarian MODULE.md case) | Defensible (tests are the oracle) but extension-blind; candidate: exempt NON_SOURCE_EXT under tests/ if doc syncs start colliding |
29
+ | P3 | background writers stale the gate stamp | Field report 2026-07-15: a training job writing untracked non-ignored files (data/) stales work_hash every turn - stop_gate demands gate reruns | By design (.gitignore IS the ignore mechanism); candidate one-line docs FAQ: gitignore background-process output dirs |
@@ -145,7 +145,133 @@
145
145
  "regex": false,
146
146
  "why": "Manifest byte-determinism contract: sorted keys, no clock field - the bin-vs-bare parity test depends on it",
147
147
  "added_at": "2026-07-14T22:13:07Z"
148
+ },
149
+ {
150
+ "id": "W-017",
151
+ "task": "cli-self-update",
152
+ "file": "lib/update.js",
153
+ "must_contain": "if (process.env.CC_SELFUPDATE_DONE) return null;",
154
+ "regex": false,
155
+ "why": "Handoff-exactly-once loop guard: the re-exec child never re-checks the registry",
156
+ "added_at": "2026-07-14T23:25:27Z"
157
+ },
158
+ {
159
+ "id": "W-018",
160
+ "task": "cli-self-update",
161
+ "file": "lib/update.js",
162
+ "must_contain": "if (versionCompare(latest, PKG.version) <= 0) {",
163
+ "regex": false,
164
+ "why": "Strictly-newer is the only re-exec trigger; equal or older proceeds silently with the current CLI",
165
+ "added_at": "2026-07-14T23:25:27Z"
166
+ },
167
+ {
168
+ "id": "W-019",
169
+ "task": "provenance-shipping",
170
+ "file": "install.sh",
171
+ "must_contain": "copy_if_absent \"$SRC/company/provenance.json\" \"$TARGET/company/provenance.json\"",
172
+ "regex": false,
173
+ "why": "Fresh installs ship the arming manifest - the delegation enforcer is armed by default in the field",
174
+ "added_at": "2026-07-15T01:01:51Z"
175
+ },
176
+ {
177
+ "id": "W-020",
178
+ "task": "provenance-shipping",
179
+ "file": "update.sh",
180
+ "must_contain": "note: delegation enforcer installed but disarmed - create company/provenance.json to arm",
181
+ "regex": false,
182
+ "why": "update never auto-arms - a payload refresh must not switch a project's enforcement regime",
183
+ "added_at": "2026-07-15T01:01:51Z"
184
+ },
185
+ {
186
+ "id": "W-021",
187
+ "task": "settings-merger-fix",
188
+ "file": "install.sh",
189
+ "must_contain": "have = commands_by_matcher(existing_groups) # issue-67: dedup per matcher",
190
+ "regex": false,
191
+ "why": "Settings merge dedups per (matcher, command) - reverting to command-per-event re-drops multi-matcher hook groups",
192
+ "added_at": "2026-07-15T10:34:04Z"
193
+ },
194
+ {
195
+ "id": "W-022",
196
+ "task": "settings-merger-fix",
197
+ "file": "update.sh",
198
+ "must_contain": "have = commands_by_matcher(existing_groups) # issue-67: dedup per matcher",
199
+ "regex": false,
200
+ "why": "update.sh merge block stays byte-identical to install.sh - the heal path for broken field installs depends on it",
201
+ "added_at": "2026-07-15T10:34:04Z"
202
+ },
203
+ {
204
+ "id": "W-023",
205
+ "task": "pack-leak-fix",
206
+ "file": "install.sh",
207
+ "must_contain": "\"$TARGET/company/change-requests\" # issue-68",
208
+ "regex": false,
209
+ "why": "Record trees are scaffolded empty, never copied - reverting to copy_tree_if_absent re-leaks repo records into git-clone installs",
210
+ "added_at": "2026-07-15T11:14:01Z"
211
+ },
212
+ {
213
+ "id": "W-024",
214
+ "task": "pack-leak-fix",
215
+ "file": "package.json",
216
+ "must_contain": "\"!company/briefs/**\",",
217
+ "regex": false,
218
+ "why": "Tarball belt - dropping the files-negations re-ships repo records via npm",
219
+ "added_at": "2026-07-15T11:14:01Z"
220
+ },
221
+ {
222
+ "id": "W-026",
223
+ "task": "model-routing-arming",
224
+ "file": ".claude/hooks/guard_models.py",
225
+ "must_contain": "def check_spawn_wiring",
226
+ "regex": false,
227
+ "why": "run_check wiring assertion - regression means the models gate reads green while Task|Agent enforcement is unwired (code-without-wiring)",
228
+ "added_at": "2026-07-22T15:23:53Z"
229
+ },
230
+ {
231
+ "id": "W-027",
232
+ "task": "model-routing-arming",
233
+ "file": "update.sh",
234
+ "must_contain": "BYTE-IDENTICAL to the",
235
+ "regex": false,
236
+ "why": "models.json merge heredoc coupling marker - install and update must migrate identically",
237
+ "added_at": "2026-07-22T15:23:53Z"
238
+ },
239
+ {
240
+ "id": "W-028",
241
+ "task": "model-routing-arming",
242
+ "file": ".claude/hooks/guard_models.py",
243
+ "must_contain": "inherit the session model",
244
+ "regex": false,
245
+ "why": "Bare builtin-spawn BLOCK path - regression means built-in spawns silently inherit the session model (the live incident)",
246
+ "added_at": "2026-07-22T15:24:05Z"
247
+ },
248
+ {
249
+ "id": "W-029",
250
+ "task": "spawn-depth-shipping",
251
+ "file": ".claude/settings.json",
252
+ "must_contain": "CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH",
253
+ "regex": false,
254
+ "why": "Auto-update-era guard: template must ship spawn depth 2 or every install silently flattens to depth 1 on the next Claude Code",
255
+ "added_at": "2026-07-22T18:50:20Z"
256
+ },
257
+ {
258
+ "id": "W-030",
259
+ "task": "test-infra-fixes",
260
+ "file": "tests/hooks/run_tests.sh",
261
+ "must_contain": "unittest discover",
262
+ "regex": false,
263
+ "why": "CI calls this runner; reverting to a direct exec silently drops CI from 224 hook tests back to 103 with no other symptom",
264
+ "added_at": "2026-07-26T17:30:00Z"
265
+ },
266
+ {
267
+ "id": "W-031",
268
+ "task": "test-infra-fixes",
269
+ "file": "tests/cli/test_cli.sh",
270
+ "must_contain": "--json --silent",
271
+ "regex": false,
272
+ "why": "Without --silent the npm prepare banner corrupts the pack JSON and all 10 pack-manifest assertions die as false MISSING, turning the local gate red",
273
+ "added_at": "2026-07-26T17:30:00Z"
148
274
  }
149
275
  ],
150
- "checksum": "1179d1ca054e151ab34c6a0535acc2969de82f42991939c48a66f6b1acc18b02"
276
+ "checksum": "a2bf1d5ece833826945887fde2d32814011ffa242c6916cd31fa05cfab5c3d17"
151
277
  }
@@ -80,6 +80,8 @@ Each role is one markdown file in `.claude/agents/`. The file's frontmatter sets
80
80
 
81
81
  The same file carries a `pricing` map (USD per million tokens) that `cost_capture.py` reads to estimate spend in `company/state/costs.log` and the `/standup` Spend line. Update it when your rates differ; leave a model out of it and the standup reports raw token counts for that model instead of dollars. The numbers are estimates for visibility, never a bill.
82
82
 
83
+ - **Arm or disarm the delegation enforcer**: `company/provenance.json` is the switch. A fresh install ships it, so the enforcer is armed by default; delete it to disarm every provenance mode (same rollout posture as `company/models.json`). An `update` never creates it - if a project lacks the file, update prints a one-line notice and leaves the enforcement regime unchanged, so refreshing the payload never silently arms a project.
84
+
83
85
  Two roles are load-bearing; change them carefully. `tech-lead` is the only agent allowed to spawn other agents, and `developer` carries the working rules every builder inherits.
84
86
 
85
87
  ## Adjust the founding defaults