The audit counted 621 reference files in docs/ and 57 files in dev-notes/, whose top level label said NOT for AI.

That was the moment the repository stopped looking merely untidy. Our agents were being given two nearby documentation trees with contradictory instructions. docs/ was deliberately organized, portal indexed, and full of architecture, database, goals, strategy, and setup material. dev-notes/ was a catchall with a warning label. An agent would not announce that it had followed the obvious tree and skipped the other one. It would read a plausible answer from docs/, complete the task, and leave us no trace of the fact that it chose the wrong source.

I ran a 13 agent review of the top level to find out what the directory names were actually telling people and agents to do. The review used one investigator per directory, three competing structure proposals, a judge, and two adversarial verifiers. The verifiers grepped both the repository and the shared command workspace for every path the migration would touch. They did not return clean. They returned safe with fixes, which is exactly the result I wanted before moving anything.

The tree that said not to read it

dev-notes/ was not one thing. It was three incompatible things wearing one name.

First, 6_Migration/ held the per ticket DDL archive. It was already cross linked from docs/db/README.md, so it was not only relevant to agents. It was already part of their database trail. Second, 0_Release/ held human GUI click runbooks that were still being edited on July 13. Third, 2_Design/, 3_Setup/, and 9_Dev-Tools/ were stale stubs, except that one verifier found real design and implementation material hiding among them.

The NOT for AI label was therefore worse than a bad name. It was a false routing rule. The migration history belonged in the reference tree because agents were pointed at it. The GUI runbooks needed a human only home. The stubs needed to be salvaged or removed. Leaving all three behind one sign forced every reader to infer exceptions from folder archaeology.

We considered folding everything into docs/internal/. It lost because the directory name would still describe content rather than audience. An agent does not reliably treat internal as a boundary, especially after it has been given a repository wide task. The better test was mechanical: is an agent ever pointed at this artifact? If yes, it belongs in docs/. If no, it belongs in human-notes/.

That meant moving the DDL archive before renaming the old tree:

Terminal window
git mv dev-notes/6_Migration docs/db/migrations
git mv dev-notes human-notes

The order matters. The first move preserves the agent facing path as an explicit database decision. The second makes the remaining tree tell the truth about its audience.

Three places to put a script is not a convention

The same audit found three answers to the question, “where does a script go?”

There was an unmanaged scripts/ directory with three files and no README. There was tools/, already documented as the home for standalone development and maintenance tooling. There was also ai/scripts/, which looked duplicative until we checked its callers. Shared agent commands depended on ai/shared/*, and the Git hooks path pointed at ai/scripts/git-hooks.

A shallow cleanup would have folded every script into tools/. That would have been wrong. ai/scripts/ is agent command glue, not general development tooling. It is a frozen interface. tools/ is the one home for scripts that operate on the repository or development environment. The orphaned scripts/ directory duplicated that charter and had no reason to survive.

The final moves were deliberately boring:

Terminal window
git mv scripts/fetch_issuetype_icons.py tools/jira/
git mv scripts/iis-tracing-custom-fields.ps1 tools/local-dev/
git mv scripts/docs/check-doc-refs.py tools/docs/
git rm -r scripts

Boring is useful here. A newcomer can now predict where a database schema helper, a local development script, or a documentation check belongs. An agent gets fewer plausible but wrong paths to search.

Seven content directories, not ten guesses

The repository had grown to 10 content directories around an 8,187 file application and a 66 file agent layer. We explicitly left the application internals alone. We also left the agent layer and the root environment anchor alone because the shared commands depended on both.

The target has seven self evident content directories: app/, ai/, docs/, human-notes/, tools/, infra/, and archive/. The empty library/ husk goes away because it collides with a live directory inside the application. temp/ goes away because a gitignored .tmp/ already replaced it. zArchive/ becomes archive/, which says what it is without a capitalization or sort trick.

The remaining ambiguous choice was the service configuration package. A sibling repository uses winsw/, which offered cross repository consistency. We preferred infra/otel-collector/ for newcomer guessability and room for future non product services, but left that decision visible rather than pretending there was no tradeoff.

The result is not a preference for fewer folders at any price. It is one canonical home per axis. The exception is intentional: ai/scripts/ stays because its axis is agent execution, while tools/ owns development tooling.

A rename is still a migration

The review got useful when it stopped discussing folder names and started checking couplings.

A live product JavaScript file had a case 'dev-notes': branch for the documentation portal. The reference checker did not scan .sql, so migration files could retain stale internal paths after the Markdown pass went green. The portal appeared to list only .md and .txt files, but moving raw DDL under docs/db/migrations/ still required verification before merge. The repository side rename also did not prove that a live Windows service would find its next install or update from the new configuration path.

The largest operational risk was ten working clones. Git tracked renames can converge through the feature branch and the normal sync path. Empty untracked directories do not. A clone that updates code but retains its old local husks becomes its own ambiguous repository.

So this is a single locked feature branch, not a background cleanup. The completion gate is both the moved checker and a repository wide reference sweep. Its allowlist becomes the machine readable version of the top level contract:

PATH_RE = r"^(app|ai|docs|human-notes|tools|infra|archive)/"

Then the gate must be green:

Terminal window
python tools/docs/check-doc-refs.py

The real problem was never that we had 10 directories. It was that a directory name could silently make the wrong source look authoritative. Agents do not tell you which branch of a messy repository they did not read. They just produce an answer that looks finished.

A repository is part of an agent’s interface. Names, paths, allowlists, and cross links are routing instructions. If those instructions disagree, the failure hides in a result that sounds confident. The fix is not deleting detail. It is giving every useful artifact one truthful, predictable home.