Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
73b8fed
records: plan the setup redesign (four intents, their specs, and the …
REPPL Oct 24, 2026
7439b84
feat: setup offers to put abcd's folder on the PATH in the shell sett…
REPPL Oct 24, 2026
a0f8258
fix: report the install mode of a recorded copy with no plugin root
REPPL Oct 24, 2026
dd45246
fix: treat a go run build of the checkout being installed as current
REPPL Oct 24, 2026
27f895d
feat: setup checks first, shows one plan, and asks a person only thei…
REPPL Oct 24, 2026
dd3a0ed
docs: describe the setup flow and amend invariant 1 for setup
REPPL Oct 24, 2026
95b1ca4
feat: each person's commit identity lives on their own machine
REPPL Oct 24, 2026
65cb33d
fix: judge the shell settings file again just before it is replaced
REPPL Oct 24, 2026
cf6bd18
docs: record the findings and deferrals from building the setup redesign
REPPL Oct 24, 2026
c94f8b6
Merge branch 'feat/setup-redesign-identity' into feat/setup-redesign
REPPL Oct 24, 2026
728e41c
Merge branch 'feat/setup-redesign-shellpath' into feat/setup-redesign
REPPL Oct 24, 2026
0a807d0
feat(tools): install tools from downloads pinned in abcd's release
REPPL Oct 24, 2026
083ccf0
Merge branch 'feat/setup-redesign-flow' into feat/setup-redesign
REPPL Oct 24, 2026
5bd1a5c
Merge branch 'feat/setup-redesign-tools' into feat/setup-redesign
REPPL Oct 24, 2026
953c559
docs: record the tools lane's limits and two review findings left for…
REPPL Oct 24, 2026
103b5c9
fix: count only abcd's own line for this folder as already added
REPPL Oct 24, 2026
24896ac
fix: use abcd's own tool copy only at a version abcd works with
REPPL Oct 24, 2026
ffa6f0a
fix: make setup's report, plan and questions say what the run does
REPPL Oct 24, 2026
b6dc27c
feat: ask to switch on the commit checks in a clone that does not run…
REPPL Oct 24, 2026
4f47f7b
docs: describe the setup review fixes and point the default mode at p…
REPPL Oct 24, 2026
28e4531
docs: resolve the shell-settings re-check finding
REPPL Oct 24, 2026
0eed9ca
docs: say setup asks the identity and commit-checks questions on thei…
REPPL Oct 24, 2026
ae328dd
docs: capture the setup descriptions the docs review found incomplete
REPPL Oct 24, 2026
ddce20f
docs: ship the identity, tools and shell-settings intents
REPPL Oct 24, 2026
c95c251
fix: settle the setup tests and record the entry a forced --dev writes
REPPL Oct 25, 2026
d68fae4
test: give the session-end retry test room to retry on a loaded machine
REPPL Oct 25, 2026
d2196f8
docs: capture two hold-ups met during the setup redesign build
REPPL Oct 24, 2026
7d8158c
docs: capture the duplicated download rule
REPPL Oct 24, 2026
4d1e886
docs: capture the session-end retry budget that runs out under load
REPPL Oct 25, 2026
efd10b9
fix: satisfy the self-checks the setup redesign's new code trips
REPPL Oct 25, 2026
f5f62f1
Merge remote-tracking branch 'origin/pr-1121' into feat/setup-redesign
REPPL Oct 25, 2026
b79a72f
Merge origin/main into feat/setup-redesign
REPPL Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .abcd/development/brief/02-constraints/03-invariants.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

The following are non-negotiable invariants — any architectural choice that violates them is wrong even if it works.

1. **Transparent prompts** — every `AskUserQuestion` shows current state, consequences of each option, and how to change later. No silent defaults. See [`05-internals/04-universal-patterns.md § 1`](../05-internals/04-universal-patterns.md#1-transparent-prompts).
1. **Transparent prompts** — every `AskUserQuestion` shows current state, consequences of each option, and how to change later. No silent defaults. Setup (`abcd ahoy install`) meets the rule in two steps ([adr-2610241101463288](../../decisions/adrs/2610241101463288-setup-asks-short-questions-and-lists-its.md)): one plan screen lists every change and every default before anything is written, each default with where to change it, and each setup question is one line whose answer `?` shows the whole question, current state, consequences and how to change later, before asking again. See [`05-internals/04-universal-patterns.md § 1`](../05-internals/04-universal-patterns.md#1-transparent-prompts).

2. **Adapter over native default** — every capability abcd once delegated to a bundled tool has a native default; the external tool is an opt-in adapter over the same seam ([adr-22](../../decisions/adrs/0022-bundled-deps-as-pluggable-adapters.md)). A missing or misbehaving adapter degrades to the native path; abcd never blocks on an absent tool. See [`05-internals/04-universal-patterns.md`](../05-internals/04-universal-patterns.md).

Expand Down Expand Up @@ -139,6 +139,16 @@ The following are non-negotiable invariants — any architectural choice that vi
`TestPresentReadsDiskOnly`
([adr-2610181003541997](../../decisions/adrs/2610181003541997-the-away-mode-s-stop-check-reads-the.md),
which refines adr-38 for this verb's meaning alone).
Setup (`abcd ahoy install`) downloads abcd only on a yes to the question
that names the download, the release and the file it is written to, which
is that fetch's documented meaning: the release's binary for this computer,
checked against the release's `checksums.txt` and the build provenance its
release workflow signed before anything is written, exactly as `abcd
update` checks a swap
([adr-2610241101463288](../../decisions/adrs/2610241101463288-setup-asks-short-questions-and-lists-its.md)).
The release is the one the running abcd was stamped as, or, in a source
checkout of abcd, the newest release tag the checkout holds, read from
disk; no version-discovery request is made.
Inbound, abcd listens on a network only through the dashboard, only while
someone has started it on purpose, and only for the people let in: it
listens on this computer's own Tailscale addresses alone, never every
Expand Down
254 changes: 201 additions & 53 deletions .abcd/development/brief/04-surfaces/01-ahoy.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion .abcd/development/brief/04-surfaces/05-intent.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,9 @@ origin: researcher-authored # arrival path (itd-178), DERIVED from which comma
# Stamped at mint and never rewritten.
production_mode: hand-written # how the text was produced: hand-written | dictated-and-formatted |
# scribe-transcribed. Closed choice carried by a production-mode flag; an absent
# flag takes the repo's declared default from .abcd/config/identity.json.
# flag takes the repo's declared default from .abcd/config/provenance.json
# (in a repo not yet migrated, the production_mode of .abcd/config/identity.json),
# falling back to hand-written.
# Written at mint too, but only by the path that has a value for it:
# related_issues: [iss-N | rdi-N, ...] — the intent half of the promote join (itd-4 AC3),
# written by the capture/reading promote path and appended to
Expand Down
5 changes: 3 additions & 2 deletions .abcd/development/brief/04-surfaces/19-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ across lines. abcd's own block is the "Identity (canonical)" section of

`.abcd/positioning.json` records the block's location, the family severity, and
the surfaces held to it. It sits beside the repo's other per-concern
configuration rather than under `.abcd/config/`, where `identity.json` already
means the git commit-author pin.
configuration rather than under `.abcd/config/`, where `identity.json` was the
git commit-author pin a repository stored before that pin moved to each
owner's own record on their machine.

The severity is the whole family's weight, and it is the one knob that changes
what a failed check costs. `warn`, the default, reports drift as a warning, and
Expand Down
6 changes: 6 additions & 0 deletions .abcd/development/brief/05-internals/04-universal-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ Every `AskUserQuestion` (or equivalent harness call) shows:

No silent defaults. No surprises.

Setup meets the three in two steps
([adr-2610241101463288](../../decisions/adrs/2610241101463288-setup-asks-short-questions-and-lists-its.md)):
its plan screen lists every change and every default before anything is
written, each default with where to change it, and each of its questions is
one line whose answer `?` shows all three before asking again.

Every question also has one layout (itd-2610030810350727, itd-201;
spc-2610030944505997), and abcd's question check in `abcd guard hook` refuses
an abcd question that breaks it, naming the part, the value, and the limit. The
Expand Down
1 change: 1 addition & 0 deletions .abcd/development/brief/06-delivery/03-out-of-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ That is what keeps "not hand-counted" true after the day it was written.
- `itd-2610170638184486` — Every session keeps a checklist on disk, so no run stalls and no stall spreads
- `itd-2610222029228127` — Every session's checklist reaches a shared copy and the phone
- `itd-2610222215485265` — abcd drain --drive works through the ledger from a plain terminal
- `itd-2610240913343804` — No surprises: abcd says up front what it will do on its own and what it will ask
- `itd-2610240933025076` — abcd plans unattended and asks only what a person owns
<!-- /index -->

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
id: adr-2610241022330872
slug: setup-edits-the-shell-settings-to-put
status: accepted
date: 2026-10-24
supersedes: null
superseded_by: null
related_intents: [itd-2610240919030303, itd-2610240823055889]
related_rfcs: []
related_adrs: [adr-2610050711287503]
---

# ADR-2610241022330872: Setup edits the shell settings to put abcd's folder on the PATH, on a yes

## Context

`abcd ahoy install` puts its PATH entry in `~/.local/bin` by default (iss-171). On many machines that folder is not on the PATH, and then `abcd` cannot be run by name. Until now the rule was script-first: setup printed the one-line `export` fix and never edited a shell profile, written in the brief's setup page and in the `/abcd:ahoy` command page.

On 2026-10-24 the product thinker had to paste the README's long install line by hand before setup could finish. The research behind itd-2610240919030303 found that installers disagree: some edit the shell settings themselves, some only print the line. The product thinker chose that setup asks, and then confirmed that this reverses the written rule. The design review named the costs the change has to meet: which file each shell reads, settings files that are links into a shared dotfiles folder, undoing the edit when abcd is removed, and the new terminal the edit needs before it takes effect.

The status line does not need the PATH: it runs the recorded absolute path of the entry (adr-2610050711287503). The edit is for the person typing `abcd`.

## Decision

When the folder abcd installs into is not on the PATH, `abcd ahoy install` asks a person at a terminal, once, whether to add one line to the startup file their login shell reads. The question names the file and the line. abcd edits the file only on a yes. Declined, or when abcd will not edit the file, the line is listed for the person to add.

The edit meets this bar (`internal/core/shellpath`):

- Only zsh and bash, on macOS and Linux. zsh gets `${ZDOTDIR:-$HOME}/.zshrc`, which every interactive zsh reads. bash on macOS gets the login file bash reads (the first of `.bash_profile`, `.bash_login` and `.profile`, else a new `.bash_profile`). bash on Linux gets `~/.bashrc`. A `ZDOTDIR` outside the home folder, or any other shell, gets the checklist line.
- Only a file abcd can read whole: a regular file, not a link, owned by the person, writable by no one else, and at most 256 KiB. A file that is a link is kept elsewhere, so abcd never edits it.
- Never the same folder twice. Settings that already add the folder are left alone: abcd's own marked line, or a line of the person's that sets the PATH to include the folder, in any file that shell reads at startup.
- One line, added at the end and marked: `export PATH="$HOME/.local/bin:$PATH" # added by abcd ahoy install`. The folder comes first, ahead of the PATH it had, so the abcd setup installed answers before an older copy in a system folder.
- A copy of the file goes into `~/.abcd.noindex/backups/` first, which keeps the newest ten copies of each file. No copy, no write. The file is replaced atomically with its mode kept, read back, and put back from the copy when it does not hold exactly what was written.
- The file and the line are recorded in `~/.abcd.noindex/shell-path.json`. `abcd ahoy uninstall` removes that line and nothing else, puts back the newline abcd added before it, and deletes a file abcd created for the line alone. A line the person has since edited is no longer abcd's, and uninstall leaves it in place.
- `--yes` never gives the yes, and the question is never asked off a terminal.

This supersedes the rule that abcd never edits a shell profile. adr-2610050711287503 is unchanged: the harness's user settings are still touched only for the status line.

## Alternatives Considered

- Ask, then edit under the bar above (chosen): a person who says yes is done in one step, and the edit can be found and undone.
- Keep printing the line and never edit (the old rule): no risk to the person's files, but every person whose PATH lacks the folder has to find the right file and paste the line. That is the step the product thinker hit.
- Edit without asking, as some installers do: fewer questions, but it changes a file the person owns without a consent that names the change, which the setup flow's person-only questions rule out.
- Edit through a link into a dotfiles folder: reaches the person's real file, but it changes a file shared with other machines, and uninstall on one machine would take the line out on every machine.

## Consequences

- A person at a terminal can put abcd on the PATH by answering one question, and still has to open a new terminal before `abcd` runs by name there; setup says so.
- abcd now writes to a file outside its own folders and the repository. That write has its own record, copies and read-back, and uninstall removes it.
- The already-added check reads text. It can take a line for one that adds the folder when it does not, which costs the person the offer but never adds a second line. A line that adds the folder only through a file it sources is not seen.
- Shells other than zsh and bash, and settings kept as links, still need the person to add the line. The checklist names it.
- The brief's setup page and the `/abcd:ahoy` command page now describe the offer instead of the never-edit rule.
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
id: adr-2610241022541063
slug: each-person-s-commit-identity-lives-on
status: accepted
date: 2026-10-24
supersedes: null
superseded_by: null
related_intents: [itd-2610240918547728, itd-131, itd-178]
related_rfcs: []
related_adrs: [adr-38]
---

# ADR-2610241022541063: Each person's commit identity lives on their own machine, not in the repository

## Context

[itd-131](../../intents/shipped/itd-131-managed-repo-identity-gate.md) made
the commit identity gate check git's effective author and committer "against
the identity the repo pins" in a committed `.abcd/config/identity.json`, and
made adopting that pin a step of `abcd ahoy install`. The pin names one
person. In a repository that takes outside contributions that is wrong for
everyone else: setup reports any other contributor's identity as a required
`git_identity.mismatch` gap and offers to make them commit as the pinned
person, and abcd's own committed `.githooks/pre-commit` refuses their commits
with a fix line telling them to set the maintainer's name and email
([iss-2610240830397793](../../../work/issues/resolved/iss-2610240830397793-the-committed-identity-pin-names-one.md)).
That contradicts the attribution rule that every commit is honestly authored
by the human who made it.

The pin did two useful things, and both had to survive: it caught a commit
made under a bot's or an AI tool's identity, and it caught a mistyped email of
one's own. The same committed file also carried the repository's default
`production_mode` (itd-178), which is the repository's setting, not a
person's. And an unattended routine on a fresh machine (a cloud container)
has no person at a terminal to establish an identity for.

The product thinker accepted the reversal on 2026-10-24, in the Decisions of
[itd-2610240918547728](../../intents/shipped/itd-2610240918547728-every-person-who-commits-to-a-repository.md),
after considering and declining the alternative of dropping the pin and
checking only that the author is a human.

## Decision

We will keep each person's commit identity on their own machine, and nothing
in a repository will name who may commit to it.

- The record is `{"name": "…", "email": "…"}` under the user-level home, read
most specific first: `~/.abcd.noindex/identity/<root-sha>.json` for one
repository, keyed on the full object name of its root commit (as the
worktree and transcript stores are keyed), else
`~/.abcd.noindex/identity.json`, the machine-wide default. Per repository
with a machine-wide default, so a person with a work and a personal identity
keeps each where it belongs while the common case is one file.
- It is read under the guards every home-scoped declaration takes
(`fsutil.ReadHomeDeclaration`, the rules loader's): a regular file this
account owns that no other account can write, capped in size, refused behind
a symlinked `~/.abcd.noindex`. A record that is there and fails a guard, or
does not parse, fails the check closed; it never reads as no record.
- The commit check (`identity.Check`, `abcd ahoy --identity`, and the
self-contained shell gate in abcd's own `.githooks/pre-commit`, which reads
the same two files in the same order from `$HOME`, with no binary) holds the
author and committer to that record, and refuses a machine identity, from
the one list `internal/core/identity/tool-identities.txt`, in either role
whether or not a record exists. With no record, a human identity commits:
an unattended run on a fresh machine commits as the human identity its
launcher supplied, or is refused as a machine.
- Setup proposes a person's usual git identity and records it only on their
yes, as its own question, only at a terminal; off a terminal, or under the
approve-everything flag, it records nothing and says so.
- A repository that still stores the pin is migrated on its owner's machine
(the person whose git identity is the stored one), on a yes at a terminal:
the pin becomes that person's record for the repository, its
`production_mode` moves to `.abcd/config/provenance.json`, and
`identity.json` leaves the working tree. On anyone else's machine it is not
copied, not removed and not named.
- The repository's default production mode stays committed, in
`.abcd/config/provenance.json`; while a repository has not migrated, the
legacy pin's `production_mode` member is still read when that file is
absent.

This supersedes, from itd-131, the decision that the gate checks commits
against an identity the repository pins; the scope item that adopting the gate
writes `identity.json` behind the config-change approval; and the first link
of the disk-only proposal chain, "pinned → global", which becomes "the
person's own record → global". Its other decisions stand: detection here and
establishment by whatever launches a routine (iss-2608210932052003), a
disk-only proposal chain with no network lookup (adr-38), and
propose-and-confirm at a terminal before repo-local `user.*` is written.

## Alternatives Considered

- **Keep the committed pin** (itd-131 as shipped). Rejected: it names one
person to every contributor and tells them to commit as that person.
- **Drop the pin and check only that the author is a human and matches the
committer.** Considered and declined by the product thinker: it keeps the
machine refusal but loses the check that catches a mistyped email of one's
own.
- **A machine-wide record only.** Rejected: a person who commits to work
repositories under a work identity and to their own under a personal one
would be refused in one set or the other.
- **A per-repository record only.** Rejected as the only form: every
repository would need its own question, while most people commit under one
identity everywhere.
- **Chosen: per repository, with a machine-wide default**, kept on the
person's own machine.

## Consequences

- No contributor is ever shown or offered another person's identity, and a
repository that takes outside contributions needs no per-person setup.
- The commit check is now a person's own commitment: until they record an
identity, only a machine identity is refused. A person who never runs setup
at a terminal is checked for machines alone.
- An unattended run on a fresh machine needs no record: its launcher supplies
the human identity, and a machine identity is still refused. A git identity
git invents from the account and host name, when none is configured, is not
distinguishable from a supplied one inside the hook; the Go check reports it
as `git_identity.unset`.
- The shell gate in abcd's own pre-commit hook now reads the person's home
directory, and is at least as strict as the Go reader (it refuses a
group-writable folder the Go reader admits when the group is the owner's
own).
- `.abcd/config/provenance.json` is a new committed file, and
`provenance.DeclaredMode` is the one reader of the repository's default
production mode; `identity.DeclaredProductionMode` is gone.
- abcd's own repository still stores the maintainer's pin until the
maintainer's next setup moves it; nothing reads it to check commits in the
meantime.
Loading
Loading