Skip to content

refactor(hooks/builtins): inline & simplify add_prompt_files#2530

Merged
dgageot merged 3 commits intodocker:mainfrom
dgageot:board/move-readpromptfiles-to-hooks-builtins-p-2efe0b2e
Apr 27, 2026
Merged

refactor(hooks/builtins): inline & simplify add_prompt_files#2530
dgageot merged 3 commits intodocker:mainfrom
dgageot:board/move-readpromptfiles-to-hooks-builtins-p-2efe0b2e

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented Apr 27, 2026

Moves session.ReadPromptFiles (and its private helpers) into the only
package that consumed it — pkg/hooks/builtins/add_prompt_files.go
then simplifies the architecture and tightens up the tests. No
production behavior change.

Commits

  1. refactor(hooks/builtins): inline readPromptFiles into add_prompt_files

    • Folds session.ReadPromptFiles, findFileInHierarchy, and
      isFile into the builtin as unexported helpers; drops the
      pkg/session import.
    • Relocates the seven prompt-file tests as internal tests in
      pkg/hooks/builtins.
    • Deletes pkg/session/prompt_file{,_test}.go.
  2. refactor(hooks/builtins): split discovery from I/O in add_prompt_files

    • Replaces the awkward middle layer readPromptFiles(workDir, filename) ([]string, error)
      (which mixed path discovery with I/O just to bubble a swallowed
      error up to the hook) with promptFilePaths(workDir, filename) []string
      — pure path discovery, no error return.
    • Hook now owns all I/O and the log-and-skip policy in one place:
      a single os.ReadFile + slog.Warn site.
    • Dedup uses an explicit slices.Contains instead of an inline path
      comparison.
    • Tests retargeted at promptFilePaths, asserting on discovered
      paths (more precise than round-tripping byte content). Content
      reading remains covered end-to-end by TestAddPromptFiles* in
      builtins_test.go.
  3. test(hooks/builtins): make prompt-file tests hermetic via injected homeDir

    • Fixes a real test-quality issue carried over from the original
      pkg/session tests: a homeFixture helper that wrote into the
      real \$HOME and could silently overwrite a user file with a
      colliding name (skip-cleanup-when-it-existed semantics).
    • promptFilePaths(workDir, homeDir, filename) now takes
      homeDir explicitly; the hook resolves it once via
      os.UserHomeDir() (errors → "", which disables the home
      branch). Same observable behavior in production.
    • Tests pass t.TempDir() for both `workDir` and `homeDir`,
      so they never touch the real user environment. Drops the
      homeFixture helper, the t.Skip, the unique-filename hack,
      and the conditional cleanup.
    • Adds two tests the previous shape couldn't express:
      TestPromptFilePathsWorkDirNestedInHome (workdir as a
      subdirectory of home, dedup via the hierarchy walk) and
      TestPromptFilePathsEmptyHomeDirSkipsHomeLookup (explicit
      disable contract).

Validation

  • `golangci-lint run ./...` → 0 issues
  • `go run ./lint .` → 777 files inspected, no offenses
  • `go mod tidy --diff` → clean
  • `go test ./...` → all packages pass

dgageot added 3 commits April 27, 2026 14:26
The session.ReadPromptFiles helper had only one caller — the
add_prompt_files builtin — so move it (and its private
findFileInHierarchy / isFile helpers) into the builtin itself as
unexported functions and drop the cross-package dependency.

- pkg/hooks/builtins/add_prompt_files.go: inlines readPromptFiles,
  findFileInHierarchy, isFile; removes the pkg/session import.
- pkg/hooks/builtins/add_prompt_files_test.go: relocates the seven
  prompt-file behavior tests as internal tests so they can exercise
  the now-unexported readPromptFiles directly.
- pkg/session/prompt_file.go and prompt_file_test.go: removed.

No behavior change: the public add_prompt_files hook keeps the same
working-dir-hierarchy + home-folder lookup, dedup, and missing-file
tolerance.

Assisted-By: docker-agent
The previous shape stacked three layers — addPromptFiles → readPromptFiles
→ findFileInHierarchy/home-lookup — and the middle layer mixed path
discovery with file reading just to surface a (rare, swallowed) read
error to the hook for logging.

Flatten to two layers with a single responsibility each:

- promptFilePaths(workDir, filename) []string — pure path discovery,
  no I/O errors (Stat failures already surface as "not a regular
  file"). Returns the workdir-hierarchy match first, then the
  home-dir match if it exists and isn't the same path. Dedup is now
  an explicit slices.Contains rather than an inline path comparison.
- addPromptFiles — owns the read loop and the log-and-skip policy in
  one place: per-path os.ReadFile with a single slog.Warn site.

Behavioral envelope unchanged: same workdir-hierarchy + home-folder
lookup, same dedup, same missing-file tolerance, same turn_start
output joined with "\n\n". Read errors are now logged per path
rather than per filename, which is strictly more informative and
keeps a partially-readable filename's surviving file contributing.

Tests retargeted at promptFilePaths, asserting on the discovered
paths (more precise than round-tripping byte content). End-to-end
content reading remains covered by TestAddPromptFiles* in
builtins_test.go.

Assisted-By: docker-agent
…meDir

The previous test file used a homeFixture helper that wrote into the
real $HOME with a unique-looking filename and conditionally cleaned
up. That had three problems:

1. If a real file with the test's "unique" name happened to exist
   in the user's home directory, the helper would overwrite it
   without cleaning up afterwards — silent data loss.
2. The `_, existed := os.Stat(path)` variable name lied about its
   semantics: existed is the error, so existed != nil means the
   file did *not* exist.
3. Tests required a writable $HOME and used t.Skip to mask write
   failures, blocking any future move to t.Setenv (which is also
   incompatible with t.Parallel).

Fix by dependency-injecting homeDir:

- promptFilePaths(workDir, homeDir, filename) now takes homeDir
  explicitly. homeDir == "" disables the home lookup entirely.
- addPromptFiles resolves homeDir once via os.UserHomeDir() and
  passes it in (errors fall back to "" — same observable behavior
  as the previous err == nil guard).
- Tests pass t.TempDir() for both workDir and homeDir, so they
  never touch the real user environment. Drops homeFixture, the
  t.Skip, the unique-filename hack, and the conditional cleanup.

Two new tests cover regressions the previous shape couldn't:

- TestPromptFilePathsWorkDirNestedInHome: workDir is a subdirectory
  of homeDir and the prompt file lives at homeDir; the hierarchy
  walk finds it and the home lookup must not duplicate it.
- TestPromptFilePathsEmptyHomeDirSkipsHomeLookup: homeDir == ""
  unambiguously disables the home-dir branch.

writePrompt and makeDir helpers replace the per-test boilerplate.
No behavior change in production code.

Assisted-By: docker-agent
@dgageot dgageot requested a review from a team as a code owner April 27, 2026 12:44
@dgageot dgageot merged commit bc4893c into docker:main Apr 27, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants