Skip to content

Ship iteration 2 MCP tools: 12 new verbs + rule-doc drift gate - #1981

Merged
jwbron merged 30 commits into
mainfrom
egg/issue-1917
Apr 24, 2026
Merged

Ship iteration 2 MCP tools: 12 new verbs + rule-doc drift gate#1981
jwbron merged 30 commits into
mainfrom
egg/issue-1917

Conversation

@james-in-a-box

Copy link
Copy Markdown
Contributor

Iteration 1 of the agent-facing MCP surface (#1765, merged as f24110b)
shipped 18 verbs and the mechanism to add more. This PR ships iteration 2:
12 additional verbs that complete the #1765 capability audit so agents
never need to shell out to egg-* CLIs for normal agent-role work
(AC: issue #1917).

Key changes

  1. Contract read + state-machine writes (P0, closes Agents shell out to egg-contract show via Bash — no MCP equivalent exists #1955) — ships
    mcp__sdlc__show_contract (with optional fields= projection;
    unknown-field raises HandlerError), mcp__task__add_commit,
    mcp__task__update_notes, mcp__phase__complete_phase,
    mcp__sdlc__verify_criterion. The live issue-1556 pipeline was
    caught shelling out to egg-contract show --json | python3 -c ...
    this closes that gap.
  2. BRC peer-read + overseer surface (P1) — ships
    mcp__brc__read_peer_artifact (reads local .egg-state/brc-history/*.json
    with limit/cursor pagination; cli_command=None net-new capability),
    mcp__progress__overseer_alert wrapping egg-orch overseer alert,
    and mcp__progress__query_status (REST-backed pipeline-status read
    used by the overseer role; cli_command=None per decision-13).
  3. Checkpoint namespace (P1, core 3 only per decision-3) — new
    mcp__checkpoint__{list,show,search} namespace. Backed by three
    pure helpers (_collect_checkpoints / _load_checkpoint /
    _search_checkpoints) extracted from
    shared/egg_contracts/checkpoint_cli.py so CLI and handler share
    one code path. List/search paginate via limit/cursor to stay
    under the 60 s MCP timeout.
  4. mcp__task__mark_gap (P2, no-CLI capability) — tester-to-coder
    coverage-gap handoff written to a new Task.gaps field on
    shared/egg_contracts/models.py:115 via the existing gateway mutate
    path. No new endpoint or CLI per decision-4. Old contracts load as
    gaps: [] (default), so consumers see a stable shape.
  5. Rule-doc sweep + two-way drift gate + decision-13 gate — every
    iter-2 tool with a CLI counterpart gets a Prefer this over … note
    in sandbox/agent-config/rules/*.md and hitl_editing_rules.md. A
    new test_rule_doc_drift.py asserts (a) every such note resolves
    to a TOOL_REGISTRY entry, (b) every tool with cli_command != None
    has a matching rule-doc entry, and (c) every cli_command=None
    registration has a handler docstring mentioning "no CLI" or
    "no-CLI" (closes the decision-13 gap that was previously
    untested). docs/reference/agent-tools.md is refreshed; Phase 6
    asserts len(TOOL_REGISTRY) == 30 and the 6-namespace set via a
    derived check so future iterations can't drift the prose count.

Impact

  • Sandbox agents on the default harness (claude_agent_sdk) can drop
    every egg-contract show | python3 -c pattern and its cousins.
  • Reviewer-role agents gain a structured way to read peer proposal
    history without hand-grepping .egg-state/brc-history/*.json.
  • Tester-role agents gain a first-class gap-handoff primitive instead
    of freeform NACK reasons.
  • Overseer-role agents get both alert and status-query verbs in one
    iteration — no more GET /api/v1/pipelines/<id>/status from Python
    scripts outside the sandbox.
  • Anchor verbs, directed message send/poll, and phase_get_context
    field promotion remain deferred per decisions 2, 14, and 6 — each
    gets a post-merge follow-up issue. The phantom egg-orch anchor ...
    CLI references in orchestrator.md:20-24 also persist until iter 3.

Test Plan

  • Automated:
    • Per-handler unit tests under tests/sandbox/egg_agent_tools/handlers/
      covering success, validation error, and gateway-error paths for all
      12 verbs.
    • Drift-gate entries in tests/tools/test_mcp_cli_drift.py for every
      tool with a CLI counterpart (9 of 12).
    • New tests/tools/test_rule_doc_drift.py asserting the two-way
      Prefer this over …TOOL_REGISTRY invariant AND the
      decision-13 docstring-rationale gate for cli_command=None verbs.
    • test_prompt_nudge_drift extended for the new verbs + checkpoint
      namespace; derived assertions len(TOOL_REGISTRY) == 30 and
      namespace-set == {sdlc, brc, phase, progress, task, checkpoint}.
    • Pagination boundary tests for brc_read_peer_artifact,
      checkpoint_list, checkpoint_search (empty, single, exact-limit,
      beyond-limit, bad-cursor).
    • Pydantic round-trip test confirming Task.gaps default is [];
      existing contract fixtures continue to validate; new fixture with
      populated gaps also validates.
  • Manual (PR reviewer):
    1. Spawn a sandbox agent; call mcp__sdlc__show_contract on a live
      pipeline; confirm output matches egg-contract show --json.
    2. Call mcp__brc__read_peer_artifact paging through a producer's
      history; confirm entries match raw brc-history files.
    3. Call mcp__task__mark_gap; confirm the gap appears in
      egg-contract show under the target task.
    4. Confirm docs/reference/agent-tools.md reports 30 verbs / 6
      namespaces and SYSTEM_PROMPT_NUDGE lists all 30 at server import.

Manual Steps

Pre-merge: none. No orchestrator restart, no migrations, no new secrets.
EGG_MCP_TOOLS stays default-on per decision-9.

Post-merge:

  • Run one pipeline end-to-end (refine → plan → implement) to burn in the
    new tools under live load.
  • Open follow-up issues for (a) anchor-trio third iteration INCLUDING
    retraction of the phantom egg-orch anchor ... CLI references in
    sandbox/agent-config/rules/orchestrator.md:20-24, (b) EGG_MCP_TOOLS
    flag removal, (c) phase_get_context field promotion. Each is tracked
    separately and is non-blocking for this PR.

Pipeline Context

Pipeline: issue-1917
Issue: #1917

Per-phase BRC transcripts: refine, plan, implement.

Authored-by: egg

egg-orchestrator and others added 25 commits April 23, 2026 22:20
Analyzes the scope of iteration 2 against the #1765 capability audit.
Confirms iteration 1 actually shipped 18 tools across 5 namespaces
(sdlc/brc/phase/progress/task); identifies ~15 remaining verbs across
contract read/write (per #1955), peer_read_artifact (iter-1 TD9),
checkpoint surface, overseer alert, task_mark_gap (new), and anchor
ops (REST endpoints exist, no CLI). Recommends Option B (full audit in
one PR) with Option C as safety valve. Registers 13 multi-choice
decisions + 4 open-ended feedback questions for human input.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add decision-14 (send_message/poll_messages semantics) to open-questions
  summary table; corrects count (14 decisions + 4 feedback = 18 items)
- Add plan-phase carry-over notes section capturing reviewer_refine's
  non-blocking suggestions: Option C split-trigger concretisation,
  phantom-anchor-CLI rule-doc retraction, docs-refresh must-includes,
  task_mark_gap sub-issue consideration, P0 decomposition hint, and
  close-proximity completion-verb description guidance

Addresses reviewer_refine ACK feedback; analysis intent unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Decompose iter-2 into 6 phases covering the 11 verbs resolved by the
refine decisions: Phase 1 (P0 contract read/write — closes #1955),
Phase 2 (BRC peer-read + overseer alert), Phase 3 (checkpoint core-3
namespace), Phase 4 (task_mark_gap no-CLI capability), Phase 5
(rule-doc sweep + two-way drift gate), Phase 6 (integration tests).
All tasks land in one PR per the one-issue/one-workflow rule.
Identifies 13 risks across security (R1/R2/R10), performance (R4/R8/R11),
compatibility (R3/R5/R6/R7/R13), and correctness (R9/R12). Two are
medium-severity security risks (R1: verify_criterion authz relies on
gateway enforcement per decision-7; R2: brc_read_peer_artifact
path-traversal on no-CLI verb per decision-8). Overall risk MEDIUM.

Flags one item for human review: confirmation that the orchestrator's
/api/v1/contract/mutate path rejects non-reviewer writes to
acceptance_criteria.*.verified before verify_criterion ships as MCP.
All other risks have named mitigations that map to concrete
task_planner tasks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tions

Scope is 11 verbs across 4 existing namespaces + 1 new `checkpoint`:
mcp__sdlc__{show_contract,verify_criterion}, mcp__task__{add_commit,
update_notes,mark_gap}, mcp__phase__complete_phase, mcp__brc__{
read_peer_artifact,overseer_alert}, mcp__checkpoint__{list,show,search}.
Anchor (3 verbs) deferred per decision-2; directed peer messaging
deferred per decision-14.

Aligned with the 14 refine-phase HITL resolutions and supplementary to
the task_planner's concrete plan at drafts/1917-plan.md. Covers
mechanism reuse (iter-1 handler/@tool/drift-gate stack verbatim),
handler layering for shared→sandbox package boundary, schema strategy,
pagination design, error discipline, and two-way rule-doc drift gate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address three blocking items from reviewer_plan:

1. TASK-4-1 file path — `shared/egg_contracts/schema.py` does not exist;
   correct to `shared/egg_contracts/models.py:115` (Task Pydantic model)
   plus `validator.py` and `.egg/schemas/contract.schema.json`.

2. `overseer_query_status` silently dropped — add `mcp__progress__query_status`
   as TASK-2-3 (REST-backed, cli_command=None). Verb count 11→12; total
   surface 29→30.

3. TASK-3-1 "mirroring iter-1" misleading — iter-1 handlers don't import
   from contract_cli; rewrite to spell out the actual refactor (extract
   `_collect_checkpoints`/`_load_checkpoint`/`_search_checkpoints` pure
   helpers, bound net delta ≤ +60 lines).

Also address non-blocking items: split TASK-1-3 into 1-3a/1-3b; move
overseer_alert from `brc` to `progress` namespace; pin show_contract
unknown-field behavior to raise HandlerError; add decision-13
docstring-rationale assertion as TASK-5-2 assertion C; add derived
count/namespace assertions in Phase 6; specify back-compat empty-list
for old contracts; call out phantom anchor CLI in Risks.
Blocking fixes:
- Correct verb count: 13 → 11 (16 audit − 3 anchor (decision-2) − 2
  directed messages (decision-14)); add explicit math line
- Rewrite R3: task_mark_gap uses the EXISTING /api/v1/contract/mutate
  path with new optional tasks[].gaps[] field (plan TASK-4-2). Risks
  reframed: (a) gateway mutate allow-list must admit phases.<p>.
  tasks.<t>.gaps[<n>]; (b) contract validator back-compat for
  pre-iter-2 contracts
- R5: new test file is tests/tools/test_rule_doc_drift.py (NEW), not
  an edit of existing test_mcp_cli_drift.py

Non-blocking fixes:
- R1 severity upgraded to "high_pending_confirmation" until gateway
  authz is confirmed; gating flagged BLOCKING
- R10 pinned to gateway-only role-check discipline matching decision-7
  (no in-handler EGG_AGENT_ROLE check); flagged for architect
  confirmation
- R6 softened from "MANDATORY" retraction to policy choice flagged
  for HITL
- R12 "doubles" → "+67%" (3→5 no-CLI verbs)
- Reconcile folded_into_existing: overseer_alert placed in brc
  namespace (agreed with architect/task_planner)
- Add overseer_query_status scope-miss human_review_flag (iter-2 body
  lists it but no producer placed it)
- Expand acceptance_criteria_for_plan_phase to cover R3/R10/no-CLI
  testing and overseer_query_status scope call

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds mcp__phase__query_pipeline_status as the audit's overseer_query_status
slot (was silently dropped in rev 1). Scope is now 12 verbs. Closes
decision-20 unconditionally to Option A (shared/egg_contracts/checkpoint_handlers.py).
Adds architectural_dependencies.gateway_authz_required and
documentation_requirements.agent_tools_md_structure sections so AC1.b is
actually wired up. Addresses non-blocking items: path-traversal hardening
requirement on read_peer_artifact; expanded brc vs progress rationale for
overseer_alert; line citation fix (30-46 → 32-46); verified EGG_MCP_TOOLS
wiring in shared/egg_agent/client.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address reviewer_plan NACK v2 blocking + non-blocking items:

BLOCKING: TASK-2-1 brc-history filename was wrong (`<role>-*.json`);
actual format is `<pipeline-id>-<phase>.json` per
orchestrator/routes/pipelines.py::_write_brc_history (line 5125).
Rewrite handler params to `phase` + `peer_role` (filtered inside the
file); pipeline_id resolved server-side via EGG_PIPELINE_ID; path
canonicalised + asserted under .egg-state/brc-history/ (traversal
hardening per risk_analyst R2).

NON-BLOCKING:
- TASK-3-1 test path typo: `shared/egg_contracts/tests/` →
  `tests/shared/egg_contracts/` (named 4 existing test files).
- TASK-3-1 public helper names (no underscore): collect_checkpoints
  / load_checkpoint / search_checkpoints — lets sandbox handler
  import cleanly, matches decision-18 disposition.
- TASK-2-3 query_status: set cli_command=("egg-orch","pipeline",
  "status") for drift-gate parity with overseer_alert (symmetric
  treatment; closes asymmetry flagged by reviewer); scope table +
  drift-gate count updated (9→10 of 12).
- TASK-2-1 corrupt-JSON handling: deterministic (count as
  skipped_malformed in next_cursor metadata) rather than logger
  dependency.
- Add "Open-question disposition" section resolving architect's
  decisions 15–20 so implement phase doesn't re-litigate.
…ress)

Rev 3 addresses reviewer_plan NACK: query_status verb name and namespace
must match the ACKed plan v3, not architect's rev-2 preference. Renamed
from mcp__phase__query_pipeline_status to mcp__progress__query_status.
Captures the retained trade-off (progress vs phase) in
namespace_choice_caveats for iter-3 context. Adds plan_phase_action_items
section noting the verify_criterion gateway-authz pre-flight check the
task_planner should echo into the plan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Blocking fix:
- Verb count 11 → 12 after architect rev-2 / plan v3 added
  mcp__progress__query_status (plan TASK-2-3). Summary line, scope_recap,
  acceptance criteria, and recommended_approach all updated.
- Added R14 risk assessment for query_status (low severity — read-only
  REST wrap with CLI parity via drift gate; payload-size and data-
  exposure risks documented but low).

Non-blocking fixes:
- R1 severity held at 'medium' with needs_human_review=true;
  downgrades to 'low' once architect's gateway_authz_required task
  (plan TASK-1-3) produces a passing test.
- R3 affected_components: dropped stale orchestrator/routes/contracts.py
  POST-endpoint reference; only shared/egg_contracts/ + handler touched.
- R6 retraction framing: softened from MANDATORY to 'iter-3 alongside
  mcp__anchor__*'; downgraded needs_human_review since the drift gate
  regex does not force retraction.
- scope_recap.folded_into_existing reconciled with plan v3: overseer_
  alert and query_status both fold into progress namespace (not brc).
- overseer_query_status human_review_flag marked resolved (placed by
  plan v3 as mcp__progress__query_status).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ools

TASK-5-1 + TASK-5-3 of the iter-2 MCP-tools plan. Updates the four
rule docs covered by the new two-way drift gate
(`tests/tools/test_rule_doc_drift.py`) and refreshes the agent-tools
reference to the post-iter-2 30-verb / 6-namespace surface.

- sandbox/agent-config/rules/contract.md — adds `Prefer this over
  `egg-contract <verb>`` lines for the 5 iter-2 contract verbs
  (`show_contract`, `add_commit`, `update_notes`, `complete_phase`,
  `verify_criterion`) plus the iter-1 contract verbs that didn't yet
  have rule-doc entries (`task_complete`, `register_open_question`,
  `request_feedback`); table picks up the new
  `egg-contract verify-criterion` row.
- sandbox/agent-config/rules/orchestrator.md — adds rule-doc entries
  for the 7 iter-1 BRC + heartbeat verbs and the 5 iter-1/2 progress
  verbs (`emit`, `signal_error`, `heartbeat`, `overseer_alert`,
  `query_status`); table picks up the new `egg-orch overseer alert`
  row.
- sandbox/agent-config/rules/checkpoint.md — adds rule-doc entries
  for the new `mcp__checkpoint__{list,show,search}` namespace; calls
  out the `limit`/`cursor` pagination convention and the decision-3
  scope ("core 3" only — `browse`/`context`/`cost` stay CLI-only).
- sandbox/egg_lib/data/hitl_editing_rules.md — adds `mcp__sdlc__show_contract`
  rule-doc entry so the HITL-edit harness inherits the same
  prefer-MCP guidance.
- docs/reference/agent-tools.md — bumps verb count 18 → 30 (was
  stale at 15 — also captures the iter-1 message verbs that landed
  after the original write-up); adds the 6-namespace inventory; adds
  per-tool rows for all 12 new iter-2 verbs; documents the
  `cli_command=None` rationale pattern (decision-13) and the
  `limit`/`cursor` pagination convention (decision-12); records the
  two-way rule-doc drift gate (decision-11); refreshes the testing
  matrix and known-limitations to match the post-iter-2 deferral
  list (anchor verbs, directed messaging, checkpoint
  browse/context/cost, `EGG_MCP_TOOLS` flag removal,
  phase-context field promotion). Architecture / async-error
  sections updated to call out the checkpoint helper-extraction path
  alongside the gateway path.

The rule-doc `Prefer this over `egg-…`` lines are pinned to the
iter-1 phrasing so `tests/tools/test_rule_doc_drift.py` (TASK-5-2,
tester-owned) can match them with a single regex; the registry tools
that use `cli_command=None` carry the no-CLI rationale in their
handler docstrings (also enforced by that test, assertion C). The
phantom `egg-orch anchor *` references in `orchestrator.md` are
deliberately retained per decision-2 — they will be retracted with
the iter-3 anchor PR.

Refs #1917, #1955.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Targeted fixups on top of 9469147 in response to reviewer_code's
ACK with non-blocking notes:

- sandbox/agent-config/rules/orchestrator.md (CLI table) — fix the
  `egg-orch overseer alert` flag list. The old row claimed
  `--subject`/`--body` (which are `egg-orch message send` flags); the
  real `cmd_overseer_alert` argparse at sandbox/egg_lib/orch_cli.py:1390
  takes `--anomaly`, `--priority {low,medium,high}`, `--summary`
  (required) plus optional `--detail` / `--recommend`.
- sandbox/agent-config/rules/orchestrator.md (Prefer-MCP list) — add
  a clarifying parenthetical on `mcp__progress__query_status` noting
  the deliberate namespace asymmetry (tool in `progress`, CLI in
  `pipeline` subtree per decisions 5 + 17) so skimmers don't trip on it.
- docs/reference/agent-tools.md (Pagination convention) — drop the
  `(e.g. base64-encoded offset)` parenthetical that leaked an
  implementation detail and rephrased to make the opacity contract
  the lead. The "agents must not interpret it" guidance was already
  there; the fix removes the contradicting hint.

The third reviewer item (read_peer_artifact return-shape ambiguity in
agent-tools.md:112) is left for now — that is a coordination item
with the coder on whether `skipped_malformed` is a top-level sibling
or embedded in the cursor metadata. The doc currently promises the
top-level shape; if the coder ships embedded-in-cursor instead, the
doc updates to match in a follow-up.

Refs #1917.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ships the iteration-2 MCP tool surface planned in #1917:

- **sdlc namespace (+2):** show_contract (with optional fields= projection,
  raise-on-unknown), verify_criterion (REVIEWER-only; gateway enforces).
- **task namespace (+3):** add_commit, update_notes (both share a
  _task_field_mutate helper), mark_gap (no-CLI; writes Task.gaps records).
- **phase namespace (+1):** complete_phase (transitions phases.N.status).
- **progress namespace (+2):** overseer_alert (wraps egg-orch overseer
  alert), query_status (wraps egg-orch pipeline status).
- **brc namespace (+1):** read_peer_artifact (no-CLI; reads local
  .egg-state/brc-history/<id>-<phase>.json with limit/cursor pagination).
- **new checkpoint namespace (+3):** list, show, search. Backed by three
  public helpers (collect_checkpoints, load_checkpoint,
  search_checkpoints) extracted from shared/egg_contracts/checkpoint_cli.py
  so CLI and handler share one dispatch path.

Contract/model changes:
- Task.gaps (list[TaskGap]) added to shared/egg_contracts/models.py with
  a default of []; existing contract fixtures keep validating.
- TaskGap model + .egg/schemas/contract.schema.json#/$defs/taskGap.
- roles.py FIELD_OWNERSHIP extended: phases.*.tasks.*.gaps and
  phases.*.tasks.*.gaps.* are implementer|reviewer-owned.

CLI shim refactors so the drift gate binds:
- cmd_show, cmd_add_commit, cmd_update_notes, cmd_complete_phase,
  cmd_verify_criterion (contract_cli) now delegate to the matching
  handlers; legacy stdout/stderr shape preserved byte-for-byte.
- cmd_overseer_alert, cmd_pipeline_status (orch_cli) delegate to the
  progress handlers with the same legacy output shape.
- cmd_list, cmd_show, cmd_search (checkpoint_cli) delegate to
  checkpoint_list/show/search handlers via the extracted helpers; the
  HTTP-preferred path is still tried first for gateway parity.

Decision-13 rationale added to handler docstrings for the no-CLI verbs
(brc_get_state, brc_list_blocking, phase_get_context,
phase_get_assigned_tasks, task_mark_gap, brc_read_peer_artifact,
check_hitl_answers). The tester-owned drift gate test will assert
these docstrings carry the "no CLI"/"no-CLI" literal.

Test updates (test_server.py, test_mcp_cli_drift.py) are deliberately
left for the tester role to land so the commit-authorship policy stays
honest.
Blocker #1 — brc_read_peer_artifact security (risk_analyst R2):
- Strip caller-supplied pipeline_id/issue/repo_path from both the
  handler req and the MCP schema. Identifier/repo root are now
  resolved server-side from EGG_ISSUE_NUMBER/EGG_PIPELINE_ID/
  EGG_REPO_PATH only.
- Canonicalise the resolved history-file path via .resolve() and
  assert .is_relative_to(<repo_root>/.egg-state/brc-history) before
  reading; any escape raises HandlerError.
- Reject peer_role values not matching [a-z0-9_-] with HandlerError.
- Drop the `path` echo from the response (information disclosure).
- Add `additionalProperties: false` to the tool schema so the agent
  can't smuggle unknown keys through.
- Track skipped-malformed records and surface them both as a
  top-level `skipped_malformed` integer and embedded in the cursor
  so pagination remains deterministic.

Blocker #2 — progress_query_status cross-pipeline-read (reviewer NACK #2):
- Reject caller-supplied pipeline_id if it disagrees with
  EGG_PIPELINE_ID. Env-set → caller-override must match; env-unset →
  caller value still accepted (operator shell path).

Blocker #3 — LAYERING violation (decision-20):
- Move collect_checkpoints, load_checkpoint, search_checkpoints from
  sandbox/egg_agent_tools/handlers/checkpoint.py into
  shared/egg_contracts/checkpoint_cli.py (where the plan's TASK-3-1
  spec said they should live).
- Sandbox handlers.checkpoint now imports the helpers from
  egg_contracts.checkpoint_cli inside each handler so shared/ no
  longer depends on sandbox/.
- CLI cmd_list / cmd_show / cmd_search in checkpoint_cli.py use the
  helpers directly (no more `from egg_agent_tools.handlers import
  checkpoint as _handlers`).

Blocker #4 — TaskGap model deviations (plan TASK-4-1):
- id now validates ^gap-[0-9]+$ (plan-specified "gap-<N>" shape);
- from_role / to_role / description each require min_length=1;
- to_role is now required (no default "coder" — handler still
  defaults at request layer so agents don't have to pass it);
- created_at is a `datetime` with default_factory=datetime.now(UTC)
  so malformed strings / empty timestamps can't sneak through at
  the model layer.
- JSON schema updated to pattern ^gap-[0-9]+$ and format:date-time
  for created_at; to_role is now required.

Blocker #5 — task_mark_gap TOCTOU race:
- Switch id generation from uuid hex slug to `gap-<max_existing + 1>`
  (plan-specified shape); add _next_gap_id helper.
- Wrap the read-then-append in a bounded retry loop
  (_GAP_RETRY_ATTEMPTS=3): on each attempt re-read the task, recompute
  next_gap_idx + gap_id, and try again. Retryable errors are string-
  matched on "index / out of range / already exists / conflict" in
  the gateway message; other failures bail immediately.
- Drop the `gap_id` override from the MCP schema — handler now owns
  id generation exclusively for race safety.
- Tool description names the role constraint explicitly
  ("tester role writes; coder role reads") per plan TASK-4-2.

Blocker #6 — phase_complete_phase non-atomicity (reviewer NACK #6):
- Swap the mutation order: link the commit FIRST (idempotent, retryable)
  then flip status. A mid-way failure leaves the phase not-yet-complete
  with its commit populated; callers retry the same request to progress.
- Handler docstring spells out the atomicity guarantee.
- CLI shim cmd_complete_phase now maps both gateway errors uniformly
  to "Error setting status: …" since the "Warning: Phase marked
  complete but failed to link commit:" branch no longer applies with
  the new ordering.

Test surface:
- sandbox/tests/ and tests/sandbox/egg_agent_tools/** / tests/tools/**
  remain tester-owned by commit-authorship policy; the tester role
  will update test_mcp_cli_drift.py for the new checkpoint helper
  pattern (PARSERS map + handler-dispatch AST walk) and land the
  per-handler unit tests that exercise the hardened validations.
Discharges the tester-role TASK-* assignments from the iter-2 plan:

- TASK-1-4 / TASK-1-5: per-handler unit tests for the 5 P0 verbs
  (show_contract, verify_criterion, add_commit, update_notes,
  complete_phase) plus drift-gate entries.
- TASK-2-4: read_peer_artifact pagination boundaries (empty, exact,
  beyond, bad cursor, corrupt records counted in skipped_malformed),
  env-only identifier resolution (caller override silently ignored per
  reviewer_code NACK #1a), peer_role slug validation, no-'path'-echo
  info-disclosure guard; overseer_alert + query_status drift entries +
  env-match pipeline_id enforcement (NACK #2).
- TASK-3-4: test_handlers_checkpoint.py for the decision-20 layering
  change (helpers moved to egg_contracts.checkpoint_cli); add
  shared-helper dispatch path to the drift test so cmd_list/show/search
  bind via the collect_checkpoints/load_checkpoint/search_checkpoints
  helpers instead of the AST-walked handler-import pattern.
- TASK-4-3: test_models_gaps.py — Task.gaps default, round-trip,
  back-compat against all .egg-state/contracts/ fixtures, JSON schema
  check, role-aware mutation validator.
- TASK-5-2: test_rule_doc_drift.py — three assertions (A: Prefer-line
  resolves to registered tool + CLI matches; B: every CLI-backed tool
  has a Prefer-line; C: every cli_command=None handler's docstring
  mentions 'no CLI'/'no-CLI' — decision-13 gate).
- TASK-6-1: test_server.py bumped to 30 tools + 6 namespaces, with
  derived assertions locking the prose count in agent-tools.md.
- TASK-6-2: test_full_tool_registry.py — integration test loading
  TOOL_LIST through claude_agent_sdk.create_sdk_mcp_server, plus
  state-machine-effect assertions on the four completion/mutation verbs
  (task_complete, phase_complete_phase, task_add_commit,
  verify_criterion).

All 529 new/updated handler/drift/rule-doc/registry tests pass; 27
skipped are unrelated legacy-contract fixtures with pre-iter-2 schema
drift (e.g. old agent role enums).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tester NACK on cycle 2: `ruff format --check` flagged 10 coder-owned
files with unformatted style.  No logic changes — just the standard
ruff formatter run against the files it flagged:

- sandbox/egg_agent_tools/handlers/brc.py
- sandbox/egg_agent_tools/handlers/phase.py
- sandbox/egg_agent_tools/handlers/progress.py
- sandbox/egg_agent_tools/handlers/sdlc.py
- sandbox/egg_agent_tools/handlers/task.py
- sandbox/egg_agent_tools/tools/__init__.py
- sandbox/egg_agent_tools/tools/brc.py
- sandbox/egg_lib/orch_cli.py
- shared/egg_contracts/checkpoint_cli.py
- shared/egg_contracts/models.py

`ruff format --check <paths>` now reports all 10 files formatted.
`make lint-python` gate no longer blocks.

All 467 tester-authored unit + drift tests still green after the
reformat (test_handlers_*, test_mcp_cli_drift, test_rule_doc_drift,
test_full_tool_registry, test_checkpoint_cli).

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No agent-mode design concerns.

The 12 new MCP verbs are well-aligned with agent-mode principles:

  • No excessive pre-fetching: All list/search operations use opaque cursor pagination with sensible defaults (staying under the 60s MCP timeout). show_contract supports fields= projection to narrow responses. No large diffs or file contents baked into prompts.
  • No structured output for humans: Handlers return structured data to the agent (which is appropriate for tool responses), but don't impose output formats on agent-generated human-facing content.
  • No post-processing pipelines: Handlers are thin gateway bridges — agent calls tool, handler mutates state via gateway, returns result. No parsing of agent output.
  • No rigid procedures: Rule-doc updates use "Prefer this over…" guidance, not mandated step-by-step procedures.
  • No direct LLM API calls: Zero anthropic/httpx/requests imports in the handler or tool layer.
  • No hardcoded model identifiers: Tool layer is model-agnostic.

The Task.gaps field (TaskGap model) is a good example of giving agents a structured handoff primitive without constraining how they use it — the tester role writes gaps, the coder role reads them, and neither is micromanaged on procedure.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

@james-in-a-box james-in-a-box Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contract Verification — Re-review (incremental)

Previous review: d11e4f6
Current HEAD: 9e01220

Delta Summary

One commit since last review:

  • 9e01220cf — "Add tests for re-review non-blocking findings: BRC pipeline ID validation, query_status inline format check, task_complete non-string commit"

Files changed: 3 test files only (no handler/tool/doc changes)

  • tests/sandbox/egg_agent_tools/test_handlers_brc.py (+46 lines)
  • tests/sandbox/egg_agent_tools/test_handlers_progress.py (+15 lines)
  • tests/sandbox/egg_agent_tools/test_handlers_task.py (+6 lines)

Verification of New Tests

All three tests address non-blocking findings from the previous review:

  1. TestBrcPipelineIdValidation (3 tests) — Verifies BRC handlers (brc_propose, brc_ack) reject path-traversal pipeline IDs (../other, a/b/c) via the _PIPELINE_ID_PATTERN regex at brc.py:21. Also includes a positive-path sanity check with issue-1917. Verified: Regex ^[a-zA-Z0-9_-]+$ at brc.py:21 feeds _require_pipeline_id at lines 30-36; both test assertions match the handler error message.

  2. TestProgressQueryStatusPipelineIdValidation (2 tests) — Verifies the inline format check at progress.py:240-241 that bypasses _require_pipeline_id (because progress_query_status has additional cross-pipeline-read hardening at lines 230-236 before format validation). Verified: The inline check uses the same _PIPELINE_ID_PATTERN and HandlerError as other handlers.

  3. test_non_string_commit_rejected (1 test) — Verifies that task_complete raises HandlerError("'commit' must be a string") for non-string values (e.g., commit=123) before reaching regex validation. Verified: Type check at task.py:125-126 guards the regex match.

Test Results

  • 6/6 new tests pass
  • 138/138 handler tests pass (brc + progress + task — no regressions)
  • 78/78 drift-gate tests pass (mcp_cli_drift + rule_doc_drift — no regressions)

Contract Compliance

The contract for issue-1917 has no formal acceptance_criteria or phases (both empty arrays — scope is defined by 21 resolved decisions and the PR description). The delta commit is test-only and does not alter any handler, tool registration, rule doc, or documentation. No previously verified behavior is affected.

Orchestrator note: egg-contract show / egg-contract verify-criterion unavailable (orchestrator unreachable). No formal criteria to mark.

Verdict

Approve — The delta adds targeted regression tests for three security-adjacent validation paths that were identified as non-blocking findings in the prior review. All tests pass, handler logic is correct, and no regressions detected. The PR is ready to merge.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

jwbron
jwbron previously approved these changes Apr 24, 2026
The contract was empty (phases=[], pr=null) because
_populate_contract_from_plan never ran against the final plan revision
on this branch.  Re-ran the orchestrator's populate logic (parse_plan →
to_contract_phases + PRMetadata) to fill in the 6 phases, 22 tasks, and
PR metadata from .egg-state/drafts/1917-plan.md.

Known caveat (tracked in #1988): TASK-1-3A and TASK-1-3B both normalize
to task-1-3 via the unanchored regex in plan_parser.py, so phase-1 has
two tasks sharing id task-1-3.  This matches the collision that would
have landed had populate run at plan-completion time; the parser fix
lives in #1988.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No agent-mode design concerns.

The delta since 9e01220 is the #1988 fix (plan parser alpha suffix preservation on TASK-<P>-<N><letter> ids). Changes are confined to plan_parser.py, models.py, the contract JSON, and associated tests — none of which touch the agent-facing MCP tool surface, prompt templates, or orchestration logic.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: iteration 2 MCP tools (89f8889)

The new commit fixes #1988 (alpha suffix on TASK-<P>-<N><letter> IDs) in the plan parser and regenerates the issue-1917 contract. The parser and model changes are correct in isolation. The problem is that the consumer side — which is what agents actually use — was not updated.


Blocking

1. _parse_task_id in task.py:42-62 rejects alpha-suffixed task IDs — all four MCP task handlers are broken for these IDs

_parse_task_id splits on - and calls int() on the parts. For task-1-3a:

stripped = "1-3a"
parts = ["1", "3a"]
int("3a")  → ValueError → HandlerError("Invalid task ID 'task-1-3a'")

This blocks task_complete, task_add_commit, task_update_notes, and task_mark_gap for any alpha-suffixed task. The regenerated issue-1917 contract on this branch has task-1-3a and task-1-3b — agents cannot interact with these tasks via MCP.

The deeper issue: even if parsing was fixed to extract 3 from 3a, the function returns positional indices (phase_idx, task_idx) used for array access via phases.{p}.tasks.{t}. Both task-1-3a (position 2 in the array) and task-1-3b (position 3) share task_number=3, so both would map to task_idx=2. The addressing model needs to change to ID-based lookup (read the contract, find the task by its ID string, use its array position) rather than parsing the ID into positional indices.

2. parse_task_id in contract_cli.py:155-190 has the identical issue

The CLI equivalent (egg-contract complete-task --task task-1-3a) fails the same way: int("3a")ValueError. Same fix needed.

3. TASK_PATTERN in plan_parser.py:172-175 silently drops alpha-suffixed tasks in markdown format

The markdown task parser regex:

r"\[TASK-(\d+)-(\d+)\]\s*(.+?)\s*(?:—|--|-)\s*Acceptance:\s*(.+)"

For [TASK-1-3a] Description — Acceptance: criteria, (\d+) matches 3, then the next character a doesn't match \]. The entire line fails to match, and the task is silently dropped. The YAML parser was fixed (lines 315, 499) but the markdown parser was not. Fix: add ([a-z])? after the second (\d+) group and pass it through as suffix.


Non-blocking

4. No end-to-end test exercises the roundtrip from plan → contract → handler

The new tests verify that the parser produces distinct contract IDs (task-1-3atask-1-3b) and that the model accepts them. No test verifies that these IDs can be consumed by the MCP handlers or CLI. An integration test calling task_complete({"task": "task-1-3a"}) against a contract with alpha-suffixed tasks would have caught blocking issues #1 and #2.


Summary

The alpha suffix fix (#1988) correctly handles the producer side (plan parser generates distinct IDs, model validates them). The consumer side (_parse_task_id in both the MCP handler and CLI) rejects these IDs outright, and the positional addressing model cannot disambiguate siblings. The feature is non-functional end-to-end.

— Authored by egg

@james-in-a-box james-in-a-box Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contract Compliance Re-verification: PR #1981 (Iteration 2 MCP Tools)

Delta scope: The PR branch has been rebased/squashed to a single commit 89f8889 since my last review at 9e01220. I have re-verified the full implementation against all 6 phases of the contract.

Test suite: 642 tests pass, 27 skipped (legacy fixture back-compat, expected), 0 failures.


Phase 1: Contract read + state-machine writes (P0) — VERIFIED

Task Status Notes
TASK-1-1 show_contract Fields projection + HandlerError on unknown field. Description: "Reads the contract; does not mutate state".
TASK-1-2 add_commit / update_notes Both registered, descriptions name state-machine effects. _task_field_mutate helper extracted and unit-tested.
TASK-1-3a complete_phase Registered, description names state-machine effect, handler + drift tests pass.
TASK-1-3b verify_criterion Description names REVIEWER role + state-machine effect. Handler docstring names REVIEWER role. Gateway-only enforcement per decision-7.
TASK-1-4 Unit tests 47 + 49 + 25 handler tests pass. Happy-path, validation error, GatewayError translation, unknown-field all covered.
TASK-1-5 Drift-gate entries All 5 Phase-1 tools in test_mcp_cli_drift.py, 48 drift tests pass.

Phase 2: BRC peer-read + overseer surface (P1) — VERIFIED

Task Status Notes
TASK-2-1 read_peer_artifact cli_command=None, docstring contains "no CLI". Path canonicalised via .resolve() + containment check. peer_role/phase validated against [a-z0-9_-]. Corrupt JSON → skipped_malformed counter. Pagination: empty/exact-limit/beyond-limit tested.
TASK-2-2 overseer_alert Registered under progress. message_type="OVERSEER_ALERT", to_role="all" hard-coded. Drift test passes.
TASK-2-3 query_status Returns /status JSON as-is. Rejects caller-supplied pipeline_id disagreeing with env. Drift test passes.
TASK-2-4 Tests + drift 26 BRC tests + 22 progress tests pass. All 5 pagination boundaries + traversal/cross-pipeline rejection covered.

Phase 3: Checkpoint namespace (core 3, P1) — VERIFIED

Task Status Notes
TASK-3-1 Refactor helpers collect_checkpoints, load_checkpoint, search_checkpoints extracted as public names. All 48 existing checkpoint CLI tests pass.
TASK-3-2 Handlers checkpoint_list/show/search return dicts. list/search honor limit/cursor. show raises HandlerError for unknown id.
TASK-3-3 Tool wrappers TOOL_NAMESPACES["checkpoint"] contains exactly the 3 expected tools. NAMESPACE_DESCRIPTIONS present.
TASK-3-4 Tests + drift 35 handler tests pass. Pagination empty/exact-limit/beyond-limit/bad-cursor covered. Drift-gate entries for all 3 verbs pass.

Phase 4: task_mark_gap (P2, no-CLI) — VERIFIED

Task Status Notes
TASK-4-1 Pydantic model TaskGap model with id, from_role, to_role, description, created_at, resolved. Task.gaps defaults to []. JSON schema updated. 23 legacy contracts load cleanly.
TASK-4-2 mark_gap handler cli_command=None, docstring mentions "no CLI". Generates monotonic gap-<N> ids. TOCTOU retry logic (3 attempts). Validates required fields.
TASK-4-3 Tests 15 handler unit tests + 111 Pydantic round-trip/back-compat tests pass.

Phase 5: Rule-doc sweep + drift gate — VERIFIED

Task Status Notes
TASK-5-1 Rule docs All iter-2 tools with cli_command != None have "Prefer this over" entries. Phantom anchor references preserved per decision-2.
TASK-5-2 Drift gate Three-way assertion (A: rule-doc → registry, B: CLI-backed → rule-doc, C: no-CLI → docstring rationale). 30 tests pass.
TASK-5-3 agent-tools.md 30 verbs across 6 namespaces documented. Pagination convention (decision-12) and no-CLI rationale (decision-13) sections present.

Phase 6: Integration tests + registration drift — VERIFIED

Task Status Notes
TASK-6-1 Nudge drift Registry count = 30, namespace set = 6, all tools present in nudge. 12 tests pass.
TASK-6-2 Full registry SDK integration loads TOOL_LIST cleanly. State-machine-effect phrases verified on completion/mutation verbs. 11 tests pass.

Contract Status Note

The contract shows all phases/tasks as pending — the coder agent has not yet run egg-contract complete-task or complete-phase. No top-level acceptance criteria are defined in this contract (the acceptance_criteria array is empty), so there are no criterion IDs to verify via egg-contract verify-criterion. The contract verification here is based on code review of the implementation against each task's inline acceptance criteria.

Decision Compliance

All 18 resolved decisions (1–18) are respected in the implementation:

  • decision-2: Anchor verbs deferred (not in this PR)
  • decision-3: Core 3 checkpoint verbs only (list/show/search)
  • decision-4: mark_gap is no-CLI (cli_command=None)
  • decision-7: verify_criterion gateway-only enforcement, documented in description
  • decision-12: Pagination via limit/cursor on list/search verbs
  • decision-13: No-CLI handlers have docstring rationale containing "no CLI"
  • decision-14: send_message/poll_messages deferred
  • decision-15: read_peer_artifact under brc namespace (not peer)
  • decision-17: First-class gaps[] field on task model

Verdict

Approve — all 20 contract tasks across 6 phases are implemented correctly, all acceptance criteria are met, 642 tests pass, and all 18 decisions are respected. No contract violations found.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No agent-mode design concerns in the delta since 89f8889.

The changes are handler-level correctness and input validation fixes (pipeline ID format validation, non-string commit rejection, assert→HandlerError in retry path, test CI portability). None alter the agent-facing tool surface, schemas, tool registration, rule docs, prompt templates, or orchestration logic. No new anti-patterns introduced.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@jwbron
jwbron merged commit b701c50 into main Apr 24, 2026
38 of 39 checks passed

@james-in-a-box james-in-a-box Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contract Compliance Re-Review — PR #1981

Re-review context: Previous review at commit 89f8889. That commit is no longer in the branch history (rebased away). Full review performed against current HEAD d4270d3.

Test suite: 526 passed, 31 skipped, 0 failures across all handler tests, drift tests, rule-doc drift tests, and model gap tests.


Phase 1: Contract read + state-machine writes (P0) — ALL PASS

Task Verdict Notes
task-1-1 show_contract PASS Registered; full/projected return; unknown-field raises HandlerError; description names SM effect
task-1-2 add_commit/update_notes PASS Both registered; descriptions name SM effect; _task_field_mutate unit-tested
task-1-3a complete_phase PASS Registered; description names SM effect; 12 unit test cases
task-1-3b verify_criterion PASS REVIEWER role in description + docstring; SM effect named
task-1-4 Unit tests PASS All handlers tested with happy-path, validation, GatewayError
task-1-5 Drift gate PASS All 5 tools covered in test_mcp_cli_drift.py

Phase 2: BRC peer-read + overseer surface (P1) — ALL PASS (one advisory note)

Task Verdict Notes
task-2-1 read_peer_artifact PASS Pagination, path canonicalization, skipped_malformed tracking, input validation all correct
task-2-2 overseer_alert PASS to_role="all" hard-coded; drift test passes
task-2-3 query_status PASS Cross-pipeline rejection works; drift test passes
task-2-4 Tests + drift PASS All boundary cases covered

Advisory (task-2-3): AC says "handler returns the /status JSON as-is (no projection)" but the handler projects a shaped subset (status, current_phase, pending_decisions, updated_at) with an optional include_raw flag. The projection is a reasonable design improvement (smaller payloads by default, raw available on request), but diverges from the literal AC. Non-blocking.

Phase 3: Checkpoint namespace (P1) — 1 PARTIAL

Task Verdict Notes
task-3-1 Refactor checkpoint_cli.py PARTIAL Net delta +119 lines vs AC limit of ≤+60 lines. Functional AC met (helpers pure, public, importable; existing tests pass)
task-3-2 Handlers PASS Return dicts; pagination + HandlerError correct
task-3-3 Tool wrappers PASS Namespace correct; nudge renders
task-3-4 Tests + drift PASS All pagination boundaries covered

Issue (task-3-1): The AC explicitly bounds the refactor at "net delta in checkpoint_cli.py ≤ +60 lines." Actual delta is +119 lines (222 additions, 103 deletions). The overage is almost 2x the budget. The helpers are well-structured and all functional criteria are met — this is a size bound violation, not a correctness issue.

Phase 4: task_mark_gap (P2) — ALL PASS

Task Verdict Notes
task-4-1 Pydantic model + schema PASS TaskGap model correct; gaps defaults to []; JSON schema updated; back-compat tested
task-4-2 mark_gap handler PASS Registered; ID generation; created_at stamped; "No CLI" in docstring
task-4-3 Tests PASS 15 handler test cases; round-trip + back-compat model tests

Phase 5: Rule-doc sweep + drift gate — 1 PARTIAL

Task Verdict Notes
task-5-1 Rule doc entries PASS All CLI-backed tools have "Prefer this over" entries; phantom anchor refs preserved
task-5-2 Drift gate test PASS All three assertions (A, B, C) implemented with guard-rail scenarios
task-5-3 agent-tools.md PARTIAL Reports 30 verbs / 6 namespaces; pagination and no-CLI sections present. Per-verb entries use table rows rather than individual subsections with schema/example/rationale as the AC specifies

Phase 6: Integration tests + registration drift — ALL PASS

Task Verdict Notes
task-6-1 test_server.py nudge drift PASS All behavioral requirements met via TestToolRegistry and TestSystemPromptNudge class methods
task-6-2 Full registry integration PASS Description non-empty; SM effect phrases on completion verbs; count anchors

Advisory (task-6-1): The AC and agent-tools.md reference test_prompt_nudge_drift as a function name, but the tests are class methods (TestSystemPromptNudge.test_each_namespace_appears_in_nudge, etc.). The agent-tools.md doc at line 171 has a stale test function reference. Non-blocking.


Summary

21 tasks verified: 18 PASS, 3 PARTIAL

The PARTIAL items:

  1. task-3-1checkpoint_cli.py line delta +119 vs ≤+60 AC limit (2x budget)
  2. task-5-3agent-tools.md uses table rows instead of per-verb subsections with schema/example
  3. task-2-3 (advisory) — query_status projects by default rather than returning raw as AC says

None of these are correctness issues. Items 2 and 3 are arguably design improvements over the literal AC. Item 1 is a measurable threshold violation.

Orchestrator note: egg-contract verify-criterion could not be run — orchestrator is unreachable. The top-level acceptance_criteria array in the contract is empty, so there are no AC IDs to mark.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: iteration 2 MCP tools (d4270d3)

The branch was rebased to a single commit since my last review at 89f8889. The #1988 alpha suffix fix was reverted and the issue-1917 contract was regenerated from the plan. I reviewed the full PR diff against origin/main since the old commit is unreachable.


Previous blocking issues — status

The three blocking issues from my last review (alpha-suffixed task ID parsing in _parse_task_id, parse_task_id, and TASK_PATTERN) are no longer applicable: the #1988 alpha suffix code was reverted, so the contract no longer contains alpha-suffixed task IDs. All handler and CLI code operates on numeric task IDs, which work correctly.

However, reverting #1988 while leaving TASK-1-3a / TASK-1-3b in the plan YAML (.egg-state/drafts/1917-plan.md:524,540) causes the parser to produce duplicate task-1-3 IDs in the contract (.egg-state/contracts/issue-1917.json:58,76). Per review rules, .egg-state/ artifacts are skipped, so this is noted for awareness only.

All previously-verified handler fixes (12 items from the review chain: mutation ordering, TOCTOU retry, path containment, null safety, format validation, type checks, null guards) are confirmed still present.


Blocking

1. Plan template regression — block scalar guidance removed (docs/templates/plan.md)

The PR removes |- block scalar syntax from all free-text fields in the YAML template (lines 79-103) and deletes the "YAML safety" callout warning that was added for #1974. The plan_parser.py module docstring (lines 23-40) similarly has its block scalar guidance stripped. The regression test TestYamlScalarQuotingRegression (tests/shared/egg_contracts/test_plan_parser.py:1838-1921) is also deleted.

This is a functional regression. Plain YAML scalars containing : sequences (like `code: type` or `sequence: int = 0`) cause yaml.safe_load to raise ScannerError, which forces the parser into the lossy markdown fallback, losing the pr: block (title, description, test plan). I confirmed this parse failure is still live:

yaml.safe_load('description: Add `sequence: int = 0` field')
# → ScannerError: mapping values are not allowed here

This exact pattern broke the issue #1932 pipeline, which motivated the #1974 fix. The fix had three parts: (a) block scalar syntax in the template, (b) a warning callout in the template, (c) regression tests. All three are removed in this PR. The parser code (yaml.safe_load) was not hardened to handle the plain-scalar case.

Fix: Restore |- block scalars in the template's YAML example and preserve the YAML safety callout. If the intent is to simplify the template, the parser must be made resilient to : in plain scalars first (e.g., pre-processing or quoting).


Non-blocking

2. BRC handlers missing null-safety pattern (brc.py:141,262,288)

result.get("data", {}) does not protect against data: null — the key exists with None value, so the default {} is unused. The subsequent .get(...) raises AttributeError.

This is the same pattern fixed in sdlc.py:51, phase.py:71, and progress.py:74 per review finding #5. The BRC handlers (brc_propose:141, brc_get_state:262, brc_list_blocking:288) were not included in that fix. The inconsistency within the PR is the issue — these handlers use result.get("data", {}).get(...) while all other handlers use (result.get("data") or {}).get(...).

Fix: (result.get("data") or {}) in all three locations.

3. docs/index.md:80 stale verb count

The PR ships 12 new verbs (30 total) and a new checkpoint namespace, but the Agent MCP Tools row still says "15 iteration-1 verbs" and lists only 5 namespaces (missing mcp__checkpoint__*). This is made stale by this PR — the adjacent line (81) is modified.

4. Undocumented scope: wait_for_status_change removal and #1932/#1975/#1976 reverts

The PR body frames this as "12 additional MCP verbs." It also removes significant functionality without documentation:

  • wait_for_status_change MCP tool, /status/wait HTTP route, Event.sequence, EventBus._sequence, egg_inflight_host_waits metric (all from #1932)
  • _build_rebase_cmd and --onto rebase form from #1976 (duplicate-by-content commit prevention)
  • Draft PR fallback banner from #1975
  • docs/releases/wait-for-status-change.md
  • EGG_ORCH_WAITRESS_THREADS default reverted 24 → 16

The #1932 code cleanup is itself clean (no dangling references), and wait_for_status_change has zero remaining references. But the #1976 rebase --onto form was preventing duplicate-by-content commits during push reconciliation — reverting it may reintroduce that issue. The PR description should document these removals and confirm the #1976 regression is acceptable.

5. cmd_complete_phase error message fidelity lost (contract_cli.py:623-631)

The old code had two distinct error paths: "Error setting status: ..." and "Warning: Phase marked complete but failed to link commit: ...". The new code maps all GatewayError to "Error setting status: {msg}". Since the handler now does commit-link FIRST (before status flip), the failure mode is different — but the loss of the specific warning message could affect scripts that grep stderr.

6. cmd_overseer_alert JSON output shape change (orch_cli.py:~1461)

The --json output changed from the full orchestrator response to resp.get("signal", {}). Scripts parsing the JSON output will break.


Summary

The handler layer, tool definitions, test suite, drift gates, and model changes are all solid — 30 tools across 6 namespaces, well-tested, properly documented in agent-tools.md and the rule docs.

The one blocking issue is the plan template regression (finding #1): removing the block scalar guidance reintroduces a known YAML parsing failure (#1974) that broke a real pipeline. The parser was not hardened to compensate.

— Authored by egg

@james-in-a-box

Copy link
Copy Markdown
Contributor Author

egg review completed. View run logs

26 previous review(s) hidden.

jwbron added a commit that referenced this pull request Apr 24, 2026
…[doc-updater] (#1990)

* docs: Update MCP tools docs for iteration 2 (30 verbs, 6 namespaces)

Update four docs that still referenced the old iteration-1 verb count
(15) and namespace set (5) after #1981 shipped 12 additional verbs and
the checkpoint namespace.

- docs/index.md: 15 iteration-1 verbs → 30 verbs across 6 namespaces
- sandbox/agent-config/rules/environment.md: same; add egg-checkpoint to prefer list
- sandbox/agent-config/rules/README.md: add mcp__checkpoint__* to namespace list
- docs/guides/sdlc-pipeline.md: 15-verb → 30-verb; update iter-2 status note
  (peer-read, checkpoint, overseer-alert, task-gap shipped; anchor deferred to iter-3)

* docs: Fix stale 15-verb cross-reference in release notes (review feedback)

---------

Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
james-in-a-box Bot added a commit that referenced this pull request Apr 24, 2026
* Initialize SDLC contract for issue #1917

* Refine #1917: iteration-2 MCP tools analysis

Analyzes the scope of iteration 2 against the #1765 capability audit.
Confirms iteration 1 actually shipped 18 tools across 5 namespaces
(sdlc/brc/phase/progress/task); identifies ~15 remaining verbs across
contract read/write (per #1955), peer_read_artifact (iter-1 TD9),
checkpoint surface, overseer alert, task_mark_gap (new), and anchor
ops (REST endpoints exist, no CLI). Recommends Option B (full audit in
one PR) with Option C as safety valve. Registers 13 multi-choice
decisions + 4 open-ended feedback questions for human input.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Refine #1917: fold reviewer_refine non-blocking notes into analysis

- Add decision-14 (send_message/poll_messages semantics) to open-questions
  summary table; corrects count (14 decisions + 4 feedback = 18 items)
- Add plan-phase carry-over notes section capturing reviewer_refine's
  non-blocking suggestions: Option C split-trigger concretisation,
  phantom-anchor-CLI rule-doc retraction, docs-refresh must-includes,
  task_mark_gap sub-issue consideration, P0 decomposition hint, and
  close-proximity completion-verb description guidance

Addresses reviewer_refine ACK feedback; analysis intent unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Persist statefiles after refine phase

* Persist HITL resolution after refine phase gate

* Plan #1917: iteration-2 MCP tools task decomposition

Decompose iter-2 into 6 phases covering the 11 verbs resolved by the
refine decisions: Phase 1 (P0 contract read/write — closes #1955),
Phase 2 (BRC peer-read + overseer alert), Phase 3 (checkpoint core-3
namespace), Phase 4 (task_mark_gap no-CLI capability), Phase 5
(rule-doc sweep + two-way drift gate), Phase 6 (integration tests).
All tasks land in one PR per the one-issue/one-workflow rule.

* Plan #1917: risk assessment for iter-2 MCP tool surface

Identifies 13 risks across security (R1/R2/R10), performance (R4/R8/R11),
compatibility (R3/R5/R6/R7/R13), and correctness (R9/R12). Two are
medium-severity security risks (R1: verify_criterion authz relies on
gateway enforcement per decision-7; R2: brc_read_peer_artifact
path-traversal on no-CLI verb per decision-8). Overall risk MEDIUM.

Flags one item for human review: confirmation that the orchestrator's
/api/v1/contract/mutate path rejects non-reviewer writes to
acceptance_criteria.*.verified before verify_criterion ships as MCP.
All other risks have named mitigations that map to concrete
task_planner tasks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* architect(#1917): architecture analysis aligned to refine-gate resolutions

Scope is 11 verbs across 4 existing namespaces + 1 new `checkpoint`:
mcp__sdlc__{show_contract,verify_criterion}, mcp__task__{add_commit,
update_notes,mark_gap}, mcp__phase__complete_phase, mcp__brc__{
read_peer_artifact,overseer_alert}, mcp__checkpoint__{list,show,search}.
Anchor (3 verbs) deferred per decision-2; directed peer messaging
deferred per decision-14.

Aligned with the 14 refine-phase HITL resolutions and supplementary to
the task_planner's concrete plan at drafts/1917-plan.md. Covers
mechanism reuse (iter-1 handler/@tool/drift-gate stack verbatim),
handler layering for shared→sandbox package boundary, schema strategy,
pagination design, error discipline, and two-way rule-doc drift gate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Plan #1917: revise task decomposition after reviewer_plan NACK

Address three blocking items from reviewer_plan:

1. TASK-4-1 file path — `shared/egg_contracts/schema.py` does not exist;
   correct to `shared/egg_contracts/models.py:115` (Task Pydantic model)
   plus `validator.py` and `.egg/schemas/contract.schema.json`.

2. `overseer_query_status` silently dropped — add `mcp__progress__query_status`
   as TASK-2-3 (REST-backed, cli_command=None). Verb count 11→12; total
   surface 29→30.

3. TASK-3-1 "mirroring iter-1" misleading — iter-1 handlers don't import
   from contract_cli; rewrite to spell out the actual refactor (extract
   `_collect_checkpoints`/`_load_checkpoint`/`_search_checkpoints` pure
   helpers, bound net delta ≤ +60 lines).

Also address non-blocking items: split TASK-1-3 into 1-3a/1-3b; move
overseer_alert from `brc` to `progress` namespace; pin show_contract
unknown-field behavior to raise HandlerError; add decision-13
docstring-rationale assertion as TASK-5-2 assertion C; add derived
count/namespace assertions in Phase 6; specify back-compat empty-list
for old contracts; call out phantom anchor CLI in Risks.

* Plan #1917 v2: address reviewer_plan NACK

Blocking fixes:
- Correct verb count: 13 → 11 (16 audit − 3 anchor (decision-2) − 2
  directed messages (decision-14)); add explicit math line
- Rewrite R3: task_mark_gap uses the EXISTING /api/v1/contract/mutate
  path with new optional tasks[].gaps[] field (plan TASK-4-2). Risks
  reframed: (a) gateway mutate allow-list must admit phases.<p>.
  tasks.<t>.gaps[<n>]; (b) contract validator back-compat for
  pre-iter-2 contracts
- R5: new test file is tests/tools/test_rule_doc_drift.py (NEW), not
  an edit of existing test_mcp_cli_drift.py

Non-blocking fixes:
- R1 severity upgraded to "high_pending_confirmation" until gateway
  authz is confirmed; gating flagged BLOCKING
- R10 pinned to gateway-only role-check discipline matching decision-7
  (no in-handler EGG_AGENT_ROLE check); flagged for architect
  confirmation
- R6 softened from "MANDATORY" retraction to policy choice flagged
  for HITL
- R12 "doubles" → "+67%" (3→5 no-CLI verbs)
- Reconcile folded_into_existing: overseer_alert placed in brc
  namespace (agreed with architect/task_planner)
- Add overseer_query_status scope-miss human_review_flag (iter-2 body
  lists it but no producer placed it)
- Expand acceptance_criteria_for_plan_phase to cover R3/R10/no-CLI
  testing and overseer_query_status scope call

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* architect(#1917) rev 2: address reviewer_plan NACK blockers

Adds mcp__phase__query_pipeline_status as the audit's overseer_query_status
slot (was silently dropped in rev 1). Scope is now 12 verbs. Closes
decision-20 unconditionally to Option A (shared/egg_contracts/checkpoint_handlers.py).
Adds architectural_dependencies.gateway_authz_required and
documentation_requirements.agent_tools_md_structure sections so AC1.b is
actually wired up. Addresses non-blocking items: path-traversal hardening
requirement on read_peer_artifact; expanded brc vs progress rationale for
overseer_alert; line citation fix (30-46 → 32-46); verified EGG_MCP_TOOLS
wiring in shared/egg_agent/client.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Plan #1917: v3 — fix brc-history filename + drift-gate parity + typo

Address reviewer_plan NACK v2 blocking + non-blocking items:

BLOCKING: TASK-2-1 brc-history filename was wrong (`<role>-*.json`);
actual format is `<pipeline-id>-<phase>.json` per
orchestrator/routes/pipelines.py::_write_brc_history (line 5125).
Rewrite handler params to `phase` + `peer_role` (filtered inside the
file); pipeline_id resolved server-side via EGG_PIPELINE_ID; path
canonicalised + asserted under .egg-state/brc-history/ (traversal
hardening per risk_analyst R2).

NON-BLOCKING:
- TASK-3-1 test path typo: `shared/egg_contracts/tests/` →
  `tests/shared/egg_contracts/` (named 4 existing test files).
- TASK-3-1 public helper names (no underscore): collect_checkpoints
  / load_checkpoint / search_checkpoints — lets sandbox handler
  import cleanly, matches decision-18 disposition.
- TASK-2-3 query_status: set cli_command=("egg-orch","pipeline",
  "status") for drift-gate parity with overseer_alert (symmetric
  treatment; closes asymmetry flagged by reviewer); scope table +
  drift-gate count updated (9→10 of 12).
- TASK-2-1 corrupt-JSON handling: deterministic (count as
  skipped_malformed in next_cursor metadata) rather than logger
  dependency.
- Add "Open-question disposition" section resolving architect's
  decisions 15–20 so implement phase doesn't re-litigate.

* architect(#1917) rev 3: align query_status to plan v3 namespace (progress)

Rev 3 addresses reviewer_plan NACK: query_status verb name and namespace
must match the ACKed plan v3, not architect's rev-2 preference. Renamed
from mcp__phase__query_pipeline_status to mcp__progress__query_status.
Captures the retained trade-off (progress vs phase) in
namespace_choice_caveats for iter-3 context. Adds plan_phase_action_items
section noting the verify_criterion gateway-authz pre-flight check the
task_planner should echo into the plan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Plan #1917 v3: address reviewer_plan NACK #2

Blocking fix:
- Verb count 11 → 12 after architect rev-2 / plan v3 added
  mcp__progress__query_status (plan TASK-2-3). Summary line, scope_recap,
  acceptance criteria, and recommended_approach all updated.
- Added R14 risk assessment for query_status (low severity — read-only
  REST wrap with CLI parity via drift gate; payload-size and data-
  exposure risks documented but low).

Non-blocking fixes:
- R1 severity held at 'medium' with needs_human_review=true;
  downgrades to 'low' once architect's gateway_authz_required task
  (plan TASK-1-3) produces a passing test.
- R3 affected_components: dropped stale orchestrator/routes/contracts.py
  POST-endpoint reference; only shared/egg_contracts/ + handler touched.
- R6 retraction framing: softened from MANDATORY to 'iter-3 alongside
  mcp__anchor__*'; downgraded needs_human_review since the drift gate
  regex does not force retraction.
- scope_recap.folded_into_existing reconciled with plan v3: overseer_
  alert and query_status both fold into progress namespace (not brc).
- overseer_query_status human_review_flag marked resolved (placed by
  plan v3 as mcp__progress__query_status).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Persist statefiles after plan phase

* Persist HITL resolution after plan phase gate

* docs(#1917): rule-doc sweep + agent-tools.md refresh for iter-2 MCP tools

TASK-5-1 + TASK-5-3 of the iter-2 MCP-tools plan. Updates the four
rule docs covered by the new two-way drift gate
(`tests/tools/test_rule_doc_drift.py`) and refreshes the agent-tools
reference to the post-iter-2 30-verb / 6-namespace surface.

- sandbox/agent-config/rules/contract.md — adds `Prefer this over
  `egg-contract <verb>`` lines for the 5 iter-2 contract verbs
  (`show_contract`, `add_commit`, `update_notes`, `complete_phase`,
  `verify_criterion`) plus the iter-1 contract verbs that didn't yet
  have rule-doc entries (`task_complete`, `register_open_question`,
  `request_feedback`); table picks up the new
  `egg-contract verify-criterion` row.
- sandbox/agent-config/rules/orchestrator.md — adds rule-doc entries
  for the 7 iter-1 BRC + heartbeat verbs and the 5 iter-1/2 progress
  verbs (`emit`, `signal_error`, `heartbeat`, `overseer_alert`,
  `query_status`); table picks up the new `egg-orch overseer alert`
  row.
- sandbox/agent-config/rules/checkpoint.md — adds rule-doc entries
  for the new `mcp__checkpoint__{list,show,search}` namespace; calls
  out the `limit`/`cursor` pagination convention and the decision-3
  scope ("core 3" only — `browse`/`context`/`cost` stay CLI-only).
- sandbox/egg_lib/data/hitl_editing_rules.md — adds `mcp__sdlc__show_contract`
  rule-doc entry so the HITL-edit harness inherits the same
  prefer-MCP guidance.
- docs/reference/agent-tools.md — bumps verb count 18 → 30 (was
  stale at 15 — also captures the iter-1 message verbs that landed
  after the original write-up); adds the 6-namespace inventory; adds
  per-tool rows for all 12 new iter-2 verbs; documents the
  `cli_command=None` rationale pattern (decision-13) and the
  `limit`/`cursor` pagination convention (decision-12); records the
  two-way rule-doc drift gate (decision-11); refreshes the testing
  matrix and known-limitations to match the post-iter-2 deferral
  list (anchor verbs, directed messaging, checkpoint
  browse/context/cost, `EGG_MCP_TOOLS` flag removal,
  phase-context field promotion). Architecture / async-error
  sections updated to call out the checkpoint helper-extraction path
  alongside the gateway path.

The rule-doc `Prefer this over `egg-…`` lines are pinned to the
iter-1 phrasing so `tests/tools/test_rule_doc_drift.py` (TASK-5-2,
tester-owned) can match them with a single regex; the registry tools
that use `cli_command=None` carry the no-CLI rationale in their
handler docstrings (also enforced by that test, assertion C). The
phantom `egg-orch anchor *` references in `orchestrator.md` are
deliberately retained per decision-2 — they will be retracted with
the iter-3 anchor PR.

Refs #1917, #1955.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(#1917): address reviewer_code non-blocking feedback on iter-2 docs

Targeted fixups on top of 9469147 in response to reviewer_code's
ACK with non-blocking notes:

- sandbox/agent-config/rules/orchestrator.md (CLI table) — fix the
  `egg-orch overseer alert` flag list. The old row claimed
  `--subject`/`--body` (which are `egg-orch message send` flags); the
  real `cmd_overseer_alert` argparse at sandbox/egg_lib/orch_cli.py:1390
  takes `--anomaly`, `--priority {low,medium,high}`, `--summary`
  (required) plus optional `--detail` / `--recommend`.
- sandbox/agent-config/rules/orchestrator.md (Prefer-MCP list) — add
  a clarifying parenthetical on `mcp__progress__query_status` noting
  the deliberate namespace asymmetry (tool in `progress`, CLI in
  `pipeline` subtree per decisions 5 + 17) so skimmers don't trip on it.
- docs/reference/agent-tools.md (Pagination convention) — drop the
  `(e.g. base64-encoded offset)` parenthetical that leaked an
  implementation detail and rephrased to make the opacity contract
  the lead. The "agents must not interpret it" guidance was already
  there; the fix removes the contradicting hint.

The third reviewer item (read_peer_artifact return-shape ambiguity in
agent-tools.md:112) is left for now — that is a coordination item
with the coder on whether `skipped_malformed` is a top-level sibling
or embedded in the cursor metadata. The doc currently promises the
top-level shape; if the coder ships embedded-in-cursor instead, the
doc updates to match in a follow-up.

Refs #1917.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Implement #1917 iter-2 MCP tools: 12 new verbs + Task.gaps field

Ships the iteration-2 MCP tool surface planned in #1917:

- **sdlc namespace (+2):** show_contract (with optional fields= projection,
  raise-on-unknown), verify_criterion (REVIEWER-only; gateway enforces).
- **task namespace (+3):** add_commit, update_notes (both share a
  _task_field_mutate helper), mark_gap (no-CLI; writes Task.gaps records).
- **phase namespace (+1):** complete_phase (transitions phases.N.status).
- **progress namespace (+2):** overseer_alert (wraps egg-orch overseer
  alert), query_status (wraps egg-orch pipeline status).
- **brc namespace (+1):** read_peer_artifact (no-CLI; reads local
  .egg-state/brc-history/<id>-<phase>.json with limit/cursor pagination).
- **new checkpoint namespace (+3):** list, show, search. Backed by three
  public helpers (collect_checkpoints, load_checkpoint,
  search_checkpoints) extracted from shared/egg_contracts/checkpoint_cli.py
  so CLI and handler share one dispatch path.

Contract/model changes:
- Task.gaps (list[TaskGap]) added to shared/egg_contracts/models.py with
  a default of []; existing contract fixtures keep validating.
- TaskGap model + .egg/schemas/contract.schema.json#/$defs/taskGap.
- roles.py FIELD_OWNERSHIP extended: phases.*.tasks.*.gaps and
  phases.*.tasks.*.gaps.* are implementer|reviewer-owned.

CLI shim refactors so the drift gate binds:
- cmd_show, cmd_add_commit, cmd_update_notes, cmd_complete_phase,
  cmd_verify_criterion (contract_cli) now delegate to the matching
  handlers; legacy stdout/stderr shape preserved byte-for-byte.
- cmd_overseer_alert, cmd_pipeline_status (orch_cli) delegate to the
  progress handlers with the same legacy output shape.
- cmd_list, cmd_show, cmd_search (checkpoint_cli) delegate to
  checkpoint_list/show/search handlers via the extracted helpers; the
  HTTP-preferred path is still tried first for gateway parity.

Decision-13 rationale added to handler docstrings for the no-CLI verbs
(brc_get_state, brc_list_blocking, phase_get_context,
phase_get_assigned_tasks, task_mark_gap, brc_read_peer_artifact,
check_hitl_answers). The tester-owned drift gate test will assert
these docstrings carry the "no CLI"/"no-CLI" literal.

Test updates (test_server.py, test_mcp_cli_drift.py) are deliberately
left for the tester role to land so the commit-authorship policy stays
honest.

* Address reviewer_code NACK on iter-2 MCP tools (6 blockers)

Blocker #1 — brc_read_peer_artifact security (risk_analyst R2):
- Strip caller-supplied pipeline_id/issue/repo_path from both the
  handler req and the MCP schema. Identifier/repo root are now
  resolved server-side from EGG_ISSUE_NUMBER/EGG_PIPELINE_ID/
  EGG_REPO_PATH only.
- Canonicalise the resolved history-file path via .resolve() and
  assert .is_relative_to(<repo_root>/.egg-state/brc-history) before
  reading; any escape raises HandlerError.
- Reject peer_role values not matching [a-z0-9_-] with HandlerError.
- Drop the `path` echo from the response (information disclosure).
- Add `additionalProperties: false` to the tool schema so the agent
  can't smuggle unknown keys through.
- Track skipped-malformed records and surface them both as a
  top-level `skipped_malformed` integer and embedded in the cursor
  so pagination remains deterministic.

Blocker #2 — progress_query_status cross-pipeline-read (reviewer NACK #2):
- Reject caller-supplied pipeline_id if it disagrees with
  EGG_PIPELINE_ID. Env-set → caller-override must match; env-unset →
  caller value still accepted (operator shell path).

Blocker #3 — LAYERING violation (decision-20):
- Move collect_checkpoints, load_checkpoint, search_checkpoints from
  sandbox/egg_agent_tools/handlers/checkpoint.py into
  shared/egg_contracts/checkpoint_cli.py (where the plan's TASK-3-1
  spec said they should live).
- Sandbox handlers.checkpoint now imports the helpers from
  egg_contracts.checkpoint_cli inside each handler so shared/ no
  longer depends on sandbox/.
- CLI cmd_list / cmd_show / cmd_search in checkpoint_cli.py use the
  helpers directly (no more `from egg_agent_tools.handlers import
  checkpoint as _handlers`).

Blocker #4 — TaskGap model deviations (plan TASK-4-1):
- id now validates ^gap-[0-9]+$ (plan-specified "gap-<N>" shape);
- from_role / to_role / description each require min_length=1;
- to_role is now required (no default "coder" — handler still
  defaults at request layer so agents don't have to pass it);
- created_at is a `datetime` with default_factory=datetime.now(UTC)
  so malformed strings / empty timestamps can't sneak through at
  the model layer.
- JSON schema updated to pattern ^gap-[0-9]+$ and format:date-time
  for created_at; to_role is now required.

Blocker #5 — task_mark_gap TOCTOU race:
- Switch id generation from uuid hex slug to `gap-<max_existing + 1>`
  (plan-specified shape); add _next_gap_id helper.
- Wrap the read-then-append in a bounded retry loop
  (_GAP_RETRY_ATTEMPTS=3): on each attempt re-read the task, recompute
  next_gap_idx + gap_id, and try again. Retryable errors are string-
  matched on "index / out of range / already exists / conflict" in
  the gateway message; other failures bail immediately.
- Drop the `gap_id` override from the MCP schema — handler now owns
  id generation exclusively for race safety.
- Tool description names the role constraint explicitly
  ("tester role writes; coder role reads") per plan TASK-4-2.

Blocker #6 — phase_complete_phase non-atomicity (reviewer NACK #6):
- Swap the mutation order: link the commit FIRST (idempotent, retryable)
  then flip status. A mid-way failure leaves the phase not-yet-complete
  with its commit populated; callers retry the same request to progress.
- Handler docstring spells out the atomicity guarantee.
- CLI shim cmd_complete_phase now maps both gateway errors uniformly
  to "Error setting status: …" since the "Warning: Phase marked
  complete but failed to link commit:" branch no longer applies with
  the new ordering.

Test surface:
- sandbox/tests/ and tests/sandbox/egg_agent_tools/** / tests/tools/**
  remain tester-owned by commit-authorship policy; the tester role
  will update test_mcp_cli_drift.py for the new checkpoint helper
  pattern (PARSERS map + handler-dispatch AST walk) and land the
  per-handler unit tests that exercise the hardened validations.

* tests(#1917): iter-2 MCP tool handlers + gaps model + rule-doc drift

Discharges the tester-role TASK-* assignments from the iter-2 plan:

- TASK-1-4 / TASK-1-5: per-handler unit tests for the 5 P0 verbs
  (show_contract, verify_criterion, add_commit, update_notes,
  complete_phase) plus drift-gate entries.
- TASK-2-4: read_peer_artifact pagination boundaries (empty, exact,
  beyond, bad cursor, corrupt records counted in skipped_malformed),
  env-only identifier resolution (caller override silently ignored per
  reviewer_code NACK #1a), peer_role slug validation, no-'path'-echo
  info-disclosure guard; overseer_alert + query_status drift entries +
  env-match pipeline_id enforcement (NACK #2).
- TASK-3-4: test_handlers_checkpoint.py for the decision-20 layering
  change (helpers moved to egg_contracts.checkpoint_cli); add
  shared-helper dispatch path to the drift test so cmd_list/show/search
  bind via the collect_checkpoints/load_checkpoint/search_checkpoints
  helpers instead of the AST-walked handler-import pattern.
- TASK-4-3: test_models_gaps.py — Task.gaps default, round-trip,
  back-compat against all .egg-state/contracts/ fixtures, JSON schema
  check, role-aware mutation validator.
- TASK-5-2: test_rule_doc_drift.py — three assertions (A: Prefer-line
  resolves to registered tool + CLI matches; B: every CLI-backed tool
  has a Prefer-line; C: every cli_command=None handler's docstring
  mentions 'no CLI'/'no-CLI' — decision-13 gate).
- TASK-6-1: test_server.py bumped to 30 tools + 6 namespaces, with
  derived assertions locking the prose count in agent-tools.md.
- TASK-6-2: test_full_tool_registry.py — integration test loading
  TOOL_LIST through claude_agent_sdk.create_sdk_mcp_server, plus
  state-machine-effect assertions on the four completion/mutation verbs
  (task_complete, phase_complete_phase, task_add_commit,
  verify_criterion).

All 529 new/updated handler/drift/rule-doc/registry tests pass; 27
skipped are unrelated legacy-contract fixtures with pre-iter-2 schema
drift (e.g. old agent role enums).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style: apply ruff format to 10 iter-2 MCP tool files

Tester NACK on cycle 2: `ruff format --check` flagged 10 coder-owned
files with unformatted style.  No logic changes — just the standard
ruff formatter run against the files it flagged:

- sandbox/egg_agent_tools/handlers/brc.py
- sandbox/egg_agent_tools/handlers/phase.py
- sandbox/egg_agent_tools/handlers/progress.py
- sandbox/egg_agent_tools/handlers/sdlc.py
- sandbox/egg_agent_tools/handlers/task.py
- sandbox/egg_agent_tools/tools/__init__.py
- sandbox/egg_agent_tools/tools/brc.py
- sandbox/egg_lib/orch_cli.py
- shared/egg_contracts/checkpoint_cli.py
- shared/egg_contracts/models.py

`ruff format --check <paths>` now reports all 10 files formatted.
`make lint-python` gate no longer blocks.

All 467 tester-authored unit + drift tests still green after the
reformat (test_handlers_*, test_mcp_cli_drift, test_rule_doc_drift,
test_full_tool_registry, test_checkpoint_cli).

* Persist statefiles after implement phase

* Remove ephemeral agent-output handoff artifacts (#1731)

* Persist BRC history files for PR

* Address review feedback on iter-2 MCP handlers

Blocking fixes:
1. task_complete: reorder mutations — link commit FIRST, then set
   status, matching phase_complete_phase. Mid-way failure now leaves
   the task not-yet-complete (safe to retry) instead of complete
   without its commit SHA.
2. register_open_question: add bounded-retry (3 attempts) for TOCTOU
   race on decision index, same pattern as task_mark_gap.
3. checkpoint handlers: validate caller-supplied repo_path is under
   ~/repos/ or matches EGG_REPO_PATH, preventing directory traversal.
4. verify_criterion: add pre-flight contract read to validate criterion
   index exists before mutating, preventing sparse array creation.

Non-blocking fixes:
5. _fetch_contract: use 'or {}' to handle null data (sdlc.py, phase.py).
6. progress_emit/overseer_alert: null-safe nested .get on data.
7. progress_query_status: consistent success check (no True default).
8. _require_pipeline_id: format validation ([a-zA-Z0-9_-]+).
9. task_mark_gap: replace assert with HandlerError for production path.
11. progress_query_status: fix data fallback leaking response keys.

Tests added for all fixes (TOCTOU retry, repo_path containment,
criterion bounds, null data, pipeline ID format validation).

* Fix test_caller_path_under_repos_accepted: patch expanduser for CI

* Address re-review non-blocking findings on iter-2 MCP handlers

1. brc.py _require_pipeline_id: add _PIPELINE_ID_PATTERN format validation,
   matching progress.py (finding #1)
2. progress_query_status: add inline _PIPELINE_ID_PATTERN check since this
   handler bypasses _require_pipeline_id (finding #2)
3. register_open_question: add null guard on last_error matching the pattern
   in task_mark_gap (finding #3)
4. task_complete: add isinstance(commit, str) check before _validate_commit_sha,
   matching task_add_commit and phase_complete_phase (finding #4)

* Add tests for re-review non-blocking findings: BRC pipeline ID validation, query_status inline format check, task_complete non-string commit

* Regenerate issue-1917 contract from plan

The contract was empty (phases=[], pr=null) because
_populate_contract_from_plan never ran against the final plan revision
on this branch.  Re-ran the orchestrator's populate logic (parse_plan →
to_contract_phases + PRMetadata) to fill in the 6 phases, 22 tasks, and
PR metadata from .egg-state/drafts/1917-plan.md.

Known caveat (tracked in #1988): TASK-1-3A and TASK-1-3B both normalize
to task-1-3 via the unanchored regex in plan_parser.py, so phase-1 has
two tasks sharing id task-1-3.  This matches the collision that would
have landed had populate run at plan-completion time; the parser fix
lives in #1988.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: egg-orchestrator <egg@localhost>
Co-authored-by: egg <egg@example.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
Co-authored-by: james-in-a-box[bot] <246424927+james-in-a-box[bot]@users.noreply.github.com>
Co-authored-by: James Wiesebron <jameswiesebron@khanacademy.org>
james-in-a-box Bot added a commit that referenced this pull request Apr 24, 2026
…[doc-updater] (#1990)

* docs: Update MCP tools docs for iteration 2 (30 verbs, 6 namespaces)

Update four docs that still referenced the old iteration-1 verb count
(15) and namespace set (5) after #1981 shipped 12 additional verbs and
the checkpoint namespace.

- docs/index.md: 15 iteration-1 verbs → 30 verbs across 6 namespaces
- sandbox/agent-config/rules/environment.md: same; add egg-checkpoint to prefer list
- sandbox/agent-config/rules/README.md: add mcp__checkpoint__* to namespace list
- docs/guides/sdlc-pipeline.md: 15-verb → 30-verb; update iter-2 status note
  (peer-read, checkpoint, overseer-alert, task-gap shipped; anchor deferred to iter-3)

* docs: Fix stale 15-verb cross-reference in release notes (review feedback)

---------

Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agents shell out to egg-contract show via Bash — no MCP equivalent exists

1 participant