Cluster: agent/{curator,curator_backup,background_review,review_engine,
review_idle_queue,insights,learning_graph,learning_graph_render,
learning_mutations,learn_prompt,verification_evidence,verification_stop,
verify_hooks,side_question,title_generator,turn_summary,
manual_compression_feedback,trajectory,moa_trace,trace_upload,verify/*}.
13662 -> 10693 LOC (-2969, -21.7%), behavior-neutral.
- Dead code: 27 private helpers with zero references removed
(_auto_title_session, _resolve_review_model, _parse_make_targets,
_filter_verifiable_paths, _find_subsequence, _is_under_root/_temp_dir,
_merge_runs, learning_graph_render bucket/period/node helpers,
_memories_dir/_memory_local_index/_node_detail, _cron_jobs_file,
_retention_cutoff, _scope_for_args, _clean_token, _count_diff_lines,
_ordered_verbs, _hermes_meta, _iter_skill_files).
- Unified helpers: _read_config_section (curator + curator_backup),
_write_file/_write_json (4 curator report writers), _msg_text
(background_review <- side_question), _report_failure/_notify_title
(title_generator instant/auto paths), _is_under (verification_evidence),
_scoped SQL pair builder + _query (insights), _optional_lock
(background_review), verify.recipes table-driven detection.
- if/elif routing -> dict dispatch: side_question role labels,
curator_backup summary bits, learning_graph_render buckets, insights
section rendering, verify recipe pickers.
- Redundant defensive layers, single-use wrappers and verbose narrative
comments collapsed; every non-obvious WHY/invariant kept in compact form.
Verification: parity.py (all REMOVED symbols zero-ref), import smoke for
every module + cli/run_agent/gateway.run/hermes_cli.main/
agent.conversation_loop/tui_gateway.server, old-vs-new fuzz parity on all
shared pure functions, SQL trace parity for insights and
verification_evidence, cluster tests 1354 passed / 0 failed (46 files).
49 lines
1.9 KiB
Python
49 lines
1.9 KiB
Python
"""Environment manifest for project verification.
|
|
|
|
Ported from superagent-ai/grok-cli ``src/verify/environment.ts``. The manifest
|
|
at ``<project>/.hermes/environment.json`` is the user-editable source of truth:
|
|
when present and valid it wins over fresh static detection.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from agent.verify.recipes import Recipe, detect_recipe
|
|
|
|
MANIFEST_VERSION = 1
|
|
_MANIFEST_RELPATH = Path(".hermes") / "environment.json"
|
|
|
|
|
|
def manifest_path(root: Path) -> Path:
|
|
"""Path of the verify manifest for the project at ``root``."""
|
|
return Path(root) / _MANIFEST_RELPATH
|
|
|
|
|
|
def load_manifest(root: Path) -> Recipe | None:
|
|
"""Load the saved recipe; any read/parse/shape problem returns ``None`` so a
|
|
corrupt manifest degrades to fresh detection. Accepts the wrapped
|
|
``{version, recipe}`` shape and a bare recipe."""
|
|
try:
|
|
manifest = json.loads(manifest_path(root).read_text(encoding="utf-8"))
|
|
except (OSError, ValueError): # ValueError includes JSONDecodeError
|
|
return None
|
|
return Recipe.from_dict(manifest.get("recipe", manifest)) if isinstance(manifest, dict) else None
|
|
|
|
|
|
def save_manifest(root: Path, recipe: Recipe) -> Path:
|
|
"""Persist ``recipe`` in the versioned wrapper shape; returns the manifest path."""
|
|
path = manifest_path(root)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
payload = {"version": MANIFEST_VERSION, "recipe": recipe.to_dict(), "updatedAt": datetime.now(timezone.utc).isoformat()}
|
|
path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def load_or_detect(root: Path) -> tuple[Recipe | None, str]:
|
|
"""Return ``(recipe, source)``; a saved manifest ('manifest') wins over 'detected'."""
|
|
saved = load_manifest(root)
|
|
return (saved, "manifest") if saved is not None else (detect_recipe(root), "detected")
|