StrucIn analyzes local Python repositories and generates architecture artifacts you can use for technical discovery, refactoring planning, onboarding, and architectural dependency review.
- Repository indexing with module mapping and file metadata
- Static AST analysis for imports, classes, functions, docstrings, and complexity
- Dependency graph and cycle detection
- Architecture report (
docs/REPORT.md) - Semantic code/document search (
semantic_index.jsonat repo root) - Architecture narration (
docs/EXPLAIN.md) with caching and redaction - Interactive web dashboard for dependency exploration
- Language: Python repositories
- Input: local filesystem paths
- Output: JSON, Markdown, and static dashboard files, normally written to the target repository
src/strucin/core/: analysis, reporting, semantic indexing, explanationssrc/strucin/cli/: command parsing, UX, orchestrationsrc/strucin/web/: dashboard generation and local servingsrc/strucin/core/config.py: configuration loading and defaultssrc/strucin/utils/: shared utility helperstests/: unit and regression test suite with fixtures
Install from source; a StrucIn package has not yet been published on PyPI. These instructions and the security policy describe the current source checkout.
git clone https://github.com/macitch/StrucIn.git
cd StrucInpython3 -m venv .venv
source .venv/bin/activatepython -m pip install --upgrade "pip>=26.2"
python -m pip install -e .For development and the checks below, use python -m pip install -e ".[dev]".
strucin --help# Scaffold configuration
strucin init --path /path/to/myrepo
# Analyze a local Python repo
strucin scan /path/to/myrepo
strucin analyze /path/to/myrepo
strucin report /path/to/myrepo # writes docs/REPORT.md
strucin explain --path /path/to/myrepo # writes docs/EXPLAIN.md (template, no key needed)
# Enable LLM-powered narration
python -m pip install -e ".[llm]"
export ANTHROPIC_API_KEY="sk-ant-..."
strucin explain --path /path/to/myrepo # writes docs/EXPLAIN.md
# Semantic search
strucin search "authentication middleware" --path /path/to/myrepo
# Compare two analysis snapshots
strucin diff before.json after.json
# Machine-readable output
strucin analyze /path/to/myrepo --json
# Generate and serve the web dashboard
strucin web --path /path/to/myrepo --serveWith --json, scan, analyze, search, and diff write one JSON document to
stdout on success. Progress, timing summaries, warnings, and errors go to stderr.
Check the exit status before consuming the result; failed commands return a
nonzero status. For example:
strucin analyze /path/to/myrepo --json > analysis-summary.jsonRun these commands from the cloned repository:
| Install command | Installs | Enables |
|---|---|---|
python -m pip install -e ".[llm]" |
anthropic, openai | LLM-powered narration |
python -m pip install -e ".[embeddings]" |
sentence-transformers, PyTorch, setuptools | Neural semantic search |
python -m pip install -e ".[ai]" |
all of the above | Full AI feature set |
python -m pip install -e ".[dev]" |
pytest, ruff, mypy, coverage | Development tooling |
The embeddings and ai extras require PyTorch 2.13 or newer. Published macOS
wheels for these versions require Apple Silicon and macOS 14 or newer. On other
Mac configurations, install the base package and use the hashing search backend.
See dependency auditing for security checks.
Scaffolds a .strucin.toml configuration file with commented defaults.
strucin init --path /path/to/repo
strucin init --path /path/to/repo --force # overwrite existingRecursively discovers Python files and writes repository index metadata.
python -m strucin.cli scan /path/to/repoPrimary output:
repo_index.json
Runs AST analysis, builds dependency graph, computes metrics, and detects cycles.
python -m strucin.cli analyze /path/to/repoPrimary outputs:
analysis.jsondependency_graph.json
Generates Markdown architecture report from analysis results.
python -m strucin.cli report /path/to/repoPrimary output:
docs/REPORT.md
Builds or reuses a current semantic index and returns top semantic matches.
python -m strucin.cli search "retry failed payment" --path /path/to/repo --top-k 5
python -m strucin.cli search "retry failed payment" --path /path/to/repo --refreshSearch checks the contents and paths of eligible Python and documentation files on every run. Edits, additions, deletions, renames, source-root mappings, excluded directories, and embedding model/dimension settings invalidate the saved index. Unchanged inputs reuse their existing embeddings; changing the query, result limit, worker count, or timing settings does not require rebuilding them. Older indexes without a content fingerprint are rebuilt automatically.
Use --refresh to bypass the saved index and regenerate embeddings, including
after installing a previously unavailable neural model. The fingerprint tracks
the requested model settings separately from the actual model used, so a hashing
fallback or a neural model's fixed vector width does not cause repeated rebuilds.
Safe mode continues to build in memory without reading or writing the saved index.
Queries must use the same embedding model and vector width as the index. If a
saved neural index's model becomes unavailable, search stops with an error
before scoring. Restore that model and its dependencies, or set
embedding_model = "hashing-v1" under [search] in .strucin.toml and rerun
with --refresh to rebuild all vectors. Building a new index can still fall
back to hashing when a neural model cannot load; the index records the actual
model used so subsequent queries use that same model.
Library callers can pass cached_index= to build_semantic_index to request the
same freshness check; omitting it always builds a new index.
Primary output:
semantic_index.json
Generates architecture narration with cache support and metadata output.
python -m strucin.cli explain --path /path/to/repo
python -m strucin.cli explain --path /path/to/repo --refreshPrimary outputs:
docs/EXPLAIN.mdexplain.json.strucin_cache/explain_cache.json
Analysis and narration caches are disposable. Unreadable or malformed cache
files and invalid entries are skipped with a warning, then recomputed. Valid
entries can still be reused. explain --refresh bypasses the narration cache
read and replaces it with the newly generated entry.
Cache updates use a temporary file in the same directory and atomic replacement, so readers see a complete old or new file. If cache persistence fails, the computed analysis or narration is still returned with a warning. Required output artifacts and path-containment errors continue to fail the command. These rules also apply to the separate safe-mode narration cache; safe mode still skips raw analysis caches.
Compares two analysis snapshots and shows what changed: new/resolved cycles, complexity delta, coupling shifts, and LOC changes.
strucin diff before.json after.json
strucin diff before.json after.json --jsonThe summary's total LOC delta includes added modules, removed modules, and LOC changes in retained modules. For example, adding 100 lines, removing 40, and adding 10 to an existing module reports a net change of +70 lines.
files_changed counts added and removed modules plus retained modules with a
reported LOC, complexity, or coupling change, counting each module once. Coupling
changes are reported when fan-in or fan-out changes by at least 2. A module rename
counts as one removal and one addition. Detailed metric-change lists compare
modules present in both snapshots; additions and removals have their own lists.
Generates interactive web dashboard assets and optionally serves locally.
python -m strucin.cli web --path /path/to/repo --out /path/to/repo/.strucin_web
python -m strucin.cli web --path /path/to/repo --serve --host 127.0.0.1 --port 8765Primary outputs:
.strucin_web/index.html.strucin_web/app.js.strucin_web/styles.css.strucin_web/data.json
StrucIn reads .strucin.toml from the target repository root.
[scan]
exclude_dirs = [".git", "__pycache__", "node_modules", "venv", ".venv"]
[search]
top_k = 5
dimensions = 256
embedding_model = "all-MiniLM-L6-v2" # Use "hashing-v1" for local hashing without model downloads
[performance]
max_workers = 8
executor = "auto"
[lifecycle]
cache_retention_days = 14
[observability]
structured_logging = false
timing_enabled = true
[output]
repo_index = "repo_index.json"
analysis = "analysis.json"
dependency_graph = "dependency_graph.json"
report = "docs/REPORT.md"
semantic_index = "semantic_index.json"
explain_markdown = "docs/EXPLAIN.md"
explain_metadata = "explain.json"Notes:
max_workerscontrols parallel scanning/analysis/semantic indexing- By default, JSON artifacts are written to the target repo root; Markdown reports go to
docs/ cache_retention_daysalso controls deletion of expired configured outputs, not just caches; copy snapshots you want to retain to a separate path
Each output can use an independent nested path within the repository, such as
dependency_graph = "graphs/nested/dependencies.json" or
explain_metadata = "metadata/narration/details.json". Commands validate output
paths before creating their parent directories. analyze and explain prepare
both output parents before writing either result, so a directory-creation error
does not overwrite one result while leaving the other unwritten.
StrucIn keeps repository-relative file paths separate from Python import names:
src/pkg/service.py is recorded at that path with module name pkg.service.
Flat layouts and namespace packages are supported. Source roots are selected in
this order:
- Repeated
--source-root DIRoptions for the current command. [scan] source_rootsin.strucin.toml.- Static packaging metadata: setuptools
package-dir/packages.find.whereinpyproject.toml, Poetrypackages[].from, or setuptoolspackage_dir/options.packages.find.whereinsetup.cfg. - A top-level
src/directory withoutsrc/__init__.py, then the repository root.
[scan]
source_roots = ["src", "components/shared/python"]strucin analyze /path/to/repo --source-root src --source-root components/shared/python
strucin-check-cycles /path/to/repo --source-root srcThe override is available on scan, analyze, report, explain, search,
web, and both pre-commit hooks. Library entry points accept source_roots=;
pass repository configuration explicitly, as with other library options.
Use ["."] (or --source-root .) to retain repository-relative import names
and disable automatic discovery. Paths must name existing directories inside
the repository; absolute paths and .. are rejected.
Nested roots take precedence over their parents. Named setuptools package
mappings preserve the configured package prefix. Files outside configured roots,
such as tests and scripts, retain repository-relative module names. Conflicting
files with the same import name cause a clear error; adjust the roots or exclude
the duplicate directory. StrucIn reads metadata without running setup.py or
importing the project. For dynamic build configuration or other packaging tools,
set the roots explicitly.
Analysis caches recheck module identities, and search rebuilds older indexes or indexes created with different source-root mappings. Existing snapshots keep their original module names; regenerate them before comparing layouts across this change.
Packaging conventions: setuptools package discovery and Poetry packages.
For shareable output, set [security] safe_mode = true in .strucin.toml, or
pass --safe-mode to scan, analyze, report, explain, search, or web:
strucin analyze /path/to/repo --safe-mode
strucin explain --path /path/to/repo --safe-mode
strucin web --path /path/to/repo --safe-mode
strucin diff before.json after.json --safe-modeSafe mode anonymizes names and paths in generated content and LLM context,
omits docstrings/source snippets, skips raw analysis caches, and keeps a separate
safe narration cache. Search uses an in-memory index and anonymous results.
--no-safe-mode overrides the repository setting for one run.
Existing artifacts are not retroactively scrubbed. Anonymous labels are local to
an export; compare original snapshots with diff --safe-mode. See
SECURITY.md for the full contract and library usage.
| Artifact | Location | Description |
|---|---|---|
repo_index.json |
repo root | File/module map with LOC and size |
analysis.json |
repo root | Static analysis details and dependency graph |
dependency_graph.json |
repo root | Graph nodes/edges for integrations |
semantic_index.json |
repo root | Chunk embeddings and metadata |
explain.json |
repo root | Narration metadata (timestamp/cache key) |
REPORT.md |
docs/ |
Architecture summary and hotspots |
EXPLAIN.md |
docs/ |
Architecture narrative and onboarding guide |
.strucin_web/* |
repo root | Static dashboard files |
Run StrucIn in CI and post architecture reports as PR comments:
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: macitch/StrucIn@main
with:
command: report
post-comment: "true"
fail-on-cycles: "false"Place this excerpt in a job triggered by pull_request. PR comments require a
write-capable token; for read-only tokens, including typical fork PR workflows,
set post-comment: 'false'. Use a reviewed commit SHA for reproducible Action and
pre-commit installations; main follows development changes.
Every Action command (scan, analyze, or report) produces fresh analysis
and dependency graph JSON at the locations configured in .strucin.toml.
The cycle decision uses that run's in-memory analysis. report renders the same
result; stale or malformed snapshots from earlier runs cannot change the gate.
Analysis or artifact-writing errors fail the Action even in advisory mode.
Set fail-on-cycles: 'true' to fail when the current analysis contains cycles.
safe-mode: 'true' anonymizes artifacts and preserves cycle enforcement; it does
not suppress errors. With the input left false, the repository's safe-mode
configuration still applies.
The Action exposes analysis-path, cycles-found, cycle-count, and
report-path. Outputs are set after artifact generation succeeds, including when
the cycle gate subsequently fails. A failure before completion leaves the cycle
result unset, rather than reporting false. report-path is empty for scan and
analyze; old reports are never reused for comments or uploads. Reports from
strict cycle failures are still uploaded and can be posted to the PR when enabled.
See .github/workflows/example-usage.yml for a full example with strict mode
and workflow dispatch support. .github/workflows/action-test.yml checks the
composite Action against cyclic and acyclic fixtures for all three commands.
Add StrucIn checks to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/macitch/StrucIn
rev: main
hooks:
- id: strucin-cycles # fail on dependency cycles
- id: strucin-complexity # fail on high complexity
args: ['--threshold', '15']StrucIn calculates an AST-based complexity score as 1 plus decision points:
| Construct | Added points |
|---|---|
if, elif, conditional expression (x if condition else y) |
1 each |
for, async for, while |
1 each |
except or except* handler |
1 per handler |
assert |
1 each |
Boolean and / or |
1 per operator; six operands add 5 |
| Comprehension | 1 per for generator plus 1 per if filter |
Refutable match case |
1 per case |
match guard (case ... if condition) |
1 per guard, plus boolean operators in it |
An unconditional wildcard or capture case adds no point; a guarded one adds its
guard's points. Aliases preserve the underlying pattern's behavior. An OR pattern
counts as a single case, and is unconditional if an alternative is unconditional.
try, else, finally, context managers, and ordinary expressions add no points
of their own; decisions inside them still count.
For example, return a and b and c and d and e and f scores 6. A match
with three literal cases and one unconditional wildcard scores 4.
File and function scores aggregate decisions across their entire AST, including nested definitions. Each score starts with one base point; the file score is not the sum of function scores. The complexity hook applies its threshold to file/module scores, and fails only when a score exceeds the threshold.
The corrected rules invalidate older analysis caches automatically. Regenerate saved analysis snapshots before comparing complexity across this change; the new rules can raise or lower scores without source edits.
Run project checks:
ruff check src/ tests/
ruff format --check src/ tests/
mypy src/strucin
pytest -q -m "not integration" --cov=src/strucin --cov-report=term-missing --cov-fail-under=85CI runs lint, strict type checks, and tests on Python 3.11, 3.12, and 3.13. Coverage includes the CLI and module entry point. Dependency auditing covers base, optional AI, development, and build requirements; see CONTRIBUTING.md for the audit matrix and commands. Live provider tests, neural-model quality checks, and real browser interactions are separate from the default test suite.
Generate and serve:
python -m strucin.cli web --path . --serve --host 127.0.0.1 --port 8765Open:
http://127.0.0.1:8765/index.html
The examples/ directory contains sample output artifacts generated by running
StrucIn on its own codebase, so you can see what you get before installing.
Source and documentation file links must resolve to regular files inside the repository. External, dangling, and looping links are skipped; directory links are not traversed. Older search indexes are rebuilt to apply these read rules.
See SECURITY.md for the security policy, how StrucIn handles your code, and how to report vulnerabilities.