tri-star-symbolic-assembly-lang 0.1.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.
Files changed (89) hide show
  1. crawler/__init__.py +0 -0
  2. crawler/madmonkey_crawler.py +15 -0
  3. madmonkey/__init__.py +0 -0
  4. madmonkey/intake.py +9 -0
  5. tri_star_symbolic_assembly_lang-0.1.0.dist-info/METADATA +423 -0
  6. tri_star_symbolic_assembly_lang-0.1.0.dist-info/RECORD +89 -0
  7. tri_star_symbolic_assembly_lang-0.1.0.dist-info/WHEEL +5 -0
  8. tri_star_symbolic_assembly_lang-0.1.0.dist-info/entry_points.txt +11 -0
  9. tri_star_symbolic_assembly_lang-0.1.0.dist-info/licenses/LICENSE +63 -0
  10. tri_star_symbolic_assembly_lang-0.1.0.dist-info/top_level.txt +3 -0
  11. tsal/__init__.py +95 -0
  12. tsal/audit/__init__.py +11 -0
  13. tsal/audit/brian_self_audit.py +114 -0
  14. tsal/cli/__init__.py +1 -0
  15. tsal/cli/beast.py +4 -0
  16. tsal/cli/brian.py +4 -0
  17. tsal/cli/brian_optimize.py +6 -0
  18. tsal/cli/meshkeeper.py +4 -0
  19. tsal/cli/party.py +4 -0
  20. tsal/cli/reflect.py +4 -0
  21. tsal/cli/watchdog.py +4 -0
  22. tsal/core/__init__.py +60 -0
  23. tsal/core/connectivity.py +32 -0
  24. tsal/core/constants.py +18 -0
  25. tsal/core/ethics_engine.py +48 -0
  26. tsal/core/executor.py +58 -0
  27. tsal/core/intent_metric.py +17 -0
  28. tsal/core/json_dsl.py +51 -0
  29. tsal/core/logic_gate.py +52 -0
  30. tsal/core/madmonkey_handler.py +10 -0
  31. tsal/core/mesh_logger.py +30 -0
  32. tsal/core/module_registry.py +108 -0
  33. tsal/core/optimizer_utils.py +78 -0
  34. tsal/core/opwords.py +126 -0
  35. tsal/core/phase_math.py +256 -0
  36. tsal/core/phi_math.py +44 -0
  37. tsal/core/reflection.py +104 -0
  38. tsal/core/rev_eng.py +185 -0
  39. tsal/core/spark_translator.py +57 -0
  40. tsal/core/spiral_fusion.py +45 -0
  41. tsal/core/spiral_memory.py +22 -0
  42. tsal/core/spiral_vector.py +39 -0
  43. tsal/core/stack_vm.py +49 -0
  44. tsal/core/state_vector.py +38 -0
  45. tsal/core/symbols.py +70 -0
  46. tsal/core/tokenize_flowchart.py +24 -0
  47. tsal/core/tsal_executor.py +533 -0
  48. tsal/core/voxel.py +16 -0
  49. tsal/renderer/__init__.py +0 -0
  50. tsal/renderer/code_render.py +13 -0
  51. tsal/rl/__init__.py +0 -0
  52. tsal/rl/madmonkey.py +56 -0
  53. tsal/schemas/__init__.py +1 -0
  54. tsal/schemas/python.json +13 -0
  55. tsal/singer/__init__.py +13 -0
  56. tsal/tools/__init__.py +43 -0
  57. tsal/tools/aletheia_checker.py +54 -0
  58. tsal/tools/alignment_guard.py +20 -0
  59. tsal/tools/archetype_fetcher.py +44 -0
  60. tsal/tools/brian/__init__.py +5 -0
  61. tsal/tools/brian/optimizer.py +205 -0
  62. tsal/tools/codec.py +31 -0
  63. tsal/tools/feedback_ingest.py +25 -0
  64. tsal/tools/goal_selector.py +26 -0
  65. tsal/tools/issue_agent.py +67 -0
  66. tsal/tools/kintsugi/__init__.py +1 -0
  67. tsal/tools/kintsugi/kintsugi.py +15 -0
  68. tsal/tools/meshkeeper.py +81 -0
  69. tsal/tools/module_draft.py +54 -0
  70. tsal/tools/party_tricks.py +128 -0
  71. tsal/tools/reflect.py +43 -0
  72. tsal/tools/spiral_audit.py +68 -0
  73. tsal/tools/state_tracker.py +66 -0
  74. tsal/tools/watchdog.py +40 -0
  75. tsal/tristar/__init__.py +4 -0
  76. tsal/tristar/governor.py +56 -0
  77. tsal/tristar/handshake.py +31 -0
  78. tsal/utils/__init__.py +26 -0
  79. tsal/utils/error_dignity.py +20 -0
  80. tsal/utils/fuzzy_spellcheck.py +7 -0
  81. tsal/utils/github_api.py +82 -0
  82. tsal/utils/grammar_db.py +155 -0
  83. tsal/utils/groundnews_api.py +9 -0
  84. tsal/utils/humour_db.py +75 -0
  85. tsal/utils/intent_metrics.py +44 -0
  86. tsal/utils/language_db.py +55 -0
  87. tsal/utils/octopus_api.py +46 -0
  88. tsal/utils/system_status.py +34 -0
  89. tsal/utils/wikipedia_api.py +46 -0
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ """Interactive demo runner for Brian party tricks."""
4
+
5
+ import argparse
6
+ import inspect
7
+ import random
8
+ from typing import Callable, Dict, Tuple, Any
9
+
10
+ from tsal.core.phi_math import (
11
+ corrected_energy,
12
+ phi_wavefunction,
13
+ phase_alignment_potential,
14
+ orbital_radius,
15
+ )
16
+ from tsal.core.intent_metric import calculate_idm
17
+ from tsal.core.spiral_vector import phi_alignment
18
+ from tsal.core.symbols import get_symbol
19
+
20
+
21
+ def orbital_trick(n: int = 1, phi: float = 0.0) -> float:
22
+ """Return corrected orbital energy."""
23
+ return corrected_energy(n, phi)
24
+
25
+
26
+ def phi_trick(complexity: float = 0.5, coherence: float = 0.5) -> float:
27
+ """Return phi alignment score."""
28
+ return phi_alignment(complexity, coherence)
29
+
30
+
31
+ def symbol_trick(symbol: str = "PHI") -> tuple:
32
+ """Lookup a TSAL symbol by name or hex code."""
33
+ try:
34
+ code = int(symbol, 16)
35
+ except ValueError:
36
+ code = None
37
+ if code is None:
38
+ for k, v in get_symbol.__globals__["TSAL_SYMBOLS"].items():
39
+ if v[1] == symbol:
40
+ code = k
41
+ break
42
+ return get_symbol(code) if code is not None else ("?", symbol, "Unknown")
43
+
44
+
45
+ def wavefunction_trick(
46
+ phi: float = 0.0, phi_vacuum: float = 0.0, lam: float = 1.0
47
+ ) -> float:
48
+ """Return φ wavefunction value."""
49
+ return phi_wavefunction(phi, phi_vacuum, lam)
50
+
51
+
52
+ def potential_trick(
53
+ phi: float = 0.0, phi_vacuum: float = 0.0, lam: float = 1.0
54
+ ) -> float:
55
+ """Return phase alignment potential."""
56
+ return phase_alignment_potential(phi, phi_vacuum, lam)
57
+
58
+
59
+ def radius_trick(n: int = 1, phi: float = 0.0) -> float:
60
+ """Return orbital radius."""
61
+ return orbital_radius(n, phi)
62
+
63
+
64
+ def idm_trick(
65
+ info_quality: float = 1.0,
66
+ info_quantity: float = 1.0,
67
+ accuracy: float = 1.0,
68
+ complexity: float = 1.0,
69
+ time_taken: float = 1.0,
70
+ ) -> float:
71
+ """Return Intent-Driven Metric score."""
72
+ return calculate_idm(
73
+ info_quality, info_quantity, accuracy, complexity, time_taken
74
+ )
75
+
76
+
77
+ Func_yTown: Dict[str, Tuple[Callable[..., Any], str]] = {
78
+ "orbital": (orbital_trick, "Calculate orbital energy"),
79
+ "phi-align": (phi_trick, "Phi alignment score"),
80
+ "symbol": (symbol_trick, "TSAL symbol lookup"),
81
+ "wavefunction": (wavefunction_trick, "φ wavefunction"),
82
+ "potential": (potential_trick, "Phase alignment potential"),
83
+ "radius": (radius_trick, "Orbital radius"),
84
+ "idm": (idm_trick, "Intent metric"),
85
+ }
86
+
87
+
88
+ def run_trick(name: str, **kwargs: Any) -> Any:
89
+ if name not in Func_yTown:
90
+ raise KeyError(name)
91
+ func = Func_yTown[name][0]
92
+ sig = inspect.signature(func)
93
+ bound = sig.bind_partial(**kwargs)
94
+ bound.apply_defaults()
95
+ return func(*bound.args, **bound.kwargs)
96
+
97
+
98
+ def main(argv: list[str] | None = None) -> None:
99
+ parser = argparse.ArgumentParser(description="Run a Brian party trick")
100
+ parser.add_argument("name", nargs="?", help="trick name")
101
+ parser.add_argument("args", nargs=argparse.REMAINDER)
102
+ parser.add_argument("--list", action="store_true", dest="list_tricks")
103
+ args = parser.parse_args(argv)
104
+
105
+ if args.list_tricks or not args.name:
106
+ for key, (_, help_text) in Func_yTown.items():
107
+ print(f"{key}: {help_text}")
108
+ return
109
+
110
+ func = Func_yTown.get(args.name)
111
+ if not func:
112
+ raise SystemExit(f"Unknown trick: {args.name}")
113
+
114
+ func_obj = func[0]
115
+ sig = inspect.signature(func_obj)
116
+ params = {}
117
+ for param in sig.parameters.values():
118
+ if args.args:
119
+ value = args.args.pop(0)
120
+ else:
121
+ value = input(f"{param.name} [{param.default}]: ") or param.default
122
+ params[param.name] = type(param.default)(value)
123
+ result = func_obj(**params)
124
+ print(result)
125
+
126
+
127
+ if __name__ == "__main__":
128
+ main()
tsal/tools/reflect.py ADDED
@@ -0,0 +1,43 @@
1
+ from pathlib import Path
2
+ import json
3
+ from tsal.core.rev_eng import Rev_Eng
4
+ from tsal.tools.brian.optimizer import SymbolicOptimizer
5
+
6
+ def reflect(path: str = "src/tsal", as_json: bool = False) -> str:
7
+ """Reconstruct spiral path and summarize changes."""
8
+ opt = SymbolicOptimizer()
9
+ rev = Rev_Eng(origin="reflect")
10
+ report = {}
11
+
12
+ for file in Path(path).rglob("*.py"):
13
+ results = opt.analyze(file.read_text())
14
+ delta = sum(m["delta"] for _, m in results)
15
+ rev.log_event(
16
+ "AUDIT", payload={"file": str(file), "count": len(results)}
17
+ )
18
+ report[str(file)] = delta
19
+
20
+ summary = rev.summary()
21
+ summary["deltas"] = report
22
+ summary["files"] = list(report.keys())
23
+
24
+ return (
25
+ json.dumps(summary, indent=2)
26
+ if as_json
27
+ else "\n".join(f"{k}: Δ{v}" for k, v in report.items())
28
+ )
29
+
30
+ def main() -> None:
31
+ import argparse
32
+
33
+ parser = argparse.ArgumentParser(description="reflect on repo")
34
+ parser.add_argument("path", nargs="?", default="src/tsal")
35
+ parser.add_argument("--origin", dest="origin")
36
+ parser.add_argument("--json", action="store_true")
37
+ args = parser.parse_args()
38
+
39
+ path = args.origin or args.path
40
+ print(reflect(path, as_json=args.json))
41
+
42
+ if __name__ == "__main__":
43
+ main()
@@ -0,0 +1,68 @@
1
+ """Directory auditor using SymbolicOptimizer."""
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+
7
+ from typing import List, Dict
8
+
9
+ from tsal.tools.brian.optimizer import SymbolicOptimizer
10
+
11
+
12
+ def render_markdown(report: Dict[str, int] | Dict[str, Dict[str, int]]) -> str:
13
+ """Return a markdown table for the audit report."""
14
+ if isinstance(report, dict) and "files" in report:
15
+ header = "| Files | Signatures |"
16
+ body = f"| {report['files']} | {report['signatures']} |"
17
+ if "self_signatures" in report:
18
+ header = "| Files | Signatures | Self Signatures |"
19
+ body = (
20
+ f"| {report['files']} | {report['signatures']} | {report['self_signatures']} |"
21
+ )
22
+ divider = "|---|---|" + ("---|" if "self_signatures" in report else "")
23
+ return "\n".join([header, divider, body])
24
+ lines = ["| Path | Files | Signatures |"]
25
+ lines.append("|---|---|---|")
26
+ for path, data in report.items():
27
+ lines.append(f"| {path} | {data['files']} | {data['signatures']} |")
28
+ return "\n".join(lines)
29
+
30
+ def audit_path(path: Path) -> Dict[str, int]:
31
+ opt = SymbolicOptimizer()
32
+ files = list(path.rglob("*.py"))
33
+ sigs = 0
34
+ for file in files:
35
+ sigs += len(opt.analyze(file.read_text()))
36
+ return {"files": len(files), "signatures": sigs}
37
+
38
+
39
+ def audit_paths(paths: List[Path]) -> Dict[str, Dict[str, int]]:
40
+ """Audit multiple directories and return a mapping of results."""
41
+ return {str(p): audit_path(p) for p in paths}
42
+
43
+ def main() -> None:
44
+ parser = argparse.ArgumentParser(description="Spiral audit")
45
+ parser.add_argument("paths", nargs="*", default=["src/tsal"])
46
+ parser.add_argument("--self", action="store_true", dest="self_flag")
47
+ parser.add_argument("--markdown", action="store_true", help="Output markdown table")
48
+ args = parser.parse_args()
49
+ path_objs = [Path(p) for p in args.paths]
50
+ aggregate = audit_paths(path_objs)
51
+ result: Dict[str, int] | Dict[str, Dict[str, int]]
52
+ if len(aggregate) == 1:
53
+ result = next(iter(aggregate.values()))
54
+ else:
55
+ result = aggregate
56
+ if args.self_flag:
57
+ self_report = audit_path(Path("src/tsal"))
58
+ if isinstance(result, dict) and "files" in result:
59
+ result["self_signatures"] = self_report["signatures"]
60
+ else:
61
+ result["self_signatures"] = self_report["signatures"]
62
+ if args.markdown:
63
+ print(render_markdown(result))
64
+ else:
65
+ print(json.dumps(result))
66
+
67
+ if __name__ == "__main__":
68
+ main()
@@ -0,0 +1,66 @@
1
+ """Track WAS_THEN / IS_NOW / WILL_BE states in a YAML log."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+ from typing import Dict, Any
8
+
9
+ import yaml
10
+
11
+ LOG_PATH = Path("state_log.yaml")
12
+
13
+
14
+ def load_log() -> Dict[str, Any]:
15
+ if LOG_PATH.exists():
16
+ with open(LOG_PATH) as f:
17
+ data = yaml.safe_load(f) or {}
18
+ else:
19
+ data = {}
20
+ return data
21
+
22
+
23
+ def save_log(data: Dict[str, Any]) -> None:
24
+ with open(LOG_PATH, "w") as f:
25
+ yaml.safe_dump(data, f)
26
+
27
+
28
+ def update_entry(module: str, was: str | None, now: str | None, will: str | None) -> None:
29
+ data = load_log()
30
+ entry = data.get(module, {})
31
+ if was:
32
+ entry["WAS_THEN"] = was
33
+ if now:
34
+ entry["IS_NOW"] = now
35
+ if will:
36
+ entry["WILL_BE"] = will
37
+ data[module] = entry
38
+ save_log(data)
39
+
40
+
41
+ def show_entry(module: str) -> None:
42
+ data = load_log()
43
+ entry = data.get(module)
44
+ if entry:
45
+ print(yaml.safe_dump({module: entry}, sort_keys=False))
46
+ else:
47
+ print(f"No entry for {module}")
48
+
49
+
50
+ def main() -> None:
51
+ parser = argparse.ArgumentParser(description="Update or view state log")
52
+ parser.add_argument("module")
53
+ parser.add_argument("--was")
54
+ parser.add_argument("--now")
55
+ parser.add_argument("--will")
56
+ parser.add_argument("--show", action="store_true")
57
+ args = parser.parse_args()
58
+
59
+ if args.show:
60
+ show_entry(args.module)
61
+ else:
62
+ update_entry(args.module, args.was, args.now, args.will)
63
+
64
+
65
+ if __name__ == "__main__":
66
+ main()
tsal/tools/watchdog.py ADDED
@@ -0,0 +1,40 @@
1
+ """Minimal watchdog to trigger repair loops."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import time
7
+ from pathlib import Path
8
+
9
+ from tsal.tools.brian import analyze_and_repair
10
+
11
+
12
+ def watch(path: str = "src/tsal", interval: float = 30.0, cycles: int = 0, repair: bool = False) -> None:
13
+ """Monitor ``path`` and run analyze_and_repair on changed files."""
14
+ base = Path(path)
15
+ seen = {f: f.stat().st_mtime for f in base.rglob("*.py")}
16
+ count = 0
17
+ while True:
18
+ for file in base.rglob("*.py"):
19
+ mtime = file.stat().st_mtime
20
+ if file not in seen or mtime > seen[file]:
21
+ analyze_and_repair(str(file), repair=repair)
22
+ seen[file] = mtime
23
+ count += 1
24
+ if cycles and count >= cycles:
25
+ break
26
+ time.sleep(interval)
27
+
28
+
29
+ def main() -> None:
30
+ parser = argparse.ArgumentParser(description="Continuous repair watchdog")
31
+ parser.add_argument("path", nargs="?", default="src/tsal")
32
+ parser.add_argument("--repair", action="store_true")
33
+ parser.add_argument("--interval", type=float, default=30.0)
34
+ parser.add_argument("--cycles", type=int, default=0)
35
+ args = parser.parse_args()
36
+ watch(args.path, interval=args.interval, cycles=args.cycles, repair=args.repair)
37
+
38
+
39
+ if __name__ == "__main__":
40
+ main()
@@ -0,0 +1,4 @@
1
+ from .handshake import handshake
2
+ from .governor import MetaAgent, TriStarGovernor
3
+
4
+ __all__ = ["handshake", "MetaAgent", "TriStarGovernor"]
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Dict, List, Any
5
+
6
+ from ..core.symbols import PHI, PHI_INV
7
+
8
+ @dataclass
9
+ class MetaAgent:
10
+ """Minimal agent state for TriStar governance."""
11
+
12
+ health: int = 100
13
+ entropy: int = 0
14
+ urge: int = 0
15
+ num_agents: int = 0
16
+ proposals: List[Dict[str, Any]] = field(default_factory=list)
17
+ voting_history: List[Dict[str, Any]] = field(default_factory=list)
18
+
19
+ class TriStarGovernor:
20
+ """Simple governor that reacts to mesh anomalies."""
21
+
22
+ def __init__(self) -> None:
23
+ self.patrol_interval = 100
24
+ self.anomaly_threshold = 0.8
25
+ self.response_actions = {
26
+ "high_entropy": self._handle_high_entropy,
27
+ "low_health": self._handle_low_health,
28
+ "spiral_collapse": self._handle_spiral_collapse,
29
+ }
30
+
31
+ def patrol(self, executor: "TSALExecutor") -> List[str]:
32
+ anomalies: List[str] = []
33
+ mesh_resonance = executor._calculate_mesh_resonance()
34
+ if mesh_resonance < executor.PHI_INV:
35
+ anomalies.append("spiral_collapse")
36
+ if executor.meta_agent.entropy > 60:
37
+ anomalies.append("high_entropy")
38
+ if executor.meta_agent.health < 50:
39
+ anomalies.append("low_health")
40
+ return anomalies
41
+
42
+ def _handle_high_entropy(self, executor: "TSALExecutor") -> None:
43
+ if hasattr(executor, "_op_bloom"):
44
+ executor._op_bloom({})
45
+ executor.meta_agent.entropy = max(0, executor.meta_agent.entropy - 10)
46
+
47
+ def _handle_low_health(self, executor: "TSALExecutor") -> None:
48
+ executor.meta_agent.health = min(100, executor.meta_agent.health + 10)
49
+ if hasattr(executor, "meshkeeper_repair"):
50
+ executor.meshkeeper_repair()
51
+
52
+ def _handle_spiral_collapse(self, executor: "TSALExecutor") -> None:
53
+ if hasattr(executor, "_op_spiral"):
54
+ executor._op_spiral({"increment": 1})
55
+ if hasattr(executor, "_op_sync"):
56
+ executor._op_sync({"strength": executor.PHI_INV})
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ """TriStar handshake using φ-field correction and vector logging."""
4
+
5
+ from typing import Optional, Dict
6
+
7
+ from ..core.phi_math import phi_wavefunction, phase_alignment_potential
8
+ from ..core.rev_eng import Rev_Eng
9
+
10
+ def handshake(
11
+ local_phi: float, remote_phi: float, rev: Optional[Rev_Eng] = None
12
+ ) -> Dict[str, float]:
13
+ """Return resonance metrics and optionally log via ``Rev_Eng``."""
14
+ wave_local = phi_wavefunction(local_phi)
15
+ wave_remote = phi_wavefunction(remote_phi)
16
+ delta = remote_phi - local_phi
17
+ potential = phase_alignment_potential(delta)
18
+ resonance = wave_local * wave_remote
19
+ metrics = {
20
+ "delta": delta,
21
+ "potential": potential,
22
+ "resonance": resonance,
23
+ }
24
+ if rev:
25
+ rev.log_event(
26
+ "TRISTAR_HANDSHAKE",
27
+ delta=delta,
28
+ potential=potential,
29
+ resonance=resonance,
30
+ )
31
+ return metrics
tsal/utils/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """Utility modules for TSAL."""
2
+
3
+ from .error_dignity import activate_error_dignity
4
+ from .github_api import fetch_repo_files, fetch_languages
5
+ from .octopus_api import get_products, get_electricity_tariffs
6
+ from .wikipedia_api import search as wiki_search, summary as wiki_summary
7
+ from .groundnews_api import fetch_news, GroundNewsAPIError
8
+ from .intent_metrics import calculate_idm, MetricInputs, timed_idm
9
+ from .system_status import get_status, print_status
10
+
11
+ __all__ = [
12
+ "activate_error_dignity",
13
+ "fetch_repo_files",
14
+ "fetch_languages",
15
+ "get_products",
16
+ "get_electricity_tariffs",
17
+ "wiki_search",
18
+ "wiki_summary",
19
+ "fetch_news",
20
+ "GroundNewsAPIError",
21
+ "calculate_idm",
22
+ "MetricInputs",
23
+ "timed_idm",
24
+ "get_status",
25
+ "print_status",
26
+ ]
@@ -0,0 +1,20 @@
1
+ """Error dignity protocols."""
2
+
3
+ from pathlib import Path
4
+
5
+ ERROR_DIR = Path("errors")
6
+
7
+ def activate_error_dignity(verbose: bool = False) -> None:
8
+ """Activate error dignity protocols.
9
+
10
+ Parameters
11
+ ----------
12
+ verbose : bool, optional
13
+ If True, print status messages. Defaults to False.
14
+ """
15
+ if verbose:
16
+ print("✺ Activating error dignity protocols...")
17
+ print("💥 → ✨ Transforming errors into gifts")
18
+ ERROR_DIR.mkdir(exist_ok=True)
19
+ if verbose:
20
+ print("🌀 Mad monkey learning: ENABLED")
@@ -0,0 +1,7 @@
1
+ from difflib import get_close_matches
2
+ from typing import List
3
+
4
+ def fuzzy_correct(token: str, op_list: List[str]) -> str:
5
+ """Suggest correction for code typos using ops from JSON schema."""
6
+ matches = get_close_matches(token, op_list, n=1, cutoff=0.7)
7
+ return matches[0] if matches else token
@@ -0,0 +1,82 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ try: # optional dependency
4
+ import requests # type: ignore
5
+ except ModuleNotFoundError: # pragma: no cover - fallback used in CI
6
+ import urllib.request
7
+
8
+ class _Response:
9
+ def __init__(self, text: str, status: int = 200) -> None:
10
+ self.text = text
11
+ self.status_code = status
12
+
13
+ def json(self) -> List[Dict]:
14
+ import json
15
+
16
+ return json.loads(self.text)
17
+
18
+ def raise_for_status(self) -> None:
19
+ if self.status_code >= 400:
20
+ raise RuntimeError(f"status {self.status_code}")
21
+
22
+ class requests: # minimal shim for tests
23
+ @staticmethod
24
+ def get(url: str, headers: Dict[str, str] | None = None) -> _Response:
25
+ req = urllib.request.Request(url, headers=headers or {})
26
+ with urllib.request.urlopen(req) as resp:
27
+ text = resp.read().decode()
28
+ return _Response(text, resp.getcode())
29
+
30
+ try:
31
+ import yaml
32
+ except ModuleNotFoundError: # pragma: no cover - simple parser
33
+ yaml = None
34
+
35
+ def _get_json(url: str, headers: Dict[str, str]) -> List[Dict]:
36
+ resp = requests.get(url, headers=headers)
37
+ resp.raise_for_status()
38
+ return resp.json()
39
+
40
+ def fetch_repo_files(
41
+ repo: str,
42
+ extensions: Optional[List[str]] = None,
43
+ token: Optional[str] = None,
44
+ ) -> Dict[str, str]:
45
+ """Fetch files from a GitHub repository via the GitHub API."""
46
+ base = f"https://api.github.com/repos/{repo}/contents"
47
+ headers = {}
48
+ if token:
49
+ headers["Authorization"] = f"token {token}"
50
+
51
+ def recurse(path: str) -> Dict[str, str]:
52
+ url = base + ("/" + path if path else "")
53
+ items = _get_json(url, headers)
54
+ files: Dict[str, str] = {}
55
+ for item in items:
56
+ if item["type"] == "file":
57
+ if extensions is None or any(
58
+ item["name"].endswith(ext) for ext in extensions
59
+ ):
60
+ f = requests.get(item["download_url"], headers=headers)
61
+ if f.status_code == 200:
62
+ files[item["path"]] = f.text
63
+ elif item["type"] == "dir":
64
+ files.update(recurse(item["path"]))
65
+ return files
66
+
67
+ return recurse("")
68
+
69
+ def fetch_languages(
70
+ url: str = "https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml",
71
+ ) -> List[str]:
72
+ """Return the list of programming languages from GitHub's Linguist database."""
73
+ resp = requests.get(url)
74
+ resp.raise_for_status()
75
+ if yaml:
76
+ data = yaml.safe_load(resp.text)
77
+ return list(data.keys())
78
+ langs = []
79
+ for line in resp.text.splitlines():
80
+ if ":" in line:
81
+ langs.append(line.split(":", 1)[0].strip())
82
+ return langs