From 3d89d3579523ad47f6774316227122fe56eb5ce4 Mon Sep 17 00:00:00 2001 From: micahvv Date: Sun, 30 Aug 2026 07:19:34 -0400 Subject: [PATCH 1/3] Add a reference for restructuring a tree that is already in use --- README.md | 8 +- SKILL.md | 7 +- references/live-tree-restructure.md | 192 ++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 references/live-tree-restructure.md diff --git a/README.md b/README.md index 6b6879c..f199764 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,12 @@ icm-architect/ ├─ references/ │ ├─ core.md five principles, five-layer hierarchy, naming, token discipline │ ├─ forms.md the six forms in depth: skeletons, moves, failure modes -│ ├─ system-map.md audit pipeline for the System map form -│ └─ reference-integrity.md restructure move-safety gate +│ ├─ system-map.md audit pipeline for the System map form +│ ├─ reference-integrity.md restructure move-safety gate +│ └─ live-tree-restructure.md restructuring a tree that is already in use └─ assets/templates/ copyable starters: CLAUDE.md, CONTEXT.md, stage contract, - node card, object/process cards, schema, questionnaire + node card, object/process cards, schema, questionnaire, + effects index, private shelf ``` MIT licensed, like the protocol it serves. diff --git a/SKILL.md b/SKILL.md index 5c72134..e585cd5 100644 --- a/SKILL.md +++ b/SKILL.md @@ -76,11 +76,11 @@ Real workspaces mix forms (a record library whose records are mini knowledge bun - **Product** — run-specific artifacts (→ stage `output/` or record folders) - **Dead** — stale, duplicated, or superseded (→ propose `_archive/`, never silently delete). A file is Dead only after step 4 confirms nothing depends on it — apparent disuse is not proof. -**4. Verify reference integrity — before proposing.** Apparent disuse is not proof of safety. Before any file is proposed for a move — especially a `Dead → _archive/` move — enumerate what points at it: in-vault, sibling-path (`../`), symlink, and outside this workspace (other repos, configs, scheduled jobs that hardcode a path in). External consumers are a question for the human gate, not an unbounded grep. A file with a live referrer is held, or moved only if every referrer is updated in the same change. See [references/reference-integrity.md](references/reference-integrity.md). +**4. Verify reference integrity — before proposing.** Apparent disuse is not proof of safety. Before any file is proposed for a move — especially a `Dead → _archive/` move — enumerate what points at it: in-vault, sibling-path (`../`), symlink, and outside this workspace (other repos, configs, scheduled jobs that hardcode a path in). External consumers are a question for the human gate, not an unbounded grep. A file with a live referrer is held, or moved only if every referrer is updated in the same change. See [references/reference-integrity.md](references/reference-integrity.md). If the tree is LIVE — sessions have directories in it open, daemons and scheduled jobs read paths inside it, other repos hardcode paths into it — read [references/live-tree-restructure.md](references/live-tree-restructure.md) first; the census it describes is what turns "what points at this file" from a grep into an answer. **5. Propose before moving.** Present the target tree and a migration map (old path → new path → role → referrers found). Get approval. This is a human gate in a method built on human gates — honor it. The reviewer approves against the reference report from step 4, not against a hunch. -**6. Migrate — copy, verify, then remove.** Never move-and-hope. Before any copy or rename, check whether the destination already exists **case-folded** — on Windows and macOS, `CLAUDE.md` → `CONTEXT.md` silently overwrites an existing `context.md`, and a file-inventory map will not show the collision. Surface every hit at the approval gate. Then copy to the new home, verify parity (file count and content hash) against the source, and only then remove the original. Write the entry file and contracts, de-duplicate toward one-home-per-fact (leave a link where the copy lived if anything referenced it). Separate method from instance: if the structure will be reused elsewhere, the blank template lives apart from this filled-in deployment. +**6. Migrate — copy, verify, then remove.** Never move-and-hope. Before any copy or rename, check whether the destination already exists **case-folded** — on Windows and macOS, `CLAUDE.md` → `CONTEXT.md` silently overwrites an existing `context.md`, and a file-inventory map will not show the collision. Surface every hit at the approval gate. Then copy to the new home, verify parity (file count and content hash) against the source, and only then remove the original. Write the entry file and contracts, de-duplicate toward one-home-per-fact (leave a link where the copy lived if anything referenced it). Separate method from instance: if the structure will be reused elsewhere, the blank template lives apart from this filled-in deployment. On a live tree, every move goes through one guarded primitive that halts rather than guess, one manifest row per move, and a redirects index generated from the manifests — see [references/live-tree-restructure.md](references/live-tree-restructure.md). **7. Validate with the walk test.** @@ -111,4 +111,5 @@ If a step fails, fix the structure — not by explaining more, but by moving or - [references/forms.md](references/forms.md) — the six forms in depth: skeletons, moves, failure modes. Read at step 2 of Build mode or step 2 of Restructure mode. - [references/system-map.md](references/system-map.md) — audit pipeline for the System map form. Read when that form is chosen. - [references/reference-integrity.md](references/reference-integrity.md) — the move-safety gate: what points at a file, case-folded destinations, copy-verify-remove. Read at step 4 of Restructure mode, or any time a move is contested. -- [assets/templates/](assets/templates/) — copyable starters: `CLAUDE.md`, workspace `CONTEXT.md`, `stage-CONTEXT.md`, `node.md`, `object.md`, `process.md`, `schema.md`, `questionnaire.md`. +- [references/live-tree-restructure.md](references/live-tree-restructure.md) — restructuring a tree that is in use: census, the consumers register, one guarded move primitive, the gates, shared-repo hygiene, and the drift report that catches the decay. Read at step 4 of Restructure mode whenever anything but a human reads a path in the tree. +- [assets/templates/](assets/templates/) — copyable starters: `CLAUDE.md`, workspace `CONTEXT.md`, `stage-CONTEXT.md`, `node.md`, `object.md`, `process.md`, `schema.md`, `questionnaire.md`, `effects-CONTEXT.md` (a System map's change-impact index), `private-shelf-CONTEXT.md` (a sensitivity shelf). diff --git a/references/live-tree-restructure.md b/references/live-tree-restructure.md new file mode 100644 index 0000000..79e7b72 --- /dev/null +++ b/references/live-tree-restructure.md @@ -0,0 +1,192 @@ +# Restructuring a tree that is live + +Restructure mode assumes you can move a file and then check the result. That assumption fails +the moment the tree is in use: sessions have directories in it open, scheduled jobs read paths +inside it, other repositories hardcode paths into it, and an agent harness keeps its own copy of +where things were. The walk test proves the *result* is navigable. Nothing in it proves the +*move* did not break something that was running while you made it. + +This is the field record from one such restructure: a home directory of roughly seventy +top-level entries, a dozen repositories, several agent accounts, four repository maps and a +nightly drift report, done over four phases with everything in daily use throughout. + +## When this applies + +Ask three questions before designing anything. Does something other than a human read a path in +this tree — a scheduled job, a daemon, a deploy config, an agent's session state? Do other +repositories name paths inside it? Is anyone working in it right now? One yes and the tree is +live. Two or three and the ordering below is not optional. + +The distinguishing symptom is that the failures are *silent*. A job whose input path moved +does not error; it finds nothing and reports success on an empty set. A config pointing at a +renamed directory falls back to a default. You will not find these by walking the new tree, +because the new tree is fine. You find them by knowing, before you move, who read what. + +## 1. Census before design + +Do not design the target structure first. Inventory what exists and who touches it, and let +the structure fall out. Four things worth a written record, kept and cited by row rather than +finished and discarded: + +**Who has the tree open.** List the live sessions and processes rooted anywhere inside it, +with their working directory and how long they have been idle. A session that has been idle for +two days with no children is a different decision than one mid-edit. + +**Every working copy and its parent.** Detached checkouts, worktrees, clones, each mapped back +to the repository it belongs to. A working copy living outside its own repository is what a +later "cleanup" deletes by accident. + +**Every credential-shaped name, never opened.** Name-and-kind only: a path, what sort of secret +it looks like, which tool owns it. Reading them is not part of the inventory and never becomes +part of it. + +**What runs on a schedule.** Cron entries, launch agents, systemd units, CI schedules, each +with the paths it reads and writes. These are the consumers that will not complain. + +## 2. The method's own corpus + +The plan, the specs, the census records — the documents driving the restructure — usually live +somewhere in the tree being restructured. Moving them mid-run pulls the ground out from under +the run. + +Copy the corpus into its new home at the very start, and leave the original in place as the +working reference for as long as anything cites it. At close, archive the original: copy, +repoint every referrer, then delete. Never a bare move while the run is reading it, and never a +pointer stub left behind in place of a file whose referrers you have already repointed — a stub +that nothing points at is just a second copy of the truth waiting to drift. + +## 3. The consumers register + +The highest-value artifact of the whole exercise, and it costs one table. Any time a task +writes a path that a later phase will rename, it appends a row: the consumer as `file:line`, +what the path points at today, what it must become, why, and which task registered it. Not a +mental note, not a comment in the commit. A row in one file that every phase reads. + +A phase closes only when every row is either DONE with the evidence that closed it, or +explicitly RE-REGISTERED for a later phase with the reason it is still open. Nothing is dropped +silently. Many of ours came from a task noticing a consumer in passing while doing something +else, which is exactly the knowledge that evaporates between sessions. + +The rows that matter most are the ones a search cannot find: an oblique mention in a handoff +document, a path assembled at runtime from two variables, a comment naming a file without its +extension. + +## 4. One move primitive + +Every move goes through one function, and the guard is the whole point: + +- source exists, destination does not → move +- source gone, destination exists → already done, record it, exit success +- anything else (both present, neither present) → halt, touch nothing + +The third branch is what makes a half-finished run safe to re-run. Both present means someone +else has been here, or a previous run was interrupted between the move and the record; neither +present means your row is wrong. Both are questions, not situations to resolve automatically. + +Each move appends one manifest row: identifier, old absolute path, new absolute path, kind, +size or file count, and a content hash for files. The hash proves the bytes survived the move, +not anything about the content. + +The manifests are the ledger, and the redirects index is *generated* from them, never maintained +by hand. That buys what hand-maintenance never delivers: when a file moves twice, the generator +chains the second move onto the first, so a document citing the original path still resolves. +Ours reached a few thousand rows across a couple of hundred manifests. + +## 5. Gates + +Two kinds, and they are different in kind, not degree. + +**Before a phase that mutates a live tree:** confirm zero other sessions are rooted at it. Not +"probably none" — enumerate them, and if one is there, either wait or record the decision to +proceed and why. + +**After:** a cold walk test (an agent with no context, given only the entry file, answering real +questions in a bounded number of reads), a count of top-level entries against the expected +number, and an actual exercise of every consumer you rewrote. Run the job. Open the config. Do +not read the diff and call it verified. + +Our first cold walk scored 3 of 4, and the failure was routing, not structure: the entry +document's row for "daemons" sent the reader down a branch that never mentioned the nightly job. +Two cells of one table fixed it. That is the normal outcome. The walk test finds *phrasing* +defects far more often than structural ones, which is why it must be run cold, by something that +has not been reading the plan all week. + +Owner gates — merging a pull request, rotating a credential, deleting something — are listed and +waited on. An agent does not execute them quietly because they looked mechanical. + +## 6. Worktrees + +One convention, written down once: a working copy lives inside its own repository, under a +predictable directory, named for its branch. Never as a sibling of the repository, and never in +a shared scratch area. + +Enforce it with a pre-tool hook that inspects the command before it runs and refuses the rest. +Warn everywhere by default; hard-refuse for the repositories you explicitly arm. The reason to +automate it is not tidiness. A working copy outside its repository is invisible to every +per-repository sweep, which is what makes "delete the stray directory" a data-loss event. + +For any change to a repository other people share, branch from the freshly fetched trunk +reference, not the local branch of the same name — a local `main` is a bookmark from whenever +someone last pulled. Measure the gap first. Ours was hundreds of commits on one repository, and +a patch authored on that base would not have applied at all. + +## 7. Shared-repo hygiene + +Work like this produces documents that are useful to collaborators and paths that are nobody +else's business. Both end up in the same commit unless something stops them. + +Before every commit that touches a repository other people read, run a grep gate: absolute +home paths, the operator's username, the vocabulary of private shelves and sensitivity tiers, +session identifiers, and the trailers agent tooling appends. The gate needs an allow-list, +because the same string is often the repository's own identifier — a column family, a tracked +filename, a schema name. The rule that survived contact: a hit that is a substring of a real +identifier on the trunk is *listed* and not renamed; a hit in prose is reworded. The gate reads +zero after exclusions, and the exclusions are written down. + +The mirror of this rule matters too. Do not document what a given person or role *cannot* do. +Describe the mechanism, not the permission. + +## 8. After landing + +A restructure decays. Budget for a report, not a repair job. + +A nightly job that walks the tree and *reports*, never fixes: stale generated artifacts +(compare by content hash first, only then by rebuild-and-diff), working copies outside the +convention, documents missing required frontmatter, credential-shaped filenames (names only), a +delta against the recorded top-level baseline, and redirect rows whose targets no longer exist. +One headline line — drift count and failure count, both always present — surfaced by a +session-start hook, so the number is in front of someone every morning. + +Report failures separately from drift: a step that *failed to run* is not zero drift, and if it +lands in the same number nobody notices the job is broken. And expect the count to be flat, not +zero. Ours settled at 66 items and stayed there across several unattended nights. A steady +number is the signal; a moving one is the alarm. + +## 9. What surprised us + +**A committed generated artifact can never be "current" by commit identity.** Our graph +generator stamps the commit it was built from, and that stamp is by construction the *parent* of +the commit that adds the graph — the graph is part of the commit. Every consumer testing "built +at equals HEAD" therefore called every real map stale, forever. The fix is to record a content +hash of the parsed source set beside the artifact and test currency by content, which also +survives the many commits that touch no source file at all. + +**A rename does not stop at the filesystem.** An agent harness stores the absolute working +directory in its own session state; a CLI stores project links in its own config. Our mover +rewrote both, and its first version still missed about 1,600 subagent transcripts per project +because the walk was capped at two levels, and silently rewrote nothing in one CLI config +because the live schema nested project links a level deeper than the fixture did. A verify pass +that re-searched for the old string found both. No test did. + +**A search that bypasses the repository's ignore rules over-matches into agent state.** The +convenient raw grep is not the ignore-aware one, and the difference shows up as hundreds of hits +inside tooling directories you did not mean to touch. + +**A hook that reads command text sees the text, not the value.** A guard inspecting +`git worktree add "$DEST"` sees the literal `$DEST` and refuses it. This is correct behaviour +and worth knowing before you write the command. + +**Check every claimed fix against the old system's real artifacts.** The recurring failure was +never a wrong fix. It was a *plausible* fix verified against a fixture that had drifted from +production. Read the live file, the live schema, the actual formula. A fix that survives a +genuine attempt to disprove it is the only kind worth landing on something people depend on. From 2c8d8f85e0af841320e16c1da97cf488030ac04b Mon Sep 17 00:00:00 2001 From: micahvv Date: Sun, 30 Aug 2026 07:19:34 -0400 Subject: [PATCH 2/3] Amend the system map form from four maps built under it --- references/system-map.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/references/system-map.md b/references/system-map.md index 618bf51..73b2d10 100644 --- a/references/system-map.md +++ b/references/system-map.md @@ -48,6 +48,12 @@ map/ Do not create `processes/` or `effects/` empty. Three verified noun clusters beat seven imagined shelves. +## Generated nodes + +Where the subject is code, a generator can produce a `graph/` shelf under `map/` — one node per module, route, or table, with the edges the parser can see. Cards then *link* a node instead of restating its edges, which is what keeps a card under its line budget. The graph is generated and committed; never hand-edited. + +Currency is the trap. A committed graph's `built-at` commit is, by construction, the PARENT of the commit that adds it — the graph is part of that commit — so sha equality can never mean "current" once a map has landed, and any consumer testing it that way calls every real map stale. Record a content hash of the parsed source set beside the graph (a `_sources.txt` listing each parsed file with its hash, plus a `source-hash:` line in the graph's meta file) and test currency by content instead. Count a graph as drifted only when a rebuild's nodes actually differ. + ## Audit pipeline (human-gated slices) Stop after each slice. A person or a cold walk reads the output before the next slice starts. @@ -85,8 +91,12 @@ Each process card: Input → Movement → Output; numbered steps with citations; `effects/CONTEXT.md` is a catalog: “if you are changing X, open these cards.” It does not copy waterfalls. If the index and a card disagree, fix the card. +The index is a FOUR-column table: `if you are changing… | open these cards first | and this graph node | does not hit (first-order)`. The last cell is one clause naming the obvious wrong next noun — a pointer, never a waterfall. A three-column index fails the walk test's step 4, which asks for hits AND does-not-hit. + Then walk it backwards. The index answers “I am changing X, what inside the tree moves.” It does not answer “what outside the tree points in.” Ask the owner — configs, issue trackers, agents, scheduled jobs — for absolute paths into the subject, and record each one on the card it lands on. These consumers break silently: nothing in the tree references them, so no card names them until you go looking. +Every link in an authored map file resolves to an existing file. A noun with no card is linked to its own line in `objects/_index.md` (an `` anchor) and reads `(stub)`; a fragment link needs a real heading or anchor, and a bolded phrase is neither. The one exception: an `_index.md` stub line may link its future card path, and the file says so. This is a mechanical existence check over the diff, run before every commit that touches links — not a content judgment, and not a review seat. + ### 5 — Re-verify After the first fill, rip the load-bearing claims again (especially Hits / Does not hit). Wrong waterfalls are more expensive than missing cards. @@ -110,7 +120,7 @@ A cold agent, no memory of the subject: 3. Open one object card. Does it cite source, state the why, and give a first-order waterfall? 4. From `effects/CONTEXT.md`, can it name what a stated change hits and what it does not? 5. Follow one `See` link. Does it land on source, not another essay? -6. Token check: entry + hub + one card stays in the 2k–8k band. +6. Token check: entry + hub + one card stays in the 2k–8k band. “Entry” means the entry file's inserted “Where to look” section, not the whole entry file — a house-rules book of 15k tokens is out of the map's scope. Record the whole-file count as an observation, not a band failure. If a step fails, split or move files. Do not add a “how to read this map” novel. From 18d57c5b73f980cd6d02b63afcd7a8730e597134 Mon Sep 17 00:00:00 2001 From: micahvv Date: Sun, 30 Aug 2026 07:19:34 -0400 Subject: [PATCH 3/3] Add the effects index and private shelf templates --- assets/templates/effects-CONTEXT.md | 35 +++++++++++++++++++++++ assets/templates/private-shelf-CONTEXT.md | 23 +++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 assets/templates/effects-CONTEXT.md create mode 100644 assets/templates/private-shelf-CONTEXT.md diff --git a/assets/templates/effects-CONTEXT.md b/assets/templates/effects-CONTEXT.md new file mode 100644 index 0000000..5e0f0f9 --- /dev/null +++ b/assets/templates/effects-CONTEXT.md @@ -0,0 +1,35 @@ + +# If you are changing X, open these + +This is an index, not an analysis. It answers "where do I start reading" in one line +and then gets out of the way — the actual Hits / Does not hit lives on the card, and +the exhaustive fan-in lives in `../graph/.md`. **If this file and a card +disagree, the CARD is authoritative and this file is the bug.** + +Entries are phrased as the change an editor is about to make, in their words. + +| if you are changing… | open these cards first | and this graph node | does not hit (first-order) | +|---|---|---|---| +| {a concrete change, e.g. "the format of a location id"} | `../objects/{cluster}/{noun}.md`, then `../objects/{cluster}/{neighbour}.md` | {the graph node for fan-in, a pinned golden test, a mirrored grammar in another module} | {the obvious wrong next noun — one clause, a pointer, never a waterfall} | +| {a table's columns} | `../objects/{cluster}/{table}.md` | {every writer named under Surfaces; whether another repo owns the object} | {a look-alike table or view this change does NOT reach} | +| {a shared registry or contract} | `../objects/{cluster}/{registry}.md` | {the test that fails on an unregistered entry} | {a neighbouring registry this one is never cross-wired to} | + +The last column names the obvious wrong next noun — one clause, a pointer, never a +waterfall. + +## What belongs here + +- A change that reaches further than the file you are editing. That is the whole bar. +- Changes that LOOK local and are not: a shared grammar mirrored in two modules, a + registry with a completeness test, a column two repos read, a value that must stay + byte-identical on both sides of an index or a cache key. + +## What does not + +- Anything the graph already answers mechanically ("who imports this file"). +- A waterfall copied off a card. Link the card. +- A change that touches one file and nothing else. + +Last updated: YYYY-MM-DD HH:MM TZ diff --git a/assets/templates/private-shelf-CONTEXT.md b/assets/templates/private-shelf-CONTEXT.md new file mode 100644 index 0000000..661050f --- /dev/null +++ b/assets/templates/private-shelf-CONTEXT.md @@ -0,0 +1,23 @@ +# _private — the sensitivity shelf + +Last updated: YYYY-MM-DD HH:MM TZ · mode 700 · owner: the human only + +## The load rule (binding on every agent and every account that can reach this tree) +1. Nothing under this shelf is loaded unless the current task names the file. +2. **Tier 0 files are never opened by an agent.** Report path and kind only. Rotation, pasting, and copying of a credential are human acts. +3. Tier 1 and Tier 2 files may be read only when the task requires their content, and their content is never quoted outward — not into Slack, a PR, a commit message, a doc, an artifact, a memory file, or a transcript summary. Report aggregates, never rows. +4. Never infer from this shelf. A fact learned here is not carried into another context unless the owner says so. +5. Nothing here is deleted by an agent. Superseded items stay; the owner deletes. + +## Tiers +| Tier | Folder | Holds | +|---|---|---| +| T0 | `credentials/` | env files, token snapshots, gate credentials, `.bak` copies of credential files | +| T1 | `people/` | PINs, HR rosters, onboarding drafts that carried credentials | +| T2 | `financial/` | raw customer/financial exports (accounting-system extracts, invoices, sales YTD) | + +## Credential register — stores that stay in place (path · kind · owner tool) +Agents may `ls -la` these to confirm existence; never `cat`. +| Path | Kind | Owner tool | +|---|---|---| +| | | |