sourcecode 3.2.2__py3-none-any.whl → 3.3.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__ = "3.2.2"
7
+ __version__ = "3.3.0"
sourcecode/cli.py CHANGED
@@ -8105,47 +8105,44 @@ def auth_status_cmd() -> None:
8105
8105
  """Show current authentication and plan status."""
8106
8106
  import json as _json
8107
8107
  try:
8108
- from sourcecode.license import (
8109
- _PRO_UNLOCK_ALL as _unlock,
8110
- _license_data as _ld,
8111
- is_pro as _ip,
8112
- )
8108
+ from sourcecode.license import _license_data as _ld, entitlement as _entitlement
8109
+ _ent = _entitlement()
8113
8110
  except Exception:
8114
8111
  _ld = None
8115
- _ip = False
8116
- _unlock = False
8117
-
8118
- # While the early-adoption unlock is on, every Pro command runs. Reporting
8119
- # `pro: false` here contradicted the --help header and the commands' own
8120
- # behaviour: the status has to describe the gating that is actually in force,
8121
- # not the credential that happens to be absent.
8122
- _gating = {
8123
- "pro_gating": "disabled" if _unlock else "enabled",
8124
- "pro_effective": bool(_ip or _unlock),
8125
- }
8126
- if _unlock:
8127
- _gating["pro_reason"] = "early-adoption unlock: Pro commands run without a license"
8128
-
8129
- if not _ld:
8130
- out: dict = {
8131
- "status": "unauthenticated",
8132
- "pro": bool(_unlock),
8133
- **_gating,
8112
+ _ent = {
8113
+ "entitlement": "free", "source": "free_tier", "authenticated": False,
8114
+ "paywall_active": True, "when_it_changes": "", "free_repo_java_file_limit": None,
8134
8115
  }
8135
- sys.stdout.write(_json.dumps(out, ensure_ascii=False) + "\n")
8136
- sys.stdout.flush()
8137
- return
8138
8116
 
8139
- out = {
8140
- "status": "authenticated",
8141
- "auth_method": _ld.get("auth_method", "license_key"),
8142
- "email": _ld.get("email", ""),
8143
- "plan": _ld.get("plan", "unknown"),
8144
- "plan_status": _ld.get("status", "unknown"),
8145
- "pro": _ip,
8146
- "validated_at": _ld.get("validated_at") or _ld.get("activated_at") or "",
8147
- **_gating,
8117
+ # P-2: one authority (`license.entitlement`) answers "what runs here, and why".
8118
+ # The legacy keys below are *derived* from it rather than computed a second
8119
+ # time — that is what stops `{"status": "unauthenticated", "pro": true}` from
8120
+ # reappearing. They are deprecated and will be removed in a future major.
8121
+ _pro = _ent["entitlement"] == "pro"
8122
+ _legacy = {
8123
+ "pro": _pro,
8124
+ "pro_effective": _pro,
8125
+ "pro_gating": "enabled" if _ent["paywall_active"] else "disabled",
8148
8126
  }
8127
+ if _ent["source"] == "early_adoption_unlock":
8128
+ _legacy["pro_reason"] = "early-adoption unlock: Pro commands run without a license"
8129
+
8130
+ out: dict = {
8131
+ "status": "authenticated" if _ent["authenticated"] else "unauthenticated",
8132
+ **_ent,
8133
+ **_legacy,
8134
+ "deprecated_fields": ["pro", "pro_effective", "pro_gating", "pro_reason"],
8135
+ }
8136
+ if _ld:
8137
+ out.update({
8138
+ "auth_method": _ld.get("auth_method", "license_key"),
8139
+ "email": _ld.get("email", ""),
8140
+ "plan": _ld.get("plan", "unknown"),
8141
+ # The authority reads a missing status as active, so reporting
8142
+ # "unknown" here would contradict the entitlement in the same object.
8143
+ "plan_status": _ld.get("status", "active"),
8144
+ "validated_at": _ld.get("validated_at") or _ld.get("activated_at") or "",
8145
+ })
8149
8146
  sys.stdout.write(_json.dumps(out, indent=2, ensure_ascii=False) + "\n")
8150
8147
  sys.stdout.flush()
8151
8148
 
sourcecode/license.py CHANGED
@@ -376,16 +376,78 @@ def _maybe_revalidate() -> None:
376
376
  pass
377
377
 
378
378
 
379
- def _init() -> None:
380
- global _license_data, is_pro
381
- _license_data = _load_license_file()
382
- is_pro = (
379
+ def entitlement() -> dict:
380
+ """The one answer to "what runs here today, and why" (P-2).
381
+
382
+ `auth status` used to publish four fields about this — `status`, `pro`,
383
+ `pro_gating`, `pro_effective`, plus a `pro_reason` sentence — and they read as
384
+ a contradiction: `{"status": "unauthenticated", "pro": true}`. Each field was
385
+ true on its own axis; together they told a buyer nothing about what they have,
386
+ why they have it, or what changes when it ends. That is the ADR-0008 pattern
387
+ exactly, on the commercial surface instead of an analysis one.
388
+
389
+ Every published entitlement field is derived from this dict and nowhere else.
390
+
391
+ Keys:
392
+ entitlement "pro" | "free" — what actually runs right now
393
+ source why: "license_key" | "early_adoption_unlock" | "free_tier"
394
+ authenticated whether a credential is on this machine (an independent
395
+ fact: today one can be entitled to Pro without one)
396
+ paywall_active whether the paywall is enforcing anything at all
397
+ when_it_changes what a user loses when `source` stops applying, in the
398
+ terms the gate actually uses — never a price
399
+ free_repo_java_file_limit
400
+ the number the size gate compares against
401
+ """
402
+ authenticated = _license_data is not None
403
+ licensed = bool(
383
404
  _license_data is not None
384
405
  and _license_data.get("plan") == "pro"
385
406
  and _license_data.get("status", "active") != "inactive"
386
407
  )
387
- if _PRO_UNLOCK_ALL:
388
- is_pro = True # TEMPORARY: early-adoption Pro unlock (see _PRO_UNLOCK_ALL)
408
+ if licensed:
409
+ source = "license_key"
410
+ elif _PRO_UNLOCK_ALL:
411
+ source = "early_adoption_unlock"
412
+ else:
413
+ source = "free_tier"
414
+
415
+ if source == "license_key":
416
+ when = "Your licence entitles you to Pro; nothing changes while it stays active."
417
+ elif source == "early_adoption_unlock":
418
+ when = (
419
+ "Pro runs without a licence during early adoption. When that ends, every command "
420
+ f"still runs at full output on repositories up to {_FREE_REPO_JAVA_FILE_LIMIT} Java "
421
+ "source files; above that, the heavy-analysis commands ask for Pro. Capability is "
422
+ "never what is gated — size, automation and team are."
423
+ )
424
+ else:
425
+ when = (
426
+ f"Every command runs at full output on repositories up to {_FREE_REPO_JAVA_FILE_LIMIT} "
427
+ "Java source files. Above that, the heavy-analysis commands require Pro."
428
+ )
429
+
430
+ return {
431
+ "entitlement": "pro" if (licensed or _PRO_UNLOCK_ALL) else "free",
432
+ "source": source,
433
+ "authenticated": authenticated,
434
+ "paywall_active": not _PRO_UNLOCK_ALL,
435
+ "when_it_changes": when,
436
+ "free_repo_java_file_limit": _FREE_REPO_JAVA_FILE_LIMIT,
437
+ }
438
+
439
+
440
+ def _init() -> None:
441
+ """Load the credential and derive the entitlement from the one authority.
442
+
443
+ `is_pro` is what every gate reads (`can_use`, `require_feature`), and it is
444
+ computed here from `entitlement()` rather than beside it — a second copy of
445
+ this rule is how `auth status` came to publish a state the commands
446
+ contradicted (P-2).
447
+ """
448
+ global _license_data, is_pro
449
+ _license_data = _load_license_file()
450
+ is_pro = entitlement()["entitlement"] == "pro"
389
451
 
390
452
 
391
453
  _init()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 3.2.2
3
+ Version: 3.3.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
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=U82Z_Ox36oAqoNY_hG72f5nWmFp6G5hZFIJetg0zXsI,308
1
+ sourcecode/__init__.py,sha256=9RKwt2uA8CQnRB8LqY0DRISYNXIrngyDDSP38-y1QgU,308
2
2
  sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
3
3
  sourcecode/archetype.py,sha256=qWMCyL2UzSGXJhCIZj38JjS7wI15EE3eILsWiaoCZXQ,35779
4
4
  sourcecode/architectural_baseline.py,sha256=7QzJri4pbL3nzAn9gNutZW6mZR8HHD6H2C2H1EEwYPE,17904
@@ -15,7 +15,7 @@ sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,127
15
15
  sourcecode/change_plan.py,sha256=MNgNyu4zrLGvBBaXPwyCh-T2ZGaUQX3Hm4W87uuhZ3w,7750
16
16
  sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
17
17
  sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
18
- sourcecode/cli.py,sha256=kXMCuDtQtL5Px8cHHsSPwgfB9S31mxoGJk84nOQ3vHA,411359
18
+ sourcecode/cli.py,sha256=U3Qcs1XcFo6nOeZpG3khVmz-4XHcb-7K1DZN2R4hupM,411709
19
19
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
20
20
  sourcecode/compare.py,sha256=jdePg0dNCxFpysiTGMsROL5XABLjy_zVUgVeySvdb0o,7514
21
21
  sourcecode/confidence_analyzer.py,sha256=_nvtwO6RM3k0JMuH5m7DsBxOD0PdxGFS4Bb1fTQ536E,21946
@@ -51,7 +51,7 @@ sourcecode/graph_evidence.py,sha256=rENNsYRZeNstX_ExNCLlbHJAruFQwxo5d00x6wO3xwI,
51
51
  sourcecode/hibernate_strat.py,sha256=5GmHiB865HxKdvaxeFV07ojglUgxyOP41AY2xiHfV30,61700
52
52
  sourcecode/integration_coordinates.py,sha256=7vGFxN4tCn_KTisYFJuixa8XkyHBoasEFB3fqM62OBI,7407
53
53
  sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
54
- sourcecode/license.py,sha256=OwLh4x1gU_iGL69zFKAgsnwHO2yx9jBsCOK_WK7CUUw,24907
54
+ sourcecode/license.py,sha256=vvgE1vBUWh8ozWI7USYuteh6noxntnjuzDQIPF-l4rA,27795
55
55
  sourcecode/mcp_nudge.py,sha256=lKemOqK_wny2u7Ymcr2Idi5Kx8pXY02jCi-_nJYLGMg,2992
56
56
  sourcecode/metrics_analyzer.py,sha256=gLoRWKygF18jLgwsqmGXSWopw-f5iO1VbM7Jjdif6ag,22809
57
57
  sourcecode/migrate_check.py,sha256=TJvaShbglENOr8ikvCodnlXQy54TtT05N2ZVe02b3lc,110421
@@ -165,8 +165,8 @@ sourcecode/telemetry/consent.py,sha256=pQdl-QeLl6Gcibn0eWHSKZrm-HYSsjpVqOnjrgFp8
165
165
  sourcecode/telemetry/events.py,sha256=4_yeO58U-Cwc1Qb27VB0_EjhmroY0k91n3_VGxeALB8,2776
166
166
  sourcecode/telemetry/filters.py,sha256=RzxauTz8HliO4BllQnXEXc7zTeqdCZi5MgqGEDuW7OQ,6570
167
167
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
168
- sourcecode-3.2.2.dist-info/METADATA,sha256=jVS3W1ZhWUoj34fE8GVEeg4w8CmEPhtAxCpuJ-qIEV4,19406
169
- sourcecode-3.2.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
170
- sourcecode-3.2.2.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
171
- sourcecode-3.2.2.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
172
- sourcecode-3.2.2.dist-info/RECORD,,
168
+ sourcecode-3.3.0.dist-info/METADATA,sha256=-QNzeVGjrT6BamRBDHef_8YUe7F6CtJzScZvbswMex0,19406
169
+ sourcecode-3.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
170
+ sourcecode-3.3.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
171
+ sourcecode-3.3.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
172
+ sourcecode-3.3.0.dist-info/RECORD,,