sourcecode 0.9.0__tar.gz → 0.10.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.
Files changed (101) hide show
  1. {sourcecode-0.9.0 → sourcecode-0.10.0}/PKG-INFO +7 -6
  2. {sourcecode-0.9.0 → sourcecode-0.10.0}/README.md +6 -5
  3. {sourcecode-0.9.0 → sourcecode-0.10.0}/docs/schema.md +12 -11
  4. {sourcecode-0.9.0 → sourcecode-0.10.0}/pyproject.toml +1 -1
  5. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/__init__.py +1 -1
  6. sourcecode-0.10.0/src/sourcecode/architecture_summary.py +248 -0
  7. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/cli.py +7 -5
  8. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/go.py +7 -1
  9. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/nodejs.py +30 -11
  10. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/python.py +28 -6
  11. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/schema.py +2 -0
  12. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/serializer.py +2 -2
  13. sourcecode-0.10.0/src/sourcecode/summarizer.py +177 -0
  14. sourcecode-0.10.0/tests/test_architecture_summary.py +72 -0
  15. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_detector_go_rust_java.py +2 -0
  16. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_detector_nodejs.py +24 -0
  17. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_detector_python.py +22 -0
  18. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_detectors_base.py +1 -0
  19. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_lqn.py +21 -0
  20. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_schema.py +22 -12
  21. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_summarizer.py +47 -0
  22. sourcecode-0.9.0/src/sourcecode/summarizer.py +0 -68
  23. {sourcecode-0.9.0 → sourcecode-0.10.0}/.gitignore +0 -0
  24. {sourcecode-0.9.0 → sourcecode-0.10.0}/.ruff.toml +0 -0
  25. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/classifier.py +0 -0
  26. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/coverage_parser.py +0 -0
  27. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/dependency_analyzer.py +0 -0
  28. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/__init__.py +0 -0
  29. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/base.py +0 -0
  30. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/dart.py +0 -0
  31. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/dotnet.py +0 -0
  32. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/elixir.py +0 -0
  33. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/heuristic.py +0 -0
  34. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/java.py +0 -0
  35. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  36. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/parsers.py +0 -0
  37. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/php.py +0 -0
  38. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/project.py +0 -0
  39. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/ruby.py +0 -0
  40. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/rust.py +0 -0
  41. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/systems.py +0 -0
  42. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/terraform.py +0 -0
  43. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/detectors/tooling.py +0 -0
  44. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/doc_analyzer.py +0 -0
  45. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/graph_analyzer.py +0 -0
  46. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/metrics_analyzer.py +0 -0
  47. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/redactor.py +0 -0
  48. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/scanner.py +0 -0
  49. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/semantic_analyzer.py +0 -0
  50. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/tree_utils.py +0 -0
  51. {sourcecode-0.9.0 → sourcecode-0.10.0}/src/sourcecode/workspace.py +0 -0
  52. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/__init__.py +0 -0
  53. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/conftest.py +0 -0
  54. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/coverage.xml +0 -0
  55. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  56. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
  57. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  58. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/go_service/go.mod +0 -0
  59. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/jacoco.xml +0 -0
  60. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/lcov.info +0 -0
  61. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  62. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/nextjs_app/package.json +0 -0
  63. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  64. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  65. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  66. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  67. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  68. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  69. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_classifier.py +0 -0
  70. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_cli.py +0 -0
  71. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_coverage_parser.py +0 -0
  72. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_dependency_analyzer_node_python.py +0 -0
  73. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
  74. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_dependency_schema.py +0 -0
  75. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_detector_php_ruby_dart.py +0 -0
  76. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_detector_universal_managed.py +0 -0
  77. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_detector_universal_systems.py +0 -0
  78. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_doc_analyzer_jsdom.py +0 -0
  79. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_doc_analyzer_python.py +0 -0
  80. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_graph_analyzer_polyglot.py +0 -0
  81. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_graph_analyzer_python_node.py +0 -0
  82. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_graph_schema.py +0 -0
  83. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration.py +0 -0
  84. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_dependencies.py +0 -0
  85. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_detection.py +0 -0
  86. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_docs.py +0 -0
  87. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_graph_modules.py +0 -0
  88. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_metrics.py +0 -0
  89. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_multistack.py +0 -0
  90. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_semantics.py +0 -0
  91. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_integration_universal.py +0 -0
  92. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_metrics_analyzer.py +0 -0
  93. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_packaging.py +0 -0
  94. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_real_projects.py +0 -0
  95. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_redactor.py +0 -0
  96. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_scanner.py +0 -0
  97. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_semantic_analyzer_node.py +0 -0
  98. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_semantic_analyzer_python.py +0 -0
  99. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_semantic_import_resolution.py +0 -0
  100. {sourcecode-0.9.0 → sourcecode-0.10.0}/tests/test_semantic_schema.py +0 -0
  101. {sourcecode-0.9.0 → sourcecode-0.10.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.9.0
3
+ Version: 0.10.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
@@ -94,7 +94,7 @@ sourcecode --version
94
94
  | `PATH` | `.` | Directory to analyze. |
95
95
  | `--format json\|yaml` | `json` | Output format. |
96
96
  | `--output PATH` | stdout | Write to a file instead of stdout. |
97
- | `--compact` | off | Reduced output (~500-700 tokens): `schema_version`, `project_type`, `project_summary`, `stacks`, `entry_points`, `file_paths`, `file_tree_depth1`, and `dependency_summary` when available. |
97
+ | `--compact` | off | Reduced output (~500-700 tokens): `schema_version`, `project_type`, `project_summary`, `architecture_summary`, `stacks`, `entry_points`, `file_tree_depth1`, and `dependency_summary` when available. |
98
98
  | `--dependencies` | off | Include direct dependencies, resolved versions, and transitive relationships when lockfiles make that possible. Also populates `key_dependencies`. |
99
99
  | `--graph-modules` | off | Include a structural module graph with imports and simple relations. |
100
100
  | `--graph-detail high\|medium\|full` | `high` | Graph detail level: summarized (high), balanced (medium), or full-fidelity (full). |
@@ -118,6 +118,7 @@ The full schema (`SourceMap`) includes the following fields:
118
118
  | `file_tree` | object | Repository tree where `null` represents a file and `{}` represents a directory. |
119
119
  | `file_paths` | array | Flat list of all project paths derived from `file_tree`, with forward-slash separators. Always present; respects `--depth`. |
120
120
  | `project_summary` | string\|null | Deterministic natural-language description of the project generated from detected stacks, entry points, and dependencies. Present when stacks are detected. |
121
+ | `architecture_summary` | string\|null | Static summary of the main execution flow, orchestrated modules, and output produced by the project. Present when enough structural evidence is available. |
121
122
  | `stacks` | array | Stack detections with confidence, frameworks, manifests, `primary`, `root`, `workspace`, and `signals`. |
122
123
  | `project_type` | string\|null | Overall project classification. |
123
124
  | `entry_points` | array | Detected entry points by stack. |
@@ -155,6 +156,7 @@ Real output from a Python FastAPI project:
155
156
  "schema_version": "1.0",
156
157
  "project_type": "api",
157
158
  "project_summary": "API en Python (FastAPI). Entry points: src/main.py. 12 dependencias (python).",
159
+ "architecture_summary": null,
158
160
  "stacks": [
159
161
  {
160
162
  "stack": "python",
@@ -179,7 +181,6 @@ Real output from a Python FastAPI project:
179
181
  "source": "manifest"
180
182
  }
181
183
  ],
182
- "file_paths": ["pyproject.toml", "src/main.py", "src/routes.py", "tests/test_main.py"],
183
184
  "file_tree_depth1": {
184
185
  "pyproject.toml": null,
185
186
  "src": {},
@@ -371,7 +372,7 @@ Different modes optimize for different tradeoffs between context size and depth
371
372
 
372
373
  Best for: initial orientation, deciding what to explore next, fast handoffs between agents.
373
374
 
374
- Includes: `project_summary` (instant project description), `stacks`, `entry_points`, `file_paths` (flat path list for easy grep/reasoning), `file_tree_depth1`, and `dependency_summary` when `--dependencies` was also requested.
375
+ Includes: `project_summary` (instant project description), `architecture_summary` (execution-oriented static summary), `stacks`, `entry_points`, `file_tree_depth1`, and `dependency_summary` when `--dependencies` was also requested.
375
376
 
376
377
  ```bash
377
378
  sourcecode --compact .
@@ -404,9 +405,9 @@ sourcecode --docs --docs-depth full . # include methods
404
405
  - `"Aplicacion web en Node.js (Next.js, React). Entry points: app/page.tsx."`
405
406
  - `"Monorepo con 2 workspaces en Node.js, Python."`
406
407
 
407
- **`file_paths` field**
408
+ **`architecture_summary` field**
408
409
 
409
- `file_paths` is a flat list of all project paths (forward-slash separated). It is easier for LLMs to reason about than the nested `file_tree` dict grep it, count by extension, or identify modules by path pattern without recursive traversal.
410
+ `architecture_summary` is a static 3-5 line summary oriented to execution flow. It answers what the main entry point does, which modules it orchestrates, and what the project produces. In compact mode it replaces the low-signal value that `file_paths` used to occupy.
410
411
 
411
412
  **`key_dependencies` field**
412
413
 
@@ -78,7 +78,7 @@ sourcecode --version
78
78
  | `PATH` | `.` | Directory to analyze. |
79
79
  | `--format json\|yaml` | `json` | Output format. |
80
80
  | `--output PATH` | stdout | Write to a file instead of stdout. |
81
- | `--compact` | off | Reduced output (~500-700 tokens): `schema_version`, `project_type`, `project_summary`, `stacks`, `entry_points`, `file_paths`, `file_tree_depth1`, and `dependency_summary` when available. |
81
+ | `--compact` | off | Reduced output (~500-700 tokens): `schema_version`, `project_type`, `project_summary`, `architecture_summary`, `stacks`, `entry_points`, `file_tree_depth1`, and `dependency_summary` when available. |
82
82
  | `--dependencies` | off | Include direct dependencies, resolved versions, and transitive relationships when lockfiles make that possible. Also populates `key_dependencies`. |
83
83
  | `--graph-modules` | off | Include a structural module graph with imports and simple relations. |
84
84
  | `--graph-detail high\|medium\|full` | `high` | Graph detail level: summarized (high), balanced (medium), or full-fidelity (full). |
@@ -102,6 +102,7 @@ The full schema (`SourceMap`) includes the following fields:
102
102
  | `file_tree` | object | Repository tree where `null` represents a file and `{}` represents a directory. |
103
103
  | `file_paths` | array | Flat list of all project paths derived from `file_tree`, with forward-slash separators. Always present; respects `--depth`. |
104
104
  | `project_summary` | string\|null | Deterministic natural-language description of the project generated from detected stacks, entry points, and dependencies. Present when stacks are detected. |
105
+ | `architecture_summary` | string\|null | Static summary of the main execution flow, orchestrated modules, and output produced by the project. Present when enough structural evidence is available. |
105
106
  | `stacks` | array | Stack detections with confidence, frameworks, manifests, `primary`, `root`, `workspace`, and `signals`. |
106
107
  | `project_type` | string\|null | Overall project classification. |
107
108
  | `entry_points` | array | Detected entry points by stack. |
@@ -139,6 +140,7 @@ Real output from a Python FastAPI project:
139
140
  "schema_version": "1.0",
140
141
  "project_type": "api",
141
142
  "project_summary": "API en Python (FastAPI). Entry points: src/main.py. 12 dependencias (python).",
143
+ "architecture_summary": null,
142
144
  "stacks": [
143
145
  {
144
146
  "stack": "python",
@@ -163,7 +165,6 @@ Real output from a Python FastAPI project:
163
165
  "source": "manifest"
164
166
  }
165
167
  ],
166
- "file_paths": ["pyproject.toml", "src/main.py", "src/routes.py", "tests/test_main.py"],
167
168
  "file_tree_depth1": {
168
169
  "pyproject.toml": null,
169
170
  "src": {},
@@ -355,7 +356,7 @@ Different modes optimize for different tradeoffs between context size and depth
355
356
 
356
357
  Best for: initial orientation, deciding what to explore next, fast handoffs between agents.
357
358
 
358
- Includes: `project_summary` (instant project description), `stacks`, `entry_points`, `file_paths` (flat path list for easy grep/reasoning), `file_tree_depth1`, and `dependency_summary` when `--dependencies` was also requested.
359
+ Includes: `project_summary` (instant project description), `architecture_summary` (execution-oriented static summary), `stacks`, `entry_points`, `file_tree_depth1`, and `dependency_summary` when `--dependencies` was also requested.
359
360
 
360
361
  ```bash
361
362
  sourcecode --compact .
@@ -388,9 +389,9 @@ sourcecode --docs --docs-depth full . # include methods
388
389
  - `"Aplicacion web en Node.js (Next.js, React). Entry points: app/page.tsx."`
389
390
  - `"Monorepo con 2 workspaces en Node.js, Python."`
390
391
 
391
- **`file_paths` field**
392
+ **`architecture_summary` field**
392
393
 
393
- `file_paths` is a flat list of all project paths (forward-slash separated). It is easier for LLMs to reason about than the nested `file_tree` dict grep it, count by extension, or identify modules by path pattern without recursive traversal.
394
+ `architecture_summary` is a static 3-5 line summary oriented to execution flow. It answers what the main entry point does, which modules it orchestrates, and what the project produces. In compact mode it replaces the low-signal value that `file_paths` used to occupy.
394
395
 
395
396
  **`key_dependencies` field**
396
397
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  `sourcecode` serializa un objeto `SourceMap` con schema `1.0`.
5
5
 
6
- Los campos **siempre presentes** son: `metadata`, `file_tree`, `file_paths`, `stacks`, `project_type`, `entry_points` y `project_summary`.
6
+ Los campos **siempre presentes** son: `metadata`, `file_tree`, `file_paths`, `stacks`, `project_type`, `entry_points`, `project_summary` y `architecture_summary`.
7
7
 
8
8
  Los campos **opcionales** dependen de los flags activos en la invocacion:
9
9
 
@@ -28,6 +28,7 @@ Los campos **opcionales** dependen de los flags activos en la invocacion:
28
28
  "project_type": "webapp",
29
29
  "entry_points": [],
30
30
  "project_summary": "Aplicacion web en Nodejs (Next.js). Entry points: app/page.tsx.",
31
+ "architecture_summary": null,
31
32
  "dependencies": [],
32
33
  "dependency_summary": null,
33
34
  "key_dependencies": [],
@@ -47,6 +48,7 @@ Campos:
47
48
  - `project_type`: clasificacion general del proyecto. Siempre presente (puede ser `null`).
48
49
  - `entry_points`: puntos de entrada relevantes. Siempre presente (puede ser lista vacia).
49
50
  - `project_summary`: descripcion en lenguaje natural del proyecto generada deterministicamente. Presente cuando hay stacks detectados; `null` si no (Phase 9).
51
+ - `architecture_summary`: resumen arquitectonico estatico del flujo principal, modulos orquestados y output producido. `null` si no hay evidencia suficiente (Phase 13).
50
52
  - `dependencies`: dependencias detectadas. Solo presente con `--dependencies`; lista vacia por defecto.
51
53
  - `dependency_summary`: resumen del analisis de dependencias. Solo presente con `--dependencies`; `null` por defecto.
52
54
  - `key_dependencies`: top-15 dependencias directas relevantes. Solo presente con `--dependencies`; lista vacia por defecto (Phase 9).
@@ -61,7 +63,7 @@ Campos:
61
63
  {
62
64
  "schema_version": "1.0",
63
65
  "generated_at": "2026-04-07T19:41:05.686277+00:00",
64
- "sourcecode_version": "0.9.0",
66
+ "sourcecode_version": "0.10.0",
65
67
  "analyzed_path": "/abs/path/to/project"
66
68
  }
67
69
  ```
@@ -232,7 +234,8 @@ Cada elemento de `entry_points` sigue este shape:
232
234
  "path": "app/page.tsx",
233
235
  "stack": "nodejs",
234
236
  "kind": "web",
235
- "source": "package.json"
237
+ "source": "package.json",
238
+ "confidence": "high"
236
239
  }
237
240
  ```
238
241
 
@@ -242,6 +245,7 @@ Campos:
242
245
  - `stack`: stack al que pertenece.
243
246
  - `kind`: tipo de entry point, por ejemplo `web`, `api`, `cli` o `entry`.
244
247
  - `source`: origen de la deteccion.
248
+ - `confidence`: confianza del entry point detectado (`high`, `medium` o `low`).
245
249
 
246
250
  ## dependencies
247
251
 
@@ -536,13 +540,9 @@ Con `--compact`, la salida omite `metadata`, el arbol completo, `dependencies`,
536
540
  "schema_version": "1.0",
537
541
  "project_type": "webapp",
538
542
  "project_summary": "Aplicacion web en Nodejs (Next.js, React). Entry points: app/page.tsx.",
543
+ "architecture_summary": null,
539
544
  "stacks": [],
540
545
  "entry_points": [],
541
- "file_paths": [
542
- "package.json",
543
- "app/page.tsx",
544
- "app/layout.tsx"
545
- ],
546
546
  "file_tree_depth1": {
547
547
  "package.json": null,
548
548
  "app": {}
@@ -556,16 +556,17 @@ Campos incluidos en el modo compacto:
556
556
  - `schema_version`: version del schema.
557
557
  - `project_type`: clasificacion general.
558
558
  - `project_summary`: descripcion NL del proyecto. Siempre incluido (Phase 9).
559
+ - `architecture_summary`: resumen arquitectonico estatico del flujo principal. Siempre incluido; puede ser `null` si la evidencia es insuficiente (Phase 13).
559
560
  - `stacks`: stacks detectados serializados.
560
561
  - `entry_points`: entry points serializados.
561
- - `file_paths`: lista plana de todos los paths con separador forward-slash. Siempre incluido (Phase 9).
562
562
  - `file_tree_depth1`: solo el primer nivel del `file_tree`. Se conserva por compatibilidad retroactiva.
563
563
  - `dependency_summary`: resumen de dependencias cuando `--dependencies` esta activo y `dependency_summary.requested == True`; `null` en cualquier otro caso (Phase 9).
564
564
 
565
565
  Campos **excluidos** en modo compacto aunque se combinen con otros flags:
566
566
 
567
567
  - `metadata`
568
- - `file_tree` (sustituido por `file_tree_depth1` y `file_paths`)
568
+ - `file_tree` (sustituido por `file_tree_depth1`)
569
+ - `file_paths`
569
570
  - `dependencies`
570
571
  - `key_dependencies`
571
572
  - `module_graph`
@@ -582,7 +583,7 @@ Ejemplo de salida para un monorepo con web Node.js y API Python con `--dependenc
582
583
  "metadata": {
583
584
  "schema_version": "1.0",
584
585
  "generated_at": "2026-04-07T19:41:05.686277+00:00",
585
- "sourcecode_version": "0.9.0",
586
+ "sourcecode_version": "0.10.0",
586
587
  "analyzed_path": "/abs/path/to/project"
587
588
  },
588
589
  "file_tree": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "0.9.0"
7
+ version = "0.10.0"
8
8
  description = "Genera un mapa de contexto estructurado de proyectos de software para agentes IA"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Genera mapas de contexto estructurado para agentes IA."""
2
2
 
3
- __version__ = "0.9.0"
3
+ __version__ = "0.10.0"
@@ -0,0 +1,248 @@
1
+ """Resumen arquitectonico estatico y compacto para consumo LLM."""
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from sourcecode.schema import EntryPoint, SourceMap, StackDetection
10
+ from sourcecode.tree_utils import flatten_file_tree
11
+
12
+ _TOOLING_PREFIXES = (".claude/", ".vscode/", "bin/")
13
+ _PYTHON_EXTENSIONS = {".py"}
14
+ _NODE_EXTENSIONS = {".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"}
15
+ _GO_EXTENSIONS = {".go"}
16
+
17
+
18
+ class ArchitectureSummarizer:
19
+ """Construye un resumen arquitectonico estatico de 3-5 lineas."""
20
+
21
+ def __init__(self, root: Path) -> None:
22
+ self.root = root
23
+
24
+ def generate(self, sm: SourceMap) -> str | None:
25
+ try:
26
+ return self._build_summary(sm)
27
+ except Exception:
28
+ return None
29
+
30
+ def _build_summary(self, sm: SourceMap) -> str | None:
31
+ file_paths = [
32
+ path for path in flatten_file_tree(sm.file_tree)
33
+ if not self._is_tooling_path(path)
34
+ ]
35
+ if not file_paths:
36
+ return None
37
+
38
+ entry_points = [
39
+ entry for entry in sm.entry_points
40
+ if not self._is_tooling_path(entry.path)
41
+ ]
42
+ if not entry_points:
43
+ fallback = self._infer_fallback_entry_points(file_paths, sm.stacks)
44
+ entry_points = fallback[:1]
45
+
46
+ if not entry_points:
47
+ return "Arquitectura no inferida con suficiente evidencia estatica."
48
+
49
+ entry_point = entry_points[0]
50
+ content = self._read_file(entry_point.path)
51
+ if content is None:
52
+ return f"Entry point principal: {entry_point.path}. Arquitectura no inferida con suficiente evidencia estatica."
53
+
54
+ lines = [self._describe_entry_point(entry_point, sm.project_type)]
55
+ suffix = Path(entry_point.path).suffix
56
+ if suffix in _PYTHON_EXTENSIONS:
57
+ lines.extend(self._summarize_python_entry(entry_point.path, content))
58
+ elif suffix in _NODE_EXTENSIONS:
59
+ lines.extend(self._summarize_node_entry(entry_point.path, content))
60
+ elif suffix in _GO_EXTENSIONS:
61
+ lines.extend(self._summarize_go_entry(entry_point.path, content))
62
+ else:
63
+ lines.append("Orquesta modulos internos no detallados por el analisis estatico disponible.")
64
+
65
+ unique_lines: list[str] = []
66
+ seen: set[str] = set()
67
+ for line in lines:
68
+ line = line.strip()
69
+ if not line or line in seen:
70
+ continue
71
+ seen.add(line)
72
+ unique_lines.append(line)
73
+ return "\n".join(unique_lines[:5]) if unique_lines else None
74
+
75
+ def _summarize_python_entry(self, path: str, content: str) -> list[str]:
76
+ try:
77
+ tree = ast.parse(content)
78
+ except SyntaxError:
79
+ return ["Orquesta modulos Python, pero el entry point no pudo parsearse por completo."]
80
+
81
+ package_prefix = self._python_package_prefix(path)
82
+ imported_modules: list[str] = []
83
+ optional_analyzers: list[str] = []
84
+ uses_serializer = False
85
+ uses_redactor = False
86
+
87
+ for node in ast.walk(tree):
88
+ if isinstance(node, ast.ImportFrom) and isinstance(node.module, str):
89
+ if package_prefix and node.module.startswith(package_prefix):
90
+ imported_modules.append(node.module.rsplit(".", 1)[-1])
91
+ if node.module == "sourcecode.serializer":
92
+ uses_serializer = True
93
+ if node.module == "sourcecode.redactor":
94
+ uses_redactor = True
95
+ elif isinstance(node, ast.Assign):
96
+ optional = self._extract_optional_analyzer(node)
97
+ if optional:
98
+ optional_analyzers.append(optional)
99
+
100
+ lines: list[str] = []
101
+ core_modules = self._format_module_list(imported_modules)
102
+ if core_modules:
103
+ lines.append(f"Orquesta modulos internos: {core_modules}.")
104
+ if optional_analyzers:
105
+ lines.append(f"Activa analisis opcionales: {', '.join(optional_analyzers)}.")
106
+ if uses_serializer:
107
+ output_line = "Produce un SourceMap serializado en JSON/YAML y una vista compacta."
108
+ if uses_redactor:
109
+ output_line = "Produce un SourceMap serializado en JSON/YAML y una vista compacta con redaccion de secretos."
110
+ lines.append(output_line)
111
+ elif not lines:
112
+ lines.append("Orquesta modulos Python internos sin un patron de salida claramente inferible.")
113
+ return lines
114
+
115
+ def _summarize_node_entry(self, path: str, content: str) -> list[str]:
116
+ imports = re.findall(r"""from\s+['"](\.?\.?/[^'"]+)['"]|require\(['"](\.?\.?/[^'"]+)['"]\)""", content)
117
+ modules = [item for pair in imports for item in pair if item]
118
+ lines: list[str] = []
119
+ if modules:
120
+ formatted = self._format_module_list([self._module_label(module) for module in modules])
121
+ if formatted:
122
+ lines.append(f"Orquesta modulos internos: {formatted}.")
123
+ lines.append("Produce la salida principal del entry point JavaScript/TypeScript detectado.")
124
+ return lines
125
+
126
+ def _summarize_go_entry(self, path: str, content: str) -> list[str]:
127
+ imports = re.findall(r'"([^"]+)"', content)
128
+ internal = [module for module in imports if not module.startswith(("fmt", "net/", "os", "context"))]
129
+ lines: list[str] = []
130
+ if internal:
131
+ formatted = self._format_module_list([self._module_label(module) for module in internal])
132
+ if formatted:
133
+ lines.append(f"Orquesta paquetes internos: {formatted}.")
134
+ lines.append("Produce la salida principal del binario Go detectado.")
135
+ return lines
136
+
137
+ def _describe_entry_point(self, entry_point: EntryPoint, project_type: str | None) -> str:
138
+ if entry_point.kind == "cli" or entry_point.path.endswith("cli.py"):
139
+ return f"Entry point principal: {entry_point.path} expone la CLI del proyecto."
140
+ if entry_point.kind == "web":
141
+ return f"Entry point principal: {entry_point.path} arranca la interfaz web."
142
+ if project_type == "api" or entry_point.kind == "server":
143
+ return f"Entry point principal: {entry_point.path} arranca el servicio principal."
144
+ if entry_point.kind == "binary":
145
+ return f"Entry point principal: {entry_point.path} arranca el binario principal."
146
+ return f"Entry point principal: {entry_point.path} coordina el flujo principal del proyecto."
147
+
148
+ def _extract_optional_analyzer(self, node: ast.Assign) -> str | None:
149
+ value = node.value
150
+ if not isinstance(value, ast.IfExp):
151
+ return None
152
+ if not isinstance(value.test, ast.Name):
153
+ return None
154
+ if not isinstance(value.body, ast.Call):
155
+ return None
156
+ if not isinstance(value.body.func, ast.Name):
157
+ return None
158
+ analyzer_name = value.body.func.id
159
+ if not analyzer_name.endswith("Analyzer"):
160
+ return None
161
+ return f"{analyzer_name} (--{value.test.id.replace('_', '-')})"
162
+
163
+ def _infer_fallback_entry_points(
164
+ self, file_paths: list[str], stacks: list[StackDetection]
165
+ ) -> list[EntryPoint]:
166
+ candidates: list[EntryPoint] = []
167
+ stack_name = stacks[0].stack if stacks else "unknown"
168
+ ordered_paths = sorted(file_paths, key=self._fallback_priority)
169
+ for path in ordered_paths:
170
+ if path.endswith(("cli.py", "__main__.py", "main.py")):
171
+ candidates.append(
172
+ EntryPoint(
173
+ path=path,
174
+ stack=stack_name,
175
+ kind="cli",
176
+ source="convention",
177
+ confidence="medium",
178
+ )
179
+ )
180
+ elif path.endswith(("app/page.tsx", "pages/index.js", "server.js")):
181
+ candidates.append(
182
+ EntryPoint(
183
+ path=path,
184
+ stack=stack_name,
185
+ kind="web" if "page" in path or "pages/" in path else "server",
186
+ source="convention",
187
+ confidence="medium",
188
+ )
189
+ )
190
+ elif path.endswith("main.go"):
191
+ candidates.append(
192
+ EntryPoint(
193
+ path=path,
194
+ stack=stack_name,
195
+ kind="binary",
196
+ source="convention",
197
+ confidence="medium",
198
+ )
199
+ )
200
+ return candidates
201
+
202
+ def _fallback_priority(self, path: str) -> tuple[int, int, str]:
203
+ return (
204
+ 0 if "/cli.py" in path or path.endswith("cli.py") else 1,
205
+ 0 if path.startswith("src/") else 1,
206
+ path,
207
+ )
208
+
209
+ def _format_module_list(self, modules: list[str]) -> str:
210
+ normalized = [self._module_label(module) for module in modules]
211
+ filtered = [module for module in normalized if module and not self._is_tooling_path(module)]
212
+ if not filtered:
213
+ return ""
214
+ ordered: list[str] = []
215
+ seen: set[str] = set()
216
+ for module in filtered:
217
+ if module not in seen:
218
+ seen.add(module)
219
+ ordered.append(module)
220
+ return ", ".join(ordered[:8])
221
+
222
+ def _module_label(self, module: str) -> str:
223
+ cleaned = module.strip().strip("./")
224
+ if "/" in cleaned:
225
+ return cleaned.split("/")[-1]
226
+ if "." in cleaned:
227
+ return cleaned.rsplit(".", 1)[-1]
228
+ return cleaned
229
+
230
+ def _python_package_prefix(self, path: str) -> str:
231
+ parts = Path(path).parts
232
+ if len(parts) >= 3 and parts[0] == "src":
233
+ return f"{parts[1]}."
234
+ if len(parts) >= 2:
235
+ return f"{parts[0]}."
236
+ return ""
237
+
238
+ def _read_file(self, relative_path: str) -> str | None:
239
+ try:
240
+ return (self.root / relative_path).read_text(encoding="utf-8", errors="replace")
241
+ except OSError:
242
+ return None
243
+
244
+ def _is_tooling_path(self, path: str | None) -> bool:
245
+ if not path:
246
+ return False
247
+ normalized = path.strip().lstrip("/")
248
+ return normalized.startswith(_TOOLING_PREFIXES)
@@ -473,9 +473,10 @@ def main(
473
473
  semantic_summary=sem_sum,
474
474
  )
475
475
 
476
- # Phase 9: LLM Output Quality — poblar campos derivados
477
- from sourcecode.summarizer import ProjectSummarizer
478
- from sourcecode.tree_utils import flatten_file_tree
476
+ # Phase 9: LLM Output Quality — poblar campos derivados
477
+ from sourcecode.architecture_summary import ArchitectureSummarizer
478
+ from sourcecode.summarizer import ProjectSummarizer
479
+ from sourcecode.tree_utils import flatten_file_tree
479
480
 
480
481
  # LQN-01: lista plana de paths del file_tree con separador forward-slash
481
482
  sm.file_paths = [
@@ -495,8 +496,9 @@ def main(
495
496
 
496
497
  sm.key_dependencies = sorted(direct_deps, key=_dep_sort_key)[:15]
497
498
 
498
- # LQN-02: resumen NL deterministico
499
- sm.project_summary = ProjectSummarizer().generate(sm)
499
+ # LQN-02: resumen NL deterministico
500
+ sm.project_summary = ProjectSummarizer(target).generate(sm)
501
+ sm.architecture_summary = ArchitectureSummarizer(target).generate(sm)
500
502
 
501
503
  # 4. Serializar (con o sin modo compact)
502
504
  if compact:
@@ -39,7 +39,13 @@ class GoDetector(AbstractDetector):
39
39
  ]
40
40
  preferred = [path for path in entry_candidates if path.startswith("cmd/")] or entry_candidates
41
41
  entry_points = [
42
- EntryPoint(path=path, stack="go", kind="binary", source="go.mod")
42
+ EntryPoint(
43
+ path=path,
44
+ stack="go",
45
+ kind="binary",
46
+ source="convention",
47
+ confidence="medium",
48
+ )
43
49
  for path in unique_strings(preferred)
44
50
  ]
45
51
  stack = StackDetection(
@@ -77,20 +77,33 @@ class NodejsDetector(AbstractDetector):
77
77
  def _collect_entry_points(
78
78
  self, context: DetectionContext, package_json: dict[str, Any]
79
79
  ) -> list[EntryPoint]:
80
- candidate_paths: list[str] = []
80
+ entry_points: list[EntryPoint] = []
81
+ seen: set[str] = set()
81
82
  main = package_json.get("main")
82
83
  if isinstance(main, str) and main.strip():
83
- candidate_paths.append(main.strip())
84
+ path = main.strip()
85
+ if path_exists_in_tree(context.file_tree, path):
86
+ seen.add(path)
87
+ entry_points.append(
88
+ EntryPoint(
89
+ path=path,
90
+ stack="nodejs",
91
+ kind="server",
92
+ source="package.json",
93
+ confidence="high",
94
+ )
95
+ )
84
96
 
97
+ convention_candidates: list[str] = []
85
98
  bin_field = package_json.get("bin")
86
99
  if isinstance(bin_field, str) and bin_field.strip():
87
- candidate_paths.append(bin_field.strip())
100
+ convention_candidates.append(bin_field.strip())
88
101
  elif isinstance(bin_field, dict):
89
- candidate_paths.extend(
102
+ convention_candidates.extend(
90
103
  str(value).strip() for value in bin_field.values() if isinstance(value, str) and value.strip()
91
104
  )
92
105
 
93
- candidate_paths.extend(
106
+ convention_candidates.extend(
94
107
  [
95
108
  "server.js",
96
109
  "src/index.js",
@@ -103,11 +116,17 @@ class NodejsDetector(AbstractDetector):
103
116
  ]
104
117
  )
105
118
 
106
- entry_points: list[EntryPoint] = []
107
- for path in unique_strings(candidate_paths):
108
- if path_exists_in_tree(context.file_tree, path):
109
- kind = "web" if path.startswith(("app/", "pages/")) else "server"
110
- entry_points.append(
111
- EntryPoint(path=path, stack="nodejs", kind=kind, source="package.json")
119
+ for path in unique_strings(convention_candidates):
120
+ if path in seen or not path_exists_in_tree(context.file_tree, path):
121
+ continue
122
+ kind = "web" if path.startswith(("app/", "pages/")) else "server"
123
+ entry_points.append(
124
+ EntryPoint(
125
+ path=path,
126
+ stack="nodejs",
127
+ kind=kind,
128
+ source="convention",
129
+ confidence="medium",
112
130
  )
131
+ )
113
132
  return entry_points
@@ -132,7 +132,7 @@ class PythonDetector(AbstractDetector):
132
132
  return {match.lower() for match in matches}
133
133
 
134
134
  def _collect_entry_points(self, context: DetectionContext) -> list[EntryPoint]:
135
- candidates: list[str] = []
135
+ declared_candidates: list[str] = []
136
136
  pyproject = load_toml_file(context.root / "pyproject.toml")
137
137
  if pyproject:
138
138
  project = pyproject.get("project", {})
@@ -142,16 +142,38 @@ class PythonDetector(AbstractDetector):
142
142
  for value in scripts.values():
143
143
  if isinstance(value, str) and ":" in value:
144
144
  module, _callable = value.split(":", 1)
145
- candidates.append(module.replace(".", "/") + ".py")
145
+ declared_candidates.append(module.replace(".", "/") + ".py")
146
146
 
147
- candidates.extend(["__main__.py", "main.py", "app.py", "manage.py", "src/main.py"])
148
147
  entry_points: list[EntryPoint] = []
149
- for path in unique_strings(candidates):
148
+ declared = set()
149
+ for path in unique_strings(declared_candidates):
150
150
  if path_exists_in_tree(context.file_tree, path):
151
- kind = "cli" if path.endswith(("__main__.py", "main.py")) else "app"
151
+ declared.add(path)
152
+ kind = "cli" if path.endswith(("__main__.py", "main.py", "cli.py")) else "app"
152
153
  entry_points.append(
153
- EntryPoint(path=path, stack="python", kind=kind, source="manifest")
154
+ EntryPoint(
155
+ path=path,
156
+ stack="python",
157
+ kind=kind,
158
+ source="pyproject.toml",
159
+ confidence="high",
160
+ )
154
161
  )
162
+
163
+ convention_candidates = ["cli.py", "__main__.py", "main.py", "app.py", "manage.py", "src/main.py"]
164
+ for path in unique_strings(convention_candidates):
165
+ if path in declared or not path_exists_in_tree(context.file_tree, path):
166
+ continue
167
+ kind = "cli" if path.endswith(("__main__.py", "main.py", "cli.py")) else "app"
168
+ entry_points.append(
169
+ EntryPoint(
170
+ path=path,
171
+ stack="python",
172
+ kind=kind,
173
+ source="convention",
174
+ confidence="medium",
175
+ )
176
+ )
155
177
  return entry_points
156
178
 
157
179
  def _detect_package_manager(
@@ -66,6 +66,7 @@ class EntryPoint:
66
66
  stack: str
67
67
  kind: str = "entry"
68
68
  source: str = "manifest"
69
+ confidence: Literal["high", "medium", "low"] = "high"
69
70
 
70
71
 
71
72
  @dataclass
@@ -322,6 +323,7 @@ class SourceMap:
322
323
  # Phase 9: LLM Output Quality
323
324
  file_paths: list[str] = field(default_factory=list)
324
325
  project_summary: Optional[str] = None
326
+ architecture_summary: Optional[str] = None
325
327
  key_dependencies: list[DependencyRecord] = field(default_factory=list)
326
328
  # Phase 10: Code Quality Metrics
327
329
  file_metrics: list[FileMetrics] = field(default_factory=list)