Repository navigation
Releases: gal-a/qikly
Release list
v0.6.0: Three steps on one path: --score, --scaffold and --tasks, answered by number
Changed
- Three steps, each taking the same module or folder:
qikly --score my_module.pyscores your tests (--score-codestill works),qikly --scaffold my_module.pywrites the task file with the score's questions in
it to answer, andqikly --tasks my_module.pyruns it.--validate,
--explainand--take-teststake the same path, so nobody has to learn
thatmy_module.py's task is calledMY_MODULE_VERIFY; task ids still
work as before. --scaffoldleaves 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.requirementsstarts as the module's docstring or a line to
keep what the code does, markedTODO: MODIFY THIS:for you to replace in
your own words, which--validatereminds 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 forrequirementsandacceptance_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 asfrom .util import clampworks 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.0for an option that asks for a value,keepfor the option
that is what the code does now, or a sentence, with or without quotes.
Both agents and--validateread the answer as the full criterion, the
file keeps what you typed, and--validateprints 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 withTODO, 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.--validatecounts with the same rule, says
how many are answered and that a run skips the rest, and--validateand
--tasksboth stop a file with none answered before a paid run. --scaffoldfills 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.--scaffoldtakes a folder: one task file per module that the
folder's last--scoreasked about. With no score yet, it says to score
the folder first and names a module to scaffold instead. A package's
__init__.pyis 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-idtasks 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. --scoreplants 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.--scorefinds your tests when you do not name them: thetest_*.py
and*_test.pyfiles that import the code, searched from the project
root, and it says which it used.--score-testsstill names them, and is
still needed with nothing to break.--scoreand--score-suitelead 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 thatqikly --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
intoqikly_generated/<task>/<stage>/beside your tests, keeping their
names, rewrites every import of qikly's copy to yours, writes a
conftest.pythat 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, andoutputs/conftest.pykeeps a plain
pytestfrom running qikly's own copies as well.--scoreasks a question about every fault your tests missed, and
--scaffoldputs 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 israte > 100orrate >= 100...
what should happen at exactly100?". 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-teststold 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
ifortryis 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, awith, a tuple unpacking or a later assignment is left
out, and so isindirectparametrize. Rows in a table that also holds
objects, such asdate(...), are read value by value, and a function
imported through its package (from isodate import parse_date) is found. --scoreon a folder with its tests inside says to score one
module, instead of refusing a--score-testsyou never typed; tests on
another drive no longer crash it, and the path it prints can be pasted.import os.pathno longer counts as importing a project'spath.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...
v0.5.9: Fix: --score-code results from earlier versions could be wrong; re-measure them
Fixed
--score-codeand--score-suitecould 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 asrc/layout and an editable
install, a plainpip 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 TASKgroups a task's run summaries by the prompts
that produced them and compares the arms, without a model call.--repeats Nruns 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), andQIKLY_PROMPT_OVERRIDES=0
makes one process use the shipped prompts while overrides stay on disk.--install-mcp cursorwrites.cursor/mcp.json, and a bare
--install-mcpnow writes it alongside the Claude Code and VS Code files.
The file is checked; Cursor loading it has not been, whichdocs/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 toTASK_FILE_REFERENCE.md. - A failing test was described as returning only its error text, on the
landing page and indocs/FINDINGS.md. The coding agent also sees the
failing test's source, and both now say so. docs/FINDINGS.mdis 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
Added
--validatewarns when a_VERIFYtask has nothing seeded. The suffix
means "test the code I already have", andseed.implementationis 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 newmcp-registryjob authenticates with a
GitHub OIDC token instead, checksserver.jsonagainst 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_ownerfrom the token and grantsio.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_checkfails 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 intogit 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--validatewith a traceback.seed:written
as a string or a list, rather than a mapping, raisedAttributeErrorout of
the new_VERIFYcheck, 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
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, andQIKLY_NO_INVITE=1turns 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 readSKILL.mdfrom 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, andrelease_checkprints 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
Fixed
- An old
patchon 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 apatchis GNU, which this one is: it takes--fuzzand
--dry-runwithout 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 underusr\bineven when
another one comes first. Apatchwhose 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 olderpatchrefuses these flags outright and
says so, and on macOS withoutgpatchthat is Apple's own. The one refusal
is apatchbetween 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
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.mdbeside it, which is the file Copilot actually opens.
Copying the folder alone would have put files on disk that nothing ever
reads.--install-skill autopicks it up when a project already has
.github/instructions/or acopilot-instructions.md, and not merely
because it has a.github/, which almost every repository does.seed.implementationpointing 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, sofrom 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.mdhas 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 ofsys.path, ahead of anything
PYTHONPATHadds, 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 aseed.testsconftest.pyof 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 copilotdestroyed 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.mdwas 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 aftercopytreehad 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-suitescored 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.implementationresolving 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 ajson/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 setsLLM_MODEL=claude-haiku-4-5
whenANTHROPIC_API_KEYis 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,--exampleand--demoinside one still
recognises folders made by earlier versions. - What
--install-skillprints, 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
Added
qikly --install-skillnow 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 PATHscores 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 --jsonis 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,--exampleand--demorefuse to run insidedemo/<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.pyreported 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 qiklyreplaces 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. --initand--examplenow say so before filling your home directory.
A first-time user ran--examplein his home folder and finished with
inputs_private/,outputs/, a module and ademo/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 qiklyreplaces 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-skillsaid
"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 weekon 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 --jsonon its own started a real, billed run, the third flag with
this defect and the one three separate audits walked past.--htmlhas had
this check since it shipped;--jsonsits 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 5on its own started a real, billed run. Neither
--score-mutantsnor--score-seedtriggers 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-dirwas 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-itproducestry-it/<timestamp>/
with nodemocomponent 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 geminiimplied 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,
--exampleand--demofor 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 allprinted "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
Added
-
qikly --validatenow 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-suiteplants 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,cursoror
geminiwrite 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=1turns it off.
Fixed
--score-suitecrashed on its first line, reading anargs.seedthat 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
--validatefor every task in the
run.csv.readerraises outside the read that was guarded, and
--validateis 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
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. Twoacceptance_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_loaddecides 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 qiklyon 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 namespip 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.mdnow names the Jira fetch and theUser-Agentthe 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
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.composereturns the node
tree with source offsets, the span YAML itself assigned to the key is cut out
of the raw text, and the strip andyaml.safe_loadnow 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 Ywrote a task file that did not parse,
then died inside its ownyaml.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. -
--examplehanded over two tasks, its own and--init's blank
MY_FIRST_TASKstarter, because it made the layout by callinginit_project.
One of them was documented nowhere, and a later bareqiklywith 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_allshelled out
torun.pyby 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: 2markers 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: trueunderagent:
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 underdiagnostic_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 --exampleused to lay down
task files withTODOin the two sections the agents read, while printing
the paidqikly --tasksline as a next step and--validatewarning 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 --validateandqikly --explainlook at the rest of the handover,
not only atrequirements. 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-5and 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-defaultshas 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_candmean_humidity_pctfrom 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 aspython -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.mdthat a machine can perform.--afterconfirms 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, whichQIKLY_NO_VERSION_CHECK=1turns 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.