3030
3131# Subcommands argparse owns; anything else as the first token is treated as a
3232# bare-task shorthand for `run`.
33- KNOWN_SUBCOMMANDS = {"run" , "config" , "models" , "index" , "search" , "symbols" , "context" }
33+ KNOWN_SUBCOMMANDS = {
34+ "run" , "config" , "models" , "index" , "search" , "symbols" , "context" , "memory" , "graph" ,
35+ }
3436
3537
3638# --- Routing table -----------------------------------------------------------
@@ -152,18 +154,21 @@ async def cmd_run(args):
152154 if mode == "agent" :
153155 from agent .state .agent_state import AgentState , TaskMetadata
154156 from agent .context import build_context_bundle
157+ from agent .memory .project_memory import ProjectMemoryManager
155158 state = AgentState (user_request = task_description ,
156159 task = TaskMetadata (description = task_description ))
157160 try :
158161 state .loaded_context = await build_context_bundle (settings .get_workspace_path ())
159162 except Exception :
160163 pass
164+ project_memory = ProjectMemoryManager (settings .get_workspace_path ())
165+ project_memory .load_into_state (state )
161166 if caps :
162167 state .capabilities = caps .model_dump ()
163168 # Phase 11: incremental planning is on by default (settings); the agent
164169 # loop then runs plan -> execute step -> observe -> replan.
165170 engine = build_engine ("agent" , safety_mode = safety_mode ,
166- memory_manager = orchestrator . memory_manager ,
171+ memory_manager = project_memory ,
167172 incremental = settings .incremental_planning )
168173 state = await engine .execute (state )
169174 print (f"\n Agent run complete: status={ state .final_outputs .status } , "
@@ -173,6 +178,10 @@ async def cmd_run(args):
173178 if settings .incremental_planning and state .plan .revisions :
174179 from agent .planning import render_plan_evolution
175180 print ("\n " + render_plan_evolution (state ))
181+ project_memory .update_from_state (state )
182+ if settings .observability_enabled and (args .observe or settings .verbosity == "verbose" ):
183+ from agent .observability import render_dashboard
184+ print ("\n " + render_dashboard (state ))
176185 return 0
177186
178187 # Phase 11: pipeline strategy with step-wise execution + replanning when
@@ -183,6 +192,11 @@ async def cmd_run(args):
183192 else :
184193 report_path = await orchestrator .run (task_description )
185194 print (f"\n Execution complete. Report generated at: { report_path } " )
195+ if settings .observability_enabled and (args .observe or settings .verbosity == "verbose" ):
196+ state = getattr (orchestrator , "last_state" , None )
197+ if state is not None :
198+ from agent .observability import render_dashboard
199+ print ("\n " + render_dashboard (state ))
186200 return 0
187201
188202
@@ -244,6 +258,49 @@ async def cmd_context(args):
244258 return 0
245259
246260
261+ def cmd_memory (args ):
262+ """Phase 12: view local project-memory markdown files."""
263+ from agent .memory .project_memory import ProjectMemoryManager
264+
265+ manager = ProjectMemoryManager (settings .get_workspace_path ())
266+ if not manager .enabled :
267+ print ("Project memory is disabled (PROJECT_MEMORY_ENABLED=false)." )
268+ return 0
269+ bundle = manager .load ()
270+ print (f"Project Memory: { manager .relative_dir } " )
271+ if not bundle .files :
272+ print ("(no project memory files)" )
273+ return 0
274+ for rel in sorted (bundle .files ):
275+ print ("\n " + "=" * 52 )
276+ print (rel )
277+ print ("-" * 52 )
278+ print (bundle .files [rel ].rstrip ())
279+ if bundle .recovered_files :
280+ print ("\n Recovered/normalized:" )
281+ for rel in bundle .recovered_files :
282+ print (f"- { rel } " )
283+ return 0
284+
285+
286+ def cmd_graph (args ):
287+ """Phase 13: repository graph impact query."""
288+ if getattr (args , "graph_action" , None ) != "impact" :
289+ print ("Usage: localcli graph impact <file>" )
290+ return 2
291+ from agent .graph import GraphBuilder , ImpactAnalyzer
292+
293+ if not settings .repo_graph_enabled :
294+ print ("Repository graph is disabled (REPO_GRAPH_ENABLED=false)." )
295+ return 0
296+ graph = GraphBuilder (settings .get_workspace_path ()).build (force = args .refresh )
297+ dependents = ImpactAnalyzer (graph ).dependents (args .file )
298+ print (f"Impact for { args .file } : { len (dependents )} dependent file(s)" )
299+ for dep in dependents :
300+ print (f"- { dep } " )
301+ return 0
302+
303+
247304async def cmd_index (args ):
248305 from agent .orchestrator import Orchestrator
249306 orchestrator = Orchestrator ()
@@ -303,6 +360,7 @@ def build_parser():
303360 p_run = sub .add_parser ("run" , help = "Run an end-to-end coding task" )
304361 p_run .add_argument ("task" , nargs = "*" , help = "The task description" )
305362 p_run .add_argument ("--reindex" , action = "store_true" , help = "Force full rebuild of the index first" )
363+ p_run .add_argument ("--observe" , action = "store_true" , help = "Print the Phase 14 AgentState dashboard after the run" )
306364 _add_safety_flags (p_run )
307365
308366 p_index = sub .add_parser ("index" , help = "Build/update the workspace index" )
@@ -316,6 +374,14 @@ def build_parser():
316374 p_context = sub .add_parser ("context" , help = "Generate/refresh the repository Context Bundle (Phase 9, local)" )
317375 p_context .add_argument ("--refresh" , action = "store_true" , help = "Force a full rescan, ignoring the cache" )
318376
377+ sub .add_parser ("memory" , help = "View local project memory markdown files (Phase 12)" )
378+
379+ p_graph = sub .add_parser ("graph" , help = "Repository graph queries (Phase 13 MVP)" )
380+ graph_sub = p_graph .add_subparsers (dest = "graph_action" )
381+ p_impact = graph_sub .add_parser ("impact" , help = "List files that depend on a file" )
382+ p_impact .add_argument ("file" , help = "Repository-relative file path" )
383+ p_impact .add_argument ("--refresh" , action = "store_true" , help = "Force graph regeneration first" )
384+
319385 p_config = sub .add_parser ("config" , help = "Configuration utilities" )
320386 config_sub = p_config .add_subparsers (dest = "config_action" )
321387 config_sub .add_parser ("check" , help = "Run preflight and print the resolved routing table + health" )
@@ -351,6 +417,10 @@ async def _dispatch(args):
351417 return await cmd_symbols (args )
352418 if args .subcommand == "context" :
353419 return await cmd_context (args )
420+ if args .subcommand == "memory" :
421+ return cmd_memory (args )
422+ if args .subcommand == "graph" :
423+ return cmd_graph (args )
354424 # default: run
355425 return await cmd_run (args )
356426
0 commit comments