From 9d77ed6852c4a462d27bab69a4c96614a66fd7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Villase=C3=B1or=20Montfort?= <195970+montfort@users.noreply.github.com> Date: Fri, 18 Sep 2026 00:02:45 -0600 Subject: [PATCH] =?UTF-8?q?fix(core+cli+loom):=20core-0.10.1=20/=20cli-3.4?= =?UTF-8?q?9.2=20/=20loom-0.7.1=20=E2=80=94=20every=20project-wide=20walke?= =?UTF-8?q?r=20skips=20nested=20checkouts=20(#434)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #435 (Baton half). straymark-core's source scanner and two CLI walkers also read another checkout nested in the project — a linked worktree (`.worktrees//`), a submodule, a nested clone — as the project's own code: on an adopter repo, `architecture generate` invented a `.worktrees` component holding a full copy of it. - core: `walk::is_nested_checkout` (a sub-directory with its own `.git`) used by `architecture::collect_source_files` → architecture generate / sync / validate, status --where, Loom's architecture view + Intent plane, Baton's overlay. - cli: tree_grep (followups verify --claims, analyze declared-vs-wired) and the analyze walker use the same rule. - baton: reuses the core rule (behavior unchanged, no release). - straymark-core bound moved to 0.10.1 in cli, loom AND baton together. Co-Authored-By: Claude Opus 5 (1M context) --- ...core-nested-checkout-rule-434-follow-up.md | 85 +++++++++++++++++++ CHANGELOG.md | 15 ++++ Cargo.lock | 6 +- README.md | 2 +- cli/Cargo.toml | 4 +- cli/src/analysis_engine.rs | 18 +++- cli/src/tree_grep.rs | 18 +++- core/Cargo.toml | 2 +- core/src/architecture/gather.rs | 25 +++++- core/src/lib.rs | 1 + core/src/walk.rs | 38 +++++++++ docs/adopters/CLI-REFERENCE.md | 2 +- docs/i18n/es/README.md | 2 +- docs/i18n/es/adopters/CLI-REFERENCE.md | 2 +- docs/i18n/zh-CN/README.md | 2 +- docs/i18n/zh-CN/adopters/CLI-REFERENCE.md | 2 +- experiment-baton/Cargo.toml | 2 +- experiment-baton/src/scan.rs | 12 +-- experiment-loom/CHANGELOG.md | 9 ++ experiment-loom/Cargo.toml | 4 +- 20 files changed, 223 insertions(+), 28 deletions(-) create mode 100644 .straymark/07-ai-audit/agent-logs/AILOG-2026-09-18-001-core-nested-checkout-rule-434-follow-up.md create mode 100644 core/src/walk.rs diff --git a/.straymark/07-ai-audit/agent-logs/AILOG-2026-09-18-001-core-nested-checkout-rule-434-follow-up.md b/.straymark/07-ai-audit/agent-logs/AILOG-2026-09-18-001-core-nested-checkout-rule-434-follow-up.md new file mode 100644 index 00000000..e829059c --- /dev/null +++ b/.straymark/07-ai-audit/agent-logs/AILOG-2026-09-18-001-core-nested-checkout-rule-434-follow-up.md @@ -0,0 +1,85 @@ +--- +id: AILOG-2026-09-18-001 +title: core nested-checkout rule — every project-wide walker skips other checkouts (#434 follow-up) +status: accepted +created: 2026-09-18 +agent: claude-opus-5-1m +confidence: high +review_required: false +risk_level: low +eu_ai_act_risk: not_applicable +nist_genai_risks: [information_integrity] +iso_42001_clause: [] +files_modified: + - core/src/walk.rs + - core/src/lib.rs + - core/src/architecture/gather.rs + - cli/src/tree_grep.rs + - cli/src/analysis_engine.rs + - experiment-baton/src/scan.rs + - core/Cargo.toml, cli/Cargo.toml, experiment-loom/Cargo.toml, experiment-baton/Cargo.toml, Cargo.lock + - CHANGELOG.md, experiment-loom/CHANGELOG.md, version tables x6 +observability_scope: none +tags: [core, architecture, loom, baton, adopter-feedback] +related: + - 07-ai-audit/agent-logs/AILOG-2026-09-17-002-baton-inventory-hygiene.md +--- + +# AILOG: core nested-checkout rule (#434 follow-up) + +## Summary + +#435 taught Baton's walkers to skip another checkout nested in the project. The same blind spot +existed in `straymark-core`'s source scanner and in two CLI walkers. The rule now lives once, in +`core::walk::is_nested_checkout`, and all of them use it. Released as core 0.10.1, CLI 3.49.2 and +Loom 0.7.1. + +## Context + +Demonstrated on an adopter repository with one linked worktree under `.worktrees/`. `straymark +architecture generate`, writing to a scratch `--out`, produced 15 components, one of them +`.worktrees` (glob `.worktrees/**`): a full copy of the repo, 5606 source files against 5235 in the +real tree. With this change the same command produces 14 components and no `.worktrees`. The +repository was read-only in both runs (git status unchanged). + +## Actions Performed + +1. `core/src/walk.rs`: `pub fn is_nested_checkout(dir)`. A sub-directory with its own `.git` (a + file for a linked worktree or submodule, a directory for a nested clone) is another checkout. +2. `core::architecture::collect_source_files_with` skips such directories. This covers + `architecture generate | sync | validate`, `status --where`, Loom's architecture view and + Intent plane, and Baton's overlay. +3. CLI: `tree_grep::walk` (used by `followups verify --claims` and `analyze declared-vs-wired`) and + `analysis_engine::walk_recursive` (`analyze`) use the same rule. +4. Baton: `scan::is_nested_checkout` is now a re-export of the `core` rule. Its behavior is + unchanged. +5. Tests: `core::walk` itself, and one per walker. Each walker test uses a directory name that is + not already excluded (the first `analyze` fixture used `vendor/`, which `analyze` already + excludes, so it passed without the fix; it was moved to `third_party/`). All three walker tests + fail with the guard removed. +6. Versions move atomically: core 0.10.0 → 0.10.1, and the `straymark-core` bound moves in the + CLI, Loom **and** Baton Cargo.toml, per the lesson from #282. CLI 3.49.2, Loom 0.7.1, CHANGELOG + and Loom CHANGELOG. + +## Decisions Made + +- **One rule, in `core`.** Walkers keep their own name lists, because those differ by purpose, but + the "another checkout" rule is shared so that no tool can disagree with another about what the + project contains. +- **Patch versions.** The change adds an API to core and fixes behavior; no public API breaks. +- Baton is not re-released: its behavior is identical, and the next Baton release will pick up the + new dependency bound. + +## Impact + +- **Functionality**: architecture models, `status --where`, Loom and `analyze` / `verify --claims` + no longer read another checkout as the project's code. +- **Performance**: one `stat` per visited directory. Negligible. +- **Security / Privacy / Environmental**: N/A. + +## Verification + +- [x] `cargo test --workspace`: 1041 passed +- [x] Clippy: core and Baton clean with `-D warnings`; Loom clean; CLI unchanged from the baseline + (18/24) +- [x] Before/after `architecture generate` on the adopter repository, read-only diff --git a/CHANGELOG.md b/CHANGELOG.md index 037fd622..4894189b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project uses [independent versioning](README.md#versioning) for Framewo --- +## CLI 3.49.2 / core 0.10.1 / Loom 0.7.1 — 2026-09-18 + +Follow-up to #434, which fixed the Baton half. The shared source scanner in `straymark-core` and two CLI walkers also descended into **another checkout nested in the project**: a linked git worktree such as `.worktrees//`, a submodule or a nested clone. Each read that copy as the project's own code. On an adopter's repository with one worktree inside, `straymark architecture generate` invented a `.worktrees` component holding a full copy of the repo. + +### Fixed (CLI / core / Loom) + +- **`straymark-core` 0.10.1**: new `walk::is_nested_checkout`, the one rule every project-wide walker shares. A sub-directory with its own `.git` (file or directory) is another checkout and is never walked. It is structural, so no gitignore parsing and nothing to configure. `architecture::collect_source_files` uses it, which covers: + - `straymark architecture generate | sync | validate` and `status --where`; + - Loom's architecture view and Intent plane (**Loom 0.7.1**, rebuilt); + - Baton's overlay. +- **CLI 3.49.2**: `tree_grep` (behind `followups verify --claims` and `analyze declared-vs-wired`) and the `analyze` complexity walker use the same rule. A worktree copy no longer counts as "callers" of a symbol, and its functions are no longer analyzed twice. +- Baton now reuses the `core` rule instead of its own copy. Its behavior is unchanged. + +Until upgraded, the workaround is `architecture: { excluded_dirs: [".worktrees"] }` in `.straymark/config.yml`, which covers only the architecture scanner and only that directory name. + ## Baton 0.3.0 — 2026-09-17 First Baton release driven by an adopter's Track C work (Estoa, Discussion #426). Before it, the declared-verb router (#332) classified only units with their own slot: every SpecKit task and every batch was `undeclared`, and the inventory counted work that was not live. Experimental; distributed as `baton-*` GitHub releases (`--latest=false`). diff --git a/Cargo.lock b/Cargo.lock index e642e6be..eaf1065d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2574,7 +2574,7 @@ dependencies = [ [[package]] name = "straymark-cli" -version = "3.49.1" +version = "3.49.2" dependencies = [ "anyhow", "arborist-metrics", @@ -2607,7 +2607,7 @@ dependencies = [ [[package]] name = "straymark-core" -version = "0.10.0" +version = "0.10.1" dependencies = [ "anyhow", "serde", @@ -2617,7 +2617,7 @@ dependencies = [ [[package]] name = "straymark-loom" -version = "0.7.0" +version = "0.7.1" dependencies = [ "anyhow", "axum", diff --git a/README.md b/README.md index 63099c29..b16e3e59 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ StrayMark uses independent version tags for each component: | Component | Tag prefix | Example | Includes | | --- | --- | --- | --- | | Framework | `fw-` | `fw-4.45.0` | Templates (12 types), governance, directives, Charter template + schema | -| CLI | `cli-` | `cli-3.49.1` | The `straymark` binary | +| CLI | `cli-` | `cli-3.49.2` | The `straymark` binary | | Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | The `straymark-loom` visualization server, downloaded on demand by `straymark loom serve` | Check installed versions with `straymark status` or `straymark about`. diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 2bc13e25..4459cc2b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "straymark-cli" -version = "3.49.1" +version = "3.49.2" edition = "2021" description = "CLI for StrayMark — the cognitive discipline your AI-assisted projects need" license = "MIT" @@ -25,7 +25,7 @@ path = "src/bin/gen_minimal_skills.rs" required-features = ["dev-tools"] [dependencies] -straymark-core = { version = "0.10.0", path = "../core" } +straymark-core = { version = "0.10.1", path = "../core" } clap = { version = "4", features = ["derive"] } reqwest = { version = "0.12", features = ["blocking", "rustls-tls", "json"] } serde = { version = "1", features = ["derive"] } diff --git a/cli/src/analysis_engine.rs b/cli/src/analysis_engine.rs index e7785fb0..b8b134ca 100644 --- a/cli/src/analysis_engine.rs +++ b/cli/src/analysis_engine.rs @@ -73,7 +73,9 @@ fn walk_recursive(dir: &Path, files: &mut Vec) { let path = entry.path(); if path.is_dir() { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - if !EXCLUDED_DIRS.contains(&name) { + if !EXCLUDED_DIRS.contains(&name) + && !straymark_core::walk::is_nested_checkout(&path) + { walk_recursive(&path, files); } } @@ -206,6 +208,20 @@ mod tests { assert!(files[0].ends_with("main.rs")); } + #[test] + fn test_walk_skips_nested_checkouts() { + // #434: functions in a worktree copy are not this project's functions. + let dir = TempDir::new().unwrap(); + fs::write(dir.path().join("main.rs"), "fn included() {}").unwrap(); + let clone = dir.path().join("third_party/clone"); + fs::create_dir_all(clone.join(".git")).unwrap(); + fs::write(clone.join("lib.rs"), "fn duplicated() {}").unwrap(); + + let files = walk_source_files(dir.path()); + assert_eq!(files.len(), 1); + assert!(files[0].ends_with("main.rs")); + } + #[test] fn test_single_rust_file() { let dir = TempDir::new().unwrap(); diff --git a/cli/src/tree_grep.rs b/cli/src/tree_grep.rs index b7b17e9a..6f36c937 100644 --- a/cli/src/tree_grep.rs +++ b/cli/src/tree_grep.rs @@ -80,7 +80,9 @@ fn walk(base: &Path, dir: &Path, out: &mut Vec) { let path = entry.path(); if file_type.is_dir() { let name = entry.file_name(); - if EXCLUDED_DIRS.contains(&name.to_string_lossy().as_ref()) { + if EXCLUDED_DIRS.contains(&name.to_string_lossy().as_ref()) + || straymark_core::walk::is_nested_checkout(&path) + { continue; } walk(base, &path, out); @@ -156,6 +158,20 @@ mod tests { assert_eq!(tree[0].rel_path, "main.rs"); } + #[test] + fn read_text_tree_skips_nested_checkouts() { + // #434: a worktree copy would count its symbols as the project's own. + let tmp = tempfile::TempDir::new().unwrap(); + std::fs::write(tmp.path().join("main.rs"), "fn main() {}\n").unwrap(); + let worktree = tmp.path().join(".worktrees/feature"); + std::fs::create_dir_all(&worktree).unwrap(); + std::fs::write(worktree.join(".git"), "gitdir: /elsewhere\n").unwrap(); + std::fs::write(worktree.join("main.rs"), "fn main() {}\n").unwrap(); + let tree = read_text_tree(tmp.path()); + assert_eq!(tree.len(), 1); + assert_eq!(tree[0].rel_path, "main.rs"); + } + #[test] fn symbol_occurrences_respects_word_boundaries() { let tmp = tempfile::TempDir::new().unwrap(); diff --git a/core/Cargo.toml b/core/Cargo.toml index 391cb6f7..67a9f46a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "straymark-core" -version = "0.10.0" +version = "0.10.1" edition = "2021" description = "Shared document model and knowledge graph for StrayMark — parses governance documents and builds their typed traceability graph" license = "MIT" diff --git a/core/src/architecture/gather.rs b/core/src/architecture/gather.rs index 0daa4bd0..e076cf86 100644 --- a/core/src/architecture/gather.rs +++ b/core/src/architecture/gather.rs @@ -77,7 +77,8 @@ pub fn build_governance_state(root: &Path) -> GovernanceState { /// Source files (paths relative to `root`), filtered by the project's /// [`ScanConfig`] (source extensions + excluded dirs, defaults ∪ the -/// `architecture:` config section, #279). Used for the on-disk inventory (the +/// `architecture:` config section, #279). Never descends into another +/// checkout nested in the project ([`crate::walk::is_nested_checkout`], #434). Used for the on-disk inventory (the /// `uncharted` signal) and by the CLI generator to discover component dirs. pub fn collect_source_files(root: &Path) -> Vec { collect_source_files_with(root, &resolve_scan_config(root)) @@ -94,7 +95,7 @@ pub fn collect_source_files_with(root: &Path, cfg: &ScanConfig) -> Vec let path = entry.path(); if path.is_dir() { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - if !cfg.is_excluded_dir(name) { + if !cfg.is_excluded_dir(name) && !crate::walk::is_nested_checkout(&path) { walk(&path, root, cfg, out); } } @@ -309,6 +310,26 @@ mod tests { assert_eq!(files, vec!["src/main.rs"]); // README.md (not source), target/, node_modules/ excluded } + #[test] + fn collect_source_files_skips_nested_checkouts() { + // #434: a linked worktree (`.git` file) and a nested clone (`.git` + // dir) are other checkouts, not this project's source. + let tmp = tempfile::TempDir::new().unwrap(); + let root = tmp.path(); + for d in ["src", ".worktrees/feature/src", "vendor/clone/src"] { + std::fs::create_dir_all(root.join(d)).unwrap(); + std::fs::write(root.join(d).join("main.rs"), "fn main() {}\n").unwrap(); + } + std::fs::write(root.join(".worktrees/feature/.git"), "gitdir: /elsewhere\n").unwrap(); + std::fs::create_dir(root.join("vendor/clone/.git")).unwrap(); + + let files: Vec = collect_source_files(root) + .iter() + .map(|p| p.to_string_lossy().replace('\\', "/")) + .collect(); + assert_eq!(files, vec!["src/main.rs"]); + } + #[test] fn build_state_empty_on_bare_dir() { // No .straymark, no git, no charters → every set empty, no panic. diff --git a/core/src/lib.rs b/core/src/lib.rs index b46b015d..4469e4dc 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -15,3 +15,4 @@ pub mod drift; pub mod entities; pub mod graph; pub mod utils; +pub mod walk; diff --git a/core/src/walk.rs b/core/src/walk.rs new file mode 100644 index 00000000..5052402b --- /dev/null +++ b/core/src/walk.rs @@ -0,0 +1,38 @@ +//! The one rule every project-wide directory walker shares. +//! +//! Walkers skip a fixed list of names (`target`, `node_modules`, …), but a +//! project can also hold *another checkout of itself*: a linked git worktree +//! under `.worktrees/`, a submodule, a nested clone. Reading it duplicates the +//! project — `architecture generate` invented a `.worktrees` component holding +//! a full copy of an adopter's repo (#434). The marker is structural, so no +//! gitignore parsing and nothing for an adopter to configure. + +use std::path::Path; + +/// True when `dir` is the root of another checkout: a linked worktree or a +/// submodule (a `.git` *file*) or a nested clone (a `.git` directory). Call it +/// on sub-directories only — the project root itself has a `.git`. +pub fn is_nested_checkout(dir: &Path) -> bool { + dir.join(".git").exists() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn git_file_or_dir_marks_a_nested_checkout() { + let tmp = tempfile::tempdir().unwrap(); + let worktree = tmp.path().join(".worktrees/feature"); + let clone = tmp.path().join("vendor/clone"); + let plain = tmp.path().join("src"); + for d in [&worktree, &clone, &plain] { + std::fs::create_dir_all(d).unwrap(); + } + std::fs::write(worktree.join(".git"), "gitdir: /elsewhere\n").unwrap(); + std::fs::create_dir(clone.join(".git")).unwrap(); + assert!(is_nested_checkout(&worktree)); + assert!(is_nested_checkout(&clone)); + assert!(!is_nested_checkout(&plain)); + } +} diff --git a/docs/adopters/CLI-REFERENCE.md b/docs/adopters/CLI-REFERENCE.md index 7a2b3dac..a3056181 100644 --- a/docs/adopters/CLI-REFERENCE.md +++ b/docs/adopters/CLI-REFERENCE.md @@ -46,7 +46,7 @@ StrayMark uses **independent version tags** for each component: | Component | Tag prefix | Example | What it includes | |-----------|-----------|---------|------------------| | Framework | `fw-` | `fw-4.45.0` | Templates (12 types), governance docs, directives, Charter template + schema | -| CLI | `cli-` | `cli-3.49.1` | The `straymark` binary | +| CLI | `cli-` | `cli-3.49.2` | The `straymark` binary | | Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | The `straymark-loom` visualization server, downloaded on demand by `straymark loom serve` | Framework and CLI are released independently. A framework update does not require a CLI update, and vice versa. diff --git a/docs/i18n/es/README.md b/docs/i18n/es/README.md index d1e01a2a..01785c4a 100644 --- a/docs/i18n/es/README.md +++ b/docs/i18n/es/README.md @@ -242,7 +242,7 @@ StrayMark usa tags de versión independientes para cada componente: | Componente | Prefijo de tag | Ejemplo | Incluye | |------------|---------------|---------|---------| | Framework | `fw-` | `fw-4.45.0` | Plantillas (12 tipos), gobernanza, directivas, plantilla + schema de Charter | -| CLI | `cli-` | `cli-3.49.1` | El binario `straymark` | +| CLI | `cli-` | `cli-3.49.2` | El binario `straymark` | | Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | El servidor de visualización `straymark-loom`, descargado bajo demanda por `straymark loom serve` | Verifica las versiones instaladas con `straymark status` o `straymark about`. diff --git a/docs/i18n/es/adopters/CLI-REFERENCE.md b/docs/i18n/es/adopters/CLI-REFERENCE.md index e96b50ac..160375d5 100644 --- a/docs/i18n/es/adopters/CLI-REFERENCE.md +++ b/docs/i18n/es/adopters/CLI-REFERENCE.md @@ -46,7 +46,7 @@ StrayMark usa **tags de versión independientes** para cada componente: | Componente | Prefijo de tag | Ejemplo | Qué incluye | |------------|---------------|---------|-------------| | Framework | `fw-` | `fw-4.45.0` | Plantillas (12 tipos), docs de gobernanza, directivas | -| CLI | `cli-` | `cli-3.49.1` | El binario `straymark` | +| CLI | `cli-` | `cli-3.49.2` | El binario `straymark` | | Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | El servidor de visualización `straymark-loom`, descargado bajo demanda por `straymark loom serve` | Framework y CLI se publican de forma independiente. Una actualización del framework no requiere actualización del CLI, y viceversa. diff --git a/docs/i18n/zh-CN/README.md b/docs/i18n/zh-CN/README.md index 7941ce1b..d18321f0 100644 --- a/docs/i18n/zh-CN/README.md +++ b/docs/i18n/zh-CN/README.md @@ -260,7 +260,7 @@ StrayMark 为每个组件使用独立的版本标签: | 组件 | 标签前缀 | 示例 | 包含内容 | |------|----------|------|----------| | Framework | `fw-` | `fw-4.45.0` | 模板(12 种类型)、治理文档、指令、Charter 模板 + schema | -| CLI | `cli-` | `cli-3.49.1` | `straymark` 二进制文件 | +| CLI | `cli-` | `cli-3.49.2` | `straymark` 二进制文件 | | Loom(实验性) | `loom-` | `loom-0.4.2` | `straymark-loom` 可视化服务器,由 `straymark loom serve` 按需下载 | 使用 `straymark status` 或 `straymark about` 查看已安装的版本。 diff --git a/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md b/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md index d75710ee..8cce269b 100644 --- a/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md +++ b/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md @@ -46,7 +46,7 @@ StrayMark 为每个组件使用**独立的版本标签**: | 组件 | 标签前缀 | 示例 | 包含内容 | |------|----------|------|----------| | Framework | `fw-` | `fw-4.45.0` | 模板(12 种类型)、治理文档、指令 | -| CLI | `cli-` | `cli-3.49.1` | `straymark` 二进制文件 | +| CLI | `cli-` | `cli-3.49.2` | `straymark` 二进制文件 | | Loom(实验性) | `loom-` | `loom-0.4.2` | `straymark-loom` 可视化服务器,由 `straymark loom serve` 按需下载 | Framework 和 CLI 独立发布。Framework 更新不需要 CLI 更新,反之亦然。 diff --git a/experiment-baton/Cargo.toml b/experiment-baton/Cargo.toml index 83782809..6ea730b7 100644 --- a/experiment-baton/Cargo.toml +++ b/experiment-baton/Cargo.toml @@ -18,7 +18,7 @@ name = "straymark-baton" path = "src/main.rs" [dependencies] -straymark-core = { version = "0.10.0", path = "../core" } +straymark-core = { version = "0.10.1", path = "../core" } serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yaml = "0.9" diff --git a/experiment-baton/src/scan.rs b/experiment-baton/src/scan.rs index f4a240e1..a937220d 100644 --- a/experiment-baton/src/scan.rs +++ b/experiment-baton/src/scan.rs @@ -5,16 +5,10 @@ //! Also the one rule every directory walker shares: [`is_nested_checkout`]. use std::collections::HashSet; -use std::path::Path; -/// True when `dir` is the root of *another* checkout — a linked git worktree -/// or a submodule (a `.git` file) or a nested clone (a `.git` directory). -/// Walkers never descend into one: its artifacts belong to another working -/// copy, and reading them duplicates units and contracts (#434). Structural on -/// purpose — no gitignore parsing, nothing for an adopter to configure. -pub(crate) fn is_nested_checkout(dir: &Path) -> bool { - dir.join(".git").exists() -} +/// Never descend into another checkout nested in the project (#434) — the +/// rule lives in `core` so Baton, the CLI and Loom's scanner agree on it. +pub(crate) use straymark_core::walk::is_nested_checkout; /// Scan `text` for all identifiers shaped `` where `body` is made /// of ASCII alphanumerics / hyphens and contains at least one digit. Returns diff --git a/experiment-loom/CHANGELOG.md b/experiment-loom/CHANGELOG.md index c384d190..32578b8f 100644 --- a/experiment-loom/CHANGELOG.md +++ b/experiment-loom/CHANGELOG.md @@ -12,6 +12,15 @@ project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [0.7.1] — 2026-09-18 (nested checkouts) + +### Fixed +- Rebuilt against `straymark-core` 0.10.1, whose source scanner no longer descends into another + checkout nested in the project: a linked git worktree (`.worktrees//`), a submodule or a + nested clone (#434). Before, the architecture view's on-disk inventory counted that copy as the + project's own code, which inflated `uncharted` files and duplicated whole modules. The Intent + plane's overlay uses the same inventory, so it is fixed too. No Loom code change. + ## [0.6.2] — 2026-06-16 (scoped has-debt attribution) ### Changed diff --git a/experiment-loom/Cargo.toml b/experiment-loom/Cargo.toml index 41b1a2ef..91e81ab8 100644 --- a/experiment-loom/Cargo.toml +++ b/experiment-loom/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "straymark-loom" -version = "0.7.0" +version = "0.7.1" edition = "2021" description = "Loom — StrayMark's experimental knowledge-graph visualization server (localhost development dashboard)" license = "MIT" @@ -14,7 +14,7 @@ name = "straymark-loom" path = "src/main.rs" [dependencies] -straymark-core = { version = "0.10.0", path = "../core" } +straymark-core = { version = "0.10.1", path = "../core" } straymark-baton = { path = "../experiment-baton" } tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "signal", "fs"] } axum = { version = "0.8", features = ["ws"] }