---
title: "621 Reference Files Sat Next to a Folder Labeled NOT for AI"
canonical: https://dxdev.com/blog/two-doc-trees-one-repo/
datePublished: 2026-07-23
---
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 that it had chosen the wrong source.

We ran a 13 agent review of the top level to find out what the directory names were 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 come back clean. They came back **safe with fixes**, which is the result we wanted before moving anything.

## One sign over three different things

`dev-notes/` 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 relevant to agents and 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.

So the `NOT for AI` label was 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, and an agent does not reliably treat `internal` as a boundary once it has 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:

```bash
git mv dev-notes/6_Migration docs/db/migrations
git mv dev-notes human-notes
```

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

## Three answers to where a script goes

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/`, and that would have been wrong. `ai/scripts/` is agent command glue and 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:

```bash
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, and an agent gets fewer plausible but wrong paths to search.

## Seven content directories

The repository had grown to 10 content directories around an 8,187 file application and a 66 file agent layer. We 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.

One choice stayed open: 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 we left that decision visible instead of pretending there was no tradeoff.

The goal 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 needed 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 converge through the feature branch and the normal sync path. Empty untracked directories do not. A clone that updates code but keeps its old local husks becomes its own ambiguous repository.

So this is a single locked feature branch rather than 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:

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

Then the gate must be green:

```bash
python tools/docs/check-doc-refs.py
```

## Paths are routing instructions

A directory name could silently make the wrong source look authoritative. Agents do not tell you which branch of a messy repository they skipped. They produce an answer that looks finished.

Names, paths, allowlists, and cross links are all routing instructions for an agent, and when they disagree the failure hides inside a confident result. Ask of every folder whether an agent is ever pointed at its contents, and name the folder for that audience. Then treat the rename as a migration: check every caller, run a reference sweep, and make the sweep a gate before merge.
