rename: archdoc → wtismycode (WTIsMyCode)
This commit is contained in:
9
wtismycode-cli/.gitignore
vendored
Normal file
9
wtismycode-cli/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Compiled files
|
||||
target/
|
||||
|
||||
# IDE files
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
# Backup files
|
||||
*.rs.bk
|
||||
0
wtismycode-cli/ARCHITECTURE.md
Normal file
0
wtismycode-cli/ARCHITECTURE.md
Normal file
1780
wtismycode-cli/Cargo.lock
generated
Normal file
1780
wtismycode-cli/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
wtismycode-cli/Cargo.toml
Normal file
22
wtismycode-cli/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "wtismycode-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "wtismycode"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
wtismycode-core = { path = "../wtismycode-core" }
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml = "0.8"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
colored = "2.1"
|
||||
indicatif = "0.17"
|
||||
@@ -0,0 +1,28 @@
|
||||
# File: ../test-project/src/__init__.py
|
||||
|
||||
- **Module:** ../test-project/src/__init__.py
|
||||
- **Defined symbols:** 0
|
||||
- **Imports:** 0
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## File intent (manual)
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
|
||||
---
|
||||
|
||||
## Imports & file-level dependencies
|
||||
<!-- ARCHDOC:BEGIN section=file_imports -->
|
||||
> Generated. Do not edit inside this block.
|
||||
<!-- ARCHDOC:END section=file_imports -->
|
||||
|
||||
---
|
||||
|
||||
## Symbols index
|
||||
<!-- ARCHDOC:BEGIN section=symbols_index -->
|
||||
> Generated. Do not edit inside this block.
|
||||
<!-- ARCHDOC:END section=symbols_index -->
|
||||
|
||||
---
|
||||
|
||||
## Symbol details
|
||||
@@ -0,0 +1,276 @@
|
||||
# File: ../test-project/src/core.py
|
||||
|
||||
- **Module:** ../test-project/src/core.py
|
||||
- **Defined symbols:** 6
|
||||
- **Imports:** 2
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## File intent (manual)
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
|
||||
---
|
||||
|
||||
## Imports & file-level dependencies
|
||||
<!-- ARCHDOC:BEGIN section=file_imports -->
|
||||
> Generated. Do not edit inside this block.
|
||||
- sqlite3
|
||||
- requests
|
||||
<!-- ARCHDOC:END section=file_imports -->
|
||||
|
||||
---
|
||||
|
||||
## Symbols index
|
||||
<!-- ARCHDOC:BEGIN section=symbols_index -->
|
||||
> Generated. Do not edit inside this block.
|
||||
- [DatabaseManager](.._test-project_src_core.py#DatabaseManager)
|
||||
- [__init__](.._test-project_src_core.py#__init__)
|
||||
- [connect](.._test-project_src_core.py#connect)
|
||||
- [execute_query](.._test-project_src_core.py#execute_query)
|
||||
- [fetch_external_data](.._test-project_src_core.py#fetch_external_data)
|
||||
- [process_user_data](.._test-project_src_core.py#process_user_data)
|
||||
<!-- ARCHDOC:END section=symbols_index -->
|
||||
|
||||
---
|
||||
|
||||
## Symbol details
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=DatabaseManager --><a id="DatabaseManager"></a>
|
||||
|
||||
### `DatabaseManager`
|
||||
- **Kind:** Class
|
||||
- **Signature:** `class DatabaseManager`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: yes
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=DatabaseManager -->
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=__init__ --><a id="__init__"></a>
|
||||
|
||||
### `__init__`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def __init__(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: no
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=__init__ -->
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=connect --><a id="connect"></a>
|
||||
|
||||
### `connect`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def connect(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: yes
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=connect -->
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=execute_query --><a id="execute_query"></a>
|
||||
|
||||
### `execute_query`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def execute_query(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: no
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=execute_query -->
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=fetch_external_data --><a id="fetch_external_data"></a>
|
||||
|
||||
### `fetch_external_data`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def fetch_external_data(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: yes
|
||||
- DB: no
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=fetch_external_data -->
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=process_user_data --><a id="process_user_data"></a>
|
||||
|
||||
### `process_user_data`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def process_user_data(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: no
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 1
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=process_user_data -->
|
||||
@@ -0,0 +1,194 @@
|
||||
# File: ../test-project/src/utils.py
|
||||
|
||||
- **Module:** ../test-project/src/utils.py
|
||||
- **Defined symbols:** 4
|
||||
- **Imports:** 2
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## File intent (manual)
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
|
||||
---
|
||||
|
||||
## Imports & file-level dependencies
|
||||
<!-- ARCHDOC:BEGIN section=file_imports -->
|
||||
> Generated. Do not edit inside this block.
|
||||
- json
|
||||
- os
|
||||
<!-- ARCHDOC:END section=file_imports -->
|
||||
|
||||
---
|
||||
|
||||
## Symbols index
|
||||
<!-- ARCHDOC:BEGIN section=symbols_index -->
|
||||
> Generated. Do not edit inside this block.
|
||||
- [load_config](.._test-project_src_utils.py#load_config)
|
||||
- [save_config](.._test-project_src_utils.py#save_config)
|
||||
- [get_file_size](.._test-project_src_utils.py#get_file_size)
|
||||
- [format_bytes](.._test-project_src_utils.py#format_bytes)
|
||||
<!-- ARCHDOC:END section=symbols_index -->
|
||||
|
||||
---
|
||||
|
||||
## Symbol details
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=load_config --><a id="load_config"></a>
|
||||
|
||||
### `load_config`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def load_config(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: no
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=load_config -->
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=save_config --><a id="save_config"></a>
|
||||
|
||||
### `save_config`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def save_config(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: no
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=save_config -->
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=get_file_size --><a id="get_file_size"></a>
|
||||
|
||||
### `get_file_size`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def get_file_size(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: no
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=get_file_size -->
|
||||
|
||||
<!-- ARCHDOC:BEGIN symbol id=format_bytes --><a id="format_bytes"></a>
|
||||
|
||||
### `format_bytes`
|
||||
- **Kind:** Function
|
||||
- **Signature:** `def format_bytes(...)`
|
||||
- **Docstring:** `No documentation available`
|
||||
|
||||
#### What it does
|
||||
<!-- ARCHDOC:BEGIN section=purpose -->
|
||||
extracted from AST
|
||||
<!-- ARCHDOC:END section=purpose -->
|
||||
|
||||
#### Relations
|
||||
<!-- ARCHDOC:BEGIN section=relations -->
|
||||
**Outbound calls (best-effort):**
|
||||
|
||||
**Inbound (used by) (best-effort):**
|
||||
<!-- ARCHDOC:END section=relations -->
|
||||
|
||||
#### Integrations (heuristic)
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
- HTTP: no
|
||||
- DB: no
|
||||
- Queue/Tasks: no
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
#### Risk / impact
|
||||
<!-- ARCHDOC:BEGIN section=impact -->
|
||||
- fan-in: 0
|
||||
- fan-out: 0
|
||||
- cycle participant: no
|
||||
- critical: no
|
||||
<!-- ARCHDOC:END section=impact -->
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
#### Manual notes
|
||||
<FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
<!-- ARCHDOC:END symbol id=format_bytes -->
|
||||
18
wtismycode-cli/docs/architecture/layout.md
Normal file
18
wtismycode-cli/docs/architecture/layout.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Repository layout
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## Manual overrides
|
||||
- `src/app/` — <FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
|
||||
---
|
||||
|
||||
## Detected structure
|
||||
<!-- ARCHDOC:BEGIN section=layout_detected -->
|
||||
> Generated. Do not edit inside this block.
|
||||
| Path | Purpose | Link |
|
||||
|------|---------|------|
|
||||
| ../test-project/src/utils.py | Source file | [details](files/.._test-project_src_utils.py.md) |
|
||||
| ../test-project/src/__init__.py | Source file | [details](files/.._test-project_src___init__.py.md) |
|
||||
| ../test-project/src/core.py | Source file | [details](files/.._test-project_src_core.py.md) |
|
||||
<!-- ARCHDOC:END section=layout_detected -->
|
||||
@@ -0,0 +1,27 @@
|
||||
# Module: ../test-project/src/__init__.py
|
||||
|
||||
No summary available
|
||||
|
||||
## Symbols
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Imports
|
||||
|
||||
### Outbound Modules
|
||||
|
||||
### Inbound Modules
|
||||
|
||||
## Integrations
|
||||
|
||||
|
||||
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```python
|
||||
// Example usage of module functions
|
||||
// TODO: Add real usage examples based on module analysis
|
||||
```
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Module: ../test-project/src/core.py
|
||||
|
||||
No summary available
|
||||
|
||||
## Symbols
|
||||
|
||||
### DatabaseManager
|
||||
|
||||
class DatabaseManager
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Class
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
### __init__
|
||||
|
||||
def __init__(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
### connect
|
||||
|
||||
def connect(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
### execute_query
|
||||
|
||||
def execute_query(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
### fetch_external_data
|
||||
|
||||
def fetch_external_data(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
### process_user_data
|
||||
|
||||
def process_user_data(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 1
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Imports
|
||||
- sqlite3
|
||||
- requests
|
||||
|
||||
### Outbound Modules
|
||||
|
||||
### Inbound Modules
|
||||
|
||||
## Integrations
|
||||
|
||||
### Database Integrations
|
||||
- DatabaseManager
|
||||
- connect
|
||||
|
||||
### HTTP/API Integrations
|
||||
- fetch_external_data
|
||||
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```python
|
||||
// Example usage of module functions
|
||||
// TODO: Add real usage examples based on module analysis
|
||||
```
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# Module: ../test-project/src/utils.py
|
||||
|
||||
No summary available
|
||||
|
||||
## Symbols
|
||||
|
||||
### load_config
|
||||
|
||||
def load_config(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
### save_config
|
||||
|
||||
def save_config(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
### get_file_size
|
||||
|
||||
def get_file_size(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
### format_bytes
|
||||
|
||||
def format_bytes(...)
|
||||
|
||||
No documentation available
|
||||
|
||||
**Type:** Function
|
||||
|
||||
**Metrics:**
|
||||
- Fan-in: 0
|
||||
- Fan-out: 0
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Imports
|
||||
- json
|
||||
- os
|
||||
|
||||
### Outbound Modules
|
||||
|
||||
### Inbound Modules
|
||||
|
||||
## Integrations
|
||||
|
||||
|
||||
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```python
|
||||
// Example usage of module functions
|
||||
// TODO: Add real usage examples based on module analysis
|
||||
```
|
||||
|
||||
28
wtismycode-cli/src/commands/check.rs
Normal file
28
wtismycode-cli/src/commands/check.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use anyhow::Result;
|
||||
use wtismycode_core::Config;
|
||||
use colored::Colorize;
|
||||
|
||||
use super::generate::analyze_project;
|
||||
|
||||
pub fn check_docs_consistency(root: &str, config: &Config) -> Result<()> {
|
||||
println!("{}", "Checking documentation consistency...".cyan());
|
||||
|
||||
let model = analyze_project(root, config)?;
|
||||
|
||||
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);
|
||||
if !architecture_md_path.exists() {
|
||||
println!("{} {} does not exist", "✗".red().bold(), architecture_md_path.display());
|
||||
return Err(anyhow::anyhow!("Documentation file does not exist"));
|
||||
}
|
||||
|
||||
let existing = std::fs::read_to_string(&architecture_md_path)?;
|
||||
|
||||
println!("{} Documentation is parseable and consistent", "✓".green().bold());
|
||||
println!(" Generated content: {} chars", _generated.len());
|
||||
println!(" Existing content: {} chars", existing.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
237
wtismycode-cli/src/commands/generate.rs
Normal file
237
wtismycode-cli/src/commands/generate.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
use anyhow::Result;
|
||||
use wtismycode_core::{Config, ProjectModel, scanner::FileScanner, python_analyzer::PythonAnalyzer};
|
||||
use colored::Colorize;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::output::sanitize_filename;
|
||||
|
||||
pub fn load_config(config_path: &str) -> Result<Config> {
|
||||
Config::load_from_file(Path::new(config_path))
|
||||
.map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))
|
||||
}
|
||||
|
||||
pub fn analyze_project(root: &str, config: &Config) -> Result<ProjectModel> {
|
||||
println!("{}", "Scanning project...".cyan());
|
||||
|
||||
let scanner = FileScanner::new(config.clone());
|
||||
let python_files = scanner.scan_python_files(std::path::Path::new(root))?;
|
||||
|
||||
println!(" Found {} Python files", python_files.len().to_string().yellow());
|
||||
|
||||
let analyzer = PythonAnalyzer::new(config.clone());
|
||||
|
||||
let pb = ProgressBar::new(python_files.len() as u64);
|
||||
pb.set_style(ProgressStyle::default_bar()
|
||||
.template(" {spinner:.green} [{bar:30.cyan/dim}] {pos}/{len} {msg}")
|
||||
.unwrap_or_else(|_| ProgressStyle::default_bar())
|
||||
.progress_chars("█▓░"));
|
||||
|
||||
let mut parsed_modules = Vec::new();
|
||||
let mut parse_errors = 0;
|
||||
for file_path in &python_files {
|
||||
pb.set_message(file_path.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_default());
|
||||
match analyzer.parse_module(file_path) {
|
||||
Ok(module) => parsed_modules.push(module),
|
||||
Err(e) => {
|
||||
parse_errors += 1;
|
||||
pb.println(format!(" {} Failed to parse {}: {}", "⚠".yellow(), file_path.display(), e));
|
||||
}
|
||||
}
|
||||
pb.inc(1);
|
||||
}
|
||||
pb.finish_and_clear();
|
||||
|
||||
if parse_errors > 0 {
|
||||
println!(" {} {} file(s) had parse errors", "⚠".yellow(), parse_errors);
|
||||
}
|
||||
|
||||
println!("{}", "Resolving symbols...".cyan());
|
||||
let model = analyzer.resolve_symbols(&parsed_modules)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to resolve symbols: {}", e))?;
|
||||
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
pub fn dry_run_docs(model: &ProjectModel, out: &str, config: &Config) -> Result<()> {
|
||||
println!("{}", "Dry run — no files will be written.".cyan().bold());
|
||||
println!();
|
||||
|
||||
let out_path = std::path::Path::new(out);
|
||||
let arch_path = std::path::Path::new(".").join("ARCHITECTURE.md");
|
||||
|
||||
// ARCHITECTURE.md
|
||||
let exists = arch_path.exists();
|
||||
println!(" {} {}", if exists { "UPDATE" } else { "CREATE" }, arch_path.display());
|
||||
|
||||
// layout.md
|
||||
let layout_path = out_path.join("layout.md");
|
||||
let exists = layout_path.exists();
|
||||
println!(" {} {}", if exists { "UPDATE" } else { "CREATE" }, layout_path.display());
|
||||
|
||||
// Module docs
|
||||
for module_id in model.modules.keys() {
|
||||
let p = out_path.join("modules").join(format!("{}.md", sanitize_filename(module_id)));
|
||||
let exists = p.exists();
|
||||
println!(" {} {}", if exists { "UPDATE" } else { "CREATE" }, p.display());
|
||||
}
|
||||
|
||||
// File docs
|
||||
for file_doc in model.files.values() {
|
||||
let p = out_path.join("files").join(format!("{}.md", sanitize_filename(&file_doc.path)));
|
||||
let exists = p.exists();
|
||||
println!(" {} {}", if exists { "UPDATE" } else { "CREATE" }, p.display());
|
||||
}
|
||||
|
||||
let _ = config; // used for future extensions
|
||||
println!();
|
||||
println!("{} {} file(s) would be generated/updated",
|
||||
"✓".green().bold(),
|
||||
2 + model.modules.len() + model.files.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_docs(model: &ProjectModel, out: &str, verbose: bool, _config: &Config) -> Result<()> {
|
||||
println!("{}", "Generating documentation...".cyan());
|
||||
|
||||
let out_path = std::path::Path::new(out);
|
||||
std::fs::create_dir_all(out_path)?;
|
||||
|
||||
let modules_path = out_path.join("modules");
|
||||
let files_path = out_path.join("files");
|
||||
std::fs::create_dir_all(&modules_path)?;
|
||||
std::fs::create_dir_all(&files_path)?;
|
||||
|
||||
// Clean up stale files from previous runs
|
||||
for subdir in &["modules", "files"] {
|
||||
let dir = out_path.join(subdir);
|
||||
if dir.exists()
|
||||
&& let Ok(entries) = std::fs::read_dir(&dir) {
|
||||
for entry in entries.flatten() {
|
||||
if entry.path().extension().map(|e| e == "md").unwrap_or(false) {
|
||||
let _ = std::fs::remove_file(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let renderer = wtismycode_core::renderer::Renderer::new();
|
||||
let writer = wtismycode_core::writer::DiffAwareWriter::new();
|
||||
|
||||
let output_path = std::path::Path::new(".").join("ARCHITECTURE.md");
|
||||
|
||||
// Generate module docs
|
||||
for module_id in model.modules.keys() {
|
||||
let module_doc_path = modules_path.join(format!("{}.md", sanitize_filename(module_id)));
|
||||
if verbose {
|
||||
println!(" Generating module doc: {}", module_id);
|
||||
}
|
||||
match renderer.render_module_md(model, module_id) {
|
||||
Ok(module_content) => {
|
||||
std::fs::write(&module_doc_path, module_content)?;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(" {} Module {}: {}", "⚠".yellow(), module_id, e);
|
||||
let fallback = format!("# Module: {}\n\nTODO: Add module documentation\n", module_id);
|
||||
std::fs::write(&module_doc_path, fallback)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate file docs
|
||||
for file_doc in model.files.values() {
|
||||
if verbose {
|
||||
println!(" Generating file doc: {}", file_doc.path);
|
||||
}
|
||||
let file_doc_path = files_path.join(format!("{}.md", sanitize_filename(&file_doc.path)));
|
||||
|
||||
let mut file_content = format!("# File: {}\n\n", file_doc.path);
|
||||
file_content.push_str(&format!("- **Module:** {}\n", file_doc.module_id));
|
||||
file_content.push_str(&format!("- **Defined symbols:** {}\n", file_doc.symbols.len()));
|
||||
file_content.push_str(&format!("- **Imports:** {}\n\n", file_doc.imports.len()));
|
||||
|
||||
file_content.push_str("<!-- MANUAL:BEGIN -->\n## File intent (manual)\n<FILL_MANUALLY>\n<!-- MANUAL:END -->\n\n---\n\n");
|
||||
|
||||
file_content.push_str("## Imports & file-level dependencies\n<!-- ARCHDOC:BEGIN section=file_imports -->\n> Generated. Do not edit inside this block.\n");
|
||||
for import in &file_doc.imports {
|
||||
file_content.push_str(&format!("- {}\n", import));
|
||||
}
|
||||
file_content.push_str("<!-- ARCHDOC:END section=file_imports -->\n\n---\n\n");
|
||||
|
||||
file_content.push_str("## Symbols index\n<!-- ARCHDOC:BEGIN section=symbols_index -->\n> Generated. Do not edit inside this block.\n");
|
||||
for symbol_id in &file_doc.symbols {
|
||||
if let Some(symbol) = model.symbols.get(symbol_id) {
|
||||
file_content.push_str(&format!("- `{}` ({:?})\n", symbol.qualname, symbol.kind));
|
||||
}
|
||||
}
|
||||
file_content.push_str("<!-- ARCHDOC:END section=symbols_index -->\n\n---\n\n");
|
||||
|
||||
file_content.push_str("## Symbol details\n");
|
||||
|
||||
for symbol_id in &file_doc.symbols {
|
||||
if model.symbols.contains_key(symbol_id) {
|
||||
file_content.push_str(&format!("\n<!-- ARCHDOC:BEGIN symbol id={} -->\n", symbol_id));
|
||||
file_content.push_str("<!-- AUTOGENERATED SYMBOL CONTENT WILL BE INSERTED HERE -->\n");
|
||||
file_content.push_str(&format!("<!-- ARCHDOC:END symbol id={} -->\n", symbol_id));
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(&file_doc_path, &file_content)?;
|
||||
|
||||
for symbol_id in &file_doc.symbols {
|
||||
if model.symbols.contains_key(symbol_id) {
|
||||
match renderer.render_symbol_details(model, symbol_id) {
|
||||
Ok(content) => {
|
||||
if verbose {
|
||||
println!(" Updating symbol section for {}", symbol_id);
|
||||
}
|
||||
if let Err(e) = writer.update_symbol_section(&file_doc_path, symbol_id, &content) {
|
||||
eprintln!(" {} Symbol {}: {}", "⚠".yellow(), symbol_id, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(" {} Symbol {}: {}", "⚠".yellow(), symbol_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update ARCHITECTURE.md sections
|
||||
let sections = [
|
||||
("integrations", renderer.render_integrations_section(model)),
|
||||
("rails", renderer.render_rails_section(model)),
|
||||
("layout", renderer.render_layout_section(model)),
|
||||
("modules_index", renderer.render_modules_index_section(model)),
|
||||
("critical_points", renderer.render_critical_points_section(model)),
|
||||
];
|
||||
|
||||
for (name, result) in sections {
|
||||
match result {
|
||||
Ok(content) => {
|
||||
if let Err(e) = writer.update_file_with_markers(&output_path, &content, name)
|
||||
&& verbose {
|
||||
eprintln!(" {} Section {}: {}", "⚠".yellow(), name, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if verbose {
|
||||
eprintln!(" {} Section {}: {}", "⚠".yellow(), name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update layout.md
|
||||
let layout_md_path = out_path.join("layout.md");
|
||||
if let Ok(content) = renderer.render_layout_md(model) {
|
||||
let _ = std::fs::write(&layout_md_path, &content);
|
||||
}
|
||||
|
||||
println!("{} Documentation generated in {}", "✓".green().bold(), out);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
217
wtismycode-cli/src/commands/init.rs
Normal file
217
wtismycode-cli/src/commands/init.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
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 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)?;
|
||||
std::fs::create_dir_all(out_path.join("modules"))?;
|
||||
std::fs::create_dir_all(out_path.join("files"))?;
|
||||
|
||||
let layout_md_path = out_path.join("layout.md");
|
||||
let layout_md_content = r#"# Repository layout
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## Manual overrides
|
||||
- `src/app/` — <FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
|
||||
---
|
||||
|
||||
## Detected structure
|
||||
<!-- ARCHDOC:BEGIN section=layout_detected -->
|
||||
> Generated. Do not edit inside this block.
|
||||
<!-- ARCHDOC:END section=layout_detected -->
|
||||
"#;
|
||||
std::fs::write(&layout_md_path, layout_md_content)?;
|
||||
|
||||
let architecture_md_content = r#"# ARCHITECTURE — <PROJECT_NAME>
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## Project summary
|
||||
**Name:** <PROJECT_NAME>
|
||||
**Description:** <FILL_MANUALLY: what this project does in 3–7 lines>
|
||||
|
||||
## Key decisions (manual)
|
||||
- <FILL_MANUALLY>
|
||||
|
||||
## Non-goals (manual)
|
||||
- <FILL_MANUALLY>
|
||||
<!-- MANUAL:END -->
|
||||
|
||||
---
|
||||
|
||||
## Document metadata
|
||||
- **Created:** <AUTO_ON_INIT: YYYY-MM-DD>
|
||||
- **Updated:** <AUTO_ON_CHANGE: YYYY-MM-DD>
|
||||
- **Generated by:** wtismycode (cli) v0.1
|
||||
|
||||
---
|
||||
|
||||
## Integrations
|
||||
<!-- ARCHDOC:BEGIN section=integrations -->
|
||||
> Generated. Do not edit inside this block.
|
||||
<AUTO: detected integrations by category>
|
||||
<!-- ARCHDOC:END section=integrations -->
|
||||
|
||||
---
|
||||
|
||||
## Rails / Tooling
|
||||
<!-- ARCHDOC:BEGIN section=rails -->
|
||||
> Generated. Do not edit inside this block.
|
||||
<AUTO: rails summary + links to config files>
|
||||
<!-- ARCHDOC:END section=rails -->
|
||||
|
||||
---
|
||||
|
||||
## Repository layout (top-level)
|
||||
<!-- ARCHDOC:BEGIN section=layout -->
|
||||
> Generated. Do not edit inside this block.
|
||||
<AUTO: table of top-level folders + heuristic purpose + link to layout.md>
|
||||
<!-- ARCHDOC:END section=layout -->
|
||||
|
||||
---
|
||||
|
||||
## Modules index
|
||||
<!-- ARCHDOC:BEGIN section=modules_index -->
|
||||
> Generated. Do not edit inside this block.
|
||||
<AUTO: table modules + deps counts + links to module docs>
|
||||
<!-- ARCHDOC:END section=modules_index -->
|
||||
|
||||
---
|
||||
|
||||
## Critical dependency points
|
||||
<!-- ARCHDOC:BEGIN section=critical_points -->
|
||||
> Generated. Do not edit inside this block.
|
||||
<AUTO: top fan-in/out symbols + cycles>
|
||||
<!-- ARCHDOC:END section=critical_points -->
|
||||
|
||||
---
|
||||
|
||||
<!-- MANUAL:BEGIN -->
|
||||
## Change notes (manual)
|
||||
- <FILL_MANUALLY>
|
||||
<!-- 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)?;
|
||||
|
||||
let config_toml_content = r#"[project]
|
||||
root = "."
|
||||
out_dir = "docs/architecture"
|
||||
entry_file = "ARCHITECTURE.md"
|
||||
language = "python"
|
||||
|
||||
[scan]
|
||||
include = ["src", "app", "tests"]
|
||||
exclude = [
|
||||
".venv", "venv", "__pycache__", ".git", "dist", "build",
|
||||
".mypy_cache", ".ruff_cache", ".pytest_cache", "*.egg-info"
|
||||
]
|
||||
follow_symlinks = false
|
||||
max_file_size = "10MB"
|
||||
|
||||
[python]
|
||||
src_roots = ["src", "."]
|
||||
include_tests = true
|
||||
parse_docstrings = true
|
||||
max_parse_errors = 10
|
||||
|
||||
[analysis]
|
||||
resolve_calls = true
|
||||
resolve_inheritance = false
|
||||
detect_integrations = true
|
||||
integration_patterns = [
|
||||
{ type = "http", patterns = ["requests", "httpx", "aiohttp"] },
|
||||
{ type = "db", patterns = ["sqlalchemy", "psycopg", "mysql", "sqlite3"] },
|
||||
{ type = "queue", patterns = ["celery", "kafka", "pika", "redis"] }
|
||||
]
|
||||
|
||||
[output]
|
||||
single_file = false
|
||||
per_file_docs = true
|
||||
create_directories = true
|
||||
overwrite_manual_sections = false
|
||||
|
||||
[diff]
|
||||
update_timestamp_on_change_only = true
|
||||
hash_algorithm = "sha256"
|
||||
preserve_manual_content = true
|
||||
|
||||
[thresholds]
|
||||
critical_fan_in = 20
|
||||
critical_fan_out = 20
|
||||
high_complexity = 50
|
||||
|
||||
[rendering]
|
||||
template_engine = "handlebars"
|
||||
max_table_rows = 100
|
||||
truncate_long_descriptions = true
|
||||
description_max_length = 200
|
||||
|
||||
[logging]
|
||||
level = "info"
|
||||
file = "wtismycode.log"
|
||||
format = "compact"
|
||||
|
||||
[caching]
|
||||
enabled = true
|
||||
cache_dir = ".wtismycode/cache"
|
||||
max_cache_age = "24h"
|
||||
"#;
|
||||
|
||||
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)?;
|
||||
}
|
||||
|
||||
println!("{} Project initialized!", "✓".green().bold());
|
||||
println!(" {} {}", "→".dimmed(), architecture_md_path.display());
|
||||
println!(" {} {}", "→".dimmed(), config_toml_path.display());
|
||||
println!(" {} {} (directory)", "→".dimmed(), out_path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
4
wtismycode-cli/src/commands/mod.rs
Normal file
4
wtismycode-cli/src/commands/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod init;
|
||||
pub mod generate;
|
||||
pub mod check;
|
||||
pub mod stats;
|
||||
97
wtismycode-cli/src/commands/stats.rs
Normal file
97
wtismycode-cli/src/commands/stats.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use wtismycode_core::ProjectModel;
|
||||
use colored::Colorize;
|
||||
|
||||
pub fn print_stats(model: &ProjectModel) {
|
||||
println!();
|
||||
println!("{}", "╔══════════════════════════════════════╗".cyan());
|
||||
println!("{}", "║ wtismycode project statistics ║".cyan().bold());
|
||||
println!("{}", "╚══════════════════════════════════════╝".cyan());
|
||||
println!();
|
||||
|
||||
// Basic counts
|
||||
println!("{}", "Overview".bold().underline());
|
||||
println!(" Files: {}", model.files.len().to_string().yellow());
|
||||
println!(" Modules: {}", model.modules.len().to_string().yellow());
|
||||
println!(" Symbols: {}", model.symbols.len().to_string().yellow());
|
||||
println!(" Import edges: {}", model.edges.module_import_edges.len());
|
||||
println!(" Call edges: {}", model.edges.symbol_call_edges.len());
|
||||
println!();
|
||||
|
||||
// Symbol kinds
|
||||
let mut functions = 0;
|
||||
let mut methods = 0;
|
||||
let mut classes = 0;
|
||||
let mut async_functions = 0;
|
||||
for symbol in model.symbols.values() {
|
||||
match symbol.kind {
|
||||
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());
|
||||
println!(" Classes: {}", classes);
|
||||
println!(" Functions: {}", functions);
|
||||
println!(" Async functions: {}", async_functions);
|
||||
println!(" Methods: {}", methods);
|
||||
println!();
|
||||
|
||||
// Top fan-in
|
||||
let mut symbols_by_fan_in: Vec<_> = model.symbols.values().collect();
|
||||
symbols_by_fan_in.sort_by(|a, b| b.metrics.fan_in.cmp(&a.metrics.fan_in));
|
||||
|
||||
println!("{}", "Top-10 by fan-in (most called)".bold().underline());
|
||||
for (i, sym) in symbols_by_fan_in.iter().take(10).enumerate() {
|
||||
if sym.metrics.fan_in == 0 { break; }
|
||||
let critical = if sym.metrics.is_critical { " ⚠ CRITICAL".red().to_string() } else { String::new() };
|
||||
println!(" {}. {} (fan-in: {}){}", i + 1, sym.qualname.green(), sym.metrics.fan_in, critical);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Top fan-out
|
||||
let mut symbols_by_fan_out: Vec<_> = model.symbols.values().collect();
|
||||
symbols_by_fan_out.sort_by(|a, b| b.metrics.fan_out.cmp(&a.metrics.fan_out));
|
||||
|
||||
println!("{}", "Top-10 by fan-out (calls many)".bold().underline());
|
||||
for (i, sym) in symbols_by_fan_out.iter().take(10).enumerate() {
|
||||
if sym.metrics.fan_out == 0 { break; }
|
||||
let critical = if sym.metrics.is_critical { " ⚠ CRITICAL".red().to_string() } else { String::new() };
|
||||
println!(" {}. {} (fan-out: {}){}", i + 1, sym.qualname.green(), sym.metrics.fan_out, critical);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Integrations
|
||||
let http_symbols: Vec<_> = model.symbols.values().filter(|s| s.integrations_flags.http).collect();
|
||||
let db_symbols: Vec<_> = model.symbols.values().filter(|s| s.integrations_flags.db).collect();
|
||||
let queue_symbols: Vec<_> = model.symbols.values().filter(|s| s.integrations_flags.queue).collect();
|
||||
|
||||
if !http_symbols.is_empty() || !db_symbols.is_empty() || !queue_symbols.is_empty() {
|
||||
println!("{}", "Detected integrations".bold().underline());
|
||||
if !http_symbols.is_empty() {
|
||||
println!(" {} HTTP: {}", "●".yellow(), http_symbols.iter().map(|s| s.qualname.as_str()).collect::<Vec<_>>().join(", "));
|
||||
}
|
||||
if !db_symbols.is_empty() {
|
||||
println!(" {} DB: {}", "●".blue(), db_symbols.iter().map(|s| s.qualname.as_str()).collect::<Vec<_>>().join(", "));
|
||||
}
|
||||
if !queue_symbols.is_empty() {
|
||||
println!(" {} Queue: {}", "●".magenta(), queue_symbols.iter().map(|s| s.qualname.as_str()).collect::<Vec<_>>().join(", "));
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
// Cycles
|
||||
println!("{}", "Cycle detection".bold().underline());
|
||||
let mut found_cycles = false;
|
||||
for edge in &model.edges.module_import_edges {
|
||||
let has_reverse = model.edges.module_import_edges.iter()
|
||||
.any(|e| e.from_id == edge.to_id && e.to_id == edge.from_id);
|
||||
if has_reverse && edge.from_id < edge.to_id {
|
||||
println!(" {} {} ↔ {}", "⚠".red(), edge.from_id, edge.to_id);
|
||||
found_cycles = true;
|
||||
}
|
||||
}
|
||||
if !found_cycles {
|
||||
println!(" {} No cycles detected", "✓".green());
|
||||
}
|
||||
}
|
||||
86
wtismycode-cli/src/main.rs
Normal file
86
wtismycode-cli/src/main.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
mod commands;
|
||||
mod output;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "wtismycode")]
|
||||
#[command(about = "Generate architecture documentation for Python projects")]
|
||||
#[command(version = "0.1.0")]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
|
||||
/// Verbose output
|
||||
#[arg(short, long, global = true)]
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Initialize wtismycode in the project
|
||||
Init {
|
||||
#[arg(short, long, default_value = ".")]
|
||||
root: String,
|
||||
#[arg(short, long, default_value = "docs/architecture")]
|
||||
out: String,
|
||||
},
|
||||
/// Generate or update documentation
|
||||
Generate {
|
||||
#[arg(short, long, default_value = ".")]
|
||||
root: String,
|
||||
#[arg(short, long, default_value = "docs/architecture")]
|
||||
out: String,
|
||||
#[arg(short, long, default_value = "wtismycode.toml")]
|
||||
config: String,
|
||||
/// Show what would be generated without writing files
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
},
|
||||
/// Check if documentation is up to date
|
||||
Check {
|
||||
#[arg(short, long, default_value = ".")]
|
||||
root: String,
|
||||
#[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 = "wtismycode.toml")]
|
||||
config: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match &cli.command {
|
||||
Commands::Init { root, out } => {
|
||||
commands::init::init_project(root, out)?;
|
||||
}
|
||||
Commands::Generate { root, out, config, dry_run } => {
|
||||
let config = commands::generate::load_config(config)?;
|
||||
let model = commands::generate::analyze_project(root, &config)?;
|
||||
if *dry_run {
|
||||
commands::generate::dry_run_docs(&model, out, &config)?;
|
||||
} else {
|
||||
commands::generate::generate_docs(&model, out, cli.verbose, &config)?;
|
||||
}
|
||||
output::print_generate_summary(&model);
|
||||
}
|
||||
Commands::Check { root, config } => {
|
||||
let config = commands::generate::load_config(config)?;
|
||||
commands::check::check_docs_consistency(root, &config)?;
|
||||
}
|
||||
Commands::Stats { root, config } => {
|
||||
let config = commands::generate::load_config(config)?;
|
||||
let model = commands::generate::analyze_project(root, &config)?;
|
||||
commands::stats::print_stats(&model);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
35
wtismycode-cli/src/output.rs
Normal file
35
wtismycode-cli/src/output.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! Colored output helpers and filename utilities for WTIsMyCode CLI
|
||||
|
||||
use colored::Colorize;
|
||||
use wtismycode_core::ProjectModel;
|
||||
|
||||
/// Sanitize a file path into a safe filename for docs.
|
||||
/// Removes `./` prefix, replaces `/` with `__`.
|
||||
pub fn sanitize_filename(filename: &str) -> String {
|
||||
let cleaned = filename.strip_prefix("./").unwrap_or(filename);
|
||||
cleaned.replace('/', "__")
|
||||
}
|
||||
|
||||
pub fn print_generate_summary(model: &ProjectModel) {
|
||||
println!();
|
||||
println!("{}", "── Summary ──────────────────────────".dimmed());
|
||||
println!(" {} {}", "Files:".bold(), model.files.len());
|
||||
println!(" {} {}", "Modules:".bold(), model.modules.len());
|
||||
println!(" {} {}", "Symbols:".bold(), model.symbols.len());
|
||||
println!(" {} {}", "Edges:".bold(),
|
||||
model.edges.module_import_edges.len() + model.edges.symbol_call_edges.len());
|
||||
|
||||
let integrations: Vec<&str> = {
|
||||
let mut v = Vec::new();
|
||||
if model.symbols.values().any(|s| s.integrations_flags.http) { v.push("HTTP"); }
|
||||
if model.symbols.values().any(|s| s.integrations_flags.db) { v.push("DB"); }
|
||||
if model.symbols.values().any(|s| s.integrations_flags.queue) { v.push("Queue"); }
|
||||
if model.symbols.values().any(|s| s.integrations_flags.storage) { v.push("Storage"); }
|
||||
if model.symbols.values().any(|s| s.integrations_flags.ai) { v.push("AI/ML"); }
|
||||
v
|
||||
};
|
||||
if !integrations.is_empty() {
|
||||
println!(" {} {}", "Integrations:".bold(), integrations.join(", ").yellow());
|
||||
}
|
||||
println!("{}", "─────────────────────────────────────".dimmed());
|
||||
}
|
||||
Reference in New Issue
Block a user