sync from monorepo @ 5408ddc3
This commit is contained in:
@@ -6,11 +6,34 @@
|
|||||||
|
|
||||||
<p align="center">Core libraries for the Dirigent agent orchestration platform.</p>
|
<p align="center">Core libraries for the Dirigent agent orchestration platform.</p>
|
||||||
|
|
||||||
|
> **Under construction.** Dirigent is in active development. Most crates are experimental — APIs will change. There is nothing to install from this repository yet. The standalone tools listed below have their own repositories.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Dirigent is a multi-agent orchestration platform built around the Agent-Client Protocol (ACP). This repository contains the foundational library crates — the building blocks used by downstream tools such as [dirigate](https://git.g4b.org/dirigence/dirigate) and [fermata](https://git.g4b.org/dirigence/fermata).
|
## Standalone Tools
|
||||||
|
|
||||||
> **Downstream mirror.** Active development happens in an upstream monorepo. This repository is an export of the core library crates and is updated on each release. Issues and contributions should be directed to the upstream project.
|
These tools are developed in this monorepo but distributed as independent repositories. Install them from their own repos:
|
||||||
|
|
||||||
|
| Tool | Repository | Description |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| **fermata** | [git.g4b.org/dirigence/fermata](https://git.g4b.org/dirigence/fermata) | Policy gate for AI coding agents — `.botignore` enforcement |
|
||||||
|
| **anth** | [git.g4b.org/dirigence/dirigent_anth](https://git.g4b.org/dirigence/dirigent_anth) | Tools for working with Claude Code session data |
|
||||||
|
| **dirigate** | [git.g4b.org/dirigence/dirigate](https://git.g4b.org/dirigence/dirigate) | ACP bridge connecting stdio agents to Dirigent |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="architecture.svg" alt="Dirigent package architecture" width="720">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
**Layers top-to-bottom:**
|
||||||
|
- **Standalone Tools** — installable from their own repositories; depend on foundation crates
|
||||||
|
- **Orchestration** — multi-connector runtime, ACP server, task management, archival
|
||||||
|
- **Foundation** — protocol types, tool sandbox, configuration, auth
|
||||||
|
- **Integrations** — Matrix, Langfuse, Zed, and other external system connectors
|
||||||
|
- **Parsers** — readers for third-party session formats (OpenCode, ChatGPT, Codex)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -41,9 +64,7 @@ Dirigent is a multi-agent orchestration platform built around the Agent-Client P
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## Library Usage
|
||||||
|
|
||||||
### Library crates (via git dependency)
|
|
||||||
|
|
||||||
Add a crate to your `Cargo.toml`:
|
Add a crate to your `Cargo.toml`:
|
||||||
|
|
||||||
@@ -53,21 +74,9 @@ dirigent_protocol = { git = "https://git.g4b.org/dirigence/dirigent", path = "cr
|
|||||||
dirigent_core = { git = "https://git.g4b.org/dirigence/dirigent", path = "crates/dirigent_core" }
|
dirigent_core = { git = "https://git.g4b.org/dirigence/dirigent", path = "crates/dirigent_core" }
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace `dirigent_protocol` / `dirigent_core` with the crate you need. All crates follow the same pattern.
|
Replace the crate name and path with the one you need. All crates follow the same pattern.
|
||||||
|
|
||||||
### Binary crates (cargo install)
|
> **Expect breakage.** These are internal library crates under active development. Pin to a specific commit if you depend on stability.
|
||||||
|
|
||||||
**fermata** — policy gate CLI and Claude hook adapter:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install --git https://git.g4b.org/dirigence/dirigent --features cli
|
|
||||||
```
|
|
||||||
|
|
||||||
**anth** — Claude Code session inspector:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install --git https://git.g4b.org/dirigence/dirigent --bin anth_bear --features dirigent-paths
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 520" width="720" height="520" font-family="system-ui, sans-serif" font-size="11">
|
||||||
|
<defs>
|
||||||
|
<marker id="arr" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
|
||||||
|
<path d="M0,0 L8,3 L0,6" fill="none" stroke="#666" stroke-width="1"/>
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
<rect width="720" height="520" rx="8" fill="#f8f9fa"/>
|
||||||
|
<text x="360" y="24" text-anchor="middle" font-size="14" font-weight="bold" fill="#1a1a2e">Dirigent package architecture</text>
|
||||||
|
|
||||||
|
<!-- Layer: External Tools (top) -->
|
||||||
|
<rect x="20" y="40" width="680" height="70" rx="8" fill="#e8f0fe" stroke="#4285f4" stroke-width="1.5"/>
|
||||||
|
<text x="30" y="58" font-size="10" font-weight="600" fill="#4285f4">STANDALONE TOOLS</text>
|
||||||
|
<rect x="40" y="66" width="130" height="32" rx="6" fill="#fff" stroke="#4285f4"/>
|
||||||
|
<text x="105" y="86" text-anchor="middle" fill="#333" font-weight="600">fermata</text>
|
||||||
|
<rect x="200" y="66" width="130" height="32" rx="6" fill="#fff" stroke="#4285f4"/>
|
||||||
|
<text x="265" y="86" text-anchor="middle" fill="#333" font-weight="600">dirigate</text>
|
||||||
|
<rect x="360" y="66" width="130" height="32" rx="6" fill="#fff" stroke="#4285f4"/>
|
||||||
|
<text x="425" y="86" text-anchor="middle" fill="#333" font-weight="600">anth</text>
|
||||||
|
<text x="530" y="80" fill="#4285f4" font-size="10" font-style="italic">← own repos, installable</text>
|
||||||
|
|
||||||
|
<!-- Layer: Orchestration -->
|
||||||
|
<rect x="20" y="130" width="680" height="80" rx="8" fill="#fef9e7" stroke="#f0ad4e" stroke-width="1.5"/>
|
||||||
|
<text x="30" y="148" font-size="10" font-weight="600" fill="#b9770e">ORCHESTRATION</text>
|
||||||
|
<rect x="40" y="158" width="140" height="36" rx="6" fill="#fff" stroke="#f0ad4e"/>
|
||||||
|
<text x="110" y="172" text-anchor="middle" fill="#333" font-size="10" font-weight="600">dirigent_core</text>
|
||||||
|
<text x="110" y="185" text-anchor="middle" fill="#888" font-size="9">multi-connector runtime</text>
|
||||||
|
<rect x="210" y="158" width="140" height="36" rx="6" fill="#fff" stroke="#f0ad4e"/>
|
||||||
|
<text x="280" y="172" text-anchor="middle" fill="#333" font-size="10" font-weight="600">dirigent_acp_api</text>
|
||||||
|
<text x="280" y="185" text-anchor="middle" fill="#888" font-size="9">ACP server</text>
|
||||||
|
<rect x="380" y="158" width="140" height="36" rx="6" fill="#fff" stroke="#f0ad4e"/>
|
||||||
|
<text x="450" y="172" text-anchor="middle" fill="#333" font-size="10" font-weight="600">dirigent_taskrunner</text>
|
||||||
|
<text x="450" y="185" text-anchor="middle" fill="#888" font-size="9">background tasks</text>
|
||||||
|
<rect x="550" y="158" width="140" height="36" rx="6" fill="#fff" stroke="#f0ad4e"/>
|
||||||
|
<text x="620" y="172" text-anchor="middle" fill="#333" font-size="10" font-weight="600">dirigent_archivist</text>
|
||||||
|
<text x="620" y="185" text-anchor="middle" fill="#888" font-size="9">session archival</text>
|
||||||
|
|
||||||
|
<!-- Layer: Foundation -->
|
||||||
|
<rect x="20" y="230" width="680" height="80" rx="8" fill="#e8f8f0" stroke="#1e8449" stroke-width="1.5"/>
|
||||||
|
<text x="30" y="248" font-size="10" font-weight="600" fill="#1e8449">FOUNDATION</text>
|
||||||
|
<rect x="40" y="258" width="140" height="36" rx="6" fill="#fff" stroke="#1e8449"/>
|
||||||
|
<text x="110" y="272" text-anchor="middle" fill="#333" font-size="10" font-weight="600">dirigent_protocol</text>
|
||||||
|
<text x="110" y="285" text-anchor="middle" fill="#888" font-size="9">ACP types + messages</text>
|
||||||
|
<rect x="210" y="258" width="140" height="36" rx="6" fill="#fff" stroke="#1e8449"/>
|
||||||
|
<text x="280" y="272" text-anchor="middle" fill="#333" font-size="10" font-weight="600">dirigent_tools</text>
|
||||||
|
<text x="280" y="285" text-anchor="middle" fill="#888" font-size="9">tool sandbox</text>
|
||||||
|
<rect x="380" y="258" width="140" height="36" rx="6" fill="#fff" stroke="#1e8449"/>
|
||||||
|
<text x="450" y="272" text-anchor="middle" fill="#333" font-size="10" font-weight="600">dirigent_config</text>
|
||||||
|
<text x="450" y="285" text-anchor="middle" fill="#888" font-size="9">configuration</text>
|
||||||
|
<rect x="550" y="258" width="140" height="36" rx="6" fill="#fff" stroke="#1e8449"/>
|
||||||
|
<text x="620" y="272" text-anchor="middle" fill="#333" font-size="10" font-weight="600">dirigent_auth</text>
|
||||||
|
<text x="620" y="285" text-anchor="middle" fill="#888" font-size="9">authorization</text>
|
||||||
|
|
||||||
|
<!-- Layer: Integrations -->
|
||||||
|
<rect x="20" y="330" width="680" height="80" rx="8" fill="#f3e8fd" stroke="#8e44ad" stroke-width="1.5"/>
|
||||||
|
<text x="30" y="348" font-size="10" font-weight="600" fill="#8e44ad">INTEGRATIONS</text>
|
||||||
|
<rect x="40" y="358" width="100" height="36" rx="6" fill="#fff" stroke="#8e44ad"/>
|
||||||
|
<text x="90" y="372" text-anchor="middle" fill="#333" font-size="10" font-weight="600">matrix</text>
|
||||||
|
<text x="90" y="385" text-anchor="middle" fill="#888" font-size="9">session sharing</text>
|
||||||
|
<rect x="160" y="358" width="100" height="36" rx="6" fill="#fff" stroke="#8e44ad"/>
|
||||||
|
<text x="210" y="372" text-anchor="middle" fill="#333" font-size="10" font-weight="600">langfuse</text>
|
||||||
|
<text x="210" y="385" text-anchor="middle" fill="#888" font-size="9">observability</text>
|
||||||
|
<rect x="280" y="358" width="100" height="36" rx="6" fill="#fff" stroke="#8e44ad"/>
|
||||||
|
<text x="330" y="372" text-anchor="middle" fill="#333" font-size="10" font-weight="600">zed</text>
|
||||||
|
<text x="330" y="385" text-anchor="middle" fill="#888" font-size="9">editor</text>
|
||||||
|
|
||||||
|
<!-- Layer: Parsers -->
|
||||||
|
<rect x="20" y="430" width="680" height="70" rx="8" fill="#fdecea" stroke="#c0392b" stroke-width="1.5" stroke-dasharray="6,3"/>
|
||||||
|
<text x="30" y="448" font-size="10" font-weight="600" fill="#c0392b">PARSERS (third-party format readers)</text>
|
||||||
|
<rect x="40" y="458" width="120" height="30" rx="6" fill="#fff" stroke="#c0392b" stroke-dasharray="4,2"/>
|
||||||
|
<text x="100" y="477" text-anchor="middle" fill="#333" font-size="10">opencode_client</text>
|
||||||
|
<rect x="180" y="458" width="120" height="30" rx="6" fill="#fff" stroke="#c0392b" stroke-dasharray="4,2"/>
|
||||||
|
<text x="240" y="477" text-anchor="middle" fill="#333" font-size="10">dirigent_chatgpt</text>
|
||||||
|
<rect x="320" y="458" width="120" height="30" rx="6" fill="#fff" stroke="#c0392b" stroke-dasharray="4,2"/>
|
||||||
|
<text x="380" y="477" text-anchor="middle" fill="#333" font-size="10">dirigent_codex</text>
|
||||||
|
<rect x="460" y="458" width="120" height="30" rx="6" fill="#fff" stroke="#c0392b" stroke-dasharray="4,2"/>
|
||||||
|
<text x="520" y="477" text-anchor="middle" fill="#333" font-size="10">dirigent_inspector</text>
|
||||||
|
|
||||||
|
<!-- Dependency arrows -->
|
||||||
|
<line x1="105" y1="98" x2="110" y2="158" stroke="#666" stroke-width="1" marker-end="url(#arr)"/>
|
||||||
|
<line x1="265" y1="98" x2="110" y2="158" stroke="#666" stroke-width="1" marker-end="url(#arr)"/>
|
||||||
|
<line x1="265" y1="98" x2="280" y2="158" stroke="#666" stroke-width="1" marker-end="url(#arr)"/>
|
||||||
|
<line x1="110" y1="194" x2="110" y2="258" stroke="#666" stroke-width="1" marker-end="url(#arr)"/>
|
||||||
|
<line x1="110" y1="194" x2="280" y2="258" stroke="#666" stroke-width="1" marker-end="url(#arr)"/>
|
||||||
|
<line x1="280" y1="194" x2="110" y2="258" stroke="#666" stroke-width="1" marker-end="url(#arr)"/>
|
||||||
|
<line x1="450" y1="194" x2="450" y2="258" stroke="#666" stroke-width="1" marker-end="url(#arr)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.9 KiB |
@@ -8,7 +8,7 @@ Drop a `.botignore` file in your project root. Fermata reads it and blocks your
|
|||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
secrets/**
|
secrets/**
|
||||||
conf/localsettings.yaml
|
conf/settings.local.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
That's all it takes.
|
That's all it takes.
|
||||||
@@ -90,8 +90,8 @@ Create a `.botignore` at your project root. Gitignore syntax. Blocks both reads
|
|||||||
secrets/**
|
secrets/**
|
||||||
|
|
||||||
# Local config overrides
|
# Local config overrides
|
||||||
conf/localsettings.yaml
|
conf/settings.local.yaml
|
||||||
conf/localtestsettings.yaml
|
conf/settings.test.yaml
|
||||||
|
|
||||||
# Generated files — let the tools rebuild them, not patch them
|
# Generated files — let the tools rebuild them, not patch them
|
||||||
dist/**
|
dist/**
|
||||||
@@ -107,7 +107,7 @@ For cases where `.botignore`'s uniform read+write block isn't granular enough:
|
|||||||
```toml
|
```toml
|
||||||
[read]
|
[read]
|
||||||
# Block reading secrets outright
|
# Block reading secrets outright
|
||||||
patterns = [".env*", "secrets/**", "conf/localsettings.yaml"]
|
patterns = [".env*", "secrets/**", "conf/settings.local.yaml"]
|
||||||
|
|
||||||
[write]
|
[write]
|
||||||
# Allow reading vendor code but block patching it
|
# Allow reading vendor code but block patching it
|
||||||
@@ -156,12 +156,12 @@ When Claude attempts a `Read(.env)`, `Write(vendor/foo.js)`, or `Bash(rm ./secre
|
|||||||
|
|
||||||
## Real-world scenario
|
## Real-world scenario
|
||||||
|
|
||||||
A project has `.env`, `conf/localsettings.yaml`, and a `vendor/` tree it doesn't want patched. With `.botignore`:
|
A project has `.env`, `conf/settings.local.yaml`, and a `vendor/` tree it doesn't want patched. With `.botignore`:
|
||||||
|
|
||||||
```gitignore
|
```gitignore
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
conf/localsettings.yaml
|
conf/settings.local.yaml
|
||||||
vendor/**
|
vendor/**
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -169,8 +169,8 @@ Claude attempts to read credentials:
|
|||||||
|
|
||||||
```
|
```
|
||||||
Tool: Read
|
Tool: Read
|
||||||
Path: ./conf/localsettings.yaml
|
Path: ./conf/settings.local.yaml
|
||||||
Decision: BLOCK — matched rule "conf/localsettings.yaml" (.botignore)
|
Decision: BLOCK — matched rule "conf/settings.local.yaml" (.botignore)
|
||||||
```
|
```
|
||||||
|
|
||||||
Claude attempts to read application code:
|
Claude attempts to read application code:
|
||||||
@@ -186,7 +186,7 @@ Claude attempts to run `cat .env` via bash — which would bypass a path-only ch
|
|||||||
```toml
|
```toml
|
||||||
# botignore.toml
|
# botignore.toml
|
||||||
[bash]
|
[bash]
|
||||||
deny = ["cat .env*", "cat conf/localsettings*"]
|
deny = ["cat .env*", "cat conf/settings.local*"]
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// Markers checked in priority order when walking up from a target path.
|
/// Strong markers that definitively identify a project root.
|
||||||
const MARKERS: &[&str] = &["botignore.toml", ".botignore", ".git"];
|
const STRONG_MARKERS: &[&str] = &["botignore.toml", ".botignore.toml", ".git"];
|
||||||
|
|
||||||
/// Walk upward from `target` (or its parent if `target` is a file) looking
|
/// Walk upward from `target` (or its parent if `target` is a file) looking
|
||||||
/// for the nearest project root. Roots are identified by the presence of
|
/// for the nearest project root. Strong markers (`botignore.toml`,
|
||||||
/// any marker in `MARKERS`. Walks from the **target file's location**, not
|
/// `.botignore.toml`, `.git`) stop the walk immediately. A `.botignore`
|
||||||
/// from cwd, because agents `cd` around.
|
/// file is remembered as a fallback but does not stop the walk — the search
|
||||||
|
/// continues upward for a stronger boundary. If none is found, the
|
||||||
|
/// `.botignore` location is used.
|
||||||
pub fn find_project_root(target: &Path) -> Option<PathBuf> {
|
pub fn find_project_root(target: &Path) -> Option<PathBuf> {
|
||||||
let start = if target.is_file() {
|
let start = if target.is_file() {
|
||||||
target.parent()?
|
target.parent()?
|
||||||
@@ -14,14 +16,18 @@ pub fn find_project_root(target: &Path) -> Option<PathBuf> {
|
|||||||
target
|
target
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut fallback: Option<PathBuf> = None;
|
||||||
let mut current = Some(start);
|
let mut current = Some(start);
|
||||||
while let Some(dir) = current {
|
while let Some(dir) = current {
|
||||||
for marker in MARKERS {
|
for marker in STRONG_MARKERS {
|
||||||
if dir.join(marker).exists() {
|
if dir.join(marker).exists() {
|
||||||
return Some(dir.to_path_buf());
|
return Some(dir.to_path_buf());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if fallback.is_none() && dir.join(".botignore").exists() {
|
||||||
|
fallback = Some(dir.to_path_buf());
|
||||||
|
}
|
||||||
current = dir.parent();
|
current = dir.parent();
|
||||||
}
|
}
|
||||||
None
|
fallback
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ fn finds_botignore_toml_first() {
|
|||||||
let root = tmp.path();
|
let root = tmp.path();
|
||||||
fs::create_dir_all(root.join("sub/deep")).unwrap();
|
fs::create_dir_all(root.join("sub/deep")).unwrap();
|
||||||
fs::write(root.join("botignore.toml"), "").unwrap();
|
fs::write(root.join("botignore.toml"), "").unwrap();
|
||||||
fs::write(root.join(".botignore"), "").unwrap();
|
fs::write(root.join(".botignore.toml"), "").unwrap();
|
||||||
fs::create_dir_all(root.join(".git")).unwrap();
|
fs::create_dir_all(root.join(".git")).unwrap();
|
||||||
|
|
||||||
let target = root.join("sub/deep/file.rs");
|
let target = root.join("sub/deep/file.rs");
|
||||||
@@ -19,11 +19,11 @@ fn finds_botignore_toml_first() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn falls_back_to_botignore() {
|
fn finds_dot_botignore_toml() {
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
let root = tmp.path();
|
let root = tmp.path();
|
||||||
fs::create_dir_all(root.join("sub")).unwrap();
|
fs::create_dir_all(root.join("sub")).unwrap();
|
||||||
fs::write(root.join(".botignore"), "").unwrap();
|
fs::write(root.join(".botignore.toml"), "").unwrap();
|
||||||
|
|
||||||
let target = root.join("sub/file.rs");
|
let target = root.join("sub/file.rs");
|
||||||
fs::write(&target, "").unwrap();
|
fs::write(&target, "").unwrap();
|
||||||
@@ -32,6 +32,57 @@ fn falls_back_to_botignore() {
|
|||||||
assert_eq!(found, root);
|
assert_eq!(found, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn botignore_alone_does_not_stop_walk() {
|
||||||
|
// A bare .botignore is a policy file, not a project boundary.
|
||||||
|
// The walk should continue past it to find a real root marker.
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let root = tmp.path();
|
||||||
|
fs::create_dir_all(root.join("a/b")).unwrap();
|
||||||
|
fs::create_dir_all(root.join(".git")).unwrap();
|
||||||
|
fs::write(root.join("a/.botignore"), "*.secret").unwrap();
|
||||||
|
|
||||||
|
let target = root.join("a/b/file.rs");
|
||||||
|
fs::write(&target, "").unwrap();
|
||||||
|
|
||||||
|
// Should find root (with .git), NOT root/a (with .botignore)
|
||||||
|
let found = find_project_root(&target).unwrap();
|
||||||
|
assert_eq!(found, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn botignore_used_as_fallback() {
|
||||||
|
// If only .botignore exists (no strong marker), it serves as a fallback
|
||||||
|
// root so that policy is still enforced.
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let root = tmp.path();
|
||||||
|
fs::create_dir_all(root.join("sub")).unwrap();
|
||||||
|
fs::write(root.join(".botignore"), "*.secret").unwrap();
|
||||||
|
|
||||||
|
let target = root.join("sub/file.rs");
|
||||||
|
fs::write(&target, "").unwrap();
|
||||||
|
|
||||||
|
let found = find_project_root(&target).unwrap();
|
||||||
|
assert_eq!(found, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strong_marker_preferred_over_botignore_fallback() {
|
||||||
|
// .botignore at a/b/, .git at root — walk past .botignore, use root.
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let root = tmp.path();
|
||||||
|
fs::create_dir_all(root.join("a/b/c")).unwrap();
|
||||||
|
fs::create_dir_all(root.join(".git")).unwrap();
|
||||||
|
fs::write(root.join("a/b/.botignore"), "*.key").unwrap();
|
||||||
|
|
||||||
|
let target = root.join("a/b/c/file.rs");
|
||||||
|
fs::write(&target, "").unwrap();
|
||||||
|
|
||||||
|
// Should find root (with .git), not a/b (with .botignore)
|
||||||
|
let found = find_project_root(&target).unwrap();
|
||||||
|
assert_eq!(found, root);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn falls_back_to_git() {
|
fn falls_back_to_git() {
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
@@ -59,7 +110,7 @@ fn walks_up_from_file_path_not_cwd() {
|
|||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
let root = tmp.path();
|
let root = tmp.path();
|
||||||
fs::create_dir_all(root.join("a/b/c")).unwrap();
|
fs::create_dir_all(root.join("a/b/c")).unwrap();
|
||||||
fs::write(root.join("a/.botignore"), "").unwrap();
|
fs::write(root.join("a/botignore.toml"), "").unwrap();
|
||||||
|
|
||||||
let target = root.join("a/b/c/file.rs");
|
let target = root.join("a/b/c/file.rs");
|
||||||
fs::write(&target, "").unwrap();
|
fs::write(&target, "").unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user