# nobrainer-tech-flow 2.0.2 verification record

Source version: 2.0.2. Publication is a separate check against the public tag
and release archive; this document alone does not establish distribution.

2.0.2 builds on the [2.0.1 record](v2.0.1.md): everything in it is part of this
release. It adds two installer scripts and fixes what two independent reviews found.
It adds no skill (the portfolio stays eighteen) and changes no skill text, adapter
or hook; the manifests change only their version.

## What changed

Installing without Python

- `scripts/install.sh` is a POSIX sh script for one client (`claude`, `codex`,
  `opencode` or `copilot`): `sh scripts/install.sh --client claude` previews,
  `--apply` installs, `--undo --apply` reverses. It runs in dash, bash, zsh and
  busybox on macOS, Linux and WSL, and in Git Bash on Windows, and needs only the
  usual system tools (sed, grep, head, tail, mktemp, sha256sum or shasum, and git
  to show the source commit).
- The README opens its install section with a prompt to paste into any coding
  agent: it clones or updates the repository and follows the install method the
  README gives for that agent. It names no version, script or client list, so it
  stays valid as they change. The same two commands follow for a terminal.
- It uses the same locations, variables, conflict rule and setup record as the
  Python scripts, and writes byte for byte the block the Python helper writes, so a
  setup made by either one can be undone by the other.
- It never grants an authorization, links only (like `install.py`), and leaves a
  managed block that is already there exactly as it is, where `install.py` rewrites
  it to the current text and keeps its options.
- It refuses the paths the Python scripts refuse (a control character, a line or
  paragraph separator, and, where `iconv` is installed, bytes that are not UTF-8),
  so a record it writes stays readable to them, and it reads `--home=~/x` as
  `install.py` does.

One command with Python

- `python3 scripts/install.py --client claude` does the same through the guided
  setup with every skill selected. It asks nothing, prints the exact text it would
  write, refuses a target that is not its own link, and runs the guided setup's own
  checks in the preview, so the preview refuses whatever `--apply` would refuse.

Undo that reverses the install

- The setup record keeps the backup from the first install. Before, every later
  run replaced it with the file as it was just before that run, so after an update
  the undo restored a file that still held the block (found by the second review).
- Undo puts the managed block back as it was before the first setup (removed, or
  the block that was there) and leaves the rest of the file as it is now. The file
  comes back byte for byte when nothing else changed, and edits made outside the
  block since are kept. A block that was already there before the first install,
  with its options and preference, is no longer deleted by the undo. That holds
  for setups recorded by 2.0.2: a 2.0.0 or 2.0.1 record made when the block was
  already there holds no backup, so its undo still removes the block.
- The block is replaced as whole lines, the line break after its END marker
  included, so both undos give the same bytes even after edits around the block,
  and text added after a block that ended the file keeps a line of its own.

Claude files that import the Codex instructions

- The block is skipped only when the Claude file holds nothing but the import line
  (`@~/.codex/AGENTS.md` or the absolute path) and the Codex file has the block;
  every other Claude file gets its own block. 2.0.1 tried to recognise an import
  anywhere in the file; the second review showed, against the real Claude Code
  2.1.284, ordinary files where it saw an import that Claude Code does not load
  (for example a list step whose closing fence sits at column 0, a quoted indented
  example, a link definition, a tab after spaces), which left Claude without the
  block. A second copy of the block is harmless; a missing one is not.

Fixes from the independent review of 2.0.1

Each has a regression test that fails against the 2.0.1 scripts.

- An environment variable that the selected client never reads (a literal
  `XDG_CONFIG_HOME=~/.config` with Claude Code, a relative `CODEX_HOME` with
  Copilot) stopped every client and made a finished setup impossible to undo. Only
  the variable the client reads is checked now, and its value is used exactly as
  written. An empty `CODEX_HOME` or `XDG_CONFIG_HOME` counts as unset, as Codex
  0.158.0 (checked) and the XDG specification treat it; spaces only, and an empty
  `CLAUDE_CONFIG_DIR`, are relative paths and an error.
- A grant could be forged under `--keep-options` from a `--wiki-root` path that
  carried a line-separator character (U+2028, U+0085, U+001C and others), because
  grant lines were found with `str.splitlines`. A grant is now a whole line ending
  at `\n` or `\r\n`, and a preference or wiki path must be one printable line. An
  edited grant line is not kept, and the run now says so and names the flag.
- On Python 3.13 a destination that is a symlink loop was accepted (`resolve()` no
  longer raises); it is reported again.
- Copy mode ignored every `.pyc` and any directory named like litter, so a planted
  `json.pyc` next to a script (importable, and it shadows the standard library) was
  reported as an identical copy. Only `__pycache__` directories and `.DS_Store` and
  `Thumbs.db` files are ignored now.
- A home path that is not valid UTF-8, that holds a line break, or an empty or
  relative `HOME` is refused before anything is written; such paths used to finish
  a setup that could not be undone.
- End of input at the interactive selection prompt ends the run instead of raising.
- The 2.0.1 statement that Claude Code "does not accept" a relative or `~`
  `CLAUDE_CONFIG_DIR` was wrong: Claude Code 2.1.284 creates the directory named by
  a relative value in its working directory and takes `~` literally. The installer
  still refuses such values because it cannot know what the client means.

Smaller changes from the second review

- The printed undo and install summaries are read from the setup record, not from
  printed paths, and the undo counts the links it actually removed.
- Messages name this command's flags (`--undo --apply`), a link left by an earlier
  layout gets the migration command, Windows hints say `py -3`, and when
  `CLAUDE_CONFIG_DIR`, `CODEX_HOME` or `XDG_CONFIG_HOME` chose the location the
  output says to keep it set for the undo.
- In `install.py` the link check before `--apply` runs where the skills go, not
  only in the temp folder. `install.sh` checks in the temp folder; when a link still
  fails where the skills go, it removes the links that run made and stops.

Fixes from the review of the new scripts

A third read-only review, of `install.sh`, the new undo and `install.py`, found no
blocker and 13 smaller problems. Each fix below has a test that fails against the
scripts before it, except the one for concurrent runs, which was checked by hand.

- A hint that points to the Python undo keeps `--home`; following it used to undo
  the default profile instead. On Windows it says `py -3`.
- When the Claude file had become nothing but the Codex import, a repeat run left a
  record that neither undo could reverse. The record now drops that file, as the
  guided setup's does, and the undo removes the links.
- Several runs at once could write links into the checkout, and a run could report
  success after another one removed its links. A link is never made through a link
  another run just made: one run wins, the others stop and remove only their own.
- `sh scripts/install.sh` failed with `CDPATH` set.
- When a record names another place, both installers now name the setting that
  setup was made with (for example `CLAUDE_CONFIG_DIR=...`, or the variable unset),
  and the undo command they print works as printed.
- `install.sh` undoes a file that ends in a lone CR (old Mac line breaks) or holds a
  NUL byte byte for byte; before, it installed and then refused to undo.
- A variable of another client that holds a control character no longer stops
  `install.sh`.
- The mutation check found checks that no test reached; they now have tests (see
  below).

Documentation and gates

- README, `docs/INSTALL.md`, `docs/TESTING.md` and `.opencode/INSTALL.md` describe
  the new scripts and rules; the erratum for the 2.0.1 statements above is at the
  top of its record.
- Tests: `tests/test_shell_installer.py` and `tests/test_one_command_installer.py`
  run the real scripts against a private home; the shell tests also run in every
  POSIX shell found and with no Python on the `PATH`, and check both lists of skill
  names against the Python installer. The regression tests fail against the
  previous scripts.
- Mutation check: 34 mutants of `install.sh`, each removing one check or behavior;
  the shell tests catch 30. Of the other four, two change nothing (a second check
  refuses the same backup paths, and `mktemp` already creates the record private)
  and two matter only when another process changes the links during a run: the
  re-check before an undo removes a link, and `ln -n` for runs at the same time,
  checked by hand (12 rounds of 4 runs at once: one run succeeds each time, its
  record matches its links, and nothing is written into the checkout). Of the
  `install.py` mutants tried, only the link check at `--apply` is not caught; it
  matters only where the skills folder refuses links that the temp folder allows.
- CI runs both installers' tests in the Windows job (the shell one under Git Bash),
  the whole suite on Python 3.13 with Node 22, checks `sh -n scripts/install.sh`,
  and fails instead of skipping when a runner cannot create links.
- `scripts/install.sh` is pinned to LF, like the hook scripts.

## Reproduce the repository checks

From the exact reviewed release checkout, run:

```sh
python3 scripts/validate_skills.py --suite
python3 -m unittest discover -s tests -q
```

## Evidence limits

These checks establish local helper behavior, source contracts and the client
readbacks named in the [compatibility record](../COMPATIBILITY.md), which was made
against the 2.0.1 adapters that this release leaves unchanged. They do not prove
that a client routes a task through Flow, that a model followed a skill, or that
Cursor, Gemini CLI, Kimi Code, OpenCode or Pi accept the adapters at runtime. No
client was read back on an install made by either new script.

- The shell installer ran here in dash, bash (POSIX mode), busybox sh and zsh
  emulating sh on Linux; CI runs it under macOS's own `/bin/sh` and under Git Bash
  on Windows. A Windows account without the right to create links was not run.
- The single-line import rule was checked against the real Claude Code 2.1.284
  only through the review's documents; the rule itself needs no Markdown parsing.
- `install.sh` refuses a path that is not UTF-8 only where `iconv` is installed.
  Runs at the same time were checked by hand, not in the suite.
- Known gaps that this release does not address are the cost of starting Flow on a
  trivial task, the worktree-per-task default, and the differences between the Flow
  and Dispatcher ledgers.
