From dd7f6eb6a481505945a92e14325dcd39ceb83800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Villase=C3=B1or=20Montfort?= <195970+montfort@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:51:57 -0600 Subject: [PATCH] fix(cli): update no longer copies dist-manifest.yml or dist-templates into adopter projects (cli-3.5.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devtrail update / update-framework used to walk every file in the extracted release ZIP and copy each one whose path didn't already exist in the target — which silently deposited the framework's internal dist-manifest.yml and dist-templates/ directory at the root of every updated project. devtrail init was already correct: it only extracts paths declared in manifest.files. update_framework.rs now applies the same whitelist via matches_manifest(), so only files declared in the release manifest are copied. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 7 ++ README.md | 2 +- cli/Cargo.lock | 2 +- cli/Cargo.toml | 2 +- cli/src/commands/update_framework.rs | 82 +++++++++++++++++++---- 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 +- 10 files changed, 85 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75b7f18..0006d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project uses [independent versioning](README.md#versioning) for Framewo --- +## CLI 3.5.3 — `devtrail update` no longer leaks package internals into adopter projects + +### Fixed (CLI) +- `devtrail update` (and `devtrail update-framework`) used to copy the framework's internal `dist-manifest.yml` and `dist-templates/` directory into the root of the adopter project. Both are package-internal artifacts: the manifest is the catalogue the CLI reads from the release ZIP, and `dist-templates/` is the source of agent-directive injections that are read into memory and merged via marker blocks — neither is meant to land on disk in the target project. `devtrail init` already filtered correctly via `manifest.files`; only the update path was inconsistent. Update now applies the same whitelist, so only files declared in the release manifest are copied. Existing projects affected by the bug can clean up by deleting `dist-manifest.yml` and `dist-templates/` from their project root and running `devtrail update-framework` again to regenerate `.devtrail/.checksums.json` without orphan entries. + +--- + ## CLI 3.5.2 — Remove Undocumented Vim-Style Aliases (`l`, `h`) ### Changed (CLI) diff --git a/README.md b/README.md index 8fa44ad..1a15ac4 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ DevTrail uses independent version tags for each component: | Component | Tag prefix | Example | Includes | |-----------|-----------|---------|----------| | Framework | `fw-` | `fw-4.3.0` | Templates (12 types), governance, directives | -| CLI | `cli-` | `cli-3.5.2` | The `devtrail` binary | +| CLI | `cli-` | `cli-3.5.3` | The `devtrail` binary | Check installed versions with `devtrail status` or `devtrail about`. diff --git a/cli/Cargo.lock b/cli/Cargo.lock index b5e3be0..f8910b1 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -537,7 +537,7 @@ dependencies = [ [[package]] name = "devtrail-cli" -version = "3.5.2" +version = "3.5.3" dependencies = [ "anyhow", "arborist-metrics", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 287d6b6..4e62d6b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "devtrail-cli" -version = "3.5.2" +version = "3.5.3" edition = "2021" description = "CLI tool for DevTrail - Documentation Governance for AI-Assisted Development" license = "MIT" diff --git a/cli/src/commands/update_framework.rs b/cli/src/commands/update_framework.rs index 121fae3..3d7472e 100644 --- a/cli/src/commands/update_framework.rs +++ b/cli/src/commands/update_framework.rs @@ -73,7 +73,7 @@ pub fn run() -> Result<()> { // Update framework files utils::info("Updating framework files..."); - let stats = update_files(&target, &source_root, ¤t_checksums)?; + let stats = update_files(&target, &source_root, &manifest, ¤t_checksums)?; // Update directive injections utils::info("Updating AI agent directives..."); @@ -105,6 +105,7 @@ struct UpdateStats { fn update_files( target: &Path, source_root: &Path, + manifest: &DistManifest, checksums: &Checksums, ) -> Result { let mut stats = UpdateStats { @@ -121,20 +122,19 @@ fn update_files( .strip_prefix(source_root) .unwrap_or(&source_path) .display() - .to_string(); - - // Skip user-generated documents - if utils::is_user_document(&source_path) { - continue; - } - - // Skip checksums file - if relative == ".devtrail/.checksums.json" { + .to_string() + .replace('\\', "/"); + + // Only touch files declared by the release manifest. The release ZIP also + // ships internal artifacts (`dist-manifest.yml`, `dist-templates/`) that + // the CLI consumes from the temp dir but must never copy into the + // adopter's project. Mirrors `init.rs::extract_matching_files`. + if !matches_manifest(&relative, &manifest.files) { continue; } - // Skip dist-manifest.yml (we save it separately) - if relative == ".devtrail/dist-manifest.yml" { + // Skip user-generated documents + if utils::is_user_document(&source_path) { continue; } @@ -325,6 +325,18 @@ fn find_source_root(extract_dir: &Path) -> Result { bail!("Could not find dist-manifest.yml in extracted archive"); } +/// Match a relative path (POSIX-style) against the manifest's `files` whitelist. +/// Patterns ending in `/` match any path under that directory; otherwise exact match. +fn matches_manifest(relative: &str, files: &[String]) -> bool { + files.iter().any(|pat| { + if pat.ends_with('/') { + relative.starts_with(pat.as_str()) + } else { + relative == pat + } + }) +} + fn walkdir(dir: PathBuf) -> Result> { let mut files = Vec::new(); if !dir.is_dir() { @@ -343,3 +355,49 @@ fn walkdir(dir: PathBuf) -> Result> { Ok(files) } + +#[cfg(test)] +mod tests { + use super::matches_manifest; + + fn manifest_files() -> Vec { + // Matches `dist/dist-manifest.yml` (fw-4.3.0). + vec![ + ".devtrail/".to_string(), + "DEVTRAIL.md".to_string(), + ".claude/skills/".to_string(), + ".gemini/skills/".to_string(), + ".agent/workflows/".to_string(), + ".github/workflows/docs-validation.yml".to_string(), + ] + } + + #[test] + fn package_artifacts_are_rejected() { + let files = manifest_files(); + // Regression for the bug where `devtrail update` deposited these in the + // adopter project. Both live at the ZIP root, neither is in `manifest.files`. + assert!(!matches_manifest("dist-manifest.yml", &files)); + assert!(!matches_manifest("dist-templates/directives/CLAUDE.md", &files)); + } + + #[test] + fn declared_files_and_directories_match() { + let files = manifest_files(); + assert!(matches_manifest("DEVTRAIL.md", &files)); + assert!(matches_manifest(".devtrail/00-governance/AGENT-RULES.md", &files)); + assert!(matches_manifest(".claude/skills/devtrail-new/SKILL.md", &files)); + assert!(matches_manifest( + ".github/workflows/docs-validation.yml", + &files + )); + } + + #[test] + fn undeclared_paths_are_rejected() { + let files = manifest_files(); + assert!(!matches_manifest("README.md", &files)); + assert!(!matches_manifest(".github/workflows/release-cli.yml", &files)); + assert!(!matches_manifest(".claude/agents/foo.md", &files)); + } +} diff --git a/docs/adopters/CLI-REFERENCE.md b/docs/adopters/CLI-REFERENCE.md index b7beb96..9f355cb 100644 --- a/docs/adopters/CLI-REFERENCE.md +++ b/docs/adopters/CLI-REFERENCE.md @@ -49,7 +49,7 @@ DevTrail uses **independent version tags** for each component: | Component | Tag prefix | Example | What it includes | |-----------|-----------|---------|------------------| | Framework | `fw-` | `fw-4.3.0` | Templates (12 types), governance docs, directives | -| CLI | `cli-` | `cli-3.5.2` | The `devtrail` binary | +| CLI | `cli-` | `cli-3.5.3` | The `devtrail` binary | 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 34e3d32..8c2ed07 100644 --- a/docs/i18n/es/README.md +++ b/docs/i18n/es/README.md @@ -150,7 +150,7 @@ DevTrail usa tags de versión independientes para cada componente: | Componente | Prefijo de tag | Ejemplo | Incluye | |------------|---------------|---------|---------| | Framework | `fw-` | `fw-4.3.0` | Plantillas (12 tipos), gobernanza, directivas | -| CLI | `cli-` | `cli-3.5.2` | El binario `devtrail` | +| CLI | `cli-` | `cli-3.5.3` | El binario `devtrail` | Verifica las versiones instaladas con `devtrail status` o `devtrail about`. diff --git a/docs/i18n/es/adopters/CLI-REFERENCE.md b/docs/i18n/es/adopters/CLI-REFERENCE.md index 8d3a1ab..8cab770 100644 --- a/docs/i18n/es/adopters/CLI-REFERENCE.md +++ b/docs/i18n/es/adopters/CLI-REFERENCE.md @@ -49,7 +49,7 @@ DevTrail usa **tags de versión independientes** para cada componente: | Componente | Prefijo de tag | Ejemplo | Qué incluye | |------------|---------------|---------|-------------| | Framework | `fw-` | `fw-4.3.0` | Plantillas (12 tipos), docs de gobernanza, directivas | -| CLI | `cli-` | `cli-3.5.2` | El binario `devtrail` | +| CLI | `cli-` | `cli-3.5.3` | El binario `devtrail` | 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 c278050..c2a4a24 100644 --- a/docs/i18n/zh-CN/README.md +++ b/docs/i18n/zh-CN/README.md @@ -150,7 +150,7 @@ DevTrail 为每个组件使用独立的版本标签: | 组件 | 标签前缀 | 示例 | 包含内容 | |------|----------|------|----------| | Framework | `fw-` | `fw-4.3.0` | 模板(12 种类型)、治理文档、指令 | -| CLI | `cli-` | `cli-3.5.2` | `devtrail` 二进制文件 | +| CLI | `cli-` | `cli-3.5.3` | `devtrail` 二进制文件 | 使用 `devtrail status` 或 `devtrail about` 查看已安装的版本。 diff --git a/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md b/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md index 4b52918..e6c1acc 100644 --- a/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md +++ b/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md @@ -49,7 +49,7 @@ DevTrail 为每个组件使用**独立的版本标签**: | 组件 | 标签前缀 | 示例 | 包含内容 | |------|----------|------|----------| | Framework | `fw-` | `fw-4.3.0` | 模板(12 种类型)、治理文档、指令 | -| CLI | `cli-` | `cli-3.5.2` | `devtrail` 二进制文件 | +| CLI | `cli-` | `cli-3.5.3` | `devtrail` 二进制文件 | Framework 和 CLI 独立发布。Framework 更新不需要 CLI 更新,反之亦然。