Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- **The next-step hints name the `dokimi` command, and the `arms run` example in a baseline-only report runs as written** — `dokimi personas discover`, `dokimi personas approve`, `dokimi baseline list` and `dokimi baseline compare` used to print `python cli.py …` as the command to run; they now print `dokimi …`, the console script, with the same arguments. The example `arms run` command in a model-comparison report that has only baseline arms now carries the `--product` and `--questions` that `dokimi arms run` requires, in place of a `[other parameters]` placeholder. No command, flag or output changes otherwise.
4 changes: 2 additions & 2 deletions dokimi/dokimi/commands/baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def cmd_baseline_list(args: argparse.Namespace) -> None:

if not baselines:
print("No baselines saved yet.")
print("Run: python cli.py baseline save --from-eval eval/oneTBB.json --product oneTBB")
print("Run: dokimi baseline save --from-eval eval/oneTBB.json --product oneTBB")
return

print(f"{'Name':<35} {'Product':<12} {'Saved':<17} {'n':>4} {'with':>6} {'without':>8} {'delta':>7}")
Expand Down Expand Up @@ -259,7 +259,7 @@ def cmd_baseline_compare(args: argparse.Namespace) -> None:

if not baselines:
print("No baselines found. Save one first:")
print(" python cli.py baseline save --from-eval eval/oneTBB.json --product oneTBB")
print(" dokimi baseline save --from-eval eval/oneTBB.json --product oneTBB")
raise SystemExit(1)

entry = _pick_entry(args, baselines, eval_data)
Expand Down
4 changes: 2 additions & 2 deletions dokimi/dokimi/commands/personas.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def cmd_personas_discover(args: argparse.Namespace) -> None:
print("\nNext steps:")
print(f" 1. Review: cat {output_path}")
print(" 2. Edit if needed")
print(f" 3. Approve: python cli.py personas approve --file {output_path}")
print(f" 3. Approve: dokimi personas approve --file {output_path}")


def cmd_personas_approve(args: argparse.Namespace) -> None:
Expand Down Expand Up @@ -117,7 +117,7 @@ def cmd_personas_approve(args: argparse.Namespace) -> None:
print(f"✓ Validated {len(personas['personas'])} personas")
print(f"✓ File approved: {persona_file}")
print("\nReady for question generation:")
print(f" python cli.py questions generate --product {personas['product']} --personas {persona_file}")
print(f" dokimi questions generate --product {personas['product']} --personas {persona_file}")


def register(sub, positive_int) -> None:
Expand Down
13 changes: 0 additions & 13 deletions dokimi/dokimi/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
import json
from pathlib import Path

from dokimi.report.markdown_report import write_compare_report, write_run_report


def _write_combined_report(args, regular_runs, golden_runs, run_ids, warnings) -> None:
"""Write the combined model-compare report, exiting cleanly if the output is unwritable."""
Expand Down Expand Up @@ -89,17 +87,6 @@ def cmd_report_model_compare(args: argparse.Namespace) -> None:
_write_combined_report(args, regular_runs, golden_runs, run_ids, all_warnings)


def cmd_report(args: argparse.Namespace) -> None:
"""Generate markdown report from JSON snapshot."""
data = json.loads(Path(args.input).read_text(encoding="utf-8"))
out_md = Path(args.out_md).resolve()

if "diff" in data:
write_compare_report(data, out_md)
else:
write_run_report(data, out_md)


def cmd_report_eval(args: argparse.Namespace) -> None:
"""Generate quality report from eval JSON (dynamic vs static breakdown)."""
from dokimi.records import RecordError
Expand Down
6 changes: 3 additions & 3 deletions dokimi/dokimi/report/model_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,11 +882,11 @@ def _models_compared_lines(
"> ",
"> **To generate full comparison with delta analysis, add a treatment arm:**",
"> ```bash",
"> python cli.py arms run \\",
"> dokimi arms run \\",
"> --product oneTBB --questions questions/oneTBB.json \\",
"> --arms \"baseline,docs\" \\",
"> --judge \\",
"> --judge-model claude-sonnet-5-5 \\",
"> [other parameters]",
"> --judge-model claude-sonnet-5-5",
"> ```",
"> ",
"> Treatment arm options: `docs` (Context7 documentation), `skill:<path>` (local skill), `agent` (agentic doc retrieval).",
Expand Down
8 changes: 4 additions & 4 deletions dokimi/tests/fixtures/model_compare_sections.json

Large diffs are not rendered by default.

92 changes: 92 additions & 0 deletions dokimi/tests/test_printed_hints_parse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright (C) 2026 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

"""Every command a printed hint tells the user to run names the `dokimi` console script and parses.

The hints are read by people who copy them, so each one is captured from the code that prints it,
the argv is taken from the text, and the argv goes through the same parse_args the CLI uses.
"""

import json
import re
import shlex

import pytest

from dokimi import cli
from dokimi.commands import personas as personas_cmd

# A hint names its program, then a subcommand (lower case). `dokimi` is the console script from
# pyproject.toml [project.scripts]; `python cli.py` is the source-checkout shim, which hints must not name.
_HINT = re.compile(r"(?:^|\s)(?:python cli\.py|dokimi)\s+(?=[a-z])")


def _hinted_argvs(text: str) -> list[list[str]]:
"""The argv (program first) of each command in ``text``, with backslash continuations joined."""
argvs = []
for line in text.replace("\\\n", " ").splitlines():
match = _HINT.search(line)
if match:
argvs.append(shlex.split(line[match.start():].lstrip()))
return argvs


def _assert_hints_parse(text: str) -> None:
argvs = _hinted_argvs(text)
assert argvs, f"no hint found in:\n{text}"
for argv in argvs:
assert argv[0] == "dokimi", f"hint names {argv[0]!r}, not the console script: {' '.join(argv)}"
cli.parse_args(argv[1:]) # SystemExit if the hint does not parse


class _FakeGenerator:
def __init__(self, **_):
pass

def generate_personas(self, **_):
return {"personas": [{"id": "p1", "name": "n", "skill_level": "novice"}]}

def save_personas(self, personas, output_path):
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(personas), encoding="utf-8")


def test_personas_discover_next_step_parses(tmp_path, monkeypatch, capsys):
monkeypatch.setattr(personas_cmd, "PersonaGenerator", _FakeGenerator)
args = cli.parse_args(["personas", "discover", "--product", "P", "--description", "d",
"--output", str(tmp_path / "P.json")])
args.func(args)
_assert_hints_parse(capsys.readouterr().out)


def test_personas_approve_next_step_parses(tmp_path, capsys):
persona_file = tmp_path / "P.json"
persona_file.write_text(json.dumps({"product": "P", "personas": [
{"id": "p1", "name": "n", "skill_level": "novice"}]}), encoding="utf-8")
args = cli.parse_args(["personas", "approve", "--file", str(persona_file)])
args.func(args)
_assert_hints_parse(capsys.readouterr().out)


def test_baseline_list_hint_parses(tmp_path, monkeypatch, capsys):
monkeypatch.chdir(tmp_path) # no baselines/eval/manifest.json here
args = cli.parse_args(["baseline", "list"])
args.func(args)
_assert_hints_parse(capsys.readouterr().out)


def test_baseline_compare_hint_parses(tmp_path, monkeypatch, capsys):
monkeypatch.chdir(tmp_path)
eval_file = tmp_path / "eval.json"
eval_file.write_text("{}", encoding="utf-8")
args = cli.parse_args(["baseline", "compare", "--eval", str(eval_file)])
with pytest.raises(SystemExit): # no baseline to compare against
args.func(args)
_assert_hints_parse(capsys.readouterr().out)


def test_baseline_only_report_hint_parses():
from dokimi.report.model_compare import _BASELINE_ONLY_LIMITATION

# The report quotes the command with a "> " prefix on every line.
_assert_hints_parse("\n".join(line.removeprefix("> ") for line in _BASELINE_ONLY_LIMITATION))
Loading