ckgraphify 0.2.3__tar.gz → 0.2.4__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 (45) hide show
  1. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/PKG-INFO +1 -1
  2. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/__main__.py +4 -0
  3. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/business_map.py +74 -1
  4. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/graph_main_frontend.py +46 -0
  5. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/pyproject.toml +1 -1
  6. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/LICENSE +0 -0
  7. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/MANIFEST.in +0 -0
  8. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/README.md +0 -0
  9. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/ckgraphify.egg-info/SOURCES.txt +0 -0
  10. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/__init__.py +0 -0
  11. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/analyze.py +0 -0
  12. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/benchmark.py +0 -0
  13. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/bridge_mtop.py +0 -0
  14. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/build.py +0 -0
  15. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/cache.py +0 -0
  16. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/callflow_html.py +0 -0
  17. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/cluster.py +0 -0
  18. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/dedup.py +0 -0
  19. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/detect.py +0 -0
  20. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/export.py +0 -0
  21. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/extract.py +0 -0
  22. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/global_graph.py +0 -0
  23. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/google_workspace.py +0 -0
  24. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/graph_main_backend.py +0 -0
  25. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/graph_main_frontend_sdk.py +0 -0
  26. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/graph_main_html.py +0 -0
  27. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/graph_main_merge.py +0 -0
  28. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/graph_main_trace.py +0 -0
  29. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/hooks.py +0 -0
  30. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/ingest.py +0 -0
  31. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/llm.py +0 -0
  32. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/manifest.py +0 -0
  33. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/repo_registry.py +0 -0
  34. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/report.py +0 -0
  35. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/security.py +0 -0
  36. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/serve.py +0 -0
  37. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/transcribe.py +0 -0
  38. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/tree_html.py +0 -0
  39. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/validate.py +0 -0
  40. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/watch.py +0 -0
  41. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/graphify/wiki.py +0 -0
  42. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/setup.cfg +0 -0
  43. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/skill/__init__.py +0 -0
  44. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/skill/skill-codex.md +0 -0
  45. {ckgraphify-0.2.3 → ckgraphify-0.2.4}/skill/skill.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ckgraphify
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: AI coding assistant skill for Claude Code and Codex - graph-main boundary graphs, multi-repo call chains, business-map concepts, and business search
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/safishamsi/graphify
@@ -1588,6 +1588,10 @@ def main() -> None:
1588
1588
  print("Graph matches:")
1589
1589
  for cand in result.get("graph_matches", [])[:10]:
1590
1590
  print(f"- {cand.get('kind')} {cand.get('label')} repo={cand.get('repo') or '-'} score={cand.get('score')}")
1591
+ if result.get("repo_candidates"):
1592
+ print("Repository routing hints (not evidence):")
1593
+ for cand in result.get("repo_candidates", [])[:10]:
1594
+ print(f"- {cand.get('repo_id')} {cand.get('position') or '-'}: {cand.get('reason') or '-'}")
1591
1595
  print(f"Next action: {result.get('next_action')}")
1592
1596
  if result.get("trace"):
1593
1597
  print("")
@@ -10,7 +10,7 @@ from pathlib import Path
10
10
  from graphify.graph_main_trace import trace_graph_main
11
11
 
12
12
 
13
- _STATUS_ORDER = {"verified": 0, "candidate": 1, "partial": 2, "unexplored": 3, "stale": 4}
13
+ _STATUS_ORDER = {"verified": 0, "boundary": 1, "candidate": 2, "partial": 3, "unexplored": 4, "stale": 5}
14
14
  _KG_BANKS_DIR_NAME = "kg-banks"
15
15
  _KG_BANKS_ROOT_MARKER = "__root__"
16
16
 
@@ -1096,6 +1096,19 @@ def lint_business_map(map_path: Path) -> BusinessLintResult:
1096
1096
  if status == "verified" and require_scope_for_verified and not str(owner.get("verification_scope", "")).strip():
1097
1097
  add("WARN", path, "verified flow should declare verification_scope")
1098
1098
  if status == "verified":
1099
+ has_flow_evidence_navigation = any(
1100
+ isinstance(anchor, dict) and str(anchor.get("ref", "")).strip()
1101
+ for anchor in owner.get("anchors", [])
1102
+ ) or any(
1103
+ isinstance(hint, dict) and str(hint.get("from", "")).strip()
1104
+ for hint in owner.get("trace_hints", [])
1105
+ )
1106
+ if require_scope_for_verified and not has_flow_evidence_navigation:
1107
+ add(
1108
+ "WARN",
1109
+ path,
1110
+ "verified flow has no flow-level anchors or trace_hints; add target-specific evidence navigation or narrow its status/scope",
1111
+ )
1099
1112
  candidate_refs = [
1100
1113
  str(anchor.get("ref", ""))
1101
1114
  for anchor in owner.get("anchors", [])
@@ -1446,6 +1459,7 @@ def query_business(
1446
1459
  ) -> dict:
1447
1460
  data = load_business_map(map_path)
1448
1461
  match = match_business_query(data, query, top_k=top_k)
1462
+ repo_candidates = _repo_profile_candidates(map_path.parent / "repo-profiles.json", query, top_k=top_k)
1449
1463
  result: dict = {
1450
1464
  "query": query,
1451
1465
  "map": str(map_path),
@@ -1454,6 +1468,8 @@ def query_business(
1454
1468
  "business_context": _business_context(data, query),
1455
1469
  "trace": None,
1456
1470
  "graph_matches": [],
1471
+ "repo_candidates": repo_candidates,
1472
+ "repo_profile_notice": "Optional routing hints only; not factual evidence. Missing profiles are normal.",
1457
1473
  "next_action": "",
1458
1474
  }
1459
1475
  best = match.get("best")
@@ -1479,3 +1495,60 @@ def query_business(
1479
1495
  if graph_path is not None and match.get("status") == "none":
1480
1496
  result["graph_matches"] = search_graph_main_matches(graph_path, query, top_k=10)
1481
1497
  return result
1498
+
1499
+
1500
+ def _repo_profile_candidates(profile_path: Path, query: str, top_k: int = 5) -> list[dict]:
1501
+ """Return deterministic search hints without treating profile text as business evidence."""
1502
+ try:
1503
+ catalog = json.loads(profile_path.read_text(encoding="utf-8"))
1504
+ except (OSError, ValueError, TypeError):
1505
+ return []
1506
+ profiles = catalog.get("profiles", []) if isinstance(catalog, dict) else []
1507
+ query_text = str(query or "").strip().lower()
1508
+ if not query_text:
1509
+ return []
1510
+ query_terms = set(_ascii_terms(query_text))
1511
+ candidates: list[tuple[int, str, dict]] = []
1512
+ for profile in profiles if isinstance(profiles, list) else []:
1513
+ if not isinstance(profile, dict):
1514
+ continue
1515
+ fields = {
1516
+ "repo_id": [profile.get("repo_id", "")],
1517
+ "position": [profile.get("position", "")],
1518
+ "aliases": profile.get("aliases", []),
1519
+ "tags": profile.get("tags", []),
1520
+ "capabilities": profile.get("capabilities", []),
1521
+ "summary": [profile.get("summary", "")],
1522
+ }
1523
+ weights = {"repo_id": 12, "position": 10, "aliases": 9, "tags": 7, "capabilities": 6, "summary": 3}
1524
+ score = 0
1525
+ reasons: list[str] = []
1526
+ for field, raw_values in fields.items():
1527
+ values = raw_values if isinstance(raw_values, list) else [raw_values]
1528
+ matched_values: list[str] = []
1529
+ for raw in values:
1530
+ value = str(raw or "").strip()
1531
+ lowered = value.lower()
1532
+ if not lowered:
1533
+ continue
1534
+ direct = lowered in query_text or query_text in lowered
1535
+ cjk_terms = [term for term in re.findall(r"[\u3400-\u9fff]{2,}", lowered) if term in query_text]
1536
+ term_hit = bool(query_terms.intersection(_ascii_terms(lowered))) or bool(cjk_terms)
1537
+ if direct or term_hit:
1538
+ score += weights[field] + (3 if direct else 0)
1539
+ matched_values.append(value)
1540
+ if matched_values:
1541
+ reasons.append(f"{field}: {', '.join(matched_values[:3])}")
1542
+ if score:
1543
+ repo_id = str(profile.get("repo_id", "")).strip()
1544
+ candidates.append((score, repo_id, {
1545
+ "repo_id": repo_id,
1546
+ "position": str(profile.get("position", "")).strip(),
1547
+ "score": score,
1548
+ "reason": "; ".join(reasons),
1549
+ "boundaries": [str(item) for item in profile.get("boundaries", []) if str(item).strip()],
1550
+ "routing_hint_only": True,
1551
+ "supports_factual_claims": False,
1552
+ }))
1553
+ candidates.sort(key=lambda item: (-item[0], item[1]))
1554
+ return [item[2] for item in candidates[:max(1, int(top_k or 5))]]
@@ -37,6 +37,7 @@ _SKIP_DIRS = {
37
37
 
38
38
  _IMPORT_RE = re.compile(r"""(?m)^\s*import\s+.*?\s+from\s+['"]([^'"]+)['"]""")
39
39
  _SIDE_EFFECT_IMPORT_RE = re.compile(r"""(?m)^\s*import\s+['"]([^'"]+)['"]\s*;?\s*$""")
40
+ _EXPORT_FROM_RE = re.compile(r"""(?m)^\s*export\s+(?:\*|\{[^}]*\})\s+from\s+['"]([^'"]+)['"]\s*;?\s*$""")
40
41
  _IMPORT_CLAUSE_RE = re.compile(r"""(?m)^\s*import\s+(.+?)\s+from\s+['"]([^'"]+)['"]\s*;?\s*$""")
41
42
  _REQUIRE_DECL_RE = re.compile(
42
43
  r"""(?m)^\s*(?:const|let|var)\s+(.+?)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)\s*;?\s*$"""
@@ -890,6 +891,15 @@ def _build_local_import_map(repo_root: Path, src_root: Path, code_files: list[Pa
890
891
  imports.add(_norm_rel(repo_root, resolved.resolve()))
891
892
  except OSError:
892
893
  continue
894
+ for m in _EXPORT_FROM_RE.finditer(text):
895
+ raw_imp = m.group(1).strip()
896
+ resolved = _resolve_import(p, raw_imp, src_root)
897
+ if not resolved:
898
+ continue
899
+ try:
900
+ imports.add(_norm_rel(repo_root, resolved.resolve()))
901
+ except OSError:
902
+ continue
893
903
  out[rel] = imports
894
904
  return out
895
905
 
@@ -2522,6 +2532,42 @@ def build_graph_main_frontend(*, repo_root: Path, out_path: Path) -> GraphMainFr
2522
2532
  else:
2523
2533
  detection["sdk_detection"] = sdk_detection.as_dict()
2524
2534
  propagated_file_owners = propagate_file_owners(max_depth=3)
2535
+ if mode in {"frontend-normal", "frontend-mixed"}:
2536
+ for rel, (mtop_apis, rest_apis) in file_api_map.items():
2537
+ owners = propagated_file_owners.get(rel, set())
2538
+ if not owners:
2539
+ continue
2540
+ for owner_nid in sorted(owners):
2541
+ for api_name in sorted(mtop_apis):
2542
+ api_nid = ensure_mtop(api_name, rel, "L1")
2543
+ add_edge(
2544
+ {
2545
+ "source": owner_nid,
2546
+ "target": api_nid,
2547
+ "relation": "calls_mtop",
2548
+ "confidence": "EXTRACTED",
2549
+ "confidence_score": 1.0,
2550
+ "source_file": rel,
2551
+ "source_location": "L1",
2552
+ "weight": 1.0,
2553
+ "evidence": "reachable_local_import",
2554
+ }
2555
+ )
2556
+ for rest_path in sorted(rest_apis):
2557
+ rest_nid = ensure_rest(rest_path, rel, "L1")
2558
+ add_edge(
2559
+ {
2560
+ "source": owner_nid,
2561
+ "target": rest_nid,
2562
+ "relation": "calls_rest",
2563
+ "confidence": "EXTRACTED",
2564
+ "confidence_score": 1.0,
2565
+ "source_file": rel,
2566
+ "source_location": "L1",
2567
+ "weight": 1.0,
2568
+ "evidence": "reachable_local_import",
2569
+ }
2570
+ )
2525
2571
  for dep in collect_sdk_dependencies(repo_root, code_files):
2526
2572
  dep_nid = ensure_sdk_dependency(
2527
2573
  kind=dep.kind,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ckgraphify"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "AI coding assistant skill for Claude Code and Codex - graph-main boundary graphs, multi-repo call chains, business-map concepts, and business search"
9
9
  readme = "README.md"
10
10
  license = "MIT"
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