@@ -326,6 +326,96 @@ def _engram_from_memory(mem: Dict[str, Any]) -> Dict[str, Any]:
326326 }
327327
328328
329+ _AUTO_MEMORY_TIER_BY_TYPE = {
330+ "user" : "high" ,
331+ "feedback" : "high" ,
332+ "project" : "medium" ,
333+ "reference" : "medium" ,
334+ }
335+
336+
337+ def _parse_auto_memory_file (path : Path ) -> Optional [Dict [str , Any ]]:
338+ try :
339+ text = path .read_text (encoding = "utf-8" )
340+ except OSError :
341+ return None
342+ name = path .stem
343+ description = ""
344+ mem_type = "project"
345+ body = text
346+ if text .startswith ("---" ):
347+ end = text .find ("\n ---" , 3 )
348+ if end != - 1 :
349+ header = text [3 :end ]
350+ body = text [end + 4 :].lstrip ("\n " )
351+ for line in header .splitlines ():
352+ key , sep , value = line .partition (":" )
353+ if not sep :
354+ continue
355+ key = key .strip ().lower ()
356+ value = value .strip ().strip ('"' ).strip ("'" )
357+ if key == "name" and value :
358+ name = value
359+ elif key == "description" and value :
360+ description = value
361+ elif key == "type" and value :
362+ mem_type = value
363+ body = body .strip ()
364+ if not body and not description :
365+ return None
366+ content_parts : List [str ] = [f"[{ mem_type } ] { name } " ]
367+ if description :
368+ content_parts .append (description )
369+ if body :
370+ content_parts .append (body )
371+ content = "\n \n " .join (content_parts )
372+ try :
373+ created = datetime .fromtimestamp (path .stat ().st_mtime , tz = timezone .utc ).strftime ("%Y-%m-%d" )
374+ except OSError :
375+ created = time .strftime ("%Y-%m-%d" )
376+ tier = _AUTO_MEMORY_TIER_BY_TYPE .get (mem_type , "medium" )
377+ file_id = hashlib .sha1 (str (path ).encode ("utf-8" )).hexdigest ()[:16 ]
378+ return {
379+ "id" : f"auto:{ file_id } " ,
380+ "tier" : tier ,
381+ "content" : content ,
382+ "source" : "claude_auto_memory" ,
383+ "created" : created ,
384+ "tags" : [mem_type , "auto-memory" ],
385+ "decay" : 1.0 ,
386+ "reaffirmed" : 0 ,
387+ "tokens" : _estimate_tokens (content ),
388+ }
389+
390+
391+ def _auto_memory_engrams () -> List [Dict [str , Any ]]:
392+ roots = [Path .home () / ".claude" / "projects" ]
393+ engrams : List [Dict [str , Any ]] = []
394+ for root in roots :
395+ if not root .is_dir ():
396+ continue
397+ try :
398+ project_dirs = [p for p in root .iterdir () if p .is_dir ()]
399+ except OSError :
400+ continue
401+ for project_dir in project_dirs :
402+ memory_dir = project_dir / "memory"
403+ if not memory_dir .is_dir ():
404+ continue
405+ try :
406+ files = [p for p in memory_dir .iterdir () if p .is_file () and p .suffix == ".md" ]
407+ except OSError :
408+ continue
409+ for path in files :
410+ if path .name .upper () == "MEMORY.MD" :
411+ continue
412+ eng = _parse_auto_memory_file (path )
413+ if eng :
414+ engrams .append (eng )
415+ engrams .sort (key = lambda e : e .get ("created" ) or "" , reverse = True )
416+ return engrams
417+
418+
329419# ─── App factory ──────────────────────────────────────────────────────────────
330420
331421
@@ -368,6 +458,7 @@ def list_memories(limit: int = 500) -> Dict[str, Any]:
368458 if isinstance (raw , dict ):
369459 raw = raw .get ("results" ) or raw .get ("memories" ) or []
370460 engrams = [_engram_from_memory (m ) for m in raw if m ]
461+ engrams .extend (_auto_memory_engrams ())
371462 return {"live" : True , "engrams" : engrams , "count" : len (engrams )}
372463 except Exception as exc : # noqa: BLE001
373464 log .warning ("list_memories failed: %s" , exc )
@@ -637,43 +728,22 @@ def list_workspaces_api() -> Dict[str, Any]:
637728 def create_workspace_api (payload : WorkspaceRootCreatePayload ) -> Dict [str , Any ]:
638729 try :
639730 db = _get_db ()
640- root_path = _abs_user_path (payload .root_path or _ui_repo ())
641- workspace_name = _display_name (payload .name , fallback_path = root_path )
731+ workspace_name = _display_name (payload .name , fallback = "Workspace" )
732+ if not workspace_name :
733+ raise HTTPException (status_code = 400 , detail = "Workspace name is required" )
642734 workspace = db .upsert_workspace (
643735 {
644736 "user_id" : _ui_user_id (),
645737 "name" : workspace_name ,
646738 "description" : payload .description ,
647- "root_path" : root_path ,
739+ "root_path" : None ,
648740 "metadata" : {"created_via" : "sankhya-ui" },
649741 }
650742 )
651- db .upsert_workspace_mount (
652- {
653- "workspace_id" : workspace ["id" ],
654- "user_id" : _ui_user_id (),
655- "mount_path" : root_path ,
656- "label" : os .path .basename (root_path .rstrip (os .sep )) or root_path ,
657- "is_primary" : True ,
658- }
659- )
660- general = db .upsert_workspace_project (
661- {
662- "workspace_id" : workspace ["id" ],
663- "user_id" : _ui_user_id (),
664- "name" : "General" ,
665- "description" : f"Default project for { workspace_name } " ,
666- "default_runtime" : "codex" ,
667- "metadata" : {"created_via" : "sankhya-ui" , "auto_created" : True },
668- }
669- )
670- db .replace_workspace_project_scope_rules (
671- project_id = str (general .get ("id" ) or "" ),
672- user_id = _ui_user_id (),
673- rules = [{"path_prefix" : root_path , "label" : "root" }],
674- )
675- _mirror_runtime_sessions (db , extra_paths = [root_path ])
743+ _mirror_runtime_sessions (db )
676744 return {"ok" : True , "workspace" : _workspace_summary (db , workspace )}
745+ except HTTPException :
746+ raise
677747 except Exception as exc : # noqa: BLE001
678748 raise HTTPException (status_code = 400 , detail = str (exc )) from exc
679749
@@ -819,19 +889,14 @@ def create_workspace_project_api(
819889 }
820890 )
821891 rules = _normalize_scope_rules (payload .scope_rules )
822- if not rules :
823- rules = [{"path_prefix" : _workspace_primary_path (workspace ), "label" : "root" }]
824892 db .replace_workspace_project_scope_rules (
825893 project_id = str (project .get ("id" ) or "" ),
826894 user_id = _ui_user_id (),
827895 rules = rules ,
828896 )
829897 _mirror_runtime_sessions (
830898 db ,
831- extra_paths = [
832- _workspace_primary_path (workspace ),
833- * [str (rule .get ("path_prefix" ) or "" ) for rule in rules ],
834- ],
899+ extra_paths = [str (rule .get ("path_prefix" ) or "" ) for rule in rules ],
835900 )
836901 return {"ok" : True , "project" : _project_summary (db , project )}
837902 except HTTPException :
0 commit comments