sourcecode 0.30.0__tar.gz → 0.32.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.30.0 → sourcecode-0.32.0}/PKG-INFO +1 -1
- {sourcecode-0.30.0 → sourcecode-0.32.0}/pyproject.toml +1 -1
- sourcecode-0.32.0/src/sourcecode/__init__.py +3 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/architecture_analyzer.py +9 -5
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/cli.py +207 -69
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/confidence_analyzer.py +5 -5
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/nodejs.py +11 -3
- sourcecode-0.32.0/src/sourcecode/file_classifier.py +215 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/prepare_context.py +12 -7
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/schema.py +61 -56
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/serializer.py +799 -582
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/summarizer.py +50 -31
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_architecture_analyzer.py +3 -2
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_detector_nodejs.py +2 -2
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_summarizer.py +4 -4
- sourcecode-0.30.0/src/sourcecode/__init__.py +0 -3
- {sourcecode-0.30.0 → sourcecode-0.32.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/.gitignore +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/.ruff.toml +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/CONTRIBUTING.md +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/LICENSE +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/README.md +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/SECURITY.md +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/docs/privacy.md +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/docs/schema.md +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/raw +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/__init__.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/conftest.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/lcov.info +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_architecture_summary.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_classifier.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_cli.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_coverage_parser.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_cross_consistency.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_dependency_schema.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_detector_python.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_detectors_base.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_graph_schema.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_detection.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_docs.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_lqn.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_metrics.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_multistack.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_semantics.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_integration_universal.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_packaging.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_real_projects.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_redactor.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_scanner.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_schema.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_schema_normalization.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_semantic_schema.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_telemetry.py +0 -0
- {sourcecode-0.30.0 → sourcecode-0.32.0}/tests/test_workspace_analyzer.py +0 -0
|
@@ -215,18 +215,22 @@ class ArchitectureAnalyzer:
|
|
|
215
215
|
if pattern not in (None, "unknown", "flat"):
|
|
216
216
|
if all_layers_weak:
|
|
217
217
|
# Layers came from file-naming heuristic only, not directory structure
|
|
218
|
-
confidence = "
|
|
218
|
+
confidence = "low"
|
|
219
219
|
limitations.append(
|
|
220
|
-
"
|
|
220
|
+
"Low confidence inference: pattern inferred from filenames only, without import graph confirmation"
|
|
221
221
|
)
|
|
222
222
|
else:
|
|
223
|
-
confidence = "
|
|
223
|
+
confidence = "medium" if len(strong_domains) >= 3 else "low"
|
|
224
|
+
if graph is None:
|
|
225
|
+
limitations.append(
|
|
226
|
+
"Pattern not confirmed by module import graph; run with --graph-modules for structural validation"
|
|
227
|
+
)
|
|
224
228
|
elif len(strong_domains) >= 1:
|
|
225
229
|
confidence = "medium"
|
|
226
230
|
else:
|
|
227
231
|
confidence = "low"
|
|
228
232
|
|
|
229
|
-
method = "graph+
|
|
233
|
+
method = "graph+structure" if graph is not None else "filesystem_inference"
|
|
230
234
|
|
|
231
235
|
return ArchitectureAnalysis(
|
|
232
236
|
requested=True,
|
|
@@ -339,7 +343,7 @@ class ArchitectureAnalyzer:
|
|
|
339
343
|
best_matched = matched
|
|
340
344
|
|
|
341
345
|
if best_score >= 2:
|
|
342
|
-
layer_confidence: Literal["high", "medium", "low"] = "
|
|
346
|
+
layer_confidence: Literal["high", "medium", "low"] = "medium" if best_score >= 3 else "low"
|
|
343
347
|
layers: list[ArchitectureLayer] = []
|
|
344
348
|
for layer_key, matched_dirs in best_matched.items():
|
|
345
349
|
matched_files = [
|
|
@@ -6,10 +6,10 @@ import time
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any, Optional, cast
|
|
8
8
|
|
|
9
|
-
import typer
|
|
10
|
-
|
|
11
|
-
from sourcecode import __version__
|
|
12
|
-
from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from sourcecode import __version__
|
|
12
|
+
from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# ---------------------------------------------------------------------------
|
|
@@ -118,11 +118,11 @@ def _check_pipeline_coherence(sm: "SourceMap") -> list[str]: # type: ignore[nam
|
|
|
118
118
|
)
|
|
119
119
|
|
|
120
120
|
# overall:high requires at least one production entry point
|
|
121
|
-
if cs.overall == "high":
|
|
122
|
-
prod_eps = [
|
|
123
|
-
ep for ep in sm.entry_points
|
|
124
|
-
if is_production_entry_point(ep)
|
|
125
|
-
]
|
|
121
|
+
if cs.overall == "high":
|
|
122
|
+
prod_eps = [
|
|
123
|
+
ep for ep in sm.entry_points
|
|
124
|
+
if is_production_entry_point(ep)
|
|
125
|
+
]
|
|
126
126
|
if not prod_eps and sm.entry_points:
|
|
127
127
|
issues.append(
|
|
128
128
|
"[coherence] overall=high but no production entry points exist — "
|
|
@@ -135,7 +135,7 @@ def _check_pipeline_coherence(sm: "SourceMap") -> list[str]: # type: ignore[nam
|
|
|
135
135
|
"[coherence] entry_point_confidence=high but entry_points is empty"
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
-
return issues
|
|
138
|
+
return issues
|
|
139
139
|
|
|
140
140
|
_HELP = """\
|
|
141
141
|
Deterministic codebase context for AI coding agents.
|
|
@@ -323,61 +323,79 @@ def main(
|
|
|
323
323
|
"json",
|
|
324
324
|
"--format",
|
|
325
325
|
"-f",
|
|
326
|
-
help="
|
|
326
|
+
help="Output format: json (default) or yaml. Both carry identical data — yaml is more human-readable, json is preferred for agent pipelines.",
|
|
327
327
|
show_default=True,
|
|
328
328
|
),
|
|
329
329
|
output: Optional[Path] = typer.Option(
|
|
330
330
|
None,
|
|
331
331
|
"--output",
|
|
332
332
|
"-o",
|
|
333
|
-
help="
|
|
333
|
+
help="Write output to a file instead of stdout. Useful for storing analysis snapshots or piping to downstream tools.",
|
|
334
334
|
),
|
|
335
335
|
compact: bool = typer.Option(
|
|
336
336
|
False,
|
|
337
337
|
"--compact",
|
|
338
|
-
help=
|
|
338
|
+
help=(
|
|
339
|
+
"Compact output (~600–800 tokens): project type, stacks, production entry points, "
|
|
340
|
+
"dependency summary, confidence summary, and analysis gaps. "
|
|
341
|
+
"Omits file tree, raw dependency lists, docs, and module graph. "
|
|
342
|
+
"Designed for agent context windows. Automatically enables --dependencies, --env-map, and --code-notes."
|
|
343
|
+
),
|
|
339
344
|
),
|
|
340
345
|
dependencies: bool = typer.Option(
|
|
341
346
|
False,
|
|
342
347
|
"--dependencies",
|
|
343
|
-
help=
|
|
348
|
+
help=(
|
|
349
|
+
"Analyze direct and transitive dependencies. Reads manifests (pyproject.toml, package.json, go.mod, etc.) "
|
|
350
|
+
"and lockfiles when available. Adds dependency_summary and key_dependencies to output."
|
|
351
|
+
),
|
|
344
352
|
),
|
|
345
353
|
graph_modules: bool = typer.Option(
|
|
346
354
|
False,
|
|
347
355
|
"--graph-modules",
|
|
348
|
-
help=
|
|
356
|
+
help=(
|
|
357
|
+
"Include a structural module graph: nodes (files/symbols) and edges (imports, calls, contains). "
|
|
358
|
+
"Useful for understanding coupling and call flows. Adds module_graph to output. "
|
|
359
|
+
"Combine with --graph-detail and --graph-edges to control scope."
|
|
360
|
+
),
|
|
349
361
|
),
|
|
350
362
|
graph_detail: str = typer.Option(
|
|
351
363
|
"high",
|
|
352
364
|
"--graph-detail",
|
|
353
|
-
help="
|
|
365
|
+
help="Detail level for --graph-modules: high (top modules by importance), medium (filtered by relevance), full (all nodes and edges). Default: high.",
|
|
354
366
|
show_default=True,
|
|
355
367
|
),
|
|
356
368
|
max_nodes: Optional[int] = typer.Option(
|
|
357
369
|
None,
|
|
358
370
|
"--max-nodes",
|
|
359
|
-
help="
|
|
371
|
+
help="Maximum number of nodes in --graph-modules output when using high or medium detail. Prevents oversized graphs in large codebases.",
|
|
360
372
|
min=1,
|
|
361
373
|
),
|
|
362
374
|
graph_edges: Optional[str] = typer.Option(
|
|
363
375
|
None,
|
|
364
376
|
"--graph-edges",
|
|
365
|
-
help="
|
|
377
|
+
help="Edge types for --graph-modules, comma-separated: imports,calls,contains,extends. Default: all available. Example: --graph-edges imports,calls",
|
|
366
378
|
),
|
|
367
379
|
no_tree: bool = typer.Option(
|
|
368
380
|
False,
|
|
369
381
|
"--no-tree",
|
|
370
|
-
help="
|
|
382
|
+
help="(Deprecated) Previously suppressed file_tree. The file tree is excluded by default — this flag is now a no-op. Use --tree to include the file tree.",
|
|
371
383
|
),
|
|
372
384
|
tree: bool = typer.Option(
|
|
373
385
|
False,
|
|
374
386
|
"--tree",
|
|
375
|
-
help=
|
|
387
|
+
help=(
|
|
388
|
+
"Include the full file_tree and flat file_paths list in output (deep-dive layer). "
|
|
389
|
+
"Adds significant size — use when the agent needs to browse the full file structure."
|
|
390
|
+
),
|
|
376
391
|
),
|
|
377
392
|
no_redact: bool = typer.Option(
|
|
378
393
|
False,
|
|
379
394
|
"--no-redact",
|
|
380
|
-
help=
|
|
395
|
+
help=(
|
|
396
|
+
"Disable automatic secret redaction. By default, potential secrets (API keys, tokens, passwords) "
|
|
397
|
+
"are replaced with [REDACTED]. Use with caution — output may contain sensitive values."
|
|
398
|
+
),
|
|
381
399
|
),
|
|
382
400
|
version: Optional[bool] = typer.Option(
|
|
383
401
|
None,
|
|
@@ -385,80 +403,109 @@ def main(
|
|
|
385
403
|
"-v",
|
|
386
404
|
callback=version_callback,
|
|
387
405
|
is_eager=True,
|
|
388
|
-
help="
|
|
406
|
+
help="Show version number and exit.",
|
|
389
407
|
),
|
|
390
408
|
depth: int = typer.Option(
|
|
391
409
|
4,
|
|
392
410
|
"--depth",
|
|
393
|
-
help=
|
|
411
|
+
help=(
|
|
412
|
+
"Maximum depth for file tree traversal (default: 4, range: 1–20). "
|
|
413
|
+
"Increase for deeply nested projects — Maven/Java requires at least 8 (src/main/java/...)."
|
|
414
|
+
),
|
|
394
415
|
min=1,
|
|
395
416
|
max=20,
|
|
396
417
|
),
|
|
397
418
|
docs: bool = typer.Option(
|
|
398
419
|
False,
|
|
399
420
|
"--docs",
|
|
400
|
-
help="
|
|
421
|
+
help="Extract documentation: docstrings, function signatures, and module-level comments. Adds doc_summary and docs to output. Combine with --docs-depth to control coverage.",
|
|
401
422
|
),
|
|
402
423
|
docs_depth: str = typer.Option(
|
|
403
424
|
"symbols",
|
|
404
425
|
"--docs-depth",
|
|
405
|
-
help="
|
|
426
|
+
help="Documentation extraction depth: module (module-level only), symbols (functions and classes), full (all symbols including private). Default: symbols.",
|
|
406
427
|
show_default=True,
|
|
407
428
|
),
|
|
408
429
|
full_metrics: bool = typer.Option(
|
|
409
430
|
False,
|
|
410
431
|
"--full-metrics",
|
|
411
|
-
help=
|
|
432
|
+
help=(
|
|
433
|
+
"Technical audit: lines of code, symbol counts, cyclomatic complexity, and test coverage per file. "
|
|
434
|
+
"Produces file_metrics and metrics_summary. "
|
|
435
|
+
"Not included in --agent output — designed for CI pipelines and code review tools, not as primary agent context."
|
|
436
|
+
),
|
|
412
437
|
),
|
|
413
438
|
semantics: bool = typer.Option(
|
|
414
439
|
False,
|
|
415
440
|
"--semantics",
|
|
416
|
-
help=
|
|
441
|
+
help=(
|
|
442
|
+
"Semantic analysis: cross-file symbol resolution, call graph with confidence levels, and import linking. "
|
|
443
|
+
"Adds semantic_calls, semantic_symbols, semantic_links, semantic_summary, and hotspots (files ranked by fan-in/fan-out). "
|
|
444
|
+
"Slower than default analysis — skip for quick scans. "
|
|
445
|
+
"Confidence degrades on dynamic dispatch, decorators, and generated code."
|
|
446
|
+
),
|
|
417
447
|
),
|
|
418
448
|
architecture: bool = typer.Option(
|
|
419
449
|
False,
|
|
420
450
|
"--architecture",
|
|
421
|
-
help=
|
|
451
|
+
help=(
|
|
452
|
+
"Architectural inference: detect functional layers (MVC/layered/hexagonal), bounded contexts, "
|
|
453
|
+
"and dominant structural patterns. Adds architecture to output. "
|
|
454
|
+
"Confidence is low when based on directory names alone — combine with --semantics for higher accuracy."
|
|
455
|
+
),
|
|
422
456
|
),
|
|
423
457
|
git_context: bool = typer.Option(
|
|
424
458
|
False,
|
|
425
459
|
"--git-context",
|
|
426
460
|
"-g",
|
|
427
|
-
help="
|
|
461
|
+
help="Include git activity: recent commits, change hotspots (most frequently modified files), pending uncommitted changes, and contributors. Adds git_context to output.",
|
|
428
462
|
),
|
|
429
463
|
git_depth: int = typer.Option(
|
|
430
464
|
20,
|
|
431
465
|
"--git-depth",
|
|
432
|
-
help="
|
|
466
|
+
help="Number of recent commits to include with --git-context (default: 20, max: 100).",
|
|
433
467
|
min=1,
|
|
434
468
|
max=100,
|
|
435
469
|
),
|
|
436
470
|
git_days: int = typer.Option(
|
|
437
471
|
90,
|
|
438
472
|
"--git-days",
|
|
439
|
-
help="
|
|
473
|
+
help="Time window in days for detecting change hotspots with --git-context (default: 90). Hotspots are files with the most commits in this window.",
|
|
440
474
|
min=1,
|
|
441
475
|
max=3650,
|
|
442
476
|
),
|
|
443
477
|
env_map: bool = typer.Option(
|
|
444
478
|
False,
|
|
445
479
|
"--env-map",
|
|
446
|
-
help="
|
|
480
|
+
help="Map environment variables: keys, types (string/int/bool/url/path), categories (database/auth/service/...), and which files reference them. Adds env_map and env_summary.",
|
|
447
481
|
),
|
|
448
482
|
code_notes: bool = typer.Option(
|
|
449
483
|
False,
|
|
450
484
|
"--code-notes",
|
|
451
|
-
help=
|
|
485
|
+
help=(
|
|
486
|
+
"Extract inline annotations: TODO, FIXME, HACK, NOTE, DEPRECATED, WARNING, BUG, XXX, OPTIMIZE — "
|
|
487
|
+
"with file location and enclosing symbol. "
|
|
488
|
+
"Also detects Architecture Decision Records (ADRs) in docs/decisions/, docs/adr/, and similar paths."
|
|
489
|
+
),
|
|
452
490
|
),
|
|
453
491
|
agent: bool = typer.Option(
|
|
454
492
|
False,
|
|
455
493
|
"--agent",
|
|
456
|
-
help=
|
|
494
|
+
help=(
|
|
495
|
+
"Agent-optimized output: structured, noise-free JSON for AI consumption. "
|
|
496
|
+
"Automatically enables --dependencies, --env-map, and --code-notes. Suppresses file tree. "
|
|
497
|
+
"Output includes: identity, entry points, architecture, runtime dependencies, "
|
|
498
|
+
"operational signals, confidence summary, and analysis gaps. No empty sections."
|
|
499
|
+
),
|
|
457
500
|
),
|
|
458
501
|
trace_pipeline: bool = typer.Option(
|
|
459
502
|
False,
|
|
460
503
|
"--trace-pipeline",
|
|
461
|
-
help=
|
|
504
|
+
help=(
|
|
505
|
+
"Diagnostic mode: include pipeline_trace in output showing every candidate, filter decision, "
|
|
506
|
+
"and data origin across all pipeline stages. "
|
|
507
|
+
"Use to diagnose unexpected or contaminated results. Not intended for normal agent use."
|
|
508
|
+
),
|
|
462
509
|
),
|
|
463
510
|
) -> None:
|
|
464
511
|
"""Analyze a repository and produce structured context for AI coding agents.
|
|
@@ -480,22 +527,22 @@ def main(
|
|
|
480
527
|
|
|
481
528
|
_t0 = time.monotonic()
|
|
482
529
|
|
|
483
|
-
#
|
|
530
|
+
# Validate format choices
|
|
484
531
|
if format not in FORMAT_CHOICES:
|
|
485
532
|
typer.echo(
|
|
486
|
-
f"Error:
|
|
533
|
+
f"Error: invalid value '{format}' for --format. Valid options: {', '.join(FORMAT_CHOICES)}",
|
|
487
534
|
err=True,
|
|
488
535
|
)
|
|
489
536
|
raise typer.Exit(code=1)
|
|
490
537
|
if graph_detail not in GRAPH_DETAIL_CHOICES:
|
|
491
538
|
typer.echo(
|
|
492
|
-
f"Error:
|
|
539
|
+
f"Error: invalid value '{graph_detail}' for --graph-detail. Valid options: {', '.join(GRAPH_DETAIL_CHOICES)}",
|
|
493
540
|
err=True,
|
|
494
541
|
)
|
|
495
542
|
raise typer.Exit(code=1)
|
|
496
543
|
if docs_depth not in DOCS_DEPTH_CHOICES:
|
|
497
544
|
typer.echo(
|
|
498
|
-
f"Error:
|
|
545
|
+
f"Error: invalid value '{docs_depth}' for --docs-depth. Valid options: {', '.join(DOCS_DEPTH_CHOICES)}",
|
|
499
546
|
err=True,
|
|
500
547
|
)
|
|
501
548
|
raise typer.Exit(code=1)
|
|
@@ -509,7 +556,7 @@ def main(
|
|
|
509
556
|
typer.echo(f"Error: '{target}' is not a directory.", err=True)
|
|
510
557
|
raise typer.Exit(code=1)
|
|
511
558
|
|
|
512
|
-
# ---
|
|
559
|
+
# --- Import analysis modules ---
|
|
513
560
|
from dataclasses import asdict, replace
|
|
514
561
|
|
|
515
562
|
from sourcecode.dependency_analyzer import DependencyAnalyzer
|
|
@@ -532,17 +579,17 @@ def main(
|
|
|
532
579
|
from sourcecode.serializer import agent_view, compact_view, normalize_source_map, standard_view, validate_cross_analyzer_consistency, validate_source_map, write_output
|
|
533
580
|
from sourcecode.workspace import WorkspaceAnalyzer
|
|
534
581
|
|
|
535
|
-
# 1.
|
|
582
|
+
# 1. Scan directory (SCAN-01 to SCAN-05)
|
|
536
583
|
redactor = SecretRedactor(enabled=not no_redact)
|
|
537
584
|
|
|
538
|
-
#
|
|
539
|
-
# find_manifests()
|
|
585
|
+
# Detect manifests before scan to adjust depth.
|
|
586
|
+
# find_manifests() only looks at depth 0-1, does not need the full tree.
|
|
540
587
|
_pre_scanner = FileScanner(target, max_depth=1)
|
|
541
588
|
manifests = _pre_scanner.find_manifests()
|
|
542
589
|
|
|
543
|
-
# Maven
|
|
544
|
-
#
|
|
545
|
-
#
|
|
590
|
+
# Maven uses src/main/java/<groupId>/<artifactId>/<module>/ (depth 7+).
|
|
591
|
+
# At depth=4 Java files are invisible and all analyzers fail.
|
|
592
|
+
# Require at least 8: src(1)+main(2)+java(3)+com(4)+co(5)+app(6)+module(7)+file.
|
|
546
593
|
_java_manifest_names = {"pom.xml", "build.gradle", "build.gradle.kts"}
|
|
547
594
|
_is_java = any(Path(m).name in _java_manifest_names for m in manifests)
|
|
548
595
|
_java_min_depth = 8
|
|
@@ -559,12 +606,12 @@ def main(
|
|
|
559
606
|
scanner = FileScanner(target, max_depth=effective_depth)
|
|
560
607
|
raw_tree = scanner.scan_tree()
|
|
561
608
|
|
|
562
|
-
# 2.
|
|
609
|
+
# 2. Filter .env and *.secret entries from file tree (SEC-02, all levels)
|
|
563
610
|
def filter_sensitive_files(tree: dict[str, Any]) -> dict[str, Any]:
|
|
564
611
|
filtered: dict[str, Any] = {}
|
|
565
612
|
for name, value in tree.items():
|
|
566
613
|
if redactor.should_exclude_file(name):
|
|
567
|
-
continue #
|
|
614
|
+
continue # exclude .env, *.secret from tree
|
|
568
615
|
if isinstance(value, dict):
|
|
569
616
|
filtered[name] = filter_sensitive_files(value)
|
|
570
617
|
else:
|
|
@@ -614,8 +661,8 @@ def main(
|
|
|
614
661
|
invalid_edges = sorted(parsed_graph_edges - GRAPH_EDGE_CHOICES)
|
|
615
662
|
if invalid_edges:
|
|
616
663
|
typer.echo(
|
|
617
|
-
"Error:
|
|
618
|
-
f"{', '.join(invalid_edges)}.
|
|
664
|
+
f"Error: invalid values for --graph-edges: "
|
|
665
|
+
f"{', '.join(invalid_edges)}. Valid options: {', '.join(sorted(GRAPH_EDGE_CHOICES))}",
|
|
619
666
|
err=True,
|
|
620
667
|
)
|
|
621
668
|
raise typer.Exit(code=1)
|
|
@@ -804,7 +851,7 @@ def main(
|
|
|
804
851
|
else None
|
|
805
852
|
)
|
|
806
853
|
|
|
807
|
-
# 3.
|
|
854
|
+
# 3. Build schema
|
|
808
855
|
# Compute analyzer fingerprints: short hashes of each analyzer's key rule
|
|
809
856
|
# constants so that a rule change is always visible in the output, regardless
|
|
810
857
|
# of whether the semver was bumped.
|
|
@@ -850,9 +897,29 @@ def main(
|
|
|
850
897
|
),
|
|
851
898
|
workspace=ws.path,
|
|
852
899
|
)
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
900
|
+
# Prefix paths to repo-root-relative (sm.file_paths uses root-relative)
|
|
901
|
+
_pfx = ws.path.rstrip("/") + "/"
|
|
902
|
+
all_sem_calls.extend(
|
|
903
|
+
replace(c,
|
|
904
|
+
caller_path=_pfx + c.caller_path,
|
|
905
|
+
callee_path=_pfx + c.callee_path,
|
|
906
|
+
)
|
|
907
|
+
for c in ws_calls
|
|
908
|
+
)
|
|
909
|
+
all_sem_symbols.extend(
|
|
910
|
+
replace(s, path=_pfx + s.path) for s in ws_syms
|
|
911
|
+
)
|
|
912
|
+
all_sem_links.extend(
|
|
913
|
+
replace(l,
|
|
914
|
+
importer_path=_pfx + l.importer_path,
|
|
915
|
+
source_path=(
|
|
916
|
+
_pfx + l.source_path
|
|
917
|
+
if l.source_path is not None and not l.is_external
|
|
918
|
+
else l.source_path
|
|
919
|
+
),
|
|
920
|
+
)
|
|
921
|
+
for l in ws_links
|
|
922
|
+
)
|
|
856
923
|
all_sem_summaries.append(ws_sum)
|
|
857
924
|
merged_sem = semantic_analyzer.merge_summaries(all_sem_summaries)
|
|
858
925
|
sm = replace(
|
|
@@ -882,17 +949,86 @@ def main(
|
|
|
882
949
|
[ws.path for ws in workspace_analysis.workspaces],
|
|
883
950
|
)
|
|
884
951
|
|
|
885
|
-
# Phase 9: LLM Output Quality —
|
|
952
|
+
# Phase 9: LLM Output Quality — populate derived fields
|
|
886
953
|
from sourcecode.architecture_summary import ArchitectureSummarizer
|
|
887
954
|
from sourcecode.summarizer import ProjectSummarizer
|
|
888
955
|
from sourcecode.tree_utils import flatten_file_tree
|
|
889
956
|
|
|
890
|
-
# LQN-01:
|
|
957
|
+
# LQN-01: flat path list from file_tree with forward-slash separator
|
|
891
958
|
sm.file_paths = [
|
|
892
959
|
p.replace("\\", "/") for p in flatten_file_tree(sm.file_tree)
|
|
893
960
|
]
|
|
894
961
|
|
|
895
|
-
#
|
|
962
|
+
# Semantic hotspots + coverage (needs sm.file_paths populated above)
|
|
963
|
+
if semantic_analyzer is not None and sm.semantic_summary is not None:
|
|
964
|
+
from collections import Counter as _Counter
|
|
965
|
+
from pathlib import Path as _Path
|
|
966
|
+
|
|
967
|
+
_SRC_EXTS = {".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".java", ".kt", ".rs", ".rb"}
|
|
968
|
+
_fan_in: _Counter[str] = _Counter()
|
|
969
|
+
_fan_out: _Counter[str] = _Counter()
|
|
970
|
+
for _call in sm.semantic_calls:
|
|
971
|
+
if _call.callee_path:
|
|
972
|
+
_fan_in[_call.callee_path] += 1
|
|
973
|
+
if _call.caller_path:
|
|
974
|
+
_fan_out[_call.caller_path] += 1
|
|
975
|
+
|
|
976
|
+
_all_call_files = set(_fan_in) | set(_fan_out)
|
|
977
|
+
_hotspots: list[dict] = []
|
|
978
|
+
# Filter test paths from hotspots — they dominate fan-in by calling many modules
|
|
979
|
+
_TEST_MARKERS = {"/test", "/tests", "/spec", "/specs", "_test.", ".test.", ".spec."}
|
|
980
|
+
for _p in _all_call_files:
|
|
981
|
+
if any(_m in _p for _m in _TEST_MARKERS) or _p.startswith("test"):
|
|
982
|
+
continue
|
|
983
|
+
_in = _fan_in[_p]
|
|
984
|
+
_out = _fan_out[_p]
|
|
985
|
+
_score = _in * 2.0 + _out * 1.0
|
|
986
|
+
if _score < 2:
|
|
987
|
+
continue
|
|
988
|
+
if _in > _out * 2:
|
|
989
|
+
_reason = "high fan-in: many callers depend on this"
|
|
990
|
+
elif _out > _in * 2:
|
|
991
|
+
_reason = "high fan-out: orchestrates many modules"
|
|
992
|
+
else:
|
|
993
|
+
_reason = "hub: balanced import/call traffic"
|
|
994
|
+
# Determine method confidence from calls touching this path
|
|
995
|
+
_method = next(
|
|
996
|
+
(c.method for c in sm.semantic_calls if c.callee_path == _p or c.caller_path == _p),
|
|
997
|
+
"heuristic",
|
|
998
|
+
)
|
|
999
|
+
_hotspots.append({
|
|
1000
|
+
"path": _p,
|
|
1001
|
+
"importance_score": round(_score, 1),
|
|
1002
|
+
"fan_in": _in,
|
|
1003
|
+
"fan_out": _out,
|
|
1004
|
+
"reason": _reason,
|
|
1005
|
+
"confidence": "medium" if _method == "heuristic" else "high",
|
|
1006
|
+
})
|
|
1007
|
+
_hotspots.sort(key=lambda x: -x["importance_score"])
|
|
1008
|
+
|
|
1009
|
+
_total_src = sum(
|
|
1010
|
+
1 for _fp in sm.file_paths
|
|
1011
|
+
if _Path(_fp).suffix.lower() in _SRC_EXTS
|
|
1012
|
+
)
|
|
1013
|
+
_analyzed = sm.semantic_summary.files_analyzed
|
|
1014
|
+
_cov_pct = round(_analyzed / _total_src * 100, 1) if _total_src > 0 else 0.0
|
|
1015
|
+
_cov_conf = (
|
|
1016
|
+
"high" if _cov_pct >= 80
|
|
1017
|
+
else "medium" if _cov_pct >= 40
|
|
1018
|
+
else "low"
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
sm = replace(
|
|
1022
|
+
sm,
|
|
1023
|
+
semantic_summary=replace(
|
|
1024
|
+
sm.semantic_summary,
|
|
1025
|
+
hotspots=_hotspots[:10],
|
|
1026
|
+
coverage_pct=_cov_pct,
|
|
1027
|
+
coverage_confidence=_cov_conf,
|
|
1028
|
+
),
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
# LQN-05: top-15 direct dependencies from manifest/lockfile, sorted by role
|
|
896
1032
|
if dependency_analyzer is not None:
|
|
897
1033
|
from sourcecode.dependency_analyzer import _ROLE_PRIORITY
|
|
898
1034
|
|
|
@@ -900,6 +1036,8 @@ def main(
|
|
|
900
1036
|
direct_deps = [
|
|
901
1037
|
d for d in sm.dependencies
|
|
902
1038
|
if d.scope != "transitive" and d.source in {"manifest", "lockfile"}
|
|
1039
|
+
and (d.role or "unknown") in {"runtime", "parsing", "serialization", "observability", "infra"}
|
|
1040
|
+
and d.scope not in {"dev"}
|
|
903
1041
|
]
|
|
904
1042
|
|
|
905
1043
|
def _dep_sort_key(d: Any) -> tuple[int, int, str]:
|
|
@@ -909,14 +1047,14 @@ def main(
|
|
|
909
1047
|
|
|
910
1048
|
sm.key_dependencies = sorted(direct_deps, key=_dep_sort_key)[:15]
|
|
911
1049
|
|
|
912
|
-
# LQN-02:
|
|
1050
|
+
# LQN-02: deterministic NL summary
|
|
913
1051
|
sm.project_summary = ProjectSummarizer(target).generate(sm)
|
|
914
1052
|
sm.architecture_summary = ArchitectureSummarizer(target).generate(sm)
|
|
915
1053
|
|
|
916
1054
|
# Phase 13 Plan 04: Architectural Inference (--architecture flag)
|
|
917
1055
|
if architecture:
|
|
918
1056
|
from sourcecode.architecture_analyzer import ArchitectureAnalyzer
|
|
919
|
-
arch_graph = module_graph # None
|
|
1057
|
+
arch_graph = module_graph # None if --graph-modules was not passed
|
|
920
1058
|
sm.architecture = ArchitectureAnalyzer().analyze(target, sm, arch_graph)
|
|
921
1059
|
|
|
922
1060
|
# Git Context (--git-context flag)
|
|
@@ -980,13 +1118,13 @@ def main(
|
|
|
980
1118
|
"example", "examples", "docs", "doc", "fixtures", "fixture",
|
|
981
1119
|
})
|
|
982
1120
|
for _ep in sm.entry_points:
|
|
983
|
-
_normalized_ep = normalize_entry_point(_ep)
|
|
984
|
-
_ep_type = _normalized_ep.entrypoint_type
|
|
985
|
-
_path_parts = _ep.path.replace("\\", "/").lower().split("/")
|
|
986
|
-
_filtered = (
|
|
987
|
-
_normalized_ep.classification != "production"
|
|
988
|
-
or any(p in _aux_parts for p in _path_parts)
|
|
989
|
-
)
|
|
1121
|
+
_normalized_ep = normalize_entry_point(_ep)
|
|
1122
|
+
_ep_type = _normalized_ep.entrypoint_type
|
|
1123
|
+
_path_parts = _ep.path.replace("\\", "/").lower().split("/")
|
|
1124
|
+
_filtered = (
|
|
1125
|
+
_normalized_ep.classification != "production"
|
|
1126
|
+
or any(p in _aux_parts for p in _path_parts)
|
|
1127
|
+
)
|
|
990
1128
|
if _filtered:
|
|
991
1129
|
_trace.emit("output", "agent_view", "filter_ep",
|
|
992
1130
|
target=_ep.path,
|
|
@@ -1002,7 +1140,7 @@ def main(
|
|
|
1002
1140
|
))
|
|
1003
1141
|
sm = _replace(sm, pipeline_trace=_trace.build_trace())
|
|
1004
1142
|
|
|
1005
|
-
# 4.
|
|
1143
|
+
# 4. Serialize
|
|
1006
1144
|
if agent:
|
|
1007
1145
|
data = agent_view(sm)
|
|
1008
1146
|
if not no_redact:
|
|
@@ -1056,7 +1194,7 @@ def main(
|
|
|
1056
1194
|
except Exception:
|
|
1057
1195
|
pass
|
|
1058
1196
|
|
|
1059
|
-
# 6.
|
|
1197
|
+
# 6. Write output (CLI-04)
|
|
1060
1198
|
write_output(content, output=output)
|
|
1061
1199
|
|
|
1062
1200
|
|
|
@@ -1138,7 +1276,7 @@ def prepare_context_cmd(
|
|
|
1138
1276
|
|
|
1139
1277
|
target = path.resolve()
|
|
1140
1278
|
if not target.exists() or not target.is_dir():
|
|
1141
|
-
typer.echo(f"Error: '{target}'
|
|
1279
|
+
typer.echo(f"Error: '{target}' is not a valid directory.", err=True)
|
|
1142
1280
|
raise typer.Exit(code=1)
|
|
1143
1281
|
|
|
1144
1282
|
if dry_run:
|
|
@@ -135,7 +135,7 @@ class ConfidenceAnalyzer:
|
|
|
135
135
|
if not normalized_entry_points:
|
|
136
136
|
gaps.append(AnalysisGap(
|
|
137
137
|
area="entry_points",
|
|
138
|
-
reason="
|
|
138
|
+
reason="Critical: no runtime entrypoint detected; system cannot be executed without manual inference",
|
|
139
139
|
impact="high",
|
|
140
140
|
))
|
|
141
141
|
elif all(
|
|
@@ -145,16 +145,16 @@ class ConfidenceAnalyzer:
|
|
|
145
145
|
gaps.append(AnalysisGap(
|
|
146
146
|
area="entry_points",
|
|
147
147
|
reason=(
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
148
|
+
"Critical: no production runtime entrypoint detected; detected entries are "
|
|
149
|
+
"development or auxiliary only. Add/verify a start/serve script, CLI bin, "
|
|
150
|
+
"or server bootstrap before using this context for automation."
|
|
151
151
|
),
|
|
152
152
|
impact="high",
|
|
153
153
|
))
|
|
154
154
|
elif all(ep.confidence == "low" for ep in normalized_entry_points):
|
|
155
155
|
gaps.append(AnalysisGap(
|
|
156
156
|
area="entry_points",
|
|
157
|
-
reason="Entry points inferred from code patterns only
|
|
157
|
+
reason="Entry points inferred from code patterns only; no manifest script, CLI bin, or server bootstrap declaration found",
|
|
158
158
|
impact="medium",
|
|
159
159
|
))
|
|
160
160
|
|