use std::path::Path;
use anyhow::{Context, Result};
use crate::analysis_utils;
use crate::cli;
use crate::commands::{analyze, context, evidence_packet};
pub(crate) fn handle(args: cli::PacketArgs, global: &cli::GlobalArgs) -> Result<()> {
match args.command {
cli::PacketCommand::Generate(generate) => generate_packet(generate, global),
}
}
fn generate_packet(args: cli::PacketGenerateArgs, global: &cli::GlobalArgs) -> Result<()> {
let out_dir = args.out.clone();
std::fs::create_dir_all(&out_dir)
.with_context(|| format!("failed to create packet directory {}", out_dir.display()))?;
let analyze_md = out_dir.join("analyze.md");
let analyze_json = out_dir.join("analyze.json");
let context_md = out_dir.join("context.md");
let syntax_json = out_dir.join("syntax.json");
let manifest = out_dir.join("manifest.json");
if syntax_json.exists() {
std::fs::remove_file(&syntax_json)
.with_context(|| format!("failed to clear stale {}", syntax_json.display()))?;
}
let analyze_args = analyze_args(&args);
let receipt = analyze::build_receipt(&analyze_args, global)
.context("failed to build analysis receipt for packet")?;
analysis_utils::write_analysis_to_path(
&receipt,
&analyze_json,
tokmd_types::AnalysisFormat::Json,
)
.with_context(|| format!("failed to write {}", analyze_json.display()))?;
analysis_utils::write_analysis_to_path(&receipt, &analyze_md, tokmd_types::AnalysisFormat::Md)
.with_context(|| format!("failed to write {}", analyze_md.display()))?;
context::handle(context_args(&args, &context_md), global)
.context("failed to generate packet context artifact")?;
let syntax_arg = if args.want_syntax() {
if let Err(err) = write_syntax(&args, &syntax_json) {
eprintln!("warning: syntax evidence unavailable: {err}");
}
Some(syntax_json.clone())
} else {
None
};
let packet_args = cli::EvidencePacketArgs {
preset: args.preset,
base: args.base.clone(),
head: args.head.clone(),
output: manifest,
analyze_md: Some(analyze_md),
analyze_json: Some(analyze_json),
context_md: Some(context_md),
syntax_json: syntax_arg,
context_budget: args.context_budget.clone(),
paths: args.paths.clone(),
};
evidence_packet::handle(packet_args)
}
fn analyze_args(args: &cli::PacketGenerateArgs) -> cli::CliAnalyzeArgs {
let wants_effort = matches!(
args.preset,
cli::AnalysisPreset::Estimate | cli::AnalysisPreset::BunUb
);
cli::CliAnalyzeArgs {
inputs: args.paths.clone(),
preset: Some(args.preset),
format: Some(cli::AnalysisFormat::Json),
window: None,
git: false,
no_git: false,
output_dir: None,
max_files: None,
max_bytes: None,
max_file_bytes: None,
max_commits: None,
max_commit_files: None,
granularity: None,
effort_model: None,
effort_layer: None,
effort_base_ref: wants_effort.then(|| args.base.clone()),
effort_head_ref: wants_effort.then(|| args.head.clone()),
monte_carlo: false,
mc_iterations: None,
mc_seed: None,
detail_functions: false,
near_dup: false,
near_dup_threshold: 0.80,
near_dup_max_files: 2000,
near_dup_scope: None,
near_dup_max_pairs: 10000,
near_dup_exclude: Vec::new(),
explain: None,
}
}
fn context_args(args: &cli::PacketGenerateArgs, output: &Path) -> cli::CliContextArgs {
cli::CliContextArgs {
paths: Some(args.paths.clone()),
budget: args.context_budget.clone(),
strategy: cli::ContextStrategy::default(),
rank_by: cli::ValueMetric::default(),
output_mode: cli::ContextOutput::default(),
compress: false,
no_smart_exclude: false,
module_roots: None,
module_depth: None,
git: false,
no_git: false,
max_commits: 1000,
max_commit_files: 100,
output: Some(output.to_path_buf()),
force: true,
bundle_dir: None,
max_output_bytes: 10_485_760,
log: None,
max_file_pct: 0.15,
max_file_tokens: None,
require_git_scores: false,
}
}
#[cfg(feature = "ast")]
fn write_syntax(args: &cli::PacketGenerateArgs, path: &Path) -> Result<()> {
use crate::commands::syntax;
let syntax_args = cli::SyntaxArgs {
max_bytes: tokmd_analysis::ast::DEFAULT_MAX_SYNTAX_BYTES,
include_generated_vendor: false,
paths: args.paths.clone(),
};
let packet = syntax::build_syntax_packet(&syntax_args)?;
let json = serde_json::to_string_pretty(&packet)?;
std::fs::write(path, json).with_context(|| format!("failed to write {}", path.display()))?;
Ok(())
}
#[cfg(not(feature = "ast"))]
fn write_syntax(_args: &cli::PacketGenerateArgs, _path: &Path) -> Result<()> {
anyhow::bail!("syntax evidence requires the `ast` feature")
}