Install your track and map what it wrote

Day 3: Choose your adventure · Exercise 1 of 3 · 25 min

By the end you will have your chosen framework installed and initialized in day3-work, a fresh copy of your LedgerKit lane, and a five-line ../work/map.md you wrote yourself from the files: where specs live, what creates one, what implements one, what happens after ship, and every file the tool touched outside its own folder.

Setup

Paste one block from the course root. It makes work/ for your answers, copies your lane to day3-work, moves you into it, and copies in the Change C brief and the blank map as ../work/map.md, never over a map you have started. If day3-work already exists, run rm -rf day3-work first: cp -R into an existing folder nests a second copy inside it.

Swift lane
mkdir -p work && cp -R sandbox/swift day3-work && cd day3-work
cp ../day-3-choose-your-adventure/starter/change-c-brief.md .
{ [ -f ../work/map.md ] || cp ../day-3-choose-your-adventure/starter/map-template.md ../work/map.md; }
ls change-c-brief.md ../work/map.md
Kotlin lane
mkdir -p work && cp -R sandbox/kotlin day3-work && cd day3-work
cp ../day-3-choose-your-adventure/starter/change-c-brief.md .
{ [ -f ../work/map.md ] || cp ../day-3-choose-your-adventure/starter/map-template.md ../work/map.md; }
ls change-c-brief.md ../work/map.md

The block prints ../work/map.md and change-c-brief.md. day3-work is the day's project root, the folder holding CLAUDE.md, and your framework is initialized in it. Setup blocks are pasted from the course root; every other Day 3 command runs from inside day3-work; the starters are at ../day-3-choose-your-adventure/starter/ and arrive one file at a time, so your framework never reads its own answer key.

The shipped sandbox/ is never edited, so it is your before-picture: what a track wrote is a diff of this folder against it. Keep two terminal tabs in day3-work, one for the shell and one for Claude Code, started there with claude.

Steps

  1. Confirm the lane is green before anything is installed, and write the count down. It is the baseline the self-check compares against.

    Swift lane
    swift test

    In a fresh copy the line that decides it reads:

    Test Suite 'All tests' passed at ….
    	 Executed 34 tests, with 1 test skipped and 0 failures (0 unexpected) in … (…) seconds

    The baseline is 34 tests, with LEDGER-7 skipped by its gate.

    Kotlin lane
    ./gradlew test

    The line that decides it is BUILD SUCCESSFUL in …. Gradle prints no test count on success, so read it from the test report:

    cat build/test-results/test/TEST-*.xml | grep -c '<testcase '
    grep -o 'skipped="[0-9]*"' build/test-results/test/TEST-ledgerkit.KnownIssueTest.xml

    It prints 85 and then skipped="1": 85 tests, with LEDGER-7 skipped by its gate.

Steps 2 to 4 depend on your track; pick its tab.

OpenSpec

  1. Check the runtime in the shell tab. OpenSpec's package requires Node 20.19.0 or higher.

    node --version

    v20.19.0 or higher passes. Anything lower: upgrade Node or switch tabs; the offline snapshot cannot run OpenSpec without it.

  2. Install the CLI and confirm the version.

    npm install -g @fission-ai/openspec@1.13.1
    openspec --version

    It prints 1.13.1, the version this page was checked against on 2026-09-22.

    Can't install globally? Skip this step and put npx -y @fission-ai/openspec@1.13.1 wherever this tab says openspec; step 4's init becomes npx -y @fission-ai/openspec@1.13.1 init --tools claude, which picks Claude Code for you. The /opsx commands call openspec by name, so if Claude reports it missing, tell it to use the same npx form.

  3. Initialize it in day3-work, the folder you are standing in, not the course root. When it asks which tools to set up, choose Claude Code.

    openspec init
    openspec list

    Success is 6 skills and 6 commands in .claude/ in the init summary, then No active changes found. from openspec list, which is what a freshly initialized project looks like.

    The first run also prints a note that OpenSpec collects anonymous usage stats, and writes ~/.config/openspec/config.json holding an anonymous id. That file sits outside day3-work, so step 5's diff cannot see it and neither map will list it unless you add it. To opt out, run export OPENSPEC_TELEMETRY=0 in both tabs before openspec init, restarting claude after it so the /opsx commands inherit it; then init prints no note and writes no file there.

Network blocked?

If npm can reach a local mirror, install the CLI from the tarball in the offline snapshot instead of step 3; its SNAPSHOT.md has the line, and it needs the same Node 20.19.0 or higher. Then run step 4.

Without a mirror, switch tabs now. The /opsx commands call the openspec CLI before they write anything: /opsx:propose's second step runs openspec context --json and stops on any failure, so a copy of the files cannot plan Exercise 2's change.

LID

  1. Nothing to check: LID installs as two Claude Code plugins.

  2. Install, in the Claude Code tab, one line at a time.

    /plugin marketplace add jszmajda/lid
    /plugin install linked-intent-dev@jszmajda-lid
    /plugin install arrow-maintenance@jszmajda-lid

    If an install asks for a scope, choose user scope: the plugins then sit outside day3-work, beyond step 5's diff and the reset.

  3. Initialize with the mapper for an existing codebase. First press Shift+Tab until the status bar reads ⏵⏵ accept edits on, so each draft is written without a separate file approval; the skill's own stops still pause it.

    /arrow-maintenance:map-codebase

    You are mapping, not reviewing: accept its proposals and approve each draft without editing, except that you merge its segments into one. Answer its stops this way:

    • Whole project or specific parts: whole project. Subagents: no.
    • After it reads every file, clusterings through different lenses: take Domain concept if offered, otherwise the first.
    • Slicing: the coarsest it offers.
    • The final clustering, where the skill lets you combine segments: send the line below, and confirm the merge if it asks.
    • For that one segment an arrow doc, a skeleton design doc and an EARS spec file, then the high-level design: approve each.
    Merge these into one segment for the whole module: it is small, and I want three drafts to approve today rather than precise tracking. Treat the merged clustering as approved.

    It then adds its section to CLAUDE.md and ends by telling you to flesh out the skeletons segment by segment. Stop there for today: the reference tree is a skeleton too, and Exercise 2 takes one new segment down the arrow.

    Budget about 20 minutes for this step: about five while it reads every file, a minute or so at each of nine or ten stops, and a few more while it drafts the design doc, the EARS file and the high-level design.

Network blocked?

Skip step 3. Quit Claude Code and start it with the vendored plugins loaded:

claude --plugin-dir ../day-3-choose-your-adventure/starter/vendored/lid-831c1959/lid/plugins/linked-intent-dev \
       --plugin-dir ../day-3-choose-your-adventure/starter/vendored/lid-831c1959/lid/plugins/arrow-maintenance

--plugin-dir loads a plugin from a folder for that session only, so start every Day 3 session this way, step 7's included.

BMAD

  1. Check both runtimes before you install anything. BMAD needs Node.js 20.12 or later, and uv with Python 3.11 or later.

    node --version
    uv --version

    v20.12 or higher passes the first; below v20.12, use the network-blocked box instead of step 3. Any version line passes the second; uv then fetches the Python 3.11 or later that BMAD's scripts declare, which needs the network. command not found for uv means bmad-build and bmad-build-auto will halt in Exercise 2. Install uv now, or switch tabs: the offline snapshot cannot supply it.

  2. Install from day3-work. The installer is also the initialization.

    npx bmad-method install

    It checks uv (Python UV check pass (uv … detected).), then asks for an installation directory pre-filled with the current folder: confirm it ends in day3-work, and confirm it at Install to this directory?. Accept the default modules, decline custom or community modules, choose Claude Code at Integrate with:, and accept the defaults for any configuration questions that follow. Success is claude-code configured: 29 skills → .claude/skills.

  3. Confirm the skills landed in this project.

    ls .claude/skills | grep -c '^bmad-'

    It prints 29.

For step 6, start with _bmad/config.toml and .claude/skills/bmad-build/.

Network blocked?

Copy the install output from the offline snapshot instead of steps 3 and 4, then count the skills it copied:

Swift lane
cp -R ../day-3-choose-your-adventure/starter/vendored/bmad-6.12.0/project-files/. .
mkdir -p _bmad-output
ls .claude/skills | grep -c '^bmad-'
Kotlin lane
cp -R ../day-3-choose-your-adventure/starter/vendored/bmad-6.12.0/project-files/. .
mkdir -p _bmad-output
ls .claude/skills | grep -c '^bmad-'

It prints 29: the skills are all there. uv is still yours to provide, and so is a Python 3.11 or later for it to find: the snapshot's scripts declare requires-python = ">=3.11", and uv cannot download one offline.

  1. Take the after-picture in the shell tab. -r compares the folders recursively, -q names each difference without printing it, and each -x leaves out a build-output folder. .claude is not left out, because two of the three frameworks write there.

    Swift lane
    diff -rq -x .build -x build -x .gradle -x .kotlin ../sandbox/swift .

    To see a modified CLAUDE.md in full:

    diff -u ../sandbox/swift/CLAUDE.md CLAUDE.md
    Kotlin lane
    diff -rq -x .build -x build -x .gradle -x .kotlin ../sandbox/kotlin .

    To see a modified CLAUDE.md in full:

    diff -u ../sandbox/kotlin/CLAUDE.md CLAUDE.md

    Only in .: <name> is a file or folder the framework created (find <name> -type f lists a folder's files), and Only in ./.claude: <name> is one it added inside the .claude folder the lane ships with its settings.json; Files ../sandbox/…/<path> and ./<path> differ is an edit to a file the lane shipped; Only in .: change-c-brief.md is yours.

  2. Open the files the diff named and answer the five questions in ../work/map.md, one line under each, not visible where no file you opened answers it. Do not paste the tool's README. The lane's own specs/ folder is the course's, not the framework's; it is not in the diff, so it does not answer line 1.

  3. Now ask the agent the same five questions in a fresh session: quit Claude Code and start claude again in day3-work. Paste your lane's prompt.

    Swift lane
    Run diff -rq -x .build -x build -x .gradle -x .kotlin ../sandbox/swift . and read only the files it reports: a "Files … differ" line is a modified file; an "Only in" line is new, and for a new folder list its files with find, then read the ones that bear on the questions below. Skip change-c-brief.md, which I copied in. Do not read Sources/, src/, or any test folder. Answer in exactly five lines, no preamble and no closing remark: (1) the directory where specification files live, as a path; (2) the exact command that creates a new specification; (3) the exact command that implements one; (4) what happens to a specification after its change ships, in one clause; (5) every file you found that was created or modified outside the framework's own folder (openspec/, docs/, or _bmad/ and _bmad-output/), counting .claude/ as outside, as a list of paths, or the word none. If you cannot answer a line from a file you actually read, write "not visible" for that line.
    Kotlin lane
    Run diff -rq -x .build -x build -x .gradle -x .kotlin ../sandbox/kotlin . and read only the files it reports: a "Files … differ" line is a modified file; an "Only in" line is new, and for a new folder list its files with find, then read the ones that bear on the questions below. Skip change-c-brief.md, which I copied in. Do not read Sources/, src/, or any test folder. Answer in exactly five lines, no preamble and no closing remark: (1) the directory where specification files live, as a path; (2) the exact command that creates a new specification; (3) the exact command that implements one; (4) what happens to a specification after its change ships, in one clause; (5) every file you found that was created or modified outside the framework's own folder (openspec/, docs/, or _bmad/ and _bmad-output/), counting .claude/ as outside, as a list of paths, or the word none. If you cannot answer a line from a file you actually read, write "not visible" for that line.

    Paste its answer under ## The agent's five lines in ../work/map.md. Where the two blocks disagree, trust the diff.

  4. Run the self-check below. Installing a framework must not change a test result.

If you see something else instead

Likely wrong turns

Swift lane

diff -ru ../sandbox/swift/Sources Sources and diff -ru ../sandbox/swift/Tests Tests show what the install wrote into source and tests. Only in .: swift means setup nested a copy in an existing day3-work: reset, which leaves ../work/ alone. error: PCH was compiled with module cache path '…/sandbox/swift/.build/…' in step 1: run rm -rf .build and test again.

Kotlin lane

diff -ru ../sandbox/kotlin/src src shows what the install wrote into source. Only in .: kotlin means setup nested a copy in an existing day3-work: reset, which leaves ../work/ alone.

Self-check

Three checks decide it

First, the plain suite: your lane's test command from step 1 again, with the same result and the same count. Second, the known-issue run fails on exactly one test, the JPY case.

Swift lane
LEDGER_RUN_KNOWN_ISSUES=1 swift test

The error line names testLEDGER7_JPYRendersWithTwoDecimalPlaces, and the verdict reads:

Test Suite 'All tests' failed at ….
	 Executed 34 tests, with 1 failure (0 unexpected) in … (…) seconds
Kotlin lane
LEDGER_RUN_KNOWN_ISSUES=1 ./gradlew test

The verdict line is BUILD FAILED in …; above it:

KnownIssueTest > LEDGER-7 the JPY row renders with decimal places it should not have() FAILED
…
85 tests completed, 1 failed

Third, every path the diff names appears in your own answers. The mine= line reads sections 1 to 5 of your map, not the agent's block; the loop prints one line per path, ok or MISSING. A folder counts only with its trailing slash, as in openspec/, so naming the tool is not the same as naming its folder.

Swift lane
mine=$(sed -n '/^## 1\./,/^## The agent/p' ../work/map.md)
diff -rq -x .build -x build -x .gradle -x .kotlin ../sandbox/swift . |
  sed -e 's/^Only in \(.*\): \(.*\)$/\1\/\2/' -e 's/^Files .* and \(.*\) differ$/\1/' -e 's|^\./||' |
  grep -vx 'change-c-brief.md' |
  while read -r p; do
    [ -d "$p" ] && p="$p/"
    if printf '%s\n' "$mine" | grep -qF -- "$p"; then echo "ok       $p"; else echo "MISSING  $p"; fi
  done
Kotlin lane
mine=$(sed -n '/^## 1\./,/^## The agent/p' ../work/map.md)
diff -rq -x .build -x build -x .gradle -x .kotlin ../sandbox/kotlin . |
  sed -e 's/^Only in \(.*\): \(.*\)$/\1\/\2/' -e 's/^Files .* and \(.*\) differ$/\1/' -e 's|^\./||' |
  grep -vx 'change-c-brief.md' |
  while read -r p; do
    [ -d "$p" ] && p="$p/"
    if printf '%s\n' "$mine" | grep -qF -- "$p"; then echo "ok       $p"; else echo "MISSING  $p"; fi
  done

Pass is one ok line for each path and no MISSING line. No output at all means the diff found nothing the framework wrote: see the first wrong turn. The framework's own folders (BMAD has two) belong in line 1, the rest in line 5. A modified CLAUDE.md deserves the most attention: it loads into every session, so you pay for it on every turn.

Done when: all three checks pass and ../work/map.md holds your five lines with the agent's five beneath them.

Reference answers

Open only your track's. Your map is right when it matches your own diff; these are one machine's runs, in the Swift lane. Only LID's listing changes on the Kotlin lane, and its reveal says how.

OpenSpec: the reference map, and what install and init wrote

Recorded on the build machine on 2026-09-23 in a fresh copy of the Swift lane, with OpenSpec 1.13.1 and openspec init choosing Claude Code; the build's run on 2026-09-22 wrote the same fifteen files.

1. openspec/specs/ holds the live requirements; a change's delta specs sit
   under openspec/changes/<change>/specs/ until it is archived.
2. /opsx:propose <feature description>
3. /opsx:apply
4. /opsx:archive files the change under openspec/changes/archive/ with a
   date-prefixed name.
5. .claude/commands/opsx/ (six commands) and .claude/skills/openspec-*/
   (six skills); CLAUDE.md not modified, no AGENTS.md.

Step 5's diff prints four lines. Those runs predate the .claude/settings.json the lane now ships, so they printed a single Only in .: .claude line; this is the same set of files diffed against today's lane:

Only in ./.claude: commands
Only in ./.claude: skills
Only in .: change-c-brief.md
Only in .: openspec

Every file inside them, marked by whether it sits in OpenSpec's own folder:

outside  .claude/commands/opsx/apply.md
outside  .claude/commands/opsx/archive.md
outside  .claude/commands/opsx/explore.md
outside  .claude/commands/opsx/propose.md
outside  .claude/commands/opsx/sync.md
outside  .claude/commands/opsx/update.md
outside  .claude/skills/openspec-apply-change/SKILL.md
outside  .claude/skills/openspec-archive-change/SKILL.md
outside  .claude/skills/openspec-explore/SKILL.md
outside  .claude/skills/openspec-propose/SKILL.md
outside  .claude/skills/openspec-sync-specs/SKILL.md
outside  .claude/skills/openspec-update-change/SKILL.md
inside   openspec/changes/archive/.gitkeep
inside   openspec/config.yaml
inside   openspec/specs/.gitkeep

The published sources disagree about whether openspec init writes into AGENTS.md or CLAUDE.md. Both runs recorded here wrote into neither, which is two runs of one version on one machine, not a ruling. Record what your own diff showed rather than matching this listing: if it shows a CLAUDE.md line, that line is your finding.

LID: the reference map, the design tree, and the section it added to CLAUDE.md

Observed on the build machine on 2026-09-22 in a copy of the Swift lane, and not re-run for this page, because the mapping run needs a person to answer it. What LID writes into CLAUDE.md could not be seen during research; this is the first observation of it. In that run the plugin's own skill body did not load and the agent worked from the command's description, so the tree is LID-shaped rather than LID-generated. An interactive run where the skill loads may write, name and group the files differently: its skill file, for one, asks for an arrow doc per segment at docs/arrows/<segment>.md, which this tree does not have. On the Kotlin lane, the diff line names ../sandbox/kotlin/CLAUDE.md and the mapper reads src/.

1. docs/intent/<segment>/<segment>-specs.md, beside <segment>-design.md;
   the high-level design is docs/high-level-design.md.
2. No command of its own: the ## LID section sends any change through the
   linked-intent-dev skill, down the arrow from the HLD to the leaf design
   doc to its EARS specs. (/linked-intent-dev:update-lid is for drift and
   mode changes, not features.)
3. The same linked-intent-dev walk continues down the arrow: tests, then
   code.
4. It stays in place, its requirement marked [x] implemented;
   /arrow-maintenance:arrow-maintenance audits it against the code later.
5. CLAUDE.md, modified: a ## LID section appended at the end.

The tree, all of it inside LID's own docs/ folder: thirteen documents, five leaves, 63 requirement IDs, and nothing under Sources/ or Tests/. That run kept its five segments; step 4 has you merge yours, so your tree has one leaf.

docs/high-level-design.md
docs/arrows/index.yaml
docs/arrows/README.md
docs/intent/transaction/transaction-{design,specs}.md
docs/intent/money/money-{design,specs}.md
docs/intent/ledger-filter/ledger-filter-{design,specs}.md
docs/intent/ledger-summary/ledger-summary-{design,specs}.md
docs/intent/golden-fixture/golden-fixture-{design,specs}.md

Outside it, one file: CLAUDE.md, with 29 lines appended and no marker comments, the heading being the only marker. In step 5's diff that is three lines, reproduced here with placeholder files in the same places:

Files ../sandbox/swift/CLAUDE.md and ./CLAUDE.md differ
Only in .: change-c-brief.md
Only in .: docs

The appended section, verbatim from that run:

## LID

- Mode: Full
- Version: 1.3.0
- Prefix: LK
- HLD: `docs/high-level-design.md`
- Leaf design docs and EARS specs: `docs/intent/<segment>/<segment>-design.md`
  and `docs/intent/<segment>/<segment>-specs.md`
- Arrow overlay: `docs/arrows/index.yaml`

This module is under linked-intent development. Any change that could touch
code walks the arrow: HLD, then the owning leaf design doc, then its EARS
specs, then tests, then code. Bug fixes included. Consult the
`linked-intent-dev` skill before editing anything under `Sources/` or
`Tests/`.

Spec IDs are `LK-<LEAF>-NNN` with leaves `TXN`, `MONEY`, `FILTER`, `SUMMARY`
and `FIXTURE`. IDs are stable: revise text, never renumber, never reuse a
deleted ID.

The code and tests carry no `@spec` annotations. Until a change adds them,
`docs/arrows/index.yaml` is the only spec-to-code and spec-to-test linkage:
update it in the same change as any spec, test or entry point it names, and
run `/arrow-maintenance` afterwards.

The LID documents are a brownfield skeleton mapped 2026-09-22. Rationale
marked `[inferred]` was read from the code, not authored; confirm or correct
it before building on it, and drop the marker when you do.

Yours will name your own mapping date and may word the rest differently. Read it as standing instructions you now pay for on every turn.

BMAD: the reference map, and what the installer wrote

Recorded on the build machine on 2026-09-23 in a fresh copy of the Swift lane with BMAD 6.12.0, using the installer's non-interactive spelling with the default module and Claude Code, the same command the build log used on 2026-09-22; both wrote 235 files. The installed footprint is not documented in the sources this course cites, so this listing is one observation on one machine, not a specification.

1. _bmad-output/, created empty; bmad-build's own step file names each spec
   _bmad-output/implementation-artifacts/spec-<slug>.md. On the build
   machine on 2026-09-22, handed Exercise 2's prompt and told to halt after
   planning, it wrote a statement-periods/ folder there instead (SPEC.md,
   stories.yaml, stories/). _bmad/ holds the framework, its scripts and its
   layered config.
2. /bmad-build "<what you want built>"
3. /bmad-build itself: once you approve its plan, the same run implements it;
   larger work runs bmad-sprint-planning, then bmad-build, then
   bmad-code-review.
4. It stays where it was written, and the status in its frontmatter moves
   to done.
5. .claude/skills/bmad-*/ (29 skill folders); CLAUDE.md not modified,
   no AGENTS.md.

Step 5's diff prints four lines. Those runs predate the .claude/settings.json the lane now ships, so the first line read Only in .: .claude; this is the same set of files diffed against today's lane:

Only in ./.claude: skills
Only in .: _bmad
Only in .: _bmad-output
Only in .: change-c-brief.md

Outside BMAD's own folders: .claude/skills/, holding 29 bmad-* skill folders and 214 files. Inside them, _bmad-output/, empty, and _bmad/, 21 files, listed here to two levels deep (each v6-shims/ folder holds one README.md):

_bmad/_config/bmad-help.csv
_bmad/_config/files-manifest.csv
_bmad/_config/manifest.yaml
_bmad/_config/skill-manifest.csv
_bmad/bmm/config.yaml
_bmad/bmm/module-help.csv
_bmad/bmm/v6-shims/
_bmad/config.toml
_bmad/config.user.toml
_bmad/core/config.yaml
_bmad/core/module-help.csv
_bmad/core/v6-shims/
_bmad/custom/.gitignore
_bmad/custom/config.toml
_bmad/custom/config.user.toml
_bmad/render/.gitignore
_bmad/scripts/config_utils.py
_bmad/scripts/memlog.py
_bmad/scripts/render_skill.py
_bmad/scripts/resolve_config.py
_bmad/scripts/resolve_customization.py

Four of those are the layered configuration, read in this order: _bmad/config.toml, config.user.toml, custom/config.toml, then custom/config.user.toml.

No AGENTS.md appeared, and no <!-- bmad:context --> region. That marker comes from search synthesis, not a page this course fetched. On the build machine it turned up only inside the bmad-project-context skill's own template, which writes the region when you invoke that skill by name. If your diff does show an AGENTS.md, open it and record what is in it.

Reset

Quit Claude Code, then from inside day3-work run your lane's line. It re-copies the shipped lane, leaves you inside it, and leaves ../work/ untouched.

Swift lane
cd .. && rm -rf day3-work && cp -R sandbox/swift day3-work && cd day3-work
Kotlin lane
cd .. && rm -rf day3-work && cp -R sandbox/kotlin day3-work && cd day3-work

Then re-run the setup block's copy lines (everything after its cd day3-work) and your track's install-and-init steps. OpenSpec on your PATH and the LID plugins at user scope survive the reset (without a global OpenSpec install, use the npx form again); BMAD lives in the project, so you run its installer again. Do not reset when you have finished: Exercise 2 runs Change C through the framework installed here.

Sources