Skip to content

feat(chisel): Phase B — eval harness + canonical queries - #212

Open
jpb33333 wants to merge 2 commits into
feat/pebble-chisel-scopingfrom
feat/pebble-chisel-phase-b
Open

feat(chisel): Phase B — eval harness + canonical queries#212
jpb33333 wants to merge 2 commits into
feat/pebble-chisel-scopingfrom
feat/pebble-chisel-phase-b

Conversation

@jpb33333

Copy link
Copy Markdown
Collaborator

Summary

Phase B of the Chisel plan — production-discipline regression net. Each tool / workflow ships a canonical_queries.yaml beside its manifest; chisel eval runs them through the planner and reports pass/fail with per-step diff.

Stacked on #211 (Phase A). Base branch is feat/pebble-chisel-scoping; rebase to dev after #211 lands.

What's in the PR

  • pebble/chisel/eval.py — Pydantic schema, loader, assert_plan / assert_prose, run_plan_eval, format_results
  • chisel eval CLI subcommand (filters by --unit and --tag)
  • Canonical queries for all 5 migrated tools + 1 workflow
  • 21 unit tests (pebble/tests/test_chisel_eval.py)

Design calls

  • Substring-only prose assertions per plan §11.6 (no regex — too easy to write fragile checks).
  • Planner-only by default — the CLI exits 0 without ANTHROPIC_API_KEY so local devs can validate schemas + fixtures without burning tokens. Full-pipeline prose eval is gated and not in scope for v1.
  • CI workflow deferred — repo has no .github/workflows/ directory; dropping eval-gate.yml blind to the team's actual CI surface (Buildkite? external runner?) felt premature. Tests run via pytest and the CLI; we can add the gate once we know where it should live.
  • Skipped fixtures kept in-tree with skip_reason rather than excluded — they document the gap (downstream-of-state queries, slash-bypass-planner) and stay visible for the future gated CI job.

Test plan

  • Unit tests: 21 cases (pytest pebble/tests/test_chisel_eval.py)
  • Full pebble suite: 668 pass (647 from Phase A + 21 new)
  • CLI smoke (no API key): python -m pebble.chisel.cli eval → schema-validation passes
  • CLI live: ANTHROPIC_API_KEY=... python -m pebble.chisel.cli eval (reviewer runs locally)
  • Wire CI gate once the team picks the runner

What's next

Phase C — GUI builder under financial_forecasting/frontend-v2/src/pages/chisel/ (3–4 wks).

🤖 Generated with Claude Code

Production-discipline regression net: each tool / workflow ships a
canonical_queries.yaml beside its manifest declaring user_query →
expected_plan (tool order + arg includes/excludes) and optional
expected_prose (substring includes/excludes per plan §11.6 — no
regex). The eval harness runs them through the planner and reports
per-query pass/fail.

Framework (pebble/chisel/eval.py):
- Pydantic shapes for canonical_queries.yaml — strict (extra=forbid),
  id format validated, expected_plan/expected_prose optional
- load_canonical_queries walks tools/ + workflows/, raises with the
  source path on malformed files
- assert_plan: positional step match, arg_includes (subset),
  arg_excludes (forbidden keys), step_count tolerance for extra steps
- assert_prose: substring includes/excludes (deliberately not regex)
- run_plan_eval drives one query through Planner.plan(); skip_reason
  short-circuits with passed=True so the report stays signal-rich
- format_results: PASS/FAIL/SKIP summary with per-failure detail

CLI:
- chisel eval [--unit NAME] [--tag LABEL]
- ANTHROPIC_API_KEY absent → schema-validates and exits 0 without
  burning tokens (useful for local dev + offline CI smoke)
- API key present → calls live planner, exits 1 on any failure

Fixtures (planner-only assertions; full-pipeline prose checks deferred
to the gated CI run per plan §11.12):
- search_crm: 2 NL queries
- get_record: 1 explicit-id query
- aggregate_pipeline_views: 3 NL queries
- generate_chart, request_human_review: skip_reason — both downstream
  of upstream state that single-query planner eval can't deterministically
  provoke
- weekly_pipeline_review: NL path covered; /pipeline slash path
  documented as bypass-planner (build_plan.py is unit-tested)

Tests (21 cases):
- canonical_queries.yaml schema validation (id format, extra=forbid,
  empty tool rejection)
- assert_plan: empty-expected pass, arg_includes happy + miss-key +
  value-mismatch, arg_excludes forbidden-key, step_count, wrong-tool,
  extra-step tolerance
- assert_prose: includes + excludes pass/fail
- loader: real chisel root discovery, malformed file → ValueError with
  file path
- run_plan_eval: stub planner happy / planner-error / skip paths
- format_results rendering

CI workflow file deferred — repo currently has no .github/workflows/.
Adding eval-gate.yml without aligning to the team's CI surface
(Buildkite? external runner?) is premature; tests run locally via
pytest and the CLI is the canonical entry point in the meantime.

Suite: 668 pass (647 + 21 new).

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, push a new commit or reopen this pull request to trigger a review.

…pse maps

Six-pass review surfaced ~135 lines of dead/speculative code and a few
overbuilt abstractions. This commit removes them, collapses the slash/
intent/plan-builder triple-map into one ``WorkflowEntry``, and moves
autoload to chisel-package import so production wiring doesn't depend
on streaming.py being imported first.

Behavior preserved (664 pass, was 668 — net -4 = removed tests for
deleted features, +2 for new convention).

Tier 1 — correctness:
- Autoload now runs at ``pebble.chisel`` import; ``streaming.py`` no
  longer needs the explicit ``_chisel.autoload()`` call. Eliminates the
  import-order fragility where router.py could see empty maps if loaded
  before streaming.
- WorkflowManifest auto-fills ``dispatch_intent = workflow_<name>`` if
  omitted. Closes the slash-without-intent silent-dispatch gap.
- Lints run at autoload time and surface in ``AutoloadReport.lint_warnings``
  (advisory — registration still proceeds). ``chisel validate`` exits
  non-zero when any handler under the loaded tree has a lint hit.
- schema.py: drop the dead ``setdefault`` + unconditional-overwrite
  pair; collapse two deepcopies into one in-place mutator.

Tier 2 — kill dead code (~210 lines removed):
- Drop ``ToolManifest.eval_fixtures`` / ``lint_overrides`` / ``scope``
  / ``requires_permission`` / ``output_kind`` (no reader anywhere).
- Drop ``WorkflowManifest.requires_permission`` / ``scope``.
- Collapse ``FixedCost | VariableCost`` union → flat ``cost_estimate_usd:
  float``. No tool uses variable-cost; YAML is simpler.
- Delete ``pebble/chisel/rbac.py`` entirely. Sprint-12 RBAC isn't
  wired anywhere; resurrect when it lands.
- Delete ``lints._check_no_env_in_run`` — trivially evadable via
  ``from os import environ``, load-bearing only if enforced robustly.
- Delete ``schema.assert_strict`` — test-only sugar; tests assert the
  invariant directly.
- Delete ``lints.lint_handler_module`` ``overrides=`` arg + manifest's
  ``lint_overrides`` field. No production handler used them.
- Delete ``AutoloadReport.ok()`` method — one caller, inlined.

Tier 3 — collapse abstractions:
- Replace ``slash_command_map`` + ``slash_to_intent`` + ``dispatch_workflow``
  + ``build_workflow_plan`` with two lookups: ``lookup_slash(slash) ->
  WorkflowEntry | None`` and ``lookup_intent(intent) -> WorkflowEntry |
  None``. ``WorkflowEntry`` is a frozen dataclass carrying name +
  dispatch_intent + slash_command + build_plan. One shape, one
  vocabulary, no reverse-scans.
- Slim ``HandlerContext`` from 40 lines of per-property forwarders to
  a 12-line ``__getattr__`` delegate. ``ctx.user_email`` /
  ``ctx.http_client`` still work; ``ctx.cite()`` and ``ctx.citations``
  unchanged.
- Refactor adapter to use a ``_fail()`` helper, halving the
  ToolResult construction repetition.

Tier 4 — DX:
- conftest autouse fixture drops the redundant post-yield autoload
  call; saves ~50ms per test across 664 tests.
- ``chisel validate`` (no path) now lints every loaded handler by
  default, not just the autoload errors. Default behaviour matches
  the documented purpose.

Manifests updated: 5 tool manifests + weekly_pipeline_review/workflow.yaml
lose ``output_kind`` / ``scope`` / nested ``cost_estimate: {fixed: ...}``
in favor of flat ``cost_estimate_usd: 0.0``.

Total: 22 files changed, 340 insertions, 654 deletions (net -314).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant