sourcecode 0.20.0__tar.gz → 0.22.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-0.20.0 → sourcecode-0.22.0}/PKG-INFO +70 -1
- {sourcecode-0.20.0 → sourcecode-0.22.0}/README.md +69 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/docs/schema.md +2 -2
- {sourcecode-0.20.0 → sourcecode-0.22.0}/pyproject.toml +1 -1
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/architecture_summary.py +72 -23
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/classifier.py +3 -1
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/cli.py +123 -41
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/dependency_analyzer.py +75 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/base.py +2 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/heuristic.py +2 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/nodejs.py +6 -1
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/project.py +10 -1
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/python.py +63 -14
- sourcecode-0.22.0/src/sourcecode/prepare_context.py +486 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/scanner.py +58 -2
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/schema.py +1 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/semantic_analyzer.py +2 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/serializer.py +187 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/summarizer.py +25 -10
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/tree_utils.py +14 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_architecture_summary.py +2 -2
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_dependency_analyzer_node_python.py +2 -2
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration.py +12 -5
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_dependencies.py +5 -5
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_docs.py +2 -2
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_graph_modules.py +2 -6
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_lqn.py +6 -8
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_metrics.py +7 -7
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_real_projects.py +1 -1
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_summarizer.py +1 -2
- sourcecode-0.20.0/src/sourcecode/prepare_context.py +0 -197
- {sourcecode-0.20.0 → sourcecode-0.22.0}/.gitignore +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/.ruff.toml +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/__init__.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/conftest.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/lcov.info +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_classifier.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_cli.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_coverage_parser.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_cross_consistency.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_dependency_schema.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_detector_python.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_detectors_base.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_graph_schema.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_detection.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_multistack.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_semantics.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_integration_universal.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_packaging.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_redactor.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_scanner.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_schema.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_schema_normalization.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_semantic_schema.py +0 -0
- {sourcecode-0.20.0 → sourcecode-0.22.0}/tests/test_workspace_analyzer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0
|
|
4
4
|
Summary: Genera un mapa de contexto estructurado de proyectos de software para agentes IA
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -636,6 +636,75 @@ Combine flags for a comprehensive project handoff:
|
|
|
636
636
|
sourcecode --compact --env-map --code-notes --git-context .
|
|
637
637
|
```
|
|
638
638
|
|
|
639
|
+
## `prepare-context` — Task-aware context for LLMs
|
|
640
|
+
|
|
641
|
+
`prepare-context` is a subcommand that builds a focused, task-specific context optimized for LLM reasoning. Instead of a full project dump, it returns only the data an LLM needs for a specific goal.
|
|
642
|
+
|
|
643
|
+
> **Note:** Because `sourcecode` has a positional `PATH` argument, you must provide it explicitly before the subcommand:
|
|
644
|
+
> ```bash
|
|
645
|
+
> sourcecode . prepare-context <task> # current directory
|
|
646
|
+
> sourcecode /my/project prepare-context <task>
|
|
647
|
+
> ```
|
|
648
|
+
|
|
649
|
+
### Available tasks
|
|
650
|
+
|
|
651
|
+
| Task | Goal | Output |
|
|
652
|
+
|------|------|--------|
|
|
653
|
+
| `explain` | Onboard an LLM to the project | `project_summary`, `architecture_summary`, `relevant_files`, `key_dependencies` |
|
|
654
|
+
| `fix-bug` | Identify likely bug locations | `relevant_files` (ranked by risk), `suspected_areas`, `code_notes_summary` |
|
|
655
|
+
| `refactor` | Surface improvement opportunities | `relevant_files`, `improvement_opportunities`, `architecture_summary` |
|
|
656
|
+
| `generate-tests` | Find untested areas | `test_gaps`, `relevant_files` (source without tests), `key_dependencies` |
|
|
657
|
+
|
|
658
|
+
### Usage
|
|
659
|
+
|
|
660
|
+
```bash
|
|
661
|
+
# List tasks with descriptions
|
|
662
|
+
sourcecode . prepare-context --task-help
|
|
663
|
+
|
|
664
|
+
# Explain the project
|
|
665
|
+
sourcecode . prepare-context explain
|
|
666
|
+
|
|
667
|
+
# Find bug areas, with a ready-to-paste LLM prompt
|
|
668
|
+
sourcecode . prepare-context fix-bug --llm-prompt
|
|
669
|
+
|
|
670
|
+
# Find untested files in a specific project
|
|
671
|
+
sourcecode . prepare-context generate-tests --path /my/project
|
|
672
|
+
|
|
673
|
+
# Preview what will be analyzed (no analysis run)
|
|
674
|
+
sourcecode . prepare-context refactor --dry-run
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Output format
|
|
678
|
+
|
|
679
|
+
```json
|
|
680
|
+
{
|
|
681
|
+
"task": "fix-bug",
|
|
682
|
+
"goal": "Identify the most likely files and areas where a bug may be located.",
|
|
683
|
+
"project_summary": "CLI en Python (Typer). Entry points: src/cli.py. 4 dependencias.",
|
|
684
|
+
"architecture_summary": null,
|
|
685
|
+
"relevant_files": [
|
|
686
|
+
{ "path": "src/handler.py", "role": "source", "score": 2.0, "reason": "matches 'handler'" },
|
|
687
|
+
{ "path": "src/cli.py", "role": "entrypoint", "score": 3.0, "reason": "entry point" }
|
|
688
|
+
],
|
|
689
|
+
"suspected_areas": ["src/handler.py (2 annotations)", "src/parser.py (1 annotation)"],
|
|
690
|
+
"code_notes_summary": { "total": 5, "by_kind": { "FIXME": 3, "BUG": 2 }, "top_files": ["src/handler.py"] }
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### `--llm-prompt`
|
|
695
|
+
|
|
696
|
+
Add `--llm-prompt` to include a ready-to-use prompt that you can paste directly into any LLM:
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
sourcecode . prepare-context explain --llm-prompt | jq -r '.llm_prompt'
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
The prompt is task-specific and includes the project context, relevant files, and concrete instructions for the LLM.
|
|
703
|
+
|
|
704
|
+
### Task auto-selection with `--agent`
|
|
705
|
+
|
|
706
|
+
For zero-config usage, `--agent` on the main command automatically selects the right flags for the project type. For task-aware context, use `prepare-context` explicitly with the task that matches your goal.
|
|
707
|
+
|
|
639
708
|
## Development
|
|
640
709
|
|
|
641
710
|
Editable install with development dependencies:
|
|
@@ -620,6 +620,75 @@ Combine flags for a comprehensive project handoff:
|
|
|
620
620
|
sourcecode --compact --env-map --code-notes --git-context .
|
|
621
621
|
```
|
|
622
622
|
|
|
623
|
+
## `prepare-context` — Task-aware context for LLMs
|
|
624
|
+
|
|
625
|
+
`prepare-context` is a subcommand that builds a focused, task-specific context optimized for LLM reasoning. Instead of a full project dump, it returns only the data an LLM needs for a specific goal.
|
|
626
|
+
|
|
627
|
+
> **Note:** Because `sourcecode` has a positional `PATH` argument, you must provide it explicitly before the subcommand:
|
|
628
|
+
> ```bash
|
|
629
|
+
> sourcecode . prepare-context <task> # current directory
|
|
630
|
+
> sourcecode /my/project prepare-context <task>
|
|
631
|
+
> ```
|
|
632
|
+
|
|
633
|
+
### Available tasks
|
|
634
|
+
|
|
635
|
+
| Task | Goal | Output |
|
|
636
|
+
|------|------|--------|
|
|
637
|
+
| `explain` | Onboard an LLM to the project | `project_summary`, `architecture_summary`, `relevant_files`, `key_dependencies` |
|
|
638
|
+
| `fix-bug` | Identify likely bug locations | `relevant_files` (ranked by risk), `suspected_areas`, `code_notes_summary` |
|
|
639
|
+
| `refactor` | Surface improvement opportunities | `relevant_files`, `improvement_opportunities`, `architecture_summary` |
|
|
640
|
+
| `generate-tests` | Find untested areas | `test_gaps`, `relevant_files` (source without tests), `key_dependencies` |
|
|
641
|
+
|
|
642
|
+
### Usage
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
# List tasks with descriptions
|
|
646
|
+
sourcecode . prepare-context --task-help
|
|
647
|
+
|
|
648
|
+
# Explain the project
|
|
649
|
+
sourcecode . prepare-context explain
|
|
650
|
+
|
|
651
|
+
# Find bug areas, with a ready-to-paste LLM prompt
|
|
652
|
+
sourcecode . prepare-context fix-bug --llm-prompt
|
|
653
|
+
|
|
654
|
+
# Find untested files in a specific project
|
|
655
|
+
sourcecode . prepare-context generate-tests --path /my/project
|
|
656
|
+
|
|
657
|
+
# Preview what will be analyzed (no analysis run)
|
|
658
|
+
sourcecode . prepare-context refactor --dry-run
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Output format
|
|
662
|
+
|
|
663
|
+
```json
|
|
664
|
+
{
|
|
665
|
+
"task": "fix-bug",
|
|
666
|
+
"goal": "Identify the most likely files and areas where a bug may be located.",
|
|
667
|
+
"project_summary": "CLI en Python (Typer). Entry points: src/cli.py. 4 dependencias.",
|
|
668
|
+
"architecture_summary": null,
|
|
669
|
+
"relevant_files": [
|
|
670
|
+
{ "path": "src/handler.py", "role": "source", "score": 2.0, "reason": "matches 'handler'" },
|
|
671
|
+
{ "path": "src/cli.py", "role": "entrypoint", "score": 3.0, "reason": "entry point" }
|
|
672
|
+
],
|
|
673
|
+
"suspected_areas": ["src/handler.py (2 annotations)", "src/parser.py (1 annotation)"],
|
|
674
|
+
"code_notes_summary": { "total": 5, "by_kind": { "FIXME": 3, "BUG": 2 }, "top_files": ["src/handler.py"] }
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### `--llm-prompt`
|
|
679
|
+
|
|
680
|
+
Add `--llm-prompt` to include a ready-to-use prompt that you can paste directly into any LLM:
|
|
681
|
+
|
|
682
|
+
```bash
|
|
683
|
+
sourcecode . prepare-context explain --llm-prompt | jq -r '.llm_prompt'
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
The prompt is task-specific and includes the project context, relevant files, and concrete instructions for the LLM.
|
|
687
|
+
|
|
688
|
+
### Task auto-selection with `--agent`
|
|
689
|
+
|
|
690
|
+
For zero-config usage, `--agent` on the main command automatically selects the right flags for the project type. For task-aware context, use `prepare-context` explicitly with the task that matches your goal.
|
|
691
|
+
|
|
623
692
|
## Development
|
|
624
693
|
|
|
625
694
|
Editable install with development dependencies:
|
|
@@ -81,7 +81,7 @@ Campos:
|
|
|
81
81
|
{
|
|
82
82
|
"schema_version": "1.0",
|
|
83
83
|
"generated_at": "2026-04-07T19:41:05.686277+00:00",
|
|
84
|
-
"sourcecode_version": "0.
|
|
84
|
+
"sourcecode_version": "0.22.0",
|
|
85
85
|
"analyzed_path": "/abs/path/to/project"
|
|
86
86
|
}
|
|
87
87
|
```
|
|
@@ -645,7 +645,7 @@ Ejemplo de salida para un monorepo con web Node.js y API Python con `--dependenc
|
|
|
645
645
|
"metadata": {
|
|
646
646
|
"schema_version": "1.0",
|
|
647
647
|
"generated_at": "2026-04-07T19:41:05.686277+00:00",
|
|
648
|
-
"sourcecode_version": "0.
|
|
648
|
+
"sourcecode_version": "0.22.0",
|
|
649
649
|
"analyzed_path": "/abs/path/to/project"
|
|
650
650
|
},
|
|
651
651
|
"file_tree": {
|
|
@@ -14,6 +14,20 @@ _NODE_EXTENSIONS = {".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"}
|
|
|
14
14
|
_GO_EXTENSIONS = {".go"}
|
|
15
15
|
_JAVA_EXTENSIONS = {".java", ".kt", ".scala"}
|
|
16
16
|
|
|
17
|
+
_CORE_DETECTION_MODULES = {"scanner", "detectors", "classifier", "workspace"}
|
|
18
|
+
|
|
19
|
+
_OPTIONAL_LABEL_MAP: dict[str, str] = {
|
|
20
|
+
"DependencyAnalyzer": "dependencias",
|
|
21
|
+
"GraphAnalyzer": "grafo de módulos",
|
|
22
|
+
"DocAnalyzer": "docs y docstrings",
|
|
23
|
+
"MetricsAnalyzer": "métricas de calidad",
|
|
24
|
+
"SemanticAnalyzer": "semántica y call graph",
|
|
25
|
+
"ArchitectureAnalyzer": "inferencia arquitectónica",
|
|
26
|
+
"GitAnalyzer": "contexto git",
|
|
27
|
+
"EnvAnalyzer": "variables de entorno",
|
|
28
|
+
"CodeNotesAnalyzer": "anotaciones de código",
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
|
|
18
32
|
class ArchitectureSummarizer:
|
|
19
33
|
"""Construye un resumen arquitectonico estatico de 3-5 lineas."""
|
|
@@ -51,18 +65,25 @@ class ArchitectureSummarizer:
|
|
|
51
65
|
if content is None:
|
|
52
66
|
return f"Entry point principal: {entry_point.path}. Arquitectura no inferida con suficiente evidencia estatica."
|
|
53
67
|
|
|
54
|
-
lines = [self._describe_entry_point(entry_point, sm.project_type)]
|
|
55
68
|
suffix = Path(entry_point.path).suffix
|
|
56
69
|
if suffix in _PYTHON_EXTENSIONS:
|
|
57
|
-
|
|
70
|
+
lang_lines = self._summarize_python_entry(entry_point.path, content)
|
|
58
71
|
elif suffix in _NODE_EXTENSIONS:
|
|
59
|
-
|
|
72
|
+
lang_lines = self._summarize_node_entry(entry_point.path, content)
|
|
60
73
|
elif suffix in _GO_EXTENSIONS:
|
|
61
|
-
|
|
74
|
+
lang_lines = self._summarize_go_entry(entry_point.path, content)
|
|
62
75
|
elif suffix in _JAVA_EXTENSIONS:
|
|
63
|
-
|
|
76
|
+
lang_lines = self._summarize_java_entry(entry_point.path, content, sm.stacks)
|
|
64
77
|
else:
|
|
65
|
-
|
|
78
|
+
lang_lines = []
|
|
79
|
+
|
|
80
|
+
if lang_lines:
|
|
81
|
+
# Product-level description available — no need for internal "Entry point: ..." header
|
|
82
|
+
lines = lang_lines
|
|
83
|
+
else:
|
|
84
|
+
lines = [self._describe_entry_point(entry_point, sm.project_type)]
|
|
85
|
+
if not lang_lines:
|
|
86
|
+
lines.append("Orquesta modulos internos no detallados por el analisis estatico disponible.")
|
|
66
87
|
|
|
67
88
|
unique_lines: list[str] = []
|
|
68
89
|
seen: set[str] = set()
|
|
@@ -78,11 +99,11 @@ class ArchitectureSummarizer:
|
|
|
78
99
|
try:
|
|
79
100
|
tree = ast.parse(content)
|
|
80
101
|
except SyntaxError:
|
|
81
|
-
return [
|
|
102
|
+
return []
|
|
82
103
|
|
|
83
104
|
package_prefix = self._python_package_prefix(path)
|
|
84
105
|
imported_modules: list[str] = []
|
|
85
|
-
|
|
106
|
+
optional_class_names: list[str] = []
|
|
86
107
|
uses_serializer = False
|
|
87
108
|
uses_redactor = False
|
|
88
109
|
|
|
@@ -90,30 +111,58 @@ class ArchitectureSummarizer:
|
|
|
90
111
|
if isinstance(node, ast.ImportFrom) and isinstance(node.module, str):
|
|
91
112
|
if package_prefix and node.module.startswith(package_prefix):
|
|
92
113
|
imported_modules.append(node.module.rsplit(".", 1)[-1])
|
|
93
|
-
if node.module
|
|
114
|
+
if node.module.endswith(".serializer") or "serializer" in node.module:
|
|
94
115
|
uses_serializer = True
|
|
95
|
-
if node.module
|
|
116
|
+
if node.module.endswith(".redactor") or "redactor" in node.module:
|
|
96
117
|
uses_redactor = True
|
|
97
118
|
elif isinstance(node, ast.Assign):
|
|
98
|
-
|
|
99
|
-
if
|
|
100
|
-
|
|
119
|
+
cls_name = self._extract_optional_analyzer_class_name(node)
|
|
120
|
+
if cls_name:
|
|
121
|
+
optional_class_names.append(cls_name)
|
|
101
122
|
|
|
123
|
+
module_set = set(imported_modules)
|
|
102
124
|
lines: list[str] = []
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
lines.append(
|
|
125
|
+
|
|
126
|
+
# Detection line — infer from core modules present
|
|
127
|
+
has_core = bool(module_set & _CORE_DETECTION_MODULES)
|
|
128
|
+
if has_core:
|
|
129
|
+
lines.append("Analiza el árbol del repositorio y detecta stack, entrypoints y tipo de proyecto.")
|
|
130
|
+
elif module_set:
|
|
131
|
+
lines.append("Analiza el proyecto y produce información estructurada.")
|
|
132
|
+
|
|
133
|
+
# Output line
|
|
108
134
|
if uses_serializer:
|
|
109
|
-
|
|
135
|
+
out = "Produce un SourceMap serializable en JSON/YAML"
|
|
110
136
|
if uses_redactor:
|
|
111
|
-
|
|
112
|
-
lines.append(
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
out += " con redacción de secretos"
|
|
138
|
+
lines.append(out + ".")
|
|
139
|
+
|
|
140
|
+
# Optional capabilities line
|
|
141
|
+
opt_labels = [
|
|
142
|
+
_OPTIONAL_LABEL_MAP[cls]
|
|
143
|
+
for cls in optional_class_names
|
|
144
|
+
if cls in _OPTIONAL_LABEL_MAP
|
|
145
|
+
]
|
|
146
|
+
if opt_labels:
|
|
147
|
+
if len(opt_labels) > 1:
|
|
148
|
+
joined = ", ".join(opt_labels[:-1]) + " y " + opt_labels[-1]
|
|
149
|
+
else:
|
|
150
|
+
joined = opt_labels[0]
|
|
151
|
+
lines.append(f"Opcionalmente añade {joined}.")
|
|
152
|
+
|
|
115
153
|
return lines
|
|
116
154
|
|
|
155
|
+
def _extract_optional_analyzer_class_name(self, node: ast.Assign) -> str | None:
|
|
156
|
+
value = node.value
|
|
157
|
+
if not isinstance(value, ast.IfExp):
|
|
158
|
+
return None
|
|
159
|
+
if not isinstance(value.body, ast.Call):
|
|
160
|
+
return None
|
|
161
|
+
if not isinstance(value.body.func, ast.Name):
|
|
162
|
+
return None
|
|
163
|
+
cls_name = value.body.func.id
|
|
164
|
+
return cls_name if cls_name.endswith("Analyzer") else None
|
|
165
|
+
|
|
117
166
|
def _summarize_node_entry(self, path: str, content: str) -> list[str]:
|
|
118
167
|
imports = re.findall(r"""from\s+['"](\.?\.?/[^'"]+)['"]|require\(['"](\.?\.?/[^'"]+)['"]\)""", content)
|
|
119
168
|
modules = [item for pair in imports for item in pair if item]
|
|
@@ -128,7 +128,9 @@ class TypeClassifier:
|
|
|
128
128
|
has_api = False
|
|
129
129
|
for stack in stacks:
|
|
130
130
|
frameworks = {framework.name for framework in stack.frameworks}
|
|
131
|
-
if frameworks & _WEB_FRAMEWORKS
|
|
131
|
+
if frameworks & _WEB_FRAMEWORKS:
|
|
132
|
+
has_web = True
|
|
133
|
+
elif stack.stack == "nodejs" and stack.detection_method != "heuristic":
|
|
132
134
|
has_web = True
|
|
133
135
|
if frameworks & _API_FRAMEWORKS or stack.stack in _API_STACKS:
|
|
134
136
|
has_api = True
|
|
@@ -78,7 +78,12 @@ def main(
|
|
|
78
78
|
no_tree: bool = typer.Option(
|
|
79
79
|
False,
|
|
80
80
|
"--no-tree",
|
|
81
|
-
help="Suprimir file_tree y file_paths del output (
|
|
81
|
+
help="Suprimir file_tree y file_paths del output (ahora deprecado: el arbol ya no se incluye por defecto)",
|
|
82
|
+
),
|
|
83
|
+
tree: bool = typer.Option(
|
|
84
|
+
False,
|
|
85
|
+
"--tree",
|
|
86
|
+
help="Incluir file_tree completo y file_paths en el output (capa deep-dive)",
|
|
82
87
|
),
|
|
83
88
|
no_redact: bool = typer.Option(
|
|
84
89
|
False,
|
|
@@ -114,7 +119,7 @@ def main(
|
|
|
114
119
|
full_metrics: bool = typer.Option(
|
|
115
120
|
False,
|
|
116
121
|
"--full-metrics",
|
|
117
|
-
help="
|
|
122
|
+
help="Auditoria tecnica: LOC, simbolos, complejidad ciclomatica y cobertura por fichero. No incluido en --agent (uso: CI, code review, no context principal para agentes IA)",
|
|
118
123
|
),
|
|
119
124
|
semantics: bool = typer.Option(
|
|
120
125
|
False,
|
|
@@ -159,10 +164,14 @@ def main(
|
|
|
159
164
|
agent: bool = typer.Option(
|
|
160
165
|
False,
|
|
161
166
|
"--agent",
|
|
162
|
-
help="Modo agente:
|
|
167
|
+
help="Modo agente: output estructurado y sin ruido para consumo por IA. Incluye identidad, entrypoints, arquitectura, dependencias clave, señales operacionales y gaps. Sin arbol de ficheros ni secciones vacias.",
|
|
163
168
|
),
|
|
164
169
|
) -> None:
|
|
165
170
|
"""Genera un mapa de contexto estructurado del proyecto en formato JSON o YAML."""
|
|
171
|
+
# When a subcommand (e.g. prepare-context) is invoked, skip the main analysis.
|
|
172
|
+
if ctx.invoked_subcommand is not None:
|
|
173
|
+
return
|
|
174
|
+
|
|
166
175
|
# Validar formato
|
|
167
176
|
if format not in FORMAT_CHOICES:
|
|
168
177
|
typer.echo(
|
|
@@ -212,7 +221,7 @@ def main(
|
|
|
212
221
|
SourceMap,
|
|
213
222
|
StackDetection,
|
|
214
223
|
)
|
|
215
|
-
from sourcecode.serializer import compact_view, normalize_source_map, validate_cross_analyzer_consistency, validate_source_map, write_output
|
|
224
|
+
from sourcecode.serializer import agent_view, compact_view, normalize_source_map, standard_view, validate_cross_analyzer_consistency, validate_source_map, write_output
|
|
216
225
|
from sourcecode.workspace import WorkspaceAnalyzer
|
|
217
226
|
|
|
218
227
|
# 1. Escanear el directorio (SCAN-01 a SCAN-05)
|
|
@@ -231,15 +240,13 @@ def main(
|
|
|
231
240
|
_java_min_depth = 8
|
|
232
241
|
effective_depth = max(depth, _java_min_depth) if _is_java and depth < _java_min_depth else depth
|
|
233
242
|
|
|
234
|
-
# --agent:
|
|
243
|
+
# --agent: enable signal analyzers; output via agent_view (not compact)
|
|
235
244
|
if agent:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
_agent_flags.append("--no-tree")
|
|
242
|
-
typer.echo(f"[agent] {' '.join(_agent_flags)}", err=True)
|
|
245
|
+
dependencies = True
|
|
246
|
+
env_map = True
|
|
247
|
+
code_notes = True
|
|
248
|
+
no_tree = True # agents never need the raw file tree
|
|
249
|
+
typer.echo("[agent] dependencies env-map code-notes (no-tree)", err=True)
|
|
243
250
|
|
|
244
251
|
scanner = FileScanner(target, max_depth=effective_depth)
|
|
245
252
|
raw_tree = scanner.scan_tree()
|
|
@@ -558,16 +565,20 @@ def main(
|
|
|
558
565
|
p.replace("\\", "/") for p in flatten_file_tree(sm.file_tree)
|
|
559
566
|
]
|
|
560
567
|
|
|
561
|
-
# LQN-05: top-15 dependencias directas de manifest/lockfile
|
|
568
|
+
# LQN-05: top-15 dependencias directas de manifest/lockfile, ordenadas por rol
|
|
562
569
|
if dependency_analyzer is not None:
|
|
570
|
+
from sourcecode.dependency_analyzer import _ROLE_PRIORITY
|
|
571
|
+
|
|
563
572
|
primary_ecosystem = sm.stacks[0].stack if sm.stacks else ""
|
|
564
573
|
direct_deps = [
|
|
565
574
|
d for d in sm.dependencies
|
|
566
575
|
if d.scope != "transitive" and d.source in {"manifest", "lockfile"}
|
|
567
576
|
]
|
|
568
577
|
|
|
569
|
-
def _dep_sort_key(d: Any) -> tuple[int, str]:
|
|
570
|
-
|
|
578
|
+
def _dep_sort_key(d: Any) -> tuple[int, int, str]:
|
|
579
|
+
role_order = _ROLE_PRIORITY.get(d.role or "runtime", 5)
|
|
580
|
+
eco_order = 0 if d.ecosystem == primary_ecosystem else 1
|
|
581
|
+
return (role_order, eco_order, d.name.lower())
|
|
571
582
|
|
|
572
583
|
sm.key_dependencies = sorted(direct_deps, key=_dep_sort_key)[:15]
|
|
573
584
|
|
|
@@ -612,24 +623,23 @@ def main(
|
|
|
612
623
|
for _finding in validate_cross_analyzer_consistency(sm, strict=False):
|
|
613
624
|
typer.echo(f"[consistency] {_finding}", err=True)
|
|
614
625
|
|
|
615
|
-
# 4. Serializar
|
|
616
|
-
if
|
|
626
|
+
# 4. Serializar
|
|
627
|
+
if agent:
|
|
628
|
+
data = agent_view(sm)
|
|
629
|
+
if not no_redact:
|
|
630
|
+
data = redact_dict(data)
|
|
631
|
+
content = json.dumps(data, indent=2, ensure_ascii=False)
|
|
632
|
+
elif compact:
|
|
617
633
|
data = compact_view(sm, no_tree=no_tree)
|
|
618
|
-
# Aplicar redaccion sobre el dict del compact view
|
|
619
634
|
if not no_redact:
|
|
620
635
|
data = redact_dict(data)
|
|
621
636
|
content = json.dumps(data, indent=2, ensure_ascii=False)
|
|
622
637
|
else:
|
|
623
|
-
|
|
624
|
-
raw_dict = asdict(sm)
|
|
625
|
-
if no_tree:
|
|
626
|
-
raw_dict.pop("file_tree", None)
|
|
627
|
-
raw_dict.pop("file_paths", None)
|
|
638
|
+
raw_dict = standard_view(sm, include_tree=tree and not no_tree)
|
|
628
639
|
if not no_redact:
|
|
629
640
|
raw_dict = redact_dict(raw_dict)
|
|
630
641
|
|
|
631
642
|
if format == "yaml":
|
|
632
|
-
# Para YAML, serializar el dict directamente con ruamel.yaml
|
|
633
643
|
from io import StringIO
|
|
634
644
|
|
|
635
645
|
from ruamel.yaml import YAML
|
|
@@ -654,31 +664,103 @@ def main(
|
|
|
654
664
|
|
|
655
665
|
@app.command("prepare-context")
|
|
656
666
|
def prepare_context_cmd(
|
|
657
|
-
task: str = typer.Argument(
|
|
658
|
-
|
|
667
|
+
task: Optional[str] = typer.Argument(
|
|
668
|
+
None,
|
|
669
|
+
help="Task: explain | fix-bug | refactor | generate-tests",
|
|
670
|
+
),
|
|
671
|
+
path: Path = typer.Option(
|
|
672
|
+
Path("."),
|
|
673
|
+
"--path", "-p",
|
|
674
|
+
help="Project directory to analyze (default: current directory)",
|
|
675
|
+
),
|
|
676
|
+
llm_prompt: bool = typer.Option(
|
|
677
|
+
False,
|
|
678
|
+
"--llm-prompt",
|
|
679
|
+
help="Append a ready-to-use LLM prompt to the output",
|
|
680
|
+
),
|
|
681
|
+
task_help: bool = typer.Option(
|
|
682
|
+
False,
|
|
683
|
+
"--task-help",
|
|
684
|
+
help="List available tasks with descriptions and exit",
|
|
685
|
+
),
|
|
686
|
+
dry_run: bool = typer.Option(
|
|
687
|
+
False,
|
|
688
|
+
"--dry-run",
|
|
689
|
+
help="Show what would be analyzed without running it",
|
|
690
|
+
),
|
|
659
691
|
) -> None:
|
|
660
|
-
"""
|
|
661
|
-
|
|
692
|
+
"""Prepare task-aware context optimized for LLM reasoning.
|
|
693
|
+
|
|
694
|
+
\b
|
|
695
|
+
Note: PATH must be provided before the subcommand (default: '.'):
|
|
696
|
+
sourcecode . prepare-context explain
|
|
697
|
+
sourcecode . prepare-context fix-bug --path /my/project
|
|
698
|
+
sourcecode . prepare-context generate-tests --llm-prompt
|
|
699
|
+
sourcecode . prepare-context --task-help
|
|
700
|
+
"""
|
|
701
|
+
from sourcecode.prepare_context import TASKS, TaskContextBuilder
|
|
702
|
+
|
|
703
|
+
if task_help:
|
|
704
|
+
typer.echo("Available tasks:\n")
|
|
705
|
+
for name, spec in TASKS.items():
|
|
706
|
+
typer.echo(f" {name:<20} {spec.description}")
|
|
707
|
+
typer.echo(f" {'':20} Output: {spec.output_hint}\n")
|
|
708
|
+
raise typer.Exit()
|
|
662
709
|
|
|
663
|
-
|
|
710
|
+
if task is None:
|
|
711
|
+
typer.echo(
|
|
712
|
+
f"Error: task is required. Available: {', '.join(TASKS)}\n"
|
|
713
|
+
"Use --task-help for descriptions.",
|
|
714
|
+
err=True,
|
|
715
|
+
)
|
|
716
|
+
raise typer.Exit(code=1)
|
|
664
717
|
|
|
665
|
-
|
|
718
|
+
if task not in TASKS:
|
|
719
|
+
typer.echo(
|
|
720
|
+
f"Error: unknown task '{task}'. Available: {', '.join(TASKS)}",
|
|
721
|
+
err=True,
|
|
722
|
+
)
|
|
723
|
+
raise typer.Exit(code=1)
|
|
666
724
|
|
|
725
|
+
target = path.resolve()
|
|
667
726
|
if not target.exists() or not target.is_dir():
|
|
668
727
|
typer.echo(f"Error: '{target}' no es un directorio válido.", err=True)
|
|
669
728
|
raise typer.Exit(code=1)
|
|
670
729
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
"
|
|
676
|
-
"
|
|
677
|
-
|
|
678
|
-
"
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
730
|
+
if dry_run:
|
|
731
|
+
spec = TASKS[task]
|
|
732
|
+
typer.echo(f"task: {task}")
|
|
733
|
+
typer.echo(f"goal: {spec.goal}")
|
|
734
|
+
typer.echo(f"path: {target}")
|
|
735
|
+
typer.echo(f"analyzers: dependencies={'yes' if spec.enable_dependencies else 'no'}"
|
|
736
|
+
f", code_notes={'yes' if spec.enable_code_notes else 'no'}")
|
|
737
|
+
typer.echo(f"output: {spec.output_hint}")
|
|
738
|
+
raise typer.Exit()
|
|
739
|
+
|
|
740
|
+
from dataclasses import asdict
|
|
741
|
+
|
|
742
|
+
builder = TaskContextBuilder(target)
|
|
743
|
+
output = builder.build(task)
|
|
744
|
+
|
|
745
|
+
out: dict[str, Any] = {
|
|
746
|
+
"task": output.task,
|
|
747
|
+
"goal": output.goal,
|
|
748
|
+
"project_summary": output.project_summary,
|
|
749
|
+
"architecture_summary": output.architecture_summary,
|
|
750
|
+
"relevant_files": [asdict(f) for f in output.relevant_files],
|
|
751
|
+
"key_dependencies": output.key_dependencies,
|
|
682
752
|
}
|
|
753
|
+
if output.suspected_areas:
|
|
754
|
+
out["suspected_areas"] = output.suspected_areas
|
|
755
|
+
if output.improvement_opportunities:
|
|
756
|
+
out["improvement_opportunities"] = output.improvement_opportunities
|
|
757
|
+
if output.test_gaps:
|
|
758
|
+
out["test_gaps"] = output.test_gaps
|
|
759
|
+
if output.code_notes_summary:
|
|
760
|
+
out["code_notes_summary"] = output.code_notes_summary
|
|
761
|
+
if output.limitations:
|
|
762
|
+
out["limitations"] = output.limitations
|
|
763
|
+
if llm_prompt:
|
|
764
|
+
out["llm_prompt"] = builder.render_prompt(output)
|
|
683
765
|
|
|
684
766
|
typer.echo(json.dumps(out, indent=2, ensure_ascii=False))
|