repo-layout
Skill9 starsChanged 3 months agoApache-2.0View on GitHub
Register, resolve, and validate where a project's path-slots actually live via .layout-slots.yaml markers, so relocating a slot needs zero SKILL.md edits. Do NOT use for single-file lookups.
- Installs packages
2 other projects use a version of this.
Adopted by
Most recently taken up by
- DoKu67/claude-code-skilstheir own version, first seen Oct 16, 2026
- fierc3/agent-harness-templatetheir own version, first seen Aug 3, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- DoKu67/claude-code-skils★ 4same name
- fierc3/agent-harness-template★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository22 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .cursor/rules/compiling-project-guidelines.mdcCursor rule
- .cursor/rules/demo-consume-context.mdcCursor rule
- .cursor/rules/demo-write-user-stories.mdcCursor rule
- .cursor/rules/ult-autoscaffold-content.mdcCursor rule
- .cursor/rules/ult-cep-retrofit.mdcCursor rule
- .cursor/rules/ult-cep-wizard.mdcCursor rule
- .cursor/rules/ult-codegraph.mdcCursor rule
- .cursor/rules/ult-context-generate.mdcCursor rule
- .cursor/rules/ult-institutional-memory-distill.mdcCursor rule
- .cursor/rules/ult-onboarding-index.mdcCursor rule
- .cursor/rules/ult-repo-layout.mdcCursor rule
- compiling-project-guidelinesSkill
- demo-consume-contextSkill
- demo-write-user-storiesSkill
- autoscaffold-contentSkill
- cep-retrofitSkill
- cep-wizardSkill
- codegraphSkill
- context-generateSkill
- institutional-memory-distillSkill
- onboarding-indexSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. About 0 are common, 1 is distinctive, and 13 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 55
Read references/phase-history.md for the full D20/D21 phase-by-phase build history and exit criteria — not needed to operate this skill day …
- Distinctive: line 87
Every project has a handful of path-slots — directories or files that one skill owns and others read (e.g.
- Very specific: line 236
If two slots happen to share a directory, list both entries under that one slots: array — one marker file can declare multiple slots.
- Very specific: line 301
If context-config.yaml doesn't exist at the project root, generate it using the substitution table in "Generated context-config.yaml" above …
- Very specific: line 304
Ask the human for project_name and description and fill those two placeholders — the only ones the table leaves unset.
- Very specific: line 306
Ask whether to opt into layout.workspace_root (brief explanation: re-roots this project's slots and starter-kit drop-zone under one …
- Very specific: line 317
If output_docs_structure/ (or another existing SDLC-output tree) is present, suggest — don't silently add — what_l2.include_roots entries …
- Very specific: line 355
Write <path>/.layout-slots.yaml with the marker shown above (one slot: entry per slot.
- Very specific: line 360
Write/update context-config.yaml's project_layout — one slots: entry per slot actually scaffolded in step 2, i.e.
- Very specific: line 469
If the slot's pre-D21 default path already exists (e.g.
- Very specific: line 475
If nothing is found for a slot (items 1-2), the human says no, or the slot has no signature at all (item 3), ask directly: "Where does this …
- Very specific: line 483
Write project_layout with initialized: true, version: 1, same shape as init step 3, covering every slot that was confirmed.
- Very specific: line 577
Never hand-author this section.
- Very specific: line 609
If unset: prompt interactively, skip otherwise — never create by default ("ask the human" degrades to "do nothing and say so," never to …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
ult-repo-layout
(Frontmatter name: is the bare repo-layout — ult is this skill's
namespace:, composed into the folder name and every invocation below. Same
pattern elsewhere in this repo, e.g. sec-threat-model's name: is
threat-model.)
This skill implements the project layout and path-dependency configuration
model (§15) and workspace-root consolidation (§16 —
layout.workspace_root, scaffold-not-copy, and the
layout-slots-registry.yaml superset registry), covering eleven
path-slots:
context_packages— read/written byult-context-generateand everyCONSUMING-CONTEXT-PACKAGE.mdconsumer. The only slot a real, shipped skill in this repo both produces and consumes.compiled_guidelines— written bycompiling-project-guidelines, read by its ownCONSUMING-COMPILED-GUIDELINES.mdconsumers. The registry's onlykind: fileslot until D21 §16.8/D24 Phase B added three more (below), and the first slot whose D21 default re-roots to a different bucket (inputs→cache).decision_ledger— written byult-institutional-memory-distill'sdecision_ledger.py, read byult-context-generate. Akind: fileslot, same bucket re-root ascompiled_guidelines.autoscaffold_content_state,autoscaffold_content_index— written byult-autoscaffold-content'sscaffold_state.py. Two morekind: fileslots, same bucket re-root.plans_output,brainstorm_output,user_stories_output,security_docs,security_report,project_plan_docs— six further slots, all illustrative — not shipped in this repo (see the slot registry table below; rationale inreferences/phase-history.md).
...plus the project_guidelines starter-kit drop-zone — not a
project_layout path-slot (no marker, no resolution algorithm), just a
regenerated .pointer.md scaffold file. Read
references/starter-kit-dropzones.md now and follow it if you're touching
that mechanism.
Read references/phase-history.md for the full D20/D21 phase-by-phase build
history and exit criteria — not needed to operate this skill day to day.
This file and others in this repo cite D<N>/§<N> labels (e.g. D21 §16.2) — see
../../../references/design-scratchpad-glossary.md for what each one means.
Alternative front-end: you can also view and (partly) drive this skill's state
through a local browser wizard instead of re-reading context-config.yaml and the
markers by hand — see ult-cep-wizard (D24). It's
mostly a read-only status view, but it can trigger discover in place (its own
freshness-hash-guarded POST /api/discover) and the mechanical half of init —
slot scaffolding and the layout.workspace_root opt-in this section describes,
via POST /api/init — before D20 has been initialized at all. It never asks for
project_name/description, offers a rename-before-scaffold prompt, or runs
confirm-layers/reconcile — those, and the fully conversational init, still
happen here.
Dependencies
None — this skill is foundational. ult-context-generate (utilities
bundle, context_packages), compiling-project-guidelines (utilities
bundle, compiled_guidelines), ult-institutional-memory-distill
(decision_ledger), and ult-autoscaffold-content
(autoscaffold_content_state/autoscaffold_content_index) are real,
shipped-in-this-repo consumers of the markers/index this skill maintains
(via the §15.5 resolution algorithm, documented in each owning skill's own
path-resolution note) — not dependencies of this skill. The other six
registered slots (see the slot registry table above) are owned by
illustrative skills only — same "not a dependency" status either way.
Overview
Every project has a handful of path-slots — directories or files that one
skill owns and others read (e.g. "where do approved context packages live?").
Hardcoding those paths in every SKILL.md means relocating a folder requires
editing every skill that mentions it. This skill fixes that with two pieces:
- Markers (
.layout-slots.yaml, §15.3) — a small YAML file dropped in the directory that is (or contains) a slot, declaringslot: <key>. The marker travels with the folder if it's renamed or moved — identity lives with the folder, not in a separate table. project_layoutincontext-config.yaml— a generated index (reconcilerebuilds it from markers), cached for fast lookups. It is never hand-authored and never the source of truth — markers are.
This skill is the only thing that writes markers and rebuilds the index.
Other skills only read project_layout (fast path) and fall back to a
repo-wide marker search (§15.5) if the index is stale or absent — they never
hardcode a slot's path.
Slot registry
project_layout.slots registers only slots whose path isn't already a key
under layers.* / how_dimension.* / graphify.* / cache.* in
context-config.yaml (§15.2) — relocating those is "edit the existing key,"
already supported. Eleven slots are registered:
| Slot key | Kind | Pre-D21 default | D21 default (layout.workspace_root set) | Falls back to (if unset) | Owning skill |
context_packages | directory | contexts/ | {workspace_root}/contexts/ | cache.product_context_path | ult-context-generate |
plans_output | directory | output_docs/plans/ | {workspace_root}/outputs/plans/ | — | example-plan-writer (illustrative) |
brainstorm_output | directory | output_docs/brainstorm/ | {workspace_root}/outputs/brainstorm/ | — | example-brainstorm-writer (illustrative) |
compiled_guidelines | file | starter_kit/project_guidelines/COMPILED-GUIDELINES.md | {workspace_root}/cache/project-guidelines/COMPILED-GUIDELINES.md | — | compiling-project-guidelines |
decision_ledger | file | starter_kit/decision_ledger/DECISION-LEDGER.json | {workspace_root}/cache/decision-ledger/DECISION-LEDGER.json | — | ult-institutional-memory-distill |
autoscaffold_content_state | file | starter_kit/autoscaffold-content/TRIAGE-STATE.json | {workspace_root}/cache/autoscaffold-content/TRIAGE-STATE.json | — | ult-autoscaffold-content |
autoscaffold_content_index | file | starter_kit/autoscaffold-content/CEP-INDEX.md | {workspace_root}/cache/autoscaffold-content/CEP-INDEX.md | — | ult-autoscaffold-content |
user_stories_output | directory | output_docs/user-stories/ | {workspace_root}/outputs/user-stories/ | — | example-consumer (illustrative) |
security_docs | directory | output_docs/security_docs/ | {workspace_root}/outputs/security_docs/ | — | example-threat-modeler (illustrative) |
security_report | directory | output_docs/security_report/ | {workspace_root}/outputs/security_report/ | — | example-report-writer (illustrative) |
project_plan_docs | directory | output_docs/project_plan_docs/ | {workspace_root}/outputs/project_plan_docs/ | — | example-project-planner (illustrative) |
The "D21 default" column only applies to an unmarked slot when
layout.workspace_root is set (§16.2 step 3) — it never overrides a marker or
an explicit project_layout.slots.<slot>.path (§16.2 steps 1-2). A project
that never sets layout.workspace_root resolves via the "Pre-D21
default"/"Falls back to" columns exactly as before, forever (§16.10).
plans_output and brainstorm_output are the two Gap-B slots that close
a gap left open by the first registered slot: a plan-writing or brainstorming
skill that hardcodes its own output directory, with no slot at all to
relocate it via (see references/phase-history.md's Phase 3b for the real
external skillset this gap was diagnosed and proof-tested against — that
detail is provenance, not this table's operational default). Like the eight
slots below, neither has a pre-existing config-key fallback — "Falls back to"
is "—", so an unmarked slot's step-1 fallback is simply its literal pre-D21
default.
The eight remaining slots:
compiled_guidelinesis the registry's firstkind: fileslot — its resolved path is a single file, not a directory (see "Marker file format" below for thefile:marker field this requires). It's also the first slot whose D21 default changes bucket (§16.3/§16.4): the pre-D21 defaultstarter_kit/project_guidelines/COMPILED-GUIDELINES.mdsits in theinputs-bucket starter-kit drop-zone (alongside the raw sourcescompiling-project-guidelinesreads), butCOMPILED-GUIDELINES.mditself is a derived, regenerable artifact — so its D21 default re-roots to thecachebucket,{workspace_root}/cache/project-guidelines/COMPILED-GUIDELINES.md, not{workspace_root}/inputs/....decision_ledger,autoscaffold_content_state, andautoscaffold_content_indexare three morekind: fileslots, each a derived/regenerable artifact for the same reasoncompiled_guidelinesis, so each re-roots the sameinputs→cacheway (these four are the onlykind: fileslots in the registry).decision_ledger(owned byult-institutional-memory-distill, D21 §16.8's trip-wire addition) is the decision ledger, entries only ever added viadecision_ledger.py, never hand-edited.autoscaffold_content_state/autoscaffold_content_index(both owned byult-autoscaffold-content, D24 Phase B) are the per-module triage checkpoint and its rendered index, written only viascaffold_state.py.user_stories_output,security_docs,security_report, andproject_plan_docsare four siblingdirectoryslots, each one project'soutput_docs/<family>/subtree. All four belong to theoutputsbucket, alongsideplans_output/brainstorm_output— their D21 defaults sit under{workspace_root}/outputs/. None of the four nests inside another (they diverge atoutput_docs/<family>/'s second path segment), soscripts/validate_layout.py's nesting check (§15.9 #3) needs nonests_under:whitelist entries for this set.
context_addenda (the *.addenda.yaml siblings D19 v2 writes) is not a
separate slot — its path is always
{resolved context_packages path}/{package-id}_{date}.addenda.yaml (§15.4).
scripts/validate_layout.py's SLOT_REGISTRY is written generically over
"any number of registered slots" and additionally exercises a kind: file
slot and the partial-install gate (§15.8 — full behavior under "Checks"
below, in scripts/validate_layout.py).
Marker file format — .layout-slots.yaml
One file per directory that hosts one or more slots:
# contexts/.layout-slots.yaml -- this directory IS the context_packages slot
slots:
- slot: context_packages
kind: directory
schema_version: 1plans_output/brainstorm_output/user_stories_output/security_docs/
security_report/project_plan_docs markers have the same shape, just a
different slot: key:
# my-plans/.layout-slots.yaml -- this directory IS the plans_output slot
slots:
- slot: plans_output
kind: directory
schema_version: 1compiled_guidelines is the registry's first kind: file slot (see
"Slot registry" above for the other three: decision_ledger,
autoscaffold_content_state, autoscaffold_content_index) — its marker
adds a file: field naming the file within the marker's directory:
# starter_kit/project_guidelines/.layout-slots.yaml -- this directory CONTAINS
# the compiled_guidelines slot (the file COMPILED-GUIDELINES.md within it)
slots:
- slot: compiled_guidelines
kind: file
file: COMPILED-GUIDELINES.md
schema_version: 2kind: directory→ the slot's resolved path is the marker's own directory.kind: file→ the slot's resolved path is<marker's directory>/<file>(resolved_path_for_markerinscripts/validate_layout.pyhandles this generically —compiled_guidelinesis simply the first slot to use it).decision_ledger/autoscaffold_content_state/autoscaffold_content_indexuse the samekind: filemarker shape ascompiled_guidelinesabove, just naming their own file (DECISION-LEDGER.json,TRIAGE-STATE.json,CEP-INDEX.mdrespectively).schema_version— theproject_layoutschema version that introduced this slot (§15.8);1forcontext_packages/plans_output/brainstorm_output,2forcompiled_guidelines/user_stories_output/security_docs/security_report/project_plan_docs,3fordecision_ledger(D21 §16.8),4forautoscaffold_content_state/autoscaffold_content_index(D24 Phase B).- If two slots happen to share a directory, list both entries under that one
slots:array — one marker file can declare multiple slots.
Starter-kit drop-zones and .pointer.md (D21 §16.6)
One inputs/-bucket drop-zone (project_guidelines) holds project-owned,
human-curated material that a consuming skill reads but never regenerates
(compiling-project-guidelines). Read references/starter-kit-dropzones.md
now and follow it — it has the drop-zone's .pointer.md template and the
workspace-root-aware location rules.
Generated context-config.yaml
install.ps1/install.sh -InitProject generates a baseline
context-config.yaml at the project root (only if one doesn't already exist)
by copying starter_kits/context_engineering/context-config.yaml.template and
applying this mechanical substitution table — the same defaults a human
would get by hand-copying .template and accepting every e.g. value,
byte for byte (§16.2's non-impact guarantee extends to this generated file):
Template placeholder (<...> token) | Generated value | Field |
<your-project-name> | (left as-is — human input) | project_name |
<one-line description of what this product/service does> | (left as-is — human input) | description |
<source code root, e.g. app/ or src/> | . | what_l3.path |
<requirements docs root, e.g. docs/requirements/> | docs/requirements/ | what_l2.path |
<external reference root, e.g. specs/external/> | specs/external/ | what_l1.path |
<org conventions/templates root, e.g. org/> | org/ | how_l2.path |
<process standards root, e.g. org/process-standards/> | org/process-standards/ | how_l1.path |
project_name/description are the only two fields left as literal
<placeholder> — exactly §16.6 item 2's "the only fields requiring human
input." what_l3.path resolves to . (repo root) rather than the
app/-or-src/ example: neither candidate is guaranteed to exist, and .
is the universally-safe "index the whole repo" default for a brand-new
project (a deliberate deviation from the literal e.g. value for this one
field — every other row uses its e.g. value as-is). The generated file has
no project_layout section — init step 1's "refuse if already
initialized" guard only fires on project_layout.initialized: true, so this
generated baseline composes cleanly with init below.
init (this skill) then completes this file: it asks the human for
project_name/description, optionally offers the layout.workspace_root
opt-in (re-rooting the drop-zone's .pointer.md location and pre-populating
layers.what_l2.exclude/include_roots per §16.5/§16.7), and performs its
existing slot-marker/project_layout work (below). If context-config.yaml
doesn't exist yet when init runs (the human skipped -InitProject), init
generates it inline using the table above before continuing.
Modes
init — greenfield projects
Steps 1-3 and 5 below are mechanical and backed by
validate_layout.py --init [--workspace-root <path>] [--ci-hook] — run it
verbatim once the pre-step-1 conversational items (config completion,
workspace_root opt-in, pointer regeneration) and step 4's rename-before-
scaffold offer are settled; it performs the refuse-if-initialized check, the
per-slot scaffold+marker writes, the project_layout write, and the
pre-commit hook scaffold in one pass. It refuses cleanly (rather than
generating one) if context-config.yaml doesn't exist yet — run the
pre-step-1 config-completion item first.
Before step 1 — config completion and pointer regeneration:
- If
context-config.yamldoesn't exist at the project root, generate it using the substitution table in "Generatedcontext-config.yaml" above (same outputinstall.ps1/install.sh -InitProjectwould have produced). - Ask the human for
project_nameanddescriptionand fill those two placeholders — the only ones the table leaves unset. - Ask whether to opt into
layout.workspace_root(brief explanation: re-roots this project's slots and starter-kit drop-zone under one directory, e.g.docs/). This question is skipped entirely iflayout.workspace_rootis already set (re-runninginitmust never re-prompt or change an existing value — the same "never silently reset" rule fromreconcileapplies here too). If the human opts in:- Set
layout.workspace_root: <value>(same well-formedness rule as check 7 — reject./''). - Pre-populate
layers.what_l2.exclude: [contexts/, inputs/, cache/](the §16.5 recommended triad — only add entries for subtrees that don't already appear inwhat_l2.exclude). - If
output_docs_structure/(or another existing SDLC-output tree) is present, suggest — don't silently add —what_l2.include_rootsentries per §16.7; otherwise leaveinclude_roots: [].
- Set
- Regenerate the
project_guidelinesstarter-kit drop-zone's.pointer.mdat its current location:starter_kit/project_guidelines/.pointer.mdiflayout.workspace_rootis unset (the common case — this is where-InitProjectalready created it), or{workspace_root}/inputs/starter-kit/project_guidelines/.pointer.mdif the human just opted intoworkspace_rootfor a brand-new project with no existing drop-zone content (re-rooting an existing project's drop-zone isreconcile --adopt-workspace-root's job, below —initnever moves files). Create the directory first if it doesn't exist; never touch any other file already in that directory.
Then run steps 1-5 below (except that step 2's
{workspace_root}/<leaf> resolution now has a real workspace_root value to
use if the human opted in above):
- Refuse if already initialized — if
context-config.yamlhasproject_layout.initialized: true, stop and say so; point atreconcileor offer a diff view instead. Re-runninginitmust never silently reset a customized layout back to defaults. One exception:init --ci-hookon an already-initialized repo scaffolds the opt-in pre-commit hook (see "CI / pre-commit hook" below) and stops there, leavingproject_layoutuntouched — otherwise the hook would be unreachable to anyone who raninitonce without it. - Otherwise, for each registered slot (see the slot registry table above —
all eleven) whose owning skill is installed (partial-install gate, §15.8):
- Resolve its resolved default (§16.2):
{workspace_root}/<leaf>iflayout.workspace_rootis set incontext-config.yaml(and well-formed), else the slot's "Falls back to" config key if set (onlycontext_packageshas one —cache.product_context_path), else its pre-D21 default (§15.2/§16.4 — see the slot registry table above). - Scaffold that directory if it doesn't exist yet — for
kind: fileslots (compiled_guidelines,decision_ledger,autoscaffold_content_state,autoscaffold_content_index), scaffold the containing directory only; the file itself is each owning skill's own output, never created byinit. - Write
<path>/.layout-slots.yamlwith the marker shown above (oneslot:entry per slot; slots that resolve to the same directory share one marker file'sslots:list). For akind: fileslot, the marker is written in the containing directory withkind: file, file: <filename>(see "Marker file format" above).
- Resolve its resolved default (§16.2):
- Write/update
context-config.yaml'sproject_layout— oneslots:entry per slot actually scaffolded in step 2, i.e. only the slots whose owning skill is installed (same partial-install gate as step 2 — a project that installed only thedeveloperbundle writes only that bundle's slots here, never all eleven). The example below shows every registered slot at once purely for illustration of the shape each entry takes; a real partial install'sproject_layout.slotssimply omits the rows whose owning skill isn't present:project_layout: version: 1 initialized: true slots: context_packages: path: contexts/ # or wherever step 2 resolved it kind: directory owning_skill: ult-context-generate plans_output: path: output_docs/plans/ # or wherever step 2 resolved it kind: directory owning_skill: example-plan-writer # illustrative brainstorm_output: path: output_docs/brainstorm/ # or wherever step 2 resolved it kind: directory owning_skill: example-brainstorm-writer # illustrative compiled_guidelines: path: starter_kit/project_guidelines/COMPILED-GUIDELINES.md # or wherever step 2 resolved it kind: file owning_skill: compiling-project-guidelines decision_ledger: path: starter_kit/decision_ledger/DECISION-LEDGER.json # or wherever step 2 resolved it kind: file owning_skill: ult-institutional-memory-distill autoscaffold_content_state: path: starter_kit/autoscaffold-content/TRIAGE-STATE.json # or wherever step 2 resolved it kind: file owning_skill: ult-autoscaffold-content autoscaffold_content_index: path: starter_kit/autoscaffold-content/CEP-INDEX.md # or wherever step 2 resolved it kind: file owning_skill: ult-autoscaffold-content user_stories_output: path: output_docs/user-stories/ # or wherever step 2 resolved it kind: directory owning_skill: example-consumer # illustrative # ...and so on for security_docs, security_report, project_plan_docs -- # same shape, one entry per remaining row of the slot registry table # above. - Optionally interactive up front — before scaffolding, ask: "Here's the
default layout for all eleven registered slots —
<slot>→<resolved default>for each row of the slot registry table above (or its{workspace_root}/...equivalent iflayout.workspace_rootis set) — rename or relocate any of these before we start?" A team that wants a custom layout from day one does it in one pass; each marker is written at its chosen location either way. - Scaffold a CI/pre-commit hook only if requested — see "CI /
pre-commit hook" below. Opt in with
init --ci-hook; omitted by default. (--no-ci-hookis accepted as a deprecated no-op for one release, since the hook is already off unless asked for.)
reconcile — rebuild the index from markers; the repair tool for drift
Runs the steps below independently for each registered slot — a marker
for one slot doesn't affect another's resolution, even if two slots' markers
happen to live in the same .layout-slots.yaml file's slots: list.
- Repo-wide search for
slot: <key>across every.layout-slots.yaml(the same deterministic scanvalidate_layout.pyperforms — path depth ascending, then lexical ascending). - Exactly one marker found → write/update
project_layout.slots.<key>.pathto match it. This is what fixes a stale index automatically — no prompt needed. - Zero markers found → ask the human where
<key>lives now, or whether it was intentionally removed — mention the resolved default (§16.2) as the likely candidate, e.g. "...its resolved default would be<path>— is that where it lives?". Never guess by name similarity. On confirmation, write a new marker there. - More than one marker found (for one slot) → bijectivity violation (§15.9) — surface it as a conflict for the human to resolve (pick one location, or merge the two folders' contents). Do not auto-resolve.
reconcile --validate— no prompts, just runsscripts/validate_layout.py --validateand reports pass/fail with a non-zero exit code on failure, for CI / pre-commit (§15.9).reconcile --adopt-workspace-root <path>(D21 §16.6) — advisory/dry-run helper for migrating an existing project ontolayout.workspace_root: for every registered slot and starter-kit drop-zone, reports whether it's already marker-pinned (unaffected), has a duplication conflict that needs resolving by hand, or offers a pin-here-vs-relocate choice. Readreferences/workspace-root-adoption.mdnow and follow it — it has the full per-slot decision tree.
discover — brownfield adoption (no prior markers or project_layout)
Each registered slot has its own content-based signature for guessing a
candidate location — except compiled_guidelines, user_stories_output,
security_docs, security_report, and project_plan_docs, which have none
and skip straight to asking (§15.7):
context_packages— scan for*.yamlfiles containing a top-levelcontext_package:key with anapproved_byfield, sorted deterministically (path depth ascending, then lexical ascending — the same orderreconcile's tie-breaks use).plans_output/brainstorm_output— simpler signature: does the slot's pre-D21 default path (output_docs/plans//output_docs/brainstorm/) exist and contain.mdfiles? If so, that's the candidate.compiled_guidelines/user_stories_output/security_docs/security_report/project_plan_docs— no content-based signature (§15.7): go straight to step 5 below for each of these five, every time. If the slot's pre-D21 default path already exists (e.g.output_docs/security_docs/, or another existingoutput_docs_structure/ <family>/convention), mention it as a likely answer rather than asking cold.- For slots with a candidate (items 1-2), present it to the human for
confirmation: "Found
<path>— is this where<slot>lives?" - If nothing is found for a slot (items 1-2), the human says no, or the slot
has no signature at all (item 3), ask directly: "Where does this project
keep
<slot description>? (path, or 'not in use')" - On confirmation, write a marker at that location (creating the directory
first if it doesn't exist) — per slot. For the
kind: fileslot (compiled_guidelines), write the marker in the containing directory withkind: file, file: COMPILED-GUIDELINES.md(see "Marker file format" above). - Write
project_layoutwithinitialized: true,version: 1, same shape asinitstep 3, covering every slot that was confirmed.
Layer-path discovery and confirmation — discover_layers.py / confirm_layers.py
A separate D23 mechanism from the 11-slot discover mode above: proposes and
commits the four layer paths (layers.what_l2, layers.what_l1,
how_dimension.how_l2, how_dimension.how_l1) that project_layout.slots
deliberately excludes (§17.1). Read references/layer-path-discovery.md now
and follow it if you're running discover's layer-path phase or
confirm-layers — it has the full discover_layers.py/confirm_layers.py
usage, the escalation-matrix shapes, comment-as-grammar rules, and §17.6
drift tracking.
Path resolution algorithm (§15.5 + §16.2) — for consuming skills
Each owning/consuming skill resolves its slot this way instead of a hardcoded
path. ult-context-generate/CONSUMING-CONTEXT-PACKAGE.md (context_packages)
and compiling-project-guidelines/CONSUMING-COMPILED-GUIDELINES.md
(compiled_guidelines) are two real, shipped-in-this-repo consumers;
ult-institutional-memory-distill/decision_ledger.py (decision_ledger)
and ult-autoscaffold-content/scaffold_state.py
(autoscaffold_content_state/autoscaffold_content_index) are two more. The
same algorithm applies identically to the six illustrative slots (see the
slot registry table above for which owning skill maps to which):
- No
context-config.yaml, or noproject_layoutsection → use the slot's resolved default (defined below) — unconfigured project, today's behavior, unchanged unlesslayout.workspace_rootis set. project_layout.slots.<slot>present (cache hit) → read.path, then check whether that location's.layout-slots.yamlstill listsslot: <slot>:- Confirms → resolved (fast path, one YAML read).
- Missing/stale → cache miss, fall through to step 3.
- Cache miss, or
slots.<slot>absent → repo-wide marker search (the same scanreconcileperforms):- Zero matches → the slot's resolved default (defined below), plus
non-blocking note:
"Note: slot '<slot>' has no marker in this repo — using default '<path>'. Run /ult-repo-layout reconcile to register it if this isn't where you keep it."(<path>is the resolved default). - Exactly one match → resolved to that path; if the index pointed
elsewhere, this run also flags it as stale (next
reconcilerefreshes it). - Multiple matches → bijectivity violation: read context uses the
first by stable sort order with a warning; write context hard-stops
—
/ult-repo-layout reconcilemust resolve it first.
- Zero matches → the slot's resolved default (defined below), plus
non-blocking note:
- Once resolved: existence check (
directory→ can be listed), type check (is it actually a directory?). Not found in read context → warn-and-continue. Not found in write context → governed bylayout.on_missing_write_pathbelow.
Resolved defaults per slot (D21 §16.2/§16.4)
A marker (steps 2-3 above) always wins regardless of this section — it only governs steps 1 and 3's "zero matches" case, i.e. an unmarked slot. For an unmarked slot, see the slot registry table above for every slot's "Pre-D21 default" and "D21 default" columns:
layout.workspace_rootset (and well-formed — not./'') → the slot's "D21 default" column,{workspace_root}/<leaf>(§16.4).layout.workspace_rootabsent (or malformed) → the slot's "Falls back to" config key if set (onlycontext_packageshas one —cache.product_context_path, §15.2), else its "Pre-D21 default" column, literally.
A few slots have a wrinkle worth calling out by name:
context_packagesis the only slot with a config-key fallback (cache.product_context_path) — a project that set this key before this skill ever existed continues to work unchanged, even withlayout.workspace_rootunset.compiled_guidelines,decision_ledger,autoscaffold_content_state, andautoscaffold_content_indexare the four slots whose D21 default changes bucket, not just root: each pre-D21 default sits in theinputs/starter_kitbucket (raw, project-authored territory), but each resolved file is a derived, regenerable artifact — so each D21 default re-roots to thecachebucket instead (e.g.{workspace_root}/cache/project-guidelines/COMPILED-GUIDELINES.mdforcompiled_guidelines) —{workspace_root}/inputs/...is never a candidate for any of the four (see "Slot registry" above for why).
workspace_root therefore changes defaults only — it can never override a
marker (steps 2-3) or an explicit project_layout.slots.<slot>.path (step 2).
A project that never sets layout.workspace_root resolves exactly as it
did before this mechanism existed, forever (§16.10's zero-impact guarantee).
validate_layout.py --validate rejects layout.workspace_root: . and
layout.workspace_root: '' (see below) — a single config-level check
that applies to every registered slot at once, not a per-slot check.
Config reference — context-config.yaml
This skill reads and writes:
project_layout— the generated index (seeinit/reconcileabove). Never hand-author this section; runninginit(new project) ordiscover(existing project) writes it for you. Hand-editingproject_layout.slots.<slot>.pathwithout also moving the marker produces the stale-index casereconcileexists to fix — move the folder (and its marker), or runreconcile, don't edit this path directly.cache.product_context_path— read-only for this skill, ascontext_packages's pre-D21 documented default/fallback (§15.2). Setting this alone (noproject_layout, nolayout.workspace_root) still works exactly as it did originally — step 1 of the resolution algorithm above.plans_output/brainstorm_outputhave no equivalent config-key fallback (they're newer slots, §16.4) — their step-1 fallback is always their literal pre-D21 default.layout.workspace_root(D21 §16.2) — optional, repo-relative directory path (e.g.docs/). When set, each unmarked registered slot's resolved default becomes{workspace_root}/<leaf>(§16.4) —contexts/forcontext_packages,outputs/plans/forplans_output,outputs/brainstorm/forbrainstorm_output— instead of its pre-D21 default; see "Resolved defaults per slot" above. Never overrides a marker or an explicitproject_layout.slots.<slot>.path..and''are rejected byvalidate_layout.py --validate. Absent by default — a project that never sets it resolves exactly as before.layout.on_missing_write_path— governs what happens when a skill tries to write to a registered slot but its resolved path doesn't exist:prompt(default in interactive sessions) — "Configured output path for slot '<slot>' ('<path>') doesn't exist — create it now, or has it moved? (create / tell me the new path / skip this write)"skip(default in non-interactive sessions) — warn, don't write, report the write as skipped. Whether that's an error is the calling skill's call.create— silently create the directory. Opt-in only — declares "the configured path is always correct, create it if absent."
If unset:
promptinteractively,skipotherwise — nevercreateby default ("ask the human" degrades to "do nothing and say so," never to "guess"). Applies the same way to every registered slot (see the slot registry table above — all eleven). For akind: fileslot (compiled_guidelines,decision_ledger,autoscaffold_content_state,autoscaffold_content_index), "doesn't exist" means the file itself;createcreates its containing directory only — the file is each owning skill's own output, never fabricated empty by this mechanism.
A ready-to-copy template with these keys (annotated) is at
starter_kits/context_engineering/context-config.yaml.template.
CI / pre-commit hook
init --ci-hook (opt-in — omitted unless explicitly requested) scaffolds a
hook that runs, from the repo root:
PY=$(command -v python3 || command -v python) || exit 0
"$PY" .github/skills/ult-repo-layout/scripts/validate_layout.py --validate || exit 0Fails open, not closed: a missing python3/python on PATH, or a
non-zero exit from --validate itself, exits 0 rather than blocking the
commit — a convenience nudge that prints the report either way, never a
hard gate an adopter didn't explicitly ask for. No LLM involved (§15.9),
same deterministic-check precedent as content_hash.py/md_index.py. Wire
it into whatever this repo already uses (.github/workflows/,
.pre-commit-config.yaml, etc.) — validate_layout.py itself has no
opinion on the wrapper.
scripts/validate_layout.py
Deterministic, stdlib-only (no pip install), vendorable alongside
md_index.py/content_hash.py. Run from the repo root:
python .github/skills/ult-repo-layout/scripts/validate_layout.py --validate [<repo-root>]Checks (§15.9; all eleven slots from the slot registry table above are
registered, but every check is written generically over SLOT_REGISTRY — the
mechanism was proven going from 1 to 3 to 8 to 11 registered slots with zero
logic changes, including a kind: file slot and the partial-install gate
below). Numbered in the order validate() actually runs them:
- Bijectivity — no slot has more than one marker; no two slots resolve to the same path.
- Type consistency — a slot's resolved path, if it exists, matches its
declared
kind. - Nesting — same-kind slots sharing a path prefix (excluding
.). - Path well-formedness — repo-relative only (no
..), no Windows-reserved device names (CON,COM1-COM9, etc., exact match only —COM1-migrationis fine), no trailing space/dot segments. - Cross-platform normalization —
project_layout.slots.*.pathvalues must use forward slashes. - Config-vanished check —
context-config.yaml's git history once hadinitialized: truebut the current file has noproject_layoutsection (likely accidental deletion) → runreconcile. workspace_rootwell-formedness (D21 §16.2) —layout.workspace_root, if set, must be a non-empty repo-relative path other than./''(reuses check 4's path-wellformedness rules)../''→FAIL(hard-stop, not a silent fallback to either default).- What-L2 index-path exclusion (§16.5) — if
layers.what_l2.index_pathresolves to a path underlayers.what_l2.path, it must be covered by awhat_l2.excludeentry, or What-L2 could end up indexing its own index file →FAIL. A no-op whenindex_pathresolves outsidewhat_l2.pathentirely (the default wheneverlayout.workspace_rootis unset). - What-L2 exclude-entry typo check (§16.11) — each
what_l2.excludeentry should prefix-match an existing subtree underwhat_l2.path, checked case-sensitively; one that matches nothing existing is a likely typo or case mismatch → non-blockingWARN. A no-op ifexcludeis empty, or ifwhat_l2.pathitself doesn't exist yet. - Layer path population (D23 §17.8) — a
non-blocking
WARNif an enabled layer's resolved path (layers.what_l2.path,layers.what_l1.path,how_dimension.how_l2.path,how_dimension.how_l1.path) doesn't exist or contains no files. What-L2/How-L2 are always checked (no opt-out in the shipped config surface); What-L1/How-L1 are checked only when their ownenabled: trueis set — a disabled opt-in layer left at its placeholder/absentpathnever warns. Closes the silent-fallback risk documented inult-context-generate/SKILL.md's D8 table (a misconfigured path and a genuinely empty layer previously produced identical, silent behavior). Discovery/auto-proposal of these four paths (D23 §17.2–§17.7) is a separate, deferred mechanism — this check only validates a path that's already configured (or defaulted), it never suggests one. - Layer candidate path population (D23 §17.8 per-candidate extension) —
a non-blocking
WARNif a confirmedlayers.what_l2.include_rootsentry doesn't exist or contains no files, checked individually per entry (check 10 above only ever inspectswhat_l2.pathitself, never the additional content roots alongside it).excludeentries are not duplicated here — check 9 above alreadyWARNs when one matches nothing existing; an empty-but-existing excluded directory is a harmless no-op with no actionable claim behind aWARN. layout-slots-registry.yamlconsistency check (D21 §16.8) — if that file exists at the repo root, itsslots:entries withproject_layout_slot: truemust exactly matchSLOT_REGISTRY's keys in this script;FAILon drift in either direction. A no-op (the file is library-level-only) for every consuming project and every test fixture, including this repo's own test suite. Readreferences/library-level-registry.mdnow and follow it — it has the full registry schema and the companionvalidate_path_conformance.pycheck. Runs last, after the layer checks above.
Two more behaviors sit outside this numbered list because they don't run as their own pass over all slots:
- A non-blocking
WARN, emitted per-slot while resolving markers rather than as one of the checks above: an unmarked slot with content at both its pre-D21 default and itsworkspace_root-relative default — a likely partial migration;reconcilelets the human pick one location. - A partial-install gate: if this repo has a
.github/skills/directory, a slot whoseowning_skillisn't present under it is skipped entirely for every check above (no INFO/WARN/FAIL, not part of bijectivity/nesting) — an adopter who installed only a subset of skills never sees messages about slots owned by skills they didn't install. Repos with no.github/skills/directory at all (including every test fixture in this suite) are unaffected — the gate is a no-op there.
Exits 0 (PASS) or 1 (FAIL); prints INFO/NOTE/WARN/FAIL lines per
check. Unit tests in scripts/tests/test_validate_layout.py.
Error conditions
| Condition | Action |
init run when project_layout.initialized: true | Refuse — "Already initialized. Run /ult-repo-layout reconcile to update the index, or discover to re-confirm slot locations." Add that re-running with --ci-hook scaffolds the opt-in pre-commit hook alone, without re-initializing |
init --ci-hook run when project_layout.initialized: true | Not an error — scaffold the pre-commit hook only (never overwriting an existing one) and leave project_layout as it stands |
A registered slot has zero markers (reconcile) | Ask the human where it moved or whether it was removed — never guess by name similarity |
| A registered slot has multiple markers (any mode) | Bijectivity violation — surface as a conflict; do not auto-resolve |
| Resolved slot path doesn't exist, read context | Warn-and-continue: "proceeding as if <slot> has no content for this run" |
| Resolved slot path doesn't exist, write context | Apply layout.on_missing_write_path (prompt / skip / create) |
Resolved path exists but wrong kind (file vs. directory) | Report as type-consistency violation — distinct from "not found" |
| Resolved path exists but not writable (write context) | Distinct error — "permission denied, not a project_layout configuration issue" |
validate_layout.py --validate on a repo with zero markers and no project_layout | PASS with one INFO: project_layout is not initialized... line per registered slot — not an error |
layout.workspace_root is . or '' | FAIL — hard-stop; treated as present-but-invalid, not as absent; applies to every registered slot at once |
Content at both a slot's pre-D21 default and its workspace_root-relative default, no marker | Non-blocking WARN — partial migration; reconcile lets the human choose one |
reconcile --adopt-workspace-root, slot content at both pre-D21 and workspace_root-relative defaults, no marker | Flag as the same partial-migration conflict (§16.11) — offer neither pin-marker nor mv option; human must resolve the duplication first |
reconcile --adopt-workspace-root, slot already has a marker (anywhere) | Report "unaffected — already has a marker" (§16.11); no options offered, nothing changes |
layout-slots-registry.yaml's slots: (where project_layout_slot: true) doesn't exactly match SLOT_REGISTRY | FAIL (D21 §16.8, check 12) — registry/code drift; a no-op if the file is absent (every consuming project) |
confirm-layers run with any field still PENDING, using a verb its comment doesn't offer, or an unfilled <...> placeholder | Refuse — print every such error, write nothing to context-config.yaml (no partial apply, D23 §17.5) |
A confirmed include_roots/exclude entry has disappeared or gone empty since the last discover | Narrowly-scoped, dated ## Re-discovery - <layer> - candidates - <date> section for just that candidate — the rest of the confirmed layer section is untouched (D23 §17.6) |
This skill is colocated with its own scripts/ (per the
ult-context-generate/scripts/md_index.py precedent — scripts live under the
owning skill, not a shared top-level scripts/). It does not read or write
roles/ — "slot" (path-slot, §15.2) and the roles/*.json agent personas are
unrelated concepts that happen to sound similar; see §15.2's terminology note.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

