skills/doc-and-modernize/SKILL.md
Two related workflows for a locally-cloned codebase, in one skill. Documentation mode produces a single, comprehensive, verifiable architecture document primarily by reading files on disk (local-first) — use it whenever the user wants to understand, map, document, research, or onboard onto a codebase ("research this repo", "write up the architecture", "do an architecture deep dive", "document how this codebase works", "map the system design", "create an onboarding doc"). Modernization mode generates a phased plan to modernize, migrate, upgrade, or rewrite a legacy system ("modernize this", "plan the migration", "how would we rewrite this", "how do we get off this legacy stack"); if no architecture document exists yet it first runs Documentation mode, then continues straight through to the plan. It assumes the legacy stack may be dead, runs a time-boxed feasibility spike, and picks the highest achievable rung on a safety ladder instead of demanding a fully-green legacy CI gate up front.
npx skillsauth add williamlimasilva/.copilot doc-and-modernizeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Two complementary workflows for a repository the user already has checked out locally, bundled as one skill:
When in doubt, produce the architecture document first — it is the audited evidence base both modes rely on.
Generate one definitive, cited architecture document for a repository the user already has checked out locally. The goal is a writeup someone could hand to a new engineer as their onboarding reference — broad enough to cover the whole system, deep enough on the hard parts to be useful, and trustworthy because every claim traces back to a file on disk.
Reading from the local checkout (not the GitHub API or the web) is the deliberate
default. It is faster, free, avoids rate limits, and — most importantly — it
describes the exact code in front of you rather than whatever main happens
to look like remotely. The one tradeoff is that remote-only facts (star counts,
full CI run history, sibling repos) aren't visible. That's fine: state those as
out-of-scope or mark them [UNVERIFIED] rather than guessing.
Local-first is not local-never-remote: a web/API lookup is a deliberate
last-resort fallback, reserved for a fact that genuinely cannot be determined
from disk and that materially matters to the document. When you do reach for it,
flag the result clearly (e.g. [UNVERIFIED] / sourced-remotely) so the reader
knows it didn't come from the checkout, and never let it become the easy path
that displaces reading the code on disk.
git remote -v, git branch --show-current,
and git log -1 so the document is anchored to a specific remote, branch, and
commit. A reader must be able to tell which snapshot this describes. Remote
URLs can contain embedded credentials (e.g. https://<token>@github.com/...)
— redact any credentials/tokens from the URL before recording it in the
document.go.mod, package.json,
Cargo.toml, pyproject.toml, pom.xml, etc.), the Makefile/task runner,
CI config, and any repo-specific agent or contributor docs (AGENTS.md,
CONTRIBUTING, README, docs/). These are the source of truth for the tech
stack and commands — prefer them over your prior knowledge of the framework.Produce a single Markdown file with the sections below, in this order. Adapt the headings to the actual project (a CLI tool has no "frontend" lens — fold that slot into whatever matters for that repo), but keep the three-lens shape and the verification discipline.
command | purpose | evidence), verified against the task runner /
manifests / CI config, not guessed. Cover build, run/serve, test (and how to
run a single test), lint, format, and — where they exist — typecheck,
end-to-end/smoke, contract, and any other gate commands, plus the CI
workflow(s) that run them and on what trigger. Also record whether CI is
enforced — i.e. whether any workflow is a required status check /
branch-protection rule that actually blocks merges, versus one that merely
runs — since that distinction is a manual, human-configured setting that
Modernization mode must surface, not assume. Enforcement usually cannot be
determined from the local checkout alone: ask the user, or mark it
[UNVERIFIED] unless confirmed from an authoritative source (any remote
lookup is a flagged last resort, per the local-first rule above). This
inventory is the source of
truth that downstream planning (Modernization mode) cites so its exit
criteria are runnable, not aspirational. Detect these per-ecosystem (npm/yarn/
pnpm, make, just, cargo, go, poetry/tox/nox, gradle/maven,
etc.) — do not assume a stack. Mark any command you could not verify
[UNVERIFIED].Dockerfile/Containerfile,
docker-compose* build contexts), CI runner images / setup-* versions,
engines/.nvmrc/.tool-versions/runtime.txt, serverless/lambda runtimes,
and stateful data-store image tags (DB/cache/broker/search). Cite each with
file+line. This surface is what a later platform/runtime bump must move in
lockstep — flag any drift between build-runtime and run-runtime here so it's
visible before a modernization plan is written.[INFERRED]/[UNVERIFIED] as appropriate. This is
the raw material Modernization mode's feasibility spike and hazard red-team
build on.Identify the 2-3 most complex or architecturally significant subsystems — the parts a new engineer would most struggle with, such as an evaluation/scheduling engine, a plugin loader pipeline, a state machine, or a rendering/migration framework. For each, add a dedicated subsection covering its internal structure, lifecycle or state machine, key types, and data flow, with local file+line citations and a small Mermaid diagram where it clarifies the flow. This is what separates a useful onboarding doc from a directory listing — spend real effort here.
A table of the major claim areas rated High / Inferred / Unverified, so a reader knows exactly which parts to trust outright and which to double-check.
A list of the key local files the document relies on, each with a one-line note on what it establishes.
These are the habits that distinguish this skill's output from a generic overview. They matter because the document's entire value is that a reader can rely on it without re-deriving everything.
pkg/server/server.go#L39-L41). Relative paths from
the repo root keep links clickable.[INFERRED] for something you reasoned to
but didn't see stated, and [UNVERIFIED] for something you're repeating but
didn't confirm (e.g. a build-timing claim from a doc you didn't re-measure).
Honest gaps are more useful than false confidence.[Resolved contradiction] with the explanation. Leaving
a reader to puzzle over a conflict is a failure mode.Keep the document grounded in the checkout. It's easy to drift outward into the project's wider ecosystem (related products, README marketing, satellite repos) — resist that unless it's visible on disk, and clearly label anything that comes from outside the local tree. The reader asked for this codebase, documented faithfully.
Generate a complete, actionable modernization plan for a legacy codebase. This skill focuses on the forward-looking work — what to modernize, why, in what order, and how — but it is self-sufficient: it ensures an architecture document exists first, producing one via Documentation mode when needed.
Assume dead-by-default. People reach for modernization precisely because the old stack is hard or impossible to upgrade — EOL runtimes, uncompilable native modules, retired package mirrors, abandoned frameworks. So this skill does not assume you can resurrect the legacy toolchain and stand up a fully-green CI gate before touching anything. That "freeze-then-lift" approach is the lucky case, not the default. Instead the skill runs a time-boxed feasibility spike, then picks a migration strategy and a safety strategy matched to how alive the system actually is. On a truly dead app, building a green legacy gate is itself a modernization project — a circular trap this skill is designed to avoid.
Three ideas drive the whole plan and are introduced below: the Testability Milestone (when — per component — the system can actually build, run, and pass a test again), the safety ladder (the highest rung of regression safety you can actually reach, with a downgrade treated as a blessed outcome, not a failure), and the CI Milestone (which phase first stands up CI — and the reminder that enforcing CI as a required check is a manual human step, not something the agent can do).
This skill needs an understanding of the system's architecture before it can plan. Resolve that as follows:
The result is two artifacts: the architecture document (the audited evidence base) and this modernization plan (the forward-looking action set).
Before planning, confirm a Commands & Verification Inventory exists. Exit
criteria are only worth anything if they are runnable, so the plan must be able
to cite the project's canonical build / run / test / lint / typecheck / e2e /
contract commands and CI gate(s). Documentation mode produces this
inventory in Part 1; if you're working from a README or prior context that lacks
it, detect and record those commands yourself (per-ecosystem — npm/yarn/pnpm,
make, just, cargo, go, poetry/tox/nox, gradle/mvn, etc.) before
writing exit criteria. Never invent a command you haven't verified against the
task runner / manifests / CI config.
Read the architecture document and extract:
Do NOT re-read every source file. Trust the architecture doc. Only open specific files if a modernization question requires verifying a detail (e.g., "is this dependency actually used or just declared?").
This is the pivot of the whole plan. Do it before recommending a target architecture or writing any phases. Its job is to answer, quickly and honestly: how alive is this system, what migration shape fits, and how much regression safety can we actually achieve?
Put a hard time box on it (e.g. one day). The spike's question is NOT "can we make CI green?" — it is "can we get this to run even once to capture its behavior?" Probe, per component / deployable unit:
Record what you actually observed — including partial success (common in monorepos: one service installs and tests green while another can't compile). Do not sink two sprints resurrecting a corpse to discover it's unresurrectable. When the time box expires, decide with the evidence you have.
"Testable" is not a precondition you can satisfy on a dead app — for a dead app it is an output of modernization, not an input. So every plan must name, up front, its Testability Milestone: the specific phase at which the system (or a given component) first satisfies all four conditions at once:
Assess this per component — as the reference Nylas run showed, a near-modern cloud service can cross the line immediately while a legacy Electron client doesn't cross it until several phases later. This milestone is the single most important marker in the plan. State where it lands for each component, out loud.
The Testability Milestone splits the effort into two regimes with different safety rules:
Rule: never require an automated test gate on a component before that component crosses its own testability line.
The spike decides which of two shapes fits (this is a per-component call in a monorepo):
If a component can't be built or run at all, characterization tests on the old stack are impossible by definition — invert the approach: stop trying to net the corpse and build the net on the target stack incrementally, using the old code/output as the reference oracle.
Regression safety is a ladder, not a binary. Choose the highest rung you can actually reach per component; a downgrade to a lower rung is a blessed, first-class outcome — not a failure — provided the residual risk is named.
Safety is not only tests. There are at least five sources, and on dead apps 2–4 often outweigh 1:
Anchor safety at the behavioral seams, not the unit. The durable, stack-agnostic contracts of any app are its externally observable behaviors: HTTP endpoints, DB schema/queries, file/wire formats, CLI output, protocols. Pin those as golden-master / approval snapshots — they survive the rewrite; unit tests on dead legacy code get deleted the moment you replace the module. Get an oracle, ranked by what's available:
Weigh regression cost when picking a rung: Is the app in production? Does it have real users? Is the old system still runnable as a reference? An abandoned app with zero users has near-zero regression cost — an expensive gate there is over-engineering. A production system with users justifies a higher rung and a real oracle.
Just as the Testability Milestone names when a component can first run and be tested, the CI Milestone names when continuous integration is first stood up — and it must be stated out loud in the roadmap, not buried in a phase body. A common failure of modernization plans is leaving the reader unable to tell which phase introduces CI; name it explicitly.
Rule: CI is stood up in the first lit phase — at or immediately after the component crosses its Testability Milestone — never before. CI cannot be green on a component that cannot yet build/run, so wiring a CI gate during the dark regime is the same category error as demanding a test gate there. (Trivial scaffolding — a lint-only or build-only workflow — may appear earlier, but the authoritative test gate lands at the CI Milestone.)
CI is two distinct steps, and the second is human-only — call both out:
.github/workflows/*.yml, .gitlab-ci.yml,
Azure Pipelines) that runs the gate. An agent can do this.Because step 2 is outside the agent's reach, every plan that stands up CI must emit it as an explicit user action item (in the phase's exit criteria and in §9 Open questions / decisions needed from stakeholders), so the reader knows the gate is not self-enforcing until they turn it on.
Output of Phase 2: for each component — the spike result, the chosen strategy (A or B), the located Testability Milestone, the pre/post label, and the target safety-ladder rung with any residual risk named — plus the plan-wide CI Milestone (which phase stands up CI) and the reminder that enforcing it is a manual human step. Everything downstream depends on these.
The strategic scaffolding above (testability, safety ladder, CI milestone) is
necessary but not sufficient. Modernization plans also fail on tactical,
ecosystem-predictable hazards — an incomplete quarantine set, a forgotten
namespace codemod, a runtime bump that leaves the base images behind, a stateful
data-store major treated as an image bump, an edge rewrite that drops the
anonymous route class. These are so recurring that they have their own catalog:
references/migration-hazards.md (H1–H8).
Rule: before any phase is implemented, red-team its plan against every hazard
in references/migration-hazards.md. For each hazard, ask "does this phase
trigger it?", run the hazard's detection probe against the real repo, and fold
its plan action into the phase's tasks/exit criteria before writing code. A
hazard caught in planning is a task; caught in review it's rework; caught in prod
it's an incident. Record hazards you checked and cleared, not just the ones
that fired — a silent skip is indistinguishable from a miss.
This is the institutionalized form of an independent-critic pass. Do it yourself by walking the catalog, and — where an independent second opinion is available (e.g. a rubber-duck / critique agent) — use it: on the reference PiggyMetrics run such a pass caught a blocking plan gap in every single phase, and each gap was an instance of an H1–H8 class. Bake the outcome into each phase's "Decisions made" and exit criteria so the executing agent inherits the cleared checklist rather than re-discovering the hazard.
Decision framework — always evaluate in this order:
For each component/dependency, work through these levels from least-disruptive to most-disruptive. Stop at the first level that solves the problem:
Upgrade in place — Can you bump the major version and fix breakages? (e.g., Node 14→22, Python 2→3, React 16→19, Rails 5→7). This is the default answer. Only reject it if the upgrade path is officially unsupported, the breaking changes are so extensive they exceed rewrite cost, or the framework itself is abandoned/EOL.
Swap the dependency — If the specific library is dead but the pattern is fine, can you swap to a maintained alternative with the same interface shape? (e.g., Moment.js → date-fns, Request → got/fetch, CoffeeScript → TypeScript with decaffeinate). Prefer drop-in or mechanical migration tools when they exist.
Wrap/adapt (Strangler Fig) — If the component's interface is sound but internals are unmaintainable, can you put a clean interface in front and incrementally replace internals behind it? This works when coupling is manageable and the system can run with old and new coexisting.
Rewrite — Only when: the framework is abandoned with no upgrade path, the architecture fundamentally cannot support required new capabilities (e.g., synchronous-only design that must become real-time), or honest estimation shows upgrade cost exceeds rewrite cost. Require explicit justification for every rewrite recommendation — "it's old" is not sufficient.
Remove — Dead code, features with zero usage, deprecated capabilities superseded by other systems. Verify usage claims before recommending removal.
Bias toward conservatism. The cheapest migration is the one you don't do. Every level of disruption adds risk, timeline, and cost. A working system on an older framework is better than a half-finished rewrite on a new one.
Mechanical-migration riders (apply whenever a recommendation is an upgrade,
swap, or removal). These are the parts of a target-architecture decision that
first-draft plans routinely drop; each maps to a hazard class in
references/migration-hazards.md:
2to3/pyupgrade, etc.) over hand-editing on large
trees. Don't forget the test-framework migration — it is the most-forgotten one.docker-compose build contexts, CI runner images,
engines/.nvmrc/.tool-versions, serverless runtimes — must move in the
same phase. Build-runtime and run-runtime must never drift across a phase
boundary, or the app builds green and then fails to run.Produce a Target Architecture section that includes:
For each recommendation, write an inline ADR:
#### ADR: [Decision Title]
- **Context:** [Why this decision is needed]
- **Decision:** [What we chose and which decision framework level it falls under]
- **Alternatives considered:** [What else was evaluated and why it lost]
- **Consequences:** [Tradeoffs accepted]
For each major feature/domain identified in the architecture doc, produce a section covering:
Produce an ordered, phased plan. Each phase should be independently deployable (no half-migrated states that can't run in production).
Phase gating is regime-aware (applies to every phase). State this rule at the top of the phased plan and honor it in every phase's exit criteria. A phase is not complete until its Verification & Exit Criteria pass. Those criteria must be (a) objectively verifiable; (b) actually executed and recorded before the next phase starts; and (c) gated — do not advance to phase N+1 until phase N's criteria are demonstrably met. But which criteria are valid depends on the component's regime (Phase 2c):
Report to the user any phase whose pass/fail is unknown rather than assuming it passed.
The first phase establishes maximum achievable safety — not a fixed "green legacy gate." Do not hard-code "resurrect everything and make CI green." Shape the first phase from the Phase 2 outputs, per component:
Only after the first phase's criteria are met (at whatever rung) do the subsequent infrastructure and feature phases begin.
Structure each phase as:
### Phase N: [Name] (T-shirt size: M)
**Goal:** [One sentence]
**Regime:** [pre-testability ("dark") | post-testability ("lit")] — per component
**Safety rung:** [L0–L4, with residual risk named if below L4]
**Prerequisites:** [Which phases must complete first]
**Duration estimate:** [Relative, not calendar — e.g., "2-4 sprints"]
#### Tasks
| ID | Task | Component | Blocked by |
|----|------|-----------|------------|
| N.1 | ... | ... | — |
| N.2 | ... | ... | N.1 |
#### Risks & Mitigations
- **Risk:** ... → **Mitigation:** ...
#### Decisions made
- [Every sub-decision this phase depends on, resolved and documented here so the
phase can be implemented without further user input. State "dropped" vs
"deferred" explicitly for anything cut.]
#### Verification & Exit Criteria (Definition of Done)
- [ ] [Regime-appropriate criterion. For LIT phases: a runnable command / green
CI check, citing the actual command from the Commands & Verification
Inventory. For DARK phases: the achievable safety-ladder rung's evidence —
captured seam/oracle snapshots, reversibility proven, smoke checklist
passed — NOT a green test suite the component can't yet run.]
- [ ] [Include a parity/characterization check against the chosen oracle whenever
the change is meant to preserve behavior.]
- [ ] [If purely additive, assert it: "no behavior/dependency/logic changed."]
- [ ] [If exiting below L4, state the residual risk and which later phase closes
it (e.g., "client test gate deferred to the client-migration phase").]
Ordering principles:
Modernization plans fail in execution, not on paper. Bake in the governance that keeps each phase honest and reversible (generic across any ecosystem):
git log origin/<trunk>..HEAD is empty at branch creation.
If controlled stacking is genuinely unavoidable, it is allowed only with a
required reconciliation PR that lands the stack onto trunk and an explicit
residual-risk note — never as a silent default.main) and, if a second default-ish branch exists (master), mark it
"history only — do not target" so phase work never lands on the wrong branch.MODERNIZATION_PLAN.md status markers as each phase's
exit criteria are met — ✅ complete / ⏭️ descoped / 🗑️ dropped — record the
decisions made during implementation, and update the safety-ladder rung /
residual-risk register as components cross their testability lines..github/copilot-instructions.md, the README, and any
module/topology list in the same PR. Treat those doc updates as part of the
phase's Definition of Done, not a follow-up — a stale "quarantined module" list
or a hard-coded old-branch link actively misleads the next agent/human.references/migration-hazards.md (H1–H8) and fold the fixes into tasks
and exit criteria before writing code. Record the cleared checklist in the
phase's "Decisions made"..github/copilot-instructions.md
(create the .github/ directory if needed) from the template at
references/copilot-instructions.template.md — a commands table plus the
regime-aware phase-gate and branch/PR rules — so the executing agent/human has
the canonical commands and gates in the location GitHub Copilot auto-loads. The
user edits it to their own gates. If a .github/copilot-instructions.md already
exists, do not overwrite it — merge the commands/gating/branch sections in, or
write the generated file alongside as
.github/copilot-instructions.modernization.md and tell the user to merge.Document the operational guardrails needed:
/data/db volume needs
down -v + re-seed for a demo, or a staged FCV upgrade for real data.)Produce a primary Markdown file named MODERNIZATION_PLAN.md with:
Also emit a companion .github/copilot-instructions.md (the path GitHub
Copilot auto-loads; create .github/ if absent) from
references/copilot-instructions.template.md, populated with the project's
canonical commands and phase-gating / branch rules, for whoever executes the
plan to edit and adopt. Never overwrite an existing
.github/copilot-instructions.md — merge into it or write a sibling
.github/copilot-instructions.modernization.md and flag it for the user.
[DECISION NEEDED] for genuine
stakeholder/business choices (budget, team size, product direction, timeline
pressure) that block a phase; drive those to resolution with the user during
planning rather than leaving them open in an implementable phase.references/migration-hazards.md and fold the fixes into tasks/exit
criteria first — a hazard caught in planning is a task; in review, rework; in
prod, an incident. These tactical hazards (incomplete quarantine, framework
codemods, runtime/base-image lockstep, route-class enumeration, data-store
upgrade paths, transitional-insecure-state noise, stacked-PR/trunk drift,
living-doc drift) are what strategic scaffolding alone keeps missing.tools
Create a new workshop or use an existing directory as one. Handles two paths: (A) use an existing local directory the operator points at, or (B) create a new private GitHub repo in the signed-in account. Never creates a repo inside another repo.
development
Guide for setting up vcpkg in C++ projects, managing dependency versions, and cross-compiling. Covers manifest initialization, CMake and Visual Studio integration, classic-to-manifest migration, version pinning, baselines, overrides, triplets, and cross-compilation. Use when a user is working with vcpkg project setup, installation, version management, or cross-platform builds. For specialized tasks, additional references cover custom registries and overlay ports (references/registries.md), CI/CD and binary caching (references/ci.md), and troubleshooting and dependency lifecycle (references/troubleshooting.md).
testing
Emit structured agent signals — hands-up, blocked, done, checkpoint, partnership. Signals are written as JSON to .signals/ for dashboard consumption and noted in the journal for persistence.
development
Install and configure Markstream streaming Markdown renderers for Vue, React, Svelte, Angular, Nuxt, and Vue 2 applications. Use for package selection, minimal peer dependencies, CSS order, SSR boundaries, streaming mode, and renderer setup.