Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e0c4d3f
ci: no-growth ratchet for file length and function complexity
napetrov Oct 19, 2026
c9eef76
fix(ci_local): pass resolved base ref to growth check via DOKIMI_GROW…
napetrov Oct 19, 2026
6c4cc20
ci(growth): baseline evaluate_novelty at 26, as #72 left it on main
napetrov Oct 19, 2026
f105136
feat(ci-local): pass resolved base_ref as DOKIMI_GROWTH_BASE env var
napetrov Oct 19, 2026
65ca758
fix: restore the files f1051360 reverted by mistake
napetrov Oct 19, 2026
2ef72c1
fix(growth): a raised entry needs a new rationale; test loop/try else…
napetrov Oct 19, 2026
cde1461
fix(ci_local, check_growth): propagate DOKIMI_GROWTH_BASE to isolated…
napetrov Oct 19, 2026
221ccc8
Merge intel/main into ci/growth-ratchet
napetrov Oct 19, 2026
44f14a8
fix(ci_local, check_growth): cover isolated-job env and drop dead Try…
napetrov Oct 19, 2026
a803bd8
refactor: split functions over the complexity cap
napetrov Oct 19, 2026
b8493b2
test(answers): cover the helpers answers generate was split into
napetrov Oct 19, 2026
b11229a
Merge remote-tracking branch 'intel/main' into pr93-ci-fix
napetrov Oct 20, 2026
48adb41
ci(growth): take the #101 growth into the baseline; changelog fragment
napetrov Oct 20, 2026
5809ccc
fix(growth): compare rationale against the fork point, not the base tip
xaleryb Oct 20, 2026
2a2bdd2
test(growth): pin the C901 count and guards that no test reached
xaleryb Oct 20, 2026
18fb57e
test(growth,answers): pin the match-case counts, the subdirectory bas…
napetrov Oct 20, 2026
4c8c624
test(answers): make the flag row differ from config and default; fix …
napetrov Oct 20, 2026
8346024
Merge branch 'main' of https://github.com/intel/dokimi into HEAD
napetrov Oct 21, 2026
fb5275f
ci(growth): --seed mode, reseed baseline after merging main, document…
napetrov Oct 21, 2026
a38b9be
fix(growth): the --seed rationale does not explain a raise against a …
xaleryb Oct 21, 2026
a7f377b
test(growth): --seed keeps the rationale of an unchanged entry; drop …
napetrov Oct 21, 2026
edf7e19
ci(growth): reseed the baseline after the merge of main (main has no …
napetrov Oct 21, 2026
ce9b275
Merge intel/main into ci/growth-ratchet
napetrov Oct 22, 2026
cd6bc53
ci(growth): seed the baseline after merging main, with a rationale pe…
napetrov Oct 22, 2026
622926c
Merge intel/main into ci/growth-ratchet
napetrov Oct 22, 2026
e893657
ci(growth): re-seed the baseline after merging main again (#73, #115)
napetrov Oct 22, 2026
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ jobs:
- name: registry-drift-check
if: ${{ !cancelled() }}
run: python -m dokimi.config_check
# Not a gate: file length and complexity totals and the top offenders in
# the step summary, so the trend is visible per run. The gate (no growth
# past scripts/growth_baseline.json) is tests/test_growth_ratchet.py.
- name: File length and complexity report
if: ${{ !cancelled() }}
run: python scripts/check_growth.py --report --markdown >> "$GITHUB_STEP_SUMMARY"
- name: guidance self-scan (L0)
if: ${{ !cancelled() }}
run: python cli.py repo scan .. --manifest-file benchmarks/self-scan.repo-eval.yaml --fail-on-fail
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ also holds a PR to:
- Imports follow the layers in `dokimi/tests/test_import_architecture.py`: no
upward import, no new cycle, LLM SDKs only in `dokimi.llm`, spawns only in
`dokimi.process`. Its baselines only shrink.
- No growth: a file over 800 lines (tests 1000) or a function over complexity 10
fails unless `scripts/growth_baseline.json` holds it, and then it may only
shrink. Split instead; after shrinking, `python scripts/check_growth.py --update`.
- Tests that check behaviour: `tests/test_audit_test_quality.py` fails a test that only
checks types, echoes its input, only checks a mock was called, asserts nothing or
repeats another (`python scripts/audit_test_quality.py` lists them).
Expand Down
7 changes: 7 additions & 0 deletions dokimi/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ CI additionally holds a PR to:
(front end → tracks → evaluation → components → foundation; nothing imports `cli` or
`commands`), fails a new import cycle, and keeps provider SDKs behind `dokimi.llm` and
child processes behind `dokimi.process`. Its baselines only shrink.
- **No growth in file length or complexity.** A first-party `.py` file over 800
lines (1000 under `tests/`) or a function over cyclomatic complexity 10 (ruff's
C901 count) fails `tests/test_growth_ratchet.py` unless `scripts/growth_baseline.json`
records it; a recorded one may shrink, never grow, and its entry comes down with
it (`python scripts/check_growth.py --update`). Raising or adding an entry needs a
`"rationale"` of at most 300 characters. `--report` prints the totals; the static
job adds them to its step summary.
- **Tests that fail loudly.** `pytest` fails a test that runs over 60 s
(`@pytest.mark.timeout(N)` allows more), turns a warning into an error, and
shuffles the test order (pytest-randomly). The header prints
Expand Down
10 changes: 10 additions & 0 deletions dokimi/changelog.d/20261020_091500_napetrov_growth-ratchet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
### Changed

- **Internal refactor, no behavior change** — functions over the new
complexity cap were split into helpers. CI now fails a PR that makes a
file longer or a function more complex than its entry in
`scripts/growth_baseline.json`; `python scripts/check_growth.py --update`
lowers the baseline when code shrinks.
After rebasing onto main, run it to lower shrunk entries; `--seed`
re-records every over-cap entry at its current value. Once main has a
baseline, a raised or added entry needs a hand-written rationale.
73 changes: 40 additions & 33 deletions dokimi/dokimi/commands/answers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,59 @@
)


def _load_runtime_config() -> dict:
import yaml

from dokimi.config import runtime_config_path

config_path = runtime_config_path()
if not config_path.exists():
return {}
try:
loaded = yaml.safe_load(config_path.read_text(encoding="utf-8"))
except yaml.YAMLError as exc:
print(f"Error: invalid YAML in {config_path}: {exc}", file=sys.stderr)
raise SystemExit(1) from exc
if loaded is not None and not isinstance(loaded, dict):
print(f"Error: expected a mapping in {config_path}", file=sys.stderr)
raise SystemExit(1)
return loaded or {}


def _load_questions(path: str) -> list:
from dokimi.questions.artifact import load_questions

try:
return load_questions(path).questions
except ValueError as exc: # ArtifactValidationError: names the record and the fix
print(f"Error: {exc}", file=sys.stderr)
raise SystemExit(1) from exc


def _output_path(args: argparse.Namespace) -> Path:
if args.output:
return Path(args.output)
if getattr(args, "run_id", None):
return Path(f"results/{args.product.lower()}_{args.run_id}/answers/{args.product}.json")
return Path(f"answers/{args.product}.json")


def cmd_answers_generate(args: argparse.Namespace) -> None:
"""Generate context-arm and baseline answers for questions."""
if getattr(args, "ragas", False):
_require_ragas()
import yaml
from dokimi.eval import Answerer
from dokimi.mcp.factory import (
create_doc_source_client,
resolve_library_id_for_product,
)

# Load config
from dokimi.config import runtime_config_path

config_path = runtime_config_path()
config: dict = {}
if config_path.exists():
try:
loaded = yaml.safe_load(config_path.read_text(encoding="utf-8"))
except yaml.YAMLError as exc:
print(f"Error: invalid YAML in {config_path}: {exc}", file=sys.stderr)
raise SystemExit(1) from exc
if loaded is not None and not isinstance(loaded, dict):
print(f"Error: expected a mapping in {config_path}", file=sys.stderr)
raise SystemExit(1)
config = loaded or {}

_product_config = config.get("products", {}).get(args.product, {})
config = _load_runtime_config()
_retrieval_cfg = config.get("retrieval", {})
if args.top_k is None:
args.top_k = _retrieval_cfg.get("top_k", DEFAULT_RETRIEVAL_TOP_K)

# Load questions
from dokimi.questions.artifact import load_questions

try:
questions = load_questions(args.questions).questions
except ValueError as exc: # ArtifactValidationError: names the record and the fix
print(f"Error: {exc}", file=sys.stderr)
raise SystemExit(1) from exc
questions = _load_questions(args.questions)
print(f"Loaded {len(questions)} questions from {args.questions}")

# Setup documentation source client
Expand Down Expand Up @@ -91,13 +104,7 @@ def cmd_answers_generate(args: argparse.Namespace) -> None:
debug_retrieval=args.debug_retrieval
)

if args.output:
output_path = Path(args.output)
elif getattr(args, "run_id", None):
output_path = Path(f"results/{args.product.lower()}_{args.run_id}/answers/{args.product}.json")
else:
output_path = Path(f"answers/{args.product}.json")

output_path = _output_path(args)
answers = answerer.generate_answers(
library_name=args.product,
library_id=library_id,
Expand Down
Loading