Skip to content

Releases: gal-a/qikly

v0.6.0: Three steps on one path: --score, --scaffold and --tasks, answered by number

Choose a tag to compare

@github-actions github-actions released this 19 Oct 18:02

Changed

  • Three steps, each taking the same module or folder: qikly --score my_module.py scores your tests (--score-code still works), qikly --scaffold my_module.py writes the task file with the score's questions in
    it to answer, and qikly --tasks my_module.py runs it. --validate,
    --explain and --take-tests take the same path, so nobody has to learn
    that my_module.py's task is called MY_MODULE_VERIFY; task ids still
    work as before.
  • --scaffold leaves only the questions to answer, for code you already
    have. Everything it can read from the code is filled in: the task name,
    the description, the interface without a TODO per function, the
    entrypoint. requirements starts as the module's docstring or a line to
    keep what the code does, marked TODO: MODIFY THIS: for you to replace in
    your own words, which --validate reminds you of. Left in place, the line
    goes to the agents without the mark. Each section sits under
    a dividing line with a short note on what it is for, with an example of
    the answer for requirements and acceptance_criteria, and the seed, the file under test, comes first. A module inside a
    package is seeded as its whole top-level package and named by its dotted
    path (acme.scoring), so a relative import such as from .util import clamp works in qikly's copy.
  • Each question in the task file says what your code does there now,
    found by running it in a separate process in an empty folder, and its TODO
    names the call: TODO: what apply_discount(100, 100) should do. A
    boundary is only ever run on either side of the value in question, never
    at it: "raise ValueError, as rate 101 does now" and "return a value, as
    rate 99 does now (it returns 1.0)". Other questions show what the code
    returns now and what the changed code would, labelled. The values come in
    an order fixed by the values, not with "as your code does now" first, and
    a function qikly sees reading or writing files is never run; the code runs
    in an empty folder, so only an absolute path it builds itself could reach
    your files. Questions about the
    same call with the same values are asked once. Those lines start with
    #?, so neither agent reads them.
  • Answer a question with its number. Each option qikly found is
    numbered with the criterion it becomes, and a TODO line can be answered
    2, 1: 0.0 for an option that asks for a value, keep for the option
    that is what the code does now, or a sentence, with or without quotes.
    Both agents and --validate read the answer as the full criterion, the
    file keeps what you typed, and --validate prints each short answer as
    the criterion it becomes and stops on a number that names no option.
  • A criterion left as TODO is skipped, not sent to test generation or
    any other model call as written: one that starts with TODO, followed by
    a colon, a space or nothing. A TODO inside a one-line list, [...], or
    on an anchored item is left as written, since removing it would break the
    file. --validate counts with the same rule, says
    how many are answered and that a run skips the rest, and --validate and
    --tasks both stop a file with none answered before a paid run.
  • --scaffold fills the data file with the values your tests already call
    the code with
    , so it is never only a header, and says to add any your
    answers name.
  • --scaffold takes a folder: one task file per module that the
    folder's last --score asked about. With no score yet, it says to score
    the folder first and names a module to scaffold instead. A package's
    __init__.py is named for the package, and a second module of the same
    name in another folder gets its folder's name too (SUB_UTIL_VERIFY),
    never another module's task file. Each task file names its module on its
    first line, which is how a path finds it, --task-id tasks included.
  • A run of a task about your own code ends with the step after it,
    qikly --take-tests my_module.py, which runs the new tests on your code.
  • --score plants every fault it can, up to 60 in one module or 150 in a
    folder, and a sample only when the suite is slow, where it used to plant a
    sample of 12. The demo module is now scored 10 of 13 missed where it was 9
    of 12.
  • --score finds your tests when you do not name them: the test_*.py
    and *_test.py files that import the code, searched from the project
    root, and it says which it used. --score-tests still names them, and is
    still needed with nothing to break.
  • --score and --score-suite lead with what was missed: "5 of 12
    planted faults missed (42%), 7 caught" rather than "7 of 12 caught". The
    misses are the finding, and a caught count read as a pass mark. Scripts
    that parsed the old line need the new form.
  • The saved report gives both counts on their own lines, caught then
    missed, and numbers the misses, the questions and the catches. The note
    that a score is a floor rather than a verdict moved to the end, after the
    lists it qualifies.
  • --scaffold's next steps name each file in full and put the free check
    before the paid run.
  • A run that passes says where the new tests are, that they add to the
    ones you have, and that qikly --score-suite --tasks <TASK> shows how many
    planted faults they catch.

Added

  • qikly --take-tests my_module.py: tests you can take home. The tests a
    run wrote import qikly's copy of your code; this copies each stage's files
    into qikly_generated/<task>/<stage>/ beside your tests, keeping their
    names, rewrites every import of qikly's copy to yours, writes a
    conftest.py that makes your code importable from wherever it lives, and
    runs them on your code. Failures are classified from pytest's own XML: tests
    written from your criteria, to compare with each criterion; tests no
    criterion is behind, the test writer's own reading, to check; calls to a
    helper only qikly's copy has; copies that could not import your module; and
    files that could not be collected. It never says to apply qikly's change. A
    file it did not write is never overwritten, a rerun removes its own stale
    copies, the folder is remembered, and outputs/conftest.py keeps a plain
    pytest from running qikly's own copies as well.
  • --score asks a question about every fault your tests missed, and
    --scaffold puts the questions where the answers belong.
    The report ends
    with a plain question for each miss, naming the file, line and function,
    and the comparison itself when it is the only one of its kind on the line,
    such as "your tests pass whether this is rate > 100 or rate >= 100...
    what should happen at exactly 100?". Misses that ask the same thing share
    one question. Question lines start with #? and are removed from
    everything either agent receives, wherever they end up in the file,
    because they quote the code; the answers are withheld from the coding
    agent like any criterion. A question whose line has since changed is
    dropped, and a newer score that asks nothing replaces an older one that
    did. Nothing guesses an answer and no model is called; the answers are
    yours, because tests read off the code can only agree with it.
  • Skill 0.3.7 walks an assistant through the three steps, and says the
    questions are the user's to answer, word for word, never its own.
  • Two guided flows were built and shelved before release: one question at a
    time in the terminal, and a separate file of numbered answers. On real
    libraries only 1 question in 63 had choices to pick from, so the task file,
    answered in sentences, stays the one place answers live.

Fixed

  • A failing test no answer is behind is never called a disagreement.
    --take-tests told the user to apply qikly's change to code that was
    right, when the failing tests were the test writer's own misreading of the
    API. Such failures are now listed as tests to check before changing
    anything, and the run report says the same. A failure written from an
    answer is no longer called a disagreement either: the test can assert
    more than the answer said, and qikly's change to its copy, which on one
    library altered a documented default, is offered to read, not to apply.
  • Scoring no longer hangs on a fault that loops forever. Each fault gets
    ten times the untouched suite's time, and at least a minute; one that runs
    past it is stopped and counted as caught, and the report says how many.
    cachetools' scoring had stopped for good on one.
  • A function defined under an if or try is not "missing". Such a test
    was set aside as an import failure, which hid a real disagreement.
  • Two different checks in one function are two questions. Grouping now
    also needs the same code on each line, not only the same question.
  • No anchor fault is skipped where it matters: .match(s, pos) and a
    public module-level pattern keep the ^ fault; an escaped \$ is no
    longer stripped into a pattern that does not compile.
  • Test calls are read only where the value is certain: a name changed by
    +=, a loop, a with, a tuple unpacking or a later assignment is left
    out, and so is indirect parametrize. Rows in a table that also holds
    objects, such as date(...), are read value by value, and a function
    imported through its package (from isodate import parse_date) is found.
  • --score on a folder with its tests inside says to score one
    module, instead of refusing a --score-tests you never typed; tests on
    another drive no longer crash it, and the path it prints can be pasted.
  • import os.path no longer counts as importing a project's path.py.
  • Smaller: a timed sample is never bigger than the whole set; __main__.py
    is not a module to test; a package run from inside its own folder is told
    to run from the folder above.
  • **No fault is planted by removing an anchor that change...
Read more

v0.5.9: Fix: --score-code results from earlier versions could be wrong; re-measure them

Choose a tag to compare

@github-actions github-actions released this 10 Oct 18:28

Fixed

  • --score-code and --score-suite could print a wrong score, in either
    direction, on any project.
    Each planted fault rewrites a file, and many
    rewrites, such as == to !=, leave it the same size. Python reuses a
    cached .pyc when a file's size and whole-second modification time match,
    so two such faults written within one second ran the first one's bytecode,
    and the second was given the first one's verdict. The same suite scored 2,
    3 or 4 of 8 depending on timing, including with a fixed --score-seed;
    with the fix, ten runs of it score 3 of 8 every time. Every __pycache__
    in the workspace is now deleted before each suite run, so no run can read
    a cache an earlier one wrote.
    Scores printed by earlier versions should be re-measured before they are
    quoted.
  • A score is refused when the suite never ran the code being scored.
    When the suite imported an installed copy of the package rather than the
    workspace copy, every planted fault ran against untouched code and the
    score read near 0%. That happened with a src/ layout and an editable
    install, a plain pip install ., a namespace package, and a suite living
    inside its own package. The suite is now run once with a marker in each
    scored file and with the files it loaded recorded. A scored file counts as
    never run when its marker did not fire, or when the suite loaded the
    original or an identical copy of it instead, and the score is refused when
    that is true of every scored file. Files the suite never ran are named in
    the report. When the check itself cannot complete, the report says so above
    the score, and a run in which nothing was caught is refused.
  • A suite inside the code directory is no longer mutated itself. Its
    broken tests were counted as faults caught.

Added

  • --compare-prompts TASK groups a task's run summaries by the prompts
    that produced them and compares the arms, without a model call. --repeats N runs N more of each arm first, alternating. It refuses to compare arms
    with fewer than three runs, arms that converged differently, or arms that
    differ in model or settings, and it judges noise by the interquartile range.
    To make that possible, a run summary now records which prompts were
    overridden and a digest of each (schema 7), and QIKLY_PROMPT_OVERRIDES=0
    makes one process use the shipped prompts while overrides stay on disk.
  • --install-mcp cursor writes .cursor/mcp.json, and a bare
    --install-mcp now writes it alongside the Claude Code and VS Code files.
    The file is checked; Cursor loading it has not been, which docs/mcp.md
    says.
  • Skill 0.3.6. The Skill now also sits at .claude/skills/qikly/SKILL.md,
    the path Skill directories crawl, and its mutation-score caution no longer
    quotes a count measured before the bytecode fix.

Changed

  • The landing page says what qikly is for, functional tests for backend
    Python, and what it does not test: interfaces, performance, load, security
    or other non-functional properties. It is about a third of its former
    length; the command table moved to TASK_FILE_REFERENCE.md.
  • A failing test was described as returning only its error text, on the
    landing page and in docs/FINDINGS.md. The coding agent also sees the
    failing test's source, and both now say so.
  • docs/FINDINGS.md is published, which the README already linked to,
    with the pricing example re-measured on the fixed scorer: 3 of 8, where it
    had said 4 of 8.

v0.5.8: The registry listing publishes itself, and a task named for code you already have says so when there is none

Choose a tag to compare

@github-actions github-actions released this 01 Oct 13:03

Added

  • --validate warns when a _VERIFY task has nothing seeded. The suffix
    means "test the code I already have", and seed.implementation is what
    makes that true. Without it a run writes a fresh implementation and tests
    that, which passes, so a green suite reads as a statement about your module
    when it describes code qikly wrote a minute earlier. Such a task used to
    validate clean with no warnings at all. It is a warning rather than an
    error, because generating from the requirements is a legitimate job and the
    name is the only thing suggesting otherwise.

Changed

  • The MCP registry listing is published by the release workflow, with no
    human in it.
    Publishing needed an interactive device login, so nothing in
    CI did it or could. It was missed in 0.5.5 and 0.5.6, leaving the listing
    two versions behind, and on 0.5.7 the login itself failed repeatedly and the
    listing stayed behind again. A new mcp-registry job authenticates with a
    GitHub OIDC token instead, checks server.json against the tag, and
    publishes after the PyPI upload. The listing installs by pin, so a stale
    entry hands people old software rather than merely looking out of date.

    The grant is per account, not per repository. The registry reads
    repository_owner from the token and grants io.github.<owner>/*, which is
    wider than the PyPI publisher above it. That is written in the workflow
    beside the permission rather than left to be discovered.

  • release_check fails on the cheap things first, so a wrong version or a
    dirty tree is reported in about a second rather than after a four minute
    suite. Nothing is skipped and nothing short-circuits; only the order
    changed.

  • The printed release steps can be pasted on Windows. They are run line by
    line in PowerShell, which carries on past an error, and the "wait for CI"
    lines were prose at the same indent as the commands. Pasting the block ran
    straight past the wait into git tag, which is how 0.5.5 published over two
    red Windows legs. The tag commands now sit under their own heading, and the
    manual registry steps are gone, because following them now races the job
    above and whichever publishes second fails on a duplicate version.

Fixed

  • A malformed seed: ended --validate with a traceback. seed: written
    as a string or a list, rather than a mapping, raised AttributeError out of
    the new _VERIFY check, and nothing caught it, so one bad task killed the
    whole command instead of being named by it. It now reports the same message
    the orchestrator raises at load time. Found by an audit before the tag, so
    no release carried it.

v0.5.7: A question asked at the one moment a user has just learned something, and the Skill's central rule moved to where an agent will meet it

Choose a tag to compare

@github-actions github-actions released this 30 Sep 05:09

Added

  • --score-code ... --score-tests ... now asks for the faults your tests did not notice, and
    only when there were some. The generated suites are measured against what
    real suites miss, and there is no public corpus of that, so it is the one
    thing nobody outside this project can supply any other way. It is silent
    when the suite caught everything, because an invitation printed after good
    news is an advertisement, and QIKLY_NO_INVITE=1 turns it off. This is the
    second place in the tool that asks a user for anything, and the first that
    does it when they have something to say.

Changed

  • The Skill states its central rule where an agent will actually meet it.
    Asked for tests with no specification, an agent read SKILL.md from the
    middle, never reached the routing instruction in the second section, and
    wrote a suite whose expected values it had obtained by executing the module.
    The rule was not missing, so more rules were not the answer: it is now a
    blockquote above everything, and restated in full at the head of the
    free-checks table, naming the four things that look settled and are not.
    Whether a threshold is inclusive, whether the discount applies before or
    after tax, where a floor lands, which way a half-cent rounds: an
    implementation answers all four and none of those answers is a
    specification.

    Recorded as an improvement rather than a fix, because it could not be
    shown to work. Eight benches, including controls carrying the previous
    Skill, did not separate the two versions: a capable model avoids this
    unaided, and under time pressure a weaker one declines to open the file at
    all, which no wording inside it can reach. The measurements are in the
    research log, including the description rewrite that also changed nothing,
    so neither is retried as though it were new.

Fixed

  • A Skill version that no longer moves when the instructions do. The
    number is bumped by hand, in a file edited for reasons unrelated to
    releasing. A pinned hash of the body now fails the suite when the text
    changes without it, and release_check prints which Skill a release
    carries, so "0.5.7 ships Skill 0.3.2" is stated on the day rather than
    discovered later.

v0.5.6: The `patch` a machine happens to have is now checked for being new enough, not just for being GNU

Choose a tag to compare

@github-actions github-actions released this 29 Sep 18:41

Fixed

  • An old patch on PATH aborted on every repair inside a package, and
    nothing said why.
    GNU patch 2.5.9 fails an internal assertion when a hunk
    writes into a subdirectory, which is what a package-seeded implementation is
    made of. It is from 2002, and Strawberry Perl, the usual Perl distribution
    for Windows, installs it ahead of Git's copy on PATH. The existing guard
    only asks whether a patch is GNU, which this one is: it takes --fuzz and
    --dry-run without complaint and then dies with a dialog naming a line in
    patch's own C source, so a Windows user saw every FIX fail for a reason no
    message connected to the binary they never chose. qikly now asks each
    candidate for its version and steps over anything below 2.7, which finds the
    current GNU patch that Git for Windows ships under usr\bin even when
    another one comes first. A patch whose version cannot be read is still
    used, so nothing that works today stops working. The version is a
    preference rather than a veto: where no candidate is new enough, the first
    is still used, because an older patch refuses these flags outright and
    says so, and on macOS without gpatch that is Apple's own. The one refusal
    is a patch between 2.5 and 2.7, which accepts the flags and then aborts;
    there the error names each binary, its version, and the fix for the
    platform, since nothing else would connect the crash to the cause. Found on
    GitHub's own Windows runners, which preinstall Strawberry Perl.

v0.5.5: A Skill for GitHub Copilot, an implementation that can be a whole package, and a fault outside one that says so instead of being worked around

Choose a tag to compare

@github-actions github-actions released this 29 Sep 14:59

Added

  • qikly --install-skill copilot. GitHub Copilot reads none of the four
    skill directories, so every VS Code user working in Copilot rather than in
    an agent CLI had no way in except the MCP server. It reads instruction files
    out of .github/instructions/, so this writes the Skill folder there and a
    qikly.instructions.md beside it, which is the file Copilot actually opens.
    Copying the folder alone would have put files on disk that nothing ever
    reads. --install-skill auto picks it up when a project already has
    .github/instructions/ or a copilot-instructions.md, and not merely
    because it has a .github/, which almost every repository does.
  • seed.implementation pointing at a directory now seeds a package. It is
    copied in under its own name and the task's code directory goes on the path
    for the test run, so from mypkg.money import to_cents, the way most real
    packages refer to themselves, resolves exactly as it does in your own tree.
    Every module in the package is visible to the coding agent, every one can be
    repaired, and a single patch may change several at once. interface.module
    becomes the real import path, mypkg.pricing. Before this a directory seed
    was flattened, which dropped the package's name and broke every
    self-referencing import inside it, so this is the shape that makes an
    implementation of more than one file work at all. A file seed and an
    unseeded run are untouched: they get no import root and an unmodified
    environment. docs/TASK_FILE_REFERENCE.md has the limits, and the full
    matrix of import shapes, including the ones that do not work, is pinned in
    tests/test_multi_module_seed.py.
  • A run says when a fault is somewhere it may not write. A task owns one
    directory. Until now a defect in a helper module outside it was found by the
    tests and silently unfixable, so the run either stopped with a generic
    "exceeded 10 attempts" or, worse, changed a module it did own to work around
    a defect still sitting there. Three messages replace that: the imports it
    cannot repair are named at the start, a stall names the file rather than the
    count, and a run that goes green after a failure was traced into such a
    file says so
    , because that green may be a workaround. Nothing here stops,
    shortens or alters a run; a project with shared helpers is normal and the
    guard only speaks up.

Fixed

  • A package-seeded implementation was shadowed by the user's own copy of it,
    so the tests never ran the code the agent was repairing.
    python -m pytest
    puts the working directory at the front of sys.path, ahead of anything
    PYTHONPATH adds, and the working directory is the project root where the
    original package sits. The suite imported that, every patch applied cleanly,
    and no test outcome ever changed. A pytest plugin loaded only for a
    package-seeded run now puts the task's own directory first. Nothing is
    written into your tests directory, so a seed.tests conftest.py of your
    own cannot be touched by it.
  • A diff naming a file that does not exist is now matched, by unique path
    suffix, against the files that do.
    Small models drop a directory from a
    path they were asked to copy, and a package gives them one more to drop:
    every patch in a run came back with the task's own directory missing and all
    ten were rejected. The same repair runs when a FIX names such a path, which
    previously skipped the file in silence and left PATCH generation with no code
    in its prompt at all. Only ever reached from the state where the patch was
    about to be discarded, so a diff whose paths resolve is untouched.
  • The # FILE: headers the coding agent copies into its diffs mixed \ and
    / on Windows.
    Forward slashes throughout now, which is the convention
    diffs use anyway.
  • --install-skill copilot destroyed a file somebody else had written.
    The Copilot target writes two things, the Skill folder and the pointer file
    Copilot opens, and only the folder was covered by the promise this module
    makes about not overwriting. An existing
    .github/instructions/qikly.instructions.md was replaced by a bare
    open(path, "w"): no --force, no backup, no message. That directory is
    one people write their own instruction files into. The pointer is now moved
    to a timestamped copy first, and the install says where it went.
  • A blocked pointer left the Skill folder on disk and reported that nothing
    was written.
    Something that is not a file sitting at the pointer's path
    raised after copytree had already run, so the state was partial and the
    message said total failure, and every re-run then said "already exists".
    It is found before anything is copied and named in the message.
  • A Copilot folder with no pointer beside it reported as fine. Copilot
    reads the pointer, so that install cannot load, and "already exists, so
    nothing was changed" was true of the folder and false of the installation.
    The missing pointer is now written and said so.
  • --score-suite scored nothing for an implementation of more than one
    file.
    It listed one directory level, so a package's modules were invisible
    to it and the report read exactly like a trivial implementation rather than
    like a failure. It now walks the tree, which is what the coding agent's own
    view of the code has always done.
  • A seed.implementation resolving to .. installed outside the task's own
    directory
    , into the one every task shares, and the copy then recursed into
    what it had just written. Refused now, with a message naming the fix. A
    package whose name is also a standard-library module's is refused for the
    same reason: seeding a json/ would shadow the real one for everything the
    run imports.

Changed

  • The Skill now sets the model rather than only warning about it (Skill
    0.3.1). Before the first paid command it sets LLM_MODEL=claude-haiku-4-5
    when ANTHROPIC_API_KEY is the key in play, and says so. That is the only
    provider whose default thinks before every answer, and a first run that
    takes twenty minutes gets blamed on qikly rather than on the key that
    happened to be set. A model chosen deliberately is left alone.
  • The demo's folder is named demo/throwaway_<timestamp>/. The timestamp
    alone said when, not what, and somebody who has just watched the demo work
    is standing in something that looks exactly like a working project. The
    guard that refuses --init, --example and --demo inside one still
    recognises folders made by earlier versions.
  • What --install-skill prints, in four ways. "No agent directory here
    yet" read as "your agent is not supported" to somebody sitting in one; it
    now says nothing here says which agent you use. The alternatives for Cursor
    and Copilot moved below the result, because advice about tools you do not
    use is a strange thing to read before finding out what happened. Copilot
    "reads neither" became "reads none of these", there being more than two. And
    it now says what to do when the agent does not pick it up: name it once, and
    it is usually found on its own next time, because the listing is ranked by
    how often each skill has been used and one never invoked sorts below every
    one that has.
  • A suite score now says what it cannot tell you, under the number: it
    reports how much of the code that is there your tests would notice being
    changed, and nothing about a rule nobody implemented, because there is
    nothing there to break. A seventeen-test suite caught 8 of 8 planted faults
    in a real session while a suite written from the specification found four
    genuine bugs in the same file. The Skill says the same to an agent before it
    hands somebody a percentage.

  • The README test badge floor moves to 1,700+ tests. It is a floor rather
    than a count, so it is deliberately behind what the suite actually runs.

v0.5.4: Score a suite qikly did not write, and say which qikly, when, and where it is standing

Choose a tag to compare

@github-actions github-actions released this 28 Sep 04:45

Added

  • qikly --install-skill now installs where your project says your agent
    is.
    It wrote .claude/skills/ and nothing else, so somebody working in
    Cursor got a directory their editor does not read, no error, and no hint
    that three other conventions existed. It now looks for .claude/,
    .agents/, .cursor/ and .gemini/, writes to whichever are there, says
    which it chose, and falls back to Claude Code's path plus the cross-agent
    one when a project shows no sign either way. Never both Gemini paths at
    once, which is what makes Gemini CLI warn about every skill overriding
    itself.
  • qikly --score-code PATH --score-tests PATH scores a suite qikly did not
    write.
    It needed a task file and a converged run, which is the least likely
    thing a newcomer has: everyone has a suite before they have anything else.
    Point it at code and tests you already have and it plants faults and reports
    what your tests would not have noticed. Free, no model call, nothing of yours
    modified, and the report is written beside your code rather than into qikly's
    project. Found by watching an agent that had read --help, could not use the
    flag, and wrote its own mutation harness instead.
  • Every command that does work now opens with a stamp: the version, the
    date and time, and the directory it was run in. A user reports a problem by
    sending a photograph of their terminal, and until now 0.4.6 and 0.5.3 printed
    identically, so the first reply was always a round trip asking which version.
    --validate --json is unchanged, because that output is parsed.
  • It says when the project root is somewhere else. qikly moves to a
    resolved project root at startup, so a command run in one directory can
    operate on files in another, and nothing said so. That has now caused a sweep
    that wrote into the wrong tree, a test that scored the wrong project, and a
    first-time user who could not find files the tool had just reported creating.
  • --init, --example and --demo refuse to run inside demo/<timestamp>/
    and say where to go instead. The demo runs there so it touches nothing of
    yours, which also means everything in it is thrown away; someone who has just
    watched it work is standing in something that looks exactly like a working
    project. Reported by a first-time user.

Fixed

  • tools/release_check.py reported the Skill missing when run from
    anywhere but the repository root. One check used a bare relative path where
    every other resolves against the repository, so a release could fail for a
    reason that was not true.
  • An installed Skill never updated itself and nothing said so. pip install --upgrade qikly replaces the package and cannot touch a folder copied into
    your project, so an upgrade left you following instructions that name a
    different set of commands. Any qikly command now says when the Skill it finds
    in the directory you are standing in is older than the one it ships, and what
    to run about it. Silent when it is current.
  • --init and --example now say so before filling your home directory.
    A first-time user ran --example in his home folder and finished with
    inputs_private/, outputs/, a module and a demo/ tree sitting beside
    Documents, Downloads and Dropbox, then could not tell which of two
    identical-looking trees was his project. Warned rather than refused, since
    nothing there overwrites anything.
  • The upgrade notice stopped repeating itself after instant commands. It
    prints once at the start and again at the end, because a run takes minutes
    and the first line scrolls away. On --validate, which returns in under a
    second, that put the same sentence twice in a four-line output.
  • Upgrading left you with the previous release's Skill, silently.
    pip install --upgrade qikly replaces the package and cannot touch a Skill
    already copied into your project, so an upgrade left you following
    instructions that name a different set of commands. --install-skill said
    "already exists, nothing was changed" whether your copy was identical or
    three releases behind. It now says when yours is older and what to do.
  • qikly --by week on its own started a real, billed run, the fourth flag
    with this defect. It was left out of the guard on purpose, because it carried
    an argparse default and a value the user typed was indistinguishable from the
    one argparse supplied. That reasoning was right about the symptom and wrong
    about the fix: the default now lives in the handler and the flag is guarded
    like every other. The test written to catch this class missed it, because it
    probed every value-taking option with the string "x" and argparse rejects
    that before the code under test is reached. It now probes with a value each
    option accepts.
  • qikly --json on its own started a real, billed run, the third flag with
    this defect and the one three separate audits walked past. --html has had
    this check since it shipped; --json sits beside it and never did.
  • A directory link that loops back on itself hung the workspace copy and
    produced a wall of nested OS errors. The cycle guard added for this covered
    the file count and not the copy, which protects nothing. It now refuses, and
    names the link.
  • qikly --artifacts-url ... on its own started a real, billed run, the
    same unguarded fallthrough as the one below, in the sibling flag nobody
    thought to check. Found by auditing the fix for the first one.
  • qikly --score-mutants 5 on its own started a real, billed run. Neither
    --score-mutants nor --score-seed triggers the scoring path by itself, and
    neither was listed in the guard that refuses an option with nothing to attach
    to, so control fell through to the ordinary run. Found by an audit that
    reproduced it by accident and spent money doing so.
  • A demo directory made with --demo-dir was invisible to the guard that
    stops you building a real project inside a throwaway copy. That guard matched
    on the path's shape, and --demo-dir ./try-it produces try-it/<timestamp>/
    with no demo component anywhere, which is the combination the help text
    itself recommends. Every demo directory now carries a marker file saying what
    it is.
  • The saved report still told you to converge a run you never started. The
    console message was made mode-aware and the report file was not, and the
    report is the artefact people keep and paste into a pull request.
  • --install-skill gemini implied that path had been watched loading. It
    has not: Gemini CLI also reads .agents/skills/, and that is the one that
    was observed. The message now names what was tested per path rather than per
    tool.
  • The demo marker had no way out. A kept demo folder blocked --init,
    --example and --demo for every directory beneath it, with a message that
    never named the hidden file responsible. The refusal now says how to undo it,
    and a marker that cannot be written says so rather than failing silently.
  • A symlinked subdirectory slipped past the size ceiling on the workspace
    copy: the count did not descend into it and the copy dereferenced it, so a
    project with a linked virtualenv was counted as a handful of files and then
    copied whole.
  • --install-skill all printed "That is the path all documents for skills",
    substituting the target name where an agent name belongs, and implying one
    path when it writes four.

v0.5.3: Two free checks that tell you what your tests are not measuring, and an agent Skill

Choose a tag to compare

@github-actions github-actions released this 27 Sep 07:08

Added

  • qikly --validate now names the criteria your data cannot reach. A
    criterion naming a value no input row holds produces a test that passes
    whatever the code does, and nothing said so. The check is free, makes no
    model call, and names the field and the command that drafts the missing
    rows. It is deliberately narrow: it reports only when a criterion names a
    field your data has, names a value for it, and that field holds none of
    them. Looser rules were measured against the thirteen bundled tasks first
    and each one drowned the real findings, at 49 and then 11 false reports out
    of 134 criteria. This one reports none of them, which is the right answer
    for fixtures audited for exactly this.

  • qikly --score-suite plants faults in your code and reports what your
    suite missed.
    A suite that tests nothing passes everything, and a run
    cannot tell you the difference. This breaks the converged implementation in
    one known place at a time, re-runs the suite against each, and lists what
    went unnoticed. Free, because every fault is an edit to the syntax tree and
    no model is asked anything; slow, because each fault means running your
    suite again. It refuses to score a suite that already fails on untouched
    code, and it prints the reachability warning above the number, because a
    suite cannot catch a fault in behaviour no input row exercises.

  • An agent Skill, with qikly --install-skill. A folder your coding agent
    reads: when to reach for qikly, which line of a specification is a decision
    the coder needs and which is a consequence to withhold, what a repeating
    identical patch means, and which commands cost nothing. It installs where
    Claude Code looks by default, and --install-skill agents, cursor or
    gemini write where those tools document their own. A Skill is
    instructions and enforces nothing
    : the withholding is enforced in qikly's
    code whether or not the Skill is installed, and the Skill says so itself.

  • A run says when it is waiting. A model call printed nothing until it
    answered, which on a reasoning model is minutes of silence that looks
    exactly like a hang. After ten seconds it now says so, one line every
    fifteen. QIKLY_NO_PROGRESS=1 turns it off.

Fixed

  • --score-suite crashed on its first line, reading an args.seed that no
    flag defined. Found by running the command rather than the function, which
    is now how the command line is tested: 25 free commands, each run for real,
    each asserted not to produce a traceback.
  • A NUL byte in any input file crashed --validate for every task in the
    run.
    csv.reader raises outside the read that was guarded, and
    --validate is recommended as a pre-commit hook, so one misencoded export
    stopped it reporting on anything.
  • The README's test-count badge is now a floor rather than a count. It
    claimed an exact number that only CI can observe, so every update was a
    guess below an invisible figure, and its own check had stopped running.

v0.5.2: The withheld criteria could reach the coding agent six more ways, all closed, and a run that stops when you stop it

Choose a tag to compare

@github-actions github-actions released this 24 Sep 09:56

Fixed

  • Six further ways the acceptance criteria could reach the coding agent, all
    found by audit and all confirmed by running before being believed.
    A YAML
    merge key (<<:) pulling criteria in from an anchor, which left the file
    entirely unstripped. An alias whose anchor lives under another key, so the
    reference went and the content stayed. Two acceptance_criteria: keys, where
    the parser takes the last and the strip removed the first. A second copy
    nested under an unrelated key. Criteria written as a mapping rather than a
    list, which the verification collected as nothing at all. And a requirement
    short enough to blind the check meant to exempt it.

    One cause behind all of them: yaml.safe_load decides what the criteria are,
    because it is what test generation reads, and every other idea of where they
    live is a second opinion that can differ. The strip now removes the span YAML
    itself assigns the key, then verifies by collecting every criterion the
    parser sees, at any depth and whatever shape holds it, parsing the handed-over
    text back, and comparing values to values with whitespace and Unicode
    normalised. A file it cannot prove clean is refused rather than handed
    over
    , naming the construction so its author can fix it. No bundled task,
    and no file the scaffolder writes, is affected.

  • Ctrl+C now stops the run it says it stopped. terminate() reaches the
    task process and not the pytest it is blocked inside, so a test runner
    supervised by nobody kept writing to the iteration log after the console said
    "Stopped". The whole tree goes now, and the ordering is the fix: taken after
    the task exits, its children have been reparented and there is nothing left
    to walk from.

  • A generated test that reads a name before assigning it is regenerated
    rather than run.
    records = transform(records) raises NameError every
    time, so the coding agent sees an identical failure each iteration with
    nothing to act on and spends its whole budget on a file whose problem is not
    in its code. Measured on the worked example: every failure in 50 runs was a
    test of this shape, and the check flags 4 files in 1426 real generated ones.

  • The repeated sweep only ever ran from a git clone, so a sweep started
    from an installed copy failed once per task per repetition with no model
    call. Its own accounting is what caught it, refusing to quote a rate computed
    over zero runs.

Changed

  • The update notice says how to upgrade. pip install qikly on an existing
    install prints "Requirement already satisfied" and changes nothing, so a
    reader could follow the notice, type the obvious command, and stay where they
    were. It now names pip install --upgrade qikly.
  • An aggregate says when the runs it pooled were not the same experiment,
    comparing the model, the provider, both budgets, the feedback settings, the
    version and the commit that produced each one, and saying when a field is
    missing from some of them rather than treating absent as agreeing.
  • PRIVACY.md now names the Jira fetch and the User-Agent the version
    check sends.

Measured

  • Convergence held while the bar was tightened, which is the claim worth
    making: 80% of runs pass every integration and system test and 60% pass
    everything, over 130 runs on this release and 520 across 0.5.x, unchanged
    from the 967 runs measured before any of this hardening existed.

v0.5.1: The withheld section stops being found by a regex, a worked example that arrives worked, and a run that says why it is quiet

Choose a tag to compare

@github-actions github-actions released this 24 Sep 06:03

Fixed

  • The acceptance criteria could reach the coding agent, twice over, and both
    routes are closed.
    Two separate defects in the same strip, found a week
    apart, neither triggered by any bundled task:

    A comment written between two criteria ended the match, because the lookahead
    stopped at any line beginning with a non-space character and # is one.
    Every criterion below that comment was handed over word for word. Nobody had
    put a comment there, which is why nothing caught it.

    Then, with that fixed, a wrapped criterion did the same thing. A double
    quoted YAML scalar may continue on a line starting at column 0, so a long
    criterion wrapped without indenting its second line reads as one list item to
    YAML and as the end of the section to a lookahead for the next key.

    Both come from the same mistake, so the fix is to stop making it: the strip
    no longer re-derives where the section ends. yaml.compose returns the node
    tree with source offsets, the span YAML itself assigned to the key is cut out
    of the raw text, and the strip and yaml.safe_load now agree by
    construction. A task file that does not parse raises instead of falling back
    to something looser. This also fixes a quieter bug in the same lookahead,
    which silently deleted any top level key containing a dot.

  • qikly --scaffold X --from-doc Y wrote a task file that did not parse,
    then died inside its own yaml.safe_load. Scaffold's placeholder criterion
    wraps, and the merge that replaces it matched only lines opening with a dash,
    so the wrapped half was stranded below the replacement. It is step 3 of the
    quick start, so it was reachable by following the documentation.

  • --example handed over two tasks, its own and --init's blank
    MY_FIRST_TASK starter, because it made the layout by calling init_project.
    One of them was documented nowhere, and a later bare qikly with no --tasks
    would have run both.

  • Ctrl+C during a run left the task processes running and printing, so the
    console looked like it had ignored the interrupt. Reported from a Windows
    shell, where it is worst. It now waits for each process to actually go, kills
    anything still up after fifteen seconds, and names whatever survives instead
    of promising nothing more will be written: terminate() reaches the task
    process and not the pytest it is blocked on.

  • The repeated sweep only ever ran from a git clone. run_all shelled out
    to run.py by bare relative path while its two sibling stages used -m, so
    a sweep started from an installed copy failed once per task per repetition,
    instantly, with no model call. The accounting is the only reason it did not
    read as a convergence collapse: every repetition reported 0 summaries of 13
    and the aggregate refused to quote a rate over 0 runs.

  • A generated test could call the function under test twice, as in
    transform(transform(rows)), which hands a function its own output. No
    implementation satisfies that, so the run stalls with an identical failure
    every iteration and nothing to learn from. Test generation is now told to
    call the function under test once per scenario, and to feed one function's
    output to the next one rather than to itself.

Changed

  • The # Criteria: 2 markers no longer reach the coding agent. Test
    generation writes them into each test's docstring for the reviewer and the
    coverage report, and pytest prints a failing test's whole docstring, so they
    went over too: how many criteria exist, which one was just failed, and across
    iterations a partial map of the bar. Unlike the prose around them they carry
    nothing a repair can use, which makes this the only one of the three channels
    that closes for free. traceability_markers_visible: true under agent:
    restores the old behaviour. The suite on disk keeps its markers.

    The other two channels stay open and are now written down in the README
    rather than implied. A failing test's docstring paraphrases its criterion and
    is load bearing for the repair, so it stays under diagnostic_feedback, off
    by default. Test names travel for passing tests as well, and closing that
    would cost the per-test statuses the reports are built from.

  • The worked example arrives finished. qikly --example used to lay down
    task files with TODO in the two sections the agents read, while printing
    the paid qikly --tasks line as a next step and --validate warning but
    exiting 0. Both files now carry written requirements and criteria, every
    criterion names a boundary some row in the sample data reaches, and two tests
    hold it there.

  • qikly --validate and qikly --explain look at the rest of the handover,
    not only at requirements. Everything in a task file except the criteria
    block goes to the coding agent, comments included, so a note explaining an
    answer is the answer. --explain's verdict compares text, which reads wider
    than it is, and it now says so when a line restates a criterion without
    copying it. Read both as a prompt to look rather than as a guarantee: the
    measure is word overlap, and a paraphrase can score below the threshold.

  • The run says when every call will think. Anthropic's default is
    claude-sonnet-5 and the Messages API reasons whenever no thinking parameter
    is sent, which is what this project sends, so a demo that takes under a
    minute on the Gemini default has taken about sixteen on that one with nothing
    printed throughout. The banner now names the effective model and what it
    implies, a line follows any call over 45 seconds, and
    docs/TROUBLESHOOTING.md#provider-defaults has the table.

  • Integration tests are named after the seam they cross,
    test_<first>_to_<second>_<what>, so a failure says where the pipeline came
    apart rather than only what was wrong.

  • CALC_TAX lost half its commentary. It carried a tutorial that restated
    the quick start almost point for point, and comments are not stripped, so
    every FIX and PATCH prompt of every run paid to read it.

  • The worked example pins the shape of the record it produces. Its
    specification named no output fields, so test generation invented
    station_id, day, min_temp_c and mean_humidity_pct from the prose and
    the coding agent had to arrive at the same four names from the same prose
    without seeing the tests. Nine times in ten they agreed. A field name is a
    decision somebody made rather than a consequence of one, so it belongs where
    both agents read it, which is where CALC_TAX has always put its own. Nothing
    withheld moved: the six criteria are unchanged.

  • The convergence figures were re-measured on this release, over 390 runs:
    80% [76-84] pass every integration and system test and 60% [55-65] pass
    everything
    , the same 8 in 10 and 6 in 10 measured before the hardening
    above. The rate held while the bar was tightened, which is the claim worth
    making, rather than merely holding while things changed. Both sit inside the spread of the three August sweeps, so none of
    this moved convergence by anything a sample that size can detect, and the
    numbers held while the bar was tightened rather than loosened. They are
    reported beside the earlier sweeps rather than pooled with them, for the
    reason the new aggregate guard exists.

Added

  • qikly --propose-fixtures --tasks X. It was reachable only as python -m qikly.orchestrator.tuning.propose_fixtures, shelved with the scripts that
    measure this project, while it is a step in preparing your own task: it
    reports which acceptance criteria no row of your data reaches and proposes a
    row for each, and never edits your fixtures. One model call per task, and it
    reports usage like every other command that spends money.

  • tools/release_check.py, which performs every check in
    RELEASE_CHECKLIST.md that a machine can perform. --after confirms the
    three places a release lands agree once the workflow finishes.

  • The README says where test data comes from: invented by the test-writing
    agent for the unit and integration stages, yours for the system stage. The
    agent never sees your fixture data, which is why a criterion no row reaches
    is checked by an invented row and never on the path the code will take.

  • An aggregate says when the runs it pooled were not the same experiment.
    Every run summary already recorded the model, the provider, the two budgets
    that decide how many attempts a task gets, the version, the commit and
    whether the tree was dirty, and nothing compared them. A sweep taken before a
    change and a sweep taken after it aggregated into one number with no sign it
    spanned two systems, which is the failure mode every withdrawn result in this
    project shares. Warnings rather than a refusal, printed to the terminal as
    well as rendered.

  • PRIVACY.md, linked from the README and the landing page footer. A run
    sends your specification and the code under test to the model provider you
    configured, with your own key, from your machine; there is no qikly server to
    send anything else to; and the only other network call is the version check
    against two public indexes, which QIKLY_NO_VERSION_CHECK=1 turns off. It
    also says, before installation rather than after, that if your code may not
    leave your network at all then this tool is not usable as it stands.