Private
Public Access
progress on context composition
This commit is contained in:
+71
-40
@@ -199,52 +199,83 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
|
|||||||
mtime = path.stat().st_mtime
|
mtime = path.stat().st_mtime
|
||||||
error = False
|
error = False
|
||||||
if not error and view_mode != "full":
|
if not error and view_mode != "full":
|
||||||
if view_mode == "summary":
|
try:
|
||||||
content = summarize.summarise_file(path, content)
|
if view_mode == "summary":
|
||||||
elif view_mode == "skeleton":
|
|
||||||
if path.suffix == ".py":
|
|
||||||
if not parser: parser = ASTParser("python")
|
|
||||||
content = parser.get_skeleton(content, path=str(path))
|
|
||||||
elif path.suffix in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']:
|
|
||||||
from src import mcp_client
|
|
||||||
if path.suffix in ['.c', '.h']: content = mcp_client.ts_c_get_skeleton(str(path))
|
|
||||||
else: content = mcp_client.ts_cpp_get_skeleton(str(path))
|
|
||||||
else:
|
|
||||||
content = summarize.summarise_file(path, content)
|
|
||||||
elif view_mode == "outline":
|
|
||||||
if path.suffix == ".py":
|
|
||||||
if not parser: parser = ASTParser("python")
|
|
||||||
content = parser.get_code_outline(content, path=str(path))
|
|
||||||
elif path.suffix in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']:
|
|
||||||
from src import mcp_client
|
|
||||||
if path.suffix in ['.c', '.h']: content = mcp_client.ts_c_get_code_outline(str(path))
|
|
||||||
else: content = mcp_client.ts_cpp_get_code_outline(str(path))
|
|
||||||
else:
|
|
||||||
content = summarize.summarise_file(path, content)
|
|
||||||
elif view_mode == "none":
|
|
||||||
content = "(context excluded)"
|
|
||||||
elif view_mode == "custom":
|
|
||||||
if custom_slices:
|
|
||||||
lines = content.splitlines()
|
|
||||||
slices_text = []
|
|
||||||
for s in custom_slices:
|
|
||||||
start = s.get("start_line", 1)
|
|
||||||
end = s.get("end_line", len(lines))
|
|
||||||
tag = s.get("tag", "unnamed")
|
|
||||||
comment = s.get("comment", "")
|
|
||||||
s_idx = max(0, start - 1)
|
|
||||||
e_idx = min(len(lines), end)
|
|
||||||
chunk = "\n".join(lines[s_idx:e_idx])
|
|
||||||
slices_text.append(f"---\n[Slice: {tag}] ({comment})\nLines {start}-{end}:\n{chunk}")
|
|
||||||
content = "\n\n".join(slices_text)
|
|
||||||
else:
|
|
||||||
content = summarize.summarise_file(path, content)
|
content = summarize.summarise_file(path, content)
|
||||||
|
elif view_mode == "skeleton":
|
||||||
|
suffix_lower = path.suffix.lower()
|
||||||
|
if suffix_lower == ".py":
|
||||||
|
if not parser: parser = ASTParser("python")
|
||||||
|
content = parser.get_skeleton(content, path=str(path))
|
||||||
|
elif suffix_lower in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']:
|
||||||
|
from src import mcp_client
|
||||||
|
if suffix_lower in ['.c', '.h']: content = mcp_client.ts_c_get_skeleton(str(path))
|
||||||
|
else: content = mcp_client.ts_cpp_get_skeleton(str(path))
|
||||||
|
else:
|
||||||
|
content = summarize.summarise_file(path, content)
|
||||||
|
elif view_mode == "outline":
|
||||||
|
suffix_lower = path.suffix.lower()
|
||||||
|
if suffix_lower == ".py":
|
||||||
|
if not parser: parser = ASTParser("python")
|
||||||
|
content = parser.get_code_outline(content, path=str(path))
|
||||||
|
elif suffix_lower in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']:
|
||||||
|
from src import mcp_client
|
||||||
|
if suffix_lower in ['.c', '.h']: content = mcp_client.ts_c_get_code_outline(str(path))
|
||||||
|
else: content = mcp_client.ts_cpp_get_code_outline(str(path))
|
||||||
|
else:
|
||||||
|
content = summarize.summarise_file(path, content)
|
||||||
|
elif view_mode == "masked":
|
||||||
|
suffix_lower = path.suffix.lower()
|
||||||
|
if ast_mask:
|
||||||
|
mask_sections = []
|
||||||
|
from src import mcp_client
|
||||||
|
for symbol, mode in ast_mask.items():
|
||||||
|
if mode == "hide": continue
|
||||||
|
res = ""
|
||||||
|
if suffix_lower == ".py":
|
||||||
|
res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol)
|
||||||
|
elif suffix_lower in [".c", ".h", ".cpp", ".hpp", ".cxx", ".cc"]:
|
||||||
|
is_cpp = any(ext in suffix_lower for ext in [".cpp", ".hpp", ".cxx", ".cc"])
|
||||||
|
if mode == "def":
|
||||||
|
res = mcp_client.ts_cpp_get_definition(str(path), symbol) if is_cpp else mcp_client.ts_c_get_definition(str(path), symbol)
|
||||||
|
else:
|
||||||
|
res = mcp_client.ts_cpp_get_signature(str(path), symbol) if is_cpp else mcp_client.ts_c_get_signature(str(path), symbol)
|
||||||
|
if res: mask_sections.append(res)
|
||||||
|
if mask_sections:
|
||||||
|
content = "\n\n".join(mask_sections)
|
||||||
|
else:
|
||||||
|
content = "(no masked sections visible)"
|
||||||
|
else:
|
||||||
|
content = "(no ast mask defined)"
|
||||||
|
elif view_mode == "none":
|
||||||
|
content = "(context excluded)"
|
||||||
|
elif view_mode == "custom":
|
||||||
|
if custom_slices:
|
||||||
|
lines = content.splitlines()
|
||||||
|
slices_text = []
|
||||||
|
for s in custom_slices:
|
||||||
|
start = s.get("start_line", 1)
|
||||||
|
end = s.get("end_line", len(lines))
|
||||||
|
tag = s.get("tag", "unnamed")
|
||||||
|
comment = s.get("comment", "")
|
||||||
|
s_idx = max(0, start - 1)
|
||||||
|
e_idx = min(len(lines), end)
|
||||||
|
chunk = "\n".join(lines[s_idx:e_idx])
|
||||||
|
slices_text.append(f"---\n[Slice: {tag}] ({comment})\nLines {start}-{end}:\n{chunk}")
|
||||||
|
content = "\n\n".join(slices_text)
|
||||||
|
else:
|
||||||
|
content = summarize.summarise_file(path, content)
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
content = f"ERROR in {view_mode} view mode for {path}:\n{traceback.format_exc()}"
|
||||||
|
error = True
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
content = f"ERROR: file not found: {path}"
|
content = f"ERROR: file not found: {path}"
|
||||||
mtime = 0.0
|
mtime = 0.0
|
||||||
error = True
|
error = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
content = f"ERROR: {e}"
|
import traceback
|
||||||
|
content = f"ERROR reading {path}:\n{traceback.format_exc()}"
|
||||||
mtime = 0.0
|
mtime = 0.0
|
||||||
error = True
|
error = True
|
||||||
items.append({"path": path, "entry": entry, "content": content, "error": error, "mtime": mtime, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "view_mode": view_mode, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions, "ast_mask": ast_mask, "custom_slices": custom_slices})
|
items.append({"path": path, "entry": entry, "content": content, "error": error, "mtime": mtime, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "view_mode": view_mode, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions, "ast_mask": ast_mask, "custom_slices": custom_slices})
|
||||||
|
|||||||
+25
-26
@@ -2929,29 +2929,20 @@ class AppController:
|
|||||||
if name not in presets:
|
if name not in presets:
|
||||||
raise KeyError(f"Context preset '{name}' not found.")
|
raise KeyError(f"Context preset '{name}' not found.")
|
||||||
preset = presets[name]
|
preset = presets[name]
|
||||||
# Apply it to the current state
|
|
||||||
self.ui_file_paths = [f.path for f in preset.files]
|
|
||||||
self.screenshots = list(preset.screenshots)
|
|
||||||
self._save_active_project()
|
|
||||||
# We need to tell gui_2 to populate the full FileItem state from the preset,
|
|
||||||
# which it does in _handle_refresh_from_project. But we also need to pass the detailed properties.
|
|
||||||
# We will let the project_manager handle merging in the preset files via configuration next turn.
|
|
||||||
# Wait, project manager doesn't load preset files into self.files automatically here.
|
|
||||||
# Let's write the preset files into self.project["files"] directly.
|
|
||||||
import copy
|
|
||||||
self.project.setdefault("files", {})["paths"] = [
|
|
||||||
{
|
|
||||||
"path": f.path,
|
|
||||||
"view_mode": f.view_mode,
|
|
||||||
"custom_slices": copy.deepcopy(f.custom_slices),
|
|
||||||
"ast_mask": copy.deepcopy(f.ast_mask),
|
|
||||||
"ast_signatures": getattr(f, "ast_signatures", False),
|
|
||||||
"ast_definitions": getattr(f, "ast_definitions", False)
|
|
||||||
} for f in preset.files
|
|
||||||
]
|
|
||||||
self._save_active_project()
|
|
||||||
return preset
|
|
||||||
|
|
||||||
|
# Update only temporary context state, not project files
|
||||||
|
import copy
|
||||||
|
self.context_files = []
|
||||||
|
for f in preset.files:
|
||||||
|
fi = models.FileItem(path=f.path, view_mode=f.view_mode)
|
||||||
|
fi.custom_slices = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||||
|
fi.ast_mask = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||||
|
fi.ast_signatures = getattr(f, 'ast_signatures', False)
|
||||||
|
fi.ast_definitions = getattr(f, 'ast_definitions', False)
|
||||||
|
self.context_files.append(fi)
|
||||||
|
|
||||||
|
self.screenshots = list(preset.screenshots)
|
||||||
|
return preset
|
||||||
def _cb_load_track(self, track_id: str) -> None:
|
def _cb_load_track(self, track_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
[C: src/gui_2.py:App._render_mma_track_browser]
|
[C: src/gui_2.py:App._render_mma_track_browser]
|
||||||
@@ -3459,11 +3450,19 @@ class AppController:
|
|||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
track_id = self.active_track.id if self.active_track else None
|
track_id = self.active_track.id if self.active_track else None
|
||||||
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=track_id)
|
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=track_id)
|
||||||
flat.setdefault("files", {})["paths"] = self.context_files
|
|
||||||
|
import copy
|
||||||
|
flat["files"] = copy.copy(flat.get("files", {}))
|
||||||
|
flat["files"]["paths"] = self.context_files
|
||||||
|
|
||||||
# Configure MCP so that aggregate.py can fetch skeletons for external files (e.g. gencpp)
|
import os
|
||||||
file_dicts = [f.to_dict() if hasattr(f, 'to_dict') else {"path": str(f)} for f in self.context_files]
|
file_dicts = []
|
||||||
mcp_client.configure(file_dicts, [self.active_project_root] if self.active_project_root else None)
|
for f in self.context_files:
|
||||||
|
p = f.path if hasattr(f, 'path') else str(f)
|
||||||
|
if not os.path.isabs(p):
|
||||||
|
p = os.path.join(self.ui_files_base_dir, p)
|
||||||
|
file_dicts.append({"path": p})
|
||||||
|
mcp_client.configure(file_dicts, [self.ui_files_base_dir])
|
||||||
|
|
||||||
persona = self.personas.get(self.ui_active_persona)
|
persona = self.personas.get(self.ui_active_persona)
|
||||||
strategy = persona.aggregation_strategy if persona else "auto"
|
strategy = persona.aggregation_strategy if persona else "auto"
|
||||||
|
|||||||
+92
-40
@@ -627,7 +627,16 @@ class App:
|
|||||||
[C: tests/test_context_presets.py:test_load_context_preset, tests/test_context_presets.py:test_load_nonexistent_preset]
|
[C: tests/test_context_presets.py:test_load_context_preset, tests/test_context_presets.py:test_load_nonexistent_preset]
|
||||||
"""
|
"""
|
||||||
preset = self.controller.load_context_preset(name)
|
preset = self.controller.load_context_preset(name)
|
||||||
self.context_files = [models.FileItem(path=f.path, view_mode=f.view_mode) for f in preset.files]
|
from src import models
|
||||||
|
import copy
|
||||||
|
self.context_files = []
|
||||||
|
for f in preset.files:
|
||||||
|
fi = models.FileItem(path=f.path, view_mode=f.view_mode)
|
||||||
|
fi.custom_slices = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||||
|
fi.ast_mask = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||||
|
fi.ast_signatures = getattr(f, 'ast_signatures', False)
|
||||||
|
fi.ast_definitions = getattr(f, 'ast_definitions', False)
|
||||||
|
self.context_files.append(fi)
|
||||||
self.screenshots = list(preset.screenshots)
|
self.screenshots = list(preset.screenshots)
|
||||||
self.ui_file_paths = [f.path for f in preset.files]
|
self.ui_file_paths = [f.path for f in preset.files]
|
||||||
self.ui_screenshot_paths = list(preset.screenshots)
|
self.ui_screenshot_paths = list(preset.screenshots)
|
||||||
@@ -3017,7 +3026,6 @@ def render_ast_inspector_modal(app: App) -> None:
|
|||||||
if imgui.is_item_hovered():
|
if imgui.is_item_hovered():
|
||||||
app._hovered_ast_node = full_path
|
app._hovered_ast_node = full_path
|
||||||
|
|
||||||
# Calculate space left and align radio buttons to the right
|
|
||||||
btn_width = 150 # Estimated width of the 3 radio buttons
|
btn_width = 150 # Estimated width of the 3 radio buttons
|
||||||
avail_width = imgui.get_content_region_avail().x
|
avail_width = imgui.get_content_region_avail().x
|
||||||
if avail_width > btn_width:
|
if avail_width > btn_width:
|
||||||
@@ -3255,7 +3263,6 @@ def render_context_files_table(app: App) -> None:
|
|||||||
imgui.text_colored(imgui.ImVec4(1.0, 0.5, 0.0, 1.0), "[Slices Active]")
|
imgui.text_colored(imgui.ImVec4(1.0, 0.5, 0.0, 1.0), "[Slices Active]")
|
||||||
|
|
||||||
def render_context_presets(app: App) -> None:
|
def render_context_presets(app: App) -> None:
|
||||||
imgui.text("Presets")
|
|
||||||
presets = app.controller.project.get('context_presets', {})
|
presets = app.controller.project.get('context_presets', {})
|
||||||
preset_names = [""] + sorted(presets.keys())
|
preset_names = [""] + sorted(presets.keys())
|
||||||
active = getattr(app, "ui_active_context_preset", "")
|
active = getattr(app, "ui_active_context_preset", "")
|
||||||
@@ -3264,47 +3271,86 @@ def render_context_presets(app: App) -> None:
|
|||||||
idx = preset_names.index(active)
|
idx = preset_names.index(active)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
idx = 0
|
idx = 0
|
||||||
ch, new_idx = imgui.combo("##ctx_preset", idx, preset_names)
|
|
||||||
if ch:
|
with imscope.table("ctx_presets_layout", 2, imgui.TableFlags_.none):
|
||||||
app.ui_active_context_preset = preset_names[new_idx]
|
imgui.table_next_column()
|
||||||
if preset_names[new_idx]: app.load_context_preset(preset_names[new_idx])
|
imgui.set_next_item_width(-1)
|
||||||
imgui.same_line()
|
ch, new_idx = imgui.combo("##ctx_preset", idx, preset_names)
|
||||||
changed, new_name = imgui.input_text("##new_preset", getattr(app, "ui_new_context_preset_name", ""))
|
if ch:
|
||||||
if changed: app.ui_new_context_preset_name = new_name
|
app.ui_active_context_preset = preset_names[new_idx]
|
||||||
imgui.same_line()
|
if preset_names[new_idx]:
|
||||||
if imgui.button("Save##ctx") or getattr(app, "_pending_save_ctx_click", False):
|
app.controller.load_context_preset(preset_names[new_idx])
|
||||||
app._pending_save_ctx_click = False
|
app.controller._refresh_from_project()
|
||||||
name = getattr(app, "ui_new_context_preset_name", "").strip()
|
app.context_files = list(app.controller.files)
|
||||||
if name:
|
|
||||||
missing = []
|
imgui.table_next_column()
|
||||||
root = app.controller.active_project_root
|
if active:
|
||||||
for f in app.context_files:
|
if imgui.button("Update##override", imgui.ImVec2(-1, 0)):
|
||||||
path = f.path if hasattr(f, "path") else str(f)
|
|
||||||
if not os.path.isabs(path):
|
|
||||||
full_path = os.path.join(root, path)
|
|
||||||
else:
|
|
||||||
full_path = path
|
|
||||||
if not os.path.exists(full_path):
|
|
||||||
missing.append(path)
|
|
||||||
|
|
||||||
if missing:
|
|
||||||
app.missing_context_files = missing
|
|
||||||
app.show_missing_files_modal = True
|
|
||||||
app.target_context_preset_name = name
|
|
||||||
else:
|
|
||||||
preset_files = []
|
preset_files = []
|
||||||
for f in app.context_files:
|
for f in app.context_files:
|
||||||
|
import copy
|
||||||
|
from src import models
|
||||||
p = f.path if hasattr(f, 'path') else str(f)
|
p = f.path if hasattr(f, 'path') else str(f)
|
||||||
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||||
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm))
|
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||||
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
|
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||||
|
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
|
||||||
|
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False
|
||||||
|
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
|
||||||
|
preset = models.ContextPreset(name=active, files=preset_files, screenshots=list(app.screenshots))
|
||||||
app.controller.save_context_preset(preset)
|
app.controller.save_context_preset(preset)
|
||||||
app.ui_new_context_preset_name = ""
|
else:
|
||||||
imgui.same_line()
|
imgui.text_disabled("No active preset")
|
||||||
if imgui.button("Delete##ctx"):
|
|
||||||
if getattr(app, "ui_active_context_preset", ""):
|
imgui.table_next_row()
|
||||||
app.delete_context_preset(app.ui_active_context_preset)
|
imgui.table_next_column()
|
||||||
app.ui_active_context_preset = ""
|
imgui.set_next_item_width(-1)
|
||||||
|
changed, new_name = imgui.input_text("##new_preset", getattr(app, "ui_new_context_preset_name", ""))
|
||||||
|
if changed: app.ui_new_context_preset_name = new_name
|
||||||
|
|
||||||
|
imgui.table_next_column()
|
||||||
|
if imgui.button("Save As##ctx", imgui.ImVec2(-1, 0)) or getattr(app, "_pending_save_ctx_click", False):
|
||||||
|
app._pending_save_ctx_click = False
|
||||||
|
name = getattr(app, "ui_new_context_preset_name", "").strip()
|
||||||
|
if name:
|
||||||
|
missing = []
|
||||||
|
root = app.controller.active_project_root
|
||||||
|
for f in app.context_files:
|
||||||
|
path = f.path if hasattr(f, "path") else str(f)
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
full_path = os.path.join(root, path)
|
||||||
|
else:
|
||||||
|
full_path = path
|
||||||
|
if not os.path.exists(full_path):
|
||||||
|
missing.append(path)
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
app.missing_context_files = missing
|
||||||
|
app.show_missing_files_modal = True
|
||||||
|
app.target_context_preset_name = name
|
||||||
|
else:
|
||||||
|
preset_files = []
|
||||||
|
for f in app.context_files:
|
||||||
|
import copy
|
||||||
|
from src import models
|
||||||
|
p = f.path if hasattr(f, 'path') else str(f)
|
||||||
|
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||||
|
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||||
|
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||||
|
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
|
||||||
|
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False
|
||||||
|
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
|
||||||
|
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
|
||||||
|
app.controller.save_context_preset(preset)
|
||||||
|
app.ui_new_context_preset_name = ""
|
||||||
|
|
||||||
|
if active:
|
||||||
|
imgui.table_next_row()
|
||||||
|
imgui.table_next_column()
|
||||||
|
imgui.table_next_column()
|
||||||
|
if imgui.button("Delete Active", imgui.ImVec2(-1, 0)):
|
||||||
|
app.delete_context_preset(active)
|
||||||
|
app.ui_active_context_preset = ""
|
||||||
|
|
||||||
def render_snapshot_tab(app: App) -> None:
|
def render_snapshot_tab(app: App) -> None:
|
||||||
if imgui.begin_tab_bar("snapshot_tabs"):
|
if imgui.begin_tab_bar("snapshot_tabs"):
|
||||||
@@ -5380,9 +5426,15 @@ def render_context_modals(app: App) -> None:
|
|||||||
name = app.target_context_preset_name
|
name = app.target_context_preset_name
|
||||||
preset_files = []
|
preset_files = []
|
||||||
for f in app.context_files:
|
for f in app.context_files:
|
||||||
|
import copy
|
||||||
|
from src import models
|
||||||
p = f.path if hasattr(f, 'path') else str(f)
|
p = f.path if hasattr(f, 'path') else str(f)
|
||||||
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||||
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm))
|
slc = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||||
|
msk = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||||
|
sig = f.ast_signatures if hasattr(f, 'ast_signatures') else False
|
||||||
|
dfn = f.ast_definitions if hasattr(f, 'ast_definitions') else False
|
||||||
|
preset_files.append(models.ContextFileEntry(path=p, view_mode=vm, custom_slices=slc, ast_mask=msk, ast_signatures=sig, ast_definitions=dfn))
|
||||||
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
|
preset = models.ContextPreset(name=name, files=preset_files, screenshots=list(app.screenshots))
|
||||||
app.controller.save_context_preset(preset)
|
app.controller.save_context_preset(preset)
|
||||||
app.ui_new_context_preset_name = ""
|
app.ui_new_context_preset_name = ""
|
||||||
|
|||||||
+6
-13
@@ -22,7 +22,6 @@ For each file, extracts structural information:
|
|||||||
Returns a compact markdown string per file, suitable for use as a low-token
|
Returns a compact markdown string per file, suitable for use as a low-token
|
||||||
context block that replaces full file contents in the initial <context> send.
|
context block that replaces full file contents in the initial <context> send.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -42,7 +41,6 @@ def _summarise_python(path: Path, content: str) -> str:
|
|||||||
except SyntaxError as e:
|
except SyntaxError as e:
|
||||||
parts.append(f"_Parse error: {e}_")
|
parts.append(f"_Parse error: {e}_")
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
# Imports
|
|
||||||
imports = []
|
imports = []
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
if isinstance(node, ast.Import):
|
if isinstance(node, ast.Import):
|
||||||
@@ -54,7 +52,6 @@ def _summarise_python(path: Path, content: str) -> str:
|
|||||||
if imports:
|
if imports:
|
||||||
unique_imports = sorted(set(imports))
|
unique_imports = sorted(set(imports))
|
||||||
parts.append(f"imports: {', '.join(unique_imports)}")
|
parts.append(f"imports: {', '.join(unique_imports)}")
|
||||||
# Top-level constants (ALL_CAPS assignments)
|
|
||||||
constants = []
|
constants = []
|
||||||
for node in ast.iter_child_nodes(tree):
|
for node in ast.iter_child_nodes(tree):
|
||||||
if isinstance(node, ast.Assign):
|
if isinstance(node, ast.Assign):
|
||||||
@@ -66,7 +63,6 @@ def _summarise_python(path: Path, content: str) -> str:
|
|||||||
constants.append(node.target.id)
|
constants.append(node.target.id)
|
||||||
if constants:
|
if constants:
|
||||||
parts.append(f"constants: {', '.join(constants)}")
|
parts.append(f"constants: {', '.join(constants)}")
|
||||||
# Classes + their methods
|
|
||||||
for node in ast.iter_child_nodes(tree):
|
for node in ast.iter_child_nodes(tree):
|
||||||
if isinstance(node, ast.ClassDef):
|
if isinstance(node, ast.ClassDef):
|
||||||
methods = [
|
methods = [
|
||||||
@@ -77,7 +73,6 @@ def _summarise_python(path: Path, content: str) -> str:
|
|||||||
parts.append(f"class {node.name}: {', '.join(methods)}")
|
parts.append(f"class {node.name}: {', '.join(methods)}")
|
||||||
else:
|
else:
|
||||||
parts.append(f"class {node.name}")
|
parts.append(f"class {node.name}")
|
||||||
# Top-level functions
|
|
||||||
top_fns = [
|
top_fns = [
|
||||||
node.name for node in ast.iter_child_nodes(tree)
|
node.name for node in ast.iter_child_nodes(tree)
|
||||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||||
@@ -90,7 +85,6 @@ def _summarise_toml(path: Path, content: str) -> str:
|
|||||||
lines = content.splitlines()
|
lines = content.splitlines()
|
||||||
line_count = len(lines)
|
line_count = len(lines)
|
||||||
parts = [f"**TOML** — {line_count} lines"]
|
parts = [f"**TOML** — {line_count} lines"]
|
||||||
# Extract top-level table headers [key] and [[key]]
|
|
||||||
table_pat = re.compile(r"^\s*\[{1,2}([^\[\]]+)\]{1,2}")
|
table_pat = re.compile(r"^\s*\[{1,2}([^\[\]]+)\]{1,2}")
|
||||||
tables = []
|
tables = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@@ -99,7 +93,6 @@ def _summarise_toml(path: Path, content: str) -> str:
|
|||||||
tables.append(m.group(1).strip())
|
tables.append(m.group(1).strip())
|
||||||
if tables:
|
if tables:
|
||||||
parts.append(f"tables: {', '.join(tables)}")
|
parts.append(f"tables: {', '.join(tables)}")
|
||||||
# Top-level key = value (not inside a [table])
|
|
||||||
kv_pat = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_]*)\s*=")
|
kv_pat = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_]*)\s*=")
|
||||||
in_table = False
|
in_table = False
|
||||||
top_keys = []
|
top_keys = []
|
||||||
@@ -140,7 +133,6 @@ def _summarise_generic(path: Path, content: str) -> str:
|
|||||||
if preview:
|
if preview:
|
||||||
parts.append("preview:\n```\n" + "\n".join(preview) + "\n```")
|
parts.append("preview:\n```\n" + "\n".join(preview) + "\n```")
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
# ------------------------------------------------------------------ dispatch
|
|
||||||
|
|
||||||
_SUMMARISERS: dict[str, Callable[[Path, str], str]] = {
|
_SUMMARISERS: dict[str, Callable[[Path, str], str]] = {
|
||||||
".py": _summarise_python,
|
".py": _summarise_python,
|
||||||
@@ -148,6 +140,10 @@ _SUMMARISERS: dict[str, Callable[[Path, str], str]] = {
|
|||||||
".md": _summarise_markdown,
|
".md": _summarise_markdown,
|
||||||
".ini": _summarise_generic,
|
".ini": _summarise_generic,
|
||||||
".txt": _summarise_generic,
|
".txt": _summarise_generic,
|
||||||
|
".c": _summarise_generic,
|
||||||
|
".h": _summarise_generic,
|
||||||
|
".cpp": _summarise_generic,
|
||||||
|
".hpp": _summarise_generic,
|
||||||
".ps1": _summarise_generic,
|
".ps1": _summarise_generic,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,19 +159,17 @@ def summarise_file(path: Path, content: str) -> str:
|
|||||||
cached = _summary_cache.get_summary(str(path), content_hash)
|
cached = _summary_cache.get_summary(str(path), content_hash)
|
||||||
if cached:
|
if cached:
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
suffix = path.suffix.lower() if hasattr(path, "suffix") else ""
|
suffix = path.suffix.lower() if hasattr(path, "suffix") else ""
|
||||||
fn = _SUMMARISERS.get(suffix, _summarise_generic)
|
fn = _SUMMARISERS.get(suffix, _summarise_generic)
|
||||||
try:
|
try:
|
||||||
heuristic_outline = fn(path, content)
|
heuristic_outline = fn(path, content)
|
||||||
|
|
||||||
# Smart AI Summarization
|
# Smart AI Summarization
|
||||||
is_code = suffix in [".py", ".ps1", ".js", ".ts", ".cpp", ".c", ".h", ".cs", ".go", ".rs", ".lua"]
|
is_code = suffix in [".py", ".ps1", ".js", ".ts", ".cpp", ".c", ".h", ".cs", ".go", ".rs", ".lua"]
|
||||||
try:
|
try:
|
||||||
from src import ai_client
|
from src import ai_client
|
||||||
smart_summary = ai_client.run_subagent_summarization(
|
smart_summary = ai_client.run_subagent_summarization(
|
||||||
file_path=str(path),
|
file_path=str(path),
|
||||||
content=content[:10000], # Cap content to 10k chars for summarization
|
content=content[:10000],
|
||||||
is_code=is_code,
|
is_code=is_code,
|
||||||
outline=heuristic_outline
|
outline=heuristic_outline
|
||||||
)
|
)
|
||||||
@@ -184,8 +178,7 @@ def summarise_file(path: Path, content: str) -> str:
|
|||||||
else:
|
else:
|
||||||
summary = heuristic_outline
|
summary = heuristic_outline
|
||||||
except Exception:
|
except Exception:
|
||||||
summary = heuristic_outline # Fallback
|
summary = heuristic_outline
|
||||||
|
|
||||||
_summary_cache.set_summary(str(path), content_hash, summary)
|
_summary_cache.set_summary(str(path), content_hash, summary)
|
||||||
return summary
|
return summary
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user