Skip to content

feat(subjects): subjects evidence renders a scorecard as skill_evidence.v1 - #344

Open
napetrov wants to merge 4 commits into
intel:mainfrom
napetrov:feat/evidence-format
Open

napetrov wants to merge 4 commits into
intel:mainfrom
napetrov:feat/evidence-format

Conversation

@napetrov

@napetrov napetrov commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Summary

New dokimi subjects evidence SCORECARD [--out FILE] (free, no model calls) renders a saved subject_scorecard as skill_evidence.v1 (experimental schema): one row per (cell, non-baseline arm) with paired n, delta, CI, corrected p and a verdict (improved/regressed only when significant after correction, else inconclusive), keyed by subject.distribution_digest. Nothing is measured; values the scorecard lacks stay null (e.g. effort). Rollup and scorecard cells now carry baseline_arm.

Stacked on #343 — review only the top commit until that merges.

CLI v2 (#94): maps to report RUN --per-subject --format evidence.

Evidence

  • tests/test_subjects_evidence.py, runs show/scan kind rows, CLI snapshot regenerated (py3.13); full suite 12018 passed locally.

Merge danger

Low–medium: new artifact kind + baseline_arm added to matrix_rollup.v1 cells.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings November 1, 2026 07:19
@napetrov
napetrov requested a review from a team as a code owner November 1, 2026 07:19
@github-actions github-actions Bot added the cli-interface-change Changes dokimi's user-facing CLI surface label Nov 1, 2026
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

CLI interface change detected

This PR changes dokimi's user-facing CLI surface (commands and/or options):

+ added command `dokimi subjects evidence`
    + argument `scorecard`
    + option `--help/-h`
    + option `--out`
~ `dokimi runs list` parameters changed:
    ~ changed option `--kind`: choices: ["'answers'", "'arms'", "'cli_routes'", "'eval'", "'explore_runs'", "'guidance_reach'", "'matrix_rollup'", "'novelty'", "'plan'", "'plugin_delta'", "'questions'", "'repo_budget'", "'repo_environment'", "'repo_readiness'", "'result'", "'subject_scorecard'", "'task_inspect'", "'task_runs'", "'tool_preference'"] -> ["'answers'", "'arms'", "'cli_routes'", "'eval'", "'explore_runs'", "'guidance_reach'", "'matrix_rollup'", "'novelty'", "'plan'", "'plugin_delta'", "'questions'", "'repo_budget'", "'repo_environment'", "'repo_readiness'", "'result'", "'skill_evidence'", "'subject_scorecard'", "'task_inspect'", "'task_runs'", "'tool_preference'"]

Updated on every push; the cli-interface-change label is on while the diff is non-empty.

@exai-bot

exai-bot commented Nov 1, 2026 •

Copy link
Copy Markdown

🔍 OpenCodeReview found 1 issue(s) in this PR.

By severity: 1 medium

Severity Category Location Finding
medium bug evidence.py:117-118 Must-fix: a schema-valid scorecard with significance: {"delta_mean": 12.0, "significant_adjusted": "false"} is published with verdict `...
Review details
  • Commit reviewed: 778a1ac against merge base b4a0a8c
  • Coverage: 18 files in 3 groups
  • Engine: OpenCodeReview v1.13.3, us.openai.gpt-6.1-sol
  • Usage: 481k tokens (291k from cache), 55s
  • Tool calls: 44 (36 file_read, 7 code_search, 1 code_comment)
  • Distribution digest and scorecard rollup: 6 files
  • Skill evidence schema, generation, and CLI: 8 files
  • Artifact registration and run discovery: 4 files

Comment thread dokimi/dokimi/commands/subjects.py Outdated
scorecard = load_artifact("subject_scorecard", Path(args.scorecard))
except (OSError, ValueError, ArtifactValidationError) as exc:
usage_exit(f"cannot read subject scorecard {args.scorecard}: {exc}")
evidence = build_skill_evidence(scorecard, source=str(args.scorecard))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: malformed cell data can escape input validation and crash this command. For example, take a valid subject_scorecard.v2 fixture, set cells to [null], and run dokimi subjects evidence sc.json: the scorecard schema only constrains cells to an array, so loading succeeds, but build_skill_evidence raises AttributeError at cell.get(...). Neither this handler nor the CLI's top-level handler catches it, resulting in a traceback instead of an actionable exit-2 error. Validate the cell/summary/per-arm shapes before conversion, translate malformed input into a usage error, and add a regression test for this input.

Copilot AI 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.

🟡 Changes recommended

Malformed nested scorecard content can trigger an uncaught AttributeError instead of a diagnostic and exit code 2, so input validation or rendering error handling needs correction.

1 open issue
What changed in this PR

1 new must-fix finding.

Adds a free subjects evidence command that turns saved subject scorecards into publishable skill_evidence.v1 documents, using the distribution digest introduced by #343.

Changes:

  • Exports paired statistics and significance-based verdicts without model calls.
  • Carries baseline-arm metadata through rollups and scorecards.
  • Registers the artifact and CLI route, with schema, fixtures, tests, and changelog updates.
File Description
dokimi/​tests/​test_subjects_evidence.py Tests evidence rendering, statistics, and CLI safeguards.
dokimi/​tests/​test_subjects_digest.py Tests distribution digests from the prerequisite change.
dokimi/​tests/​test_runs_show.py Adds evidence artifact summary expectations.
dokimi/​tests/​test_runs_scan.py Adds evidence artifact discovery expectations.
dokimi/​tests/​fixtures/​artifacts/​skill_evidence.v1.json Provides a sample evidence document.
dokimi/​dokimi/​subjects/​scorecard.py Records baseline arms and distribution digests.
dokimi/​dokimi/​subjects/​evidence.py Renders scorecard statistics as evidence.
dokimi/​dokimi/​schemas/​subject_scorecard.v2.json Allows the subject distribution digest.
dokimi/​dokimi/​schemas/​skill_evidence.v1.json Defines the experimental evidence format.
dokimi/​dokimi/​schemas/​matrix_rollup.v1.json Allows cell baseline-arm metadata.
dokimi/​dokimi/​runs/​kinds.py Registers evidence discovery and summary fields.
dokimi/​dokimi/​eval/​matrix_rollup.py Preserves each cell’s baseline arm.
dokimi/​dokimi/​commands/​subjects.py Adds the evidence command and output handling.
dokimi/​dokimi/​cli_routes.py Classifies evidence generation as free.
dokimi/​dokimi/​artifacts.py Registers the evidence schema version.
dokimi/​docs/​reference/​cli-interface.json Updates the CLI interface snapshot.
dokimi/​changelog.d/​20261031_130000_napetrov_skill-evidence.md Documents evidence export and baseline metadata.
dokimi/​changelog.d/​20261031_120000_napetrov_distribution-digest.md Documents the prerequisite distribution digest.

Review effort: Balanced

Comment thread dokimi/dokimi/commands/subjects.py Outdated
scorecard = load_artifact("subject_scorecard", Path(args.scorecard))
except (OSError, ValueError, ArtifactValidationError) as exc:
usage_exit(f"cannot read subject scorecard {args.scorecard}: {exc}")
evidence = build_skill_evidence(scorecard, source=str(args.scorecard))
…dex carries

subject_scorecard.v2 gains subject.distribution_digest: sha256 of the
SKILL.md bytes for a file ref or a directory holding only SKILL.md, the
value a .well-known/agent-skills index.json entry of type skill-md
carries (agentskills PR intel#254). An archive's bytes depend on how it was
packed, so a skill with resources gets null, as does a non-local ref.
ref_digest is unchanged.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…ce.v1

A free leaf that reads a saved subject_scorecard and writes the document
a publisher puts next to a skill: one result per (cell, non-baseline
arm) with model, harness, effort, n, delta, CI, corrected p-value and a
verdict, keyed by subject.distribution_digest so a client can match it
to the .well-known/agent-skills index entry it installed from. Values
the scorecard does not hold stay null.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…il a run records it

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov
napetrov force-pushed the feat/evidence-format branch from d25d6f1 to d0f1704 Compare November 1, 2026 07:43
"""
if not ref:
return None
path = Path(ref.removeprefix("local:"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: non-local references can acquire a digest from unrelated local files. On POSIX, create base/context7:/org/lib/SKILL.md as the directory's only entry, then call distribution_digest("context7:/org/lib", base=base): this returns that file's SHA-256 instead of None. The descriptor loader accepts this reference, and the evidence renderer publishes the resulting digest as subject identity. Reject non-local reference schemes before filesystem lookup (while preserving Windows drive paths), and add a collision regression test; the existing non-local test only exercises a nonexistent path. This affects both subject and member digests through this helper.

@codecov-commenter

codecov-commenter commented Nov 1, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 92.10526% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.04%. Comparing base (b4a0a8c) to head (778a1ac).

Files with missing lines Patch % Lines
dokimi/dokimi/subjects/evidence.py 89.47% 2 Missing and 2 partials ⚠️
dokimi/dokimi/commands/subjects.py 92.00% 1 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #344      +/-   ##
==========================================
- Coverage   94.05%   94.04%   -0.01%     
==========================================
  Files         297      298       +1     
  Lines       38018    38094      +76     
  Branches     6424     6439      +15     
==========================================
+ Hits        35756    35827      +71     
- Misses       1452     1456       +4     
- Partials      810      811       +1     
Files with missing lines Coverage Δ
dokimi/dokimi/artifacts.py 95.12% <ø> (ø)
dokimi/dokimi/cli_routes.py 100.00% <ø> (ø)
dokimi/dokimi/eval/matrix_rollup.py 90.00% <ø> (+2.00%) ⬆️
dokimi/dokimi/runs/kinds.py 100.00% <ø> (ø)
dokimi/dokimi/subjects/scorecard.py 88.88% <100.00%> (+2.12%) ⬆️
dokimi/dokimi/commands/subjects.py 87.24% <92.00%> (+0.54%) ⬆️
dokimi/dokimi/subjects/evidence.py 89.47% <89.47%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@antonwolfy antonwolfy 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.

Review at d0f1704. The renderer is small, reads every number from the scorecard and leaves a missing one null, and the verdict rule (significant after correction, else inconclusive) is right. Two risks are worth fixing before merge, both gaps in what the tests pin: one inline, one below. One nit, and a note on the stacking.

What I ran, on a Linux review host at d0f1704:

  • make ci-local: 27 passed, 3 failed, 7 skipped; 11,986 tests passed, 1 failed; changed-line coverage passes. The 3 failures are not the PR's: one timing assertion (test_unbalanced_quote_in_hook_completes_quickly, 1.02 s against a 1 s limit) passes when rerun alone and the PR does not touch that code; the two gitleaks steps could not install or run the tool.
  • Mutation run on the new code and tests: 60 mutants, 29 pass test_subjects_evidence.py, test_subjects_digest.py, test_runs_scan.py and test_runs_show.py.
  • Reproductions for the code findings, with the LLM transport mocked: malformed scorecards through dokimi subjects evidence, and distribution_digest with a colliding directory.

Findings that cannot be anchored inline:

  1. The baseline fallback and the interval null handling in evidence.py are unpinned. _baseline_arm (evidence.py:80-84) picks the baseline for any scorecard recorded before baseline_arm existed. The only fallback test has two delta-less arms, so it covers the "skip the cell" branch only. Replacing the fallback with return None, and dropping and not row.get("significance"), both pass the four test files. Likewise for ci (evidence.py:105-108): never returning null ({"lo": null, "hi": null, ...} for an arm with too few pairs) and keying the null check on ci_hi both pass. Add a cell with one delta-less arm and no baseline_arm (that arm is the baseline, the other is published), a cell whose delta-less arm carries significance (not treated as baseline), and an arm with ci_lo=None (expect ci is None). Also unasserted, each passing when removed: the subject kind is ... not 'skill' warning, the task-suite warning, and scorecard warnings carried through.

  2. The PR carries #343's commit. The first commit (099c2b8, distribution_digest) belongs to that PR. Rebase onto main once #343 merges, so the diff holds only this PR's two commits.

Checked, no issues

  • Pins: the verdict sign flip, ignoring significant_adjusted, the delta source, arm != baseline, a recorded baseline winning over the fallback, the source basename, and the --out-equals-scorecard and empty-results exits each fail a test when broken.
  • Schemas: subject_scorecard.v2 and matrix_rollup.v1 only gain optional nullable properties in objects that allow extra keys, so older artifacts still validate. skill_evidence.v1 is registered in SCHEMA_VERSIONS and runs/kinds.py, and the emitted document validates against it.
  • CLI: exit 2 with a message for a missing scorecard, a directory, --out naming the scorecard (also as ./sc.json), and an unwritable target. source.path keeps only the file name.
  • Commits: all three carry Signed-off-by.
Bot points I checked that no longer apply at this head (1)
  • exai-bot's point on a non-local ref picking up a digest from a local path does not apply to this PR's own change: distribution_digest comes from #343's commit, and I take it up in the review of #343.

"model": cell.get("model"),
"provider": cell.get("provider"),
"harness": cell.get("harness"),
"baseline_arm": cell.get("baseline_arm"),

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 test pins that scorecard cells carry baseline_arm. test_the_rollup_and_scorecard_cells_carry_the_baseline_arm (tests/test_subjects_evidence.py:155) only calls build_matrix_rollup; nothing calls build_subject_scorecard for this field.

Mutation check: deleting this line leaves test_subjects_evidence.py, test_subjects_digest.py, test_runs_scan.py and test_runs_show.py at 110 passed, 1 skipped, the same as the unmutated tree. Every new scorecard then loses baseline_arm and evidence.py falls back to the "one arm with no delta" guess, which is the guess the changelog says this field removes. An arm whose every answer failed has no delta either, so it can be published as the baseline or the cell skipped.

Suggestion: build a scorecard from a cell artifact that has baseline_arm: "none" and assert scorecard["cells"][0]["baseline_arm"] == "none"; rename the test or split it.

Comment thread dokimi/dokimi/commands/subjects.py Resolved
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Comment on lines +117 to +118
if delta is None or not sig.get("significant_adjusted"):
return "inconclusive"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: a schema-valid scorecard with significance: {"delta_mean": 12.0, "significant_adjusted": "false"} is published with verdict improved. The scorecard schema does not constrain nested cell statistics, and the string is truthy, so neither input validation nor the new exception handler rejects it. This silently turns malformed significance data into a positive claim. Validate this flag as boolean/null (reject other types with a handled input error), and add a regression test for the string "false"; only the boolean true should permit a directional verdict.

@antonwolfy antonwolfy 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.

I pushed a commit per nit, up to 778a1ac, which fix the nit from my review:

  • 778a1ac: dokimi/dokimi/commands/subjects.py:178: wrapped build_skill_evidence so AttributeError/TypeError exits 2 with a not-well-formed message; added a cells: [null] test.

Left as is:

  • The body's only nit-labelled point is not present; findings 1 and 2 are risks/stacking, left to the author.

make ci-local at 778a1ac: 28 passed, 2 failed (gitleaks, host), 7 skipped.

Not approving yet. Still open: the two risks from my review.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli-interface-change Changes dokimi's user-facing CLI surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants