sourcecode 2.3.0__tar.gz → 2.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. {sourcecode-2.3.0 → sourcecode-2.4.0}/CHANGELOG.md +53 -0
  2. {sourcecode-2.3.0 → sourcecode-2.4.0}/PKG-INFO +1 -1
  3. {sourcecode-2.3.0 → sourcecode-2.4.0}/pyproject.toml +1 -1
  4. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/cli.py +62 -1
  6. sourcecode-2.4.0/src/sourcecode/context_cache.py +637 -0
  7. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/explain.py +24 -0
  8. {sourcecode-2.3.0 → sourcecode-2.4.0}/.github/workflows/build-windows.yml +0 -0
  9. {sourcecode-2.3.0 → sourcecode-2.4.0}/.gitignore +0 -0
  10. {sourcecode-2.3.0 → sourcecode-2.4.0}/.ruff.toml +0 -0
  11. {sourcecode-2.3.0 → sourcecode-2.4.0}/CONTRIBUTING.md +0 -0
  12. {sourcecode-2.3.0 → sourcecode-2.4.0}/LICENSE +0 -0
  13. {sourcecode-2.3.0 → sourcecode-2.4.0}/README.md +0 -0
  14. {sourcecode-2.3.0 → sourcecode-2.4.0}/SECURITY.md +0 -0
  15. {sourcecode-2.3.0 → sourcecode-2.4.0}/raw +0 -0
  16. {sourcecode-2.3.0 → sourcecode-2.4.0}/scripts/compare_integration_engines.py +0 -0
  17. {sourcecode-2.3.0 → sourcecode-2.4.0}/scripts/customer_smoke_test.sh +0 -0
  18. {sourcecode-2.3.0 → sourcecode-2.4.0}/scripts/generate_jdk_exports.py +0 -0
  19. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/adaptive_scanner.py +0 -0
  20. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/archetype.py +0 -0
  21. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/architecture_analyzer.py +0 -0
  22. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/architecture_summary.py +0 -0
  23. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/ast_extractor.py +0 -0
  24. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/cache.py +0 -0
  25. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/call_surface.py +0 -0
  26. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/caller_metrics.py +0 -0
  27. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/canonical_ir.py +0 -0
  28. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/cir_graphs.py +0 -0
  29. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/classifier.py +0 -0
  30. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  31. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/confidence_analyzer.py +0 -0
  32. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/context_graph.py +0 -0
  33. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/context_scorer.py +0 -0
  34. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/context_summarizer.py +0 -0
  35. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/contract_model.py +0 -0
  36. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/contract_pipeline.py +0 -0
  37. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/coverage_parser.py +0 -0
  38. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/dependency_analyzer.py +0 -0
  39. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/__init__.py +0 -0
  40. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/base.py +0 -0
  41. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  42. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/dart.py +0 -0
  43. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/dotnet.py +0 -0
  44. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/elixir.py +0 -0
  45. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/go.py +0 -0
  46. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/heuristic.py +0 -0
  47. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/hybrid.py +0 -0
  48. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/java.py +0 -0
  49. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  50. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/nodejs.py +0 -0
  51. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/parsers.py +0 -0
  52. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/php.py +0 -0
  53. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/project.py +0 -0
  54. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/python.py +0 -0
  55. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/ruby.py +0 -0
  56. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/rust.py +0 -0
  57. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/systems.py +0 -0
  58. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/terraform.py +0 -0
  59. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/detectors/tooling.py +0 -0
  60. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/doc_analyzer.py +0 -0
  61. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  62. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/endpoint_literals.py +0 -0
  63. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/endpoint_metrics.py +0 -0
  64. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  65. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/env_analyzer.py +0 -0
  66. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/error_schema.py +0 -0
  67. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/evidence_provider.py +0 -0
  68. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/file_chunker.py +0 -0
  69. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/file_classifier.py +0 -0
  70. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/format_contract.py +0 -0
  71. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/fqn_utils.py +0 -0
  72. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/git_analyzer.py +0 -0
  73. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/graph_analyzer.py +0 -0
  74. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/graph_evidence.py +0 -0
  75. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/hibernate_strat.py +0 -0
  76. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/jdk_exports.py +0 -0
  77. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/license.py +0 -0
  78. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/__init__.py +0 -0
  79. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  80. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  81. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  82. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  83. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  84. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  85. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/registry.py +0 -0
  86. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/runner.py +0 -0
  87. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp/server.py +0 -0
  88. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/mcp_nudge.py +0 -0
  89. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/metrics_analyzer.py +0 -0
  90. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/migrate_check.py +0 -0
  91. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/openapi_surface.py +0 -0
  92. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/output_budget.py +0 -0
  93. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/path_filters.py +0 -0
  94. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  95. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/pr_impact.py +0 -0
  96. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/prepare_context.py +0 -0
  97. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/progress.py +0 -0
  98. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/ranking_engine.py +0 -0
  99. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/redactor.py +0 -0
  100. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/relevance_scorer.py +0 -0
  101. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/rename_refactor.py +0 -0
  102. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/repo_classifier.py +0 -0
  103. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/repository_ir.py +0 -0
  104. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/ris.py +0 -0
  105. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/runtime_classifier.py +0 -0
  106. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/scanner.py +0 -0
  107. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/schema.py +0 -0
  108. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/security_config.py +0 -0
  109. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/security_posture.py +0 -0
  110. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/semantic_analyzer.py +0 -0
  111. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  112. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  113. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/semantic_services.py +0 -0
  114. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/serializer.py +0 -0
  115. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/spring_event_topology.py +0 -0
  116. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/spring_findings.py +0 -0
  117. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/spring_impact.py +0 -0
  118. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/spring_model.py +0 -0
  119. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/spring_security_audit.py +0 -0
  120. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/spring_semantic.py +0 -0
  121. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
  122. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/summarizer.py +0 -0
  123. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/telemetry/__init__.py +0 -0
  124. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/telemetry/config.py +0 -0
  125. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/telemetry/consent.py +0 -0
  126. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/telemetry/events.py +0 -0
  127. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/telemetry/filters.py +0 -0
  128. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/telemetry/transport.py +0 -0
  129. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/tree_utils.py +0 -0
  130. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/type_usage_surface.py +0 -0
  131. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/validation_inference.py +0 -0
  132. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/validation_surface.py +0 -0
  133. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/version_check.py +0 -0
  134. {sourcecode-2.3.0 → sourcecode-2.4.0}/src/sourcecode/workspace.py +0 -0
  135. {sourcecode-2.3.0 → sourcecode-2.4.0}/supabase/functions/README.md +0 -0
  136. {sourcecode-2.3.0 → sourcecode-2.4.0}/supabase/functions/get-license/index.ts +0 -0
  137. {sourcecode-2.3.0 → sourcecode-2.4.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  138. {sourcecode-2.3.0 → sourcecode-2.4.0}/supabase/functions/telemetry/index.ts +0 -0
  139. {sourcecode-2.3.0 → sourcecode-2.4.0}/supabase/sql/license_event_ordering.sql +0 -0
  140. {sourcecode-2.3.0 → sourcecode-2.4.0}/supabase/sql/licensing_schema.sql +0 -0
  141. {sourcecode-2.3.0 → sourcecode-2.4.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.0] — 2026-07-12
4
+
5
+ **AI Context Cache — a provider-agnostic cache for ASK's reusable Knowledge
6
+ Context.** This is *not* a prompt cache: it never stores prompts, generated text,
7
+ or LLM responses. It caches the structured knowledge ASK derives from source —
8
+ so expensive commands stop re-parsing the same repository on every run. The cache
9
+ belongs entirely to ASK and is consumed identically by any AI provider (GPT,
10
+ Claude, Gemini, local models) or none at all; the LLM never participates in
11
+ producing or reading it.
12
+
13
+ ### Added
14
+
15
+ - **`context_cache.py` — the AI Context Cache component.** Operates at the
16
+ *knowledge* level: it caches the reusable Canonical IR (the expensive Java
17
+ parse) keyed **only by knowledge state**, never by command or prompt. The key
18
+ is `worktree_signature + KL_SCHEMA_VERSION + PRODUCER_VERSION + scope`, where
19
+ `worktree_signature` is the committed HEAD folded with a hash of the working-tree
20
+ diff when dirty — so a new commit *or* an uncommitted edit deterministically
21
+ invalidates the entry rather than serving stale context. Invalidation is
22
+ state-based, never TTL-based for correctness (TTL and a max-entry cap exist only
23
+ as cleanup). Entries are gzip'd, inspectable envelopes (no opaque blobs);
24
+ persistence survives across executions under `~/.sourcecode/context-cache/`.
25
+ - **`get_or_build_cir()` — the reusable-knowledge entry point.** Builds the
26
+ Canonical IR once and, on a hit, rebuilds the full CIR cheaply from the cached
27
+ raw IR via `ir_dict_to_canonical` — skipping the parse entirely, with the
28
+ `cir_hash` verified equal so the rebuilt CIR is provably faithful. Because the
29
+ key is command-agnostic, every command that needs a CIR shares one entry: once
30
+ wired, `impact` / `review-pr` / `modernize` reuse it for free.
31
+ - **`CachedContext`** — an inspectable structured record (`metadata`, `entities`,
32
+ `evidence`, `observations`, `summaries`, `graph_metrics`, `payload`,
33
+ `timestamps`, `producer_version`). Advisory metrics are tracked from day one:
34
+ hits, misses, invalidations, stores, hit ratio, average lookup/build time, bytes
35
+ stored, contexts reused.
36
+ - **`ask cache context-stats` / `ask cache context-clear`** — observability and
37
+ management for the context cache, kept distinct from the existing snapshot-cache
38
+ `cache status` / `cache clear` so neither clobbers the other.
39
+ - **`ask explain` is the first consumer.** It fetches its CIR through
40
+ `get_or_build_cir` and derives the class explanation cheaply from the shared
41
+ knowledge; each run reports `Context cache: HIT/MISS` with timing. Output is
42
+ byte-identical whether served from cache or a fresh build.
43
+
44
+ ### Notes
45
+
46
+ - **Only `explain` is wired** — a deliberate reference integration to validate the
47
+ design before connecting `impact` / `onboard` / `prepare-context` / `archetype` /
48
+ `review-pr` / `modernize`. The SourceMap-based commands (`onboard`,
49
+ `prepare-context`, `archetype`) will use a separate cache scope.
50
+ - No AI-provider-specific logic, no embeddings, no RAG, no prompt/response caching.
51
+ - Env vars: `SOURCECODE_CONTEXT_CACHE_{DIR,DISABLE,TTL_DAYS,MAX_ENTRIES}`.
52
+ - Tests: `tests/test_context_cache.py` adds 23 tests (hit/miss, commit/dirty/version
53
+ invalidation, persistence, corruption recovery, concurrency, TTL GC, CIR
54
+ round-trip fidelity, cross-command reuse). No report or behaviour cutover.
55
+
3
56
  ## [2.3.0] — 2026-07-12
4
57
 
5
58
  **Module graph promoted to a first-class Evidence Provider for architectural
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.3.0
3
+ Version: 2.4.0
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "2.3.0"
7
+ version = "2.4.0"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -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.4.0"
@@ -5341,6 +5341,7 @@ def explain_cmd(
5341
5341
  from sourcecode.context_graph import ContextGraph
5342
5342
  from sourcecode.spring_model import SpringSemanticModel
5343
5343
  from sourcecode.explain import explain_class
5344
+ from sourcecode import context_cache as _ctxcache
5344
5345
 
5345
5346
  if not class_name.strip():
5346
5347
  _emit_error_json(
@@ -5383,14 +5384,27 @@ def explain_cmd(
5383
5384
  )
5384
5385
  raise typer.Exit(code=1)
5385
5386
 
5387
+ # AI Context Cache — operates at the *knowledge* level: it caches the
5388
+ # reusable Canonical IR (the expensive Java parse), keyed only by knowledge
5389
+ # state, not by command. explain then derives its answer cheaply from that
5390
+ # shared CIR. The same cached CIR is reused for free by any other command.
5391
+ # Provider-agnostic and best-effort: any fault degrades to a fresh build.
5386
5392
  _prog = Progress()
5387
5393
  _prog.start(f"explaining {class_name} ({len(file_list)} files)")
5394
+ _cc_look = None
5388
5395
  try:
5389
- cir = ContextGraph.build(file_list, target).cir
5396
+ try:
5397
+ cir, _cc_look = _ctxcache.get_or_build_cir(
5398
+ _resolve_repo_root(target), target, file_list
5399
+ )
5400
+ except Exception:
5401
+ cir = ContextGraph.build(file_list, target).cir # fallback: never break explain
5390
5402
  model = SpringSemanticModel.build(cir)
5391
5403
  explanation = explain_class(class_name, cir, model)
5392
5404
  finally:
5393
5405
  _prog.finish()
5406
+ if _cc_look is not None:
5407
+ typer.echo(_cc_look.render(), err=True)
5394
5408
 
5395
5409
  if format == "json":
5396
5410
  output = _json.dumps(explanation.to_dict(), indent=2, ensure_ascii=False)
@@ -7199,6 +7213,53 @@ def cache_freshness_cmd(
7199
7213
  typer.echo(f"RIS updated: {result.get('ris_last_updated_at') or 'never'}")
7200
7214
 
7201
7215
 
7216
+ @cache_app.command("context-stats")
7217
+ def cache_context_stats_cmd(
7218
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
7219
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
7220
+ ) -> None:
7221
+ """Show AI Context Cache statistics (structured-knowledge reuse metrics).
7222
+
7223
+ This is the provider-agnostic context cache — distinct from the snapshot
7224
+ cache reported by `ask cache status`. It measures reuse of ASK's structured
7225
+ context (entities/evidence/observations/summaries), never prompts or LLM output.
7226
+ """
7227
+ from sourcecode import context_cache as _ctxcache
7228
+ target = _resolve_repo_root(Path(path))
7229
+ stats = _ctxcache.stats_for_repo(target)
7230
+ if json_output:
7231
+ import json as _j
7232
+ typer.echo(_j.dumps(stats, indent=2, ensure_ascii=False))
7233
+ else:
7234
+ typer.echo(f"Cache dir: {stats['cache_dir']}")
7235
+ typer.echo(f"Enabled: {stats['enabled']}")
7236
+ typer.echo(f"Contexts: {stats['contexts']}")
7237
+ typer.echo(f"Hits: {stats['hits']}")
7238
+ typer.echo(f"Misses: {stats['misses']}")
7239
+ typer.echo(f"Invalidations: {stats['invalidations']}")
7240
+ typer.echo(f"Stores: {stats['stores']}")
7241
+ typer.echo(f"Hit ratio: {stats['hit_ratio']}")
7242
+ typer.echo(f"Avg lookup: {stats['avg_lookup_ms']} ms")
7243
+ typer.echo(f"Avg build: {stats['avg_build_ms']} ms")
7244
+ typer.echo(f"Bytes stored: {stats['bytes_stored']}")
7245
+ typer.echo(f"KL version: {stats['kl_schema_version']} producer={stats['producer_version']}")
7246
+
7247
+
7248
+ @cache_app.command("context-clear")
7249
+ def cache_context_clear_cmd(
7250
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
7251
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
7252
+ ) -> None:
7253
+ """Delete the AI Context Cache for a repository (structured context + stats)."""
7254
+ from sourcecode import context_cache as _ctxcache
7255
+ target = _resolve_repo_root(Path(path))
7256
+ if not yes and sys.stdin.isatty():
7257
+ import click as _click
7258
+ _click.confirm(f"Delete AI context cache for {target}?", abort=True, err=True)
7259
+ removed = _ctxcache.clear_for_repo(target)
7260
+ typer.echo(f"Removed {removed} context file(s).", err=True)
7261
+
7262
+
7202
7263
  # ── Entry point ───────────────────────────────────────────────────────────────
7203
7264
 
7204
7265
  def _stderr_is_interactive() -> bool: