forked from Rockachopa/Timmy-time-dashboard
## Thinking Engine Tests (#1314) - New: tests/timmy/test_thinking_engine.py — 117 tests across 21 test classes - Covers ThinkingEngine core + all 4 mixin classes: - engine.py: init, idle detection, store/retrieve, pruning, dedup, continuity, context assembly, novel thought generation, think_once, journal, broadcast - _distillation.py: should_distill, build_distill_prompt, parse_facts_response, filter_and_store_facts, maybe_distill - _issue_filing.py: references_real_files, get_recent_thoughts_for_issues, build_issue_classify_prompt, parse_issue_items, file_single_issue - _seeds_mixin.py: pick_seed_type, gather_seed, all seed sources, check_workspace - _snapshot.py: system snapshot, memory context, update_memory - _db.py: get_conn, row_to_thought, Thought dataclass - seeds.py: constants, prompt template, think tag regex - Targets 80%+ coverage of engine.py's 430 lines ## Stack Manifest (#986) - New: docs/stack_manifest.json — 8 categories, 40+ tools with pinned versions - LLM Inference, Coding Agents, Image Gen, Music/Voice, Orchestration, Nostr+Lightning+Bitcoin, Memory/KG, Streaming/Content - Schema: {tool, version, role, install_command, license, status} - New: src/timmy/stack_manifest.py — query_stack() runtime tool - Category and tool filtering (case-insensitive, partial match) - Manifest caching, graceful error handling - New: tests/timmy/test_stack_manifest.py — 24 tests - Registered query_stack in tool registry + tool catalog - Total: 141 new tests, all passing
161 lines
5.2 KiB
Python
161 lines
5.2 KiB
Python
"""Sovereign tech stack manifest — machine-readable catalog with runtime query tool.
|
|
|
|
Loads ``docs/stack_manifest.json`` and exposes ``query_stack()`` for Timmy to
|
|
introspect his own technology stack at runtime.
|
|
|
|
Issue: #986 (parent: #982 Session Crystallization)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Resolve project root: this file lives at src/timmy/stack_manifest.py
|
|
# Project root is two levels up from src/timmy/
|
|
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
_MANIFEST_PATH = _PROJECT_ROOT / "docs" / "stack_manifest.json"
|
|
|
|
# Cached manifest (loaded on first access)
|
|
_manifest_cache: dict[str, Any] | None = None
|
|
|
|
|
|
def _load_manifest(path: Path | None = None) -> dict[str, Any]:
|
|
"""Load and cache the stack manifest from disk.
|
|
|
|
Args:
|
|
path: Override manifest path (useful for testing).
|
|
|
|
Returns:
|
|
The parsed manifest dict.
|
|
|
|
Raises:
|
|
FileNotFoundError: If the manifest file doesn't exist.
|
|
json.JSONDecodeError: If the manifest is invalid JSON.
|
|
"""
|
|
global _manifest_cache
|
|
|
|
target = path or _MANIFEST_PATH
|
|
|
|
if _manifest_cache is not None and path is None:
|
|
return _manifest_cache
|
|
|
|
with open(target, encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
if path is None:
|
|
_manifest_cache = data
|
|
return data
|
|
|
|
|
|
def _reset_cache() -> None:
|
|
"""Reset the manifest cache (for testing)."""
|
|
global _manifest_cache
|
|
_manifest_cache = None
|
|
|
|
|
|
def _match_tool(tool: dict, category: str | None, tool_name: str | None) -> bool:
|
|
"""Check if a tool entry matches the given filters.
|
|
|
|
Matching is case-insensitive and supports partial matches.
|
|
"""
|
|
if tool_name:
|
|
name_lower = tool_name.lower()
|
|
tool_lower = tool["tool"].lower()
|
|
if name_lower not in tool_lower and tool_lower not in name_lower:
|
|
return False
|
|
return True
|
|
|
|
|
|
def query_stack(
|
|
category: str | None = None,
|
|
tool: str | None = None,
|
|
) -> str:
|
|
"""Query Timmy's sovereign tech stack manifest.
|
|
|
|
Use this tool to discover what tools, frameworks, and services are available
|
|
in the sovereign stack — with exact versions, install commands, and roles.
|
|
|
|
Args:
|
|
category: Filter by category name or ID (e.g., 'llm_inference',
|
|
'Music and Voice', 'nostr'). Case-insensitive, partial match.
|
|
tool: Filter by tool name (e.g., 'Ollama', 'FastMCP', 'Neo4j').
|
|
Case-insensitive, partial match.
|
|
|
|
Returns:
|
|
Formatted string listing matching tools with version, role, install
|
|
command, license, and status. Returns a summary if no filters given.
|
|
|
|
Examples:
|
|
query_stack() → Full stack summary
|
|
query_stack(category="llm") → All LLM inference tools
|
|
query_stack(tool="Ollama") → Ollama details
|
|
query_stack(category="nostr", tool="LND") → LND in the Nostr category
|
|
"""
|
|
try:
|
|
manifest = _load_manifest()
|
|
except FileNotFoundError:
|
|
return "Stack manifest not found. Run from the project root or check docs/stack_manifest.json."
|
|
except json.JSONDecodeError as exc:
|
|
return f"Stack manifest is invalid JSON: {exc}"
|
|
|
|
categories = manifest.get("categories", [])
|
|
results: list[str] = []
|
|
match_count = 0
|
|
|
|
for cat in categories:
|
|
cat_id = cat.get("id", "")
|
|
cat_name = cat.get("name", "")
|
|
|
|
# Category filter
|
|
if category:
|
|
cat_lower = category.lower()
|
|
if (
|
|
cat_lower not in cat_id.lower()
|
|
and cat_lower not in cat_name.lower()
|
|
):
|
|
continue
|
|
|
|
cat_tools = cat.get("tools", [])
|
|
matching_tools = []
|
|
|
|
for t in cat_tools:
|
|
if _match_tool(t, category, tool):
|
|
matching_tools.append(t)
|
|
match_count += 1
|
|
|
|
if matching_tools:
|
|
results.append(f"\n## {cat_name} ({cat_id})")
|
|
results.append(f"{cat.get('description', '')}\n")
|
|
for t in matching_tools:
|
|
status_badge = f" [{t['status'].upper()}]" if t.get("status") != "active" else ""
|
|
results.append(f" **{t['tool']}** v{t['version']}{status_badge}")
|
|
results.append(f" Role: {t['role']}")
|
|
results.append(f" Install: `{t['install_command']}`")
|
|
results.append(f" License: {t['license']}")
|
|
results.append("")
|
|
|
|
if not results:
|
|
if category and tool:
|
|
return f'No tools found matching category="{category}", tool="{tool}".'
|
|
if category:
|
|
return f'No category matching "{category}". Available: {", ".join(c["id"] for c in categories)}'
|
|
if tool:
|
|
return f'No tool matching "{tool}" in any category.'
|
|
return "Stack manifest is empty."
|
|
|
|
header = f"Sovereign Tech Stack — {match_count} tool(s) matched"
|
|
if category:
|
|
header += f' (category: "{category}")'
|
|
if tool:
|
|
header += f' (tool: "{tool}")'
|
|
|
|
version = manifest.get("version", "unknown")
|
|
footer = f"\n---\nManifest v{version} | Source: docs/stack_manifest.json"
|
|
|
|
return header + "\n" + "\n".join(results) + footer
|