chronolith-lite 3.2.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.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Ethernium (X: @Steveblackbeard)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: chronolith-lite
3
+ Version: 3.2.0
4
+ Summary: Chronolith (Lite): A professional AI chronolith framework for zero-friction context handoffs.
5
+ Author: SteveBlackbeard
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/SteveBlackbeard/CHRONOLITH-by-Ethernium
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: typer>=0.9.0
14
+ Requires-Dist: rich>=13.0.0
15
+ Dynamic: license-file
16
+
17
+ # Chronolith Lite
18
+
19
+ ![Version](https://img.shields.io/badge/version-3.0.3-blueviolet)
20
+
21
+ Chronolith Lite is part of the Ethernium Chronolith Python runtime.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install chronolith-lite
27
+ ```
28
+
29
+ ## Commands
30
+
31
+ ```bash
32
+ chronolith-lite --help
33
+ ```
34
+
35
+ ## Role
36
+
37
+ Minimal local chronolith for fast handoffs, state checks, and lightweight CLI workflows.
38
+
39
+ ## Governance Boundary
40
+
41
+ Chronolith is the Python runtime and governance kernel. Chronolith Conekta is an external visual control surface, and AgentOps is an extractable incubated tool.
42
+
43
+ ## Quality Gate
44
+
45
+ Before release, run the root repository checks:
46
+
47
+ ```bash
48
+ python scripts/health_guard.py --strict
49
+ python scripts/golden_baseline.py verify
50
+ pytest -q
51
+ python -m build
52
+ python -m twine check dist\*
53
+ ```
@@ -0,0 +1,37 @@
1
+ # Chronolith Lite
2
+
3
+ ![Version](https://img.shields.io/badge/version-3.0.3-blueviolet)
4
+
5
+ Chronolith Lite is part of the Ethernium Chronolith Python runtime.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install chronolith-lite
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ ```bash
16
+ chronolith-lite --help
17
+ ```
18
+
19
+ ## Role
20
+
21
+ Minimal local chronolith for fast handoffs, state checks, and lightweight CLI workflows.
22
+
23
+ ## Governance Boundary
24
+
25
+ Chronolith is the Python runtime and governance kernel. Chronolith Conekta is an external visual control surface, and AgentOps is an extractable incubated tool.
26
+
27
+ ## Quality Gate
28
+
29
+ Before release, run the root repository checks:
30
+
31
+ ```bash
32
+ python scripts/health_guard.py --strict
33
+ python scripts/golden_baseline.py verify
34
+ pytest -q
35
+ python -m build
36
+ python -m twine check dist\*
37
+ ```
@@ -0,0 +1,362 @@
1
+ import logging
2
+ import hashlib
3
+ import os
4
+ import json
5
+ from pathlib import Path
6
+ from datetime import datetime
7
+ from typing import Optional
8
+ import sys
9
+
10
+ import typer
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.table import Table
14
+ from rich.progress import Progress, SpinnerColumn, TextColumn
15
+ from rich import print as rprint
16
+
17
+ def _configure_stdio_for_unicode() -> bool:
18
+ for stream_name in ("stdout", "stderr"):
19
+ stream = getattr(sys, stream_name, None)
20
+ if stream is not None and hasattr(stream, "reconfigure"):
21
+ try:
22
+ stream.reconfigure(encoding="utf-8", errors="replace")
23
+ except Exception:
24
+ pass
25
+ encoding = ((getattr(sys.stdout, "encoding", None) or "") + (getattr(sys.stderr, "encoding", None) or "")).lower()
26
+ return "utf" in encoding
27
+
28
+ UNICODE_OK = _configure_stdio_for_unicode()
29
+
30
+ LITE_ICON = "LITE"
31
+
32
+ # CHRONOLITH Lite (v2.1.0) - Evolution DNA Guardian
33
+ # -------------------------------------------------------------
34
+ # [!] Industrial Grade Refactor: Typer CLI, Rich UI, SHA-256 Signatures, Structured Logs.
35
+
36
+ app = typer.Typer(
37
+ help=f"{LITE_ICON} Chronolith Lite: The professional AI chronolith framework.",
38
+ add_completion=False,
39
+ no_args_is_help=True,
40
+ )
41
+ console = Console(emoji=False)
42
+ def setup_logger(repo_root: Path):
43
+ log_dir = repo_root / ".chronolith" / "logs"
44
+ log_dir.mkdir(parents=True, exist_ok=True)
45
+
46
+ log_file = log_dir / f"chronolith_{datetime.now().strftime('%Y%m%d')}.jsonl"
47
+
48
+ # Custom JSON Formatter
49
+ class JsonFormatter(logging.Formatter):
50
+ def format(self, record):
51
+ log_record = {
52
+ "timestamp": datetime.utcnow().isoformat() + "Z",
53
+ "level": record.levelname,
54
+ "message": record.getMessage(),
55
+ "module": record.module
56
+ }
57
+ if hasattr(record, "dna_hash"):
58
+ log_record["dna_hash"] = record.dna_hash
59
+ return json.dumps(log_record)
60
+
61
+ logger = logging.getLogger("chronolith")
62
+ logger.setLevel(logging.INFO)
63
+
64
+ if not logger.handlers:
65
+ fh = logging.FileHandler(log_file)
66
+ fh.setFormatter(JsonFormatter())
67
+ logger.addHandler(fh)
68
+ return logger
69
+
70
+ ASCII_ART = """
71
+ [bold cyan]
72
+ ______ ____ _ __ ______ ____ _ __ _ __ ____ ______ __ __
73
+ / ____// __ \\ / | / //_ __// _// | / // / / //_ __//_ __/ \\ \\/ /
74
+ / / / / / // |/ / / / / / / |/ // / / / / / / / \\ /
75
+ / /___ / /_/ // /| / / / _/ / / /| // /__/ / / / / / / /
76
+ \\____/ \\____//_/ |_/ /_/ /___//_/ |_/ \\____/ /_/ /_/ /_/
77
+
78
+ LEGACY [bold white]v2.1.0[/bold white] | [italic green]Industrial Evolution DNA Guardian[/italic green]
79
+ [/bold cyan]
80
+ """
81
+
82
+ def calculate_sha256(path: Path | str) -> str:
83
+ path = Path(path) # accept str or Path, matching Pro/Omega's signature
84
+ if not (path.exists() and path.is_file()): return ""
85
+
86
+ # Cross-Platform DNA Protection: Normalize to LF (\n), rstrip lines, and exclude self-referencing badges
87
+ try:
88
+ content = path.read_text(encoding="utf-8")
89
+ except UnicodeDecodeError:
90
+ return ""
91
+
92
+ if path.suffix.lower() == ".md":
93
+ lines = content.splitlines()
94
+ filtered = []
95
+ for l in lines:
96
+ if "DNA CRYSTAL" in l or "img.shields.io/badge/DNA--Crystallized" in l:
97
+ continue
98
+ filtered.append(l.rstrip())
99
+ # Re-join with strict LF and final strip to ensure identical hashes everywhere (Windows/Linux)
100
+ return hashlib.sha256("\n".join(filtered).strip().encode("utf-8")).hexdigest()
101
+
102
+ return hashlib.sha256(path.read_bytes()).hexdigest()
103
+
104
+ def sign_state(data: dict) -> str:
105
+ """Generates a SHA-256 signature for the state record.
106
+ Hardware binding has been removed to support Open Source collaboration across
107
+ different developer machines while maintaining semantic integrity.
108
+ """
109
+ serialized = json.dumps({k: v for k, v in data.items() if k != "signature"}, sort_keys=True)
110
+ return hashlib.sha256(serialized.encode("utf-8")).hexdigest()
111
+
112
+ # Merkle leaf format tag. Bumped when the leaf derivation changes so an upgrade
113
+ # re-baselines once instead of raising a false drift alarm.
114
+ LEAF_FORMAT = "path-bound-v1"
115
+
116
+ def verify_signature(state: dict) -> bool:
117
+ """Recompute the state signature and compare it to the stored one.
118
+
119
+ Without this, `sign_state` is write-only: a signature is stamped but never
120
+ checked, so STATE.json can be edited by hand undetected. A missing signature
121
+ is treated as unverifiable (returns True) so states written before signing
122
+ are not falsely rejected; a present-but-mismatched signature means the record
123
+ was tampered with (returns False)."""
124
+ stored = state.get("signature")
125
+ if not stored:
126
+ return True
127
+ return sign_state(state) == stored
128
+
129
+ def leaf_hash(rel_path: str, content_hash: str) -> str:
130
+ """Bind the filename into the Merkle leaf.
131
+
132
+ Content-only leaves make the root blind to structure: a rename or a content
133
+ swap between two files leaves the root unchanged. Hashing the path with the
134
+ content makes both mutations alter the root."""
135
+ return hashlib.sha256(f"{rel_path}\n{content_hash}".encode("utf-8")).hexdigest()
136
+
137
+ def ensure_file(path: Path, template: str, description: str):
138
+ if not path.exists():
139
+ console.log(f"[yellow][?][/yellow] Missing Nucleotide: [bold]{description}[/bold]")
140
+ path.parent.mkdir(parents=True, exist_ok=True)
141
+ path.write_text(template, encoding="utf-8")
142
+ console.log(f" [green][OK][/green] Re-synthesized: [italic]{path.name}[/italic]")
143
+ return True
144
+ return False
145
+
146
+ def install_hooks(repo_root: Path):
147
+ hook_path = repo_root / ".git" / "hooks" / "pre-push"
148
+ if not (repo_root / ".git").exists():
149
+ console.log("[bold red][!][/bold red] ERROR: Not a git repository. Cannot install hooks.")
150
+ return
151
+
152
+ # Vector 1: Prevenir secuestro de $PATH inyectando ruta absoluta al binario
153
+ abs_python = sys.executable.replace("\\", "/")
154
+ abs_script = Path(__file__).resolve().as_posix()
155
+
156
+ # Vector 3: Mantener politica Fail-Closed (|| exit 1)
157
+ hook_content = f"#!/bin/sh\n# Chronolith Sentinel Guardian (Fail-Closed Security)\necho '[*] Ethernium: Guarding DNA Lineage...'\n\"{abs_python}\" \"{abs_script}\" check\nRESULT=$?\nif [ $RESULT -ne 0 ]; then\n echo '[!] PUSH REJECTED: DNA Drift Detected. Run chronolith-lite check.'\n exit 1\nfi\nexit 0\n"
158
+ hook_path.parent.mkdir(parents=True, exist_ok=True)
159
+ hook_path.write_text(hook_content, encoding="utf-8")
160
+
161
+ if os.name != "nt": os.chmod(hook_path, 0o755)
162
+ console.log(f"[green][OK][/green] Push Hook Guardian (v2.1.0) installed and active.")
163
+
164
+
165
+ def build_merkle_root(hashes: list[str]) -> str:
166
+ """RFC 6962 compliant Merkle Tree with leaf/node prefix hardening."""
167
+ if not hashes: return "0" * 64
168
+ # Leaf nodes: H(0x00 || data)
169
+ current_level = [hashlib.sha256(b"\x00" + h.encode("utf-8")).hexdigest() for h in sorted(hashes)]
170
+ while len(current_level) > 1:
171
+ next_level = []
172
+ if len(current_level) % 2 != 0:
173
+ current_level.append(current_level[-1])
174
+ for i in range(0, len(current_level), 2):
175
+ # Internal nodes: H(0x01 || left || right)
176
+ combined = b"\x01" + (current_level[i] + current_level[i+1]).encode("utf-8")
177
+ next_level.append(hashlib.sha256(combined).hexdigest())
178
+ current_level = next_level
179
+ return current_level[0]
180
+
181
+ @app.command()
182
+ def init(
183
+ repo_root: Path = typer.Option(".", "--repo-root", help="Project root directory."),
184
+ no_hook: bool = typer.Option(False, "--no-hook", help="Disable automatic Git-Hook installation.")
185
+ ):
186
+ """Initialize the Chronolith memory core in the current project."""
187
+ console.print(ASCII_ART)
188
+ logger = setup_logger(repo_root)
189
+
190
+ root = repo_root.resolve()
191
+ logger.info("Initializing Chronolith Core", extra={"repo_root": str(root)})
192
+
193
+ with Progress(
194
+ SpinnerColumn(),
195
+ TextColumn("[progress.description]{task.description}"),
196
+ transient=True,
197
+ ) as progress:
198
+ progress.add_task(description="Forging memory core...", total=None)
199
+
200
+ # Create core files
201
+ ensure_file(root / "PROJECT_CONTEXT.md", "# Project Context\n\n- Define your project core here.", "PROJECT_CONTEXT.md")
202
+ ensure_file(root / "LIVE_HANDOFF.md", "# Live Handoff\n\n- No handoff pending.", "LIVE_HANDOFF.md")
203
+
204
+ # Create and SIGN STATE.json
205
+ state_path = root / "STATE.json"
206
+ if not state_path.exists():
207
+ state_data = {"phase": "stable", "last_update": datetime.utcnow().isoformat()}
208
+ state_data["signature"] = sign_state(state_data)
209
+ state_path.write_text(json.dumps(state_data, indent=2), encoding="utf-8")
210
+ console.log(f" [green][OK][/green] Crystallized + Signed: [italic]STATE.json[/italic]")
211
+
212
+ if not no_hook:
213
+ install_hooks(root)
214
+
215
+ console.print(Panel("[bold green]Success:[/bold green] Chronolith Core crystallized at root level.", title="Init Complete", expand=False))
216
+
217
+ @app.command()
218
+ def check(
219
+ repo_root: Path = typer.Option(".", "--repo-root", help="Project root directory."),
220
+ interactive: bool = typer.Option(False, "--interactive", help="Ask for confirmation on DNA drift."),
221
+ verbose: bool = typer.Option(False, "--verbose", help="Show individual nucleotide hashes.")
222
+ ):
223
+ """Validate the DNA parity, Merkle Root integrity, and state signature."""
224
+ console.print(ASCII_ART)
225
+ root = repo_root.resolve()
226
+
227
+ md_files = []
228
+ CANONICAL_AUDIT_DIRS = [".", "OTHER_LANGUAGES"]
229
+ for audit_dir in CANONICAL_AUDIT_DIRS:
230
+ a_path = root / audit_dir
231
+ if not a_path.exists(): continue
232
+
233
+ if audit_dir == ".":
234
+ for f in a_path.glob("*.md"):
235
+ if "PROJECT_DNA" not in f.name:
236
+ md_files.append(f)
237
+ else:
238
+ for f in a_path.rglob("*.md"):
239
+ if "PROJECT_DNA" not in f.name:
240
+ md_files.append(f)
241
+
242
+ sorted_md_tuples = sorted(
243
+ [(md.relative_to(root).as_posix(), md) for md in md_files],
244
+ key=lambda x: x[0]
245
+ )
246
+
247
+ nucleotides = []
248
+
249
+ if verbose:
250
+ table = Table(title="Nucleotide Audit", show_header=True, header_style="bold yellow")
251
+ table.add_column("File", style="cyan")
252
+ table.add_column("SHA-256 (LF-Norm)")
253
+
254
+ for rel_path, md in sorted_md_tuples:
255
+ content_h = calculate_sha256(md)
256
+ nucleotides.append(leaf_hash(rel_path, content_h))
257
+ if verbose:
258
+ table.add_row(rel_path, content_h[:16])
259
+
260
+ if verbose:
261
+ console.print(table)
262
+
263
+ merkle_root = build_merkle_root(nucleotides)
264
+
265
+ # Logic for DNA Parity Check
266
+ # We compare the current Merkle Root with the one stored in README.md or STATE.json
267
+ # For now, let's just use the current Merkle against the previous if it existed
268
+
269
+ drift_detected = False
270
+ state_path = root / "STATE.json"
271
+ if state_path.exists():
272
+ state = json.loads(state_path.read_text(encoding="utf-8"))
273
+ # Fail-closed: a present-but-invalid signature means STATE.json itself was
274
+ # edited outside the tool. Reject before trusting its stored baseline.
275
+ if not verify_signature(state):
276
+ console.print("[bold red][!] ERROR: STATE.json signature invalid — the state record was tampered with. Aborting.[/bold red]")
277
+ raise typer.Exit(code=1)
278
+ if "merkle_root" in state and state.get("leaf_format") != LEAF_FORMAT:
279
+ # Integrity leaf format was upgraded: re-baseline once rather than
280
+ # reporting the format change as semantic drift.
281
+ console.print(f"[yellow][i] Merkle leaf format upgraded ({state.get('leaf_format') or 'legacy'} -> {LEAF_FORMAT}); re-crystallizing baseline.[/yellow]")
282
+ elif "merkle_root" in state and state["merkle_root"] != merkle_root:
283
+ drift_detected = True
284
+ console.print(f"[bold yellow][!] DNA DRIFT DETECTED:[/bold yellow]")
285
+ console.print(f" Current: [cyan]{merkle_root[:16]}[/cyan]")
286
+ console.print(f" Expected: [magenta]{state['merkle_root'][:16]}[/magenta]")
287
+
288
+ if drift_detected:
289
+ if interactive:
290
+ confirm = typer.confirm("WARNING: DNA mismatch detected. This might indicate unauthorized changes. Proceed anyway?", default=False)
291
+ if not confirm:
292
+ console.print("[bold red]Aborting as requested.[/bold red]")
293
+ raise typer.Exit(code=1)
294
+ else:
295
+ console.print("[bold yellow]Proceeding despite DNA drift...[/bold yellow]")
296
+ else:
297
+ # Non-interactive mode (CI) is Fail-Closed
298
+ console.print("[bold red][!] ERROR: DNA Drift detected in non-interactive mode. Aborting.[/bold red]")
299
+ raise typer.Exit(code=1)
300
+
301
+ # If we are here, either there's no drift or the user said 'Yes' in interactive mode
302
+ # Update STATE.json with the new Merkle Root to 'crystallize' it
303
+ if state_path.exists():
304
+ state = json.loads(state_path.read_text(encoding="utf-8"))
305
+ state["merkle_root"] = merkle_root
306
+ state["leaf_format"] = LEAF_FORMAT
307
+ state["last_check"] = datetime.utcnow().isoformat()
308
+ state["signature"] = sign_state(state)
309
+ state_path.write_text(json.dumps(state, indent=2), encoding="utf-8")
310
+
311
+ console.print(Panel(f"[bold green]Parity Confirmed:[/bold green] Merkle Root `{merkle_root[:16]}...`", title="DNA Guardian", expand=False))
312
+
313
+ @app.command()
314
+ def status(
315
+ repo_root: Path = typer.Option(".", "--repo-root", help="Project root directory.")
316
+ ):
317
+ """View the current status of the logical lineage."""
318
+ root = repo_root.resolve()
319
+ state_path = root / "STATE.json"
320
+
321
+ if not state_path.exists():
322
+ console.print("[yellow]Chronolith not initialized here. Use `init` first.[/yellow]")
323
+ return
324
+
325
+ state = json.loads(state_path.read_text(encoding="utf-8"))
326
+ signature_ok = verify_signature(state)
327
+
328
+ table = Table(title="Lineage Status", show_header=True, header_style="bold magenta")
329
+ table.add_column("Property", style="dim")
330
+ table.add_column("Value")
331
+
332
+ for k, v in state.items():
333
+ table.add_row(k, str(v))
334
+
335
+ table.add_row(
336
+ "signature_valid",
337
+ "[green]yes[/green]" if signature_ok else "[bold red]NO — tampered[/bold red]",
338
+ )
339
+ console.print(table)
340
+
341
+ @app.command()
342
+ def log(
343
+ intent: str = typer.Argument(..., help="Detailed session intent capture."),
344
+ repo_root: Path = typer.Option(".", "--repo-root", help="Project root directory.")
345
+ ):
346
+ """Capture session intent into the permanent timeline."""
347
+ root = repo_root.resolve()
348
+ log_path = root / "SESSION_LOG.md"
349
+
350
+ if not log_path.exists():
351
+ log_path.write_text("# Chronolith Session Log\n\n", encoding="utf-8")
352
+
353
+ with open(log_path, "a", encoding="utf-8") as f:
354
+ f.write(f"- [{datetime.utcnow().isoformat()}Z] {intent}\n")
355
+
356
+ console.log(f"[green][OK][/green] Intent captured in SESSION_LOG.md")
357
+
358
+ def main():
359
+ app()
360
+
361
+ if __name__ == "__main__":
362
+ main()
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+ import argparse
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import sys
7
+ from pathlib import Path
8
+ from datetime import datetime
9
+
10
+ # CHRONOLITH: Active Universal Translation Sync
11
+ # ----------------------------------------------------
12
+ # This script manages multilingual documentation across the 4 levels:
13
+ # Root, Pro, Lite, and Omega. It detects drift and can auto-generate READMEs.
14
+
15
+ LANG_CODES = ["es", "ja", "ru", "zh", "fr", "it", "de", "pt", "en"]
16
+
17
+ def calculate_md5(path: Path) -> str:
18
+ if not path.exists(): return ""
19
+ return hashlib.md5(path.read_bytes()).hexdigest()
20
+
21
+ def get_edition_name(root: Path) -> str:
22
+ # Detect which edition we are in
23
+ if (root / "CHRONOLITH Pro").exists(): return "Root Portal"
24
+ if "Pro" in root.name: return "Pro Edition"
25
+ if "Lite" in root.name: return "Lite Edition"
26
+ if "Omega" in root.name: return "Omega Edition"
27
+ return "Universal Core"
28
+
29
+ def generate_localized_readme(lang, edition_name, source_content):
30
+ # Professional localized templates
31
+ templates = {
32
+ "es": f"# CHRONOLITH: {edition_name}\n\nVersión localizada del framework de continuidad técnica.",
33
+ "ja": f"# CHRONOLITH: {edition_name}\n\nテクニカル・コンティニュイティ・フレームワークのローカライズ版。",
34
+ "ru": f"# CHRONOLITH: {edition_name}\n\nЛокализованная версия фреймворка технической непрерывности.",
35
+ "zh": f"# CHRONOLITH: {edition_name}\n\n技术连续性框架的本地化版本。",
36
+ }
37
+
38
+ # Generic fallback
39
+ base = templates.get(lang, f"# CHRONOLITH: {edition_name}\n\nLocalized version of the technical chronolith framework.")
40
+
41
+ # Add strategic metadata footer
42
+ footer = f"\n\n---\n*CHRONOLITH: Global Infrastructure - Generated {datetime.utcnow().isoformat()}Z*"
43
+ return base + footer
44
+
45
+ def sync_all(repo_root: Path, auto_gen: bool):
46
+ edition = get_edition_name(repo_root)
47
+ source_path = repo_root / "README.md"
48
+
49
+ if not source_path.exists():
50
+ print(f"[!] ERROR: Source README.md not found in {repo_root}")
51
+ return
52
+
53
+ print(f"[*] SYNC: Analyzing {edition} in {repo_root}...")
54
+ source_hash = calculate_md5(source_path)
55
+
56
+ lang_dir = repo_root / "OTHER_LANGUAGES"
57
+ lang_dir.mkdir(parents=True, exist_ok=True)
58
+
59
+ for lang in LANG_CODES:
60
+ target_path = lang_dir / f"README_{lang}.md"
61
+
62
+ if auto_gen:
63
+ print(f" -> Updating {lang} README...")
64
+ content = generate_localized_readme(lang, edition, source_path.read_text(encoding="utf-8"))
65
+ target_path.write_text(content, encoding="utf-8")
66
+
67
+ print(f"[✔] SYNC: {edition} is now globally synchronized.")
68
+
69
+ def main() -> None:
70
+ parser = argparse.ArgumentParser(description="Active Universal Translation Sync.")
71
+ parser.add_argument("--repo-root", default=".")
72
+ parser.add_argument("--auto-generate", action="store_true", help="Automatically generate/update localized files.")
73
+ args = parser.parse_args()
74
+
75
+ root = Path(args.repo_root).resolve()
76
+ sync_all(root, args.auto_generate)
77
+
78
+ if __name__ == "__main__":
79
+ main()
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: chronolith-lite
3
+ Version: 3.2.0
4
+ Summary: Chronolith (Lite): A professional AI chronolith framework for zero-friction context handoffs.
5
+ Author: SteveBlackbeard
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/SteveBlackbeard/CHRONOLITH-by-Ethernium
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: typer>=0.9.0
14
+ Requires-Dist: rich>=13.0.0
15
+ Dynamic: license-file
16
+
17
+ # Chronolith Lite
18
+
19
+ ![Version](https://img.shields.io/badge/version-3.0.3-blueviolet)
20
+
21
+ Chronolith Lite is part of the Ethernium Chronolith Python runtime.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install chronolith-lite
27
+ ```
28
+
29
+ ## Commands
30
+
31
+ ```bash
32
+ chronolith-lite --help
33
+ ```
34
+
35
+ ## Role
36
+
37
+ Minimal local chronolith for fast handoffs, state checks, and lightweight CLI workflows.
38
+
39
+ ## Governance Boundary
40
+
41
+ Chronolith is the Python runtime and governance kernel. Chronolith Conekta is an external visual control surface, and AgentOps is an extractable incubated tool.
42
+
43
+ ## Quality Gate
44
+
45
+ Before release, run the root repository checks:
46
+
47
+ ```bash
48
+ python scripts/health_guard.py --strict
49
+ python scripts/golden_baseline.py verify
50
+ pytest -q
51
+ python -m build
52
+ python -m twine check dist\*
53
+ ```
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ chronolith_lite.egg-info/PKG-INFO
5
+ chronolith_lite.egg-info/SOURCES.txt
6
+ chronolith_lite.egg-info/dependency_links.txt
7
+ chronolith_lite.egg-info/entry_points.txt
8
+ chronolith_lite.egg-info/requires.txt
9
+ chronolith_lite.egg-info/top_level.txt
10
+ chronolith_lite/chronolith/run_chronolith_lite.py
11
+ chronolith_lite/chronolith/sync_translations.py
12
+ tests/test_lite_logic.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ chronolith-lite = chronolith_lite.chronolith.run_chronolith_lite:main
@@ -0,0 +1,2 @@
1
+ typer>=0.9.0
2
+ rich>=13.0.0
@@ -0,0 +1 @@
1
+ chronolith_lite
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "chronolith-lite"
7
+ version = "3.2.0"
8
+ authors = [
9
+ { name = "SteveBlackbeard" },
10
+ ]
11
+ description = "Chronolith (Lite): A professional AI chronolith framework for zero-friction context handoffs."
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ license = "MIT"
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ dependencies = [
20
+ "typer>=0.9.0",
21
+ "rich>=13.0.0",
22
+ ]
23
+
24
+ [project.urls]
25
+ "Homepage" = "https://github.com/SteveBlackbeard/CHRONOLITH-by-Ethernium"
26
+
27
+ [project.scripts]
28
+ chronolith-lite = "chronolith_lite.chronolith.run_chronolith_lite:main"
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["."]
32
+ include = ["chronolith_lite*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,126 @@
1
+ import hashlib
2
+ import pytest
3
+ from pathlib import Path
4
+ import sys
5
+
6
+ # CHRONOLITH: Logic Audit Tests (v2.1.1 - RFC 6962 Hardened)
7
+ # ------------------------------------------------------------------
8
+
9
+ sys.path.append(str(Path(__file__).parent.parent / 'chronolith_lite' / 'chronolith'))
10
+
11
+ from run_chronolith_lite import (
12
+ build_merkle_root,
13
+ calculate_sha256,
14
+ sign_state,
15
+ verify_signature,
16
+ leaf_hash,
17
+ )
18
+
19
+ # --- SHA-256 Tests ---
20
+
21
+ def test_calculate_sha256(tmp_path):
22
+ test_file = tmp_path / "test.txt"
23
+ content = b"Ethernium Chronolith"
24
+ test_file.write_bytes(content)
25
+ expected = hashlib.sha256(content).hexdigest()
26
+ assert calculate_sha256(test_file) == expected
27
+
28
+ def test_calculate_sha256_missing():
29
+ assert calculate_sha256(Path("non_existent_phantom.txt")) == ""
30
+
31
+ def test_calculate_sha256_accepts_str_path(tmp_path):
32
+ # API parity with Pro/Omega: accept a plain string, not only a Path.
33
+ f = tmp_path / "x.txt"
34
+ f.write_bytes(b"content")
35
+ assert calculate_sha256(str(f)) == calculate_sha256(f)
36
+
37
+ # --- Merkle Tree Tests (RFC 6962 compliant) ---
38
+
39
+ def test_build_merkle_root_empty():
40
+ assert build_merkle_root([]) == "0" * 64
41
+
42
+ def test_build_merkle_root_single():
43
+ h1 = "abc123"
44
+ # Single node: leaf hash = H(0x00 || "abc123")
45
+ expected = hashlib.sha256(b"\x00" + b"abc123").hexdigest()
46
+ assert build_merkle_root([h1]) == expected
47
+
48
+ def test_build_merkle_root_even():
49
+ # 2 nodes: sorted ["a", "b"]
50
+ # Leaf: L0 = H(0x00 || "a"), L1 = H(0x00 || "b")
51
+ # Root: H(0x01 || L0 || L1)
52
+ L0 = hashlib.sha256(b"\x00a").hexdigest()
53
+ L1 = hashlib.sha256(b"\x00b").hexdigest()
54
+ expected = hashlib.sha256(b"\x01" + (L0 + L1).encode("utf-8")).hexdigest()
55
+ assert build_merkle_root(["b", "a"]) == expected
56
+
57
+ def test_build_merkle_root_odd():
58
+ # 3 nodes sorted: ["a", "b", "c"]
59
+ # Leaves: L0=H(0x00||"a"), L1=H(0x00||"b"), L2=H(0x00||"c")
60
+ # Padded: L3 = L2 (duplicate)
61
+ # Level 1: N0=H(0x01||L0||L1), N1=H(0x01||L2||L3)
62
+ # Root: H(0x01||N0||N1)
63
+ L0 = hashlib.sha256(b"\x00a").hexdigest()
64
+ L1 = hashlib.sha256(b"\x00b").hexdigest()
65
+ L2 = hashlib.sha256(b"\x00c").hexdigest()
66
+ N0 = hashlib.sha256(b"\x01" + (L0 + L1).encode("utf-8")).hexdigest()
67
+ N1 = hashlib.sha256(b"\x01" + (L2 + L2).encode("utf-8")).hexdigest()
68
+ expected = hashlib.sha256(b"\x01" + (N0 + N1).encode("utf-8")).hexdigest()
69
+ assert build_merkle_root(["c", "b", "a"]) == expected
70
+
71
+ def test_merkle_deterministic_order():
72
+ """Tree must be order-independent (sorted internally)."""
73
+ assert build_merkle_root(["z", "a", "m"]) == build_merkle_root(["a", "m", "z"])
74
+
75
+ # --- State Signature Tests (Expert #1: Cybersecurity) ---
76
+
77
+ def test_sign_state_deterministic():
78
+ data = {"phase": "stable", "last_update": "2026-01-01T00:00:00"}
79
+ sig1 = sign_state(data)
80
+ sig2 = sign_state(data)
81
+ assert sig1 == sig2
82
+ assert len(sig1) == 64 # SHA-256 hex digest
83
+
84
+ def test_sign_state_excludes_signature_field():
85
+ data = {"phase": "stable", "signature": "old_sig"}
86
+ sig = sign_state(data)
87
+ # Changing the signature field should NOT change the hash
88
+ data2 = {"phase": "stable", "signature": "different_sig"}
89
+ assert sign_state(data2) == sig
90
+
91
+ def test_sign_state_detects_tampering():
92
+ data = {"phase": "stable", "last_update": "2026-01-01T00:00:00"}
93
+ sig = sign_state(data)
94
+ data["phase"] = "compromised"
95
+ assert sign_state(data) != sig
96
+
97
+ # --- Signature Verification (closes the write-only-signature gap) ---
98
+
99
+ def test_verify_signature_accepts_valid():
100
+ data = {"phase": "stable", "last_update": "2026-01-01T00:00:00"}
101
+ data["signature"] = sign_state(data)
102
+ assert verify_signature(data) is True
103
+
104
+ def test_verify_signature_rejects_tampered_state():
105
+ data = {"phase": "stable", "last_update": "2026-01-01T00:00:00"}
106
+ data["signature"] = sign_state(data)
107
+ data["merkle_root"] = "deadbeef" * 8 # edited after signing
108
+ assert verify_signature(data) is False
109
+
110
+ def test_verify_signature_absent_is_unverifiable_not_rejected():
111
+ assert verify_signature({"phase": "stable"}) is True
112
+
113
+ # --- Path-Bound Merkle Leaves (root reacts to renames / content swaps) ---
114
+
115
+ def test_leaf_hash_binds_filename():
116
+ content_h = calculate_sha256(Path("non_existent_phantom.txt")) or "abc"
117
+ assert leaf_hash("A.md", content_h) != leaf_hash("B.md", content_h)
118
+
119
+ def test_leaf_hash_content_swap_changes_root():
120
+ # Two files swapping content must change the root when leaves are path-bound.
121
+ ha, hb = "hash_a", "hash_b"
122
+ original = build_merkle_root([leaf_hash("A.md", ha), leaf_hash("B.md", hb)])
123
+ swapped = build_merkle_root([leaf_hash("A.md", hb), leaf_hash("B.md", ha)])
124
+ assert original != swapped
125
+
126
+