Compare commits

3 Commits

Author SHA1 Message Date
f4f8b8fa34 rename: archdoc → wtismycode (WTIsMyCode) 2026-02-15 12:12:33 +03:00
136697caf0 fix: prefer file docstring over __init__.py for module summary
For non-init files, use the file's own docstring first before
falling back to the parent __init__.py docstring.

Also skip dataclass-like classes (≤2 methods) from critical marking
to avoid false positives on simple data containers like ToolResult.
2026-02-15 11:36:49 +03:00
8e79e3950f feat: auto-detect project name from pyproject.toml or directory basename
On init, detect project name by:
1. Parsing pyproject.toml [project] name field
2. Falling back to directory basename
Replace <PROJECT_NAME> placeholder in ARCHITECTURE.md template.
2026-02-15 11:36:45 +03:00
87 changed files with 317 additions and 217 deletions

View File

@@ -1,6 +1,6 @@
# Changelog
All notable changes to ArchDoc are documented in this file.
All notable changes to WTIsMyCode are documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/).
@@ -12,8 +12,8 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
- **Dependency cycle detection** (`cycle_detector.rs`) — DFS-based algorithm to find circular module dependencies
- **Cycle detection in renderer** — Critical points section now shows detected dependency cycles
- **Full pipeline integration tests** — Tests for config validation, scanning, cycle detection, and rendering
- **Stats command** — `archdoc stats` displays project-level statistics (files, modules, symbols, edges)
- **Check command** — `archdoc check` verifies documentation consistency with code
- **Stats command** — `wtismycode stats` displays project-level statistics (files, modules, symbols, edges)
- **Check command** — `wtismycode check` verifies documentation consistency with code
- **Colored CLI output** — Progress bars and colored status messages
- **Comprehensive README** — Badges, configuration reference table, command documentation, architecture overview

72
Cargo.lock generated
View File

@@ -79,42 +79,6 @@ version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
name = "archdoc-cli"
version = "0.1.0"
dependencies = [
"anyhow",
"archdoc-core",
"clap",
"colored",
"indicatif",
"serde",
"serde_json",
"thiserror 1.0.69",
"tokio",
"toml 0.8.23",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "archdoc-core"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"handlebars",
"rustpython-ast",
"rustpython-parser",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"toml 0.9.12+spec-1.1.0",
"tracing",
"walkdir",
]
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -2063,6 +2027,42 @@ dependencies = [
"wasmparser",
]
[[package]]
name = "wtismycode-cli"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"colored",
"indicatif",
"serde",
"serde_json",
"thiserror 1.0.69",
"tokio",
"toml 0.8.23",
"tracing",
"tracing-subscriber",
"wtismycode-core",
]
[[package]]
name = "wtismycode-core"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"handlebars",
"rustpython-ast",
"rustpython-parser",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"toml 0.9.12+spec-1.1.0",
"tracing",
"walkdir",
]
[[package]]
name = "zerocopy"
version = "0.8.39"

View File

@@ -1,3 +1,3 @@
[workspace]
members = ["archdoc-cli", "archdoc-core"]
members = ["wtismycode-cli", "wtismycode-core"]
resolver = "3"

View File

@@ -1,8 +1,8 @@
# PR: Major improvements to ArchDoc
# PR: Major improvements to WTIsMyCode
## Summary
Comprehensive refactoring and feature additions to ArchDoc — the Python architecture documentation generator. This PR improves code quality, adds new features, and significantly enhances the development experience.
Comprehensive refactoring and feature additions to WTIsMyCode — the Python architecture documentation generator. This PR improves code quality, adds new features, and significantly enhances the development experience.
**Stats:** 24 files changed, ~3900 insertions, ~1400 deletions, 50 tests

View File

@@ -1,4 +1,4 @@
# ArchDoc
# WTIsMyCode
![Rust](https://img.shields.io/badge/Rust-1.85%2B-orange?logo=rust)
![License](https://img.shields.io/badge/License-MIT-blue)
@@ -6,7 +6,7 @@
**Automatic architecture documentation generator for Python projects.**
ArchDoc analyzes your Python codebase using AST parsing and generates comprehensive Markdown documentation covering module structure, dependencies, integration points, and critical hotspots.
WTIsMyCode analyzes your Python codebase using AST parsing and generates comprehensive Markdown documentation covering module structure, dependencies, integration points, and critical hotspots.
## Features
@@ -15,7 +15,7 @@ ArchDoc analyzes your Python codebase using AST parsing and generates comprehens
- **Integration Detection** — Automatically identifies HTTP, database, and message queue integrations
- **Diff-Aware Updates** — Preserves manually written sections while regenerating docs
- **Caching** — Content-hash based caching for fast incremental regeneration
- **Config Validation** — Comprehensive validation of `archdoc.toml` with helpful error messages
- **Config Validation** — Comprehensive validation of `wtismycode.toml` with helpful error messages
- **Statistics** — Project-level stats: file counts, symbol counts, fan-in/fan-out metrics
- **Consistency Checks** — Verify documentation stays in sync with code changes
@@ -24,33 +24,33 @@ ArchDoc analyzes your Python codebase using AST parsing and generates comprehens
Requires Rust 1.85+:
```bash
cargo install --path archdoc-cli
cargo install --path wtismycode-cli
```
## Quick Start
```bash
# Initialize config in your Python project
archdoc init
wtismycode init
# Generate architecture docs
archdoc generate
wtismycode generate
# View project statistics
archdoc stats
wtismycode stats
# Check docs are up-to-date
archdoc check
wtismycode check
```
## Commands
### `archdoc generate`
### `wtismycode generate`
Scans the project, analyzes Python files, and generates documentation:
```
$ archdoc generate
$ wtismycode generate
🔍 Scanning project...
📂 Found 24 Python files in 6 modules
🔬 Analyzing dependencies...
@@ -65,12 +65,12 @@ Output includes:
- **Integration map** — HTTP, database, and queue integration points
- **Critical points** — High fan-in/fan-out symbols and dependency cycles
### `archdoc stats`
### `wtismycode stats`
Displays project statistics without generating docs:
```
$ archdoc stats
$ wtismycode stats
📊 Project Statistics
Files: 24
Modules: 6
@@ -80,29 +80,29 @@ $ archdoc stats
Edges: 134
```
### `archdoc check`
### `wtismycode check`
Verifies documentation consistency with the current codebase:
```
$ archdoc check
$ wtismycode check
✅ Documentation is up-to-date
```
Returns non-zero exit code if docs are stale — useful in CI pipelines.
### `archdoc init`
### `wtismycode init`
Creates a default `archdoc.toml` configuration file:
Creates a default `wtismycode.toml` configuration file:
```
$ archdoc init
✅ Created archdoc.toml with default settings
$ wtismycode init
✅ Created wtismycode.toml with default settings
```
## Configuration Reference
ArchDoc is configured via `archdoc.toml`:
WTIsMyCode is configured via `wtismycode.toml`:
| Section | Key | Default | Description |
|---------|-----|---------|-------------|
@@ -125,7 +125,7 @@ ArchDoc is configured via `archdoc.toml`:
| `thresholds` | `critical_fan_in` | `20` | Fan-in threshold for critical symbols |
| `thresholds` | `critical_fan_out` | `20` | Fan-out threshold for critical symbols |
| `caching` | `enabled` | `true` | Enable analysis caching |
| `caching` | `cache_dir` | `".archdoc/cache"` | Cache directory |
| `caching` | `cache_dir` | `".wtismycode/cache"` | Cache directory |
| `caching` | `max_cache_age` | `"24h"` | Cache TTL (supports s, m, h, d, w) |
### Example Configuration
@@ -172,12 +172,12 @@ max_cache_age = "24h"
## Architecture
```
archdoc/
├── archdoc-cli/ # CLI binary (commands, output formatting)
wtismycode/
├── wtismycode-cli/ # CLI binary (commands, output formatting)
│ └── src/
│ ├── main.rs
│ └── commands/ # generate, check, stats, init
├── archdoc-core/ # Core library
├── wtismycode-core/ # Core library
│ └── src/
│ ├── config.rs # Config loading & validation
│ ├── scanner.rs # File discovery

View File

@@ -17,7 +17,7 @@
## Document metadata
- **Created:** 2026-01-25
- **Updated:** 2026-02-15
- **Generated by:** archdoc (cli) v0.1
- **Generated by:** wtismycode (cli) v0.1
---

View File

@@ -1,6 +1,6 @@
# Test Project
A test project for ArchDoc development and testing.
A test project for WTIsMyCode development and testing.
## Installation

View File

@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "test-project"
version = "0.1.0"
description = "A test project for ArchDoc"
description = "A test project for WTIsMyCode"
authors = [
{name = "Test Author", email = "test@example.com"}
]

View File

@@ -53,10 +53,10 @@ description_max_length = 200
[logging]
level = "info"
file = "archdoc.log"
file = "wtismycode.log"
format = "compact"
[caching]
enabled = true
cache_dir = ".archdoc/cache"
cache_dir = ".wtismycode/cache"
max_cache_age = "24h"

View File

@@ -1,10 +1,14 @@
[package]
name = "archdoc-cli"
name = "wtismycode-cli"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "wtismycode"
path = "src/main.rs"
[dependencies]
archdoc-core = { path = "../archdoc-core" }
wtismycode-core = { path = "../wtismycode-core" }
clap = { version = "4.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use archdoc_core::Config;
use wtismycode_core::Config;
use colored::Colorize;
use super::generate::analyze_project;
@@ -9,7 +9,7 @@ pub fn check_docs_consistency(root: &str, config: &Config) -> Result<()> {
let model = analyze_project(root, config)?;
let renderer = archdoc_core::renderer::Renderer::new();
let renderer = wtismycode_core::renderer::Renderer::new();
let _generated = renderer.render_architecture_md(&model, None)?;
let architecture_md_path = std::path::Path::new(root).join(&config.project.entry_file);

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use archdoc_core::{Config, ProjectModel, scanner::FileScanner, python_analyzer::PythonAnalyzer};
use wtismycode_core::{Config, ProjectModel, scanner::FileScanner, python_analyzer::PythonAnalyzer};
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use std::path::Path;
@@ -118,8 +118,8 @@ pub fn generate_docs(model: &ProjectModel, out: &str, verbose: bool, _config: &C
}
}
let renderer = archdoc_core::renderer::Renderer::new();
let writer = archdoc_core::writer::DiffAwareWriter::new();
let renderer = wtismycode_core::renderer::Renderer::new();
let writer = wtismycode_core::writer::DiffAwareWriter::new();
let output_path = std::path::Path::new(".").join("ARCHITECTURE.md");

View File

@@ -1,8 +1,47 @@
use anyhow::Result;
use colored::Colorize;
/// Detect project name from pyproject.toml or directory basename.
fn detect_project_name(root: &str) -> String {
let root_path = std::path::Path::new(root);
// Try pyproject.toml
let pyproject_path = root_path.join("pyproject.toml");
if let Ok(content) = std::fs::read_to_string(&pyproject_path) {
let mut in_project = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed == "[project]" {
in_project = true;
continue;
}
if trimmed.starts_with('[') {
in_project = false;
continue;
}
if in_project && trimmed.starts_with("name") {
if let Some(val) = trimmed.split('=').nth(1) {
let name = val.trim().trim_matches('"').trim_matches('\'');
if !name.is_empty() {
return name.to_string();
}
}
}
}
}
// Fallback: directory basename
root_path
.canonicalize()
.ok()
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()))
.unwrap_or_else(|| "Project".to_string())
}
pub fn init_project(root: &str, out: &str) -> Result<()> {
println!("{}", "Initializing archdoc project...".cyan().bold());
println!("{}", "Initializing wtismycode project...".cyan().bold());
let project_name = detect_project_name(root);
let out_path = std::path::Path::new(out);
std::fs::create_dir_all(out_path)?;
@@ -45,7 +84,7 @@ pub fn init_project(root: &str, out: &str) -> Result<()> {
## Document metadata
- **Created:** <AUTO_ON_INIT: YYYY-MM-DD>
- **Updated:** <AUTO_ON_CHANGE: YYYY-MM-DD>
- **Generated by:** archdoc (cli) v0.1
- **Generated by:** wtismycode (cli) v0.1
---
@@ -95,8 +134,10 @@ pub fn init_project(root: &str, out: &str) -> Result<()> {
<!-- MANUAL:END -->
"#;
let architecture_md_content = architecture_md_content.replace("<PROJECT_NAME>", &project_name);
let architecture_md_path = std::path::Path::new(root).join("ARCHITECTURE.md");
std::fs::write(&architecture_md_path, architecture_md_content)?;
std::fs::write(&architecture_md_path, &architecture_md_content)?;
let config_toml_content = r#"[project]
root = "."
@@ -153,16 +194,16 @@ description_max_length = 200
[logging]
level = "info"
file = "archdoc.log"
file = "wtismycode.log"
format = "compact"
[caching]
enabled = true
cache_dir = ".archdoc/cache"
cache_dir = ".wtismycode/cache"
max_cache_age = "24h"
"#;
let config_toml_path = std::path::Path::new(root).join("archdoc.toml");
let config_toml_path = std::path::Path::new(root).join("wtismycode.toml");
if !config_toml_path.exists() {
std::fs::write(&config_toml_path, config_toml_content)?;
}

View File

@@ -1,10 +1,10 @@
use archdoc_core::ProjectModel;
use wtismycode_core::ProjectModel;
use colored::Colorize;
pub fn print_stats(model: &ProjectModel) {
println!();
println!("{}", "╔══════════════════════════════════════╗".cyan());
println!("{}", "archdoc project statistics ║".cyan().bold());
println!("{}", "wtismycode project statistics ║".cyan().bold());
println!("{}", "╚══════════════════════════════════════╝".cyan());
println!();
@@ -24,10 +24,10 @@ pub fn print_stats(model: &ProjectModel) {
let mut async_functions = 0;
for symbol in model.symbols.values() {
match symbol.kind {
archdoc_core::model::SymbolKind::Function => functions += 1,
archdoc_core::model::SymbolKind::Method => methods += 1,
archdoc_core::model::SymbolKind::Class => classes += 1,
archdoc_core::model::SymbolKind::AsyncFunction => async_functions += 1,
wtismycode_core::model::SymbolKind::Function => functions += 1,
wtismycode_core::model::SymbolKind::Method => methods += 1,
wtismycode_core::model::SymbolKind::Class => classes += 1,
wtismycode_core::model::SymbolKind::AsyncFunction => async_functions += 1,
}
}
println!("{}", "Symbol breakdown".bold().underline());

View File

@@ -5,7 +5,7 @@ use clap::{Parser, Subcommand};
use anyhow::Result;
#[derive(Parser)]
#[command(name = "archdoc")]
#[command(name = "wtismycode")]
#[command(about = "Generate architecture documentation for Python projects")]
#[command(version = "0.1.0")]
pub struct Cli {
@@ -19,7 +19,7 @@ pub struct Cli {
#[derive(Subcommand)]
enum Commands {
/// Initialize archdoc in the project
/// Initialize wtismycode in the project
Init {
#[arg(short, long, default_value = ".")]
root: String,
@@ -32,7 +32,7 @@ enum Commands {
root: String,
#[arg(short, long, default_value = "docs/architecture")]
out: String,
#[arg(short, long, default_value = "archdoc.toml")]
#[arg(short, long, default_value = "wtismycode.toml")]
config: String,
/// Show what would be generated without writing files
#[arg(long)]
@@ -42,14 +42,14 @@ enum Commands {
Check {
#[arg(short, long, default_value = ".")]
root: String,
#[arg(short, long, default_value = "archdoc.toml")]
#[arg(short, long, default_value = "wtismycode.toml")]
config: String,
},
/// Show project statistics
Stats {
#[arg(short, long, default_value = ".")]
root: String,
#[arg(short, long, default_value = "archdoc.toml")]
#[arg(short, long, default_value = "wtismycode.toml")]
config: String,
},
}

View File

@@ -1,7 +1,7 @@
//! Colored output helpers and filename utilities for ArchDoc CLI
//! Colored output helpers and filename utilities for WTIsMyCode CLI
use colored::Colorize;
use archdoc_core::ProjectModel;
use wtismycode_core::ProjectModel;
/// Sanitize a file path into a safe filename for docs.
/// Removes `./` prefix, replaces `/` with `__`.

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.939017204Z","file_modified_at":"2026-02-15T09:12:21.938241573Z","parsed_module":{"path":"/tmp/.tmpjrzBI1/test.py","module_path":"/tmp/.tmpjrzBI1/test.py","imports":[],"symbols":[{"id":"calculate_sum","kind":"Function","module_id":"","file_id":"","qualname":"calculate_sum","signature":"def calculate_sum(a, b)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.929046662Z","file_modified_at":"2026-02-15T09:12:21.928241645Z","parsed_module":{"path":"/tmp/.tmpucjtMF/test.py","module_path":"/tmp/.tmpucjtMF/test.py","imports":[{"module_name":"redis","alias":null,"line_number":8}],"symbols":[{"id":"process_job","kind":"Function","module_id":"","file_id":"","qualname":"process_job","signature":"def process_job(job_data)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"redis.Redis","line_number":55,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"client.lpush","line_number":73,"call_type":"Unresolved"},{"caller_symbol":"process_job","callee_expr":"redis.Redis","line_number":55,"call_type":"Unresolved"},{"caller_symbol":"process_job","callee_expr":"client.lpush","line_number":73,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.901000313Z","file_modified_at":"2026-02-15T09:12:21.900241847Z","parsed_module":{"path":"/tmp/.tmpQwpTTi/test.py","module_path":"/tmp/.tmpQwpTTi/test.py","imports":[],"symbols":[{"id":"hello","kind":"Function","module_id":"","file_id":"","qualname":"hello","signature":"def hello()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator","kind":"Class","module_id":"","file_id":"","qualname":"Calculator","signature":"class Calculator","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator.add","kind":"Method","module_id":"","file_id":"","qualname":"Calculator.add","signature":"def add(self, a, b)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.638281687Z","file_modified_at":"2026-02-15T09:12:27.637200566Z","parsed_module":{"path":"/tmp/.tmp5HECBh/test.py","module_path":"/tmp/.tmp5HECBh/test.py","imports":[{"module_name":"requests","alias":null,"line_number":8}],"symbols":[{"id":"fetch_data","kind":"Function","module_id":"","file_id":"","qualname":"fetch_data","signature":"def fetch_data()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"requests.get","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"response.json","line_number":107,"call_type":"Unresolved"},{"caller_symbol":"fetch_data","callee_expr":"requests.get","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"fetch_data","callee_expr":"response.json","line_number":107,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.938417589Z","file_modified_at":"2026-02-15T09:12:21.937241580Z","parsed_module":{"path":"/tmp/.tmpHn93FX/test.py","module_path":"/tmp/.tmpHn93FX/test.py","imports":[{"module_name":"requests","alias":null,"line_number":8}],"symbols":[{"id":"fetch_data","kind":"Function","module_id":"","file_id":"","qualname":"fetch_data","signature":"def fetch_data()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"requests.get","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"response.json","line_number":107,"call_type":"Unresolved"},{"caller_symbol":"fetch_data","callee_expr":"requests.get","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"fetch_data","callee_expr":"response.json","line_number":107,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.900267168Z","file_modified_at":"2026-02-15T09:12:21.899241854Z","parsed_module":{"path":"/tmp/.tmpVPUjB4/test.py","module_path":"/tmp/.tmpVPUjB4/test.py","imports":[],"symbols":[{"id":"hello","kind":"Function","module_id":"","file_id":"","qualname":"hello","signature":"def hello()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.939756459Z","file_modified_at":"2026-02-15T09:12:21.938241573Z","parsed_module":{"path":"/tmp/.tmp5yAI8O/test.py","module_path":"/tmp/.tmp5yAI8O/test.py","imports":[{"module_name":"redis","alias":null,"line_number":8}],"symbols":[{"id":"process_job","kind":"Function","module_id":"","file_id":"","qualname":"process_job","signature":"def process_job(job_data)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"redis.Redis","line_number":55,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"client.lpush","line_number":73,"call_type":"Unresolved"},{"caller_symbol":"process_job","callee_expr":"redis.Redis","line_number":55,"call_type":"Unresolved"},{"caller_symbol":"process_job","callee_expr":"client.lpush","line_number":73,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.949122466Z","file_modified_at":"2026-02-15T00:22:51.124088300Z","parsed_module":{"path":"../test-project/src/utils.py","module_path":"../test-project/src/utils.py","imports":[{"module_name":"json","alias":null,"line_number":54},{"module_name":"os","alias":null,"line_number":66}],"symbols":[{"id":"load_config","kind":"Function","module_id":"","file_id":"","qualname":"load_config","signature":"def load_config(config_path: str)","annotations":null,"docstring_first_line":"Load configuration from a JSON file.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"save_config","kind":"Function","module_id":"","file_id":"","qualname":"save_config","signature":"def save_config(config: dict, config_path: str)","annotations":null,"docstring_first_line":"Save configuration to a JSON file.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"get_file_size","kind":"Function","module_id":"","file_id":"","qualname":"get_file_size","signature":"def get_file_size(filepath: str)","annotations":null,"docstring_first_line":"Get the size of a file in bytes.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"format_bytes","kind":"Function","module_id":"","file_id":"","qualname":"format_bytes","signature":"def format_bytes(size: int)","annotations":null,"docstring_first_line":"Format bytes into a human-readable string.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"open","line_number":169,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"json.load","line_number":213,"call_type":"Unresolved"},{"caller_symbol":"load_config","callee_expr":"open","line_number":169,"call_type":"Unresolved"},{"caller_symbol":"load_config","callee_expr":"json.load","line_number":213,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"open","line_number":330,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"json.dump","line_number":367,"call_type":"Unresolved"},{"caller_symbol":"save_config","callee_expr":"open","line_number":330,"call_type":"Unresolved"},{"caller_symbol":"save_config","callee_expr":"json.dump","line_number":367,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"os.path.getsize","line_number":494,"call_type":"Unresolved"},{"caller_symbol":"get_file_size","callee_expr":"os.path.getsize","line_number":494,"call_type":"Unresolved"}],"file_docstring":"Utility functions for the test project."}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.932282950Z","file_modified_at":"2026-02-15T09:12:21.931241624Z","parsed_module":{"path":"/tmp/.tmpMK4GyS/test.py","module_path":"/tmp/.tmpMK4GyS/test.py","imports":[],"symbols":[{"id":"hello","kind":"Function","module_id":"","file_id":"","qualname":"hello","signature":"def hello()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"goodbye","kind":"Function","module_id":"","file_id":"","qualname":"goodbye","signature":"def goodbye()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.646855488Z","file_modified_at":"2026-02-15T09:12:27.645200509Z","parsed_module":{"path":"/tmp/.tmpXh0uQg/test.py","module_path":"/tmp/.tmpXh0uQg/test.py","imports":[],"symbols":[{"id":"calculate_sum","kind":"Function","module_id":"","file_id":"","qualname":"calculate_sum","signature":"def calculate_sum(a, b)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.932289740Z","file_modified_at":"2026-02-15T09:12:21.931241624Z","parsed_module":{"path":"/tmp/.tmpn1WePQ/test.py","module_path":"/tmp/.tmpn1WePQ/test.py","imports":[],"symbols":[{"id":"hello","kind":"Function","module_id":"","file_id":"","qualname":"hello","signature":"def hello()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator","kind":"Class","module_id":"","file_id":"","qualname":"Calculator","signature":"class Calculator","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator.add","kind":"Method","module_id":"","file_id":"","qualname":"Calculator.add","signature":"def add(self, a, b)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.646347331Z","file_modified_at":"2026-02-15T09:12:27.645200509Z","parsed_module":{"path":"/tmp/.tmpFFmDl3/test.py","module_path":"/tmp/.tmpFFmDl3/test.py","imports":[{"module_name":"sqlite3","alias":null,"line_number":8}],"symbols":[{"id":"get_user","kind":"Function","module_id":"","file_id":"","qualname":"get_user","signature":"def get_user(user_id)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"sqlite3.connect","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"conn.cursor","line_number":95,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"cursor.execute","line_number":113,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"cursor.fetchone","line_number":187,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"sqlite3.connect","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"conn.cursor","line_number":95,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"cursor.execute","line_number":113,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"cursor.fetchone","line_number":187,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.937802033Z","file_modified_at":"2026-02-15T09:12:21.936241587Z","parsed_module":{"path":"/tmp/.tmpU9hOcm/test.py","module_path":"/tmp/.tmpU9hOcm/test.py","imports":[{"module_name":"sqlite3","alias":null,"line_number":8}],"symbols":[{"id":"get_user","kind":"Function","module_id":"","file_id":"","qualname":"get_user","signature":"def get_user(user_id)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"sqlite3.connect","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"conn.cursor","line_number":95,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"cursor.execute","line_number":113,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"cursor.fetchone","line_number":187,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"sqlite3.connect","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"conn.cursor","line_number":95,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"cursor.execute","line_number":113,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"cursor.fetchone","line_number":187,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.646167123Z","file_modified_at":"2026-02-15T09:12:27.645200509Z","parsed_module":{"path":"/tmp/.tmpj84SS2/test.py","module_path":"/tmp/.tmpj84SS2/test.py","imports":[{"module_name":"requests","alias":null,"line_number":8}],"symbols":[{"id":"fetch_data","kind":"Function","module_id":"","file_id":"","qualname":"fetch_data","signature":"def fetch_data()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"requests.get","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"response.json","line_number":107,"call_type":"Unresolved"},{"caller_symbol":"fetch_data","callee_expr":"requests.get","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"fetch_data","callee_expr":"response.json","line_number":107,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.647109436Z","file_modified_at":"2026-02-15T09:12:27.646200502Z","parsed_module":{"path":"/tmp/.tmpTS6Kf7/test.py","module_path":"/tmp/.tmpTS6Kf7/test.py","imports":[{"module_name":"redis","alias":null,"line_number":8}],"symbols":[{"id":"process_job","kind":"Function","module_id":"","file_id":"","qualname":"process_job","signature":"def process_job(job_data)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"redis.Redis","line_number":55,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"client.lpush","line_number":73,"call_type":"Unresolved"},{"caller_symbol":"process_job","callee_expr":"redis.Redis","line_number":55,"call_type":"Unresolved"},{"caller_symbol":"process_job","callee_expr":"client.lpush","line_number":73,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.906280597Z","file_modified_at":"2026-02-15T00:21:25.872722975Z","parsed_module":{"path":"tests/golden/test_project/src/example.py","module_path":"tests/golden/test_project/src/example.py","imports":[{"module_name":"os","alias":null,"line_number":42},{"module_name":"typing.List","alias":null,"line_number":64}],"symbols":[{"id":"Calculator","kind":"Class","module_id":"","file_id":"","qualname":"Calculator","signature":"class Calculator","annotations":null,"docstring_first_line":"A simple calculator class.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator.__init__","kind":"Method","module_id":"","file_id":"","qualname":"Calculator.__init__","signature":"def __init__(self)","annotations":null,"docstring_first_line":"Initialize the calculator.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator.add","kind":"Method","module_id":"","file_id":"","qualname":"Calculator.add","signature":"def add(self, a: int, b: int)","annotations":null,"docstring_first_line":"Add two numbers.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator.multiply","kind":"Method","module_id":"","file_id":"","qualname":"Calculator.multiply","signature":"def multiply(self, a: int, b: int)","annotations":null,"docstring_first_line":"Multiply two numbers.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"process_numbers","kind":"Function","module_id":"","file_id":"","qualname":"process_numbers","signature":"def process_numbers(numbers: List[int])","annotations":null,"docstring_first_line":"Process a list of numbers.","purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"Calculator","line_number":519,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"calc.add","line_number":544,"call_type":"Unresolved"},{"caller_symbol":"process_numbers","callee_expr":"Calculator","line_number":519,"call_type":"Unresolved"},{"caller_symbol":"process_numbers","callee_expr":"calc.add","line_number":544,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"process_numbers","line_number":648,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"print","line_number":677,"call_type":"Unresolved"}],"file_docstring":"Example module for testing."}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.639487788Z","file_modified_at":"2026-02-15T09:12:27.638200559Z","parsed_module":{"path":"/tmp/.tmp7gcSsx/test.py","module_path":"/tmp/.tmp7gcSsx/test.py","imports":[{"module_name":"redis","alias":null,"line_number":8}],"symbols":[{"id":"process_job","kind":"Function","module_id":"","file_id":"","qualname":"process_job","signature":"def process_job(job_data)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"redis.Redis","line_number":55,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"client.lpush","line_number":73,"call_type":"Unresolved"},{"caller_symbol":"process_job","callee_expr":"redis.Redis","line_number":55,"call_type":"Unresolved"},{"caller_symbol":"process_job","callee_expr":"client.lpush","line_number":73,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.623913794Z","file_modified_at":"2026-02-15T09:12:27.622200674Z","parsed_module":{"path":"/tmp/.tmpY5jXEG/test.py","module_path":"/tmp/.tmpY5jXEG/test.py","imports":[],"symbols":[{"id":"hello","kind":"Function","module_id":"","file_id":"","qualname":"hello","signature":"def hello()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator","kind":"Class","module_id":"","file_id":"","qualname":"Calculator","signature":"class Calculator","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator.add","kind":"Method","module_id":"","file_id":"","qualname":"Calculator.add","signature":"def add(self, a, b)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.623293468Z","file_modified_at":"2026-02-15T09:12:27.622200674Z","parsed_module":{"path":"/tmp/.tmpbimwTO/test.py","module_path":"/tmp/.tmpbimwTO/test.py","imports":[],"symbols":[{"id":"hello","kind":"Function","module_id":"","file_id":"","qualname":"hello","signature":"def hello()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.638405646Z","file_modified_at":"2026-02-15T09:12:27.637200566Z","parsed_module":{"path":"/tmp/.tmpDqAWXp/test.py","module_path":"/tmp/.tmpDqAWXp/test.py","imports":[{"module_name":"sqlite3","alias":null,"line_number":8}],"symbols":[{"id":"get_user","kind":"Function","module_id":"","file_id":"","qualname":"get_user","signature":"def get_user(user_id)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"sqlite3.connect","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"conn.cursor","line_number":95,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"cursor.execute","line_number":113,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"cursor.fetchone","line_number":187,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"sqlite3.connect","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"conn.cursor","line_number":95,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"cursor.execute","line_number":113,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"cursor.fetchone","line_number":187,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.928408667Z","file_modified_at":"2026-02-15T09:12:21.927241652Z","parsed_module":{"path":"/tmp/.tmpkuoSO4/test.py","module_path":"/tmp/.tmpkuoSO4/test.py","imports":[],"symbols":[{"id":"calculate_sum","kind":"Function","module_id":"","file_id":"","qualname":"calculate_sum","signature":"def calculate_sum(a, b)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.642603187Z","file_modified_at":"2026-02-15T09:12:27.641200538Z","parsed_module":{"path":"/tmp/.tmplZ7Gfg/test.py","module_path":"/tmp/.tmplZ7Gfg/test.py","imports":[],"symbols":[{"id":"hello","kind":"Function","module_id":"","file_id":"","qualname":"hello","signature":"def hello()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator","kind":"Class","module_id":"","file_id":"","qualname":"Calculator","signature":"class Calculator","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"Calculator.add","kind":"Method","module_id":"","file_id":"","qualname":"Calculator.add","signature":"def add(self, a, b)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.642573298Z","file_modified_at":"2026-02-15T09:12:27.641200538Z","parsed_module":{"path":"/tmp/.tmpiVOCMi/test.py","module_path":"/tmp/.tmpiVOCMi/test.py","imports":[],"symbols":[{"id":"hello","kind":"Function","module_id":"","file_id":"","qualname":"hello","signature":"def hello()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}},{"id":"goodbye","kind":"Function","module_id":"","file_id":"","qualname":"goodbye","signature":"def goodbye()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.927910330Z","file_modified_at":"2026-02-15T09:12:21.926241659Z","parsed_module":{"path":"/tmp/.tmp1gFjk3/test.py","module_path":"/tmp/.tmp1gFjk3/test.py","imports":[{"module_name":"sqlite3","alias":null,"line_number":8}],"symbols":[{"id":"get_user","kind":"Function","module_id":"","file_id":"","qualname":"get_user","signature":"def get_user(user_id)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"sqlite3.connect","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"conn.cursor","line_number":95,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"cursor.execute","line_number":113,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"cursor.fetchone","line_number":187,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"sqlite3.connect","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"conn.cursor","line_number":95,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"cursor.execute","line_number":113,"call_type":"Unresolved"},{"caller_symbol":"get_user","callee_expr":"cursor.fetchone","line_number":187,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:21.927753122Z","file_modified_at":"2026-02-15T09:12:21.926241659Z","parsed_module":{"path":"/tmp/.tmpp9A45l/test.py","module_path":"/tmp/.tmpp9A45l/test.py","imports":[{"module_name":"requests","alias":null,"line_number":8}],"symbols":[{"id":"fetch_data","kind":"Function","module_id":"","file_id":"","qualname":"fetch_data","signature":"def fetch_data()","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[{"caller_symbol":"unknown","callee_expr":"requests.get","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"unknown","callee_expr":"response.json","line_number":107,"call_type":"Unresolved"},{"caller_symbol":"fetch_data","callee_expr":"requests.get","line_number":51,"call_type":"Unresolved"},{"caller_symbol":"fetch_data","callee_expr":"response.json","line_number":107,"call_type":"Unresolved"}],"file_docstring":null}}

View File

@@ -0,0 +1 @@
{"created_at":"2026-02-15T09:12:27.638896492Z","file_modified_at":"2026-02-15T09:12:27.638200559Z","parsed_module":{"path":"/tmp/.tmp7IEFw5/test.py","module_path":"/tmp/.tmp7IEFw5/test.py","imports":[],"symbols":[{"id":"calculate_sum","kind":"Function","module_id":"","file_id":"","qualname":"calculate_sum","signature":"def calculate_sum(a, b)","annotations":null,"docstring_first_line":null,"purpose":"extracted from AST","outbound_calls":[],"inbound_calls":[],"integrations_flags":{"http":false,"db":false,"queue":false,"storage":false,"ai":false},"metrics":{"fan_in":0,"fan_out":0,"is_critical":false,"cycle_participant":false}}],"calls":[],"file_docstring":null}}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
[package]
name = "archdoc-core"
name = "wtismycode-core"
version = "0.1.0"
edition = "2024"

View File

@@ -1,10 +1,10 @@
//! Caching module for ArchDoc
//! Caching module for WTIsMyCode
//!
//! This module provides caching functionality to speed up repeated analysis
//! by storing parsed ASTs and analysis results.
use crate::config::Config;
use crate::errors::ArchDocError;
use crate::errors::WTIsMyCodeError;
use crate::model::ParsedModule;
use std::path::Path;
use std::fs;
@@ -39,7 +39,7 @@ impl CacheManager {
}
/// Get cached parsed module if available and not expired
pub fn get_cached_module(&self, file_path: &Path) -> Result<Option<ParsedModule>, ArchDocError> {
pub fn get_cached_module(&self, file_path: &Path) -> Result<Option<ParsedModule>, WTIsMyCodeError> {
if !self.config.caching.enabled {
return Ok(None);
}
@@ -53,10 +53,10 @@ impl CacheManager {
// Read cache file
let content = fs::read_to_string(&cache_file)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
let cache_entry: CacheEntry = serde_json::from_str(&content)
.map_err(|e| ArchDocError::AnalysisError(format!("Failed to deserialize cache entry: {}", e)))?;
.map_err(|e| WTIsMyCodeError::AnalysisError(format!("Failed to deserialize cache entry: {}", e)))?;
// Check if cache is expired
let now = Utc::now();
@@ -73,10 +73,10 @@ impl CacheManager {
// Check if source file has been modified since caching
let metadata = fs::metadata(file_path)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
let modified_time = metadata.modified()
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
let modified_time: DateTime<Utc> = modified_time.into();
@@ -90,7 +90,7 @@ impl CacheManager {
}
/// Store parsed module in cache
pub fn store_module(&self, file_path: &Path, parsed_module: ParsedModule) -> Result<(), ArchDocError> {
pub fn store_module(&self, file_path: &Path, parsed_module: ParsedModule) -> Result<(), WTIsMyCodeError> {
if !self.config.caching.enabled {
return Ok(());
}
@@ -100,10 +100,10 @@ impl CacheManager {
// Get file modification time
let metadata = fs::metadata(file_path)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
let modified_time = metadata.modified()
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
let modified_time: DateTime<Utc> = modified_time.into();
@@ -114,10 +114,10 @@ impl CacheManager {
};
let content = serde_json::to_string(&cache_entry)
.map_err(|e| ArchDocError::AnalysisError(format!("Failed to serialize cache entry: {}", e)))?;
.map_err(|e| WTIsMyCodeError::AnalysisError(format!("Failed to serialize cache entry: {}", e)))?;
fs::write(&cache_file, content)
.map_err(ArchDocError::Io)
.map_err(WTIsMyCodeError::Io)
}
/// Generate cache key for a file path
@@ -133,7 +133,7 @@ impl CacheManager {
}
/// Parse duration string like "24h" or "7d" into seconds
fn parse_duration(&self, duration_str: &str) -> Result<u64, ArchDocError> {
fn parse_duration(&self, duration_str: &str) -> Result<u64, WTIsMyCodeError> {
if duration_str.is_empty() {
return Ok(0);
}
@@ -141,26 +141,26 @@ impl CacheManager {
let chars: Vec<char> = duration_str.chars().collect();
let (number_str, unit) = chars.split_at(chars.len() - 1);
let number: u64 = number_str.iter().collect::<String>().parse()
.map_err(|_| ArchDocError::AnalysisError(format!("Invalid duration format: {}", duration_str)))?;
.map_err(|_| WTIsMyCodeError::AnalysisError(format!("Invalid duration format: {}", duration_str)))?;
match unit[0] {
's' => Ok(number), // seconds
'm' => Ok(number * 60), // minutes
'h' => Ok(number * 3600), // hours
'd' => Ok(number * 86400), // days
_ => Err(ArchDocError::AnalysisError(format!("Unknown duration unit: {}", unit[0]))),
_ => Err(WTIsMyCodeError::AnalysisError(format!("Unknown duration unit: {}", unit[0]))),
}
}
/// Clear all cache entries
pub fn clear_cache(&self) -> Result<(), ArchDocError> {
pub fn clear_cache(&self) -> Result<(), WTIsMyCodeError> {
if Path::new(&self.cache_dir).exists() {
fs::remove_dir_all(&self.cache_dir)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
// Recreate cache directory
fs::create_dir_all(&self.cache_dir)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
}
Ok(())

View File

@@ -1,10 +1,10 @@
//! Configuration management for ArchDoc
//! Configuration management for WTIsMyCode
//!
//! This module handles loading and validating the archdoc.toml configuration file.
//! This module handles loading and validating the wtismycode.toml configuration file.
use serde::{Deserialize, Serialize};
use std::path::Path;
use crate::errors::ArchDocError;
use crate::errors::WTIsMyCodeError;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Default)]
@@ -383,7 +383,7 @@ fn default_log_level() -> String {
}
fn default_log_file() -> String {
"archdoc.log".to_string()
"wtismycode.log".to_string()
}
fn default_log_format() -> String {
@@ -415,7 +415,7 @@ fn default_caching_enabled() -> bool {
}
fn default_cache_dir() -> String {
".archdoc/cache".to_string()
".wtismycode/cache".to_string()
}
fn default_max_cache_age() -> String {
@@ -426,17 +426,17 @@ impl Config {
/// Validate the configuration for correctness.
///
/// Checks that paths exist, values are parseable, and settings are sensible.
pub fn validate(&self) -> Result<(), ArchDocError> {
pub fn validate(&self) -> Result<(), WTIsMyCodeError> {
// Check project.root exists and is a directory
let root = Path::new(&self.project.root);
if !root.exists() {
return Err(ArchDocError::ConfigError(format!(
return Err(WTIsMyCodeError::ConfigError(format!(
"project.root '{}' does not exist",
self.project.root
)));
}
if !root.is_dir() {
return Err(ArchDocError::ConfigError(format!(
return Err(WTIsMyCodeError::ConfigError(format!(
"project.root '{}' is not a directory",
self.project.root
)));
@@ -444,7 +444,7 @@ impl Config {
// Check language is python
if self.project.language != "python" {
return Err(ArchDocError::ConfigError(format!(
return Err(WTIsMyCodeError::ConfigError(format!(
"project.language '{}' is not supported. Only 'python' is currently supported",
self.project.language
)));
@@ -452,7 +452,7 @@ impl Config {
// Check scan.include is not empty
if self.scan.include.is_empty() {
return Err(ArchDocError::ConfigError(
return Err(WTIsMyCodeError::ConfigError(
"scan.include must not be empty — at least one directory must be specified".to_string(),
));
}
@@ -461,7 +461,7 @@ impl Config {
for src_root in &self.python.src_roots {
let path = root.join(src_root);
if !path.exists() {
return Err(ArchDocError::ConfigError(format!(
return Err(WTIsMyCodeError::ConfigError(format!(
"python.src_roots entry '{}' does not exist (resolved to '{}')",
src_root,
path.display()
@@ -471,7 +471,7 @@ impl Config {
// Parse max_cache_age
parse_duration(&self.caching.max_cache_age).map_err(|e| {
ArchDocError::ConfigError(format!(
WTIsMyCodeError::ConfigError(format!(
"caching.max_cache_age '{}' is not valid: {}. Use formats like '24h', '7d', '30m'",
self.caching.max_cache_age, e
))
@@ -479,7 +479,7 @@ impl Config {
// Parse max_file_size
parse_file_size(&self.scan.max_file_size).map_err(|e| {
ArchDocError::ConfigError(format!(
WTIsMyCodeError::ConfigError(format!(
"scan.max_file_size '{}' is not valid: {}. Use formats like '10MB', '1GB', '500KB'",
self.scan.max_file_size, e
))
@@ -489,21 +489,21 @@ impl Config {
}
/// Load configuration from a TOML file
pub fn load_from_file(path: &Path) -> Result<Self, ArchDocError> {
pub fn load_from_file(path: &Path) -> Result<Self, WTIsMyCodeError> {
let content = std::fs::read_to_string(path)
.map_err(|e| ArchDocError::ConfigError(format!("Failed to read config file: {}", e)))?;
.map_err(|e| WTIsMyCodeError::ConfigError(format!("Failed to read config file: {}", e)))?;
toml::from_str(&content)
.map_err(|e| ArchDocError::ConfigError(format!("Failed to parse config file: {}", e)))
.map_err(|e| WTIsMyCodeError::ConfigError(format!("Failed to parse config file: {}", e)))
}
/// Save configuration to a TOML file
pub fn save_to_file(&self, path: &Path) -> Result<(), ArchDocError> {
pub fn save_to_file(&self, path: &Path) -> Result<(), WTIsMyCodeError> {
let content = toml::to_string_pretty(self)
.map_err(|e| ArchDocError::ConfigError(format!("Failed to serialize config: {}", e)))?;
.map_err(|e| WTIsMyCodeError::ConfigError(format!("Failed to serialize config: {}", e)))?;
std::fs::write(path, content)
.map_err(|e| ArchDocError::ConfigError(format!("Failed to write config file: {}", e)))
.map_err(|e| WTIsMyCodeError::ConfigError(format!("Failed to write config file: {}", e)))
}
}

View File

@@ -1,7 +1,7 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ArchDocError {
pub enum WTIsMyCodeError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),

View File

@@ -1,4 +1,4 @@
//! ArchDoc Core Library
//! WTIsMyCode Core Library
//!
//! This crate provides the core functionality for analyzing Python projects
//! and generating architecture documentation.
@@ -15,7 +15,7 @@ pub mod cache;
pub mod cycle_detector;
// Re-export commonly used types
pub use errors::ArchDocError;
pub use errors::WTIsMyCodeError;
pub use config::Config;
pub use model::ProjectModel;

View File

@@ -1,4 +1,4 @@
//! Intermediate Representation (IR) for ArchDoc
//! Intermediate Representation (IR) for WTIsMyCode
//!
//! This module defines the data structures that represent the analyzed Python project
//! and are used for generating documentation.

View File

@@ -1,11 +1,11 @@
//! Python AST analyzer for ArchDoc
//! Python AST analyzer for WTIsMyCode
//!
//! This module handles parsing Python files using AST and extracting
//! imports, definitions, and calls.
use crate::model::{ParsedModule, ProjectModel, Import, Call, CallType, Symbol, Module, FileDoc};
use crate::config::Config;
use crate::errors::ArchDocError;
use crate::errors::WTIsMyCodeError;
use crate::cache::CacheManager;
use std::path::Path;
use std::fs;
@@ -23,17 +23,17 @@ impl PythonAnalyzer {
Self { config, cache_manager }
}
pub fn parse_module(&self, file_path: &Path) -> Result<ParsedModule, ArchDocError> {
pub fn parse_module(&self, file_path: &Path) -> Result<ParsedModule, WTIsMyCodeError> {
// Try to get from cache first
if let Some(cached_module) = self.cache_manager.get_cached_module(file_path)? {
return Ok(cached_module);
}
let code = fs::read_to_string(file_path)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
let ast = ast::Suite::parse(&code, file_path.to_str().unwrap_or("<unknown>"))
.map_err(|e| ArchDocError::ParseError {
.map_err(|e| WTIsMyCodeError::ParseError {
file: file_path.to_string_lossy().to_string(),
line: 0,
message: format!("Failed to parse: {}", e),
@@ -589,7 +589,7 @@ impl PythonAnalyzer {
normalized.to_string()
}
pub fn resolve_symbols(&self, modules: &[ParsedModule]) -> Result<ProjectModel, ArchDocError> {
pub fn resolve_symbols(&self, modules: &[ParsedModule]) -> Result<ProjectModel, WTIsMyCodeError> {
let mut project_model = ProjectModel::new();
// Build import alias map for call resolution
@@ -658,9 +658,9 @@ impl PythonAnalyzer {
let doc_summary = if is_init {
parsed_module.file_docstring.clone()
} else {
// For non-init files, check if there's an __init__.py docstring for this module's parent
init_docstrings.get(&module_id).cloned()
.or_else(|| parsed_module.file_docstring.clone())
// For non-init files, use file docstring first, then check __init__.py
parsed_module.file_docstring.clone()
.or_else(|| init_docstrings.get(&module_id).cloned())
};
let module = Module {
@@ -729,7 +729,7 @@ impl PythonAnalyzer {
}
}
fn build_dependency_graphs(&self, project_model: &mut ProjectModel, parsed_modules: &[ParsedModule]) -> Result<(), ArchDocError> {
fn build_dependency_graphs(&self, project_model: &mut ProjectModel, parsed_modules: &[ParsedModule]) -> Result<(), WTIsMyCodeError> {
// Collect known internal module IDs
let known_modules: std::collections::HashSet<String> = project_model.modules.keys().cloned().collect();
@@ -799,7 +799,26 @@ impl PythonAnalyzer {
Ok(())
}
fn compute_metrics(&self, project_model: &mut ProjectModel) -> Result<(), ArchDocError> {
/// Check if a class symbol is a simple data container (dataclass-like).
/// A class is considered a dataclass if it has ≤2 methods (typically __init__ and __repr__/__str__).
fn is_dataclass_like(symbol_id: &str, project_model: &ProjectModel) -> bool {
let symbol = match project_model.symbols.get(symbol_id) {
Some(s) => s,
None => return false,
};
if symbol.kind != crate::model::SymbolKind::Class {
return false;
}
// Count methods belonging to this class
let class_name = &symbol.qualname;
let method_prefix = format!("{}::{}.", symbol.module_id, class_name);
let method_count = project_model.symbols.values()
.filter(|s| s.kind == crate::model::SymbolKind::Method && s.id.starts_with(&method_prefix))
.count();
method_count <= 2
}
fn compute_metrics(&self, project_model: &mut ProjectModel) -> Result<(), WTIsMyCodeError> {
// Collect fan-in/fan-out first to avoid borrow issues
let mut metrics: std::collections::HashMap<String, (usize, usize)> = std::collections::HashMap::new();
@@ -815,12 +834,20 @@ impl PythonAnalyzer {
metrics.insert(symbol_id.clone(), (fan_in, fan_out));
}
// Pre-compute which symbols are dataclass-like (need immutable borrow)
let dataclass_ids: std::collections::HashSet<String> = metrics.keys()
.filter(|id| Self::is_dataclass_like(id, project_model))
.cloned()
.collect();
for (symbol_id, (fan_in, fan_out)) in &metrics {
if let Some(symbol) = project_model.symbols.get_mut(symbol_id) {
symbol.metrics.fan_in = *fan_in;
symbol.metrics.fan_out = *fan_out;
symbol.metrics.is_critical = *fan_in > self.config.thresholds.critical_fan_in
// Don't mark dataclass-like classes as critical — they're just data containers
let exceeds_threshold = *fan_in > self.config.thresholds.critical_fan_in
|| *fan_out > self.config.thresholds.critical_fan_out;
symbol.metrics.is_critical = exceeds_threshold && !dataclass_ids.contains(symbol_id);
}
}

View File

@@ -1,4 +1,4 @@
//! Markdown renderer for ArchDoc
//! Markdown renderer for WTIsMyCode
//!
//! This module handles generating Markdown documentation from the project model
//! using templates.
@@ -65,7 +65,7 @@ impl Renderer {
## Document metadata
- **Created:** {{{created_date}}}
- **Updated:** {{{updated_date}}}
- **Generated by:** archdoc (cli) v0.1
- **Generated by:** wtismycode (cli) v0.1
---

View File

@@ -1,10 +1,10 @@
//! File scanner for ArchDoc
//! File scanner for WTIsMyCode
//!
//! This module handles scanning the file system for Python files according to
//! the configuration settings.
use crate::config::Config;
use crate::errors::ArchDocError;
use crate::errors::WTIsMyCodeError;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
@@ -17,17 +17,17 @@ impl FileScanner {
Self { config }
}
pub fn scan_python_files(&self, root: &Path) -> Result<Vec<PathBuf>, ArchDocError> {
pub fn scan_python_files(&self, root: &Path) -> Result<Vec<PathBuf>, WTIsMyCodeError> {
// Check if root directory exists
if !root.exists() {
return Err(ArchDocError::Io(std::io::Error::new(
return Err(WTIsMyCodeError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Root directory does not exist: {}", root.display())
)));
}
if !root.is_dir() {
return Err(ArchDocError::Io(std::io::Error::new(
return Err(WTIsMyCodeError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Root path is not a directory: {}", root.display())
)));
@@ -41,7 +41,7 @@ impl FileScanner {
.into_iter() {
let entry = entry.map_err(|e| {
ArchDocError::Io(std::io::Error::other(
WTIsMyCodeError::Io(std::io::Error::other(
format!("Failed to read directory entry: {}", e)
))
})?;

View File

@@ -1,9 +1,9 @@
//! Diff-aware file writer for ArchDoc
//! Diff-aware file writer for WTIsMyCode
//!
//! This module handles writing generated documentation to files while preserving
//! manual content and only updating generated sections.
use crate::errors::ArchDocError;
use crate::errors::WTIsMyCodeError;
use std::path::Path;
use std::fs;
use chrono::Utc;
@@ -42,17 +42,17 @@ impl DiffAwareWriter {
file_path: &Path,
generated_content: &str,
section_name: &str,
) -> Result<(), ArchDocError> {
) -> Result<(), WTIsMyCodeError> {
// Read existing file
let existing_content = if file_path.exists() {
fs::read_to_string(file_path)
.map_err(ArchDocError::Io)?
.map_err(WTIsMyCodeError::Io)?
} else {
// Create new file with template
let template_content = self.create_template_file(file_path, section_name)?;
// Write template to file
fs::write(file_path, &template_content)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
template_content
};
@@ -74,7 +74,7 @@ impl DiffAwareWriter {
if content_changed {
let updated_content = self.update_timestamp(new_content)?;
fs::write(file_path, updated_content)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
}
// If not changed, skip writing entirely
}
@@ -87,16 +87,16 @@ impl DiffAwareWriter {
file_path: &Path,
symbol_id: &str,
generated_content: &str,
) -> Result<(), ArchDocError> {
) -> Result<(), WTIsMyCodeError> {
// Read existing file
let existing_content = if file_path.exists() {
fs::read_to_string(file_path)
.map_err(ArchDocError::Io)?
.map_err(WTIsMyCodeError::Io)?
} else {
// If file doesn't exist, create it with a basic template
let template_content = self.create_template_file(file_path, "symbol")?;
fs::write(file_path, &template_content)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
template_content
};
@@ -118,7 +118,7 @@ impl DiffAwareWriter {
if content_changed {
let updated_content = self.update_timestamp(new_content)?;
fs::write(file_path, updated_content)
.map_err(ArchDocError::Io)?;
.map_err(WTIsMyCodeError::Io)?;
}
// If not changed, skip writing entirely
} else {
@@ -128,7 +128,7 @@ impl DiffAwareWriter {
Ok(())
}
fn find_section_markers(&self, content: &str, section_name: &str) -> Result<Vec<SectionMarker>, ArchDocError> {
fn find_section_markers(&self, content: &str, section_name: &str) -> Result<Vec<SectionMarker>, WTIsMyCodeError> {
let begin_marker = format!("<!-- ARCHDOC:BEGIN section={} -->", section_name);
let end_marker = format!("<!-- ARCHDOC:END section={} -->", section_name);
@@ -155,7 +155,7 @@ impl DiffAwareWriter {
Ok(markers)
}
fn find_symbol_markers(&self, content: &str, symbol_id: &str) -> Result<Vec<SymbolMarker>, ArchDocError> {
fn find_symbol_markers(&self, content: &str, symbol_id: &str) -> Result<Vec<SymbolMarker>, WTIsMyCodeError> {
let begin_marker = format!("<!-- ARCHDOC:BEGIN symbol id={} -->", symbol_id);
let end_marker = format!("<!-- ARCHDOC:END symbol id={} -->", symbol_id);
@@ -187,7 +187,7 @@ impl DiffAwareWriter {
content: &str,
marker: &SectionMarker,
new_content: &str,
) -> Result<String, ArchDocError> {
) -> Result<String, WTIsMyCodeError> {
let before = &content[..marker.start_pos];
let after = &content[marker.end_pos..];
@@ -205,7 +205,7 @@ impl DiffAwareWriter {
content: &str,
marker: &SymbolMarker,
new_content: &str,
) -> Result<String, ArchDocError> {
) -> Result<String, WTIsMyCodeError> {
let before = &content[..marker.start_pos];
let after = &content[marker.end_pos..];
@@ -218,7 +218,7 @@ impl DiffAwareWriter {
))
}
fn update_timestamp(&self, content: String) -> Result<String, ArchDocError> {
fn update_timestamp(&self, content: String) -> Result<String, WTIsMyCodeError> {
// Update the "Updated" field in the document metadata section
// Find the metadata section and update the timestamp
let today = Utc::now().format("%Y-%m-%d").to_string();
@@ -238,7 +238,7 @@ impl DiffAwareWriter {
Ok(updated_lines.join("\n"))
}
fn create_template_file(&self, _file_path: &Path, template_type: &str) -> Result<String, ArchDocError> {
fn create_template_file(&self, _file_path: &Path, template_type: &str) -> Result<String, WTIsMyCodeError> {
// Create file with appropriate template based on type
match template_type {
"architecture" => {
@@ -261,7 +261,7 @@ impl DiffAwareWriter {
## Document metadata
- **Created:** <AUTO_ON_INIT: YYYY-MM-DD>
- **Updated:** <AUTO_ON_CHANGE: YYYY-MM-DD>
- **Generated by:** archdoc (cli) v0.1
- **Generated by:** wtismycode (cli) v0.1
---

View File

@@ -1,11 +1,11 @@
//! Caching tests for ArchDoc
//! Caching tests for WTIsMyCode
//!
//! These tests verify that the caching functionality works correctly.
use std::path::Path;
use std::fs;
use tempfile::TempDir;
use archdoc_core::{Config, python_analyzer::PythonAnalyzer};
use wtismycode_core::{Config, python_analyzer::PythonAnalyzer};
#[test]
fn test_cache_store_and_retrieve() {

View File

@@ -1,11 +1,11 @@
//! Enhanced analysis tests for ArchDoc
//! Enhanced analysis tests for WTIsMyCode
//!
//! These tests verify that the enhanced analysis functionality works correctly
//! with complex code that includes integrations, calls, and docstrings.
use std::fs;
use std::path::Path;
use archdoc_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
use wtismycode_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
#[test]
fn test_enhanced_analysis_with_integrations() {
@@ -15,8 +15,8 @@ fn test_enhanced_analysis_with_integrations() {
// Try different paths for the config file
let possible_paths = [
"tests/golden/test_project/archdoc.toml",
"../tests/golden/test_project/archdoc.toml",
"tests/golden/test_project/wtismycode.toml",
"../tests/golden/test_project/wtismycode.toml",
];
let config_path = possible_paths.iter().find(|&path| {
@@ -100,17 +100,17 @@ fn test_enhanced_analysis_with_integrations() {
// Check that we found the UserService class with DB integration
let user_service_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::UserService"));
assert!(user_service_symbol.is_some());
assert_eq!(user_service_symbol.unwrap().kind, archdoc_core::model::SymbolKind::Class);
assert_eq!(user_service_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Class);
// Check that we found the NotificationService class with queue integration
let notification_service_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::NotificationService"));
assert!(notification_service_symbol.is_some());
assert_eq!(notification_service_symbol.unwrap().kind, archdoc_core::model::SymbolKind::Class);
assert_eq!(notification_service_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Class);
// Check that we found the fetch_external_user_data function with HTTP integration
let fetch_external_user_data_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::fetch_external_user_data"));
assert!(fetch_external_user_data_symbol.is_some());
assert_eq!(fetch_external_user_data_symbol.unwrap().kind, archdoc_core::model::SymbolKind::Function);
assert_eq!(fetch_external_user_data_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Function);
// Check file imports
let mut found_advanced_file = false;

View File

@@ -1,12 +1,12 @@
//! Error handling tests for ArchDoc
//! Error handling tests for WTIsMyCode
//!
//! These tests verify that ArchDoc properly handles various error conditions
//! These tests verify that WTIsMyCode properly handles various error conditions
//! and edge cases.
use std::path::Path;
use std::fs;
use tempfile::TempDir;
use archdoc_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
use wtismycode_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
#[test]
fn test_scanner_nonexistent_directory() {
@@ -19,7 +19,7 @@ fn test_scanner_nonexistent_directory() {
// Check that we get an IO error
match result.unwrap_err() {
archdoc_core::errors::ArchDocError::Io(_) => {},
wtismycode_core::errors::WTIsMyCodeError::Io(_) => {},
_ => panic!("Expected IO error"),
}
}
@@ -40,7 +40,7 @@ fn test_scanner_file_instead_of_directory() {
// Check that we get an IO error
match result.unwrap_err() {
archdoc_core::errors::ArchDocError::Io(_) => {},
wtismycode_core::errors::WTIsMyCodeError::Io(_) => {},
_ => panic!("Expected IO error"),
}
}
@@ -56,7 +56,7 @@ fn test_analyzer_nonexistent_file() {
// Check that we get an IO error
match result.unwrap_err() {
archdoc_core::errors::ArchDocError::Io(_) => {},
wtismycode_core::errors::WTIsMyCodeError::Io(_) => {},
_ => panic!("Expected IO error"),
}
}
@@ -77,7 +77,7 @@ fn test_analyzer_invalid_python_syntax() {
// Check that we get a parse error
match result.unwrap_err() {
archdoc_core::errors::ArchDocError::ParseError { .. } => {},
wtismycode_core::errors::WTIsMyCodeError::ParseError { .. } => {},
_ => panic!("Expected parse error"),
}
}

View File

@@ -1,12 +1,12 @@
//! Full pipeline integration tests for ArchDoc
//! Full pipeline integration tests for WTIsMyCode
//!
//! Tests the complete scan → analyze → render pipeline using test-project/.
use archdoc_core::config::Config;
use archdoc_core::cycle_detector;
use archdoc_core::model::{Module, ProjectModel};
use archdoc_core::renderer::Renderer;
use archdoc_core::scanner::FileScanner;
use wtismycode_core::config::Config;
use wtismycode_core::cycle_detector;
use wtismycode_core::model::{Module, ProjectModel};
use wtismycode_core::renderer::Renderer;
use wtismycode_core::scanner::FileScanner;
use std::path::Path;
#[test]
@@ -14,7 +14,7 @@ fn test_config_load_and_validate() {
let config_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("test-project/archdoc.toml");
.join("test-project/wtismycode.toml");
let config = Config::load_from_file(&config_path).expect("Failed to load config");
assert_eq!(config.project.language, "python");
@@ -26,7 +26,7 @@ fn test_config_validate_on_test_project() {
let config_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("test-project/archdoc.toml");
.join("test-project/wtismycode.toml");
let mut config = Config::load_from_file(&config_path).expect("Failed to load config");
// Set root to actual test-project path so validation passes
@@ -48,7 +48,7 @@ fn test_scan_test_project() {
.unwrap()
.join("test-project");
let config_path = test_project.join("archdoc.toml");
let config_path = test_project.join("wtismycode.toml");
let mut config = Config::load_from_file(&config_path).expect("Failed to load config");
config.project.root = test_project.to_string_lossy().to_string();
@@ -148,7 +148,7 @@ fn test_renderer_produces_output() {
#[test]
fn test_parse_duration_values() {
use archdoc_core::config::{parse_duration, parse_file_size};
use wtismycode_core::config::{parse_duration, parse_file_size};
assert_eq!(parse_duration("24h").unwrap(), 86400);
assert_eq!(parse_duration("7d").unwrap(), 604800);

View File

@@ -1,4 +1,4 @@
//! Golden tests for ArchDoc
//! Golden tests for WTIsMyCode
//!
//! These tests generate documentation for test projects and compare the output
//! with expected "golden" files to ensure consistency.
@@ -7,7 +7,7 @@ mod test_utils;
use std::fs;
use std::path::Path;
use archdoc_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
use wtismycode_core::{Config, scanner::FileScanner, python_analyzer::PythonAnalyzer};
#[test]
fn test_simple_project_generation() {
@@ -17,8 +17,8 @@ fn test_simple_project_generation() {
// Try different paths for the config file
let possible_paths = [
"tests/golden/test_project/archdoc.toml",
"../tests/golden/test_project/archdoc.toml",
"tests/golden/test_project/wtismycode.toml",
"../tests/golden/test_project/wtismycode.toml",
];
let config_path = possible_paths.iter().find(|&path| {
@@ -92,12 +92,12 @@ fn test_simple_project_generation() {
// Check that we found the Calculator class
let calculator_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::Calculator"));
assert!(calculator_symbol.is_some());
assert_eq!(calculator_symbol.unwrap().kind, archdoc_core::model::SymbolKind::Class);
assert_eq!(calculator_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Class);
// Check that we found the process_numbers function
let process_numbers_symbol = project_model.symbols.values().find(|s| s.id.ends_with("::process_numbers"));
assert!(process_numbers_symbol.is_some());
assert_eq!(process_numbers_symbol.unwrap().kind, archdoc_core::model::SymbolKind::Function);
assert_eq!(process_numbers_symbol.unwrap().kind, wtismycode_core::model::SymbolKind::Function);
// Check file imports
assert!(!project_model.files.is_empty());

View File

@@ -17,7 +17,7 @@
## Document metadata
- **Created:** 2026-01-25
- **Updated:** 2026-01-25
- **Generated by:** archdoc (cli) v0.1
- **Generated by:** wtismycode (cli) v0.1
---

View File

@@ -53,10 +53,10 @@ description_max_length = 200
[logging]
level = "info"
file = "archdoc.log"
file = "wtismycode.log"
format = "compact"
[caching]
enabled = true
cache_dir = ".archdoc/cache"
cache_dir = ".wtismycode/cache"
max_cache_age = "24h"

View File

@@ -1,4 +1,4 @@
//! Integration detection tests for ArchDoc
//! Integration detection tests for WTIsMyCode
//!
//! These tests verify that the integration detection functionality works correctly.
//! Integration detection now happens at module level during resolve_symbols,
@@ -6,7 +6,7 @@
use std::fs;
use tempfile::TempDir;
use archdoc_core::{Config, python_analyzer::PythonAnalyzer};
use wtismycode_core::{Config, python_analyzer::PythonAnalyzer};
#[test]
fn test_http_integration_detection() {

View File

@@ -1,4 +1,4 @@
//! Integration tests for ArchDoc
//! Integration tests for WTIsMyCode
// Include golden tests
mod golden;

View File

@@ -1,6 +1,6 @@
//! Tests for analyzing the test project
use archdoc_core::{
use wtismycode_core::{
config::Config,
python_analyzer::PythonAnalyzer,
};
@@ -9,7 +9,7 @@ use std::path::Path;
#[test]
fn test_project_analysis() {
// Load config from test project
let config = Config::load_from_file(Path::new("../test-project/archdoc.toml")).unwrap();
let config = Config::load_from_file(Path::new("../test-project/wtismycode.toml")).unwrap();
// Initialize analyzer
let analyzer = PythonAnalyzer::new(config);
@@ -55,7 +55,7 @@ fn test_project_analysis() {
#[test]
fn test_full_project_resolution() {
// Load config from test project
let config = Config::load_from_file(Path::new("../test-project/archdoc.toml")).unwrap();
let config = Config::load_from_file(Path::new("../test-project/wtismycode.toml")).unwrap();
// Initialize analyzer
let analyzer = PythonAnalyzer::new(config);

View File

@@ -1,6 +1,6 @@
//! Tests for the renderer functionality
use archdoc_core::{
use wtismycode_core::{
model::{ProjectModel, Symbol, SymbolKind, IntegrationFlags, SymbolMetrics},
renderer::Renderer,
};