feat(store): buttons install <name | tag:x> from a Source - #190
Conversation
First slice of the install client (#274), flat (no `store` namespace): - internal/store: a `Source` interface (Index + Fetch) + `LocalSource` (a directory) so the CLI is backend-agnostic; the registry HTTPSource (#275) is a drop-in later. - install resolves `name`, `name@version`, or `tag:<x>` (every button with the tag), installs each + its button.json `requires` deps transitively, and stamps source/version/content_hash into button.json. - cmd/install.go: `buttons install <name|tag:x> --source <dir>` ($BUTTONS_SOURCE). Removes the `cmd/store.go` stub. Refs autonoco/autono#274 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds the ChangesInstall Subcommand and Store Layer
Sequence Diagram(s)sequenceDiagram
participant User
participant installCmd
participant InstallSpec
participant LocalSource
participant Disk
User->>installCmd: buttons install alpha@1.0 --source ./src
installCmd->>InstallSpec: spec="alpha@1.0", sourceRef="./src"
InstallSpec->>InstallSpec: splitVersion → name="alpha", version="1.0"
InstallSpec->>LocalSource: Fetch("alpha", "1.0")
LocalSource-->>InstallSpec: Bundle
InstallSpec->>InstallSpec: stamp button.json (Source, Version, ContentHash)
InstallSpec->>Disk: mkdir buttons/alpha/pressed, write files
loop beta in alpha.Requires
InstallSpec->>LocalSource: Fetch("beta", "")
LocalSource-->>InstallSpec: Bundle
InstallSpec->>Disk: write buttons/beta/...
end
InstallSpec-->>installCmd: Result{Installed: ["alpha","beta"]}
installCmd-->>User: JSON or stderr output
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/install.go`:
- Around line 45-46: The config.WriteJSONError() call is discarding its error
return value, which masks write failures and prevents proper error reporting.
Instead of using the blank identifier to ignore the error from
config.WriteJSONError(), capture the returned error and check if it occurred. If
config.WriteJSONError() returns an error, return that error immediately so the
caller knows about the write failure. Only return errSilent as a fallback when
the JSON write operation succeeds. Apply this fix to all occurrences of the
config.WriteJSONError() pattern mentioned in the comment (including the second
location at lines 55-56).
In `@internal/store/install.go`:
- Around line 105-108: The file mode selection logic in the code block starting
at line 105 currently grants 0700 permissions only to files starting with
"main.", but the repository guideline requires 0700 for all code files and 0600
only for spec/history JSON files. Refactor the permission logic to check if the
file path (rel variable) is a spec/history JSON file instead of checking for the
"main." prefix. If the file is a spec/history JSON file, set mode to 0600,
otherwise default to 0700 to align with the policy that all code files require
executable permissions.
- Around line 104-111: The code in the bundle file installation loop has a path
traversal vulnerability where the relative path variable `rel` is not validated
before being joined with the target directory `dir` in the filepath.Join call.
An attacker could provide paths containing traversal sequences like `../` to
escape the intended installation directory. Add path containment validation
before the os.WriteFile call to ensure the resolved path stays within the target
directory, either by rejecting paths containing `..` or by validating that the
absolute resolved path has the target directory as its prefix.
In `@internal/store/source.go`:
- Around line 90-116: The LocalSource.Fetch method ignores the version parameter
(currently marked with underscore) which breaks install-by-version semantics.
Instead of discarding this parameter, use it to validate that the requested
version matches the version found in the unmarshaled button.Button object after
parsing button.json. After unmarshaling the button.Button, compare the requested
version with b.Version and return an error if they do not match, ensuring that
the method honors version-specific install requests.
- Around line 90-103: The Fetch method in LocalSource has a path traversal
vulnerability where the unvalidated name parameter is directly joined with
s.Root using filepath.Join, allowing attackers to escape the source root with
inputs like ../../. After constructing the dir path by joining s.Root with name,
validate that the resolved path still remains within s.Root by using
filepath.Clean and filepath.Abs to ensure the final path starts with the root
directory. This prevents unauthorized access to files outside the intended
source directory.
In `@internal/store/store_test.go`:
- Around line 17-25: The writeSourceButton test fixture uses non-compliant
permission modes that violate the internal package policy. Change the
os.MkdirAll call to use 0700 instead of 0755 for the directory creation, change
the button.json file creation to use 0600 instead of 0644 since it is a
spec/history JSON file, and change the main.sh file creation to use 0700 instead
of 0644 since it is a code file. These changes ensure all file and directory
permissions comply with the required standards for internal package tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d9cc7569-e974-4ff2-828d-7155ada4fb32
📒 Files selected for processing (5)
cmd/install.gocmd/store.gointernal/store/install.gointernal/store/source.gointernal/store/store_test.go
💤 Files with no reviewable changes (1)
- cmd/store.go
| _ = config.WriteJSONError("VALIDATION_ERROR", msg) | ||
| return errSilent |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Don’t discard JSON write failures on error paths
If config.WriteJSONError(...) fails, returning errSilent hides the real failure mode. Return the write error first, then fall back to errSilent only when emission succeeds.
Small fix
@@
if jsonOutput {
- _ = config.WriteJSONError("VALIDATION_ERROR", msg)
+ if werr := config.WriteJSONError("VALIDATION_ERROR", msg); werr != nil {
+ return werr
+ }
return errSilent
}
@@
if jsonOutput {
- _ = config.WriteJSONError("INSTALL_ERROR", err.Error())
+ if werr := config.WriteJSONError("INSTALL_ERROR", err.Error()); werr != nil {
+ return werr
+ }
return errSilent
}Also applies to: 55-56
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/install.go` around lines 45 - 46, The config.WriteJSONError() call is
discarding its error return value, which masks write failures and prevents
proper error reporting. Instead of using the blank identifier to ignore the
error from config.WriteJSONError(), capture the returned error and check if it
occurred. If config.WriteJSONError() returns an error, return that error
immediately so the caller knows about the write failure. Only return errSilent
as a fallback when the JSON write operation succeeds. Apply this fix to all
occurrences of the config.WriteJSONError() pattern mentioned in the comment
(including the second location at lines 55-56).
| mode := os.FileMode(0600) | ||
| if strings.HasPrefix(rel, "main.") { | ||
| mode = 0700 // #nosec G302 -- code files need the exec bit to run via sh/python/node | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
File mode policy in internal/** does not fully match repository guideline
Current logic grants 0700 only to main.*, but your guideline requires 0700 for code files and 0600 only for spec/history JSON files. This likely under-permissions non-main.* code artifacts.
As per coding guidelines, "Set file permissions to 0700 for data directories and code files, and 0600 for spec/history JSON files".
Policy-aligned mode selection example
@@
- mode := os.FileMode(0600)
- if strings.HasPrefix(rel, "main.") {
- mode = 0700 // `#nosec` G302 -- code files need the exec bit to run via sh/python/node
- }
+ mode := os.FileMode(0700) // code/data default in internal/** policy
+ if rel == "button.json" || strings.HasSuffix(rel, ".history.json") {
+ mode = 0600
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| mode := os.FileMode(0600) | |
| if strings.HasPrefix(rel, "main.") { | |
| mode = 0700 // #nosec G302 -- code files need the exec bit to run via sh/python/node | |
| } | |
| mode := os.FileMode(0700) // code/data default in internal/** policy | |
| if rel == "button.json" || strings.HasSuffix(rel, ".history.json") { | |
| mode = 0600 | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/store/install.go` around lines 105 - 108, The file mode selection
logic in the code block starting at line 105 currently grants 0700 permissions
only to files starting with "main.", but the repository guideline requires 0700
for all code files and 0600 only for spec/history JSON files. Refactor the
permission logic to check if the file path (rel variable) is a spec/history JSON
file instead of checking for the "main." prefix. If the file is a spec/history
JSON file, set mode to 0600, otherwise default to 0700 to align with the policy
that all code files require executable permissions.
Source: Coding guidelines
| if err := os.MkdirAll(dir, 0755); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| data, _ := json.MarshalIndent(&b, "", " ") | ||
| if err := os.WriteFile(filepath.Join(dir, "button.json"), data, 0644); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if err := os.WriteFile(filepath.Join(dir, "main.sh"), []byte("#!/bin/sh\necho hi\n"), 0644); err != nil { | ||
| t.Fatal(err) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Use repository-required permission modes in test fixtures.
writeSourceButton currently creates directories/files with 0755/0644, which violates the internal/**/*.go permission policy and weakens permission-sensitive coverage.
Suggested patch
- if err := os.MkdirAll(dir, 0755); err != nil {
+ if err := os.MkdirAll(dir, 0700); err != nil {
t.Fatal(err)
}
data, _ := json.MarshalIndent(&b, "", " ")
- if err := os.WriteFile(filepath.Join(dir, "button.json"), data, 0644); err != nil {
+ if err := os.WriteFile(filepath.Join(dir, "button.json"), data, 0600); err != nil {
t.Fatal(err)
}
- if err := os.WriteFile(filepath.Join(dir, "main.sh"), []byte("#!/bin/sh\necho hi\n"), 0644); err != nil {
+ if err := os.WriteFile(filepath.Join(dir, "main.sh"), []byte("#!/bin/sh\necho hi\n"), 0700); err != nil {
t.Fatal(err)
}As per coding guidelines, "internal/**/*.go: Set file permissions to 0700 for data directories and code files, and 0600 for spec/history JSON files".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if err := os.MkdirAll(dir, 0755); err != nil { | |
| t.Fatal(err) | |
| } | |
| data, _ := json.MarshalIndent(&b, "", " ") | |
| if err := os.WriteFile(filepath.Join(dir, "button.json"), data, 0644); err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := os.WriteFile(filepath.Join(dir, "main.sh"), []byte("#!/bin/sh\necho hi\n"), 0644); err != nil { | |
| t.Fatal(err) | |
| if err := os.MkdirAll(dir, 0700); err != nil { | |
| t.Fatal(err) | |
| } | |
| data, _ := json.MarshalIndent(&b, "", " ") | |
| if err := os.WriteFile(filepath.Join(dir, "button.json"), data, 0600); err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := os.WriteFile(filepath.Join(dir, "main.sh"), []byte("#!/bin/sh\necho hi\n"), 0700); err != nil { | |
| t.Fatal(err) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/store/store_test.go` around lines 17 - 25, The writeSourceButton
test fixture uses non-compliant permission modes that violate the internal
package policy. Change the os.MkdirAll call to use 0700 instead of 0755 for the
directory creation, change the button.json file creation to use 0600 instead of
0644 since it is a spec/history JSON file, and change the main.sh file creation
to use 0700 instead of 0644 since it is a code file. These changes ensure all
file and directory permissions comply with the required standards for internal
package tests.
Source: Coding guidelines
Address three CodeRabbit findings on the install path: - read traversal: LocalSource.Fetch rejects names that aren't a single path component (validName), so a CLI spec or a button's `requires` can't escape the source root via "../". - write traversal: install() routes every bundle file key through safeJoin, containing writes to the button dir — defense for the untrusted HTTPSource registry (#275) behind the Source interface. - version pin: Fetch honors the version arg, erroring on a mismatch instead of silently returning whatever is on disk. Tests: traversal name/bundle rejection, version mismatch, safeJoin table. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/store/source.go (1)
118-137: 🔒 Security & Privacy | 🔴 CriticalReject symlinked source files before reading bundle contents.
validNameensuresdirstays unders.Root, butos.ReadFile(filepath.Join(dir, e.Name()))at line 133 follows symlinks. If a local source directory contains a symlinked entry likesecret.txt -> /etc/passwd, the code will read the target file instead of rejecting it, bypassing the read-containment guarantee.Proposed fix
files := map[string][]byte{} for _, e := range entries { - if e.IsDir() { + path := filepath.Join(dir, e.Name()) + info, err := os.Lstat(path) + if err != nil { + return nil, err + } + if info.Mode()&os.ModeSymlink != 0 { + return nil, fmt.Errorf("button %q: symlink %q is not allowed", name, e.Name()) + } + if info.IsDir() { continue // skip pressed/ } // `#nosec` G304 -- dir/name both come from enumerated entries under s.Root. - data, err := os.ReadFile(filepath.Join(dir, e.Name())) + data, err := os.ReadFile(path) if err != nil { return nil, err }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/store/source.go` around lines 118 - 137, The Fetch method of LocalSource does not check for symlinks before reading files with os.ReadFile, which allows symlinked entries to bypass the containment guarantee by reading files outside s.Root. In the loop iterating through entries returned by os.ReadDir, before calling os.ReadFile on each entry, add a check to determine if the entry is a symlink (using the IsSymlink method available on os.DirEntry) and skip that entry if it is, ensuring that only regular files within the designated directory are read.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/store/store_test.go`:
- Around line 166-181: The path containment assertion in TestSafeJoin using
strings.HasPrefix(dst, dir) is insufficient because it only checks string prefix
matching, allowing paths like /tmp/install/alpha2/file to incorrectly pass as
contained within /tmp/install/alpha. Update the assertion to use a
separator-aware containment check that ensures the destination path either
equals dir exactly or has dir followed by a path separator, similar to how the
production safeJoin function should validate containment. This prevents
sibling-directory prefix escapes from passing the test.
- Around line 184-189: The install function creates the button directory before
validating all bundle keys for safety, which can leave partial installations
behind when a traversal bundle is rejected. Refactor the install function to
prevalidate all safeJoin results before performing any directory creation or
file writing operations, ensuring that bundle safety checks happen first.
Additionally, extend the TestInstallRejectsTraversalBundle test to assert that
no button directory or related files exist after the rejected traversal bundle
install attempt, verifying that rejected installations do not leave behind any
partial state.
---
Outside diff comments:
In `@internal/store/source.go`:
- Around line 118-137: The Fetch method of LocalSource does not check for
symlinks before reading files with os.ReadFile, which allows symlinked entries
to bypass the containment guarantee by reading files outside s.Root. In the loop
iterating through entries returned by os.ReadDir, before calling os.ReadFile on
each entry, add a check to determine if the entry is a symlink (using the
IsSymlink method available on os.DirEntry) and skip that entry if it is,
ensuring that only regular files within the designated directory are read.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8c23fb56-1e88-4bbf-8be7-721512c783b2
📒 Files selected for processing (3)
internal/store/install.gointernal/store/source.gointernal/store/store_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/store/install.go
| func TestSafeJoin(t *testing.T) { | ||
| dir := filepath.Clean("/tmp/install/alpha") | ||
| for _, r := range []string{"../evil", "../../etc/passwd", "..", "/abs/path", "sub/../../escape"} { | ||
| if _, err := safeJoin(dir, r); err == nil { | ||
| t.Errorf("safeJoin(%q) should be rejected", r) | ||
| } | ||
| } | ||
| for _, r := range []string{"button.json", "main.sh", "AGENT.md", "sub/file.txt"} { | ||
| dst, err := safeJoin(dir, r) | ||
| if err != nil { | ||
| t.Errorf("safeJoin(%q) should be allowed: %v", r, err) | ||
| } | ||
| if !strings.HasPrefix(dst, dir) { | ||
| t.Errorf("safeJoin(%q) escaped: %q", r, dst) | ||
| } | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Tighten the allowed-path assertion to catch sibling-prefix escapes.
Line 178 accepts any path with prefix dir, so a regression returning /tmp/install/alpha2/file would pass. Mirror the production separator-aware containment check.
Proposed test fix
- if !strings.HasPrefix(dst, dir) {
+ if !strings.HasPrefix(dst, dir+string(filepath.Separator)) {
t.Errorf("safeJoin(%q) escaped: %q", r, dst)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/store/store_test.go` around lines 166 - 181, The path containment
assertion in TestSafeJoin using strings.HasPrefix(dst, dir) is insufficient
because it only checks string prefix matching, allowing paths like
/tmp/install/alpha2/file to incorrectly pass as contained within
/tmp/install/alpha. Update the assertion to use a separator-aware containment
check that ensures the destination path either equals dir exactly or has dir
followed by a path separator, similar to how the production safeJoin function
should validate containment. This prevents sibling-directory prefix escapes from
passing the test.
| func TestInstallRejectsTraversalBundle(t *testing.T) { | ||
| t.Setenv("BUTTONS_HOME", t.TempDir()) | ||
| if _, err := install(traversalSource{}, "evil", "", "local:test"); err == nil { | ||
| t.Fatal("install should reject a bundle file that escapes the button dir") | ||
| } | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Assert rejected traversal bundles leave no partial install behind.
The supplied install path creates the button directory before all bundle keys are proven safe, and then validates/writes per file. This test can pass while a rejected bundle leaves button.json, pressed/, or other valid files behind; prevalidate all safeJoin results before creating/writing, and extend the test to assert no installed button directory remains. See internal/store/install.go:95-109 in the provided context.
Suggested production shape
+ dsts := make(map[string]string, len(bundle.Files))
+ for rel := range bundle.Files {
+ dst, err := safeJoin(dir, rel)
+ if err != nil {
+ return nil, err
+ }
+ dsts[rel] = dst
+ }
if err := os.MkdirAll(filepath.Join(dir, "pressed"), 0700); err != nil {
return nil, fmt.Errorf("create button dir: %w", err)
}
for rel, data := range bundle.Files {
- dst, err := safeJoin(dir, rel)
- if err != nil {
- return nil, err
- }
+ dst := dsts[rel]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/store/store_test.go` around lines 184 - 189, The install function
creates the button directory before validating all bundle keys for safety, which
can leave partial installations behind when a traversal bundle is rejected.
Refactor the install function to prevalidate all safeJoin results before
performing any directory creation or file writing operations, ensuring that
bundle safety checks happen first. Additionally, extend the
TestInstallRejectsTraversalBundle test to assert that no button directory or
related files exist after the rejected traversal bundle install attempt,
verifying that rejected installations do not leave behind any partial state.
rd2) Follow-up to ad3aeb4 addressing the re-review: - install() pre-validates every bundle path via safeJoin before creating the button dir or writing, so a rejected traversal bundle leaves no partial state behind. - LocalSource.Fetch skips symlink entries instead of following them out of the source root (a shared/untrusted pack could symlink to secrets). - TestSafeJoin uses a separator-aware containment assertion; the reject test asserts no dir is left behind; add TestFetchSkipsSymlinks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (#192) `buttons update` now brings everything current in one command: the CLI binary (existing self-updater) AND the content of installed buttons, re-fetched from the source each was installed from. - internal/store/update.go: UpdateInstalled reconciles every installed button against its stamped `source`. Drift is detected by content hash (the install-time SHA256 from #190 vs a fresh Fetch). Drifted buttons are re-installed (re-verified + re-stamped); un-sourced (hand-authored) or not-yet-resolvable (registry, #275) buttons are skipped, never fatal. SourceResolver is injectable; DefaultSourceResolver wires `local:<dir>`. - cmd/update.go: factor the binary path into runBinaryUpdate; add --binary / --content scoping (default = both) and make --check report available content updates too. A Homebrew-managed binary is now a soft skip (was a hard error) so content still updates in a combined run. Tested: store unit tests (drift detect/apply, --check is read-only, skip un-sourced + registry-pinned, resolver) + end-to-end install→drift →update smoke. Stacked on #190 (needs internal/store). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
First slice of the install client (#274), flat — drops the
storenamespace:internal/store— aSourceinterface (Index,Fetch) so the CLI is backend-agnostic.LocalSource(a directory) ships now; the registryHTTPSource(MCP 2026-07-28: unknown tool name in tools/call → -32602 (not -32601) #275) is a drop-in later.name,name@version, ortag:<x>(every button carrying the tag). Installs each button + itsrequiresdeps transitively, and stampssource/version/content_hashinto the installedbutton.json(pinning).cmd/install.go—buttons install <name | tag:x> --source <dir>(or$BUTTONS_SOURCE). Removes thecmd/store.gostub.Model
Buttons are the atomic unit; collections are tags (
install tag:autono-cal); deps come from buttonrequires. No "pack" object.Test
go build ./...,go vet,internal/storetests pass (install-by-name + deps, install-by-tag, no-match error, version parse).buttons install tag:demo --source ./packinstalls the tagged button with provenance stamped.Follow-ups (this PR is intentionally scoped to install)
buttons update(binary + content) reusing a factoredinternal/updaterstore.lockHTTPSource(MCP 2026-07-28: unknown tool name in tools/call → -32602 (not -32601) #275)Refs autonoco/autono#274
Summary by CodeRabbit
installCLI subcommand to install button specs by name ortag:<tag>, including transitive dependency resolutionname@version--source/BUTTONS_SOURCEto select the source directory