Repository navigation
feat(novelty): routing takes the catalog field, skips disabled and mirrored skills, adds all-vs-all - #172
feat(novelty): routing takes the catalog field, skips disabled and mirrored skills, adds all-vs-all#172napetrov wants to merge 35 commits into
Conversation
…anifest --catalog already took the competitors from a skill directory; this adds the name the flag is asked for under, and makes the catalog reader the one catalog scan uses. A skills.yaml at the root now decides which skills are competitors. A declared skill that cannot be loaded is skipped with a warning instead of making the whole field unreadable. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
A shell glob over a skills directory passes every file beside the skills, and the first helper script (activation-rule.mjs) aborted the whole routing run. A skill: competitor that is not a SKILL.md, or a directory without one, is now left out of the field with a warning. The artifact under test is still refused, and a text: competitor is unchanged. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…the field No harness lists a skill that sets disable-model-invocation, so the model cannot select it, and a routing card for one measured a choice that does not exist. Such skills are now left out of a --catalog and of --competitors, with a warning on stderr. --include-disabled keeps them. A disabled artifact is still routed, with a warning. The flag is read with the truthy set repos.budget already uses. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
A plugin copy of a skills/ tree is a byte-identical SKILL.md under another path with the same name. The catalog read both, so the field named the skill twice and the run was refused. Catalog entries are now grouped by the SHA-256 of SKILL.md; the copy nearest the catalog root stands for the card, and the others are named in the warnings. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
… table Every skill with queries becomes the gold in turn and its queries are routed against the whole field. The output names the skills picked for other skills' queries (attractors), with how many of those queries each took and from which skills. Lexical arm always; the model arm when --model is given. Takes --queries-by-skill and --out, and no --artifact. The --artifact flag is now optional for a single run. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…-model counts its calls --competitors-from-catalog was an alias of --catalog, which is already on main. Drop the alias. The changelog and docs now describe the --catalog change itself: a skills.yaml at the catalog root decides which skills are competitors. Two catalog skills with one name and different content are refused, naming both paths, instead of falling through to check_field's "pass each competitor once". With --model, all-vs-all prints the number of model calls (one per query) to stderr before it makes them, and the docs note the cost. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 3ab7f1b. The field logic holds up: the manifest read, the name-clash check before the mirror dedupe, and the disable-flag spellings behave as described. The problems are in the new --all-vs-all mode and its tests. Five things are worth fixing before merge, and one nit.
What I ran, on a Linux review host at 3ab7f1b:
make ci-local: 20 passed, 8 failed, 7 skipped. The 8 failures are exit 127 (ruff, zizmor, pre-commit, bandit, mypy, deptry, mkdocs, pip-audit missing from my venv):make devstops at the tools install on this base, which #179 fixed onmain. Static checks, mypy and the strict docs build pass on GitHub's CI at this head. The test job passed: 7,100 passed, 21 skipped, changed-line coverage gate passed.tests/test_novelty_routing.py: 79 passed.- Reproductions for the code findings, with the LLM transport mocked.
- A mutation run of about 60 mutants on the new code: 35 killed, the survivors are in the test comment.
Checked, no issues
- Disable flag:
true,True,'true',yes,on,1read as disabled;false,no,ydo not. This matchesrepos/budget.py. - Clash and mirrors: a disabled skill that shares a name with an enabled one is dropped first, so there is no false clash. Ordering is deterministic.
- Call count: the stderr count equals the calls made on a valid queries file.
- Confusion and attractor figures:
own_rate,wrongly_picked,sibling_queriesmatch hand counts, and a query with no shared word is(none). - Changelog fragments: format and location match the existing ones.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #172 +/- ##
==========================================
+ Coverage 92.76% 92.80% +0.04%
==========================================
Files 261 262 +1
Lines 32898 33280 +382
Branches 5503 5578 +75
==========================================
+ Hits 30518 30886 +368
- Misses 1581 1584 +3
- Partials 799 810 +11
🚀 New features to boost your workflow:
|
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 70b7a72, which fix the nit from my review:
70b7a72:dokimi/docs/skill-novelty.md:602: the sentence now says a disabled artifact is still routed, with a warning, and the flag does not change that; the doubled "before quoting either:" at line 647 is dropped.
make ci-local at 70b7a72: 20 passed, 8 failed, 7 skipped.
Not approving yet. Still open: the bug and the four risks from my review.
… file, max-usd doc Rank with the gold last so a lexical tie is no win; count n_failed and n_abstained in the model arm, warn on failures and exit non-zero when every call failed; catch OSError/UnicodeDecodeError on --queries-by-skill; say routing has --max-usd; add tests for the surviving mutants. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…rowth caps Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 924aba9. All five points from my earlier review are fixed and tested, and the field logic still holds. Two things are worth fixing before merge, both in the new --all-vs-all command, and there are two nits inline plus one in the body.
What I ran, on a Linux review host at 924aba9:
make ci-local: 28 passed, 1 failed, 7 skipped. The failure ispip-auditongitpython 3.2.0(CVE-2026-114145, fix 3.2.1), a pinned dependency the PR does not touch. Tests: 7,258 passed, 1 skipped. The changed-line coverage gate passed.tests/test_novelty_routing.pyandtests/test_novelty_routing_all_vs_all.py: 99 passed.- Reproductions for the code findings, with the LLM transport mocked.
- A mutation run of about 55 mutants on the new code. The survivors from my earlier review are killed, except
gold=0, which you explained. Several new ones survive (below).
Nit: the all-vs-all tests leave a few outputs unpinned. With the full suite at 99 passed, these mutants survive: seed=0 passed to all_vs_all from _cmd_routing_all_vs_all; the top-five cap in _print_attractors changed to three; the name tie-break on equal wrongly_picked (and in from) dropped or reversed; the gold != name filter removed from sibling_queries, which only changes rate for an attractor that has queries of its own (the exact-row tests use a hub with none). Suggestion: add one case with two attractors tied on count and one attractor that also has queries, and assert seed in the saved payload.
Checked, no issues
- Tie scoring: two identical cards give
confusion {"a": {"b": 1}, "b": {"a": 1}}andown_rate0.0 in both field orders. A query with no shared word is(none). - Failed calls: a transport that raises on every call gives
n_failedequal to the query count, a stderr warning and a non-zero exit with the partial result written. - Queries file: a missing file, a non-UTF-8 file, a top-level list,
null, non-string items, an empty file and an unknown skill name each exit with a message, not a traceback. --max-usd: with a cap of 1.0 and a mock priced at 0.6 per call, the run stops after 2 calls withspend cap reached, and no--outfile is written. The docs now say so.- Field logic:
true,True,'true',yes,on,1read as disabled;falseand'off'do not. Byte-identical mirrors collapse to the copy nearest the root, and a same-name clash with different bytes names both paths. - Docs and changelog: the flags, outputs and defaults in
skill-novelty.md, the walkthrough and the five fragments match the code.
Single-mode routing.json keeps the selector model; the all-vs-all model arm did not, so files from two different models were indistinguishable. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
… its own The existing guard never ran: in its fixture no attractor has queries, so "rate over all queries" and "own queries not excluded" mutants passed. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
|
antonwolfy
left a comment
There was a problem hiding this comment.
Review at adbe542. The two commits since 924aba9 are fine: model is recorded in the model arm and the new attractor-rate test fails when the gold != name filter is removed. The four threads from my last review are unchanged at this head, so the PR is not ready: T1 and T2 are still risks.
What I ran, on a Linux review host at adbe542:
make ci-local: 28 passed, 1 failed, 7 skipped. The failure ispip-audit, which fails on the lock files; this PR does not touch them. Tests: 7,261 passed, 1 skipped.- Reproductions for the open threads, with the LLM transport mocked.
- A mutation check of the two new tests: removing
model=client.modelfails bothmodel-a/model-bcases (2 failed, 31 passed); removing thegold != namefilter insibling_queriesfails the new attractor test (1 failed, 32 passed).
Still open from my earlier review:
dokimi/dokimi/commands/novelty.py:364: a partial model failure still exits 0 and printsOK Saved. Re-run at this head: 5 queries overdbandperf, a transport that answers once then raisesrate limited, gives exit 0,own_rate{db: 0.5, perf: 0.0}andn_failed4.dokimi/dokimi/novelty/routing.py:541:{"a":["x"],"a":["y"]}still loads as{'a': ['y']}with no error.dokimi/dokimi/novelty/routing.py:600: the "not in the field" error still does not namedisable-model-invocationor--include-disabled.dokimi/dokimi/commands/novelty.py:375:--queriesand--queries-from-headingsare still ignored under--all-vs-allwhen--queries-by-skillis also given;--queries /nonexistent.json --queries-by-skill q.json --all-vs-all --out o.jsonprintsOK Saved all-vs-all routing.
@xaleryb's points on the own_rate denominator, the lexical tie among non-gold cards and the missing field rule in the payload wait for the author.
Checked, no issues
- Docs and changelog: the flags, outputs and defaults in
skill-novelty.md, the walkthrough and the five fragments match the code, including the newmodelsentence;mkdocs build --strictpasses. - Repo rules: SPDX header on the new test file, no schema or
with_docschanges, no generated artifacts, changelog fragments inchangelog.d/.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com> # Conflicts: # dokimi/scripts/growth_baseline.json # dokimi/tests/test_novelty_routing.py
…ree, partial failures exit non-zero Rates exclude unscored queries (no content word, failed model call) and report null when a skill has none scored. A tie among other cards is (tie). Repeated skill keys in the queries file are refused, the not-in-the-field error names disable-model-invocation, --queries is refused with --all-vs-all, and routing results record the field rule. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Review-body items from @xaleryb's comment, in 5e27afa: (1) own_rate denominator: rates are now over scored queries; a query with no content word or a failed model call is unscored, reported in n_scored/n_unscored per skill, and a skill with none scored has own_rate null (test_a_query_with_no_content_word_is_not_scored_as_a_miss). (2) lexical tie among non-gold cards: two or more other cards tied at the top are a (tie) bucket in every field order, never an attractor (test_a_tie_among_other_cards_is_a_tie_in_any_field_order). (3) field rule: routing.json, single and all-vs-all, now carries field {include_disabled, competitors, left_out} (test_the_saved_payload_records_the_field_rule, test_a_single_artifact_routing_run_records_the_field_rule_too). |
CLI interface change detectedThis PR changes dokimi's user-facing CLI surface (commands and/or options): ~ `dokimi novelty routing` parameters changed:
+ added option `--all-vs-all`
+ added option `--include-disabled`
+ added option `--queries-by-skill`
~ changed option `--artifact`: required: True -> FalseUpdated on every push; the |
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 898e053. The four threads from my last review are fixed and tested, and the field logic mostly holds. Five points are new: one bug in the mirror dedupe, three risks and one nit.
What I ran, on a Linux review host at 898e053:
make ci-local: 28 passed, 1 failed, 7 skipped. The failure ispip-audit(filelock, setuptools), which fails on the lock files; this PR does not touch them. Tests: 7,827 passed, 1 skipped. The changed-line coverage gate passed.- Reproductions for the code findings, with the LLM transport mocked.
- A mutation run of 13 mutants on the 5e27afa behaviours in
test_novelty_routing_all_vs_all.py: 10 killed, 2 survive (point 1), 1 equivalent.
Finding that cannot be anchored inline:
- Two tie rules are not pinned by a test. Changing
len(others) == 1to<= 2in the tie rule leaves all 44 tests intest_novelty_routing_all_vs_all.pypassing: it brings back the field-order dependence for a tie of exactly two other cards.test_a_tie_among_other_cards_is_a_tie_in_any_field_orderuses three other cards, andtest_a_three_way_tie_is_not_a_perfect_pickasserts onlyown_rate == 0.0. Changing the lexicalunscored.get(name, 0) + 1to= 1also passes, because the lexicaln_unscoredis only tested with one unscored query. Add a case with exactly two tied other cards that asserts(tie)in the confusion row, in both field orders, and a case with two contentless queries for one skill that assertsn_unscored == 2.
Checked, no issues
- Earlier threads: a partial model failure writes the result and exits non-zero; a repeated key in the queries file is refused; the not-in-the-field error names
--include-disabled;--queriesand--queries-from-headingsare refused under--all-vs-all. - Disable flag:
true,yes,TRUEandtrue # cread as disabled;falseand an absent key do not. This matchesrepos/budget.py. - Manifest and
fieldrecord: an unlistedSKILL.mdis not a sibling underskills.yaml, a ghost entry is a warning plus aleft_outline, and the flags and payload keys in the docs match the code. - Spend cap:
SpendCapReachedstill passes throughmodel_routing'sexcept Exception.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to b5830ba, which fix the nit from my review:
b5830ba:dokimi/changelog.d/20261021_133000_napetrov_routing-mirror-dedupe.md:3: the entry now describes what is new, that--catalogwith askills.yamlcounts byte-identicalSKILL.mdentries once and names the dropped copies on stderr.
make ci-local at b5830ba: 26 passed, 3 failed (host: gitleaks install and scan, pip-audit as at the reviewed head), 7 skipped.
GitHub CI has not run on these commits: the branch conflicts with main, and Actions runs no workflow on a conflicting PR. CI will check them once main is merged in.
Not approving yet. Still open: the bug and the three risks from my review.
vchamarthi
left a comment
There was a problem hiding this comment.
Not approving yet: 1 item to fix before merge.
One bug blocks merge: the mirror dedupe keys on file bytes alone and drops distinct skills that share text. The branch also conflicts with main, so no workflow has run at this head. The three open threads from the last review (console summary on ties and unscored queries, model arm scoring contentless queries) are not repeated here.
CI at send time: 0 failing, 0 pending.
| if best is None or (len(skill.path.parts), skill.path.as_posix()) < ( | ||
| len(best.path.parts), best.path.as_posix()): | ||
| keeper[digest] = skill | ||
| return keeper |
There was a problem hiding this comment.
Should fix: _keepers_by_digest keys on the digest alone, so two differently named skills with identical text collapse into one; key it on (skill.name, digest) as discover_skills does.
discover_skills at skills/loader.py documents that without a frontmatter name the name is the directory's, so identical text in skills/a/ and skills/b/ is two skills, and it keys on (_skill_name(meta, file), sha256). _eligible_catalog_skills returns (skill, digest), _keepers_by_digest stores keeper[digest], and catalog_competitors then drops every other skill with that digest as identical to .... Result: the field shrinks from two cards to one, the saved field.left_out calls a different skill a mirror, and an all-vs-all run loses a sibling, which is the confusable pair the mode exists to find. The same applies to two differently named skills.yaml entries that point at identical bytes. _refuse_conflicting_names does not catch it because it only compares copies of one name. Same point as the open inline thread by antonwolfy; a test with two differently named, same-text skills should accompany the fix.
There was a problem hiding this comment.
Fixed in 4596c9f, the same change as the thread on antonwolfy's review: _keepers_by_digest is now _keepers_by_copy, keyed on (skill.name, digest). Tests: test_two_differently_named_skills_with_identical_text_both_stay_in_the_field and test_two_manifest_entries_with_identical_text_and_different_names_both_stay, both red before and green after.
| { | ||
| "files": { | ||
| "dokimi/commands/novelty.py": {"lines": 864, "rationale": "W3 adds routing no_queries handling, W10 a --catalog help line; file split tracked separately to avoid conflicts across the W-series"}, | ||
| "dokimi/commands/novelty.py": {"lines": 1029, "rationale": "routing catalog field, disabled/mirrored skills, all-vs-all, field record in novelty CLI; file split tracked separately"}, |
There was a problem hiding this comment.
Question: growth_baseline.json on main now has novelty.py at 922 lines plus a cmd_novelty_routing complexity entry, so this line conflicts; after rebasing, does the 1029 figure still match the merged file, and does cmd_novelty_routing still sit under its complexity budget now that it dispatches to _cmd_routing_all_vs_all?
PR head novelty.py is 1029 lines. main growth_baseline.json has "dokimi/commands/novelty.py": {"lines": 922, ...} and "dokimi/commands/novelty.py::cmd_novelty_routing": {"complexity": 13, ...}. The PR's one-line edit replaces the 864 entry that main has since changed. mergeable_state is dirty, and only CodeFactor and DCO reported at this head.
There was a problem hiding this comment.
On the current head novelty.py is 1111 lines. The growth_baseline entry is that exact value with a rationale; 083ca41 raised it from 1088 for the console caveat and the blind-skill warning. cmd_novelty_routing keeps its recorded complexity of 13 (cap 10) under its existing rationale, and the gate measures 13 and passes. The new warning lives in its own helper, _warn_blind_skills, so _cmd_routing_all_vs_all is under the cap and needs no entry.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com> # Conflicts: # dokimi/dokimi/external/catalog.py # dokimi/scripts/growth_baseline.json # dokimi/tests/test_novelty_routing.py
Two differently named skills with identical SKILL.md text were collapsed to one card, because the dedupe keyed on the digest alone. Key on (name, digest) so only a true mirror, same name and same bytes, is dropped. Red tests first: both fail on the digest-only key. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
A tie and an unscored query name no pick, so 'no skill was picked' beside them read as a clean result. Print the tied and unscored counts under each arm, and warn on stderr when the lexical arm cannot score any query of a skill. Adds the contentless-queries test. novelty.py baseline raised to 1111 with rationale. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Found by the pr-review pass on 083ca41; no behaviour change. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Pushed 470cf73 on top of b5830ba (fast-forward, no force). intel/main (b2a9473) is merged in with sign-off; the conflicts were in catalog.py, growth_baseline.json and test_novelty_routing.py, resolved by keeping main's refactor and this PR's tests. Items from the two reviews at b5830ba:
Still open:
Not ready for another look until the model-arm decision is made. |
antonwolfy
left a comment
There was a problem hiding this comment.
The mirror dedupe and the console caveat are fixed, and CI is green. The model-arm decision is still open, and two new tests leave real regressions uncaught. Two risks and one nit are listed below.
What I ran, on a Linux review host at 470cf73:
make ci-local: 29 passed, 0 failed, 7 skipped. Tests: 8,449 passed, 2 skipped. The changed-line coverage gate passed.- Reproductions for the findings, with the LLM transport mocked.
- A mutation run of 62 mutants on the new code and its tests; the survivors I re-ran are inline.
Still open from my earlier review:
dokimi/dokimi/novelty/routing.py(model arm): a query with no content word is still sent to the selector. Fieldalpha/beta, queries{"alpha": ["the of and", "convert json"]}, a selector that always picks card 1: lexicaln_scored{alpha: 1},n_unscored{alpha: 1},own_rate1.0; modeln_scored{alpha: 2},n_unscored{},own_rate0.5, and 2 calls made. The docs (skill-novelty.md, then_scoredsentence) and the payloadnotestill say such a query "is not scored" without limiting that to the lexical arm. Either skip it in the model arm and count it inn_unscored, or say in both places that the model arm scores every query.
Checked, no issues
- Mirror dedupe: keyed on
(name, digest);skills/aandskills/bwith identical text stay two cards, and a same-name mirror collapses to the copy nearest the root. - Console summary: the tied and unscored counts equal the
(tie)total inconfusionand the sum ofn_unscoredin the saved JSON; the blind-skill warning fires only whenn_scoredis 0. - Docs and changelog: the flags, output keys and defaults match the code; the mirror fragment now claims only the
skills.yamlpath. - Growth baseline:
novelty.pyis 1111 lines and the entry says 1111.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 995b153, which fix the nit from my review:
995b153:dokimi/dokimi/novelty/routing.py:499: the Raises text now says a broken SKILL.md is skipped with a note, and that an invalid skills.yaml is what raises.
Left as is:
dokimi/dokimi/novelty/routing.py: the model-arm point is a risk left for the author, and the body has no Nit: or Non-blocking: item.
make ci-local at 995b153: 29 passed, 0 failed, 7 skipped.
Not approving yet. Still open: the two risks from my review; the earlier thread of mine above nit level.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com> # Conflicts: # dokimi/scripts/growth_baseline.json
Both arms now leave a contentless query unscored, so their rates cover the same queries and no selector call is spent on it; the stderr call count matches. Tests pin the tied/unscored console line (tied only, unscored only, summed over skills), a tie of exactly two other cards, and a same-name copy that differs only late in the body. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Pushed 1de5d71 on top of 995b153 (no force). intel/main (a2b787a) is merged in with sign-off in fc49df7; the only conflict was scripts/growth_baseline.json: main's version is taken, Items from @antonwolfy's reviews at 898e053 and 470cf73:
Still open for the author's call: two differently named skills with identical text are two cards, so in all-vs-all they tie ( Local: |
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 1de5d71. The model arm now skips a query with no content word, as the lexical arm does, and the stderr call count and the failed-call figure follow it. I found no wrong behaviour in the new code. One nit inline: a line of that change is not pinned by any test. @napetrov's question, whether two differently named skills with identical text should be one tied group in all-vs-all, is still open for the author.
What I ran, on Linux at 1de5d71:
make ci-local: 28 passed, 0 failed, 7 skipped. Tests: 9,057 passed, 2 skipped. The changed-line coverage gate passed.- Reproductions with the LLM transport mocked: a skill whose queries are all contentless, an abstaining selector, and contentless plus failed calls on one skill. The stderr call count equals the calls made, and the "N of M model call(s) failed" figure counts only calls sent.
- A mutation run of 15 mutants on the changed lines of
_model_armand_cmd_routing_all_vs_all: 13 killed, 1 equivalent (the earlycontinue), 1 survives (inline). - Mutants for my two test threads:
andfororin the console line fails 3 of the new console tests, and hashingread_bytes()[:50]fails the new late-difference test.
Checked, no issues
- Call count:
callsincommands/novelty.pyfilters with the sametokenizeas_model_arm; dropping the filter or counting every query fails a test. - Docs and changelog: the
n_scoredsentence, the cost paragraph, the payloadnoteand the changelog fragment now say a contentless query is unscored in both arms and not sent. - Growth baseline:
novelty.pyis 1174 lines andcheck_growth.pypasses.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
all_vs_all makes one selector call per query, so --repeats 3 ran one repeat, exited 0 and left no trace of the request in the payload. Refuse it as --queries is refused, and say so in the docs and fragment. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
|
antonwolfy
left a comment
There was a problem hiding this comment.
LGTM. The two commits since 1de5d71 do what they say: --all-vs-all now refuses --repeats above 1 instead of dropping it, and the failed-call accumulation onto contentless queries is pinned by a test. No new findings.
What I ran, on Linux at c8a9592:
make ci-local: 26 passed, 2 failed, 7 skipped. The 2 failures are the gitleaks download (the proxy returned HTTP 504) and the scan step that needs it; a gitleaks scan oforigin/main..HEADrun by hand found no leaks. Tests: 9,059 passed, 2 skipped.- Mutation runs on the new lines: replacing
unscored.get(name, 0) + routed["n_failed"]withrouted["n_failed"]fails the new test ({'alpha': 1} != {'alpha': 2}, 1 failed, 55 passed). Deleting theargs.repeats > 1check fails onlytest_all_vs_all_refuses_repeats_it_would_not_make(1 failed, 55 passed).
Checked, no issues
--repeats: the default is the int 1 (positive_int), so the comparison is safe. The refusal fires before any model call and before any--outfile is written; the test asserts both.- Docs and changelog: the
--artifact,--queries,--queries-from-headingsand--repeatsrefusals inskill-novelty.mdand the all-vs-all fragment match_check_all_vs_all_args. - Growth baseline:
novelty.pyentry raised to 1176 with a rationale;check_growth.pypasses inci-local.
@napetrov's question on whether two differently named skills with identical text should be one tied group waits for the author.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
all-vs-all: skills with the same description are reported once as tied_groups (console: a "tied:" line) and are no attractor of each other. A pick between them stays in the confusion matrix only, and their queries are not sibling queries. Resolves the scripts/growth_baseline.json conflict with intel/main (main's file taken). novelty.py baseline raised to 1195 with a rationale. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Self-review of the tied-group rule: group skills whose descriptions have the same lexical tokens (case and punctuation aside), not the raw string, since the tie they explain is lexical. Empty descriptions are no group. Tests now pin the sibling count, the model arm, the empty-description case, and the console line on a query that would otherwise print the group-mate as an attractor. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com> # Conflicts: # dokimi/docs/skill-novelty-walkthrough.md # dokimi/docs/skill-novelty.md # dokimi/dokimi/commands/novelty.py # dokimi/dokimi/external/catalog.py # dokimi/scripts/growth_baseline.json
…t as an abstention A model call that raises with no message stored error "", so the routing and all-vs-all counts skipped it and the run exited 0. The error now falls back to the exception type name. Also: --catalog help and docs name the mirror-once rule and say a shell glob must be expanded with the skill: prefix on each path; import order and two blank lines fixed. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Takes the PR branch's newer commits (fix(novelty): empty-message transport failure counts as failed) and resolves scripts/growth_baseline.json: novelty.py baseline set to the measured 1198 lines. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Part of #4.
Depends on #164 and #168: rebase after both merge (routing.py and the routing tests overlap with those branches).
Summary
novelty routingbuilds its field the way a harness does: the catalog's own manifest, without skills the model cannot select, and with each mirror counted once. A new--all-vs-allmode routes every skill's queries against the whole field and reports who takes whose queries.Evidence
Eleven new tests fail against
intel/main; the other 68 in the file pass there. The run revertscommands/novelty.pyonly, because the new tests importall_vs_all,load_queries_by_skillanddisables_model_invocationfromnovelty/routing.py, so reverting that file too would fail the whole module on import:The
routing.py-level tests (the attractor table, the(none)rule, the disable-flag spelling intest_the_disable_flag_is_read_the_way_the_repo_budget_reads_it) need the new module and cannot be run againstintel/mainat all.Local checks on the rebased head:
Merge Danger
Door: two-way. Every mode is additive except the default field, which now differs for catalogs with disabled or mirrored skills. Reverting restores the old field.
Blast Radius: medium. Existing routing figures change for any catalog that has disabled or duplicated skills, so older numbers are not comparable with new ones.
--catalogor--competitorsfield no longer includes a skill that setsdisable-model-invocation, and an artifact with that flag is routed with a warning. To keep the old field, pass--include-disabled.skills.yamlpresent,--catalogtakes its entries, and a declared skill that does not load is skipped with a note rather than aborting the run.SKILL.mdunder another path is dropped, keeping the copy nearest the catalog root.skill:reference to a file that is notSKILL.md, or to a directory without one, is skipped with a warning.text:and plain paths are unchanged.--all-vs-allmakes one selector call per query that has a content word (a query without one is unscored in both arms and not sent) over the whole field when--modelis given. The CLI prints the call count on stderr before it starts.--artifactis now optional only with--all-vs-all; a single run without it still fails.all_vs_allpayload written to its own--outfile.Checklist
pytestpassesruff,mypy, and schema checks passdocs/skill-novelty.mdanddocs/skill-novelty-walkthrough.md🤖 Generated with Claude Code