v1.16.0 ·
The memory that outlived the code
The problem
This framework keeps a written memory of the codebase it works on. As work completes, it records what the code is made of — which parts exist, what they are responsible for, which decisions were taken and why — so that the next piece of work does not have to rediscover the whole system from scratch. The phases are told to read that memory before they go looking at the code, because reading a summary is cheaper than re-deriving one.
That memory has no way of knowing when it has gone out of date.
The report came from someone else's project, not this one: a desktop application built with this framework, forty-eight pieces of work deep. On the fifty-first, the memory stated that an earlier piece of work had produced a particular screen component. It had not. The component named there had never existed under that name; the real one was called something else entirely. Had the design been written against what the memory said, it would have produced code calling into a thing that was not there, and the mistake would not have surfaced until somebody tried to run it.
Nothing had gone wrong mechanically. The entry was recorded accurately at the time, by a piece of work that genuinely was building that area. What happened afterwards is that a later piece of work renamed things, and nothing went back to revise the note. The memory was not corrupted, it was simply old, and old and wrong are indistinguishable from inside it.
That would be a smaller problem if the phases treated it as a starting point. They did not, and the wording is the reason. One phase was told to consult the memory "before re-deriving from code". Another was told it is "the first source of understanding" and to go back to the code only for what the memory does not cover. Read plainly, both sentences say the same thing: the summary is the cheap path and opening the file is the fallback. A careful reader following those instructions does exactly what went wrong.
The same report carried a second complaint that turned out to have the same shape. Several pieces of work had been framed as building something new and turned out, on contact with the code, to be generalising something half-built already. One looked like adding reminders; the scheduling, the badge on the dock icon and the settings it needed were all sitting there. Another looked like deleting a feature's folder and turned out to be about what else reached into that folder, because it also held the notifications the whole application used.
Both of those cost a re-framing after the design was written, which is the expensive moment to discover it — by then the work has been broken into tasks and the tasks describe the wrong thing.
Looking at where the design phase actually asks about existing code, the answer was nowhere. It read the requirements, then went straight to describing components and their responsibilities. Between "here is what is wanted" and "here is how it will be built" there was no step that opened a single file, and nothing at the end that checked whether the design had accounted for what was already there. A phase that never asks the question will get the answer wrong roughly as often as the codebase is not empty — which, after the first piece of work on any project, is always.
How it could be solved
Three ways to stop a stale memory from being believed, and the choice turned on how much new machinery each one demanded.
The first was to detect the staleness. Walk the recorded memory, take every name it mentions, look for that name in the code, and report the ones that have gone missing. This is appealing because it is automatic and because the information needed is genuinely available. It was rejected on cost, and the cost is not the writing of it. A check like that has to run somewhere — when work moves between phases, or when somebody asks for a status summary — and from then on every future piece of work passes through it. This project has measured what that does. In its first nine days it shipped eleven features and no fixes; in the next five it shipped one feature and seven fixes, and five of those seven traced back to features whose job was to watch the lifecycle rather than serve anybody using it. Each one added a step that everything afterwards had to satisfy, so its capacity to break was multiplied by all the work still to come. A checker for stale notes is exactly that shape.
The second was to make the memory advertise its own uncertainty — record when each note was last confirmed, and show its age wherever it is read, so an old note looks old. This is cheap and it is not wrong. It was rejected because it does not change what anybody does. A note carrying a date still reads as a fact, and the failure being fixed here is not that somebody thought a note was fresh. It is that the instructions told them the note was the authority and the code was the fallback.
That leaves the third: change what the instructions say. The memory stays exactly as it is, and the sentence describing how to use it stops describing it as an authority. It is an index — the cheapest way to find out where in the code to look, and no more than that. Anything it names by name gets opened before it gets designed against. This costs nothing to run, adds no step for future work to pass, and it is aimed at the actual mechanism, which was always a sentence rather than a missing tool.
Two commands for confirming a note and correcting one already existed, unmentioned by any phase. The new wording points at them, which is why this change adds no tooling: the tools were there and nothing sent anybody to them.
Then three smaller decisions, each of which could reasonably have gone the other way.
The report named one phase. The wording was in three, and the worst of them was not the one reported — the phase furthest upstream was the one that called the memory "the first source of understanding". Fixing only what was reported would have left the strongest version of the problem standing one step before the fix, which is the kind of outcome that gets rediscovered later as a new problem. So the rule applied was to find every place the instruction appears rather than the place the complaint pointed at. There is already a lesson written down here about that, earned by a report of one bad prompt that turned out to be twelve.
The new step that reads existing code before proposing a shape needed a limit, because "go and read the existing code" is how a fast phase becomes a slow one. The obvious limit is a number — read at most five files. It was rejected in favour of words: the parts the requirements actually touch, a handful of files, not a survey. A number is wrong in both directions at once. On a large seam it forbids the reading that would have helped; on a small one it invites four files of padding to reach the quota.
And there was a real question about whether this belongs one phase earlier, in the writing of the requirements, since a requirement phrased as "build a notification system" has already committed to the fresh-build framing before any design begins. That is true, and it was still left alone. The requirements describe what somebody wants; the design is where a shape gets proposed, so the design is where knowing what already exists changes the output. Asking for the same reading twice would cost twice and improve nothing.
The last decision was to write down the finding either way. If nothing in the codebase does any part of what is being asked for, the design says so, in a sentence. That sounds like paperwork and it is the part most likely to earn its keep, because it is the difference between a genuinely new component and one designed alongside the code that should have absorbed it. Those two look identical on the page. Only the sentence separates them.
How AIDLC solves it
The graph is now an index, in all three places it is consulted
The roadmap item named 30-design.md. Grepping the mechanism
(knowledge context --scope phase) found the same framing in two more phase
skills, and the ideation copy was the worst of the three:
| Skill | Was | Now |
|---|---|---|
10-ideation.md:33 | "the graph is the first source of understanding; re-explore only what it doesn't cover" | cheapest way to find where to look, but "an index, not an authority" — open the source before treating a named symbol as real |
30-design.md:29 | consult the graph "before re-deriving them from code" | "Consult the knowledge graph, then check it against the source" — confirm a named symbol exists before designing against it |
40-implementation.md:29 | query it "before re-exploring code it already describes" | same, plus "as an index into the source, never a substitute for reading it" |
Fixing only the reported site would have left the strongest version of the defect
in place, one phase upstream of it. That is CLAUDE.md's "a bug report naming one
call site is a sample, not the population" applied as written.
Design points at aidlc knowledge verify <id> and aidlc knowledge update <id>
for the confirm-and-correct path. Both already exist — the fix adds no machinery,
which was the condition for doing this at all.
Design gained a step: find the seams before proposing structure
New step 3, between "review requirements" and "define architecture", where there
was previously nothing that asked what already exists. It requires the existing
files to be named in design.md, and it is bounded in prose rather than by a
count — "the seams the requirements actually touch, a handful of files, not a
survey". A hard number would be ignored on a big seam and over-read on a small one.
"Nothing exists" is a finding to state, not a silent default. That sentence is what separates a genuinely new component from one designed alongside the code that should have absorbed it, which is the failure the reporting project hit twice (their 050 and 052).
Step 4 now asks each component to say whether it extends a seam from step 3 or is new, so the reading in step 3 has to reach the architecture rather than stopping at being done.
Two exit criteria, so the absence is visible
design.mdnames the existing code the change extends, or states that no such code exists- Every knowledge-graph symbol the design builds on was checked against the source
Steps renumbered 9 → 10
Inserting step 3 pushed the task-breakdown step to 10. tasks-content.test.ts
asserts on that step's text, not its number, so it was unaffected — but the
numbering is now pinned by a test (brownfield-content.test.ts) so a future
insertion cannot silently duplicate or skip an index.