v1.29.0 ·
The phase nothing ever measured
The problem
The framework had never once measured the first phase of any piece of work it ran.
That is not what the report said. The report came from running the lifecycle end to end on somebody else's codebase for the first time — five findings, written up carefully, each with a suggested fix. It said that reopening a phase erased its timing data. The observation behind that was real: two of four phases were reporting zero elapsed seconds. The mechanism named for it was not.
Reopening a phase preserves its metrics deliberately, and the code says so in a comment. Re-completing it recomputes them. Measured on a scratch instance, a phase that had recorded twenty minutes still read twenty minutes after a reopen, and twenty-two after being finished again. There was no path that could write a zero.
The zero came from somewhere else, and it was worse. A phase gets its entry timestamp from the transition that moves into it. The first phase of an instance is not moved into — it is where the instance starts. So nothing ever stamped it, the completion code skipped the elapsed calculation because the anchor was missing, and the phase closed reading zero. Every instance, every time, since the beginning. The lead-time report then filters for phases that have both an entry and a completion timestamp, which means Requirements has been silently excluded from every lead-time figure this project has published about itself. Fifty-seven instances of it.
The second finding failed the same way. It said a coverage gate reported an acceptance criterion as untested while a test for it sat in the suite, and offered no diagnostic — that diagnosing it cost a detour through compiled output. The diagnostic existed. It was worded almost exactly as the report requested, and it printed in the same block, four lines above the violation it explained. What the report had not noticed is that the run was driving the command with a machine-readable flag, and in that mode the warnings were printed under a condition that excluded them and appeared in none of the four payloads the command can emit. The gate named its own fix to humans and hid it from agents, which is the only audience that flag has.
Two findings, two real symptoms, two wrong causes. Both would have sent work at something that is not broken while leaving the actual defect in place. The remaining three held exactly as written: an amendment could not express that nothing downstream was invalidated, so the only accepted answer manufactured rework in the audit log; nothing warned at a phase boundary that the session had grown past its budget, at the one moment a fresh start is free; and the instructions for creating a piece of work by hand told the agent to write out each phase's required artifacts while the only copy of that list sat inside an installed dependency, with a placeholder where the real names belonged.
A third defect surfaced only because of the timing investigation. One per-phase metric had been initialised to zero when work was created and assigned by nothing, anywhere, ever since. It was zero in every file of every instance, read by no code, and shaped exactly like a measurement.
How it could be solved
The report arrived with its own list of fixes, ranked by whoever had just been bitten. That is a better starting point than a blank page, and reproducing it first was still most of the work — because two of the five entries would have produced changes that fixed nothing.
So the first decision was whether to reproduce at all. The alternative was to trust a careful report from a real run, which is not a foolish option: the person writing it had just spent sixty dollars and a hundred and fifty responses on the ground. Reproducing cost an afternoon in a scratch project driven by the built command line, and it changed two of the seven fixes completely. It stays the default: a field report is the best available source of symptoms and the worst available source of diagnoses, and the two are easy to mistake for each other because they arrive in the same paragraph.
For the first phase never being timed, the obvious option was to stamp every phase with an entry time when work is created. Rejected, because a status display derives "last updated" from the newest entry timestamp across phases, and stamping all of them would make every phase look entered at once and every instance look freshly active. Only the initial phase is stamped, which is the one phase no transition will ever reach.
That leaves the instances already on disk, whose first phase has no timestamp and never will. The option of migrating them was rejected: writing timestamps into recorded history to make it look measured is the same dishonesty as the metric described below, and history that has been edited to look complete is worse than history that admits a gap. Instead the completion path falls back to the instance's creation time, which is a real fact already recorded, and only future completions benefit.
The metric that had never been written presented a genuine fork: compute it, or delete it. The case for computing was that the field had a name and a type and somebody had once meant it. The case for deleting was that nothing read it, nothing had ever written it, and a value that is always zero while shaped like a measurement is worse than a value that is absent — a reader can tell an absent number from a zero, but not a zero from a real zero. It was deleted. The related choice, in the helper that computes elapsed time, was to return nothing rather than zero when there is no anchor, so callers keep their existing guard and an unmeasurable phase stays absent instead of quietly becoming another false zero.
The context budget was the one place a gate was genuinely tempting. The measurement is cheap, the overrun is large — this very release ran at a hundred and seventy percent of the budget and said so in its own output — and refusing to advance until somebody started a fresh session would work. It was rejected on this project's own record: five of its six traceable bug fixes came from features that watch the lifecycle rather than serve a user, each one adding a gate that every future piece of work must then pass. It is a warning, and the reading is wrapped so that an unreadable transcript or a malformed configuration cannot refuse a transition that would otherwise pass.
That decision then collided with a guard from the release that introduced the budget, which asserted the reading appeared nowhere in the transition source — a text search standing in for "never a gate". Two options: weaken the guard, or route the reading around it. Neither. The guard was replaced with the property it had been approximating, for that one file: the reading reaches no violation, no exit code, and no verdict. That is a stronger claim than absence, and it was recorded as a formal amendment rather than a quiet edit — using the amendment feature this same release adds, on its own requirements, to declare that nothing downstream needed reopening.
The last finding suggested two fixes and only one was built. Shipping the resolved templates into each project's own directory at setup time was rejected: it creates a file that drifts from the package it was copied from, and drift then needs a health check to detect, which is another gate. A command that reads the package and prints the answer cannot go stale.
How AIDLC solves it
Seven fixes, from five findings that came back from running the lifecycle end to end on a project that is not this one. Two of the five described a mechanism that does not exist, so what ships is the verified set, not the reported set.
- Amendments can now say that nothing downstream was invalidated. Writing
nonewhere a phase list goes is accepted, records the amendment, and reopens nothing. Before this, the only accepted answer was to name a completed phase that needed nothing, which reopened that phase and every phase after it and wrote rework into the transitions log that never happened. - The first phase of every instance is now timed. It was never entered by a transition, so it never received an entry timestamp, so it always completed with an elapsed time of zero — and the lead-time report filters on that timestamp being present. Requirements and Ideation have therefore been absent from every lead-time figure this project has ever produced. Existing instances are rescued by falling back to the instance's creation time.
- Elapsed time for a reopened phase is measured from the reopen, not from the original entry, so the period the phase sat finished is no longer billed as work. One helper now owns that choice for both places that compute it.
- The per-phase artifact count is deleted. It was written as zero when an instance was created and assigned nowhere afterwards, so it has been zero in every phase file of every instance since it shipped, while reading as a measurement. Nothing consumed it.
- Gate warnings now reach machine consumers. The transition command printed them for
humans and put them in none of its four JSON payloads, so an agent driving it with
--jsonsaw a criterion reported as uncovered and never saw the adjacent line naming the fix. Warnings and the context reading now come from one source and appear in every payload. - The transition command reports how much context the session is carrying against the budget, and recommends starting the next phase fresh when it is over. It is a warning and never a gate: an unreadable measurement, a malformed config, or a figure three times the budget cannot refuse a transition that would otherwise pass. The measurement already existed and had exactly one caller, which had to be remembered.
- A new
templatescommand prints each template's phases and the artifacts each phase requires. That data lived only inside the installed content package, while the instance-creation instructions told agents to write phase files from it and showed a placeholder where the real names should be. Guessing was the only behaviour that instruction could produce, and on the first real run it produced three artifacts the chosen template does not require. The instruction now names the command.
The instance-creation step in the overview skill changed with it, so every project gets the corrected instruction on its next update.