Skip to content

Commit 67aabbe

Browse files
committed
fix: align nested-glob status source resolution with apply and doctor
- Resolve nested-glob source relative to project root in status (matching apply and doctor), not source_dir - Document Node.js >=18 as the required minimum with v24.14.0+ as optional - Correct status-output docs: nested-glob emits a single target-level entry - Add regression test for nested-glob status entry structure
1 parent bf48079 commit 67aabbe

4 files changed

Lines changed: 66 additions & 3 deletions

File tree

src/commands/status.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,13 @@ pub fn collect_status_entries(linker: &Linker, config_path: &Path) -> Result<Vec
9999
}
100100

101101
let destination = linker.project_root().join(&target.destination);
102-
let source_path = source_dir.join(&target.source);
102+
// NestedGlob resolves `source` relative to the project root (matching apply and
103+
// doctor); all other sync types resolve it relative to source_dir.
104+
let source_path = if target.sync_type == SyncType::NestedGlob {
105+
linker.project_root().join(&target.source)
106+
} else {
107+
source_dir.join(&target.source)
108+
};
103109

104110
let entry = match target.sync_type {
105111
SyncType::Symlink => validate_symlink_entry(

src/commands/status_tests.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,59 @@ mod tests {
279279
assert!(entry_is_problematic(entry));
280280
}
281281

282+
#[test]
283+
#[cfg(unix)]
284+
fn test_collect_status_entries_nested_glob_emits_single_target_level_entry() {
285+
let temp_dir = TempDir::new().unwrap();
286+
287+
// Search root lives at the project root (matching apply/doctor semantics).
288+
let search_root = temp_dir.path().join("packages");
289+
fs::create_dir_all(search_root.join("alpha")).unwrap();
290+
fs::create_dir_all(search_root.join("beta")).unwrap();
291+
fs::write(search_root.join("alpha").join("AGENTS.md"), "# Alpha").unwrap();
292+
fs::write(search_root.join("beta").join("AGENTS.md"), "# Beta").unwrap();
293+
294+
let config = r#"
295+
[agents.claude]
296+
enabled = true
297+
298+
[agents.claude.targets.nested]
299+
source = "packages"
300+
pattern = "**/AGENTS.md"
301+
destination = "packages/{relative_path}/CLAUDE.md"
302+
type = "nested-glob"
303+
"#;
304+
305+
let (linker, config_path) = load_linker(&temp_dir, config);
306+
let entries = collect_status_entries(&linker, &config_path).unwrap();
307+
308+
// A single target-level entry, NOT one entry per discovered match.
309+
assert_eq!(entries.len(), 1);
310+
let entry = &entries[0];
311+
assert_eq!(entry.sync_type, "nested-glob");
312+
// The destination is the configured template, not an expanded match.
313+
assert!(
314+
entry
315+
.destination
316+
.ends_with("packages/{relative_path}/CLAUDE.md")
317+
);
318+
// The expected source resolves from the project root (where the search root lives).
319+
assert!(
320+
entry
321+
.expected_source
322+
.as_deref()
323+
.unwrap()
324+
.ends_with("packages")
325+
);
326+
327+
let json = serde_json::to_value(&entries).unwrap();
328+
let arr = json.as_array().unwrap();
329+
assert_eq!(arr.len(), 1);
330+
assert_eq!(arr[0]["sync_type"], "nested-glob");
331+
assert_eq!(arr[0]["destination_kind"], "missing");
332+
assert!(!arr[0]["issues"].as_array().unwrap().is_empty());
333+
}
334+
282335
#[test]
283336
#[cfg(unix)]
284337
fn test_collect_status_hints_reports_recognized_mode_mismatch_without_problem() {

website/docs/src/content/docs/contributing/development.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ We use `thiserror` for defining custom, recoverable errors in the core logic and
4242
The JS workspace acts as a wrapper to provide an easy installation path via `pnpm/npm`. It manages platform-specific binary downloads and provides a programmatic API.
4343

4444
### Tools & Version
45-
- **Node.js**: v24.14.0+ (recommended for development)
45+
- **Node.js**: >=18 (required, matching `npm/agentsync`'s `engines`)
46+
- **Node.js (development)**: v24.14.0+ (optional recommendation, see `.nvmrc`)
4647
- **Package Manager**: `pnpm` 10+
4748
- **Location**: `npm/agentsync/`
4849

website/docs/src/content/docs/reference/status-output.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ expected`.
4848

4949
### `nested-glob`
5050

51-
Validates the expanded destination for each discovered match as a managed symlink.
51+
Emits a single target-level entry. The destination is the configured template (for example
52+
`packages/{relative_path}/CLAUDE.md`) and `expected_source` is the search root resolved from the
53+
project root. Individual expanded matches are **not** emitted as separate entries; run
54+
`agentsync apply` or inspect the filesystem to see per-match symlinks.
5255

5356
### `module-map`
5457

0 commit comments

Comments
 (0)