Bundle Generator Module Reference¶
Technical documentation for the bundle_generator module (amplihack-utils crate),
which provides the types, error handling, pipeline traits, packaging, and
distribution API for generating AI agent bundles from natural language
descriptions.
Scope note. This reference documents the internal organization of the module. The refactor that split the former single-file
bundle_generator.rsinto a directory module is behavior-preserving: every public path, type, and function is unchanged. If you only consume the API, see Usage — nothing about your imports or call sites changes.
Overview¶
bundle_generator implements a six-stage pipeline that turns a natural language
prompt into a distributable agent bundle:
- Parsing — analyse prompts ([
PromptParser]) - Extraction — extract intent and requirements ([
IntentExtractor]) - Generation — create agent content ([
AgentGenerator]) - Building — assemble bundles ([
BundleBuilder]) - Packaging — produce distributable packages ([
FilesystemPackager]) - Distribution — publish to GitHub ([
GitHubDistributor])
The module is exposed from the crate root as amplihack_utils::bundle_generator
(declared by pub mod bundle_generator; in crates/amplihack-utils/src/lib.rs).
Module Layout¶
The module is a directory module. mod.rs is the only public surface: it
declares the private submodules and re-exports their items so that every public
symbol remains reachable at the flat bundle_generator::<Item> path.
crates/amplihack-utils/src/bundle_generator/
├── mod.rs # Module docs + private `mod` decls + `pub use` re-exports
├── error.rs # BundleGeneratorError and recovery guidance
├── models.rs # Serde data models (DTOs), enums, metrics
├── traits.rs # Pipeline trait definitions
├── packager.rs # FilesystemPackager + output-path safety guard
└── distributor.rs # GitHubDistributor (gh CLI) + helpers
Dependency direction¶
Submodules form a strict, acyclic layering. Lower layers never depend on higher layers:
graph TD
error[error.rs]
models[models.rs]
traits[traits.rs]
packager[packager.rs]
distributor[distributor.rs]
modrs[mod.rs]
models --> error
traits --> models
traits --> error
packager --> models
packager --> error
distributor --> models
distributor --> error
modrs --> error
modrs --> models
modrs --> traits
modrs --> packager
modrs --> distributor
Submodule responsibilities¶
| Submodule | Responsibility | Key public items |
|---|---|---|
error.rs |
Error hierarchy and human-readable recovery hints. Foundation layer with no intra-module dependencies. | [BundleGeneratorError], BundleGeneratorError::recovery_suggestion |
models.rs |
All serde-serializable data-transfer types, their enums, validation methods, Default impls, and run metrics. Depends only on error. |
[ParsedPrompt], [AgentRequirement], [ExtractedIntent], [GeneratedAgent], [AgentBundle], [PackagedBundle], [DistributionResult], [TestResult], [GenerationMetrics], and the enums [BundleAction], [Complexity], [AgentType], [BundleStatus], [PackageFormat], [DistributionPlatform], [TestType] |
traits.rs |
The four pipeline stage traits, each Send + Sync. Depends on models + error. |
[PromptParser], [IntentExtractor], [AgentGenerator], [BundleBuilder] |
packager.rs |
Filesystem packaging and the output-directory safety guard. | [FilesystemPackager]; private validate_output_dir, UNSAFE_PATHS |
distributor.rs |
GitHub distribution via the gh CLI. |
[GitHubDistributor]; private truncate_to_char_boundary, GitHubDistributor::get_file_sha |
mod.rs |
Module-level docs and the re-export barrel. | (re-exports only) |
Private items (validate_output_dir, UNSAFE_PATHS, truncate_to_char_boundary,
and the GitHubDistributor::get_file_sha method) stay co-located with the code they
support and with their tests. They are not re-exported and are not part of the
public API.
Public API¶
All items below are re-exported from mod.rs and remain available at their
original paths. No signatures changed in the refactor.
Errors — error.rs¶
pub enum BundleGeneratorError { /* Parsing, Extraction, Generation,
Validation, Packaging, Distribution, ... */ }
impl BundleGeneratorError {
/// Human-readable, actionable recovery guidance for this error.
pub fn recovery_suggestion(&self) -> &str;
}
Data models — models.rs¶
Data types (all #[derive(Serialize, Deserialize)]) with their validation and
convenience methods:
pub struct ParsedPrompt { /* ... */ }
impl ParsedPrompt { pub fn validate(&self) -> Result<(), BundleGeneratorError>; }
pub struct AgentRequirement { pub constraints: Vec<String>, /* ... */ }
impl AgentRequirement { pub fn validate(&self) -> Result<(), BundleGeneratorError>; }
pub struct ExtractedIntent { pub constraints: Vec<String>, /* ... */ }
impl ExtractedIntent { pub fn validate(&self) -> Result<(), BundleGeneratorError>; }
pub struct GeneratedAgent { /* ... */ }
impl GeneratedAgent { pub fn file_size_kb(&self) -> f64; }
pub struct AgentBundle { /* ... */ }
impl AgentBundle {
pub fn validate(&self) -> Result<(), BundleGeneratorError>;
pub fn agent_count(&self) -> usize;
pub fn total_size_kb(&self) -> f64;
}
pub struct PackagedBundle { /* ... */ }
pub struct DistributionResult { /* ... */ }
impl DistributionResult {
pub fn has_errors(&self) -> bool;
pub fn has_warnings(&self) -> bool;
}
pub struct TestResult { /* ... */ }
impl TestResult { pub fn success_rate(&self) -> f64; }
pub struct GenerationMetrics { /* ... */ }
impl GenerationMetrics { pub fn average_agent_time(&self) -> f64; }
pub enum BundleAction { /* ... */ }
pub enum Complexity { /* ... */ }
pub enum AgentType { /* ... */ }
pub enum BundleStatus { /* ... */ }
pub enum PackageFormat { /* ... */ }
pub enum DistributionPlatform { /* ... */ }
pub enum TestType { /* ... */ }
Pipeline traits — traits.rs¶
pub trait PromptParser: Send + Sync {
fn parse(&self, prompt: &str) -> Result<ParsedPrompt, BundleGeneratorError>;
}
pub trait IntentExtractor: Send + Sync {
fn extract(&self, parsed: &ParsedPrompt) -> Result<ExtractedIntent, BundleGeneratorError>;
}
pub trait AgentGenerator: Send + Sync {
fn generate(
&self,
requirement: &AgentRequirement,
context: &ExtractedIntent,
) -> Result<GeneratedAgent, BundleGeneratorError>;
}
pub trait BundleBuilder: Send + Sync {
fn build(
&self,
name: &str,
agents: Vec<GeneratedAgent>,
intent: &ExtractedIntent,
) -> Result<AgentBundle, BundleGeneratorError>;
}
Packaging — packager.rs¶
pub struct FilesystemPackager { /* ... */ }
impl FilesystemPackager {
/// Create a packager targeting `output_dir`.
///
/// Rejects system directories (e.g. `/`, `/etc`, `/usr`, `/bin`) via the
/// output-path safety guard.
pub fn new(output_dir: impl Into<PathBuf>) -> Result<Self, BundleGeneratorError>;
/// Write a complete package (agents/, tests/, docs/, config/,
/// manifest.json, README.md) and return its path.
pub fn create_package(
&self,
bundle: &AgentBundle,
options: Option<&HashMap<String, serde_json::Value>>,
) -> Result<PathBuf, BundleGeneratorError>;
}
Distribution — distributor.rs¶
pub struct GitHubDistributor { /* token is PRIVATE */ }
impl GitHubDistributor {
pub fn new(token: impl Into<String>) -> Self;
pub fn create_repository(
&self, name: &str, description: &str, public: bool,
) -> Result<String, BundleGeneratorError>;
pub fn push_bundle(
&self, repo: &str, path: &str, content: &[u8], message: &str,
) -> Result<(), BundleGeneratorError>;
/// Create the repository and upload the packaged bundle (public repo).
pub fn distribute(
&self, bundle: &PackagedBundle, repo_name: &str,
) -> Result<DistributionResult, BundleGeneratorError>;
/// Distribute with explicit visibility control.
pub fn distribute_with_options(
&self, bundle: &PackagedBundle, repo_name: &str, public: bool,
) -> Result<DistributionResult, BundleGeneratorError>;
}
Usage¶
Import paths are identical to before the refactor. The directory module is a drop-in replacement for the former single file.
Packaging a bundle¶
use amplihack_utils::bundle_generator::{AgentBundle, FilesystemPackager};
let packager = FilesystemPackager::new("/home/user/out")?;
let path = packager.create_package(&bundle, None)?;
println!("Package written to {}", path.display());
Distributing to GitHub¶
use amplihack_utils::bundle_generator::{GitHubDistributor, PackagedBundle};
let distributor = GitHubDistributor::new(std::env::var("GH_TOKEN")?);
let result = distributor.distribute(&packaged, "owner/my-agent-bundle")?;
assert!(!result.has_errors());
Implementing a pipeline stage¶
use amplihack_utils::bundle_generator::{
BundleGeneratorError, ParsedPrompt, PromptParser,
};
struct MyParser;
impl PromptParser for MyParser {
fn parse(&self, prompt: &str) -> Result<ParsedPrompt, BundleGeneratorError> {
// ...
todo!()
}
}
Maintenance Guide¶
Use this table to decide where a change belongs:
| You want to… | Edit |
|---|---|
| Add or change an error variant / recovery hint | error.rs |
| Add a data field, DTO, enum, or metric | models.rs |
| Add or change a pipeline stage contract | traits.rs |
| Change how packages are written to disk, or the path-safety blocklist | packager.rs |
Change GitHub distribution behaviour or gh invocation |
distributor.rs |
| Expose a new public item | Add the definition to the right submodule and add a pub use line in mod.rs |
Guidelines:
- Keep the barrel authoritative. Anything meant to be public must appear in
a
pub useinmod.rs. If it's not re-exported there, it's not public. - Respect the dependency direction.
errordepends on nothing in the module;modelsmay useerror; everything else may usemodels+error. Do not introduce upward or cyclic dependencies. - Keep tests co-located. Each submodule owns its
#[cfg(test)]tests, including tests that exercise private helpers. Do not widen visibility (e.g.pub(crate)) solely to relocate a test.
Invariants Preserved by the Refactor¶
The split maintains all pre-existing behavioral and security guarantees:
- Public API is byte-compatible. All public items keep the same
bundle_generator::<Item>paths and signatures; consumers need no changes. GitHubDistributor.tokenstays private. The token is never re-exported, never placed on a command line/argv, and never interpolated into logs, error messages, or written files. It is supplied toghthrough the process environment only.ghis always invoked via argument vectors (Command::args/arg), never by constructing a shell string — no shell-injection surface.- Output-path safety guard is unchanged.
FilesystemPackager::newstill rejects system directories viavalidate_output_dirand theUNSAFE_PATHSblocklist. - UTF-8 boundary safety is unchanged.
truncate_to_char_boundarystill truncates descriptions without splitting a multi-byte character. - Test suite is fully preserved (27 tests total) and continues to pass with
no regressions. Tests move with the code they exercise:
models.rs(15),distributor.rs(10),error.rs(1), andpackager.rs(1);traits.rsandmod.rscarry no unit tests today.