1pub mod cockpit;
32
33use std::path::PathBuf;
34
35use serde::{Deserialize, Serialize};
36
37pub const SCHEMA_VERSION: u32 = 2;
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42pub struct Totals {
43 pub code: usize,
44 pub lines: usize,
45 pub files: usize,
46 pub bytes: usize,
47 pub tokens: usize,
48 pub avg_lines: usize,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
52pub struct LangRow {
53 pub lang: String,
54 pub code: usize,
55 pub lines: usize,
56 pub files: usize,
57 pub bytes: usize,
58 pub tokens: usize,
59 pub avg_lines: usize,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct LangReport {
64 pub rows: Vec<LangRow>,
65 pub total: Totals,
66 pub with_files: bool,
67 pub children: ChildrenMode,
68 pub top: usize,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
72pub struct ModuleRow {
73 pub module: String,
74 pub code: usize,
75 pub lines: usize,
76 pub files: usize,
77 pub bytes: usize,
78 pub tokens: usize,
79 pub avg_lines: usize,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct ModuleReport {
84 pub rows: Vec<ModuleRow>,
85 pub total: Totals,
86 pub module_roots: Vec<String>,
87 pub module_depth: usize,
88 pub children: ChildIncludeMode,
89 pub top: usize,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
93#[serde(rename_all = "snake_case")]
94pub enum FileKind {
95 Parent,
96 Child,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
100pub struct FileRow {
101 pub path: String,
102 pub module: String,
103 pub lang: String,
104 pub kind: FileKind,
105 pub code: usize,
106 pub comments: usize,
107 pub blanks: usize,
108 pub lines: usize,
109 pub bytes: usize,
110 pub tokens: usize,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ExportData {
115 pub rows: Vec<FileRow>,
116 pub module_roots: Vec<String>,
117 pub module_depth: usize,
118 pub children: ChildIncludeMode,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct RunReceipt {
123 pub schema_version: u32,
124 pub generated_at_ms: u128,
125 pub lang_file: String,
126 pub module_file: String,
127 pub export_file: String,
128 }
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132#[serde(rename_all = "snake_case")]
133pub enum ScanStatus {
134 Complete,
135 Partial,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
144#[serde(rename_all = "snake_case")]
145pub enum CommitIntentKind {
146 Feat,
147 Fix,
148 Refactor,
149 Docs,
150 Test,
151 Chore,
152 Ci,
153 Build,
154 Perf,
155 Style,
156 Revert,
157 Other,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, Default)]
161pub struct ToolInfo {
162 pub name: String,
163 pub version: String,
164}
165
166impl ToolInfo {
167 pub fn current() -> Self {
168 Self {
169 name: "tokmd".to_string(),
170 version: env!("CARGO_PKG_VERSION").to_string(),
171 }
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct ScanArgs {
177 pub paths: Vec<String>,
178 pub excluded: Vec<String>,
179 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
181 pub excluded_redacted: bool,
182 pub config: ConfigMode,
183 pub hidden: bool,
184 pub no_ignore: bool,
185 pub no_ignore_parent: bool,
186 pub no_ignore_dot: bool,
187 pub no_ignore_vcs: bool,
188 pub treat_doc_strings_as_comments: bool,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct LangArgsMeta {
193 pub format: String,
194 pub top: usize,
195 pub with_files: bool,
196 pub children: ChildrenMode,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct LangReceipt {
201 pub schema_version: u32,
202 pub generated_at_ms: u128,
203 pub tool: ToolInfo,
204 pub mode: String, pub status: ScanStatus,
206 pub warnings: Vec<String>,
207 pub scan: ScanArgs,
208 pub args: LangArgsMeta,
209 #[serde(flatten)]
210 pub report: LangReport,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct ModuleArgsMeta {
215 pub format: String,
216 pub module_roots: Vec<String>,
217 pub module_depth: usize,
218 pub children: ChildIncludeMode,
219 pub top: usize,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct ModuleReceipt {
224 pub schema_version: u32,
225 pub generated_at_ms: u128,
226 pub tool: ToolInfo,
227 pub mode: String, pub status: ScanStatus,
229 pub warnings: Vec<String>,
230 pub scan: ScanArgs,
231 pub args: ModuleArgsMeta,
232 #[serde(flatten)]
233 pub report: ModuleReport,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct ExportArgsMeta {
238 pub format: ExportFormat,
239 pub module_roots: Vec<String>,
240 pub module_depth: usize,
241 pub children: ChildIncludeMode,
242 pub min_code: usize,
243 pub max_rows: usize,
244 pub redact: RedactMode,
245 pub strip_prefix: Option<String>,
246 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
248 pub strip_prefix_redacted: bool,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct ExportReceipt {
253 pub schema_version: u32,
254 pub generated_at_ms: u128,
255 pub tool: ToolInfo,
256 pub mode: String, pub status: ScanStatus,
258 pub warnings: Vec<String>,
259 pub scan: ScanArgs,
260 pub args: ExportArgsMeta,
261 #[serde(flatten)]
262 pub data: ExportData,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct LangArgs {
267 pub paths: Vec<PathBuf>,
268 pub format: TableFormat,
269 pub top: usize,
270 pub files: bool,
271 pub children: ChildrenMode,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct ModuleArgs {
276 pub paths: Vec<PathBuf>,
277 pub format: TableFormat,
278 pub top: usize,
279 pub module_roots: Vec<String>,
280 pub module_depth: usize,
281 pub children: ChildIncludeMode,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct ExportArgs {
286 pub paths: Vec<PathBuf>,
287 pub format: ExportFormat,
288 pub output: Option<PathBuf>,
289 pub module_roots: Vec<String>,
290 pub module_depth: usize,
291 pub children: ChildIncludeMode,
292 pub min_code: usize,
293 pub max_rows: usize,
294 pub redact: RedactMode,
295 pub meta: bool,
296 pub strip_prefix: Option<PathBuf>,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct ContextReceipt {
301 pub schema_version: u32,
302 pub generated_at_ms: u128,
303 pub tool: ToolInfo,
304 pub mode: String,
305 pub budget_tokens: usize,
306 pub used_tokens: usize,
307 pub utilization_pct: f64,
308 pub strategy: String,
309 pub rank_by: String,
310 pub file_count: usize,
311 pub files: Vec<ContextFileRow>,
312 #[serde(default, skip_serializing_if = "Option::is_none")]
314 pub rank_by_effective: Option<String>,
315 #[serde(default, skip_serializing_if = "Option::is_none")]
317 pub fallback_reason: Option<String>,
318 #[serde(default, skip_serializing_if = "Vec::is_empty")]
320 pub excluded_by_policy: Vec<PolicyExcludedFile>,
321 #[serde(default, skip_serializing_if = "Option::is_none")]
323 pub token_estimation: Option<TokenEstimationMeta>,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
326 pub bundle_audit: Option<TokenAudit>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct ContextFileRow {
331 pub path: String,
332 pub module: String,
333 pub lang: String,
334 pub tokens: usize,
335 pub code: usize,
336 pub lines: usize,
337 pub bytes: usize,
338 pub value: usize,
339 #[serde(default, skip_serializing_if = "String::is_empty")]
340 pub rank_reason: String,
341 #[serde(default, skip_serializing_if = "is_default_policy")]
343 pub policy: InclusionPolicy,
344 #[serde(default, skip_serializing_if = "Option::is_none")]
346 pub effective_tokens: Option<usize>,
347 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub policy_reason: Option<String>,
350 #[serde(default, skip_serializing_if = "Vec::is_empty")]
352 pub classifications: Vec<FileClassification>,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
361pub struct DiffRow {
362 pub lang: String,
363 pub old_code: usize,
364 pub new_code: usize,
365 pub delta_code: i64,
366 pub old_lines: usize,
367 pub new_lines: usize,
368 pub delta_lines: i64,
369 pub old_files: usize,
370 pub new_files: usize,
371 pub delta_files: i64,
372 pub old_bytes: usize,
373 pub new_bytes: usize,
374 pub delta_bytes: i64,
375 pub old_tokens: usize,
376 pub new_tokens: usize,
377 pub delta_tokens: i64,
378}
379
380#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
382pub struct DiffTotals {
383 pub old_code: usize,
384 pub new_code: usize,
385 pub delta_code: i64,
386 pub old_lines: usize,
387 pub new_lines: usize,
388 pub delta_lines: i64,
389 pub old_files: usize,
390 pub new_files: usize,
391 pub delta_files: i64,
392 pub old_bytes: usize,
393 pub new_bytes: usize,
394 pub delta_bytes: i64,
395 pub old_tokens: usize,
396 pub new_tokens: usize,
397 pub delta_tokens: i64,
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct DiffReceipt {
403 pub schema_version: u32,
404 pub generated_at_ms: u128,
405 pub tool: ToolInfo,
406 pub mode: String,
407 pub from_source: String,
408 pub to_source: String,
409 pub diff_rows: Vec<DiffRow>,
410 pub totals: DiffTotals,
411}
412
413#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
418#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
419#[serde(rename_all = "kebab-case")]
420pub enum TableFormat {
421 Md,
423 Tsv,
425 Json,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
430#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
431#[serde(rename_all = "kebab-case")]
432pub enum ExportFormat {
433 Csv,
435 Jsonl,
437 Json,
439 Cyclonedx,
441}
442
443#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
444#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
445#[serde(rename_all = "kebab-case")]
446pub enum ConfigMode {
447 #[default]
449 Auto,
450 None,
452}
453
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
455#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
456#[serde(rename_all = "kebab-case")]
457pub enum ChildrenMode {
458 Collapse,
460 Separate,
462}
463
464#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
465#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
466#[serde(rename_all = "kebab-case")]
467pub enum ChildIncludeMode {
468 Separate,
470 ParentsOnly,
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
475#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
476#[serde(rename_all = "kebab-case")]
477pub enum RedactMode {
478 None,
480 Paths,
482 All,
484}
485
486#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
487#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
488#[serde(rename_all = "kebab-case")]
489pub enum AnalysisFormat {
490 Md,
491 Json,
492 Jsonld,
493 Xml,
494 Svg,
495 Mermaid,
496 Obj,
497 Midi,
498 Tree,
499 Html,
500}
501
502#[derive(Debug, Clone, Serialize, Deserialize)]
505pub struct ContextLogRecord {
506 pub schema_version: u32,
507 pub generated_at_ms: u128,
508 pub tool: ToolInfo,
509 pub budget_tokens: usize,
510 pub used_tokens: usize,
511 pub utilization_pct: f64,
512 pub strategy: String,
513 pub rank_by: String,
514 pub file_count: usize,
515 pub total_bytes: usize,
516 pub output_destination: String,
517}
518
519pub const HANDOFF_SCHEMA_VERSION: u32 = 5;
525
526pub const CONTEXT_BUNDLE_SCHEMA_VERSION: u32 = 2;
528
529pub const CONTEXT_SCHEMA_VERSION: u32 = 4;
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct TokenEstimationMeta {
545 pub bytes_per_token_est: f64,
547 pub bytes_per_token_low: f64,
549 pub bytes_per_token_high: f64,
551 #[serde(alias = "tokens_high")]
553 pub tokens_min: usize,
554 pub tokens_est: usize,
556 #[serde(alias = "tokens_low")]
558 pub tokens_max: usize,
559 pub source_bytes: usize,
561}
562
563impl TokenEstimationMeta {
564 pub const DEFAULT_BPT_EST: f64 = 4.0;
566 pub const DEFAULT_BPT_LOW: f64 = 3.0;
567 pub const DEFAULT_BPT_HIGH: f64 = 5.0;
568
569 pub fn from_bytes(bytes: usize, bpt: f64) -> Self {
571 Self::from_bytes_with_bounds(bytes, bpt, Self::DEFAULT_BPT_LOW, Self::DEFAULT_BPT_HIGH)
572 }
573
574 pub fn from_bytes_with_bounds(bytes: usize, bpt_est: f64, bpt_low: f64, bpt_high: f64) -> Self {
576 Self {
577 bytes_per_token_est: bpt_est,
578 bytes_per_token_low: bpt_low,
579 bytes_per_token_high: bpt_high,
580 tokens_min: (bytes as f64 / bpt_high).ceil() as usize,
581 tokens_est: (bytes as f64 / bpt_est).ceil() as usize,
582 tokens_max: (bytes as f64 / bpt_low).ceil() as usize,
583 source_bytes: bytes,
584 }
585 }
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct TokenAudit {
591 pub output_bytes: u64,
593 #[serde(alias = "tokens_high")]
595 pub tokens_min: usize,
596 pub tokens_est: usize,
598 #[serde(alias = "tokens_low")]
600 pub tokens_max: usize,
601 pub overhead_bytes: u64,
603 pub overhead_pct: f64,
605}
606
607impl TokenAudit {
608 pub fn from_output(output_bytes: u64, content_bytes: u64) -> Self {
610 Self::from_output_with_divisors(
611 output_bytes,
612 content_bytes,
613 TokenEstimationMeta::DEFAULT_BPT_EST,
614 TokenEstimationMeta::DEFAULT_BPT_LOW,
615 TokenEstimationMeta::DEFAULT_BPT_HIGH,
616 )
617 }
618
619 pub fn from_output_with_divisors(
621 output_bytes: u64,
622 content_bytes: u64,
623 bpt_est: f64,
624 bpt_low: f64,
625 bpt_high: f64,
626 ) -> Self {
627 let overhead_bytes = output_bytes.saturating_sub(content_bytes);
628 let overhead_pct = if output_bytes > 0 {
629 overhead_bytes as f64 / output_bytes as f64
630 } else {
631 0.0
632 };
633 Self {
634 output_bytes,
635 tokens_min: (output_bytes as f64 / bpt_high).ceil() as usize,
636 tokens_est: (output_bytes as f64 / bpt_est).ceil() as usize,
637 tokens_max: (output_bytes as f64 / bpt_low).ceil() as usize,
638 overhead_bytes,
639 overhead_pct,
640 }
641 }
642}
643
644#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
650#[serde(rename_all = "snake_case")]
651pub enum FileClassification {
652 Generated,
654 Fixture,
656 Vendored,
658 Lockfile,
660 Minified,
662 DataBlob,
664 Sourcemap,
666}
667
668#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
670#[serde(rename_all = "snake_case")]
671pub enum InclusionPolicy {
672 #[default]
674 Full,
675 HeadTail,
677 Summary,
679 Skip,
681}
682
683fn is_default_policy(policy: &InclusionPolicy) -> bool {
685 *policy == InclusionPolicy::Full
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct PolicyExcludedFile {
691 pub path: String,
692 pub original_tokens: usize,
693 pub policy: InclusionPolicy,
694 pub reason: String,
695 pub classifications: Vec<FileClassification>,
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize)]
700pub struct HandoffManifest {
701 pub schema_version: u32,
702 pub generated_at_ms: u128,
703 pub tool: ToolInfo,
704 pub mode: String,
705 pub inputs: Vec<String>,
706 pub output_dir: String,
707 pub budget_tokens: usize,
708 pub used_tokens: usize,
709 pub utilization_pct: f64,
710 pub strategy: String,
711 pub rank_by: String,
712 pub capabilities: Vec<CapabilityStatus>,
713 pub artifacts: Vec<ArtifactEntry>,
714 pub included_files: Vec<ContextFileRow>,
715 pub excluded_paths: Vec<HandoffExcludedPath>,
716 pub excluded_patterns: Vec<String>,
717 pub smart_excluded_files: Vec<SmartExcludedFile>,
718 pub total_files: usize,
719 pub bundled_files: usize,
720 pub intelligence_preset: String,
721 #[serde(default, skip_serializing_if = "Option::is_none")]
723 pub rank_by_effective: Option<String>,
724 #[serde(default, skip_serializing_if = "Option::is_none")]
726 pub fallback_reason: Option<String>,
727 #[serde(default, skip_serializing_if = "Vec::is_empty")]
729 pub excluded_by_policy: Vec<PolicyExcludedFile>,
730 #[serde(default, skip_serializing_if = "Option::is_none")]
732 pub token_estimation: Option<TokenEstimationMeta>,
733 #[serde(default, skip_serializing_if = "Option::is_none")]
735 pub code_audit: Option<TokenAudit>,
736}
737
738#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct SmartExcludedFile {
741 pub path: String,
742 pub reason: String,
743 pub tokens: usize,
744}
745
746#[derive(Debug, Clone, Serialize, Deserialize)]
748pub struct ContextBundleManifest {
749 pub schema_version: u32,
750 pub generated_at_ms: u128,
751 pub tool: ToolInfo,
752 pub mode: String,
753 pub budget_tokens: usize,
754 pub used_tokens: usize,
755 pub utilization_pct: f64,
756 pub strategy: String,
757 pub rank_by: String,
758 pub file_count: usize,
759 pub bundle_bytes: usize,
760 pub artifacts: Vec<ArtifactEntry>,
761 pub included_files: Vec<ContextFileRow>,
762 pub excluded_paths: Vec<ContextExcludedPath>,
763 pub excluded_patterns: Vec<String>,
764 #[serde(default, skip_serializing_if = "Option::is_none")]
766 pub rank_by_effective: Option<String>,
767 #[serde(default, skip_serializing_if = "Option::is_none")]
769 pub fallback_reason: Option<String>,
770 #[serde(default, skip_serializing_if = "Vec::is_empty")]
772 pub excluded_by_policy: Vec<PolicyExcludedFile>,
773 #[serde(default, skip_serializing_if = "Option::is_none")]
775 pub token_estimation: Option<TokenEstimationMeta>,
776 #[serde(default, skip_serializing_if = "Option::is_none")]
778 pub bundle_audit: Option<TokenAudit>,
779}
780
781#[derive(Debug, Clone, Serialize, Deserialize)]
783pub struct ContextExcludedPath {
784 pub path: String,
785 pub reason: String,
786}
787
788#[derive(Debug, Clone, Serialize, Deserialize)]
790pub struct HandoffIntelligence {
791 pub tree: Option<String>,
792 pub tree_depth: Option<usize>,
793 pub hotspots: Option<Vec<HandoffHotspot>>,
794 pub complexity: Option<HandoffComplexity>,
795 pub derived: Option<HandoffDerived>,
796 pub warnings: Vec<String>,
797}
798
799#[derive(Debug, Clone, Serialize, Deserialize)]
801pub struct HandoffExcludedPath {
802 pub path: String,
803 pub reason: String,
804}
805
806#[derive(Debug, Clone, Serialize, Deserialize)]
808pub struct HandoffHotspot {
809 pub path: String,
810 pub commits: usize,
811 pub lines: usize,
812 pub score: usize,
813}
814
815#[derive(Debug, Clone, Serialize, Deserialize)]
817pub struct HandoffComplexity {
818 pub total_functions: usize,
819 pub avg_function_length: f64,
820 pub max_function_length: usize,
821 pub avg_cyclomatic: f64,
822 pub max_cyclomatic: usize,
823 pub high_risk_files: usize,
824}
825
826#[derive(Debug, Clone, Serialize, Deserialize)]
828pub struct HandoffDerived {
829 pub total_files: usize,
830 pub total_code: usize,
831 pub total_lines: usize,
832 pub total_tokens: usize,
833 pub lang_count: usize,
834 pub dominant_lang: String,
835 pub dominant_pct: f64,
836}
837
838#[derive(Debug, Clone, Serialize, Deserialize)]
840pub struct CapabilityStatus {
841 pub name: String,
842 pub status: CapabilityState,
843 #[serde(skip_serializing_if = "Option::is_none")]
844 pub reason: Option<String>,
845}
846
847#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
849#[serde(rename_all = "snake_case")]
850pub enum CapabilityState {
851 Available,
853 Skipped,
855 Unavailable,
857}
858
859#[derive(Debug, Clone, Serialize, Deserialize)]
861pub struct ArtifactEntry {
862 pub name: String,
863 pub path: String,
864 pub description: String,
865 pub bytes: u64,
866 #[serde(skip_serializing_if = "Option::is_none")]
867 pub hash: Option<ArtifactHash>,
868}
869
870#[derive(Debug, Clone, Serialize, Deserialize)]
872pub struct ArtifactHash {
873 pub algo: String,
874 pub hash: String,
875}