# Architecture Decision Records Chronological record of decisions that changed the design described in [`PLAN.md`](./PLAN.md) or the work breakdown in [`TODO.md`](./TODO.md). Each entry records the date, the affected task, and the reason. Before changing an existing decision, read this file and confirm the change does not contradict an earlier one; if it does, get explicit approval first. > Split this file per phase once it exceeds 200 lines. Records added after the initial planning phase (ADR-0001 through ADR-0007, in this file) are kept in a sequence of per-phase files, split each time the previous one approached that limit - check the latest one first for the most recent decisions: | File | ADRs | |---|---| | [`adr-phase-2.md`](./adr-phase-2.md) | ADR-0008 – ADR-0013 | | [`adr-phase-3.md`](./adr-phase-3.md) | ADR-0014 – ADR-0017 | | [`adr-phase-4.md`](./adr-phase-4.md) | ADR-0018 – ADR-0019 | | [`adr-phase-5.md`](./adr-phase-5.md) | ADR-0020 – ADR-0025 | | [`adr-phase-6.md`](./adr-phase-6.md) | ADR-0026 – ADR-0033 | | [`adr-phase-7.md`](./adr-phase-7.md) | ADR-0034 – ADR-0036 | | [`adr-phase-8.md`](./adr-phase-8.md) | ADR-0037 | | [`adr-phase-9.md`](./adr-phase-9.md) | ADR-0038 – ADR-0046 | | [`adr-phase-10.md`](./adr-phase-10.md) | ADR-0047 – ADR-0048 | --- ## ADR-0001 — Use MSBuild instead of CMake - **Date**: 2026-07-25 - **Affected**: `TODO.md` M0, `PLAN.md` §4 / §5, `AGENTS.md` §3 / §4, `README.md`, `CONTRIBUTING.md` - **Status**: Accepted ### Decision The project is built with **MSBuild** (`.sln` + `.vcxproj`), not CMake. No `CMakeLists.txt`, `CMakePresets.json`, or `vcpkg.json` is introduced. ### Reason - The maintainer develops in **Visual Studio 2026** and wants the solution to be the primary, first-class artifact rather than a generated by-product. - The original template only ever *described* a CMake build; no `CMakeLists.txt` was ever written, so there is no migration cost — this is a greenfield choice, not a conversion. - MSBuild is the native project format for the C++/WinRT and Windows SDK integration this project depends on, and removes a layer of indirection when configuring the app manifest, per-architecture toolsets, and the MSTest integration. ### Consequences - Every documented build/test command changes from `cmake` / `ctest` to `msbuild` / `vstest.console.exe`. - Build output paths change. To keep a single `.gitignore` entry effective and keep the documented run paths uniform, output is redirected into `build\\\` rather than MSBuild's default `\\`. - Shared settings are centralised in `Directory.Build.props` (properties) and `props\syncwingetlink.common.props` (compiler/linker `ItemDefinitionGroup`), so the three project files stay thin. ### Verified environment (checked 2026-07-25 on the maintainer's machine) | Item | Result | |---|---| | Visual Studio | 2026 Enterprise, install version **18** | | VC targets directory | `MSBuild\Microsoft\VC\v180` | | Platform toolset | **v145**, present for both `x64` and `ARM64` | | MSVC toolchain | 14.51.36231 (also 14.50.35717, 14.44.35207) | | ARM64 cross tools | `VC\Tools\MSVC\14.51.36231\bin\Hostx64\arm64` present | | Windows SDKs | 10.0.22621.0, **10.0.26100.0**, 10.0.28000.0 | This resolves a contradiction in the original template, which simultaneously claimed "MSVC toolset v145" and "Visual Studio 2022/2026". **v145 requires Visual Studio 2026** (VS2022 provides v143), so VS2022 is no longer listed as a supported build environment. --- ## ADR-0002 — Use MSTest (C++) and split the core into a static library - **Date**: 2026-07-25 - **Affected**: `TODO.md` M0, `PLAN.md` §5, `AGENTS.md` §3, `CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md` - **Status**: Accepted ### Decision Unit tests use the **Microsoft Unit Testing Framework for C++** (`CppUnitTest.h`, namespace `Microsoft::VisualStudio::CppUnitTestFramework`), replacing the template's undecided "Catch2 or GoogleTest". Tests are run with `vstest.console.exe` and appear in the Visual Studio Test Explorer. Consequently the source tree is split into **three projects**: | Project | Type | Contents | |---|---|---| | `src\syncwingetlink.core.vcxproj` | StaticLibrary | `cli\`, `core\`, `rules\`, `tui\` — all domain logic | | `src\syncwingetlink.vcxproj` | Application | `main.cpp` only; links the core library | | `tests\syncwingetlink.tests.vcxproj` | DynamicLibrary | MSTest cases; links the core library | ### Reason - The maintainer standardises on MSTest. - A C++ MSTest project **must be a DLL** — that is how the test adapter loads it. A DLL cannot link the object files of an EXE, so the original single-executable layout in `PLAN.md` §5 could not be unit-tested at all. Extracting the logic into a static library that both the EXE and the test DLL link is the minimal fix. - The split also enforces the layering rule already stated in `AGENTS.md` §3: `cli/` and `tui/` are thin presentation layers over `core/`, and `core/` must stay independently testable. ### Verified environment (checked 2026-07-25) | Item | Result | |---|---| | Headers | `VC\Auxiliary\VS\UnitTest\include\CppUnitTest.h` | | Libraries | `VC\Auxiliary\VS\UnitTest\lib\{x64,ARM64,x86}\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.lib` | | Test runner | `Common7\IDE\Extensions\TestPlatform\vstest.console.exe` | | Native adapter | `...\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension.dll` | Note for whoever writes the test `.vcxproj`: `CppUnitTestCommon.h` links the framework with `#pragma comment(lib, "x64\\Microsoft...lib")` — the architecture subdirectory is part of the pragma. `LibraryPath` must therefore point at the **parent** directory `$(VCInstallDir)Auxiliary\VS\UnitTest\lib`, not at the per-architecture folder. ### Consequences - `PLAN.md` §5 gains a third project and an explicit statement of why the core is a static library. - Adding a source file now requires updating both the `.vcxproj` and its `.vcxproj.filters`. - ARM64 test **execution** requires an ARM64 host; see ADR-0004. --- ## ADR-0003 — CRT linkage is a build-time switch, defaulting to the dynamic CRT - **Date**: 2026-07-25 - **Affected**: `TODO.md` M0 / M8 - **Status**: Accepted ### Context `TODO.md` M8 wants releases to ship "an unsigned single exe (static link)", which means compiling with `/MT`. But all three projects share one static core library, and a static library's CRT choice is recorded in its object files: linking a `/MT` core into a `/MD` test DLL (or vice versa) produces `LNK2038`. ### Decision Introduce a `StaticRuntime` MSBuild property, defaulting to `false` (dynamic CRT, `/MD` and `/MDd`). Release packaging opts in explicitly: ```powershell msbuild syncwingetlink.sln -p:Configuration=Release -p:Platform=x64 ` -p:StaticRuntime=true -t:syncwingetlink ``` Normal development builds use the dynamic CRT. This repo has no CI workflow yet (see issue #21); once CI is added it should build with the same dynamic-CRT default as local development. Release packaging opts into the static CRT explicitly. The MSTest project supports either setting, which lets a release validation build use the same CRT as the shipping executable when required. ### Verification The x64 smoke-test DLL was rebuilt and executed successfully with both `/MTd` (Debug) and `/MT` (Release) using the Visual Studio 2026 Desktop C++ Unit Test framework. Therefore the framework library is compatible with the project's static-CRT test builds. The default remains dynamic because it is the conventional development configuration; the static setting remains an explicit release-packaging choice. Commands used: ```powershell msbuild syncwingetlink.sln -p:Configuration=Debug -p:Platform=x64 -p:StaticRuntime=true -m vstest.console.exe build\x64\Debug\syncwingetlink.tests.dll /Platform:x64 msbuild syncwingetlink.sln -p:Configuration=Release -p:Platform=x64 -p:StaticRuntime=true -m vstest.console.exe build\x64\Release\syncwingetlink.tests.dll /Platform:x64 msbuild syncwingetlink.sln -p:Configuration=Release -p:Platform=ARM64 -p:StaticRuntime=true -m ``` x64 Debug `/MTd` and Release `/MT` each passed one of one smoke tests. ARM64 Release `/MT` cross-built with zero warnings and errors and was **not** run on the x64 host — see open item 3. --- ## ADR-0004 — Flat `src\` layout; two project files share the `src` directory - **Date**: 2026-07-25 - **Affected**: `PLAN.md` §5, `AGENTS.md` §3 - **Status**: Accepted ### Decision Project files live at `src\syncwingetlink.core.vcxproj` and `src\syncwingetlink.vcxproj`, both directly under `src\`. The source directory structure documented in `PLAN.md` §5 (`src\main.cpp`, `src\cli\`, `src\core\`, `src\rules\`, `src\tui\`) is preserved exactly as-is. ### Reason An earlier proposal nested the projects as `src\core\syncwingetlink.core.vcxproj` and `src\app\syncwingetlink.vcxproj`. That was rejected: the core project owns `cli\`, `rules\`, and `tui\` as well as `core\`, so placing it inside `src\core\` would put a project file in a directory that represents only one quarter of its contents, and would force every other source folder to be referenced through a parent-relative path. The flat layout keeps the already-documented tree intact, so the migration touches the build system without churning source paths. ### Consequences - `PLAN.md` §5 only needs project files added to the tree, not a restructure. - Two `.vcxproj` files sit in one directory. This is supported; intermediate directories are keyed by `$(MSBuildProjectName)` so they do not collide. --- ## ADR-0005 — Standard library and Windows APIs first; dependencies must be vulnerability-free - **Date**: 2026-07-25 - **Affected**: `AGENTS.md` §5 / §10, `PLAN.md` §11, the `cpp-msbuild` skill - **Status**: Accepted ### Decision 1. Implementations use the **C++ standard library and Windows APIs** by default. A third-party dependency requires justification in the PR, MIT compatibility, and a pinned version. 2. **A dependency with a known vulnerability is a release blocker.** "No known vulnerabilities" joins "all unit tests green" as an explicit Definition of Done item, rather than being left implicit. ### Reason - The tool is a small, self-contained native CLI. Every dependency added to it becomes a supply-chain surface on a program that creates filesystem links in the user's profile. - C++20 plus the Windows SDK already covers essentially everything the design in `PLAN.md` needs — filesystem traversal, formatting, regex, known folders, reparse-point inspection, and (through the C++/WinRT dependency the COM data source already requires) JSON parsing. - Making both conditions explicit removes the ambiguity where a change "builds" and is reported as done without the tests having been run or the dependencies checked. ### Consequences - The `cpp-msbuild` skill carries a decision table mapping common needs to the standard library or Windows API, and names what *not* to reach for. - `AGENTS.md` §10 and `PLAN.md` §11 gained two Definition of Done items. - Dependabot (`.github/dependabot.yml`) covers `github-actions` weekly. It **cannot** cover native dependencies — see ADR-0007, which replaces NuGet with vcpkg and explains why this leaves the vulnerability gate manual for now. ### Known tension `rules.json` parsing needs a JSON reader. The Windows-native answer is `winrt::Windows::Data::Json`, which requires an initialised apartment. If a code path must parse rules before or without WinRT, that is a genuine conflict with this ADR and should be raised rather than resolved by quietly adding a header-only parser. ADR-0007 confirmed that `windows.data.json.h` ships in the Windows SDK, so this choice costs **no dependency at all** — which strengthens the case for it. --- ## ADR-0006 — Split the C++ rules out of AGENTS.md into a shared skill - **Date**: 2026-07-25 - **Affected**: `AGENTS.md` §4 / §5 / §9, `.github/skills/`, `.claude/skills/`, `.codex/skills/`, `.gitignore`, `tools/` - **Status**: Accepted ### Decision The C++-specific content of `AGENTS.md` §4 (build/test/run) and §5 (coding standards) moves into a skill, `cpp-msbuild`. `AGENTS.md` keeps a short summary and a pointer. - **Canonical**: `.github/skills/cpp-msbuild/SKILL.md` - **Mirrors**: `.claude/skills/cpp-msbuild/SKILL.md`, `.codex/skills/cpp-msbuild/SKILL.md` ### Reason `AGENTS.md` had grown past the point where an agent reliably applies all of it, and most of its length was C++ detail irrelevant to documentation or process tasks. A skill is loaded on demand for the tasks it applies to, which keeps the always-read file short. ### Why duplicated rather than symlinked Each tool discovers skills by its own path, and symlinks do not survive a plain `git clone` on Windows without Developer Mode. The mirrors are therefore real files, generated by `tools/sync-skills.sh` / `tools/Sync-Skills.ps1`. Both support `--check` / `-Check`, which exits non-zero when a mirror is stale — wire this into CI when the workflow is created. **Never edit a mirror.** Edit the canonical copy and re-run the sync script. ### Consequences - **Only the canonical copy is tracked.** `.gitignore` excludes `.claude/*` and `.codex/*`, so the two mirrors are local build products, not repository content. Each developer materialises the mirror their tool needs by running the sync script after cloning. This keeps a single reviewable copy in git and makes drift impossible to commit. - Consequently `tools/sync-skills.sh` is a **setup step**, not just a maintenance one. Its `--check` mode remains useful locally, but in CI it can only verify a mirror that CI itself created, so it is of limited value there. > **Unverified**: `.claude/skills//SKILL.md` is the documented layout for Claude > Code. The exact discovery conventions for OpenAI Codex (`.codex/skills/`) and GitHub > Copilot (`.github/skills/`) were **not** confirmed against vendor documentation. If a > tool does not pick the skill up, adjust that mirror's path and record it here. --- ## ADR-0007 — vcpkg is the package manager; NuGet is not used - **Date**: 2026-07-25 - **Affected**: `TODO.md` M0, `.github/dependabot.yml`, `.gitignore`, ADR-0005, the `cpp-msbuild` skill - **Status**: Accepted - **Supersedes**: the NuGet parts of ADR-0005 and open item 2 ### Decision Native dependencies are managed with **[vcpkg](https://github.com/microsoft/vcpkg)** in **manifest mode** (`vcpkg.json` at the repository root, with a pinned `builtin-baseline`). NuGet is not used for C++ dependencies. ### Verified environment (checked 2026-07-25) | Item | Result | |---|---| | vcpkg | **Bundled with Visual Studio 2026** at `VC\vcpkg\vcpkg.exe`, version `2026-05-27-d5b6777d` | | C++/WinRT headers | **Shipped in the Windows SDK**: `Include\10.0.26100.0\cppwinrt\winrt\` — 345 headers, including `windows.data.json.h` | | `cppwinrt.exe` | **Shipped in the Windows SDK**: `bin\10.0.26100.0\{x64,arm64}\cppwinrt.exe` | No separate vcpkg installation or bootstrap is required on a VS2026 machine. ### Consequence: the project may need no dependencies at all This is the important finding. The earlier plan assumed the `Microsoft.Windows.CppWinRT` NuGet package was needed to use C++/WinRT. **It is not.** The Windows SDK already ships both the C++/WinRT headers for `winrt::Windows::*` and the `cppwinrt.exe` projection compiler. Therefore: - `winrt::Windows::Data::Json` — the JSON reader chosen in ADR-0005 — is available with **zero packages**, from the SDK alone. - The winget `Microsoft.Management.Deployment` projection can be generated by invoking the SDK's `cppwinrt.exe` against the winmd in a pre-build step. What remains unresolved is *sourcing the winmd itself* (open item 1), not the tooling around it. - `Microsoft.Windows.CppWinRT` is dropped from the plan entirely. So vcpkg is adopted as the **policy and mechanism** for when a native dependency becomes necessary. Add `vcpkg.json` only at that point; do not add an empty manifest for its own sake. Combined with ADR-0005 (standard library and Windows APIs first), the expected steady state is an empty dependency set. ### Triplets and the CRT — interacts with ADR-0003 vcpkg triplets encode both library linkage and CRT linkage, and the triplet **must** match the project's `RuntimeLibrary` or the link fails: | Triplet | Libraries | CRT | Use for | |---|---|---|---| | `x64-windows-static-md` | static | dynamic (`/MD`) | default builds, and the MSTest DLL | | `x64-windows-static` | static | static (`/MT`) | shipping build, `-p:StaticRuntime=true` | | `arm64-windows-static-md` / `arm64-windows-static` | as above | as above | ARM64 equivalents | `x64-windows` (dynamic libraries) is deliberately not used — it would require shipping DLLs alongside the executable, defeating the single-file release goal in `TODO.md` M8. When dependencies are introduced, MSBuild integration is enabled per project with: ```xml true true $(Platform.ToLower())-windows-static $(Platform.ToLower())-windows-static-md ``` > **Unverified**: the exact property names and the `$(Platform.ToLower())` expression have > not been exercised against a real build. Confirm when the projects are created and > correct this block with what actually works. ### Consequence: Dependabot cannot monitor vcpkg — the vulnerability gate weakens **Dependabot does not support vcpkg as a package ecosystem**, and GitHub's dependency graph does not parse `vcpkg.json`. The `nuget` entry in `.github/dependabot.yml` is therefore removed rather than replaced, leaving only `github-actions`. This is a real regression against the "no known vulnerabilities" Definition of Done in ADR-0005. Until something automated is in place, the gate rests on: 1. A pinned `builtin-baseline`, so port versions never move implicitly. 2. A manual advisory check against the exact port version whenever a port is added or the baseline is rolled — see the procedure in the `cpp-msbuild` skill. 3. The fact that the expected dependency set is empty, which currently makes the exposure nil rather than merely small. Evaluating a scanner that does understand vcpkg (OSV-Scanner has been suggested, but its vcpkg coverage was **not** verified) is recorded as open item 6. Do not describe the vulnerability gate as automated until one is actually wired into CI. **Resolved 2026-08-16, see ADR-0043** (`docs/adr-phase-9.md`): the evaluation found no scanner understands vcpkg. Rather than close this gap with a scanner that measures nothing, `docs/adr-phase-9.md` ADR-0043 wires in a CI tripwire that keeps the tracked dependency set exactly what `.github/dependency-inventory.json` says it is, plus an enforced pin-and-allow-list check on GitHub Actions. An OSV-Scanner forward-coverage job was attempted but reverted — it fails at GitHub's workflow-parse stage for a reason not diagnosable from a checkout (ADR-0043 decision 4) — so it remains an open follow-up, not a shipped feature. Items 1–3 above still describe the vcpkg-specific procedure accurately; only the "gate rests on" framing is superseded — the presence of a dependency is now enforced automatically, its safety is still checked manually. --- ## Open items carried forward These are not decisions yet. They are recorded so they are not silently guessed at (`AGENTS.md` §2 rule 4). 1. ~~**Where to source the `Microsoft.Management.Deployment` winmd.**~~ **Resolved 2026-07-25.** ADR-0008 sources the winmd from the installed Microsoft Desktop App Installer package and generates the projection with the Windows SDK compiler. 2. ~~**NuGet reference style for native projects.**~~ **Moot as of 2026-07-25.** ADR-0007 adopted vcpkg and dropped NuGet, and the SDK turned out to supply C++/WinRT, so no package reference of either kind is currently needed. 3. ~~**ARM64 test execution.**~~ **Resolved 2026-08-16.** The `windows-11-vs2026-arm` hosted runner (preview) is available, including on private repositories, and is the only ARM64 runner image that ships the `v145` (VS2026) toolset this repo requires (`windows-11-arm` is VS2022/`v143` and does not qualify). `ci.yml`'s ARM64 leg now builds and runs `vstest.console.exe /Platform:ARM64` natively on that runner instead of cross-compiling on `windows-latest` and skipping tests. See `docs/adr-phase-9.md` ADR-0046, issue #173. Local x64 dev machines still cannot run ARM64 tests and must keep saying "cross-built, not run" for local verification. 4. ~~**Localized documents are drifting.**~~ **Resolved 2026-07-25.** `README_ja.md`, `PLAN_ja.md`, and `TODO_ja.md` were brought in line with their English counterparts under an explicit, one-off authorisation from the maintainer that overrode the standing `AGENTS.md` prohibition on agents touching `*_ja.md`. `rules_ja.md` and `com-api_ja.md` needed no changes. **The prohibition still stands by default** — a future agent must not take this as blanket permission; ask again. 5. **Vendor skill discovery paths are unconfirmed.** See the caveat in ADR-0006: only the Claude Code layout was verified against documentation. If Codex or Copilot fails to load the skill, fix that mirror's path and record the correction. 6. **No automated vulnerability scanning covers vcpkg.** Dependabot does not support the vcpkg ecosystem and GitHub's dependency graph does not parse `vcpkg.json` (ADR-0007). The "no known vulnerabilities" Definition of Done is therefore enforced **manually** today. Evaluate a scanner that understands vcpkg — OSV-Scanner is a candidate, but its vcpkg coverage was **not** verified — and wire it into CI. Until then, do not describe this gate as automated. 7. **vcpkg MSBuild property names are unverified.** The `VcpkgEnabled` / `VcpkgManifestInstall` / `VcpkgTriplet` block in ADR-0007 was written from the documented integration but never exercised against a real build. Confirm and correct when the projects are created.