Two thousand artifacts, three taxonomies: crawling a customer engagement
Problem
A repo crawler assumes the corpus is on disk, that it is yours, and that it holds still. A customer engagement folder breaks all three.
It sits on a shared drive colleagues write to, so it moves while you read it. Most of it
isn’t on disk at all — Google-native files are dataless placeholders, and cat on one
returns Error reading …, not JSON. And the folder is not the whole record: the same
account’s context files, cases and tickets also live in the enterprise search index, and
the two halves disagree in ways nobody has reconciled.
That last part is the actual problem. Both halves look authoritative. Neither says which one is stale.
Inputs
Three live engagement folders from one technical-account-management practice, anonymized here as A, B and C. Deliberately different shapes:
| Account A | Account B | Account C | |
|---|---|---|---|
| Artifacts enumerated | 945 | 326 | 740 |
| Google-native stubs | 119 | 202 | 106 |
| Taxonomy | NN Name (numbered, spaces) |
freeform | NN_Name (numbered, underscores) |
Has a _meta/ self-map |
yes | no | no |
Three folders, three filing conventions. A has a maintained _meta/ directory describing
its own taxonomy; B has neither convention nor map, so roles had to be inferred from
directory and file names; C uses a third numbered convention with underscores, plus three
build/state directories that dominate its file count.
Commands
# one run per account; the pack is written into that account's own folder
/crawl-cust2llms "<account>" --depth standard
# the step that makes the rest possible: Google-native files are dataless,
# but the Drive ID is in an extended attribute — and the "#S" is part of the name
xattr "<file>.gdoc" # -> com.google.drivefs.item-id#S
xattr -p 'com.google.drivefs.item-id#S' "<file>.gdoc"
# verification, shipped with the skill; exits non-zero on any failure
python3 scripts/selfcheck.py "<account>" --expect "<other,accounts,legitimately,present>"
Drop the #S and xattr -p answers No such xattr: com.google.drivefs.item-id on every
file in the corpus. It is not an error you notice — it is 119 stubs resolving to zero and
a crawl that looks like it merely found nothing to read.
Outputs
Eleven files per account. Four keep .txt because they are llms.txt-standard family names
that the lint and the sibling crawlers key on; the five sidecars this skill adds are
ordinary markdown.
| A | B | C | |
|---|---|---|---|
| Stubs resolved | 116 / 119 | 201 / 202 | 99 / 106 |
| Deep-read | 47 | 18 | 16 |
| Index documents examined | 23 | 14 | 12 |
| Atomic tagged facts | 61 | 45 | 51 |
| Claims tagged stale | 9 | 3 | 2 |
| Conflicts recorded | 6 | 5 | 4 |
| Redactions | 1 | 0 | 0 |
416 of 427 stubs resolved. The 11 that didn’t are broken shortcuts and cloud-only files
with no extended attribute; each is carded [unresolved] and its content is inferred
nowhere. That distinction matters more than the success rate — a crawler that guesses a
document’s contents from its filename produces a pack that reads as complete and isn’t.
The importance tiers are the honest tell on taxonomy detection:
| Tier | A | B | C |
|---|---|---|---|
| critical | 23 | 16 | 9 |
| high | 48 | 54 | 67 |
| normal | 152 | 220 | 160 |
| peripheral | 722 | 36 | 504 |
B has 220 of 326 files in normal — two thirds of the corpus at the default tier — because
without a numbered folder convention, a filename is a much weaker role signal than a
directory is. The heuristic path works; it just discriminates less, and the numbers say so
rather than hiding it.
What the runs found
The newest context file was outside the folder, in two of three accounts. One account had more than twenty-five generations of “context file” scattered across the drive, a code host, and generated artifacts, spanning five months, with none marked canonical — and the newest one wasn’t filed in the engagement folder at all. Same for the second account. The third had its canonical file correctly filed, which is what makes this a rule rather than a restatement of one finding.
This drove a skill change. The first version gathered the candidates and recorded the ambiguity, which hands the reader back the exact problem the pack was meant to solve. It now orders by modified time and names one canonical, always — including when that document is absent from the folder, because that absence is the finding: the folder is not a complete record of the account. The override is scoped to the context-file family, where a document is a generated snapshot rather than a signed deliverable. It never touches live case status, where the system of record still wins.
Two accounts each carried an artifact contaminated with the other’s data. A one-character difference between two account identifiers had, at some earlier point, produced an export populated with the wrong account’s rows — in both directions. Each folder now holds a file named after the other account’s contamination. Neither pack would have surfaced it without merging the two halves; each folder on its own just looks like it has an oddly-named spreadsheet.
A headline risk was void. One account’s register led with an aged high-severity case carrying a multi-hundred-day theme age. A verification pass against the system of record found it closed months earlier. The register was internally consistent and wrong, which is the failure mode staleness tagging exists for.
A “verified, not exhaustive” case list. For one account the index holds no account identifier on case records at all, so an account-scoped query silently truncates weeks early. Any open-case list built that way is incomplete with no signal that it is. The pack states this at the top of its open-items file rather than presenting a clean list.
Lessons
The checker was wrong in the unsafe direction. The self-check greps the emitted pack for every other account’s name, to catch confidentiality bleed. The first version filtered candidate names by length to avoid noise — which silently skipped any account whose folder name is two characters. One such name appears seven times in another account’s pack. The check that existed specifically to catch cross-account bleed could not see it. Fixed by matching on word boundaries and declaring legitimate cross-account names explicitly, so an undeclared hit is a real failure rather than noise to be filtered away.
A checker that can disagree with the artifact it checks is worse than none. The first version hardcoded a count that the pack later corrected. The pack was right, the script went on reporting a mismatch that no longer existed, and the mismatch was ignored because it was known. It now reads its constants out of the pack — the stale count from the header, the census from the manifest — so drift between checker and artifact is structurally impossible. It also ships with the skill instead of being reimplemented per run, which is how the hardcoded constant got in.
Folder-name rules over-classify. Mapping a directory called “Context and Customer
Files” to the context role tagged 36 files as context documents when nine were; the rest
were evaluation datasets, JSON exports and a bundled reference. Classifying by filename and
content instead of by parent directory brought it to the real number. An inflated count in
an inventory is worse than a missing one — it reads as coverage.
A sidecar mirror is not an export. One folder carries 167 machine-generated
.processed.md files that look like local copies of the Google-native documents. They are
roughly 1 KB digests. Reading one instead of resolving the stub silently swaps the document
for a summary of it, and nothing about the filename says so.
Four skill versions came out of three runs — the extended-attribute name, the canonical-newest rule, the file-extension split, and shipping the checker. None of them were visible from reading the specification. All four came from the corpus refusing to behave.
Reproduce
# 1. resolve one stub by hand first — if this prints nothing, nothing else will work
xattr "<engagement-folder>/<some-file>.gdoc"
xattr -p 'com.google.drivefs.item-id#S' "<engagement-folder>/<some-file>.gdoc"
# 2. run the skill against one account
/crawl-cust2llms "<account>" --depth standard
# 3. verify; exits 0 only when all ten checks pass
python3 ~/.claude/skills/crawl-customer-to-llms/scripts/selfcheck.py "<account>" \
--expect "<names legitimately present via a declared multi-account source>"
# 4. re-run later; only re-cards what changed
/crawl-cust2llms "<account>" --refresh
The pack lands in the account’s own folder, never a shared store — one run, one customer,
with the boundary grep enforcing it. Read llms-small.txt before a touchpoint,
llms-open.md for live state, and llms-full.txt § Conflicts before quoting any figure
back to anyone.