KeiSeiKit-1.0/_assembler/src/placeholders.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

129 lines
3.9 KiB
Rust

//! Placeholder check — reject unsubstituted `{{PLACEHOLDER}}` tokens.
//!
//! Constructor Pattern: one cube = one validation concern.
//! Extracted from `validator.rs` to keep that file under 200 LOC.
use crate::manifest::Manifest;
/// Reject manifests that still carry `{{PLACEHOLDER}}` tokens — the wizard
/// should have substituted them. Matches `{{...}}` conservatively (not
/// single braces).
pub fn check(m: &Manifest) -> Result<(), String> {
let check = |field: &str, value: &str| -> Result<(), String> {
if contains_placeholder(value) {
Err(format!(
"Unsubstituted template placeholder in field '{field}': {value}. Did the wizard skip a substitution?"
))
} else {
Ok(())
}
};
check("name", &m.name)?;
check("description", &m.description)?;
check("model", &m.model)?;
check("role", &m.role)?;
for (i, t) in m.tools.iter().enumerate() {
check(&format!("tools[{i}]"), t)?;
}
for (i, b) in m.blocks.iter().enumerate() {
check(&format!("blocks[{i}]"), b)?;
}
for (i, d) in m.domain_in.iter().enumerate() {
check(&format!("domain_in[{i}]"), d)?;
}
for (i, d) in m.forbidden_domain.iter().enumerate() {
check(&format!("forbidden_domain[{i}]"), d)?;
}
for (i, h) in m.handoff.iter().enumerate() {
check(&format!("handoff[{i}].target"), &h.target)?;
check(&format!("handoff[{i}].trigger"), &h.trigger)?;
}
for (i, o) in m.output_extra_fields.iter().enumerate() {
check(&format!("output_extra_fields[{i}]"), o)?;
}
if let Some(v) = &m.substrate_role {
check("substrate_role", v)?;
}
if let Some(v) = &m.memory_project {
check("memory_project", v)?;
}
if let Some(v) = &m.project_claudemd {
check("project_claudemd", v)?;
}
if let Some(r) = &m.references {
for (i, e) in r.extra.iter().enumerate() {
check(&format!("references.extra[{i}]"), e)?;
}
}
Ok(())
}
fn contains_placeholder(s: &str) -> bool {
if let Some(start) = s.find("{{") {
if s[start + 2..].contains("}}") {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manifest::{Handoff, Manifest};
fn base() -> Manifest {
Manifest {
name: "test".into(),
description: "d".into(),
tools: vec!["Read".into()],
model: "opus".into(),
role: "r".into(),
blocks: vec!["baseline".into(), "evidence-grading".into(), "memory-protocol".into()],
domain_in: vec!["x".into()],
forbidden_domain: vec!["y".into()],
handoff: vec![Handoff {
target: "a".into(),
trigger: "b".into(),
expects_artifact: None,
produces_artifact: None,
}],
output_extra_fields: vec![],
memory_project: None,
project_claudemd: None,
references: None,
produces_artifact: None,
substrate_role: None,
rule_blocks: vec![],
}
}
#[test]
fn rejects_placeholder_in_memory_project() {
let mut m = base();
m.memory_project = Some("{{MEMORY_PROJECT}}".into());
let err = check(&m).unwrap_err();
assert!(err.contains("memory_project"), "err = {err}");
assert!(err.contains("{{MEMORY_PROJECT}}"), "err = {err}");
}
#[test]
fn accepts_single_braces() {
let mut m = base();
m.description = "hello {world}".into();
assert!(check(&m).is_ok());
}
#[test]
fn accepts_empty_manifest() {
assert!(check(&base()).is_ok());
}
#[test]
fn rejects_placeholder_in_role() {
let mut m = base();
m.role = "do {{THING}}".into();
assert!(check(&m).is_err());
}
}