Skip to content

Commit 25d2f9d

Browse files
authored
Merge pull request #121 from clkao/spacedock-ensign/cherry-pick-claude-team-narrowing
Narrow claude-team find_subagent_jsonl scan to team session dir
2 parents e2db211 + 60961e0 commit 25d2f9d

2 files changed

Lines changed: 97 additions & 11 deletions

File tree

docs/plans/cherry-pick-claude-team-narrowing.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,36 @@ land the cherry-pick.
161161
### Summary
162162

163163
Confirmed `b09051f4` is a clean, single-file cherry-pick onto current `main`. Expanded the entity body with a problem statement, approach with pre/post code shape, five acceptance criteria (each with a test column), and a proportional test plan (static suite required, cheap unit test with four cases flagged optional, no live CI). The independent reviewer's stderr-warning question is captured as informational only — the warning fires only on narrowed-succeeded-but-empty, not on every normal run. Scope kept tight: no prose changes, no test-predicate changes, no warning-tuning. Implementation stage is unblocked.
164+
165+
## Stage Report (implementation)
166+
167+
1. **Stay inside worktree `/Users/clkao/git/spacedock/.worktrees/spacedock-ensign-cherry-pick-claude-team-narrowing` on branch `spacedock-ensign/cherry-pick-claude-team-narrowing`.****DONE.** All commands executed inside the worktree; `git branch --show-current` confirmed the expected branch before and after cherry-pick.
168+
2. **Read the entity body's approach + acceptance criteria + test plan sections.****DONE.** Scope confirmed: cherry-pick `b09051f4`, verify single-file diff, run static suite; optional unit test evaluated below.
169+
3. **Cherry-pick commit `b09051f4` from `origin/spacedock-ensign/diagnose-opus-4-7-fo-regression`.****DONE.** Fetched origin ref, ran `git cherry-pick b09051f4`; applied cleanly with no conflicts. New commit on branch: `3d5b8f15`.
170+
4. **Verify the cherry-picked changes touch ONLY `skills/commission/bin/claude-team`.****DONE.** `git show HEAD --stat` reports `1 file changed, 63 insertions(+), 10 deletions(-)` against `skills/commission/bin/claude-team`. No other paths touched.
171+
5. **Run the static suite.****DONE.** Initial run after cherry-pick: `unset CLAUDECODE && make test-static``426 passed, 22 deselected, 10 subtests passed in 20.32s`. Re-run after the SyntaxWarning fix (item 9): `426 passed ... in 19.85s`. No new failures vs `main`.
172+
6. **Evaluate optional unit test for narrowed helper.****SKIPPED.** Rationale: the entity's test plan explicitly flags this as optional and "not required to land the cherry-pick"; the dispatch scope says "Do NOT modify tests unless the entity's approach specifies a unit test addition" — the approach section does not mandate it. Also, adding the test would expand scope beyond a pure cherry-pick and introduce surface area (fixture scaffolding, `os.path.expanduser` monkey-patching) that the dispatch explicitly warns against. Acceptance criterion #5 (fallback path) is indirectly exercised by the static suite's import+parse path and the helper's contract (unchanged return shape) matches the pre-commit behavior; follow-up task can add the four-case unit test if operational data motivates it.
173+
7. **Commit changes on the worktree branch.****DONE.** Cherry-pick produced commit `3d5b8f15`; SyntaxWarning fix (item 9) produced `5c278b68`; stage report commit follows this write.
174+
8. **Write this Stage Report (implementation) section.****DONE** (this section).
175+
9. **Fix SyntaxWarning on `claude-team` line 46 (captain scope expansion).****DONE.** Converted the `extract_stage_subsection` docstring to a raw string (`r"""..."""`) so the literal `` \` `` inside the example heading stops being interpreted as an invalid escape sequence. Verified: `python3 -W all skills/commission/bin/claude-team list-standing --workflow-dir /Users/clkao/git/spacedock/docs/plans` and `python3 skills/commission/bin/claude-team --help` — both produce clean stderr, no SyntaxWarning. Single-line diff (+1 / −1). Static suite re-run: 426 passed.
176+
177+
### Summary
178+
179+
Cherry-picked `b09051f4` onto `spacedock-ensign/cherry-pick-claude-team-narrowing` with no conflicts (commit `3d5b8f15`). Verified single-file scope — `skills/commission/bin/claude-team` (+63/−10). Static suite green: 426 passed, 22 deselected, 0 failures. Skipped the optional four-case unit test with rationale (entity flags optional, dispatch scope excludes test additions not mandated in approach). Passenger fix (commit `5c278b68`): converted `extract_stage_subsection` docstring to a raw string, silencing the pre-existing SyntaxWarning on line 46 (verified via `list-standing` and `--help` invocations). Ready for validation.
180+
181+
## Stage Report (validation)
182+
183+
1. **Read both prior stage reports in the entity body.****[x] DONE.** Read ideation and implementation reports. Noted the audit gap: the implementation report was written before commit `5c278b68` was appended mid-stage via a captain-scope expansion, so that commit is summarized as an addendum (item 9) rather than integrated into the top-level summary. Not a blocker; dispatch flagged this explicitly.
184+
2. **Audit gap noted.****[x] DONE.** Two commits landed on the worktree branch beyond the entering-state base: `3d5b8f15` (cherry-pick) and `5c278b68` (SyntaxWarning fix). Plus `4d9083f8` + `d7b9b63e` stage-report commits. The SyntaxWarning fix was out-of-scope for the original ideation but in-scope per captain approval mid-implementation; audit trail captured in item 9 of the implementation report.
185+
3. **Verify AC-1 (clean cherry-pick).****[x] DONE.** `git log --oneline -5` shows `3d5b8f15 fix: #182 narrow find_subagent_jsonl scan to one team's leadSessionId` on the worktree branch. `git show 3d5b8f15 --stat` confirms `1 file changed, 63 insertions(+), 10 deletions(-)` against `skills/commission/bin/claude-team` — single-file scope matches the ideation's claim exactly.
186+
4. **Verify AC-2 (semantic match with b09051f4).****[x] DONE.** Read lines 354-438 of `skills/commission/bin/claude-team` in the worktree. Structure matches the ideation's pre/post description: `find_subagent_jsonl` (lines 354-394) orchestrates narrowed-first-then-fallback; `_narrowed_subagent_patterns` (397-421) returns a single-element glob list scoped to `leadSessionId` or `None`; `_scan_subagent_meta` (424-438) is the extracted scan loop. Stderr warning fires only on the narrowed-succeeded-but-empty branch (lines 380-385).
187+
5. **Verify AC-3 (static suite).****[x] DONE.** `unset CLAUDECODE && make test-static``426 passed, 22 deselected, 10 subtests passed in 20.18s`. Meets the ≥ 426 threshold. No new failures vs main.
188+
6. **Verify AC-4 (empirical narrowing on this machine).****[x] DONE.** Broad scan haystack: 4863 `agent-*.meta.json` files across both broad patterns (0 at depth-1, 4863 at depth-2). 53 team configs present under `~/.claude/teams/*/config.json`. Sampled three configs: `test-project-rejection-pipeline` (narrow=0), `spacedock-plans-20260417-2342-f26a6ff0` (narrow=16), `sparkling-rolling-adleman` (narrow=9). Reduction: **~300–500× fewer meta-file opens** per lookup when narrowing applies. Matches the commit-message claim (empirically narrows ~4816 → ~9). `./skills/commission/bin/claude-team context-budget --name comm-officer` returned clean JSON (`resident_tokens: 42690`, `reuse_ok: true`, exit 0) — narrowed path exercised successfully via this workflow's live team config (`spacedock-plans-20260417-2342-f26a6ff0`). `list-standing --workflow-dir .../docs/plans` also ran clean.
189+
7. **Verify AC-5 (fallback path exists).****[x] DONE.** Code inspection (lines 387-388) confirms: when `_narrowed_subagent_patterns` returns `None`, control flows straight to `_scan_subagent_meta(broad_patterns, name)` with no warning. When narrowed returns a pattern but `_scan_subagent_meta` is empty, stderr warns then falls back (lines 380-386). Return contract (`str | None`) is unchanged from the pre-commit signature.
190+
8. **Verify SyntaxWarning fix (passenger).****[x] DONE.** Worktree binary: `python3 -W all ./skills/commission/bin/claude-team list-standing --workflow-dir /Users/clkao/git/spacedock/docs/plans 2>&1 | grep -i "syntaxwarning"` returns empty — **no warning**. Main-branch binary (baseline): `python3 -W all /Users/clkao/git/spacedock/skills/commission/bin/claude-team --help 2>&1 | grep -i "syntaxwarning"` returns `/Users/clkao/git/spacedock/skills/commission/bin/claude-team:46: SyntaxWarning: "\`" is an invalid escape sequence.` — warning present on main, absent on worktree. Fix is scoped correctly.
191+
9. **Recommend PASSED or REJECTED.****[x] DONE. Recommendation: PASSED.** All five acceptance criteria met with evidence. SyntaxWarning passenger fix verified scoped correctly. Static suite green. No regressions. Single-file cherry-pick surface area matches ideation.
192+
10. **Write this Stage Report (validation) section.****[x] DONE** (this section).
193+
194+
### Summary
195+
196+
Validation PASSED. Cherry-pick `3d5b8f15` applies cleanly as a single-file change (+63/−10 on `skills/commission/bin/claude-team`) and semantically matches `b09051f4` (narrowed-first-then-broad-fallback with stderr warning only on narrowed-succeeded-but-empty). Static suite: 426 passed, 0 regressions. Empirical narrowing confirmed on this machine: 4863-file broad haystack reduced to 9-16 files for active teams (~300-500× reduction), matching the commit-message claim. Fallback path preserved (code + live `context-budget --name comm-officer` invocation confirm). Passenger SyntaxWarning fix (commit `5c278b68`) verified scoped correctly — warning absent on worktree binary, present on main-branch binary. Audit gap noted: implementation report written before the SyntaxWarning commit landed, but item 9 of that report documents the addendum honestly. Ready for merge gate.

skills/commission/bin/claude-team

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ MODEL_ENUM_LIST = 'must be one of: sonnet, opus, haiku'
4141

4242

4343
def extract_stage_subsection(readme_path, stage_name):
44-
"""Extract the full ### {stage_name} subsection from a workflow README.
44+
r"""Extract the full ### {stage_name} subsection from a workflow README.
4545
4646
Accepts both backtick-quoted (`### \`work\``) and bare (`### work`) headings
4747
so fixtures and hand-authored workflows both parse cleanly.
@@ -354,17 +354,75 @@ THRESHOLD_PCT = 60
354354
def find_subagent_jsonl(name: str) -> str | None:
355355
"""Find the most recently modified subagent jsonl matching the given name.
356356
357-
Scans ~/.claude/projects/*/subagents/agent-*.meta.json for agentType matching name.
358-
Also scans with session subdirectory: ~/.claude/projects/*/*/subagents/agent-*.meta.json.
357+
Looks up the team config containing a member named `name`, reads its
358+
`leadSessionId`, and globs only that session's subagents directory:
359+
~/.claude/projects/*/{leadSessionId}/subagents/agent-*.meta.json. On a
360+
populated machine this narrows the haystack from thousands of files
361+
(every project's subagents) to dozens (one session's subagents).
362+
363+
Falls back to the broad scan if no team config is found, no
364+
leadSessionId is set, or the narrowed scan returns nothing — the broad
365+
scan stays correct, just slow. A warning is emitted to stderr in the
366+
fallback case.
367+
368+
Scans agent-*.meta.json for agentType matching name.
359369
Returns the sibling .jsonl path, or None.
360370
"""
361371
home = os.path.expanduser("~")
362-
patterns = [
372+
broad_patterns = [
363373
os.path.join(home, ".claude", "projects", "*", "subagents", "agent-*.meta.json"),
364374
os.path.join(home, ".claude", "projects", "*", "*", "subagents", "agent-*.meta.json"),
365375
]
366376

367-
matches = []
377+
narrowed = _narrowed_subagent_patterns(home, name)
378+
if narrowed is not None:
379+
matches = _scan_subagent_meta(narrowed, name)
380+
if not matches:
381+
print(
382+
f"warning: narrowed subagent scan for {name!r} found nothing; "
383+
f"falling back to broad ~/.claude/projects scan",
384+
file=sys.stderr,
385+
)
386+
matches = _scan_subagent_meta(broad_patterns, name)
387+
else:
388+
matches = _scan_subagent_meta(broad_patterns, name)
389+
390+
if not matches:
391+
return None
392+
393+
matches.sort(key=lambda x: x[0], reverse=True)
394+
return matches[0][1]
395+
396+
397+
def _narrowed_subagent_patterns(home: str, name: str) -> list[str] | None:
398+
"""Return narrowed glob patterns for a subagent jsonl scoped to one team session.
399+
400+
Returns None when no team config containing `name` declares a leadSessionId.
401+
"""
402+
teams_pattern = os.path.join(home, ".claude", "teams", "*", "config.json")
403+
for config_path in glob.glob(teams_pattern):
404+
try:
405+
with open(config_path, "r") as f:
406+
config = json.load(f)
407+
except (json.JSONDecodeError, OSError):
408+
continue
409+
member_names = {m.get("name") for m in config.get("members", []) if isinstance(m, dict)}
410+
if name not in member_names:
411+
continue
412+
lead_session_id = config.get("leadSessionId")
413+
if not lead_session_id:
414+
continue
415+
return [
416+
os.path.join(
417+
home, ".claude", "projects", "*", lead_session_id, "subagents",
418+
"agent-*.meta.json",
419+
),
420+
]
421+
return None
422+
423+
424+
def _scan_subagent_meta(patterns: list[str], name: str) -> list[tuple[float, str]]:
425+
matches: list[tuple[float, str]] = []
368426
for pattern in patterns:
369427
for meta_path in glob.glob(pattern):
370428
try:
@@ -377,12 +435,7 @@ def find_subagent_jsonl(name: str) -> str | None:
377435
matches.append((mtime, jsonl_path))
378436
except (json.JSONDecodeError, OSError):
379437
continue
380-
381-
if not matches:
382-
return None
383-
384-
matches.sort(key=lambda x: x[0], reverse=True)
385-
return matches[0][1]
438+
return matches
386439

387440

388441
def extract_resident_tokens(jsonl_path: str) -> int | None:

0 commit comments

Comments
 (0)