v1.13.1 ·
The command that answered from the wrong project
The problem
Run a command from the wrong directory and you expect an error. That is not what happened here.
Every command in this tool asked the operating system "where am I?" and treated the answer as the project root. Standing in the project's top directory, that is correct. Standing anywhere else — a package subdirectory, a nested folder, wherever a background process happened to inherit — it was wrong, and nothing said so.
The write side created a second project inside the first. Recording usage from a subdirectory printed
recorded estimated entry, exited zero, and wrote the numbers into a freshly-created project tree
that no report reads. The capture log for that write says ok beside appended: 0. There is no
error to find because, from the writer's point of view, nothing failed.
The read side was worse, because a wrong answer arrived dressed as a measurement. From one directory
below the root, the status table listed all twenty-five pieces of work as unreachable at 0% with no
cost, and the metrics command reported 0 instances, 0 with cost data. Neither of those is a failure
message. They are zeroes, in the same layout as real zeroes, from the command this project's own
instructions single out as the one to trust when deciding what a feature really cost.
Two of those stray trees had been sitting in this repository for eight days, each holding usage nothing could read. They were found by accident, while reproducing something else.
The bug report named one line in one file. That line was real. It was also a sample: twenty places had independently reproduced the same mistake, across eleven separate pieces of work, because there was no shared answer to "which project is this?" for any of them to call. Searching the whole codebase for such a helper returned nothing. Every author had reached for the same convenient wrong thing, and each one had been right that it worked — from the root.
How it could be solved
Walk up from where you are until you find the marker directory, and fall back to where you started if there isn't one. That is the whole mechanism, and it was never the interesting part.
The first real decision was which project to find, because this codebase already had an answer to a question that sounds identical. The roadmap resolves the primary checkout, deliberately: a captured item written inside a feature branch's separate working directory is stranded on that branch until it merges, which for an inbox is indistinguishable from losing it. Reusing that rule here would have been the tidy move, and it would have been wrong in the opposite direction. Progress records and cost belong to the working directory that produced them. So the new rule takes the nearest enclosing project, the old rule keeps taking the primary one, and a test now asserts that nobody unifies them. Two rules that look like duplication and are not.
The second decision was where to stop. A uniform fix applied to all twenty places would have been wrong for two of them. The setup command creates a project — have it search upward and running it inside an existing project silently reconfigures the parent instead of setting up the directory you asked about. The scanner has the same shape: it describes the code it is pointed at, and resolving upward would overwrite a parent's documentation with a description of the wrong codebase. Both keep the old behaviour, both carry a comment in the file saying why, and a test asserts each exemption is still being used — because an exemption nobody needs quietly becomes precedent for the next one.
That question cut the other way too. One command looked exactly like an exemption and was not: the one that begins a new piece of work. It reads an existing project's configuration and writes into that project, so from a subdirectory it had been filing work in the wrong place. It wasn't in the bug report. It was found by asking, for each site, whether this command creates a project or consumes one.
The third decision is the one that mattered most, and it is a test rather than a fix. Twenty repaired call sites leave the twenty-first free to reintroduce the bug, and it would arrive as quietly as the first twenty did. So alongside the behavioural tests there is one that reads the source code and fails when a new unrouted lookup appears anywhere, with the exemption list and its reasons stated in the test itself. The defect was never a wrong line. It was an absent mechanism, and what an absent mechanism needs is not twenty corrections but something that notices the twenty-first.
Deliberately not built: a repair check for the stray trees. It was offered and declined. The mechanism that created them is gone, and adding a permanent gate to catch a defect that can no longer occur is a tax on every future piece of work — this project has already learned that lesson the expensive way, and wrote it down.
How AIDLC solves it
Every aidlc command used to treat whatever directory you were standing in as the project
root. From a subdirectory that was silently wrong, and silence is the whole story: running
aidlc cost record one level down printed "recorded estimated entry" and put the usage in a
brand-new project tree created at that subdirectory, which no report reads. The capture log
recorded error_code: "ok" next to appended: 0. From the writer's point of view nothing
failed.
The reads were worse, because they did not even error. From packages/cli/ — an ordinary
place to stand in this repository — aidlc status showed every one of 25 instances as
(remote worktree) at 0% with no cost, and aidlc metrics reported 0 instances, 0 with cost data. Those are not error messages. They are zeroes rendered as if measured, from the
command this project's own instructions tell you to trust for true cost.
The fix is one small module that walks up from the current directory to the nearest
ancestor holding a .aidlc/ directory, and falls back to the starting directory when there
is none, so behaviour outside a project is unchanged. Fifteen command modules and the
lifecycle event bus now go through it. It requires the marker to be a directory rather than
merely present, because a stray file of that name would otherwise resolve a root whose
every subsequent path join fails somewhere far from the cause.
Two commands deliberately keep reading the current directory, and each says so in the file.
init creates a project: if it searched upward, running it inside an existing project would
silently reconfigure the ancestor instead of initializing where you asked. discover scans
the tree it is pointed at, and resolving upward would describe the wrong code. One further
command was not in the original bug report at all — start — and it looked like an
exemption without being one. It consumes an existing project, so from a subdirectory it was
creating instances in the wrong tree.
The report named one call site. Twenty had the same defect, and the reason is that there
was no project-root discovery mechanism to call: a repository-wide search found no
findUp-style helper anywhere. So the real deliverable is not the twenty edits but the
tripwire beside them — a test that scans source text and fails when a new unrouted read
appears, because the twenty-first would fail exactly as quietly as the first twenty.
There is a second rule that must not be unified with the first, and a test pins it. The roadmap resolves the primary checkout on purpose: an inbox written inside a feature worktree is stranded on that branch until it merges. Instance state and cost belong to the worktree that produced them, so the new resolution takes the nearest enclosing project. Both rules are correct; neither is a fallback for the other.
Also removed the last stray .aidlc/ tree left on disk in this repository, with the
orphaned entry it held transcribed verbatim into the instance's code-complete record
before deletion.