Skip to content

Expand ~ and $HOME in the configured outputDirectory before writing - #129

Merged
grimicorn merged 4 commits into
mainfrom
agent/expand-home-output-dir
Aug 27, 2026
Merged

Expand ~ and $HOME in the configured outputDirectory before writing#129
grimicorn merged 4 commits into
mainfrom
agent/expand-home-output-dir

Conversation

@grimicorn-agent

@grimicorn-agent grimicorn-agent commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

What & why

outputDirectory was persisted and read as a raw string, so a value like ~/notes or $HOME/notes (from a quoted config set, the interactive prompt, or a .env file — none of which a shell expands) was handed straight to existsSync/mkdirSync/resolve. The result was a literal ~ directory created under the cwd, silently scattering synced notes. This expands a leading ~/$HOME/${HOME} at read time so files land in the home directory.

Closes #125

Changes

  • src/libs/paths.ts (new): expandHomeDirectory(inputPath, resolveHomeDirectory) — a pure string transform that expands a leading ~, ~/…, $HOME, or ${HOME} and returns everything else (absolute, relative, or a mid-string tilde) untouched.
  • src/libs/markdown.ts: getOutputDirectory() now runs the configured value through the expander. This is the single read seam both ensureOutputDirectory (sync) and requireOutputDirectory (dry-run preview) go through, so both paths are covered.
  • src/libs/config.ts: the interactive prompt now trims its answer, matching config set — a pasted " ~/notes" would otherwise never match the leading-token check.
  • Tests for paths.ts (all required cases: ~, ~/sub, $HOME, ${HOME}, absolute, relative-with-mid-tilde, plus ~user/$HOMEwork non-matches, no-prefix skips env access, empty-home throws), the write + dry-run seams in markdown.ts, and the prompt-trim in config.ts.
  • README: documents the expansion, the quoting requirement, and the relative-path caveat.

Key decisions

  • Leading-only, POSIX / separator. Only a home reference at the very start of the string expands; a directory legitimately named ~ mid-path (notes/~drafts) stays literal. ~user (another user's home) is not resolvable, so it's left alone.
  • Home resolver injected, not called eagerly. os.homedir() runs only when a prefix actually matched, so a fully absolute OUTPUT_DIRECTORY never depends on the environment. An empty home fails loud rather than letting join('', 'notes') degrade the path into a cwd-relative write.
  • Expand at read time, not on write to the store. Keeps the user's ~/notes visible in the config file and re-resolves per machine, matching the issue's recommendation.

Viewable

Not a UI change. Behaviour is exercised by markpost sync / markpost sync --dry-run with outputDirectory set to ~/notes.

Follow-up suggestions

  • Expand ~/$HOME in push input pathsmarkpost push '~/vault/**' globs a literal ~ and matches nothing; run the same expander over push args in resolveMarkdownInputs. (suggested: P3, effort: S, evidence: src/commands/push.ts, src/libs/files.ts)
  • Show expanded target in config getconfig get outputDirectory prints the raw ~/notes while files land elsewhere; display the resolved path alongside it for debuggability. (suggested: P4, effort: S, evidence: src/commands/config.ts formatValue)
  • Optional Windows separator support in home expansion — expansion only recognises the POSIX /; a Windows ~\notes stays literal. Gate a sep-based check on platform if Windows becomes a target. (suggested: P4, effort: S, evidence: src/libs/paths.ts)

@grimicorn-agent

Copy link
Copy Markdown
Collaborator Author

Independent code review trail

Ran the independent Opus reviewer over git diff origin/main...HEAD for 3 rounds (loop cap). The core expandHomeDirectory algorithm was clean in every round; findings were in surrounding tests/docs/seams.

Round 1 — 4 findings

  • Fixed: README env-var doc said "Absolute path", now misleading → updated to document ~/$HOME/${HOME} expansion.
  • Fixed: integration tests only covered ~ via env and asserted only writeFileSync → added a test covering the persisted config value, $HOME, and mkdirSync receiving the expanded path.
  • Skipped (out of scope): Windows \ separator support. The issue and task specify ~/ (forward slash); ~/$HOME are Unix conventions. Documented the POSIX-/ decision in paths.ts instead of adding an untestable win32 branch. → follow-up.
  • Skipped (out of scope): config get outputDirectory still prints the unexpanded value. Changing display output is separate behaviour with its own surface; the comment "shown as-is" remains literally true. → follow-up.

Round 2 — 6 findings

  • Fixed: os.homedir() was evaluated eagerly for every read, including fully-absolute paths → injected the home resolver so it's called only when a prefix matched.
  • Fixed: a falsy home directory would let join('', 'notes') silently degrade ~/notes into a cwd-relative write → now throws (fail loud).
  • Fixed: inverted comment ("catching" → "rejecting" ~foo/$HOMEfoo).
  • Fixed: the dry-run seam (buildWritePreviewrequireOutputDirectory) had no expansion test → added one.
  • Skipped (repeat, out of scope): Windows separator, config get display → follow-ups.

Round 3 — 5 findings

  • Fixed: interactive prompt didn't trim its answer, unlike config set — a pasted " ~/notes" would defeat expansion → trims now (also benefits apiToken); added a test.
  • Fixed: README didn't note the value must be quoted (an unquoted $HOME/notes is shell-expanded first) → added the quoting hint.
  • Fixed: dropping "Absolute" from the README row lost the relative-path caveat → added a note that a relative path resolves against the cwd, bad for scheduled runs.
  • Fixed: comment overstated that os.homedir() throws on unset $HOME (it falls back to the passwd entry) → reworded; the empty-string guard is defensive.
  • Skipped (out of scope): push command doesn't expand ~ in its input globs. Real UX gap but a separate code path from issue Expand ~ and $HOME in the configured outputDirectory before writing #125's outputDirectory scope → follow-up.

Verification: npm run typecheck, npm run lint, and npm test (717 tests) all green after each round.

@grimicorn-agent grimicorn-agent added the has-suggestions PR carries follow-up suggestions for the improvement digest label Aug 23, 2026
@grimicorn
grimicorn merged commit 6f39bcb into main Aug 27, 2026
3 checks passed
@grimicorn
grimicorn deleted the agent/expand-home-output-dir branch August 27, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

has-suggestions PR carries follow-up suggestions for the improvement digest

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expand ~ and $HOME in the configured outputDirectory before writing

2 participants