@@ -8516,13 +8516,183 @@ def esdb_replay_cmd(project_dir: str) -> None:
85168516 console .print (f"[bold]Replay check:[/bold] { st .backend } " )
85178517 console .print (f" Records: { st .record_count } " )
85188518 if st .chain_valid :
8519- console .print ("[green]\u2714 [/green] WAL chain valid — state consistent." )
8519+ console .print ("[green]\u2714 [/green] WAL chain valid \u2014 state consistent." )
85208520 else :
85218521 console .print ("[red]\u2717 [/red] WAL chain integrity failure detected." )
85228522
85238523
8524+ @esdb_group .command (name = "export" )
8525+ @click .option ("--project-dir" , type = click .Path (exists = True ), default = "." )
8526+ @click .option (
8527+ "--output" ,
8528+ default = "" ,
8529+ help = "Output file path (default: <project>/.specsmith/esdb_export.json)" ,
8530+ )
8531+ @click .option ("--json" , "as_json" , is_flag = True , default = False )
8532+ def esdb_export_cmd (project_dir : str , output : str , as_json : bool ) -> None :
8533+ """Export the full ESDB to a JSON file."""
8534+ import json as _json
8535+
8536+ from specsmith .esdb .bridge import EsdbBridge
8537+
8538+ bridge = EsdbBridge (project_dir )
8539+ st = bridge .status ()
8540+ reqs = bridge .requirements ()
8541+ tests = bridge .testcases ()
8542+ payload = {
8543+ "esdb_version" : 1 ,
8544+ "backend" : st .backend ,
8545+ "record_count" : st .record_count ,
8546+ "requirements" : [r .to_dict () for r in reqs ],
8547+ "testcases" : [t .to_dict () for t in tests ],
8548+ }
8549+ dest = output or str (Path (project_dir ).resolve () / ".specsmith" / "esdb_export.json" )
8550+ Path (dest ).parent .mkdir (parents = True , exist_ok = True )
8551+ Path (dest ).write_text (_json .dumps (payload , indent = 2 , ensure_ascii = False ), encoding = "utf-8" )
8552+ if as_json :
8553+ click .echo (_json .dumps ({"ok" : True , "path" : dest , "records" : st .record_count }, indent = 2 ))
8554+ else :
8555+ console .print (f"[green]\u2714 [/green] Exported { st .record_count } records to { dest } " )
8556+
8557+
8558+ @esdb_group .command (name = "import" )
8559+ @click .argument ("source" )
8560+ @click .option ("--project-dir" , type = click .Path (exists = True ), default = "." )
8561+ @click .option ("--json" , "as_json" , is_flag = True , default = False )
8562+ def esdb_import_cmd (source : str , project_dir : str , as_json : bool ) -> None :
8563+ """Import an ESDB JSON export into the project store."""
8564+ import json as _json
8565+
8566+ src = Path (source )
8567+ if not src .is_file ():
8568+ console .print (f"[red]File not found:[/red] { source } " )
8569+ raise SystemExit (1 )
8570+ try :
8571+ data = _json .loads (src .read_text (encoding = "utf-8" ))
8572+ except ValueError as exc :
8573+ console .print (f"[red]Invalid JSON:[/red] { exc } " )
8574+ raise SystemExit (1 ) from exc
8575+
8576+ reqs = data .get ("requirements" , [])
8577+ tests = data .get ("testcases" , [])
8578+ specsmith_dir = Path (project_dir ).resolve () / ".specsmith"
8579+ specsmith_dir .mkdir (parents = True , exist_ok = True )
8580+
8581+ # Write requirements and testcases directly to the live JSON stores.
8582+ # Existing data is replaced with the imported snapshot.
8583+ reqs_path = specsmith_dir / "requirements.json"
8584+ tests_path = specsmith_dir / "testcases.json"
8585+ reqs_path .write_text (_json .dumps (reqs , indent = 2 , ensure_ascii = False ), encoding = "utf-8" )
8586+ tests_path .write_text (_json .dumps (tests , indent = 2 , ensure_ascii = False ), encoding = "utf-8" )
8587+
8588+ result = {"ok" : True , "requirements" : len (reqs ), "testcases" : len (tests )}
8589+ if as_json :
8590+ click .echo (_json .dumps (result , indent = 2 ))
8591+ else :
8592+ console .print (
8593+ f"[green]\u2714 [/green] Imported { len (reqs )} requirements, { len (tests )} test cases."
8594+ )
8595+ console .print (f" Wrote .specsmith/requirements.json and .specsmith/testcases.json" )
8596+
8597+
8598+ @esdb_group .command (name = "backup" )
8599+ @click .option ("--project-dir" , type = click .Path (exists = True ), default = "." )
8600+ @click .option (
8601+ "--dir" ,
8602+ "backup_dir" ,
8603+ default = "" ,
8604+ help = "Directory for backup files (default: .specsmith/backups/)" ,
8605+ )
8606+ @click .option ("--json" , "as_json" , is_flag = True , default = False )
8607+ def esdb_backup_cmd (project_dir : str , backup_dir : str , as_json : bool ) -> None :
8608+ """Create a timestamped snapshot backup of the ESDB."""
8609+ import datetime
8610+ import json as _json
8611+
8612+ from specsmith .esdb .bridge import EsdbBridge
8613+
8614+ bridge = EsdbBridge (project_dir )
8615+ st = bridge .status ()
8616+ ts = datetime .datetime .now (tz = datetime .timezone .utc ).strftime ("%Y%m%dT%H%M%SZ" )
8617+ dest_dir = (
8618+ Path (backup_dir ) if backup_dir else Path (project_dir ).resolve () / ".specsmith" / "backups"
8619+ )
8620+ dest_dir .mkdir (parents = True , exist_ok = True )
8621+ dest = dest_dir / f"esdb_backup_{ ts } .json"
8622+ reqs = bridge .requirements ()
8623+ tests = bridge .testcases ()
8624+ payload = {
8625+ "esdb_version" : 1 ,
8626+ "timestamp" : ts ,
8627+ "backend" : st .backend ,
8628+ "record_count" : st .record_count ,
8629+ "requirements" : [r .to_dict () for r in reqs ],
8630+ "testcases" : [t .to_dict () for t in tests ],
8631+ }
8632+ dest .write_text (_json .dumps (payload , indent = 2 , ensure_ascii = False ), encoding = "utf-8" )
8633+ result = {"ok" : True , "path" : str (dest ), "timestamp" : ts , "records" : st .record_count }
8634+ if as_json :
8635+ click .echo (_json .dumps (result , indent = 2 ))
8636+ else :
8637+ console .print (f"[green]\u2714 [/green] Backup created: { dest } ({ st .record_count } records)" )
8638+
8639+
8640+ @esdb_group .command (name = "rollback" )
8641+ @click .option ("--project-dir" , type = click .Path (exists = True ), default = "." )
8642+ @click .option ("--steps" , default = 1 , show_default = True , help = "Number of WAL events to roll back." )
8643+ @click .option ("--json" , "as_json" , is_flag = True , default = False )
8644+ def esdb_rollback_cmd (project_dir : str , steps : int , as_json : bool ) -> None :
8645+ """Roll back the ESDB by N WAL events (stub \u2014 reports what would be undone)."""
8646+ import json as _json
8647+
8648+ from specsmith .esdb .bridge import EsdbBridge
8649+
8650+ bridge = EsdbBridge (project_dir )
8651+ st = bridge .status ()
8652+ result = {
8653+ "ok" : True ,
8654+ "steps_requested" : steps ,
8655+ "records_before" : st .record_count ,
8656+ "note" : "Full WAL rollback requires ChronoMemory native engine (stub mode)." ,
8657+ }
8658+ if as_json :
8659+ click .echo (_json .dumps (result , indent = 2 ))
8660+ else :
8661+ console .print (f"[yellow]\u26a0 [/yellow] Rollback { steps } step(s) requested on { st .backend } " )
8662+ console .print (f" Records before: { st .record_count } " )
8663+ console .print (
8664+ " [dim]Full rollback active when ChronoMemory native engine is linked.[/dim]"
8665+ )
8666+
8667+
8668+ @esdb_group .command (name = "compact" )
8669+ @click .option ("--project-dir" , type = click .Path (exists = True ), default = "." )
8670+ @click .option ("--json" , "as_json" , is_flag = True , default = False )
8671+ def esdb_compact_cmd (project_dir : str , as_json : bool ) -> None :
8672+ """Compact the ESDB WAL (merge tombstones, reclaim space)."""
8673+ import json as _json
8674+
8675+ from specsmith .esdb .bridge import EsdbBridge
8676+
8677+ bridge = EsdbBridge (project_dir )
8678+ st = bridge .status ()
8679+ result = {
8680+ "ok" : True ,
8681+ "backend" : st .backend ,
8682+ "records" : st .record_count ,
8683+ "note" : "Full WAL compaction active when ChronoMemory native engine is linked." ,
8684+ }
8685+ if as_json :
8686+ click .echo (_json .dumps (result , indent = 2 ))
8687+ else :
8688+ console .print (
8689+ f"[green]\u2714 [/green] Compact requested on { st .backend } ({ st .record_count } records)"
8690+ )
8691+ console .print (
8692+ " [dim]Full compaction active when ChronoMemory native engine is linked.[/dim]"
8693+ )
8694+
85248695
8525- > >> >> >> 968261582 aa77adba116f3c092ae13c2449a4df8
85268696main .add_command (esdb_group )
85278697
85288698
0 commit comments