sourcecode 0.44.0__tar.gz → 0.46.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.44.0 → sourcecode-0.46.0}/PKG-INFO +1 -1
- {sourcecode-0.44.0 → sourcecode-0.46.0}/pyproject.toml +1 -1
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/cli.py +89 -28
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/contract_pipeline.py +3 -1
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/heuristic.py +9 -1
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/env_analyzer.py +2 -2
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/prepare_context.py +38 -3
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/serializer.py +36 -3
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/workspace.py +35 -1
- {sourcecode-0.44.0 → sourcecode-0.46.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/.gitignore +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/.ruff.toml +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/CONTRIBUTING.md +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/LICENSE +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/README.md +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/SECURITY.md +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/docs/privacy.md +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/docs/schema.md +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/raw +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/__init__.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/conftest.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/lcov.info +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_architecture_summary.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_ast_extractor.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_block1_reliability.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_block2_coverage.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_block5_quality.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_classifier.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_cli.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_context_scorer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_coverage_parser.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_cross_consistency.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_dependency_schema.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_detector_python.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_detectors_base.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_graph_schema.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_detection.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_docs.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_lqn.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_metrics.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_multistack.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_semantics.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_integration_universal.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_packaging.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_real_projects.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_redactor.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_scanner.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_schema.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_schema_normalization.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_semantic_schema.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_summarizer.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_telemetry.py +0 -0
- {sourcecode-0.44.0 → sourcecode-0.46.0}/tests/test_workspace_analyzer.py +0 -0
|
@@ -660,6 +660,21 @@ def main(
|
|
|
660
660
|
)
|
|
661
661
|
raise typer.Exit(code=1)
|
|
662
662
|
|
|
663
|
+
if symbol and mode not in ("contract", "standard"):
|
|
664
|
+
typer.echo(
|
|
665
|
+
f"Error: --symbol requires --mode contract or standard (got '{mode}'). "
|
|
666
|
+
"Symbol search uses the contract pipeline which does not run in raw mode.",
|
|
667
|
+
err=True,
|
|
668
|
+
)
|
|
669
|
+
raise typer.Exit(code=1)
|
|
670
|
+
|
|
671
|
+
if entrypoints_only and mode not in ("contract", "standard"):
|
|
672
|
+
typer.echo(
|
|
673
|
+
f"Error: --entrypoints-only requires --mode contract or standard (got '{mode}').",
|
|
674
|
+
err=True,
|
|
675
|
+
)
|
|
676
|
+
raise typer.Exit(code=1)
|
|
677
|
+
|
|
663
678
|
if dependency_depth > 0:
|
|
664
679
|
typer.echo(
|
|
665
680
|
f"[warning] --dependency-depth {dependency_depth} has no effect: "
|
|
@@ -716,8 +731,11 @@ def main(
|
|
|
716
731
|
# These flags produce standard_view-only output sections not in contract_view.
|
|
717
732
|
# Preserves backward compat: callers using any legacy flag get their previous format.
|
|
718
733
|
# New callers opt into contract mode via --mode contract (or bare invocation).
|
|
734
|
+
# Legacy flags that produce output sections incompatible with contract_view
|
|
735
|
+
# force mode to raw. --agent is excluded: it now runs the contract pipeline
|
|
736
|
+
# and enriches contract_view with auto-enabled analyzers (deps, env, notes).
|
|
719
737
|
_legacy_flags_active = (
|
|
720
|
-
compact or
|
|
738
|
+
compact or tree or format == "yaml" or trace_pipeline
|
|
721
739
|
or docs or semantics or graph_modules or full_metrics or architecture
|
|
722
740
|
)
|
|
723
741
|
if mode in ("contract", "standard") and _legacy_flags_active:
|
|
@@ -1257,7 +1275,13 @@ def main(
|
|
|
1257
1275
|
eco_order = 0 if d.ecosystem == primary_ecosystem else 1
|
|
1258
1276
|
return (role_order, eco_order, d.name.lower())
|
|
1259
1277
|
|
|
1260
|
-
|
|
1278
|
+
_seen_dep_names: set[str] = set()
|
|
1279
|
+
_deduped_deps: list[Any] = []
|
|
1280
|
+
for d in sorted(direct_deps, key=_dep_sort_key):
|
|
1281
|
+
if d.name not in _seen_dep_names:
|
|
1282
|
+
_seen_dep_names.add(d.name)
|
|
1283
|
+
_deduped_deps.append(d)
|
|
1284
|
+
sm.key_dependencies = _deduped_deps[:15]
|
|
1261
1285
|
|
|
1262
1286
|
# LQN-02: deterministic NL summary
|
|
1263
1287
|
sm.project_summary = ProjectSummarizer(target).generate(sm)
|
|
@@ -1356,37 +1380,61 @@ def main(
|
|
|
1356
1380
|
_is_contract_mode = mode in ("contract", "standard")
|
|
1357
1381
|
if _is_contract_mode:
|
|
1358
1382
|
from sourcecode.contract_pipeline import ContractPipeline
|
|
1383
|
+
from sourcecode.contract_model import ContractSummary as _ContractSummary
|
|
1359
1384
|
_cp = ContractPipeline()
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1385
|
+
try:
|
|
1386
|
+
_contracts, _contract_summary = _cp.run(
|
|
1387
|
+
target,
|
|
1388
|
+
sm.file_paths,
|
|
1389
|
+
entry_points=sm.entry_points,
|
|
1390
|
+
monorepo_packages=sm.monorepo_packages,
|
|
1391
|
+
mode=mode,
|
|
1392
|
+
rank_by=rank_by, # type: ignore[arg-type]
|
|
1393
|
+
max_symbols=max_symbols,
|
|
1394
|
+
dependency_depth=dependency_depth,
|
|
1395
|
+
entrypoints_only=entrypoints_only,
|
|
1396
|
+
changed_only=changed_only,
|
|
1397
|
+
symbol=symbol,
|
|
1398
|
+
compress_types=compress_types,
|
|
1399
|
+
max_importers=max_importers,
|
|
1400
|
+
semantic_calls=sm.semantic_calls or None,
|
|
1401
|
+
code_notes=sm.code_notes or None,
|
|
1402
|
+
)
|
|
1403
|
+
except Exception as _exc:
|
|
1404
|
+
typer.echo(f"[error] contract pipeline failed: {_exc}", err=True)
|
|
1405
|
+
_contracts = []
|
|
1406
|
+
_contract_summary = _ContractSummary(
|
|
1407
|
+
mode=mode,
|
|
1408
|
+
total_files=0,
|
|
1409
|
+
extracted_files=0,
|
|
1410
|
+
filtered_files=0,
|
|
1411
|
+
method_breakdown={},
|
|
1412
|
+
ranked_by=rank_by,
|
|
1413
|
+
limitations=[f"pipeline_error: {type(_exc).__name__}"],
|
|
1414
|
+
)
|
|
1377
1415
|
sm = _replace(sm, file_contracts=_contracts, contract_summary=_contract_summary)
|
|
1378
1416
|
if symbol is not None and len(_contracts) == 0:
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1417
|
+
_jvm_stacks = {"java", "kotlin", "scala", "groovy"}
|
|
1418
|
+
_is_jvm_repo = any(s.stack in _jvm_stacks for s in sm.stacks)
|
|
1419
|
+
if _is_jvm_repo:
|
|
1420
|
+
typer.echo(
|
|
1421
|
+
f"[warning] --symbol '{symbol}' matched 0 files. "
|
|
1422
|
+
"Per-file AST extraction is not available for Java/JVM repos — "
|
|
1423
|
+
"symbol search works only with Python, TypeScript, and JavaScript. "
|
|
1424
|
+
"Use --git-context or --code-notes for JVM navigation.",
|
|
1425
|
+
err=True,
|
|
1426
|
+
)
|
|
1427
|
+
else:
|
|
1428
|
+
typer.echo(
|
|
1429
|
+
f"[warning] --symbol '{symbol}' matched 0 files. "
|
|
1430
|
+
"The symbol may not exist, the name may differ in case, "
|
|
1431
|
+
"or the file may be outside the scanned depth. "
|
|
1432
|
+
"Try --depth 8 or verify the symbol name.",
|
|
1433
|
+
err=True,
|
|
1434
|
+
)
|
|
1387
1435
|
|
|
1388
1436
|
# 4. Serialize
|
|
1389
|
-
if _is_contract_mode:
|
|
1437
|
+
if _is_contract_mode and not agent:
|
|
1390
1438
|
from sourcecode.serializer import contract_view as _contract_view
|
|
1391
1439
|
_depth = _CONTRACT_DEPTH.get(mode, "minimal")
|
|
1392
1440
|
data = _contract_view(sm, emit_graph=emit_graph, depth=_depth)
|
|
@@ -1395,6 +1443,19 @@ def main(
|
|
|
1395
1443
|
content = json.dumps(data, indent=2, ensure_ascii=False)
|
|
1396
1444
|
elif agent:
|
|
1397
1445
|
data = agent_view(sm)
|
|
1446
|
+
# When contract pipeline ran (mode=contract, no legacy flags), include
|
|
1447
|
+
# per-file contracts in agent output so agents get structural context.
|
|
1448
|
+
if _is_contract_mode and sm.file_contracts:
|
|
1449
|
+
from sourcecode.serializer import _serialize_contract_minimal
|
|
1450
|
+
data["contracts"] = [_serialize_contract_minimal(c) for c in sm.file_contracts]
|
|
1451
|
+
if sm.contract_summary is not None:
|
|
1452
|
+
cs = sm.contract_summary
|
|
1453
|
+
data["contract_summary"] = {
|
|
1454
|
+
"files": cs.extracted_files,
|
|
1455
|
+
"total": cs.total_files,
|
|
1456
|
+
}
|
|
1457
|
+
if cs.method_breakdown:
|
|
1458
|
+
data["contract_summary"]["methods"] = cs.method_breakdown
|
|
1398
1459
|
if not no_redact:
|
|
1399
1460
|
data = redact_dict(data)
|
|
1400
1461
|
content = json.dumps(data, indent=2, ensure_ascii=False)
|
|
@@ -241,7 +241,9 @@ class ContractPipeline:
|
|
|
241
241
|
contracts.append(contract)
|
|
242
242
|
method_counts[contract.extraction_method] += 1
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
_js_ts_languages = {"typescript", "javascript", "tsx", "jsx"}
|
|
245
|
+
_has_js_ts = any(c.language in _js_ts_languages for c in contracts)
|
|
246
|
+
if _has_js_ts and not self._extractor.has_tree_sitter():
|
|
245
247
|
limitations.append(
|
|
246
248
|
"tree_sitter_unavailable: JS/TS extraction uses heuristics. "
|
|
247
249
|
"Install with: pip install 'sourcecode[ast]'"
|
|
@@ -66,6 +66,13 @@ class HeuristicDetector(AbstractDetector):
|
|
|
66
66
|
counts[stack] += 1
|
|
67
67
|
break
|
|
68
68
|
|
|
69
|
+
# Suppress minority stacks: if a language appears in fewer than 3 files
|
|
70
|
+
# AND represents less than 10% of detected source files, it is likely
|
|
71
|
+
# noise (stray config files, vendored snippets) rather than a real stack.
|
|
72
|
+
# Always emit the dominant language regardless of absolute count.
|
|
73
|
+
total_detected = sum(counts.values())
|
|
74
|
+
_ABS_MIN = 3
|
|
75
|
+
_REL_MIN = 0.10
|
|
69
76
|
stacks = [
|
|
70
77
|
StackDetection(
|
|
71
78
|
stack=stack,
|
|
@@ -73,7 +80,8 @@ class HeuristicDetector(AbstractDetector):
|
|
|
73
80
|
confidence="low",
|
|
74
81
|
manifests=[],
|
|
75
82
|
)
|
|
76
|
-
for stack,
|
|
83
|
+
for stack, count in counts.most_common()
|
|
84
|
+
if count >= _ABS_MIN or (total_detected > 0 and count / total_detected >= _REL_MIN)
|
|
77
85
|
]
|
|
78
86
|
|
|
79
87
|
entry_points: list[EntryPoint] = []
|
|
@@ -11,8 +11,8 @@ _MAX_FILE_SIZE = 512 * 1024 # 512 KB
|
|
|
11
11
|
|
|
12
12
|
_SKIP_DIRS = {
|
|
13
13
|
"node_modules", ".git", "__pycache__", ".venv", "venv",
|
|
14
|
-
".mypy_cache", "dist", "build", "
|
|
15
|
-
".next", ".nuxt", ".output", "vendor",
|
|
14
|
+
".mypy_cache", "dist", "build", "target", ".gradle",
|
|
15
|
+
".tox", ".eggs", "coverage", ".next", ".nuxt", ".output", "vendor",
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
_CODE_EXTENSIONS = {
|
|
@@ -349,21 +349,56 @@ class TaskContextBuilder:
|
|
|
349
349
|
spec = TASKS[task_name]
|
|
350
350
|
|
|
351
351
|
# ── 1. Scan ────────────────────────────────────────────────────────
|
|
352
|
-
from sourcecode.
|
|
352
|
+
from sourcecode.adaptive_scanner import AdaptiveScanner
|
|
353
|
+
from sourcecode.repo_classifier import RepoClassifier
|
|
353
354
|
from sourcecode.tree_utils import flatten_file_tree
|
|
354
355
|
|
|
355
|
-
|
|
356
|
+
_topology = RepoClassifier().classify(self.root)
|
|
357
|
+
scanner = AdaptiveScanner(self.root, topology=_topology, base_depth=6)
|
|
356
358
|
file_tree = scanner.scan_tree()
|
|
357
359
|
manifests = scanner.find_manifests()
|
|
358
360
|
all_paths = [p.replace("\\", "/") for p in flatten_file_tree(file_tree)]
|
|
359
361
|
|
|
360
362
|
# ── 2. Detect stacks + entry points ───────────────────────────────
|
|
363
|
+
from dataclasses import replace as _replace
|
|
361
364
|
from sourcecode.detectors import ProjectDetector, build_default_detectors
|
|
362
365
|
from sourcecode.workspace import WorkspaceAnalyzer
|
|
363
366
|
|
|
364
367
|
detector = ProjectDetector(build_default_detectors())
|
|
365
368
|
workspace_analysis = WorkspaceAnalyzer().analyze(self.root, manifests)
|
|
366
|
-
|
|
369
|
+
|
|
370
|
+
_root_manifests = [
|
|
371
|
+
m for m in manifests
|
|
372
|
+
if Path(m).resolve().parent == self.root
|
|
373
|
+
]
|
|
374
|
+
_detection_manifests = _root_manifests if workspace_analysis.workspaces else manifests
|
|
375
|
+
if workspace_analysis.is_monorepo and not _root_manifests:
|
|
376
|
+
from sourcecode.schema import EntryPoint, StackDetection
|
|
377
|
+
stacks: list[StackDetection] = []
|
|
378
|
+
entry_points: list[EntryPoint] = []
|
|
379
|
+
else:
|
|
380
|
+
stacks, entry_points, _ = detector.detect(self.root, file_tree, _detection_manifests)
|
|
381
|
+
|
|
382
|
+
# Iterate workspaces to collect per-workspace stacks and entry points —
|
|
383
|
+
# same approach as the main CLI (cli.py lines 971-1041).
|
|
384
|
+
for workspace in workspace_analysis.workspaces:
|
|
385
|
+
ws_root = self.root / workspace.path
|
|
386
|
+
if not ws_root.exists() or not ws_root.is_dir():
|
|
387
|
+
continue
|
|
388
|
+
_ws_topology = RepoClassifier().classify(ws_root)
|
|
389
|
+
_ws_scanner = AdaptiveScanner(ws_root, topology=_ws_topology, base_depth=6)
|
|
390
|
+
_ws_tree = _ws_scanner.scan_tree()
|
|
391
|
+
_ws_manifests = _ws_scanner.find_manifests()
|
|
392
|
+
_ws_stacks, _ws_eps, _ = detector.detect(ws_root, _ws_tree, _ws_manifests)
|
|
393
|
+
stacks.extend(
|
|
394
|
+
_replace(s, root=workspace.path, workspace=workspace.path, primary=False)
|
|
395
|
+
for s in _ws_stacks
|
|
396
|
+
)
|
|
397
|
+
entry_points.extend(
|
|
398
|
+
_replace(ep, path=f"{workspace.path}/{ep.path}")
|
|
399
|
+
for ep in _ws_eps
|
|
400
|
+
)
|
|
401
|
+
|
|
367
402
|
stacks, project_type = detector.classify_results(
|
|
368
403
|
file_tree, stacks, entry_points,
|
|
369
404
|
project_type_override="monorepo" if workspace_analysis.is_monorepo else None,
|
|
@@ -121,10 +121,16 @@ def _dependency_groups(sm: SourceMap) -> dict[str, list[dict[str, Any]]]:
|
|
|
121
121
|
|
|
122
122
|
if role in _PRODUCTION_DEP_ROLES and scope not in {"dev"}:
|
|
123
123
|
groups["production_dependencies"].append(item)
|
|
124
|
+
_jvm_ecosystems = {"maven", "gradle", "java", "kotlin", "scala", "groovy"}
|
|
124
125
|
if dep.source == "manifest" and name_key not in import_index:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
if dep.ecosystem in _jvm_ecosystems:
|
|
127
|
+
# Static import check unsupported for JVM: import index only covers
|
|
128
|
+
# Python/JS/TS. Flagging JVM deps as suspicious produces only false positives.
|
|
129
|
+
pass
|
|
130
|
+
else:
|
|
131
|
+
suspect = dict(item)
|
|
132
|
+
suspect["reason"] = "declared as production dependency but no static import observed"
|
|
133
|
+
groups["suspicious_dependencies"].append(suspect)
|
|
128
134
|
elif role in _TEST_DEP_ROLES:
|
|
129
135
|
groups["test_utilities"].append(item)
|
|
130
136
|
elif role in _BUILD_DEP_ROLES:
|
|
@@ -1168,6 +1174,33 @@ def _contract_view_minimal(
|
|
|
1168
1174
|
if cs.symbol_truncation:
|
|
1169
1175
|
result["symbol_query"] = cs.symbol_truncation
|
|
1170
1176
|
|
|
1177
|
+
# Monorepo package roles — helps agents understand workspace structure
|
|
1178
|
+
if sm.monorepo_packages:
|
|
1179
|
+
_noise_roles = {"benchmark_layer", "tooling_layer", "docs_layer", "test_layer"}
|
|
1180
|
+
operational_pkgs = [
|
|
1181
|
+
{"path": p.path, "role": p.architectural_role, "criticality": p.criticality}
|
|
1182
|
+
for p in sm.monorepo_packages
|
|
1183
|
+
if p.architectural_role not in _noise_roles
|
|
1184
|
+
]
|
|
1185
|
+
if operational_pkgs:
|
|
1186
|
+
result["workspace_packages"] = operational_pkgs
|
|
1187
|
+
|
|
1188
|
+
# Confidence summary — detection quality signal
|
|
1189
|
+
if sm.confidence_summary is not None:
|
|
1190
|
+
cs_conf = sm.confidence_summary
|
|
1191
|
+
conf: dict[str, Any] = {
|
|
1192
|
+
"overall": cs_conf.overall,
|
|
1193
|
+
"stack": cs_conf.stack_confidence,
|
|
1194
|
+
"entry_points": cs_conf.entry_point_confidence,
|
|
1195
|
+
}
|
|
1196
|
+
if cs_conf.anomalies:
|
|
1197
|
+
conf["anomalies"] = cs_conf.anomalies
|
|
1198
|
+
result["confidence"] = conf
|
|
1199
|
+
|
|
1200
|
+
# Analysis gaps — explicit about what could not be analyzed
|
|
1201
|
+
if sm.analysis_gaps:
|
|
1202
|
+
result["analysis_gaps"] = [asdict(g) for g in sm.analysis_gaps]
|
|
1203
|
+
|
|
1171
1204
|
return result
|
|
1172
1205
|
|
|
1173
1206
|
|
|
@@ -45,7 +45,7 @@ class WorkspaceAnalyzer:
|
|
|
45
45
|
|
|
46
46
|
def _detect_markers(self, root: Path) -> list[str]:
|
|
47
47
|
markers: list[str] = []
|
|
48
|
-
for filename in ("pnpm-workspace.yaml", "go.work", "turbo.json", "lerna.json"):
|
|
48
|
+
for filename in ("pnpm-workspace.yaml", "go.work", "turbo.json", "lerna.json", "nx.json", "rush.json"):
|
|
49
49
|
if (root / filename).exists():
|
|
50
50
|
markers.append(filename)
|
|
51
51
|
|
|
@@ -54,6 +54,17 @@ class WorkspaceAnalyzer:
|
|
|
54
54
|
content = cargo.read_text(encoding="utf-8", errors="replace")
|
|
55
55
|
if "[workspace]" in content:
|
|
56
56
|
markers.append("Cargo.toml[workspace]")
|
|
57
|
+
|
|
58
|
+
pkg = root / "package.json"
|
|
59
|
+
if pkg.exists():
|
|
60
|
+
try:
|
|
61
|
+
import json as _json
|
|
62
|
+
data = _json.loads(pkg.read_text(encoding="utf-8", errors="replace"))
|
|
63
|
+
if isinstance(data, dict) and data.get("workspaces"):
|
|
64
|
+
markers.append("package.json[workspaces]")
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
57
68
|
return markers
|
|
58
69
|
|
|
59
70
|
def _workspace_candidates_from_manifests(
|
|
@@ -84,6 +95,8 @@ class WorkspaceAnalyzer:
|
|
|
84
95
|
candidates.extend(self._from_go_work(root))
|
|
85
96
|
if "Cargo.toml[workspace]" in markers:
|
|
86
97
|
candidates.extend(self._from_cargo_workspace(root))
|
|
98
|
+
if "package.json[workspaces]" in markers:
|
|
99
|
+
candidates.extend(self._from_npm_workspaces(root))
|
|
87
100
|
return [candidate for candidate in candidates if self._is_allowed_workspace(candidate.path)]
|
|
88
101
|
|
|
89
102
|
def _from_pnpm_workspace(self, root: Path) -> list[WorkspaceCandidate]:
|
|
@@ -143,6 +156,27 @@ class WorkspaceAnalyzer:
|
|
|
143
156
|
in_members = False
|
|
144
157
|
return candidates
|
|
145
158
|
|
|
159
|
+
def _from_npm_workspaces(self, root: Path) -> list[WorkspaceCandidate]:
|
|
160
|
+
try:
|
|
161
|
+
import json as _json
|
|
162
|
+
data = _json.loads((root / "package.json").read_text(encoding="utf-8", errors="replace"))
|
|
163
|
+
except Exception:
|
|
164
|
+
return []
|
|
165
|
+
workspaces = data.get("workspaces", [])
|
|
166
|
+
if isinstance(workspaces, dict):
|
|
167
|
+
workspaces = workspaces.get("packages", [])
|
|
168
|
+
if not isinstance(workspaces, list):
|
|
169
|
+
return []
|
|
170
|
+
candidates: list[WorkspaceCandidate] = []
|
|
171
|
+
for pattern in workspaces:
|
|
172
|
+
if not isinstance(pattern, str):
|
|
173
|
+
continue
|
|
174
|
+
for path in self._resolve_pattern(root, pattern):
|
|
175
|
+
candidates.append(
|
|
176
|
+
WorkspaceCandidate(path=path, reason="marker:package.json[workspaces]", depth=len(Path(path).parts))
|
|
177
|
+
)
|
|
178
|
+
return candidates
|
|
179
|
+
|
|
146
180
|
def _resolve_pattern(self, root: Path, pattern: str) -> list[str]:
|
|
147
181
|
matches: list[str] = []
|
|
148
182
|
for candidate in root.glob(pattern):
|
{sourcecode-0.44.0 → sourcecode-0.46.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md
RENAMED
|
File without changes
|
{sourcecode-0.44.0 → sourcecode-0.46.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md
RENAMED
|
File without changes
|
{sourcecode-0.44.0 → sourcecode-0.46.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sourcecode-0.44.0 → sourcecode-0.46.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|