Files
msd-core/.changeset
Andreas Brauchli cbf7c82841 feat(#323): fish-shell support in post-install PATH suggestion (#727)
* feat(#323): fish-shell support in post-install PATH suggestion

Two additive changes to the post-install PATH-suggestion seam, both scoped
to existing functions.

A. Projection: add a fish entry to the persist-mode shell-action list in
   projectPathActionProjection() (src/shell-command-projection.cts). fish has
   no `export`/`$PATH`-list syntax, so the existing zsh/bash `export PATH=...`
   commands are inert when pasted. The new entry emits the fish-native
   `fish_add_path '<dir>'` (fish 3.2+, persists via the universal-variable
   store, de-duplicating). The directory is single-quoted with the same POSIX
   literal escaping as the zsh/bash siblings; verified round-tripping through
   real fish 3.7.0 for paths containing quotes, spaces, `$`, `*`, backticks
   and unicode.

B. Detection: add homePathCoveredByFishConfig() in bin/install.js, called
   from maybeSuggestPathExport() alongside homePathCoveredByRc(). fish does
   not use sh-style `export PATH=` rc files, so a fish user whose
   fish_user_paths already covers the global bin would otherwise get a
   false-positive "not on your PATH" warning on every install. Two
   side-effect-free detection routes (no fish subprocess):

   1. The universal-variable store (~/.config/fish/fish_variables). fish
      serializes this with `full_escape`: every byte outside [A-Za-z0-9/_]
      becomes `\xHH` (space -> \x20, `-` -> \x2d, `.` -> \x2e, `$` -> \x24,
      unicode -> \uXXXX) and list elements are joined by the literal 4-char
      token `\x1e` (NOT a raw 0x1e byte). The detector splits on `\x1e`,
      decodes the escapes, then compares each as an absolute literal — a
      decoded `$` is part of the directory name, not an unexpanded variable.
      Verified against real fish 3.7.0 output.
   2. config.fish (`fish_add_path`, `set -gx PATH`, `set -Ux fish_user_paths`)
      — plain shell tokens: HOME forms ($HOME/${HOME}/~) are expanded and a
      token still holding `$` (e.g. `$PATH`, `$fish_user_paths`) is skipped.

   Honours $XDG_CONFIG_HOME and always also checks ~/.config/fish.

No behaviour change for bash/zsh/PowerShell/cmd/Git-Bash users: their entries
and command strings are unchanged; the fish entry is additive and the fish
detector only narrows the set of cases that warn.

Tests: update the projection length assertion (2 -> 3) and fish escaping in
bug-3441; add fish detection + suppression cases in install-path-detection
(uvar store with real fish escaping, dot/hyphen/space/$-literal decode
regressions, config.fish routes, commented-out, relative-segment guard,
unreadable-file fault injection, suppression and emission via
maybeSuggestPathExport).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(changeset): add Changed fragment for #323 fish PATH support (#727)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#323): address review — action-only fish docs, decoder property test, win32 guard

Addresses @trek-e's review on #727:

- docs (blocker): keep the how-to action-only (Diátaxis). Drop the
  `# fish — persists via …` comment and the internal-mechanism clause
  naming fish_variables/config.fish; leave one command + the exec-fish
  directive.
- tests (minor): extract decodeFishUniversalValue to a pure, exported
  module function and add fast-check round-trip properties
  (decode(fishEscape(p)) === p over arbitrary unicode, abs-path variant,
  totality). Consolidated into install-path-detection.test.cjs to respect
  the install test-file-count ratchet.
- tests (follow-up): port #721's win32 negative-projection test (no fish
  action on win32; persist projection is PowerShell/cmd.exe/Git Bash).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#323): address review — drop unused 'after' import, clarify escaping comment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-06-24 17:20:52 -04:00
..

Changeset Fragments

This directory holds per-PR CHANGELOG fragments. Every PR with user-facing changes drops one (or more) <random-name>.md files here describing its CHANGELOG entry. Fragments are consolidated into the top-level CHANGELOG.md at release time.

Why

Two PRs that both edit the ### Fixed block of CHANGELOG.md always conflict on merge — git can't pick a serialization order without human input. Two PRs that each add a fresh .changeset/<unique-name>.md never conflict because they don't share lines.

See #2975 for the full rationale.

Adding a fragment

node scripts/changeset/new.cjs \
  --type Fixed \
  --pr 1234 \
  --body "fix the thing — explain the user-visible change in one sentence"

This writes .changeset/<adjective>-<noun>-<noun>.md with frontmatter and a body. Three random words → concurrent PRs don't collide.

Format

---
type: Fixed
pr: 1234
---
**`/gsd-foo` no longer drops trailing slashes** — explain the user-visible change.

Allowed type: values follow Keep a Changelog: Added, Changed, Deprecated, Removed, Fixed, Security.

Opting out

PRs that legitimately have no user-facing impact can add the no-changelog label. CI honors it. When unsure, add the fragment.

At release time

Promotion is automatic. The release workflow's finalize job runs:

node scripts/changeset/cli.cjs render --version vX.Y.Z --date YYYY-MM-DD --allow-empty

This reads every fragment, groups bullets by type:, replaces ## [Unreleased] with a new ## [vX.Y.Z] - YYYY-MM-DD block, opens a fresh ## [Unreleased] above, and deletes consumed fragments. The --allow-empty flag ensures a no-change release still gets a dated heading (with a _No notable changes._ placeholder). A subsequent verify step confirms the promotion landed correctly. Maintainers do not run this by hand.

Archived fragments

.changeset/archived/ holds fragments for already-shipped releases (≤ 1.3.1), retained for provenance. Their content was hand-curated into the dated ## [1.x.y] sections of CHANGELOG.md during the #690 backfill — they were never consumed by render. All changeset tooling enumerates .changeset/ non-recursively, so archived fragments are never picked up or rendered. Do not move them back to the top level.