Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8cc6979
docs(stories): create story 0-4 template loading context
EdouardZemb Feb 6, 2026
a1e8925
chore: add node_modules and test-results to gitignore
EdouardZemb Feb 6, 2026
2da042a
feat(tf-config): implement template loading with format validation
EdouardZemb Feb 6, 2026
464cee9
docs(stories): update story 0-4 progress and sprint status
EdouardZemb Feb 6, 2026
aac42b6
fix(tf-config): address code review findings for template module
EdouardZemb Feb 6, 2026
601a3f9
docs(stories): mark story 0-4 review findings resolved
EdouardZemb Feb 6, 2026
53d2c06
docs(stories): add round 2 review findings for story 0-4
EdouardZemb Feb 6, 2026
bbc6b05
refactor(tf-config): address round 2 review findings for template module
EdouardZemb Feb 6, 2026
c5f0073
docs(stories): mark story 0-4 round 2 findings resolved
EdouardZemb Feb 6, 2026
99828b2
docs(stories): add round 3 review findings for story 0-4
EdouardZemb Feb 6, 2026
f77db1d
fix(tf-config): address round 3 review findings for template module
EdouardZemb Feb 6, 2026
19335f5
docs(stories): mark story 0-4 round 3 findings resolved
EdouardZemb Feb 6, 2026
92a0484
docs(stories): add round 4 review findings for story 0-4
EdouardZemb Feb 6, 2026
20ea378
build(tf-config): move tempfile to workspace dep, add serde_json
EdouardZemb Feb 6, 2026
82da0eb
fix(tf-config): address round 4 review findings for template module
EdouardZemb Feb 6, 2026
349085c
docs(stories): mark story 0-4 round 4 findings resolved
EdouardZemb Feb 6, 2026
56fb9a9
docs(stories): add round 5 review findings for story 0-4
EdouardZemb Feb 6, 2026
ee46955
fix(tf-config): address round 5 review findings for template module
EdouardZemb Feb 6, 2026
d12b486
docs(stories): mark story 0-4 round 5 findings resolved
EdouardZemb Feb 6, 2026
e505e78
docs(stories): add round 6 review findings for story 0-4
EdouardZemb Feb 6, 2026
a4ba35c
build(tf-config): add test-utils feature flag
EdouardZemb Feb 6, 2026
b31e7aa
refactor(tf-config): address round 6 review findings for template module
EdouardZemb Feb 6, 2026
75323a2
docs(stories): mark story 0-4 round 6 findings resolved
EdouardZemb Feb 6, 2026
e9b1b11
docs(stories): add round 7 review findings for story 0-4
EdouardZemb Feb 6, 2026
3f92c02
fix(tf-config): address round 7 review findings for template module
EdouardZemb Feb 6, 2026
5e40a9e
docs(stories): mark story 0-4 round 7 findings resolved
EdouardZemb Feb 6, 2026
ff4b76b
docs(stories): add round 8 review findings for story 0-4
EdouardZemb Feb 6, 2026
4b1139e
refactor(tf-config): address round 8 review findings for template module
EdouardZemb Feb 6, 2026
5882637
refactor(tf-config): deduplicate extension validation in config.rs
EdouardZemb Feb 6, 2026
e522bcf
docs(stories): mark story 0-4 round 8 findings resolved
EdouardZemb Feb 6, 2026
d4010f7
docs(stories): add round 9 review findings for story 0-4
EdouardZemb Feb 6, 2026
6101ecb
refactor(tf-config): expose redact_url_sensitive_params as pub(crate)
EdouardZemb Feb 6, 2026
72ff4fa
fix(tf-config): address round 9 review findings for template module
EdouardZemb Feb 6, 2026
ac3ac7f
docs(stories): mark story 0-4 round 9 findings resolved
EdouardZemb Feb 6, 2026
9eef268
docs(stories): add round 10 review findings for story 0-4
EdouardZemb Feb 6, 2026
b0e4cbf
fix(tf-config): address round 10 review findings for template module
EdouardZemb Feb 6, 2026
3f40fec
docs(stories): mark story 0-4 round 10 findings resolved
EdouardZemb Feb 6, 2026
662ad72
fix(tf-config): enforce PPTX binary contract and close story 0-4 review
EdouardZemb Feb 6, 2026
eeed27d
Merge remote-tracking branch 'origin/main' into feature/0-4-chargemen…
EdouardZemb Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ incremental/
*.rlib
*.rmeta
*.d

# Node
node_modules/

# Playwright / test output
test-results/
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ keyring = { version = "3.6", features = ["sync-secret-service", "windows-native"

# Testing
assert_matches = "1.5"
tempfile = "3.10"

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion _bmad-output/implementation-artifacts/sprint-status.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ development_status:
0-1-configurer-un-projet-via-config-yaml: done
0-2-definir-et-selectionner-des-profils-de-configuration: done
0-3-gestion-des-secrets-via-secret-store: done
0-4-charger-des-templates-cr-ppt-anomalies: backlog
0-4-charger-des-templates-cr-ppt-anomalies: done
0-5-journalisation-baseline-sans-donnees-sensibles: backlog
0-6-configurer-checklist-de-testabilite-et-regles-de-scoring: backlog
0-7-anonymisation-automatique-avant-envoi-cloud: backlog
Expand Down
6 changes: 5 additions & 1 deletion crates/tf-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ license.workspace = true
description = "Configuration management for test-framework"
readme = "README.md"

[features]
test-utils = []

[dependencies]
serde.workspace = true
serde_yaml.workspace = true
thiserror.workspace = true

[dev-dependencies]
assert_matches.workspace = true
tempfile = "3.10"
serde_json.workspace = true
tempfile.workspace = true
32 changes: 19 additions & 13 deletions crates/tf-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ fn default_max_tokens() -> u32 {
/// - `https://user:secret@jira.example.com` -> `https://[REDACTED]@jira.example.com`
/// - `https://jira.example.com?token=secret123` -> `https://jira.example.com?token=[REDACTED]`
/// - `https://api.example.com?api_key=sk-123&foo=bar` -> `https://api.example.com?api_key=[REDACTED]&foo=bar`
fn redact_url_sensitive_params(url: &str) -> String {
pub(crate) fn redact_url_sensitive_params(url: &str) -> String {
// List of sensitive parameter names (case-insensitive matching)
// Includes both snake_case and camelCase variants
const SENSITIVE_PARAMS: &[&str] = &[
Expand Down Expand Up @@ -1654,10 +1654,15 @@ fn is_safe_path(path: &str) -> bool {
true
}

/// Validate template file extension
fn has_valid_extension(path: &str, expected_extensions: &[&str]) -> bool {
let lower = path.to_lowercase();
expected_extensions.iter().any(|ext| lower.ends_with(ext))
/// Validate template file extension using [`TemplateKind::expected_extension()`] as
/// single source of truth (shared with `template::validate_extension`).
fn has_valid_template_extension(path: &str, kind: crate::template::TemplateKind) -> bool {
let expected_no_dot = &kind.expected_extension()[1..]; // skip leading dot
std::path::Path::new(path)
.extension()
.and_then(|e| e.to_str())
.map(|e| e.eq_ignore_ascii_case(expected_no_dot))
.unwrap_or(false)
}

/// Validate configuration fields
Expand Down Expand Up @@ -1936,7 +1941,7 @@ fn validate_config(config: &ProjectConfig) -> Result<(), ConfigError> {
"a direct path without '..' (e.g., './templates/cr.md')",
));
}
if !has_valid_extension(cr, &[".md"]) {
if !has_valid_template_extension(cr, crate::template::TemplateKind::Cr) {
return Err(ConfigError::invalid_value(
"templates.cr",
"must be a Markdown file",
Expand All @@ -1959,7 +1964,7 @@ fn validate_config(config: &ProjectConfig) -> Result<(), ConfigError> {
"a direct path without '..' (e.g., './templates/report.pptx')",
));
}
if !has_valid_extension(ppt, &[".pptx"]) {
if !has_valid_template_extension(ppt, crate::template::TemplateKind::Ppt) {
return Err(ConfigError::invalid_value(
"templates.ppt",
"must be a PowerPoint file",
Expand All @@ -1982,7 +1987,7 @@ fn validate_config(config: &ProjectConfig) -> Result<(), ConfigError> {
"a direct path without '..' (e.g., './templates/anomaly.md')",
));
}
if !has_valid_extension(anomaly, &[".md"]) {
if !has_valid_template_extension(anomaly, crate::template::TemplateKind::Anomaly) {
return Err(ConfigError::invalid_value(
"templates.anomaly",
"must be a Markdown file",
Expand Down Expand Up @@ -2634,11 +2639,12 @@ templates:

#[test]
fn test_extension_helper() {
assert!(has_valid_extension("file.md", &[".md"]));
assert!(has_valid_extension("file.MD", &[".md"]));
assert!(has_valid_extension("path/to/file.pptx", &[".pptx"]));
assert!(!has_valid_extension("file.txt", &[".md"]));
assert!(!has_valid_extension("file.ppt", &[".pptx"]));
use crate::template::TemplateKind;
assert!(has_valid_template_extension("file.md", TemplateKind::Cr));
assert!(has_valid_template_extension("file.MD", TemplateKind::Cr));
assert!(has_valid_template_extension("path/to/file.pptx", TemplateKind::Ppt));
assert!(!has_valid_template_extension("file.txt", TemplateKind::Cr));
assert!(!has_valid_template_extension("file.ppt", TemplateKind::Ppt));
}

#[test]
Expand Down
4 changes: 4 additions & 0 deletions crates/tf-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
pub mod config;
pub mod error;
pub mod profiles;
pub mod template;

pub use config::{
load_config, JiraConfig, LlmConfig, LlmMode, ProjectConfig, Redact, SquashConfig, TemplatesConfig,
Expand All @@ -67,3 +68,6 @@ pub use error::ConfigError;

// Profile types for Story 0.2
pub use profiles::{ProfileId, ProfileOverride};

// Template types for Story 0.4
pub use template::{validate_content, LoadedTemplate, TemplateError, TemplateKind, TemplateLoader};
Loading