How to guide large-scale refactors and reorganizations using explicit, versioned rules instead of tribal memory
Every repository tells a story. The problem is, most of that story is buried in commit logs, scattered Jira tickets, and the heads of two engineers who happen to remember why utils_v2_FINAL.py exists alongside utils_v3_backup.py.
Over time, repos accumulate entropy. Modules overlap. Naming conventions diverge. Artifacts live in a gray zone between “deprecated” and “someone might still need this.” New contributors can’t tell which module is canonical. Downstream systems can’t rely on stable interfaces. And the longer this goes on, the harder it becomes to refactor without breaking something — or someone’s assumptions.
This article introduces a pattern we call history-aware steering — a way to treat your repository as a governed knowledge system by encoding its organizational rules, naming conventions, and lifecycle states into a durable, versioned steering file. When combined with Kiro, AWS’s spec-driven agentic IDE, these rules become machine-readable contracts that both humans and AI agents can reason over.
The Entropy Problem
Consider a repository that has been under active development for two years. A new engineer opens the project and sees:
project/
├── api/
│ ├── routes.py
│ ├── routes_new.py
│ └── routes_v2_draft.py
├── models/
│ ├── core.py
│ ├── core_backup.py
│ └── graph_experimental.py
├── config/
│ ├── prod.yaml
│ ├── prod_OLD.yaml
│ └── staging_BACKUP.yaml
├── utils/
│ ├── helpers.py
│ ├── helpers_v2.py
│ └── string_utils_deprecated.py
├── scripts/
│ ├── deploy.sh
│ ├── deploy_v3.sh
│ └── cleanup_TODO.sh
└── README.md
Which routes file is the one to use? Is core_backup.py safe to delete? Does graph_experimental.py represent the future or a dead end? The answers exist — but they live in tribal memory, not in the repository itself.

This is a history problem. The repository contains every change ever made, but it lacks an explicit narrative about which assets are canonical, which are historical, and how new ones should be integrated. Git knows what changed. It doesn’t know why something matters now.
What Is a Steering File?
A steering file is a Markdown document — typically placed at .kiro/steering/history.md — that acts as a high-level contract for how the repository is organized and how it should evolve over time.
Think of it as the difference between a library that happens to contain books and a library with a catalog system. The catalog doesn’t move the books — it defines where books go, how they’re classified, and what happens when a new edition replaces an old one.
A history-aware steering file encodes three categories of rules:
### Ordering Rules**Purpose:** Define how related assets are grouped and sequenced**Example:** Group API modules by domain area; within each domain, order by maturity: experimental → canonical → deprecated### Renaming Rules**Purpose:** Enforce consistent naming patterns while preserving traceability**Example:** When a module is renamed, the old name must appear in a `supersedes` annotation in the new file's header### Cataloging Rules**Purpose:** Declare lifecycle state for every tracked asset**Example:** Every module must carry a status: `canonical`, `experimental`, `deprecated`, or `archived`
Anatomy of a Steering File
Here’s what a history.md steering file looks like in practice:
---
inclusion: always
---
# Repository History & Governance Rules## Lifecycle StatesEvery tracked asset in this repository MUST carry one of the following status values: | State | Meaning | |:------------- |:------------------------------------------ | | canonical | Current, authoritative version | | experimental | Under development, not yet promoted | | deprecated | Scheduled for removal, do not extend | | superseded | Replaced by a named successor | | archived | Retained for reference only, read-only | ## Ordering Rules- Group modules by domain: `api/`, `models/`, `config/`, `scripts/` - Within each domain, order by lifecycle state: canonical → experimental → deprecated → archived - New domains require a PR that updates this steering file## Renaming Rules- File renames MUST preserve traceability: - Add `supersedes: <old-path>` to the new file header - Add `superseded-by: <new-path>` to the old file header - Naming pattern: `<domain>/<purpose>.<ext>` - NO version suffixes (_v2, _new, _draft) in canonical files - Experimental files use the `experimental/` subdirectory## Cataloging Rules- The `catalog.yaml` at repo root is the single source of truth - Every file change MUST update the catalog entry - Deprecated assets must include a `deprecated-date` and `migration-path` annotation - Assets with no catalog entry are flagged as untracked## Supersession ProtocolWHEN a new asset replaces an existing canonical asset: 1. The old asset status changes to `superseded` 2. The old asset receives a `superseded-by` pointer 3. The new asset receives a `supersedes` pointer 4. The catalog is updated atomically in the same commit 5. Downstream references are updated within 1 sprint
The inclusion: always frontmatter tells Kiro to load this file into context for every interaction — the same mechanism used for product.md and tech.md steering files. This means every time a contributor works with Kiro, the agent already understands the repo’s organizational contract.
The Catalog: Machine-Readable State
The steering file references a catalog.yaml — a structured index that makes the rules actionable by both humans and automation:
# catalog.yaml — Auto-maintained, validated by CI
schema_version: "1.0" last_updated: "2026-04-13"assets: api/routes.py: status: canonical domain: api description: "Primary API route definitions" owner: "@backend-team" api/routes_v2.py: status: experimental domain: api description: "GraphQL route layer (RFC-042)" supersedes: null target_promotion: "2026-Q3" models/core.py: status: canonical domain: models description: "Core data models and schemas" owner: "@data-team" models/core_backup.py: status: archived domain: models description: "Pre-migration backup" superseded_by: models/core.py archived_date: "2026-01-15" utils/helpers_v2.py: status: deprecated domain: utils description: "Legacy string utilities" deprecated_date: "2026-03-01" migration_path: "Use utils/helpers.py instead" removal_target: "2026-06-01"
This separation is intentional. The steering file describes policy (“every asset must have a status”). The catalog describes state (“this specific asset is deprecated as of March 1”). Together, they form a complete governance layer.
How Kiro Uses Steering Rules
When history-aware steering is combined with Kiro’s agentic capabilities, three powerful workflows emerge.

Guided Placement
When a contributor proposes adding a new module — say, a new data transformer — Kiro reads the steering rules and can suggest:
- Where it should live based on domain grouping rules
- What it should be named based on naming conventions
- What status it should carry (likely
experimentalfor a new addition) - Whether it supersedes anything by checking the catalog for overlapping functionality
This turns a “where should I put this?” Slack question into a deterministic, rule-driven answer.
Supersession Handling
When a file or component supersedes another, the steering rules describe exactly what happens:
Contributor: "I'm replacing the REST routes with GraphQL."
Kiro (reading history.md):
1. api/routes.py → status: superseded
superseded_by: api/graphql_routes.py
2. api/graphql_routes.py → status: canonical
supersedes: api/routes.py
3. catalog.yaml updated
4. Flagging 3 downstream imports for migration
No one has to remember the protocol. It’s encoded in the steering file, and Kiro executes it.
Periodic Gardening
The most valuable workflow may be the least glamorous: automated housekeeping. A Kiro agent (or a CI job reading the steering rules) can periodically scan the repository and surface:
- Orphaned files — present in the repo but missing from the catalog
- Naming violations — files with
_v2,_backup, or_OLDsuffixes in canonical directories - Stale deprecations — assets past their
removal_targetdate - Untracked experiments — files in
experimental/with no promotion timeline
This is the “gardening” that keeps a codebase healthy but rarely gets prioritized. With steering rules, it becomes automatable.
Asset Lifecycle
The lifecycle states defined in the steering file form a directed graph. Assets progress through defined transitions, and each transition has clear rules:

### experimental → canonical **Trigger:** Promotion review approved **Required Actions:** Update status, move to canonical directory, update catalog ### canonical → deprecated **Trigger:** Replacement identified or feature sunset **Required Actions:** Set `deprecated_date`, define `migration_path`, notify dependents ### canonical → superseded **Trigger:** New asset replaces this one **Required Actions:** Set `superseded_by`, update successor's `supersedes` field ### deprecated → archived **Trigger:** Past removal target date **Required Actions:** Move to `archive/`, set read-only, retain for reference ### superseded → archived **Trigger:** Migration complete, no remaining references **Required Actions:** Move to `archive/`, update catalog
The key insight: these transitions are not suggestions — they are rules. By encoding them in the steering file, you make lifecycle management a first-class concern rather than an afterthought.
Scaling Beyond a Single Expert
The deepest value of history-aware steering isn’t automation — it’s knowledge transfer.
In most projects, organizational knowledge lives in the heads of one or two senior engineers. They know that helpers_v2.py is actually the canonical version despite its name. They know that the experimental/ directory is actually production-critical for Client X. They know which config files are templates and which are live.
When those engineers leave, that knowledge evaporates. When the team grows, it doesn’t replicate.
A steering file changes this dynamic:
┌──────────────────────────────────────────────────┐
│ │
│ BEFORE: Knowledge lives in people │
│ │
│ Senior Dev ──> "I just know where │
│ things go" │
│ │
│ New Dev ──> "I'll ask in Slack... │
│ nobody answered" │
│ │
│ Kiro Agent ──> "I'll guess based on │
│ file proximity" │
│ │
├──────────────────────────────────────────────────┤
│ │
│ AFTER: Knowledge lives in the repo │
│ │
│ Senior Dev ──> "I update history.md │
│ when things change" │
│ │
│ New Dev ──> "I read the steering │
│ file and catalog" │
│ │
│ Kiro Agent ──> "I follow the rules in │
│ history.md" │
│ │
└──────────────────────────────────────────────────┘
The rules evolve with the project through normal change processes — pull requests, code reviews, and version control. The “institutional memory” is captured in a form that Kiro can reason over and that humans can audit.
Integration Patterns
A history-aware steering file sits between the raw repository and its downstream consumers. Here’s how it connects:

Documentation Generation
A docs generator can read catalog.yaml to produce an always-current inventory of the project’s modules, their statuses, and their relationships. No more manually maintained “architecture overview” pages that go stale after the first sprint.
# Example: Generate a status report from the catalog
import yaml
def generate_status_report(catalog_path: str) -> dict: """Reads the catalog and produces a summary by status.""" with open(catalog_path) as f: catalog = yaml.safe_load(f) report = {} for path, meta in catalog["assets"].items(): status = meta["status"] report.setdefault(status, []).append({ "path": path, "description": meta.get("description", ""), "owner": meta.get("owner", "unassigned"), }) return report# Output: # { # "canonical": [ # {"path": "api/routes.py", ...}, # {"path": "models/core.py", ...} # ], # "experimental": [...], # "deprecated": [...], # }
CI Pipeline Validation
A CI step can validate that every committed file has a catalog entry and that no naming violations exist:
# .github/workflows/steering-check.yml
name: Steering Rules Validation on: [pull_request]jobs: validate-catalog: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check catalog completeness run: | python scripts/validate_catalog.py \ --catalog catalog.yaml \ --steering .kiro/steering/history.md \ --fail-on-orphans \ --fail-on-naming-violations - name: Check lifecycle compliance run: | python scripts/check_lifecycle.py \ --catalog catalog.yaml \ --warn-stale-deprecations \ --fail-on-expired-removals
Agent-Driven Refactoring
A Kiro agent (or any LLM-based agent) can use the steering rules to propose refactor plans:
## Agent Refactor Proposal
Based on `.kiro/steering/history.md` rules:### Violations Found 1. `utils/helpers_v2.py` — naming violation: version suffix in canonical directory → Recommend: rename to `utils/helpers.py`, supersede current `utils/helpers.py` → archive2. `scripts/cleanup_TODO.sh` — untracked: no catalog entry exists → Recommend: add to catalog as `experimental` or remove if no longer needed3. `config/prod_OLD.yaml` — stale deprecation: deprecated 2025-11-01, removal target 2026-02-01 → Recommend: archive immediately (60 days overdue)### Estimated Impact - 3 files affected - 1 downstream import to update - 0 breaking changes (all superseded paths retained)
Getting Started
If you’re running Kiro today, adding history-aware steering takes about 30 minutes:
Step 1: Create the steering file
mkdir -p .kiro/steering
touch .kiro/steering/history.md
Add the lifecycle states, ordering rules, renaming rules, and cataloging rules relevant to your project. Start minimal — you can always add rules as patterns emerge.
Step 2: Bootstrap the catalog
# Generate an initial catalog from your repo structure
find . -type f \
-not -path './.git/*' \
-not -path './node_modules/*' \
-not -path './.kiro/*' \
| python scripts/bootstrap_catalog.py > catalog.yaml
Review the generated catalog manually. Mark statuses. Identify assets that should be deprecated or archived. This is the one-time “archaeology” step — after this, the catalog evolves incrementally.
Step 3: Wire up validation
Add a CI check that validates the catalog against the steering rules on every PR. Start with warnings, then graduate to failures as the team builds confidence.
Step 4: Let Kiro enforce it
Because the steering file uses inclusion: always, Kiro will automatically incorporate these rules into every interaction. When a contributor asks Kiro to add a new module, Kiro will consult the steering rules before suggesting placement, naming, and status.
When to Use This Pattern
History-aware steering is not limited to application code. The pattern applies wherever assets accumulate and organizational clarity degrades over time:
### Application code **What Gets Steered:** Modules, services, components **Example Rule:** API controllers live in `api/<domain>/`; status tracked in catalog ### Infrastructure-as-code **What Gets Steered:** Terraform modules, CloudFormation stacks **Example Rule:** Deprecated modules must have a migration runbook before archival ### Data pipelines **What Gets Steered:** ETL jobs, schema definitions, DAGs **Example Rule:** DAGs follow `<source>_to_<target>_<frequency>` naming; no orphan DAGs ### Ontology libraries **What Gets Steered:** RDF schemas, SHACL shapes, OWL classes **Example Rule:** Namespace changes require a `supersedes` triple in the successor ### Documentation **What Gets Steered:** Specs, ADRs, runbooks **Example Rule:** ADRs are append-only; superseded ADRs link forward to their replacement ### ML/AI projects **What Gets Steered:** Model configs, training scripts, datasets **Example Rule:** Models carry version + lineage; only `canonical` models deploy to prod
Conclusion
Repositories are living systems. Left ungoverned, they drift toward entropy — overlapping modules, inconsistent naming, and artifacts whose status no one can confirm without a Slack thread.
History-aware steering files offer a lightweight but powerful corrective. They encode organizational intent in a form that is:
- Human-readable — any contributor can open
history.mdand understand the rules - Machine-consumable — Kiro and CI pipelines can enforce them automatically
- Versioned — rules evolve through PRs, just like code
- Durable — institutional knowledge survives team turnover
When combined with Kiro’s spec-driven development model, history-aware steering transforms a repository from a pile of files into a governed knowledge system — one where every asset has a clear status, every change follows a contract, and every contributor (human or agent) knows the rules.
The best time to add steering to your repo was six months ago. The second best time is your next commit.
Traey Hatch is the CEO of New Math Data, an AWS Professional Services Consulting Partner based in Tulsa, Oklahoma. New Math Data specializes in data architecture, semantic web technologies, and AI agent orchestration for enterprise clients.