sourcecode 2.3.0__py3-none-any.whl → 2.5.0__py3-none-any.whl

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/__init__.py CHANGED
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "2.3.0"
7
+ __version__ = "2.5.0"
sourcecode/archetype.py CHANGED
@@ -80,6 +80,17 @@ _MIN_PRIMARY_SCORE = 0.05
80
80
  # A winner must clear this absolute score (not just the top-2 margin) to earn
81
81
  # medium/high confidence — a near-floor lone candidate stays "low".
82
82
  _MIN_CONFIDENT_SCORE = 0.5
83
+ # Fraction of engine's positive contribution discounted for a non-runnable artifact
84
+ # (P0 library de-bias): an engine-like internal core is expected of a library, so it is not
85
+ # discriminating. Tuned so a clear library (no app/server/servlet entry) is not confidently
86
+ # labeled an engine, while runnable systems (which never trigger the gate) are untouched.
87
+ _LIBRARY_ENGINE_DISCOUNT = 0.55
88
+ # Entry kinds that make a repo a runnable system (an app/server/servlet/daemon) rather
89
+ # than a consumable library. Used to gate the library de-bias (P0).
90
+ _RUNNABLE_ENTRY_KINDS = frozenset({
91
+ "application", "bootstrap", "main", "server", "daemon", "worker",
92
+ "mvc_controller", "jax_rs_controller", "servlet", "security_filter", "filter",
93
+ })
83
94
 
84
95
 
85
96
  @dataclass
@@ -389,6 +400,41 @@ class ArchetypeClassifier:
389
400
  f"{f.module_count} modules; distributed mass ≈ {cm['distributed']:.0%}",
390
401
  1.5, multi, max(cm["distributed"], 0.1))
391
402
  self._apply_graph_evidence("software_archetype", cands, f)
403
+ # ── Library de-bias (P0) ────────────────────────────────────────────────
404
+ # A consumable JVM artifact — `jar` packaging with no server/bootstrap/application
405
+ # entry — is a library, not a runnable system. But a library naturally exhibits
406
+ # engine-like topology: a few core API types with high fan-in, tightly-coupled
407
+ # internals (SCCs), hub concentration. Those graph metrics (`graph_*`) otherwise
408
+ # label every library an "engine" (observed: jobrunr → engine(high), library 0.3).
409
+ # Correct it transparently: (1) add a packaging-grounded library signal that does
410
+ # not depend on an `api/` package existing; (2) append a NAMED negative evidence row
411
+ # that discounts most of engine's graph-derived score, since topological hubness in
412
+ # a library is expected and therefore not discriminating for "engine".
413
+ # A runnable/web entry of ANY kind (an app bootstrap, an HTTP controller, a
414
+ # servlet, a daemon) means the repo is a system you run, not a library you depend
415
+ # on. Packaging is unreliable here (gradle libs report no packaging; maven
416
+ # aggregators report `pom`), so the entry-kind surface is the robust signal.
417
+ is_runnable = any(k in f.entry_kinds for k in _RUNNABLE_ENTRY_KINDS)
418
+ is_library_shaped = not is_runnable
419
+ if is_library_shaped:
420
+ add("library", "no_runnable_entry",
421
+ f"entry kinds {sorted(f.entry_kinds) or 'none'} — no app/server/servlet "
422
+ "bootstrap; a consumable artifact, not a runnable system",
423
+ 3.0, 1.0, 0.7)
424
+ # A library legitimately owns an engine-like core (storage/kernel mass, hub
425
+ # topology, SCCs) — that is what a library *is*. Discount most of engine's
426
+ # positive evidence for a non-runnable artifact so its internal machinery does
427
+ # not, alone, promote it to "engine". Transparent: a named negative row.
428
+ engine_positive = sum(max(0.0, e.contribution) for e in cands["engine"])
429
+ if engine_positive > 0:
430
+ discount = engine_positive * _LIBRARY_ENGINE_DISCOUNT
431
+ cands["engine"].append(Evidence(
432
+ "library_core_discount",
433
+ "non-runnable artifact: an engine-like internal core (storage/kernel "
434
+ "mass, hubs, SCCs) is expected of a library and is not, alone, evidence "
435
+ "of an engine product",
436
+ 1.0, 1.0, 1.0, -discount,
437
+ ))
392
438
  return self._resolve("software_archetype", cands, f)
393
439
 
394
440
  def _architectural_style(self, f: _Features) -> DimensionResult:
sourcecode/cli.py CHANGED
@@ -248,6 +248,8 @@ _SUBCOMMANDS: frozenset[str] = frozenset(
248
248
  "chunk-file",
249
249
  # Experimental evidence-based archetype (parallel to legacy)
250
250
  "archetype",
251
+ # Experimental Semantic Retrieval Engine (Layer 3.5)
252
+ "retrieve",
251
253
  }
252
254
  )
253
255
 
@@ -600,6 +602,12 @@ app.add_typer(cache_app, name="cache")
600
602
  auth_app = typer.Typer(help="Authentication: status, logout.", rich_markup_mode="rich")
601
603
  app.add_typer(auth_app, name="auth")
602
604
 
605
+ retrieve_app = typer.Typer(
606
+ help="[EXPERIMENTAL] Semantic Retrieval Engine — typed knowledge queries.",
607
+ rich_markup_mode="rich",
608
+ )
609
+ app.add_typer(retrieve_app, name="retrieve")
610
+
603
611
 
604
612
  def _maybe_show_telemetry_notice() -> None:
605
613
  """Show first-run telemetry notice once, on interactive TTYs only.
@@ -3887,7 +3895,7 @@ def endpoints_cmd(
3887
3895
  as policy "none_detected"):
3888
3896
  {
3889
3897
  "customSecurityAnnotations": [
3890
- {"shortName": "M3FiltroSeguridad",
3898
+ {"shortName": "CustomSecurityAnnotation",
3891
3899
  "resourceParam": "nombreRecurso", "levelParam": "nivelRequerido"}
3892
3900
  ]
3893
3901
  }
@@ -5341,6 +5349,7 @@ def explain_cmd(
5341
5349
  from sourcecode.context_graph import ContextGraph
5342
5350
  from sourcecode.spring_model import SpringSemanticModel
5343
5351
  from sourcecode.explain import explain_class
5352
+ from sourcecode import context_cache as _ctxcache
5344
5353
 
5345
5354
  if not class_name.strip():
5346
5355
  _emit_error_json(
@@ -5383,14 +5392,27 @@ def explain_cmd(
5383
5392
  )
5384
5393
  raise typer.Exit(code=1)
5385
5394
 
5395
+ # AI Context Cache — operates at the *knowledge* level: it caches the
5396
+ # reusable Canonical IR (the expensive Java parse), keyed only by knowledge
5397
+ # state, not by command. explain then derives its answer cheaply from that
5398
+ # shared CIR. The same cached CIR is reused for free by any other command.
5399
+ # Provider-agnostic and best-effort: any fault degrades to a fresh build.
5386
5400
  _prog = Progress()
5387
5401
  _prog.start(f"explaining {class_name} ({len(file_list)} files)")
5402
+ _cc_look = None
5388
5403
  try:
5389
- cir = ContextGraph.build(file_list, target).cir
5404
+ try:
5405
+ cir, _cc_look = _ctxcache.get_or_build_cir(
5406
+ _resolve_repo_root(target), target, file_list
5407
+ )
5408
+ except Exception:
5409
+ cir = ContextGraph.build(file_list, target).cir # fallback: never break explain
5390
5410
  model = SpringSemanticModel.build(cir)
5391
5411
  explanation = explain_class(class_name, cir, model)
5392
5412
  finally:
5393
5413
  _prog.finish()
5414
+ if _cc_look is not None:
5415
+ typer.echo(_cc_look.render(), err=True)
5394
5416
 
5395
5417
  if format == "json":
5396
5418
  output = _json.dumps(explanation.to_dict(), indent=2, ensure_ascii=False)
@@ -5544,7 +5566,12 @@ def fix_bug_cmd(
5544
5566
  ctx: typer.Context,
5545
5567
  path: Path = typer.Argument(
5546
5568
  Path("."),
5547
- help="Repository root (default: current directory)",
5569
+ help="Repository root (default: current directory), OR the bug symptom when a second path argument follows (sibling-consistent `fix-bug <symptom> <path>`).",
5570
+ ),
5571
+ path2: Optional[Path] = typer.Argument(
5572
+ None,
5573
+ help="Repository root when the first argument is the bug symptom.",
5574
+ show_default=False,
5548
5575
  ),
5549
5576
  symptom: Optional[str] = typer.Option(
5550
5577
  None, "--symptom", "-s",
@@ -5582,7 +5609,16 @@ def fix_bug_cmd(
5582
5609
  ask impact <target> — Propagate impact from a specific class
5583
5610
  ask onboard . — Full architecture context first
5584
5611
  """
5585
- # Detect misuse: `fix-bug "symptom text" /path` — path arg looks like a symptom.
5612
+ # Sibling-consistent positional form: `fix-bug <symptom> <path>` (mirrors
5613
+ # `impact <target> <path>` / `explain <symbol> <path>`). When a second positional is
5614
+ # supplied, the first positional is the symptom and the second is the repository path.
5615
+ if path2 is not None:
5616
+ if not symptom:
5617
+ symptom = str(path)
5618
+ path = path2
5619
+
5620
+ # Detect misuse: `fix-bug "symptom text"` alone — the single positional looks like a
5621
+ # symptom, not a path.
5586
5622
  _path_str = str(path)
5587
5623
  _path_looks_like_symptom = (
5588
5624
  not Path(_path_str).exists()
@@ -6495,6 +6531,410 @@ def archetype_cmd(
6495
6531
  typer.echo(out)
6496
6532
 
6497
6533
 
6534
+ def _run_retrieval_intent(
6535
+ intent_name: str,
6536
+ path: Path,
6537
+ output_path: Optional[Path],
6538
+ trace: bool,
6539
+ anchors: tuple = (),
6540
+ ) -> None:
6541
+ """Shared adapter for the experimental Semantic Retrieval commands. The CLI is one
6542
+ thin surface over the CLI-independent engine: it submits a typed RetrievalRequest and
6543
+ prints the structured JSON result (optionally with the runtime trace/profiling)."""
6544
+ import json as _json
6545
+ from sourcecode.retrieval import (
6546
+ RetrievalAnchor,
6547
+ RetrievalIntent,
6548
+ RetrievalRequest,
6549
+ SemanticRetriever,
6550
+ )
6551
+
6552
+ target = Path(path).resolve()
6553
+ retriever = SemanticRetriever.for_repo(target)
6554
+ request = RetrievalRequest(
6555
+ intent=RetrievalIntent(intent_name),
6556
+ anchors=tuple(RetrievalAnchor(kind=k, raw=v) for k, v in anchors),
6557
+ )
6558
+ run = retriever.run(request)
6559
+ payload: dict = run.result.to_dict()
6560
+ if trace:
6561
+ payload = {"result": payload, "runtime": run.report.to_dict()}
6562
+ out = _json.dumps(payload, indent=2, ensure_ascii=False)
6563
+ if output_path:
6564
+ _safe_write_file(output_path, out)
6565
+ typer.echo(f"Retrieval result written to {output_path}")
6566
+ else:
6567
+ typer.echo(out)
6568
+
6569
+
6570
+ @retrieve_app.command("architectural-core")
6571
+ def retrieve_architectural_core_cmd(
6572
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6573
+ output_path: Optional[Path] = typer.Option(
6574
+ None, "--output", "-o", help="Write output to a file instead of stdout."
6575
+ ),
6576
+ trace: bool = typer.Option(
6577
+ False, "--trace", help="Include the Query Plan Runtime trace + per-step profiling."
6578
+ ),
6579
+ ) -> None:
6580
+ """[EXPERIMENTAL] Retrieve the ARCHITECTURAL_CORE characterization as structured JSON.
6581
+
6582
+ A Semantic Retrieval query (Layer 3.5): it consults ONLY existing Knowledge-Layer
6583
+ capabilities (ArchetypeClassifier + GraphEvidenceProvider) — no extraction, no new
6584
+ heuristics, no natural language. Output is fully typed/structured JSON.
6585
+ """
6586
+ _run_retrieval_intent("architectural-core", path, output_path, trace)
6587
+
6588
+
6589
+ @retrieve_app.command("architectural-style")
6590
+ def retrieve_architectural_style_cmd(
6591
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6592
+ output_path: Optional[Path] = typer.Option(
6593
+ None, "--output", "-o", help="Write output to a file instead of stdout."
6594
+ ),
6595
+ trace: bool = typer.Option(
6596
+ False, "--trace", help="Include the Query Plan Runtime trace + per-step profiling."
6597
+ ),
6598
+ ) -> None:
6599
+ """[EXPERIMENTAL] Retrieve the ARCHITECTURAL_STYLE characterization as structured JSON.
6600
+
6601
+ Same Query Plan Runtime as architectural-core — this intent differs only in its
6602
+ declarative plan (the archetype dimension). No runtime code changes to add it.
6603
+ """
6604
+ _run_retrieval_intent("architectural-style", path, output_path, trace)
6605
+
6606
+
6607
+ @retrieve_app.command("deployment-shape")
6608
+ def retrieve_deployment_shape_cmd(
6609
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6610
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6611
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6612
+ ) -> None:
6613
+ """[EXPERIMENTAL] DEPLOYMENT_SHAPE — the deployment-shape archetype dimension.
6614
+ Same plan as architectural-core, different ArchetypeClassifier dimension."""
6615
+ _run_retrieval_intent("deployment-shape", path, output_path, trace)
6616
+
6617
+
6618
+ @retrieve_app.command("primary-interface")
6619
+ def retrieve_primary_interface_cmd(
6620
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6621
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6622
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6623
+ ) -> None:
6624
+ """[EXPERIMENTAL] PRIMARY_INTERFACE — the primary-interface archetype dimension.
6625
+ Same plan as architectural-core, different ArchetypeClassifier dimension."""
6626
+ _run_retrieval_intent("primary-interface", path, output_path, trace)
6627
+
6628
+
6629
+ @retrieve_app.command("blast-radius")
6630
+ def retrieve_blast_radius_cmd(
6631
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN) or file to analyze."),
6632
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6633
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6634
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6635
+ ) -> None:
6636
+ """[EXPERIMENTAL] BLAST_RADIUS — structural change impact (dependents + risk) for a
6637
+ target, via the Semantic Retrieval Engine. Reuses `compute_blast_radius`."""
6638
+ _run_retrieval_intent("blast-radius", path, output_path, trace, anchors=(("symbol", target),))
6639
+
6640
+
6641
+ @retrieve_app.command("affected-endpoints")
6642
+ def retrieve_affected_endpoints_cmd(
6643
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN) or file to analyze."),
6644
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6645
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6646
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6647
+ ) -> None:
6648
+ """[EXPERIMENTAL] AFFECTED_ENDPOINTS — HTTP endpoints in the blast cone of a target."""
6649
+ _run_retrieval_intent("affected-endpoints", path, output_path, trace, anchors=(("symbol", target),))
6650
+
6651
+
6652
+ @retrieve_app.command("affected-integrations")
6653
+ def retrieve_affected_integrations_cmd(
6654
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN) or file to analyze."),
6655
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6656
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6657
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6658
+ ) -> None:
6659
+ """[EXPERIMENTAL] AFFECTED_INTEGRATIONS — external integrations in the blast cone of a
6660
+ target. Reuses `SemanticIntegrationEngine`."""
6661
+ _run_retrieval_intent(
6662
+ "affected-integrations", path, output_path, trace, anchors=(("symbol", target),)
6663
+ )
6664
+
6665
+
6666
+ @retrieve_app.command("transaction-boundaries")
6667
+ def retrieve_transaction_boundaries_cmd(
6668
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN) or file to analyze."),
6669
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6670
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6671
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6672
+ ) -> None:
6673
+ """[EXPERIMENTAL] TRANSACTION_BOUNDARIES — transactional boundaries touched by a
6674
+ target's blast cone."""
6675
+ _run_retrieval_intent(
6676
+ "transaction-boundaries", path, output_path, trace, anchors=(("symbol", target),)
6677
+ )
6678
+
6679
+
6680
+ @retrieve_app.command("cross-module-impact")
6681
+ def retrieve_cross_module_impact_cmd(
6682
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN) or file to analyze."),
6683
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6684
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6685
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6686
+ ) -> None:
6687
+ """[EXPERIMENTAL] CROSS_MODULE_IMPACT — subsystems/modules hit by a target's change."""
6688
+ _run_retrieval_intent(
6689
+ "cross-module-impact", path, output_path, trace, anchors=(("symbol", target),)
6690
+ )
6691
+
6692
+
6693
+ @retrieve_app.command("transaction-flow")
6694
+ def retrieve_transaction_flow_cmd(
6695
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6696
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6697
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6698
+ ) -> None:
6699
+ """[EXPERIMENTAL] TRANSACTION_FLOW — the @Transactional boundary map. Reuses
6700
+ SpringSemanticModel's transaction index."""
6701
+ _run_retrieval_intent("transaction-flow", path, output_path, trace)
6702
+
6703
+
6704
+ @retrieve_app.command("transaction-entrypoints")
6705
+ def retrieve_transaction_entrypoints_cmd(
6706
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6707
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6708
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6709
+ ) -> None:
6710
+ """[EXPERIMENTAL] TRANSACTION_ENTRYPOINTS — transactional boundaries at the API surface."""
6711
+ _run_retrieval_intent("transaction-entrypoints", path, output_path, trace)
6712
+
6713
+
6714
+ @retrieve_app.command("transaction-propagation")
6715
+ def retrieve_transaction_propagation_cmd(
6716
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6717
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6718
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6719
+ ) -> None:
6720
+ """[EXPERIMENTAL] TRANSACTION_PROPAGATION — non-default propagation points + propagation
6721
+ risks. Reuses the Spring transaction engine."""
6722
+ _run_retrieval_intent("transaction-propagation", path, output_path, trace)
6723
+
6724
+
6725
+ @retrieve_app.command("security-surface")
6726
+ def retrieve_security_surface_cmd(
6727
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6728
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6729
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6730
+ ) -> None:
6731
+ """[EXPERIMENTAL] SECURITY_SURFACE — the security audit surface. Reuses run_security_audit."""
6732
+ _run_retrieval_intent("security-surface", path, output_path, trace)
6733
+
6734
+
6735
+ @retrieve_app.command("security-chain")
6736
+ def retrieve_security_chain_cmd(
6737
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN) to scope the chain to."),
6738
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6739
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6740
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6741
+ ) -> None:
6742
+ """[EXPERIMENTAL] SECURITY_CHAIN — security findings scoped to a target symbol."""
6743
+ _run_retrieval_intent("security-chain", path, output_path, trace, anchors=(("symbol", target),))
6744
+
6745
+
6746
+ @retrieve_app.command("validation-points")
6747
+ def retrieve_validation_points_cmd(
6748
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6749
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6750
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6751
+ ) -> None:
6752
+ """[EXPERIMENTAL] VALIDATION_POINTS — request-body validation constraints + gaps.
6753
+ Reuses build_validation_surface."""
6754
+ _run_retrieval_intent("validation-points", path, output_path, trace)
6755
+
6756
+
6757
+ @retrieve_app.command("endpoint-inventory")
6758
+ def retrieve_endpoint_inventory_cmd(
6759
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6760
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6761
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6762
+ ) -> None:
6763
+ """[EXPERIMENTAL] ENDPOINT_INVENTORY — the repository's HTTP surface.
6764
+ Reuses SpringSemanticModel.endpoint_index."""
6765
+ _run_retrieval_intent("endpoint-inventory", path, output_path, trace)
6766
+
6767
+
6768
+ @retrieve_app.command("integration-inventory")
6769
+ def retrieve_integration_inventory_cmd(
6770
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6771
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6772
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6773
+ ) -> None:
6774
+ """[EXPERIMENTAL] INTEGRATION_INVENTORY — every detected external integration.
6775
+ Reuses SemanticIntegrationEngine (whole repo, no cone filter)."""
6776
+ _run_retrieval_intent("integration-inventory", path, output_path, trace)
6777
+
6778
+
6779
+ @retrieve_app.command("event-topology")
6780
+ def retrieve_event_topology_cmd(
6781
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6782
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6783
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6784
+ ) -> None:
6785
+ """[EXPERIMENTAL] EVENT_TOPOLOGY — Spring event publishers/listeners per event type.
6786
+ Reuses SpringSemanticModel.event_graph."""
6787
+ _run_retrieval_intent("event-topology", path, output_path, trace)
6788
+
6789
+
6790
+ @retrieve_app.command("subsystem-inventory")
6791
+ def retrieve_subsystem_inventory_cmd(
6792
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6793
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6794
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6795
+ ) -> None:
6796
+ """[EXPERIMENTAL] SUBSYSTEM_INVENTORY — the repository's subsystem partition.
6797
+ Reuses the repo IR subsystem model."""
6798
+ _run_retrieval_intent("subsystem-inventory", path, output_path, trace)
6799
+
6800
+
6801
+ @retrieve_app.command("subsystem-members")
6802
+ def retrieve_subsystem_members_cmd(
6803
+ subsystem: str = typer.Option(..., "--subsystem", "-s", help="Subsystem label or package prefix."),
6804
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6805
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6806
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6807
+ ) -> None:
6808
+ """[EXPERIMENTAL] SUBSYSTEM_MEMBERS — the member symbols of a subsystem.
6809
+ Reuses the repo IR subsystem model."""
6810
+ _run_retrieval_intent(
6811
+ "subsystem-members", path, output_path, trace, anchors=(("subsystem", subsystem),)
6812
+ )
6813
+
6814
+
6815
+ @retrieve_app.command("module-dependents")
6816
+ def retrieve_module_dependents_cmd(
6817
+ module: str = typer.Option(..., "--module", "-m", help="Module path or package prefix."),
6818
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6819
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6820
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6821
+ ) -> None:
6822
+ """[EXPERIMENTAL] MODULE_DEPENDENTS — modules that depend on the scope.
6823
+ Reuses the module graph (GraphAnalyzer)."""
6824
+ _run_retrieval_intent(
6825
+ "module-dependents", path, output_path, trace, anchors=(("module", module),)
6826
+ )
6827
+
6828
+
6829
+ @retrieve_app.command("module-dependencies")
6830
+ def retrieve_module_dependencies_cmd(
6831
+ module: str = typer.Option(..., "--module", "-m", help="Module path or package prefix."),
6832
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6833
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6834
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6835
+ ) -> None:
6836
+ """[EXPERIMENTAL] MODULE_DEPENDENCIES — modules the scope depends on.
6837
+ Reuses the module graph (GraphAnalyzer)."""
6838
+ _run_retrieval_intent(
6839
+ "module-dependencies", path, output_path, trace, anchors=(("module", module),)
6840
+ )
6841
+
6842
+
6843
+ @retrieve_app.command("type-dependents")
6844
+ def retrieve_type_dependents_cmd(
6845
+ target: str = typer.Option(..., "--target", "-t", help="Type/symbol (simple name or FQN)."),
6846
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6847
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6848
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6849
+ ) -> None:
6850
+ """[EXPERIMENTAL] TYPE_DEPENDENTS — types that import/implement/inject the target.
6851
+ Reuses the repo IR reverse graph."""
6852
+ _run_retrieval_intent(
6853
+ "type-dependents", path, output_path, trace, anchors=(("symbol", target),)
6854
+ )
6855
+
6856
+
6857
+ @retrieve_app.command("endpoint-contract")
6858
+ def retrieve_endpoint_contract_cmd(
6859
+ ref: str = typer.Option(..., "--endpoint", "-e", help="Endpoint path or handler symbol."),
6860
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6861
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6862
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6863
+ ) -> None:
6864
+ """[EXPERIMENTAL] ENDPOINT_CONTRACT — the request/validation contract of an endpoint.
6865
+ Reuses build_validation_surface, scoped to one endpoint."""
6866
+ _run_retrieval_intent(
6867
+ "endpoint-contract", path, output_path, trace, anchors=(("endpoint", ref),)
6868
+ )
6869
+
6870
+
6871
+ @retrieve_app.command("endpoint-security")
6872
+ def retrieve_endpoint_security_cmd(
6873
+ ref: str = typer.Option(..., "--endpoint", "-e", help="Endpoint path or handler symbol."),
6874
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6875
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6876
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6877
+ ) -> None:
6878
+ """[EXPERIMENTAL] ENDPOINT_SECURITY — effective security of an endpoint handler.
6879
+ Reuses the endpoint model's CanonicalSecurity + run_security_audit."""
6880
+ _run_retrieval_intent(
6881
+ "endpoint-security", path, output_path, trace, anchors=(("endpoint", ref),)
6882
+ )
6883
+
6884
+
6885
+ @retrieve_app.command("call-neighborhood")
6886
+ def retrieve_call_neighborhood_cmd(
6887
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN/method)."),
6888
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6889
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6890
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6891
+ ) -> None:
6892
+ """[EXPERIMENTAL] CALL_NEIGHBORHOOD — forward callees + reverse callers of a symbol.
6893
+ Reuses SpringSemanticModel.call_adj."""
6894
+ _run_retrieval_intent(
6895
+ "call-neighborhood", path, output_path, trace, anchors=(("symbol", target),)
6896
+ )
6897
+
6898
+
6899
+ @retrieve_app.command("execution-paths-through")
6900
+ def retrieve_execution_paths_cmd(
6901
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN/method)."),
6902
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6903
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6904
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6905
+ ) -> None:
6906
+ """[EXPERIMENTAL] EXECUTION_PATHS_THROUGH — multi-hop caller reachability of a symbol.
6907
+ Reuses spring_impact.run_impact_chain."""
6908
+ _run_retrieval_intent(
6909
+ "execution-paths-through", path, output_path, trace, anchors=(("symbol", target),)
6910
+ )
6911
+
6912
+
6913
+ @retrieve_app.command("transactions-reaching")
6914
+ def retrieve_transactions_reaching_cmd(
6915
+ target: str = typer.Option(..., "--target", "-t", help="Symbol (class/FQN/method)."),
6916
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6917
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6918
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6919
+ ) -> None:
6920
+ """[EXPERIMENTAL] TRANSACTIONS_REACHING — @Transactional boundaries that reach a symbol.
6921
+ Reuses run_impact_chain + the tx boundary index."""
6922
+ _run_retrieval_intent(
6923
+ "transactions-reaching", path, output_path, trace, anchors=(("symbol", target),)
6924
+ )
6925
+
6926
+
6927
+ @retrieve_app.command("subsystem-coupling")
6928
+ def retrieve_subsystem_coupling_cmd(
6929
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
6930
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
6931
+ trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
6932
+ ) -> None:
6933
+ """[EXPERIMENTAL] SUBSYSTEM_COUPLING — module-graph hubs/cycles/orphans + subsystems.
6934
+ Reuses the module graph summary + the repo IR subsystem partition."""
6935
+ _run_retrieval_intent("subsystem-coupling", path, output_path, trace)
6936
+
6937
+
6498
6938
  @app.command("cold-start")
6499
6939
  def cold_start_cmd(
6500
6940
  path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
@@ -7199,6 +7639,53 @@ def cache_freshness_cmd(
7199
7639
  typer.echo(f"RIS updated: {result.get('ris_last_updated_at') or 'never'}")
7200
7640
 
7201
7641
 
7642
+ @cache_app.command("context-stats")
7643
+ def cache_context_stats_cmd(
7644
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
7645
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
7646
+ ) -> None:
7647
+ """Show AI Context Cache statistics (structured-knowledge reuse metrics).
7648
+
7649
+ This is the provider-agnostic context cache — distinct from the snapshot
7650
+ cache reported by `ask cache status`. It measures reuse of ASK's structured
7651
+ context (entities/evidence/observations/summaries), never prompts or LLM output.
7652
+ """
7653
+ from sourcecode import context_cache as _ctxcache
7654
+ target = _resolve_repo_root(Path(path))
7655
+ stats = _ctxcache.stats_for_repo(target)
7656
+ if json_output:
7657
+ import json as _j
7658
+ typer.echo(_j.dumps(stats, indent=2, ensure_ascii=False))
7659
+ else:
7660
+ typer.echo(f"Cache dir: {stats['cache_dir']}")
7661
+ typer.echo(f"Enabled: {stats['enabled']}")
7662
+ typer.echo(f"Contexts: {stats['contexts']}")
7663
+ typer.echo(f"Hits: {stats['hits']}")
7664
+ typer.echo(f"Misses: {stats['misses']}")
7665
+ typer.echo(f"Invalidations: {stats['invalidations']}")
7666
+ typer.echo(f"Stores: {stats['stores']}")
7667
+ typer.echo(f"Hit ratio: {stats['hit_ratio']}")
7668
+ typer.echo(f"Avg lookup: {stats['avg_lookup_ms']} ms")
7669
+ typer.echo(f"Avg build: {stats['avg_build_ms']} ms")
7670
+ typer.echo(f"Bytes stored: {stats['bytes_stored']}")
7671
+ typer.echo(f"KL version: {stats['kl_schema_version']} producer={stats['producer_version']}")
7672
+
7673
+
7674
+ @cache_app.command("context-clear")
7675
+ def cache_context_clear_cmd(
7676
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
7677
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
7678
+ ) -> None:
7679
+ """Delete the AI Context Cache for a repository (structured context + stats)."""
7680
+ from sourcecode import context_cache as _ctxcache
7681
+ target = _resolve_repo_root(Path(path))
7682
+ if not yes and sys.stdin.isatty():
7683
+ import click as _click
7684
+ _click.confirm(f"Delete AI context cache for {target}?", abort=True, err=True)
7685
+ removed = _ctxcache.clear_for_repo(target)
7686
+ typer.echo(f"Removed {removed} context file(s).", err=True)
7687
+
7688
+
7202
7689
  # ── Entry point ───────────────────────────────────────────────────────────────
7203
7690
 
7204
7691
  def _stderr_is_interactive() -> bool: