Overview
Goal
Stand up a complete, reproducible personal development forge from an empty machine profile -- a
versioned Neovim config repo you can git clone and use to edit, navigate, search, and run code with
LSP + Treesitter -- and prove editing fluency by driving a scripted refactor in it with no
mouse, no arrow keys. This is the Pass-0 boundary capstone: it integrates topics 1-3 (Just Enough
Nvim, Just Enough Lua, Extending Neovim) into one artifact you actually keep using for the rest of this
journey, starting with the very next topic (Just Enough Python).
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
flowchart LR
A["Bootstrap<br/>vim.pack.add#40;pinned#41;"]:::blue
B["Open sample<br/>LSP + Treesitter"]:::orange
C["Mouse-free refactor<br/>vimgrep + macro + cdo"]:::teal
D[":terminal check<br/>beside the source"]:::purple
E["Healthcheck<br/>:checkhealth, zero missing dep"]:::brown
A --> B --> C --> D --> E
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef purple fill:#CC78BC,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef brown fill:#CA9161,stroke:#000000,color:#FFFFFF,stroke-width:2px
Concepts exercised
- raw-form editing (topic 1 -- Just Enough Nvim)
- Lua modules, closures, metatables (topic 2 -- Just Enough Lua)
- plugin manager + LSP + Treesitter + a user command + an autocommand (topic 3 -- Extending Neovim)
- a reproducible config repo layout
- the
:terminalbuild/run loop
All colocated code lives under code/: the versioned Neovim config in nvim-config/, the sample
Python project in sample-project/, and the complete launch sequence with real, captured output in
transcript.md. Every listing below is the complete file, verbatim -- nothing on this page is
truncated or paraphrased. The config reuses topic 3's own proven capstone config file-for-file (same
pinned plugin versions, same modules); nothing here is re-derived from scratch.
Step 1: bootstrap the forge
code/nvim-config/init.lua is the single entry point. Its first act is vim.pack.add({...}), the
same built-in, Git-backed plugin manager topic 3 taught, installing three plugins each pinned to an
exact tag or commit hash.
code/nvim-config/init.lua (complete file, content and quoting verbatim; indentation shown as
2-space here for page-display width -- the source file itself is tab-indented per stylua)
-- init.lua -- Pass-0 capstone forge: complete config entry point
-- Reproducible from an empty ~/.config/nvim: copy this whole nvim-config/ tree into
-- $XDG_CONFIG_HOME/nvim (default ~/.config/nvim) and restart Neovim.
-- Reuses the same pinned plugins, require order, and vim.lsp.enable call as
-- topic-03 (Extending Neovim)'s capstone config; comments rewritten for this page.
vim.pack.add({ -- vim.pack: Neovim's built-in, Git-backed plugin manager
{
src = "https://github.com/neovim/nvim-lspconfig",
version = "v2.10.0", -- pinned tag
},
{
src = "https://github.com/neovim-treesitter/nvim-treesitter",
version = "df7489eeea351bece7fd0f9c825be5cb6a1438f0", -- pinned commit (active fork ships no tags)
},
{
src = "https://github.com/neovim-treesitter/treesitter-parser-registry",
version = "6eb15358bb9fc88f0d3401d8538d56652e9bdf3c", -- required companion repo
},
})
require("options")
require("keymaps")
require("lsp")
require("treesitter")
require("plugins.greet").setup()
vim.lsp.enable("pyright")The remaining five modules and one auto-discovered LSP config file -- lua/options.lua,
lua/keymaps.lua, lua/lsp.lua, lua/treesitter.lua, lua/plugins/greet.lua, and
lsp/pyright.lua -- are byte-identical to the files topic 3's own capstone already built and
verified; see
Extending Neovim's capstone for their annotated
listings, or code/nvim-config/ in this folder for the files themselves.
Verify: nvim --headless "+qa" against a totally empty $XDG_CONFIG_HOME seeded only with
code/nvim-config/.
Output (real, unedited -- see transcript.md Step 1 for the full session):
vim.pack: 100% Installing plugins (3/3)
$ echo $?
0Key takeaway: The same vim.pack.add({...}) call that bootstrapped topic 3's capstone bootstraps
this one too -- no separate installer, no bootstrap script, and it exits 0 on a completely fresh
machine profile.
Why it matters: A forge you cannot reproduce from empty isn't actually reproducible -- this step is the proof, not just the claim.
Step 2: open the sample project, verify LSP + Treesitter
code/sample-project/ is a small Python project: greetkit.py (two functions) and
test_greetkit.py (a stdlib unittest suite). Opening greetkit.py in the forge exercises the exact
LSP + Treesitter wiring topic 3 built.
code/sample-project/greetkit.py (complete file, as shipped -- before the Step 3 refactor)
"""greetkit -- a tiny greeting library, the capstone's sample Python project."""
def build_message(nam: str) -> str:
"""Build a greeting message for nam."""
if not nam:
nam = "World"
return f"Hello, {nam}!"
def shout_message(nam: str) -> str:
"""Build an all-caps greeting message for nam."""
return build_message(nam).upper()Verify: open the file, install the Python Treesitter parser once, then inspect the active parser language and the attached LSP client.
Output (real, unedited):
ts_active: true python
lsp_attached: true pyrightKey takeaway: Treesitter reports the buffer's language as python and a real pyright client
attaches -- both driven purely by code/nvim-config/, with zero project-specific configuration beyond
the .py file extension.
Why it matters: This is the payoff of topic 3's "language intelligence" half landing on a fresh project instead of the topic's own scratch file -- proof the forge generalizes.
Step 3: drive a scripted, mouse-free refactor
greetkit.py deliberately names its parameter nam in 8 places across its two functions. The
refactor renames every occurrence to name without touching the mouse or arrow keys: :vimgrep
populates the quickfix list with every match, a macro is defined directly on register q (ciw --
"change inner word" -- is the motion), then :cdo replays that one macro at every quickfix entry --
motions + a macro + the quickfix list, the exact workflow topic 1 (Just Enough Nvim) taught, now
driving a real refactor instead of a single-file demo.
The refactor (three Ex commands, run headless for a scripted, reproducible transcript):
:vimgrep /\<nam\>/g greetkit.py
:let @q = "ciwname\<Esc>"
:cdo normal @q
:wallVerify: count whole-word occurrences of nam before and after.
Output (condensed from the real run's two grep -noE '\<nam\>' greetkit.py | wc -l counts -- see
transcript.md Step 3 for the full session including the quickfix listing):
before: 8
after: 0Key takeaway: :cdo iterates the quickfix list produced by :vimgrep and replays register q's
macro at each of the 8 entries in one command -- no per-occurrence hand-editing, no mouse.
Why it matters: This is the mouse-free workflow's actual payoff -- a multi-occurrence rename that would take 8 manual edits collapses to 4 keystrokes-worth of Ex commands, and because it is scripted, it is provably repeatable: replaying the identical three commands against a fresh copy of the pre-refactor file reproduces the identical post-refactor file, byte for byte -- verified, not merely claimed.
Step 4: run the sample project's check from :terminal
With the refactor landed, test_greetkit.py's three unittest cases exercise the renamed
build_message/shout_message functions, run from a real :terminal job beside the source -- the
DD-17 build/run loop topic 3 already taught.
code/sample-project/test_greetkit.py (complete file)
"""Stdlib unittest suite for greetkit -- the capstone's :terminal check."""
import unittest
from greetkit import build_message, shout_message
class GreetkitTests(unittest.TestCase):
def test_build_message_with_name(self):
self.assertEqual(build_message("Neovim"), "Hello, Neovim!")
def test_build_message_default(self):
self.assertEqual(build_message(""), "Hello, World!")
def test_shout_message(self):
self.assertEqual(shout_message("Neovim"), "HELLO, NEOVIM!")
if __name__ == "__main__":
unittest.main()Verify: :terminal python3 -m unittest test_greetkit -v.
Output (real, unedited -- see transcript.md Step 4 for the full session):
test_build_message_default (test_greetkit.GreetkitTests.test_build_message_default) ... ok
test_build_message_with_name (test_greetkit.GreetkitTests.test_build_message_with_name) ... ok
test_shout_message (test_greetkit.GreetkitTests.test_shout_message) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OKKey takeaway: All three cases pass against the refactored source, run from inside the forge's own
:terminal, with no context switch out of Neovim.
Why it matters: A forge that can only edit isn't a forge -- the :terminal loop is what makes it a
place you can also run and check the code you just refactored, end to end.
Acceptance criteria
- From an empty
$XDG_CONFIG_HOME/nvimseeded only withcode/nvim-config/,nvim --headless "+qa"installs all three pinned plugins and exits0. - Opening
code/sample-project/greetkit.pyshows real Treesitter highlighting (parser languagepython) and a realpyrightLSP client attached. - The scripted
:vimgrep+ macro-register +:cdorefactor renames all 8 occurrences ofnamtonamewith zero mouse/arrow-key input, and replaying the identical commands against a fresh copy of the pre-refactor file reproduces the identical result. python3 -m unittest test_greetkit -v, run from:terminalbeside the source, reports all 3 casesok.nvim --headless "+checkhealth" "+qa"exits0and reports no missing required dependency for any mechanism this config actually declares (vim.pack,vim.lsp, Treesitter).
Pass retrospective / synthesis
Two Cross-Cutting Big Ideas recurred across Pass 0, and this capstone is where they visibly compound:
mechanism-vs-policy-- topic 1 opened with vanilla Neovim as pure mechanism (motions, registers, the quickfix list) with zero built-in policy about how to use them. Topic 3 layered policy on top:vim.pack's pinned versions are a reproducibility policy;vim.lsp.enable('pyright')is a policy choice about which language server backs which filetype. This capstone's Step 3 shows the mechanism (:cdo+ a macro register) staying exactly as raw and reusable as topic 1 left it, while Step 1 and Step 2 show the policy layer (pinned plugins, an LSP config file discovered purely by filesystem location) doing the opinionated work around it.abstraction-and-its-cost-- topic 2 named Lua's tables as one universal abstraction standing in for arrays, maps, objects, and modules, at the cost of no compiler-enforced shape. Topic 3 built on that same table-as-module pattern for every one of its own config modules (options,keymaps,lsp,treesitter,plugins.greet) and for the Language Server Protocol itself, which abstracts away each language's own tooling behind one uniform client/server contract. This capstone'snvim-config/lua/tree is that abstraction paying off directly: five independent modules, each a plain Lua table,require()-d into oneinit.luawith no framework in between.
Explain in your own words (no answer key -- the value is in articulating it, not reading it):
- Where in this capstone's four steps did mechanism (something you could use however you wanted) and policy (a specific opinionated choice) sit closest together, and what would break if you swapped which layer made which decision?
lua/plugins/greet.luaand the Language Server Protocol are both abstractions this capstone relies on. What does each one hide, and what is the one situation where that hidden detail would leak back into view?- If you had to extend this forge for a language other than Python (say, the Bash the very next pass uses), which files would you touch, and which would stay untouched purely because of how this config's abstraction boundaries are drawn?
Done bar
This capstone is runnable end to end: a reader who copies code/nvim-config/ into a fresh
$XDG_CONFIG_HOME/nvim and follows transcript.md's exact commands, in order, reaches the identical
output shown on this page and in transcript.md -- verified against a real, running Neovim v0.12.3
session (not merely described), including a genuine network install of every pinned plugin, a genuine
pyright attachment, a genuine mouse-free refactor whose replay was independently re-verified to
reproduce byte-identical output, and a genuine unittest run from a real :terminal job. The same
honest exception transcript.md's acceptance-criteria section documents for topic 3's own capstone
applies here too: this sandbox's own :checkhealth reports one unrelated tmux $TERM ERROR -- a
setting of the machine running Neovim, not a dependency this config installs or controls. Every
mechanism this capstone combines traces to a primary source already cited in topics 1-3's Accuracy
notes and DD-35 citations; no new fact was needed to write this page, beyond the one new,
independently-verified finding surfaced while re-running :checkhealth here: nvim-lspconfig v2.10.0
now marks its own :checkhealth lspconfig integration deprecated in favor of :checkhealth vim.lsp
(the plugin itself still functions; only its healthcheck hook is being retired).
← Previous: 3 · Extending Neovim Drilling
Last updated July 13, 2026