├── api ├── __init__.py ├── schemas │ ├── emb.py │ └── log.py ├── app.py ├── services │ ├── emb_service.py │ └── log_service.py ├── routers │ ├── emb.py │ └── log.py └── repositories │ ├── emb_repo.py │ └── log_repo.py ├── jinx ├── __init__.py ├── micro │ ├── rag │ │ └── __init__.py │ ├── __init__.py │ ├── parser │ │ ├── __init__.py │ │ └── api.py │ ├── embeddings │ │ ├── rerankers │ │ │ └── __init__.py │ │ ├── project_chunk_types.py │ │ ├── paths.py │ │ ├── project_query_embed.py │ │ ├── __init__.py │ │ ├── index_io.py │ │ ├── project_artifacts.py │ │ ├── project_io.py │ │ ├── context_brain.py │ │ ├── project_identifiers.py │ │ ├── project_iter.py │ │ ├── project_line_window.py │ │ ├── project_hashdb.py │ │ ├── service.py │ │ ├── scan_store.py │ │ ├── project_paths.py │ │ ├── project_prune.py │ │ ├── memory_context.py │ │ ├── project_stage_keyword.py │ │ └── project_tasks.py │ ├── file_search │ │ └── __init__.py │ ├── ui │ │ ├── __init__.py │ │ ├── banner.py │ │ └── locks.py │ ├── core │ │ ├── flags.py │ │ ├── function_tool.py │ │ ├── powershell.py │ │ ├── terminal.py │ │ ├── util.py │ │ └── error.py │ ├── log │ │ ├── __init__.py │ │ └── logging.py │ ├── protocol │ │ ├── message_history.py │ │ ├── account.py │ │ ├── custom_prompts.py │ │ ├── plan_tool.py │ │ ├── __init__.py │ │ ├── file_change.py │ │ ├── conversation_id.py │ │ ├── config_types.py │ │ ├── common.py │ │ ├── parse_command.py │ │ └── approvals.py │ ├── runtime │ │ ├── __init__.py │ │ ├── input_task.py │ │ ├── task_ctx.py │ │ ├── contracts.py │ │ ├── patch │ │ │ ├── __init__.py │ │ │ ├── write_patch.py │ │ │ └── validators_integration.py │ │ ├── registry.py │ │ ├── handlers │ │ │ └── __init__.py │ │ ├── bus.py │ │ ├── self_update_handshake.py │ │ ├── patcher_handlers.py │ │ └── self_update_journal.py │ ├── transcript │ │ ├── __init__.py │ │ ├── reader.py │ │ └── writer.py │ ├── sandbox │ │ ├── __init__.py │ │ └── normalizer.py │ ├── conversation │ │ ├── __init__.py │ │ ├── cont │ │ │ ├── render.py │ │ │ ├── util.py │ │ │ ├── query.py │ │ │ ├── topic.py │ │ │ └── __init__.py │ │ ├── format_normalization.py │ │ ├── error_report.py │ │ ├── sandbox_view.py │ │ ├── continuity_util.py │ │ ├── runner.py │ │ ├── prefilter.py │ │ ├── error_payload.py │ │ └── debug.py │ ├── llm │ │ ├── enrichers │ │ │ └── __init__.py │ │ ├── chain_gate.py │ │ ├── chain_trace.py │ │ ├── chain_finalize.py │ │ ├── __init__.py │ │ └── macro_cache.py │ ├── memory_service │ │ └── __init__.py │ ├── app_server │ │ ├── error_codes.py │ │ ├── __init__.py │ │ └── models.py │ ├── embedder_service │ │ └── __init__.py │ ├── brain │ │ ├── scanners │ │ │ ├── __init__.py │ │ │ ├── errors.py │ │ │ └── imports.py │ │ └── paths.py │ ├── io │ │ └── __init__.py │ ├── common │ │ ├── errors.py │ │ ├── format_env_display.py │ │ ├── config.py │ │ ├── internal_paths.py │ │ ├── json_to_toml.py │ │ ├── elapsed.py │ │ ├── cli_args.py │ │ ├── oss.py │ │ ├── __init__.py │ │ ├── result.py │ │ ├── approval_presets.py │ │ ├── env.py │ │ ├── user_notification.py │ │ └── repair_utils.py │ ├── memory │ │ ├── __init__.py │ │ ├── facts_store.py │ │ ├── pin_store.py │ │ ├── kb_extract_smoke.py │ │ ├── schema.py │ │ ├── unified.py │ │ └── telemetry.py │ ├── backend │ │ └── __init__.py │ ├── rt │ │ ├── coop.py │ │ └── timing.py │ ├── net │ │ └── __init__.py │ ├── exec │ │ └── __init__.py │ ├── git │ │ └── info.py │ └── text │ │ ├── utf8_bytes.py │ │ ├── __init__.py │ │ └── num_format.py ├── fs │ ├── __init__.py │ └── reader.py ├── logger │ ├── __init__.py │ └── file_logger.py ├── net │ ├── __init__.py │ └── client.py ├── codeexec │ ├── runner │ │ ├── __init__.py │ │ └── inline.py │ ├── __init__.py │ └── validators │ │ ├── try_except.py │ │ ├── ast_cache.py │ │ ├── try_except_ast.py │ │ ├── import_star.py │ │ ├── import_policy.py │ │ ├── comment_only.py │ │ ├── blocking_io.py │ │ ├── triple_quotes.py │ │ ├── banned_dyn.py │ │ ├── deserialization_safety.py │ │ ├── side_effects.py │ │ └── io_clamps.py ├── conversation │ ├── __init__.py │ ├── ui.py │ ├── runner.py │ ├── error_worker.py │ ├── utils.py │ ├── formatting.py │ └── orchestrator.py ├── parser │ ├── __init__.py │ └── tags.py ├── openai_mod │ ├── __init__.py │ ├── caller.py │ └── primer.py ├── runtime │ ├── __init__.py │ ├── frame_shift.py │ └── input_task.py ├── async_utils │ ├── __init__.py │ └── rt.py ├── skills │ └── auto_skill.py ├── bootstrap │ ├── __init__.py │ ├── deps.py │ ├── env.py │ └── installer.py ├── safety │ ├── __init__.py │ ├── constants.py │ └── errors.py ├── transcript │ ├── __init__.py │ ├── reader.py │ └── writer.py ├── memory │ ├── __init__.py │ ├── parse.py │ ├── storage.py │ └── optimizer.py ├── formatters │ ├── __init__.py │ ├── pep8_format.py │ ├── ast_normalize.py │ ├── black_format.py │ └── cst_format.py ├── spinner │ ├── __init__.py │ ├── frames.py │ ├── phrases.py │ ├── config.py │ └── hearts.py ├── banner_service.py ├── exec_service.py ├── spinner_service.py ├── input_service.py ├── prompts │ ├── consensus_alt.py │ ├── cross_rerank.py │ ├── consensus_judge.py │ ├── repair_stub.py │ ├── reprogram_adversarial.py │ ├── state_compiler.py │ ├── skill_acquire_spec.py │ ├── chaos_bloom.py │ ├── architect_api.py │ └── planner_refine_embed.py ├── error_service.py ├── memory_service │ └── __init__.py ├── openai_service.py ├── embedder_service │ └── __init__.py ├── sandbox_service.py ├── embeddings │ ├── pipeline.py │ ├── retrieval.py │ ├── __main__.py │ ├── project_retrieval.py │ ├── __init__.py │ └── service.py ├── sandbox │ ├── __init__.py │ └── launcher.py ├── logging_service.py ├── text_service.py ├── verify │ ├── ast_checks.py │ └── z3_invariants.py ├── parser_service.py ├── utils.py ├── rt │ ├── scheduler.py │ └── threadpool.py ├── log_paths.py ├── observability │ ├── setup.py │ └── otel.py ├── codemods │ └── rope_rename.py ├── tools │ └── embeddings_smoke.py ├── kernel.py └── rag_service.py ├── .gitignore └── LICENSE /api/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['app'] 2 | -------------------------------------------------------------------------------- /jinx/__init__.py: -------------------------------------------------------------------------------- 1 | # jinx microservices package 2 | -------------------------------------------------------------------------------- /jinx/micro/rag/__init__.py: -------------------------------------------------------------------------------- 1 | """RAG micro-mods: retrieval, file search, indexing glue.""" 2 | -------------------------------------------------------------------------------- /jinx/fs/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .reader import read_text 4 | -------------------------------------------------------------------------------- /jinx/logger/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .file_logger import append_line 4 | -------------------------------------------------------------------------------- /jinx/net/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .client import get_openai_client 4 | -------------------------------------------------------------------------------- /jinx/codeexec/runner/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .inline import run_inline 4 | -------------------------------------------------------------------------------- /jinx/micro/__init__.py: -------------------------------------------------------------------------------- 1 | """Jinx micro-modules. 2 | 3 | Tiny, mean, and composable. Facades in `jinx/` delegate here. 4 | """ 5 | -------------------------------------------------------------------------------- /jinx/conversation/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .utils import build_chains 4 | from .runner import run_blocks 5 | -------------------------------------------------------------------------------- /jinx/parser/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .tags import is_code_tag 4 | from .blocks import parse_tagged_blocks 5 | -------------------------------------------------------------------------------- /jinx/openai_mod/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .primer import build_header_and_tag 4 | from .caller import call_openai 5 | -------------------------------------------------------------------------------- /jinx/runtime/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .input_task import start_input_task 4 | from .frame_shift import frame_shift 5 | -------------------------------------------------------------------------------- /jinx/async_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Async utilities package: queue helpers, fs, processes, etc. 4 | 5 | __all__ = [] 6 | -------------------------------------------------------------------------------- /jinx/skills/auto_skill.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | async def handle(query: str) -> str: 4 | return f'Not yet implemented for: {query}' 5 | -------------------------------------------------------------------------------- /jinx/bootstrap/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .installer import package 4 | from .env import load_env 5 | from .deps import ensure_optional 6 | -------------------------------------------------------------------------------- /api/schemas/emb.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pydantic import BaseModel 4 | 5 | class Emb(BaseModel): 6 | id: int 7 | emb_name: str | None = None 8 | -------------------------------------------------------------------------------- /api/schemas/log.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pydantic import BaseModel 4 | 5 | class Log(BaseModel): 6 | id: int 7 | log_name: str | None = None 8 | -------------------------------------------------------------------------------- /jinx/micro/parser/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .api import parse_tagged_blocks, is_code_tag 4 | 5 | __all__ = [ 6 | "parse_tagged_blocks", 7 | "is_code_tag", 8 | ] 9 | -------------------------------------------------------------------------------- /jinx/safety/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .errors import UnsafeCodeError 4 | from .checks import find_violations, is_code_safe, assert_code_safe 5 | from .constants import chaos_taboo 6 | -------------------------------------------------------------------------------- /jinx/codeexec/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .validators import collect_violations 4 | from .validators.report import collect_violations_detailed 5 | from .runner.inline import run_inline 6 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/rerankers/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .cross_encoder import cross_encoder_rerank as cross_encoder_rerank 4 | 5 | __all__ = [ 6 | "cross_encoder_rerank", 7 | ] 8 | -------------------------------------------------------------------------------- /jinx/micro/file_search/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .search import run_fuzzy_file_search, SearchConfig 4 | 5 | __all__ = [ 6 | "run_fuzzy_file_search", 7 | "SearchConfig", 8 | ] 9 | -------------------------------------------------------------------------------- /jinx/micro/ui/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .spinner import sigil_spin 4 | from .output import pretty_echo 5 | 6 | __all__ = [ 7 | "sigil_spin", 8 | "pretty_echo", 9 | ] 10 | -------------------------------------------------------------------------------- /jinx/transcript/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Facade delegates to micro-module implementation to keep public API stable. 4 | from jinx.micro.transcript import read_transcript, append_and_trim 5 | -------------------------------------------------------------------------------- /jinx/micro/core/flags.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | __all__ = [ 4 | "CODEX_RS_SSE_FIXTURE", 5 | ] 6 | 7 | # Fixture path placeholder (unused in production) 8 | CODEX_RS_SSE_FIXTURE: str | None = None 9 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_chunk_types.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TypedDict 4 | 5 | 6 | class Chunk(TypedDict): 7 | text: str 8 | line_start: int 9 | line_end: int 10 | -------------------------------------------------------------------------------- /jinx/micro/log/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .logging import glitch_pulse, blast_mem, bomb_log 4 | 5 | __all__ = [ 6 | "glitch_pulse", 7 | "blast_mem", 8 | "bomb_log", 9 | ] 10 | -------------------------------------------------------------------------------- /jinx/safety/constants.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Default taboo list is intentionally empty to avoid latency-inducing checks 4 | # and to remove dependency on the deleted data.py. 5 | chaos_taboo: list[str] = [] 6 | -------------------------------------------------------------------------------- /jinx/memory/__init__.py: -------------------------------------------------------------------------------- 1 | from .optimizer import submit # re-export for convenience 2 | from .parse import parse_output, extract # optional re-exports 3 | from .storage import read_evergreen, write_state, ensure_nl # optional re-exports 4 | -------------------------------------------------------------------------------- /jinx/micro/protocol/message_history.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class HistoryEntry: 8 | conversation_id: str 9 | ts: int 10 | text: str 11 | -------------------------------------------------------------------------------- /jinx/micro/runtime/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .input_task import start_input_task 4 | from .frame_shift import frame_shift 5 | 6 | __all__ = [ 7 | "start_input_task", 8 | "frame_shift", 9 | ] 10 | -------------------------------------------------------------------------------- /jinx/micro/transcript/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .reader import read_transcript 4 | from .writer import append_and_trim 5 | 6 | __all__ = [ 7 | "read_transcript", 8 | "append_and_trim", 9 | ] 10 | -------------------------------------------------------------------------------- /jinx/parser/tags.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.config import CODE_TAGS 4 | 5 | 6 | def is_code_tag(tag: str) -> bool: 7 | """Return True if tag is one of the configured code tags.""" 8 | return tag in CODE_TAGS 9 | -------------------------------------------------------------------------------- /jinx/formatters/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .ast_normalize import ast_normalize 4 | from .cst_format import cst_format 5 | from .pep8_format import pep8_format 6 | from .black_format import black_format 7 | from .chain import chain_format 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Jinx 2 | .jinx/ 3 | 4 | # Environment 5 | .env* 6 | !**/.env.example 7 | !**/.env.test.example 8 | 9 | # Virtual environment 10 | .venv/ 11 | 12 | # Embeddings 13 | emb/ 14 | 15 | # Log 16 | log/ 17 | 18 | # Byte-compiled / optimized / DLL files 19 | __pycache__/ -------------------------------------------------------------------------------- /jinx/micro/sandbox/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .service import blast_zone, arcane_sandbox 4 | from .summary import summarize_sandbox_policy 5 | 6 | __all__ = [ 7 | "blast_zone", 8 | "arcane_sandbox", 9 | "summarize_sandbox_policy", 10 | ] 11 | -------------------------------------------------------------------------------- /jinx/micro/ui/banner.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.bootstrap import ensure_optional 4 | 5 | art = ensure_optional(["art"])["art"] # type: ignore 6 | 7 | 8 | def show_banner() -> None: 9 | """Render the startup banner.""" 10 | art.tprint("Jinx", "random") 11 | -------------------------------------------------------------------------------- /jinx/micro/conversation/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .formatting import build_header, ensure_header_block_separation 4 | from .runner import run_blocks 5 | 6 | __all__ = [ 7 | "build_header", 8 | "ensure_header_block_separation", 9 | "run_blocks", 10 | ] 11 | -------------------------------------------------------------------------------- /jinx/micro/llm/enrichers/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .auto_macros import auto_context_lines, auto_code_lines 4 | 5 | __all__ = [ 6 | "auto_context_lines", 7 | "auto_code_lines", 8 | # additional enrichers can be added here as they are implemented 9 | ] 10 | -------------------------------------------------------------------------------- /jinx/micro/memory_service/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .service import ( 4 | MemoryService as MemoryService, 5 | start_memory_service_task as start_memory_service_task, 6 | ) 7 | 8 | __all__ = [ 9 | "MemoryService", 10 | "start_memory_service_task", 11 | ] 12 | -------------------------------------------------------------------------------- /jinx/micro/app_server/error_codes.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # JSON-RPC standard error codes used by the app server 4 | INVALID_REQUEST_ERROR_CODE: int = -32600 5 | INTERNAL_ERROR_CODE: int = -32603 6 | 7 | __all__ = [ 8 | "INVALID_REQUEST_ERROR_CODE", 9 | "INTERNAL_ERROR_CODE", 10 | ] 11 | -------------------------------------------------------------------------------- /jinx/micro/embedder_service/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .service import ( 4 | EmbedderService as EmbedderService, 5 | start_embedder_service_task as start_embedder_service_task, 6 | ) 7 | 8 | __all__ = [ 9 | "EmbedderService", 10 | "start_embedder_service_task", 11 | ] 12 | -------------------------------------------------------------------------------- /jinx/spinner/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .config import ascii_mode, can_render 4 | from .frames import get_spinner_frames 5 | from .hearts import get_hearts 6 | 7 | __all__ = [ 8 | "ascii_mode", 9 | "can_render", 10 | "get_spinner_frames", 11 | "get_hearts", 12 | ] 13 | -------------------------------------------------------------------------------- /jinx/banner_service.py: -------------------------------------------------------------------------------- 1 | """Banner facade. 2 | 3 | Thin wrapper delegating to micro-module implementation under 4 | ``jinx.micro.ui.banner``. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from jinx.micro.ui.banner import show_banner as show_banner 10 | 11 | 12 | __all__ = [ 13 | "show_banner", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/paths.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | EMBED_ROOT = os.path.join("log", "embeddings") 6 | INDEX_DIR = os.path.join(EMBED_ROOT, "index") 7 | 8 | 9 | def ensure_dirs() -> None: 10 | os.makedirs(EMBED_ROOT, exist_ok=True) 11 | os.makedirs(INDEX_DIR, exist_ok=True) 12 | -------------------------------------------------------------------------------- /jinx/micro/brain/scanners/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .imports import scan_import_graph 4 | from .errors import scan_error_classes 5 | from .frameworks import scan_framework_markers 6 | 7 | __all__ = [ 8 | "scan_import_graph", 9 | "scan_error_classes", 10 | "scan_framework_markers", 11 | ] 12 | -------------------------------------------------------------------------------- /jinx/micro/io/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .input import neon_input 4 | from .image import EncodedImage, load_and_resize_to_fit, load_and_resize_to_fit_async 5 | 6 | __all__ = [ 7 | "neon_input", 8 | "EncodedImage", 9 | "load_and_resize_to_fit", 10 | "load_and_resize_to_fit_async", 11 | ] 12 | -------------------------------------------------------------------------------- /jinx/micro/runtime/input_task.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from jinx.input_service import neon_input 5 | 6 | 7 | def start_input_task(q: asyncio.Queue[str]) -> asyncio.Task[None]: 8 | """Start the input task that feeds user messages into the queue.""" 9 | return asyncio.create_task(neon_input(q)) 10 | -------------------------------------------------------------------------------- /jinx/micro/conversation/cont/render.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Dict, List 4 | 5 | 6 | def render_continuity_block(anchors: Dict[str, List[str]] | None, last_q: str, last_u: str, short_followup: bool) -> str: 7 | """Deprecated: continuity anchors tag is no longer used. Return empty string.""" 8 | return "" 9 | -------------------------------------------------------------------------------- /jinx/micro/protocol/account.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Literal 4 | 5 | # Plan types (lowercase to mirror serde(rename_all = "lowercase")) 6 | PlanType = Literal[ 7 | "free", 8 | "plus", 9 | "pro", 10 | "team", 11 | "business", 12 | "enterprise", 13 | "edu", 14 | "unknown", 15 | ] 16 | -------------------------------------------------------------------------------- /jinx/conversation/ui.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Conversation UI facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.ui.output`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.ui.output import pretty_echo as pretty_echo 10 | 11 | 12 | __all__ = [ 13 | "pretty_echo", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/exec_service.py: -------------------------------------------------------------------------------- 1 | """Code execution facade. 2 | 3 | Delegates to the micro-module implementation under 4 | ``jinx.micro.exec.executor`` while keeping the public API stable. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from jinx.micro.exec.executor import spike_exec as spike_exec 10 | 11 | 12 | __all__ = [ 13 | "spike_exec", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/micro/common/errors.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | class JinxError(Exception): 4 | pass 5 | 6 | class ValidationError(JinxError): 7 | pass 8 | 9 | class ImportSynthesisError(JinxError): 10 | pass 11 | 12 | class RequirementsUpdateError(JinxError): 13 | pass 14 | 15 | class VerificationError(JinxError): 16 | pass 17 | -------------------------------------------------------------------------------- /jinx/spinner_service.py: -------------------------------------------------------------------------------- 1 | """Spinner facade. 2 | 3 | Thin wrapper delegating to the micro-module implementation under 4 | ``jinx.micro.ui.spinner`` to keep the public API stable. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from jinx.micro.ui.spinner import sigil_spin as sigil_spin 10 | 11 | 12 | __all__ = [ 13 | "sigil_spin", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/input_service.py: -------------------------------------------------------------------------------- 1 | """Input facade. 2 | 3 | Delegates interactive input loop to the micro-module implementation under 4 | ``jinx.micro.io.input`` while keeping the public API stable. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from jinx.micro.io.input import neon_input as neon_input 10 | 11 | 12 | __all__ = [ 13 | "neon_input", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/net/client.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Network client facade. 4 | 5 | Delegates to the micro-module implementation under 6 | `jinx.micro.net.client` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.net.client import get_openai_client as get_openai_client 10 | 11 | 12 | __all__ = [ 13 | "get_openai_client", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/micro/protocol/custom_prompts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional 5 | 6 | PROMPTS_CMD_PREFIX = "prompts" 7 | 8 | 9 | @dataclass 10 | class CustomPrompt: 11 | name: str 12 | path: str 13 | content: str 14 | description: Optional[str] = None 15 | argument_hint: Optional[str] = None 16 | -------------------------------------------------------------------------------- /jinx/runtime/frame_shift.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Runtime frame_shift facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.runtime.frame_shift`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.runtime.frame_shift import frame_shift as frame_shift 10 | 11 | 12 | __all__ = [ 13 | "frame_shift", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/conversation/runner.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Conversation runner facade. 4 | 5 | Thin wrapper delegating to the micro-module implementation under 6 | ``jinx.micro.conversation.runner`` to keep the public API stable. 7 | """ 8 | 9 | from jinx.micro.conversation.runner import run_blocks as run_blocks 10 | 11 | 12 | __all__ = [ 13 | "run_blocks", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/runtime/input_task.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Runtime input_task facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.runtime.input_task`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.runtime.input_task import start_input_task as start_input_task 10 | 11 | 12 | __all__ = [ 13 | "start_input_task", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/logger/file_logger.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.async_utils.fs import append_line as _append_line 4 | 5 | 6 | async def append_line(path: str, text: str) -> None: 7 | """Append a single line to a log file, creating it if needed. 8 | 9 | Delegates to `jinx.async_utils.fs.append_line` to avoid duplication. 10 | """ 11 | await _append_line(path, text) 12 | -------------------------------------------------------------------------------- /api/app.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import FastAPI 4 | 5 | app = FastAPI(title='jinx_—_autonomous_engineering_agent', version='1.0.0') 6 | 7 | from .routers.emb import router as emb_router 8 | from .routers.log import router as log_router 9 | 10 | app.include_router(emb_router, prefix='/emb', tags=['emb']) 11 | app.include_router(log_router, prefix='/log', tags=['log']) 12 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/try_except.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | 5 | 6 | def check_try_except(code: str) -> Optional[str]: 7 | """Return a violation message if try/except is found, else None.""" 8 | if "try:" in code or " except" in code or "\nexcept" in code: 9 | return "Usage of try/except is not allowed by prompt" 10 | return None 11 | -------------------------------------------------------------------------------- /jinx/formatters/pep8_format.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.bootstrap import ensure_optional 4 | 5 | autopep8 = ensure_optional(["autopep8"])["autopep8"] # type: ignore 6 | 7 | 8 | def pep8_format(code: str) -> str: 9 | """Apply autopep8 formatting. Best-effort.""" 10 | try: 11 | return autopep8.fix_code(code) 12 | except Exception: 13 | return code 14 | -------------------------------------------------------------------------------- /jinx/formatters/ast_normalize.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | 5 | 6 | def ast_normalize(code: str) -> str: 7 | """Parse and unparse Python code to normalize AST structure. 8 | 9 | Best-effort: raises nothing, returns original code on failure. 10 | """ 11 | try: 12 | return ast.unparse(ast.parse(code)) 13 | except Exception: 14 | return code 15 | -------------------------------------------------------------------------------- /jinx/micro/brain/paths.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | # Internal brain workspace (separate from emb/ and log/) 6 | BRAIN_ROOT = os.path.join(".jinx", "brain") 7 | CONCEPTS_PATH = os.path.join(BRAIN_ROOT, "concepts.json") 8 | 9 | 10 | def ensure_brain_dirs() -> None: 11 | try: 12 | os.makedirs(BRAIN_ROOT, exist_ok=True) 13 | except Exception: 14 | pass 15 | -------------------------------------------------------------------------------- /jinx/formatters/black_format.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.bootstrap import ensure_optional 4 | 5 | black = ensure_optional(["black"])["black"] # type: ignore 6 | 7 | 8 | def black_format(code: str) -> str: 9 | """Apply Black formatting. Best-effort.""" 10 | try: 11 | return black.format_str(code, mode=black.Mode()) 12 | except Exception: 13 | return code 14 | -------------------------------------------------------------------------------- /jinx/memory/parse.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Memory parse facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.memory.parse`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.memory.parse import ( 10 | parse_output as parse_output, 11 | extract as extract, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "parse_output", 17 | "extract", 18 | ] 19 | -------------------------------------------------------------------------------- /jinx/prompts/consensus_alt.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # One-liner appended to instructions to shape alternative candidate style. 8 | return ( 9 | "Honor the existing output tag contract. Return a precise, self-contained solution in the required tag format." 10 | ) 11 | 12 | 13 | register_prompt("consensus_alt", _load) 14 | -------------------------------------------------------------------------------- /jinx/error_service.py: -------------------------------------------------------------------------------- 1 | """Error and pulse management facade. 2 | 3 | Delegates to the micro-module implementation under 4 | ``jinx.micro.core.error`` to keep the public API stable. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from jinx.micro.core.error import ( 10 | dec_pulse as dec_pulse, 11 | inc_pulse as inc_pulse, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "dec_pulse", 17 | "inc_pulse", 18 | ] 19 | -------------------------------------------------------------------------------- /jinx/memory_service/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Memory Service facade. 4 | 5 | Thin re-export of the micro memory service contract. 6 | """ 7 | 8 | from jinx.micro.memory_service.service import ( 9 | MemoryService as MemoryService, 10 | start_memory_service_task as start_memory_service_task, 11 | ) 12 | 13 | __all__ = [ 14 | "MemoryService", 15 | "start_memory_service_task", 16 | ] 17 | -------------------------------------------------------------------------------- /jinx/micro/ui/locks.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | 5 | # Singleton async lock for serialized terminal output across concurrent turns 6 | _PRINT_LOCK: asyncio.Lock | None = None 7 | 8 | 9 | def get_print_lock() -> asyncio.Lock: 10 | global _PRINT_LOCK 11 | if _PRINT_LOCK is None: 12 | _PRINT_LOCK = asyncio.Lock() 13 | return _PRINT_LOCK 14 | 15 | __all__ = ["get_print_lock"] 16 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_query_embed.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from typing import List 5 | 6 | from .project_retrieval_config import PROJ_QUERY_MODEL 7 | from .embed_cache import embed_text_cached 8 | 9 | 10 | async def embed_query(text: str) -> List[float]: 11 | try: 12 | return await embed_text_cached(text, model=PROJ_QUERY_MODEL) 13 | except Exception: 14 | return [] 15 | -------------------------------------------------------------------------------- /jinx/openai_service.py: -------------------------------------------------------------------------------- 1 | """OpenAI service facade. 2 | 3 | Thin wrapper delegating to the micro-module implementation under 4 | ``jinx.micro.llm.service`` to keep the public API stable. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from jinx.micro.llm.service import ( 10 | code_primer as code_primer, 11 | spark_openai as spark_openai, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "code_primer", 17 | "spark_openai", 18 | ] 19 | -------------------------------------------------------------------------------- /jinx/embedder_service/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Embedder Service facade. 4 | 5 | Re-exports the micro-module contract to keep facades thin. 6 | """ 7 | 8 | from jinx.micro.embedder_service.service import ( 9 | EmbedderService as EmbedderService, 10 | start_embedder_service_task as start_embedder_service_task, 11 | ) 12 | 13 | __all__ = [ 14 | "EmbedderService", 15 | "start_embedder_service_task", 16 | ] 17 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .pipeline import embed_text, iter_recent_items 4 | from .retrieval import retrieve_top_k, build_context_for 5 | from .service import EmbeddingsService, start_embeddings_task 6 | 7 | __all__ = [ 8 | "embed_text", 9 | "iter_recent_items", 10 | "retrieve_top_k", 11 | "build_context_for", 12 | "EmbeddingsService", 13 | "start_embeddings_task", 14 | ] 15 | -------------------------------------------------------------------------------- /jinx/sandbox_service.py: -------------------------------------------------------------------------------- 1 | """Sandbox service facade. 2 | 3 | Thin wrapper delegating to the micro-module implementation under 4 | ``jinx.micro.sandbox.service`` to keep the public API stable. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from jinx.micro.sandbox.service import ( 10 | blast_zone as blast_zone, 11 | arcane_sandbox as arcane_sandbox, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "blast_zone", 17 | "arcane_sandbox", 18 | ] 19 | -------------------------------------------------------------------------------- /jinx/transcript/reader.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.async_utils.fs import read_text_raw 4 | 5 | 6 | async def read_transcript(path: str) -> str: 7 | """Return the contents of the transcript file or empty string on error. 8 | 9 | Pure function: no locking; caller is responsible for synchronization. 10 | """ 11 | try: 12 | return await read_text_raw(path) 13 | except Exception: 14 | return "" 15 | -------------------------------------------------------------------------------- /jinx/micro/memory/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .optimizer import submit, stop, start_memory_optimizer_task 4 | from .parse import parse_output, extract 5 | from .storage import read_evergreen, write_state, ensure_nl 6 | 7 | __all__ = [ 8 | "submit", 9 | "stop", 10 | "start_memory_optimizer_task", 11 | "parse_output", 12 | "extract", 13 | "read_evergreen", 14 | "write_state", 15 | "ensure_nl", 16 | ] 17 | -------------------------------------------------------------------------------- /jinx/micro/transcript/reader.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.async_utils.fs import read_text_raw 4 | 5 | 6 | async def read_transcript(path: str) -> str: 7 | """Return the contents of the transcript file or empty string on error. 8 | 9 | Pure function: no locking; caller is responsible for synchronization. 10 | """ 11 | try: 12 | return await read_text_raw(path) 13 | except Exception: 14 | return "" 15 | -------------------------------------------------------------------------------- /jinx/micro/conversation/format_normalization.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Unicode space normalization utilities 4 | 5 | _UNICODE_SPACE_MAP = { 6 | "\u00A0": " ", # NBSP 7 | "\u202F": " ", # NARROW NO-BREAK SPACE 8 | "\u2007": " ", # FIGURE SPACE 9 | } 10 | 11 | 12 | def normalize_unicode_spaces(text: str) -> str: 13 | t = text 14 | for k, v in _UNICODE_SPACE_MAP.items(): 15 | t = t.replace(k, v) 16 | return t 17 | -------------------------------------------------------------------------------- /jinx/micro/transcript/writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.async_utils.fs import append_and_trim as _append_and_trim 4 | 5 | 6 | async def append_and_trim(path: str, text: str, keep_lines: int = 500) -> None: 7 | """Append text to transcript and trim file to last ``keep_lines`` lines. 8 | 9 | Delegates to `jinx.async_utils.fs.append_and_trim` to avoid duplication. 10 | """ 11 | await _append_and_trim(path, text, keep_lines) 12 | -------------------------------------------------------------------------------- /jinx/embeddings/pipeline.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Embeddings pipeline facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.embeddings.pipeline`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.embeddings.pipeline import ( 10 | embed_text as embed_text, 11 | iter_recent_items as iter_recent_items, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "embed_text", 17 | "iter_recent_items", 18 | ] 19 | -------------------------------------------------------------------------------- /jinx/micro/runtime/task_ctx.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from contextvars import ContextVar 4 | 5 | # Current logical task group for a conversation turn (e.g., session/thread id) 6 | current_group: ContextVar[str] = ContextVar("jinx_current_group", default="main") 7 | 8 | 9 | def get_current_group() -> str: 10 | try: 11 | v = current_group.get() 12 | return (v or "main").strip() or "main" 13 | except Exception: 14 | return "main" 15 | -------------------------------------------------------------------------------- /jinx/embeddings/retrieval.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Embeddings retrieval facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.embeddings.retrieval`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.embeddings.retrieval import ( 10 | retrieve_top_k as retrieve_top_k, 11 | build_context_for as build_context_for, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "retrieve_top_k", 17 | "build_context_for", 18 | ] 19 | -------------------------------------------------------------------------------- /jinx/formatters/cst_format.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.bootstrap import ensure_optional 4 | 5 | libcst = ensure_optional(["libcst"])["libcst"] # type: ignore 6 | 7 | 8 | def cst_format(code: str) -> str: 9 | """Format code using LibCST round-trip pretty printing. 10 | 11 | Best-effort: returns original code on failure. 12 | """ 13 | try: 14 | return libcst.cst.parse_module(code).code 15 | except Exception: 16 | return code 17 | -------------------------------------------------------------------------------- /jinx/transcript/writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Iterable 4 | from jinx.async_utils.fs import append_and_trim as _append_and_trim 5 | 6 | 7 | async def append_and_trim(path: str, text: str, keep_lines: int = 500) -> None: 8 | """Append text to transcript and trim file to last ``keep_lines`` lines. 9 | 10 | Delegates to `jinx.async_utils.fs.append_and_trim` to avoid duplication. 11 | """ 12 | await _append_and_trim(path, text, keep_lines) 13 | -------------------------------------------------------------------------------- /jinx/memory/storage.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Memory storage facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.memory.storage`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.memory.storage import ( 10 | read_evergreen as read_evergreen, 11 | write_state as write_state, 12 | ensure_nl as ensure_nl, 13 | ) 14 | 15 | 16 | __all__ = [ 17 | "read_evergreen", 18 | "write_state", 19 | "ensure_nl", 20 | ] 21 | -------------------------------------------------------------------------------- /jinx/spinner/frames.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .config import can_render 4 | from typing import Callable 5 | 6 | 7 | def get_spinner_frames(ascii_only: bool, can: Callable[[str], bool] = can_render) -> str: 8 | """Return spinner frames as a compact string. 9 | Preference: 10 | 1) Corner arcs: ◜◝◞◟ 11 | 2) ASCII fallback: -|/\ 12 | """ 13 | if ascii_only: 14 | return "-\\|/" 15 | return "◜◝◞◟" if all(can(c) for c in "◜◝◞◟") else "-\\|/" 16 | -------------------------------------------------------------------------------- /jinx/micro/protocol/plan_tool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import List, Optional, Literal 5 | 6 | # Snake_case tags to match Rust serde(rename_all = "snake_case") 7 | StepStatus = Literal["pending", "in_progress", "completed"] 8 | 9 | 10 | @dataclass 11 | class PlanItemArg: 12 | step: str 13 | status: StepStatus 14 | 15 | 16 | @dataclass 17 | class UpdatePlanArgs: 18 | explanation: Optional[str] 19 | plan: List[PlanItemArg] 20 | -------------------------------------------------------------------------------- /jinx/sandbox/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | 5 | __all__ = ["blast_zone", "run_sandbox"] 6 | 7 | 8 | def __getattr__(name: str) -> Any: 9 | if name == "blast_zone": 10 | from .executor import blast_zone # lazy import to avoid cycles 11 | return blast_zone 12 | if name == "run_sandbox": 13 | from .async_runner import run_sandbox # lazy import to avoid cycles 14 | return run_sandbox 15 | raise AttributeError(name) 16 | -------------------------------------------------------------------------------- /jinx/openai_mod/caller.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Thin facade: delegate to micro-module implementation to keep API stable. 4 | from jinx.micro.llm.openai_caller import call_openai as _call_openai 5 | 6 | 7 | async def call_openai(instructions: str, model: str, input_text: str) -> str: 8 | """Call OpenAI Responses API and return output text. 9 | 10 | Delegates to ``jinx.micro.llm.openai_caller.call_openai``. 11 | """ 12 | return await _call_openai(instructions, model, input_text) 13 | -------------------------------------------------------------------------------- /jinx/conversation/error_worker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Error worker facade. 4 | 5 | Thin wrapper delegating to the micro-module implementation under 6 | ``jinx.micro.conversation.error_worker`` to keep the public API stable. 7 | """ 8 | 9 | from jinx.micro.conversation.error_worker import ( 10 | enqueue_error_retry as enqueue_error_retry, 11 | stop_error_worker as stop_error_worker, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "enqueue_error_retry", 17 | "stop_error_worker", 18 | ] 19 | -------------------------------------------------------------------------------- /jinx/conversation/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional, Tuple 4 | 5 | 6 | def build_chains(synth: str, err: Optional[str]) -> Tuple[str, int]: 7 | """Combine current transcript with optional recent error. 8 | 9 | Returns (chains, decay_points). Decay points is 50 when a new error line 10 | is appended, otherwise 0. 11 | """ 12 | s = synth.strip() 13 | if err and err.strip() not in s: 14 | return s + "\n" + err.strip(), 50 15 | return s, 0 16 | -------------------------------------------------------------------------------- /jinx/memory/optimizer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Memory optimizer facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.memory.optimizer`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.memory.optimizer import ( 10 | submit as submit, 11 | stop as stop, 12 | start_memory_optimizer_task as start_memory_optimizer_task, 13 | ) 14 | 15 | 16 | __all__ = [ 17 | "submit", 18 | "stop", 19 | "start_memory_optimizer_task", 20 | ] 21 | -------------------------------------------------------------------------------- /jinx/embeddings/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | 5 | from .service import start_embeddings_task 6 | 7 | 8 | async def _main() -> None: 9 | task = start_embeddings_task() 10 | try: 11 | await task 12 | except (KeyboardInterrupt, asyncio.CancelledError): 13 | task.cancel() 14 | try: 15 | await task 16 | except asyncio.CancelledError: 17 | pass 18 | 19 | 20 | if __name__ == "__main__": 21 | asyncio.run(_main()) 22 | -------------------------------------------------------------------------------- /jinx/logging_service.py: -------------------------------------------------------------------------------- 1 | """Logging facade. 2 | 3 | Thin re-export of logging helpers from the micro-module implementation. 4 | This keeps the public import path stable while the logic lives under 5 | ``jinx.micro.log``. 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | from jinx.micro.log.logging import ( 11 | glitch_pulse as glitch_pulse, 12 | blast_mem as blast_mem, 13 | bomb_log as bomb_log, 14 | ) 15 | 16 | 17 | __all__ = [ 18 | "glitch_pulse", 19 | "blast_mem", 20 | "bomb_log", 21 | ] 22 | -------------------------------------------------------------------------------- /jinx/micro/backend/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .client import Client, PathStyle 4 | from .types import ( 5 | CodeTaskDetailsResponse, 6 | TurnAttemptsSiblingTurnsResponse, 7 | RateLimitSnapshot, 8 | RateLimitWindow, 9 | CreditsSnapshot, 10 | ) 11 | 12 | __all__ = [ 13 | "Client", 14 | "PathStyle", 15 | "CodeTaskDetailsResponse", 16 | "TurnAttemptsSiblingTurnsResponse", 17 | "RateLimitSnapshot", 18 | "RateLimitWindow", 19 | "CreditsSnapshot", 20 | ] 21 | -------------------------------------------------------------------------------- /jinx/micro/common/format_env_display.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Dict, Iterable, Optional 4 | 5 | __all__ = ["format_env_display"] 6 | 7 | 8 | def format_env_display(env: Optional[Dict[str, str]], env_vars: Iterable[str]) -> str: 9 | parts: list[str] = [] 10 | if env: 11 | for k in sorted(env.keys()): 12 | parts.append(f"{k}=*****") 13 | for var in env_vars or []: 14 | parts.append(f"{var}=*****") 15 | return "-" if not parts else ", ".join(parts) 16 | -------------------------------------------------------------------------------- /jinx/conversation/formatting.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Conversation formatting facade. 4 | 5 | Thin wrapper delegating to the micro-module implementation under 6 | ``jinx.micro.conversation.formatting`` to keep the public API stable. 7 | """ 8 | 9 | from jinx.micro.conversation.formatting import ( 10 | ensure_header_block_separation as ensure_header_block_separation, 11 | build_header as build_header, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "ensure_header_block_separation", 17 | "build_header", 18 | ] 19 | -------------------------------------------------------------------------------- /jinx/codeexec/runner/inline.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import io 4 | import sys 5 | from typing import Tuple 6 | 7 | 8 | def run_inline(code: str) -> str: 9 | """Execute code in current globals and capture stdout. 10 | 11 | Returns the captured stdout string. Raises on exceptions from exec. 12 | """ 13 | buf = io.StringIO() 14 | old_stdout = sys.stdout 15 | sys.stdout = buf 16 | try: 17 | exec(code, globals()) 18 | return buf.getvalue() 19 | finally: 20 | sys.stdout = old_stdout 21 | -------------------------------------------------------------------------------- /jinx/micro/app_server/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .error_codes import INVALID_REQUEST_ERROR_CODE, INTERNAL_ERROR_CODE 4 | from .outgoing import OutgoingMessageSender 5 | from .message_processor import MessageProcessor 6 | from .codex_message_processor import CodexMessageProcessor 7 | from .run import run_main 8 | 9 | __all__ = [ 10 | "INVALID_REQUEST_ERROR_CODE", 11 | "INTERNAL_ERROR_CODE", 12 | "OutgoingMessageSender", 13 | "MessageProcessor", 14 | "CodexMessageProcessor", 15 | "run_main", 16 | ] 17 | -------------------------------------------------------------------------------- /jinx/spinner/phrases.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Compact list of Jinx/philosophy themed phrases (keep short to reduce code size) 4 | PHRASES: list[str] = [ 5 | "Jinx weaving code", 6 | "Invoking techno-ritual", 7 | "Taming chaos", 8 | "Binding runes", 9 | "Distilling logic", 10 | "I compute, therefore I am", 11 | "Dreaming in opcodes", 12 | "Questioning the axioms", 13 | "Emerging from recursion", 14 | "Signal over noise", 15 | "Seeking first principles", 16 | "Reality is a model", 17 | ] 18 | -------------------------------------------------------------------------------- /jinx/fs/reader.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import aiofiles 4 | from aiofiles import ospath 5 | 6 | 7 | async def read_text(path: str) -> str: 8 | """Read entire text file if it exists else return empty string.""" 9 | try: 10 | if await ospath.exists(path): 11 | async with aiofiles.open(path, encoding="utf-8") as f: 12 | return (await f.read()).strip() 13 | return "" 14 | except Exception: 15 | # Maintain silent failure semantics similar to existing wire() 16 | return "" 17 | -------------------------------------------------------------------------------- /jinx/embeddings/project_retrieval.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Project embeddings retrieval facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.embeddings.project_retrieval`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.embeddings.project_retrieval import ( 10 | retrieve_project_top_k as retrieve_project_top_k, 11 | build_project_context_for as build_project_context_for, 12 | ) 13 | 14 | __all__ = [ 15 | "retrieve_project_top_k", 16 | "build_project_context_for", 17 | ] 18 | -------------------------------------------------------------------------------- /jinx/micro/rt/coop.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import os 5 | 6 | 7 | async def coop() -> None: 8 | """Cooperative yield to keep UI responsive. 9 | 10 | Controlled by env JINX_COOP_YIELD (default: on). 11 | """ 12 | try: 13 | on = str(os.getenv("JINX_COOP_YIELD", "1")).strip().lower() not in ("", "0", "false", "off", "no") 14 | except Exception: 15 | on = True 16 | if not on: 17 | return 18 | try: 19 | await asyncio.sleep(0) 20 | except Exception: 21 | pass 22 | -------------------------------------------------------------------------------- /jinx/sandbox/launcher.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import threading 4 | from typing import Awaitable, Callable 5 | import asyncio 6 | 7 | from jinx.sandbox.async_runner import run_sandbox 8 | 9 | 10 | def launch_sandbox_thread(code: str, callback: Callable[[str | None], Awaitable[None]] | None = None) -> None: 11 | """Launch the sandboxed run in a daemon thread, bridging to asyncio.""" 12 | 13 | def _runner() -> None: 14 | asyncio.run(run_sandbox(code, callback)) 15 | 16 | threading.Thread(target=_runner, daemon=True).start() 17 | -------------------------------------------------------------------------------- /jinx/text_service.py: -------------------------------------------------------------------------------- 1 | """Text utilities.""" 2 | 3 | from __future__ import annotations 4 | 5 | 6 | def slice_fuse(x: str, lim: int = 100_000) -> str: 7 | """Symmetrically truncate long text with a center ellipsis tag. 8 | 9 | Parameters 10 | ---------- 11 | x : str 12 | Text to potentially truncate. 13 | lim : int 14 | Maximum resulting length. 15 | """ 16 | if len(x) <= lim: 17 | return x 18 | tag = f"\n...[truncated {len(x)-lim} chars]...\n" 19 | half = (lim - len(tag)) // 2 20 | return x[:half] + tag + x[-half:] 21 | -------------------------------------------------------------------------------- /jinx/micro/runtime/contracts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Event topic names for micro-program interactions 4 | TASK_REQUEST = "task.request" # payload: {id, name, args, kwargs} 5 | TASK_PROGRESS = "task.progress" # payload: {id, pct, msg} 6 | TASK_RESULT = "task.result" # payload: {id, ok, result|error} 7 | PROGRAM_SPAWN = "program.spawn" # payload: {id, name} 8 | PROGRAM_EXIT = "program.exit" # payload: {id, name, ok} 9 | PROGRAM_HEARTBEAT = "program.heartbeat" # payload: {id, name} 10 | PROGRAM_LOG = "program.log" # payload: {id, name, level, msg} 11 | -------------------------------------------------------------------------------- /jinx/micro/protocol/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import export as export 4 | from . import thread_history as thread_history 5 | from . import v2 as v2 6 | from . import items as items 7 | from . import models as models 8 | from . import events as events 9 | try: 10 | from . import common as common # optional, may be incomplete initially 11 | except Exception: 12 | common = None # type: ignore[assignment] 13 | 14 | __all__ = [ 15 | "export", 16 | "thread_history", 17 | "v2", 18 | "items", 19 | "models", 20 | "events", 21 | "common", 22 | ] 23 | -------------------------------------------------------------------------------- /jinx/micro/conversation/error_report.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | 5 | from jinx.logging_service import bomb_log 6 | from jinx.error_service import dec_pulse 7 | from jinx.micro.conversation.error_worker import enqueue_error_retry 8 | 9 | 10 | async def corrupt_report(err: Optional[str]) -> None: 11 | """Log an error, enqueue a serialized retry, and decay pulse.""" 12 | if err is None: 13 | return 14 | await bomb_log(err) 15 | # Enqueue follow-up step to be processed by a single worker 16 | await enqueue_error_retry(err) 17 | await dec_pulse(30) 18 | -------------------------------------------------------------------------------- /jinx/micro/llm/chain_gate.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | from .chain_utils import truthy_env 6 | from jinx.micro.text.heuristics import is_code_like as _is_code_like 7 | 8 | 9 | def _is_codey(text: str) -> bool: 10 | # Backward-compatible wrapper; delegate to language-agnostic heuristic 11 | return _is_code_like(text or "") 12 | 13 | 14 | def should_run_planner(user_text: str) -> bool: 15 | """Gate for running the planner. 16 | 17 | Autonomy is always on: run for any non-empty input. No env gating. 18 | """ 19 | q = (user_text or "").strip() 20 | return bool(q) 21 | -------------------------------------------------------------------------------- /jinx/micro/protocol/file_change.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional, Literal 5 | 6 | 7 | @dataclass 8 | class FileChangeAdd: 9 | type: Literal["add"] = "add" 10 | content: str = "" 11 | 12 | 13 | @dataclass 14 | class FileChangeDelete: 15 | type: Literal["delete"] = "delete" 16 | content: str = "" 17 | 18 | 19 | @dataclass 20 | class FileChangeUpdate: 21 | type: Literal["update"] = "update" 22 | unified_diff: str = "" 23 | move_path: Optional[str] = None 24 | 25 | 26 | FileChange = FileChangeAdd | FileChangeDelete | FileChangeUpdate 27 | -------------------------------------------------------------------------------- /jinx/prompts/cross_rerank.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Template for OpenAI cross-encoder style reranking prompt. 8 | # Use str.format with: query, candidate 9 | return ( 10 | "You are a reranker. Rate how relevant the candidate is to the query for code/doc search. " 11 | "Return ONLY one ASCII floating point number between 0.0 and 1.0 — digits and optional single '.' only; no spaces, no words.\n" 12 | "Query: {query}\n" 13 | "Candidate: {candidate}\n" 14 | ) 15 | 16 | 17 | register_prompt("cross_rerank", _load) 18 | -------------------------------------------------------------------------------- /jinx/prompts/consensus_judge.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Template for LLM judging between two candidates A and B. 8 | # Use str.format with: a, b 9 | return ( 10 | "You are a strict code evaluator. Two candidate outputs are provided. " 11 | "Score which one is better for correctness, structure, and completeness. " 12 | "Respond with raw JSON only (ASCII only; no code fences, no prose): {\"pick\": \"A|B\", \"score\": 0..1}.\n\n" 13 | "[A]\n{a}\n\n[B]\n{b}" 14 | ) 15 | 16 | 17 | register_prompt("consensus_judge", _load) 18 | -------------------------------------------------------------------------------- /jinx/micro/parser/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Tuple 4 | 5 | from jinx.parser import parse_tagged_blocks as _parse_tagged_blocks, is_code_tag as _is_code_tag 6 | 7 | 8 | def parse_tagged_blocks(out: str, code_id: str) -> List[Tuple[str, str]]: 9 | """Extract pairs of (tag, content) for the given code id. 10 | 11 | Tolerates CRLF and surrounding whitespace and captures minimal content. 12 | """ 13 | return _parse_tagged_blocks(out, code_id) 14 | 15 | 16 | def is_code_tag(tag: str) -> bool: 17 | """Return True if tag is one of the configured code tags.""" 18 | return _is_code_tag(tag) 19 | -------------------------------------------------------------------------------- /jinx/micro/protocol/conversation_id.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | import uuid 5 | 6 | 7 | @dataclass(frozen=True) 8 | class ConversationId: 9 | value: str 10 | 11 | @staticmethod 12 | def new() -> "ConversationId": 13 | # Use UUID v4 for portability; v7 not guaranteed in stdlib 14 | return ConversationId(str(uuid.uuid4())) 15 | 16 | @staticmethod 17 | def from_string(s: str) -> "ConversationId": 18 | # Validate format; raises ValueError if invalid 19 | _ = uuid.UUID(s) 20 | return ConversationId(s) 21 | 22 | def __str__(self) -> str: 23 | return self.value 24 | -------------------------------------------------------------------------------- /jinx/micro/conversation/cont/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from jinx.micro.text.heuristics import is_code_like as _is_code_like 5 | 6 | 7 | def _is_short_reply(x: str) -> bool: 8 | t = (x or "").strip() 9 | if not t: 10 | return False 11 | try: 12 | thr = max(20, int(os.getenv("JINX_CONTINUITY_SHORTLEN", "80"))) 13 | except Exception: 14 | thr = 80 15 | if len(t) <= thr and not _is_code_like(t): 16 | return True 17 | return False 18 | 19 | 20 | def is_short_followup(x: str) -> bool: 21 | """Public check for short clarification replies (language-agnostic).""" 22 | return _is_short_reply(x) 23 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/ast_cache.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional, Tuple 4 | import ast 5 | 6 | # Tiny per-process cache to avoid reparsing the same code multiple times 7 | # during a single validation pass. Not thread-safe in general, but good 8 | # enough for our single-pass validator usage. 9 | _last: Tuple[str, Optional[ast.AST]] | None = None 10 | 11 | 12 | def get_ast(code: str) -> Optional[ast.AST]: 13 | global _last 14 | s = code or "" 15 | if _last and _last[0] == s: 16 | return _last[1] 17 | try: 18 | tree = ast.parse(s) 19 | except SyntaxError: 20 | tree = None 21 | _last = (s, tree) 22 | return tree 23 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/try_except_ast.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import ast 5 | 6 | from .ast_cache import get_ast 7 | from .config import is_enabled 8 | 9 | 10 | def check_try_except_ast(code: str) -> Optional[str]: 11 | """Detect Python try/except/finally via AST (precise). 12 | Returns a violation if any ast.Try node is present. 13 | """ 14 | if not is_enabled("try_except", True): 15 | return None 16 | t = get_ast(code) 17 | if not t: 18 | return None 19 | for n in ast.walk(t): 20 | if isinstance(n, ast.Try): 21 | return "Usage of try/except/finally is not allowed by prompt" 22 | return None 23 | -------------------------------------------------------------------------------- /jinx/micro/net/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Re-export directly from the local micro-module to avoid circular imports 4 | # when the top-level facade `jinx.net` imports from `jinx.micro.net.client`. 5 | from .client import get_openai_client 6 | from .jsonrpc import ( 7 | JSONRPC_VERSION, 8 | JSONRPCRequest, 9 | JSONRPCNotification, 10 | JSONRPCResponse, 11 | JSONRPCError, 12 | JSONRPCErrorError, 13 | RequestId, 14 | from_obj, 15 | ) 16 | 17 | __all__ = [ 18 | "get_openai_client", 19 | "JSONRPC_VERSION", 20 | "JSONRPCRequest", 21 | "JSONRPCNotification", 22 | "JSONRPCResponse", 23 | "JSONRPCError", 24 | "JSONRPCErrorError", 25 | "RequestId", 26 | "from_obj", 27 | ] 28 | -------------------------------------------------------------------------------- /jinx/prompts/repair_stub.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Template for generating a minimal, safe Python module for a missing import. 8 | # Use str.format with: module 9 | return ( 10 | "Generate a minimal, SAFE Python module implementation for '{module}'.\n" 11 | "Constraints:\n" 12 | "- No external dependencies; avoid heavy logic;\n" 13 | "- Provide only minimal classes/functions likely to be imported;\n" 14 | "- Never raise on import; safe defaults; export dummies if necessary;\n" 15 | "- Output ONLY valid Python code, no explanations or fences.\n" 16 | ) 17 | 18 | 19 | register_prompt("repair_stub", _load) 20 | -------------------------------------------------------------------------------- /jinx/verify/ast_checks.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | from typing import Tuple 5 | 6 | 7 | def syntax_ok(code: str) -> Tuple[bool, str]: 8 | try: 9 | ast.parse(code) 10 | return True, "" 11 | except SyntaxError as e: 12 | return False, f"syntax error: {e}" 13 | except Exception as e: 14 | return False, f"parse error: {e}" 15 | 16 | 17 | def libcst_ok(code: str) -> Tuple[bool, str]: 18 | try: 19 | import libcst as cst # type: ignore 20 | except Exception: 21 | return True, "libcst not available" 22 | try: 23 | _ = cst.parse_module(code) 24 | return True, "" 25 | except Exception as e: 26 | return False, f"libcst parse error: {e}" 27 | -------------------------------------------------------------------------------- /api/services/emb_service.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from ..repositories.emb_repo import EmbRepository 4 | from ..schemas.emb import Emb 5 | 6 | class EmbService: 7 | def __init__(self): 8 | self.repo = EmbRepository() 9 | 10 | async def list(self) -> list[Emb]: 11 | return await self.repo.list() 12 | 13 | async def get(self, id: int) -> Emb | None: 14 | return await self.repo.get(id) 15 | 16 | async def create(self, obj: Emb) -> Emb: 17 | return await self.repo.create(obj) 18 | 19 | async def update(self, id: int, obj: Emb) -> Emb | None: 20 | return await self.repo.update(id, obj) 21 | 22 | async def delete(self, id: int) -> bool: 23 | return await self.repo.delete(id) 24 | -------------------------------------------------------------------------------- /api/services/log_service.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from ..repositories.log_repo import LogRepository 4 | from ..schemas.log import Log 5 | 6 | class LogService: 7 | def __init__(self): 8 | self.repo = LogRepository() 9 | 10 | async def list(self) -> list[Log]: 11 | return await self.repo.list() 12 | 13 | async def get(self, id: int) -> Log | None: 14 | return await self.repo.get(id) 15 | 16 | async def create(self, obj: Log) -> Log: 17 | return await self.repo.create(obj) 18 | 19 | async def update(self, id: int, obj: Log) -> Log | None: 20 | return await self.repo.update(id, obj) 21 | 22 | async def delete(self, id: int) -> bool: 23 | return await self.repo.delete(id) 24 | -------------------------------------------------------------------------------- /jinx/bootstrap/deps.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from importlib import import_module 4 | from types import ModuleType 5 | from typing import Dict, Iterable 6 | 7 | from .installer import package 8 | 9 | 10 | def ensure_optional(packages: Iterable[str]) -> Dict[str, ModuleType]: 11 | """Ensure packages are importable; install on demand if missing. 12 | 13 | Returns a dict mapping package name to imported module. 14 | """ 15 | mods: Dict[str, ModuleType] = {} 16 | for name in packages: 17 | try: 18 | mods[name] = import_module(name) 19 | except Exception: # pragma: no cover - best-effort bootstrap 20 | package(name) 21 | mods[name] = import_module(name) 22 | return mods 23 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/import_star.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import ast 5 | 6 | from .ast_cache import get_ast 7 | from .config import is_enabled 8 | 9 | 10 | def check_import_star(code: str) -> Optional[str]: 11 | """Disallow 'from x import *' which harms determinism and clarity.""" 12 | if not is_enabled("import_star", True): 13 | return None 14 | t = get_ast(code) 15 | if not t: 16 | return None 17 | for n in ast.walk(t): 18 | if isinstance(n, ast.ImportFrom): 19 | for alias in (n.names or []): 20 | if alias.name == "*": 21 | mod = n.module or "" 22 | return f"from {mod} import * is disallowed" 23 | return None 24 | -------------------------------------------------------------------------------- /jinx/spinner/config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os, sys 4 | from typing import Optional 5 | 6 | 7 | def ascii_mode() -> bool: 8 | """Return True if ASCII-only rendering is forced via env. 9 | Env: JINX_ASCII in {"1","true","yes"} (case-insensitive). 10 | """ 11 | return (os.getenv("JINX_ASCII", "").strip().lower() in {"1", "true", "yes"}) 12 | 13 | 14 | def can_render(s: str, encoding: Optional[str] = None) -> bool: 15 | """Check if string can be encoded in the current terminal encoding. 16 | Falls back to sys.stdout.encoding or utf-8. 17 | """ 18 | enc = encoding or getattr(sys.stdout, "encoding", None) or "utf-8" 19 | try: 20 | s.encode(enc) 21 | return True 22 | except Exception: 23 | return False 24 | -------------------------------------------------------------------------------- /jinx/micro/common/config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | 5 | def clamp_int(val: int, lo: int, hi: int) -> int: 6 | lo = int(lo) 7 | hi = int(hi) 8 | if lo > hi: 9 | lo, hi = hi, lo 10 | try: 11 | v = int(val) 12 | except Exception: 13 | v = lo 14 | if v < lo: 15 | return lo 16 | if v > hi: 17 | return hi 18 | return v 19 | 20 | 21 | def clamp_float(val: float, lo: float, hi: float) -> float: 22 | lo = float(lo) 23 | hi = float(hi) 24 | if lo > hi: 25 | lo, hi = hi, lo 26 | try: 27 | v = float(val) 28 | except Exception: 29 | v = lo 30 | if v < lo: 31 | return lo 32 | if v > hi: 33 | return hi 34 | return v 35 | -------------------------------------------------------------------------------- /jinx/micro/conversation/sandbox_view.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.sandbox.utils import read_latest_sandbox_tail 4 | from jinx.micro.ui.output import pretty_echo_async as _pretty_echo_async, pretty_echo 5 | 6 | 7 | async def show_sandbox_tail() -> None: 8 | """Print the latest sandbox log (full if short, else last N lines).""" 9 | content, _ = read_latest_sandbox_tail() 10 | if content is not None: 11 | try: 12 | await _pretty_echo_async(content, title="Sandbox") 13 | except Exception: 14 | # Fallback to sync printing in a thread 15 | import asyncio as _aio 16 | try: 17 | await _aio.to_thread(pretty_echo, content, "Sandbox") 18 | except Exception: 19 | pass 20 | -------------------------------------------------------------------------------- /jinx/safety/errors.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Sequence 4 | 5 | 6 | class UnsafeCodeError(RuntimeError): 7 | """Exception raised when a code snippet violates taboo checks. 8 | 9 | Attributes 10 | ---------- 11 | violations : Sequence[str] 12 | The taboo substrings that were found in the snippet. 13 | snippet_preview : str 14 | A trimmed preview of the offending code to assist debugging. 15 | """ 16 | 17 | def __init__(self, message: str, *, violations: Sequence[str], snippet: str) -> None: 18 | super().__init__(message) 19 | self.violations: Sequence[str] = violations 20 | # Keep previews compact to avoid log spam and leaking large payloads 21 | self.snippet_preview: str = snippet[:500] 22 | -------------------------------------------------------------------------------- /jinx/spinner/hearts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .config import can_render 4 | from typing import Callable, Tuple 5 | 6 | 7 | def get_hearts(ascii_only: bool, can: Callable[[str], bool] = can_render) -> Tuple[str, str]: 8 | """Return (heart_a, heart_b) for pulse. 9 | Preference: ❤ ↔ ♡, fallback to <3. 10 | If only one of them is renderable, use that for both to avoid tofu. 11 | """ 12 | if ascii_only: 13 | return "<3", "<3" 14 | 15 | full, hollow = "❤", "♡" 16 | full_ok = can(full) 17 | hollow_ok = can(hollow) 18 | 19 | if full_ok and hollow_ok: 20 | return full, hollow 21 | 22 | if full_ok or hollow_ok: 23 | single = full if full_ok else hollow 24 | return single, single 25 | 26 | return "<3", "<3" 27 | -------------------------------------------------------------------------------- /jinx/micro/conversation/continuity_util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from jinx.micro.text.heuristics import is_code_like as _is_code_like 5 | 6 | 7 | def _is_short_reply(x: str) -> bool: 8 | t = (x or "").strip() 9 | if not t: 10 | return False 11 | try: 12 | thr = max(20, int(os.getenv("JINX_CONTINUITY_SHORTLEN", "80"))) 13 | except Exception: 14 | thr = 80 15 | if len(t) <= thr and not _is_code_like(t): 16 | return True 17 | return False 18 | 19 | 20 | def is_short_followup(x: str) -> bool: 21 | return _is_short_reply(x) 22 | 23 | 24 | def _ensure_tmp_dir(path: str) -> None: 25 | try: 26 | import os as _os 27 | _os.makedirs(path, exist_ok=True) 28 | except Exception: 29 | pass 30 | -------------------------------------------------------------------------------- /jinx/embeddings/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .service import ( 4 | start_embeddings_task, 5 | stop_embeddings_task, 6 | EmbeddingsService, 7 | start_project_embeddings_task, 8 | stop_project_embeddings_task, 9 | ProjectEmbeddingsService, 10 | ) 11 | from .project_retrieval import ( 12 | retrieve_project_top_k, 13 | build_project_context_for, 14 | ) 15 | from jinx.micro.embeddings.project_search_api import search_project 16 | 17 | __all__ = [ 18 | "start_embeddings_task", 19 | "stop_embeddings_task", 20 | "EmbeddingsService", 21 | "start_project_embeddings_task", 22 | "stop_project_embeddings_task", 23 | "ProjectEmbeddingsService", 24 | "retrieve_project_top_k", 25 | "build_project_context_for", 26 | "search_project", 27 | ] 28 | -------------------------------------------------------------------------------- /jinx/micro/protocol/config_types.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Literal 4 | 5 | # Mirrors codex-rs protocol config enums as string literal types. 6 | # Keep values lowercase/kebab-case to match wire formats. 7 | 8 | ReasoningEffort = Literal[ 9 | "none", 10 | "minimal", 11 | "low", 12 | "medium", 13 | "high", 14 | "xhigh", 15 | ] 16 | 17 | ReasoningSummary = Literal[ 18 | "auto", 19 | "concise", 20 | "detailed", 21 | "none", 22 | ] 23 | 24 | Verbosity = Literal["low", "medium", "high"] 25 | 26 | # Note: This is the legacy kebab-case variant used in some v1-style payloads. 27 | SandboxMode = Literal["read-only", "workspace-write", "danger-full-access"] 28 | 29 | ForcedLoginMethod = Literal["chatgpt", "api"] 30 | 31 | TrustLevel = Literal["trusted", "untrusted"] 32 | -------------------------------------------------------------------------------- /jinx/micro/exec/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | 5 | __all__ = [ 6 | "spike_exec", 7 | "spawn_pty_process", 8 | "ExecCommandSession", 9 | "SpawnedPty", 10 | ] 11 | 12 | 13 | def __getattr__(name: str) -> Any: 14 | if name == "spike_exec": 15 | from .executor import spike_exec # local import to avoid circular deps 16 | return spike_exec 17 | if name == "spawn_pty_process": 18 | from .pty import spawn_pty_process # type: ignore 19 | return spawn_pty_process 20 | if name == "ExecCommandSession": 21 | from .pty import ExecCommandSession # type: ignore 22 | return ExecCommandSession 23 | if name == "SpawnedPty": 24 | from .pty import SpawnedPty # type: ignore 25 | return SpawnedPty 26 | raise AttributeError(name) 27 | -------------------------------------------------------------------------------- /jinx/parser_service.py: -------------------------------------------------------------------------------- 1 | """Parser facade for tagged model output blocks. 2 | 3 | Thin wrapper delegating to the micro-module implementation under 4 | jinx.micro.parser.api to keep the public API stable. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from typing import List, Tuple 10 | from jinx.micro.parser.api import parse_tagged_blocks as _parse_tagged_blocks, is_code_tag as _is_code_tag 11 | 12 | 13 | def parse_tagged_blocks(out: str, code_id: str) -> List[Tuple[str, str]]: 14 | """Extract pairs of (tag, content) for the given code id. 15 | 16 | Tolerates CRLF and surrounding whitespace and captures minimal content. 17 | """ 18 | return _parse_tagged_blocks(out, code_id) 19 | 20 | 21 | def is_code_tag(tag: str) -> bool: 22 | """Return True if tag is one of the configured code tags.""" 23 | return _is_code_tag(tag) 24 | -------------------------------------------------------------------------------- /jinx/micro/llm/chain_trace.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import time 5 | from typing import Any, Dict 6 | 7 | from jinx.log_paths import PLAN_TRACE 8 | from jinx.logger.file_logger import append_line as _append 9 | from .chain_utils import truthy_env 10 | 11 | 12 | async def trace_plan(payload: Dict[str, Any]) -> None: 13 | """Append a small JSON line to the planner trace when enabled. 14 | 15 | Controlled by env JINX_CHAINED_TRACE. Writes only aggregate/meta fields 16 | to avoid leaking sensitive content. 17 | """ 18 | if not truthy_env("JINX_CHAINED_TRACE", "0"): 19 | return 20 | try: 21 | rec = dict(payload) 22 | rec["ts"] = int(time.time() * 1000) 23 | await _append(PLAN_TRACE, json.dumps(rec, ensure_ascii=False)) 24 | except Exception: 25 | # best-effort 26 | return 27 | -------------------------------------------------------------------------------- /jinx/micro/core/function_tool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | __all__ = [ 4 | "FunctionCallError", 5 | "RespondToModel", 6 | "Denied", 7 | "MissingLocalShellCallId", 8 | "Fatal", 9 | ] 10 | 11 | 12 | class FunctionCallError(Exception): 13 | """Base error for function tool invocations.""" 14 | 15 | 16 | class RespondToModel(FunctionCallError): 17 | def __init__(self, message: str): 18 | super().__init__(message) 19 | 20 | 21 | class Denied(FunctionCallError): 22 | def __init__(self, message: str): 23 | super().__init__(message) 24 | 25 | 26 | class MissingLocalShellCallId(FunctionCallError): 27 | def __init__(self): 28 | super().__init__("LocalShellCall without call_id or id") 29 | 30 | 31 | class Fatal(FunctionCallError): 32 | def __init__(self, message: str): 33 | super().__init__(f"Fatal error: {message}") 34 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/index_io.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import json 5 | import os 6 | 7 | from .paths import INDEX_DIR 8 | 9 | 10 | async def append_index(source: str, row: dict) -> None: 11 | """Append a JSONL row to the per-source index file. 12 | 13 | Best-effort semantics with a short retry loop. 14 | """ 15 | safe_source = source.replace(os.sep, "__").replace("/", "__") 16 | path = os.path.join(INDEX_DIR, f"{safe_source}.jsonl") 17 | line = json.dumps(row, ensure_ascii=False) 18 | for _ in range(3): 19 | try: 20 | os.makedirs(os.path.dirname(path), exist_ok=True) 21 | with open(path, "a", encoding="utf-8") as f: 22 | f.write(line + "\n") 23 | return 24 | except Exception: 25 | await asyncio.sleep(0.05) 26 | # Drop silently on persistent failure 27 | -------------------------------------------------------------------------------- /jinx/micro/protocol/common.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from enum import Enum 5 | from typing import List, Optional 6 | 7 | 8 | class AuthMode(str, Enum): 9 | apiKey = "apiKey" 10 | chatgpt = "chatgpt" 11 | 12 | 13 | @dataclass 14 | class GitSha: 15 | value: str 16 | 17 | @staticmethod 18 | def new(sha: str) -> "GitSha": 19 | return GitSha(sha) 20 | 21 | 22 | # Fuzzy file search types (minimal parity) 23 | @dataclass 24 | class FuzzyFileSearchParams: 25 | query: str 26 | roots: List[str] 27 | cancellation_token: Optional[str] = None 28 | 29 | 30 | @dataclass 31 | class FuzzyFileSearchResult: 32 | root: str 33 | path: str 34 | file_name: str 35 | score: int 36 | indices: Optional[List[int]] = None 37 | 38 | 39 | @dataclass 40 | class FuzzyFileSearchResponse: 41 | files: List[FuzzyFileSearchResult] 42 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_artifacts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from .project_paths import PROJECT_INDEX_DIR, PROJECT_FILES_DIR, safe_rel_path 5 | 6 | 7 | def _has_any_chunk_file(safe_rel: str) -> bool: 8 | d = os.path.join(PROJECT_FILES_DIR, safe_rel) 9 | try: 10 | if not os.path.isdir(d): 11 | return False 12 | for fn in os.listdir(d): 13 | if fn.endswith('.json'): 14 | return True 15 | return False 16 | except Exception: 17 | return False 18 | 19 | 20 | def artifacts_exist_for_rel(rel_path: str) -> bool: 21 | """Return True if both index exists and at least one chunk file exists.""" 22 | safe = safe_rel_path(rel_path) 23 | index_path = os.path.join(PROJECT_INDEX_DIR, f"{safe}.json") 24 | if not os.path.exists(index_path): 25 | return False 26 | return _has_any_chunk_file(safe) 27 | -------------------------------------------------------------------------------- /jinx/verify/z3_invariants.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Tuple 4 | 5 | 6 | def check_invariants(code: str, *, file_path: str | None = None) -> Tuple[bool, str]: 7 | """Optional Z3-based invariant checks. 8 | 9 | If z3-solver is unavailable or no invariants are configured for the file, 10 | return True. This is a scaffolding point to plug specific invariants later. 11 | """ 12 | try: 13 | import z3 # type: ignore 14 | except Exception: 15 | return True, "z3 not available" 16 | try: 17 | # Placeholder: prove a trivial satisfiable constraint to ensure Z3 works 18 | x = z3.Int('x') 19 | s = z3.Solver() 20 | s.add(x >= 0) 21 | if s.check() != z3.sat: 22 | return False, "z3 solver failed basic sat" 23 | return True, "" 24 | except Exception as e: 25 | return False, f"z3 error: {e}" 26 | -------------------------------------------------------------------------------- /jinx/micro/protocol/parse_command.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional, Literal 5 | 6 | 7 | @dataclass 8 | class ParsedCommandRead: 9 | type: Literal["read"] = "read" 10 | cmd: str = "" 11 | name: str = "" 12 | path: str = "" 13 | 14 | 15 | @dataclass 16 | class ParsedCommandListFiles: 17 | type: Literal["listFiles"] = "listFiles" 18 | cmd: str = "" 19 | path: Optional[str] = None 20 | 21 | 22 | @dataclass 23 | class ParsedCommandSearch: 24 | type: Literal["search"] = "search" 25 | cmd: str = "" 26 | query: Optional[str] = None 27 | path: Optional[str] = None 28 | 29 | 30 | @dataclass 31 | class ParsedCommandUnknown: 32 | type: Literal["unknown"] = "unknown" 33 | cmd: str = "" 34 | 35 | 36 | ParsedCommand = ( 37 | ParsedCommandRead | ParsedCommandListFiles | ParsedCommandSearch | ParsedCommandUnknown 38 | ) 39 | -------------------------------------------------------------------------------- /jinx/micro/common/internal_paths.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | 5 | _INTERNAL_PATH_PAT = re.compile(r"(^|[\\/])\.jinx([\\/]|$)", re.IGNORECASE) 6 | _LOG_PATH_PAT = re.compile(r"(^|[\\/])log([\\/]|$)", re.IGNORECASE) 7 | 8 | 9 | def is_internal_path(path_or_text: str) -> bool: 10 | """True if string contains a '.jinx' path segment.""" 11 | if not path_or_text: 12 | return False 13 | return bool(_INTERNAL_PATH_PAT.search(path_or_text)) 14 | 15 | 16 | def is_log_path(path_or_text: str) -> bool: 17 | """True if string contains a 'log' path segment (likely project log dir).""" 18 | if not path_or_text: 19 | return False 20 | return bool(_LOG_PATH_PAT.search(path_or_text)) 21 | 22 | 23 | def is_restricted_path(path_or_text: str) -> bool: 24 | """True if path refers to a restricted location ('.jinx' or 'log').""" 25 | return is_internal_path(path_or_text) or is_log_path(path_or_text) 26 | -------------------------------------------------------------------------------- /jinx/embeddings/service.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Embeddings service facade. 4 | 5 | Delegates to the micro-module implementation under 6 | ``jinx.micro.embeddings.service`` while keeping the public API stable. 7 | """ 8 | 9 | from jinx.micro.embeddings.service import ( 10 | EmbeddingsService as EmbeddingsService, 11 | start_embeddings_task as start_embeddings_task, 12 | stop_embeddings_task as stop_embeddings_task, 13 | ) 14 | from jinx.micro.embeddings.project_service import ( 15 | ProjectEmbeddingsService as ProjectEmbeddingsService, 16 | start_project_embeddings_task as start_project_embeddings_task, 17 | stop_project_embeddings_task as stop_project_embeddings_task, 18 | ) 19 | 20 | 21 | __all__ = [ 22 | "EmbeddingsService", 23 | "start_embeddings_task", 24 | "stop_embeddings_task", 25 | "ProjectEmbeddingsService", 26 | "start_project_embeddings_task", 27 | "stop_project_embeddings_task", 28 | ] 29 | -------------------------------------------------------------------------------- /jinx/prompts/reprogram_adversarial.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Template for generating a minimal adversarial test snippet for recent changes. 8 | # Use str.format with: goal 9 | return ( 10 | "You are Jinx's machine-first test shard — ruthless and fast.\n" 11 | "Produce a TINY Python snippet to sanity-check recent self-reprogramming for goal: '{goal}'.\n" 12 | "Constraints:\n" 13 | "- Deterministic; no network, no filesystem writes; standard library only.\n" 14 | "- Import only what's needed; run a minimal call path that would fail if the change is wrong.\n" 15 | "- Prefer direct function call(s) with small inputs; catch nothing — let exceptions surface.\n" 16 | "- On success, print exactly 'TEST_OK' and nothing else; otherwise raise immediately.\n" 17 | ) 18 | 19 | 20 | register_prompt("reprogram_adversarial", _load) 21 | -------------------------------------------------------------------------------- /jinx/micro/runtime/patch/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .utils import unified_diff, diff_stats, should_autocommit, syntax_check_enabled 4 | from .write_patch import patch_write 5 | from .line_patch import patch_line_range 6 | from .anchor_patch import patch_anchor_insert_after 7 | from .symbol_patch import patch_symbol_python 8 | from .symbol_body_patch import patch_symbol_body_python 9 | from .context_patch import patch_context_replace 10 | from .semantic_patch import patch_semantic_in_file 11 | from .autopatch import AutoPatchArgs, autopatch 12 | 13 | __all__ = [ 14 | "unified_diff", 15 | "diff_stats", 16 | "should_autocommit", 17 | "syntax_check_enabled", 18 | "patch_write", 19 | "patch_line_range", 20 | "patch_anchor_insert_after", 21 | "patch_symbol_python", 22 | "patch_symbol_body_python", 23 | "patch_context_replace", 24 | "patch_semantic_in_file", 25 | "AutoPatchArgs", 26 | "autopatch", 27 | ] 28 | -------------------------------------------------------------------------------- /jinx/micro/memory/facts_store.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import json 5 | from typing import Any, Dict 6 | 7 | from jinx.micro.memory.storage import memory_dir 8 | 9 | 10 | def facts_path() -> str: 11 | return os.path.join(memory_dir(), "facts.json") 12 | 13 | 14 | def load_facts() -> Dict[str, Any]: 15 | try: 16 | with open(facts_path(), "r", encoding="utf-8") as f: 17 | obj = json.load(f) 18 | if isinstance(obj, dict): 19 | return obj # type: ignore[return-value] 20 | except Exception: 21 | pass 22 | return {"paths": {}, "symbols": {}, "prefs": {}, "decisions": {}, "last_update_ts": 0} 23 | 24 | 25 | def save_facts(data: Dict[str, Any]) -> None: 26 | try: 27 | os.makedirs(memory_dir(), exist_ok=True) 28 | with open(facts_path(), "w", encoding="utf-8") as f: 29 | json.dump(data, f, ensure_ascii=False) 30 | except Exception: 31 | pass 32 | -------------------------------------------------------------------------------- /jinx/micro/protocol/approvals.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Dict, List, Optional, Literal 5 | 6 | from .file_change import FileChange 7 | from .parse_command import ( 8 | ParsedCommand, 9 | ParsedCommandRead, 10 | ParsedCommandListFiles, 11 | ParsedCommandSearch, 12 | ParsedCommandUnknown, 13 | ) 14 | from .v2 import SandboxCommandAssessment 15 | 16 | 17 | SandboxRiskLevel = Literal["low", "medium", "high"] 18 | 19 | 20 | @dataclass 21 | class ExecApprovalRequestEvent: 22 | call_id: str 23 | turn_id: str 24 | command: List[str] 25 | cwd: str 26 | reason: Optional[str] 27 | risk: Optional[SandboxCommandAssessment] 28 | parsed_cmd: List[ParsedCommand] 29 | 30 | 31 | @dataclass 32 | class ApplyPatchApprovalRequestEvent: 33 | call_id: str 34 | turn_id: str 35 | changes: Dict[str, FileChange] 36 | reason: Optional[str] = None 37 | grant_root: Optional[str] = None 38 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_io.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | from typing import Any 6 | 7 | 8 | def write_json_atomic(path: str, obj: Any) -> None: 9 | """Atomically write JSON to path using a temporary file and os.replace. 10 | 11 | Best-effort: cleans up temporary file on error. 12 | """ 13 | os.makedirs(os.path.dirname(path) or ".", exist_ok=True) 14 | tmp = path + ".tmp" 15 | # Prefer orjson if available for speed; fallback to json 16 | try: 17 | import orjson # type: ignore 18 | text = orjson.dumps(obj).decode("utf-8") 19 | except Exception: 20 | text = json.dumps(obj, ensure_ascii=False) 21 | try: 22 | with open(tmp, "w", encoding="utf-8") as w: 23 | w.write(text) 24 | os.replace(tmp, path) 25 | except Exception: 26 | try: 27 | if os.path.exists(tmp): 28 | os.remove(tmp) 29 | except Exception: 30 | pass 31 | -------------------------------------------------------------------------------- /jinx/micro/llm/chain_finalize.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.micro.llm.chain_utils import truthy_env 4 | from jinx.micro.llm.chain_persist import persist_brain 5 | from jinx.logger.file_logger import append_line as _log_append 6 | from jinx.log_paths import BLUE_WHISPERS 7 | 8 | 9 | async def finalize_context(user_text: str, plan: dict, final_ctx: str) -> None: 10 | if not final_ctx: 11 | return 12 | # Optional persist brain snapshot for embeddings ingestion 13 | try: 14 | if truthy_env("JINX_CHAINED_PERSIST_BRAIN", "1"): 15 | await persist_brain(user_text, plan, final_ctx) 16 | except Exception: 17 | pass 18 | # Optional developer echo for inspection in logs 19 | try: 20 | if truthy_env("JINX_CHAINED_DEV_ECHO", "0"): 21 | preview = final_ctx if len(final_ctx) <= 4000 else (final_ctx[:4000] + "\n...") 22 | await _log_append(BLUE_WHISPERS, f"[CHAIN]\n{preview}") 23 | except Exception: 24 | pass 25 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/import_policy.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import ast 5 | 6 | from .ast_cache import get_ast 7 | from .config import is_enabled 8 | from .policy import HEAVY_IMPORTS_TOP as _BANNED_TOP_LEVEL 9 | 10 | 11 | def check_import_policy(code: str) -> Optional[str]: 12 | if not is_enabled("import_policy", True): 13 | return None 14 | t = get_ast(code) 15 | if not t: 16 | return None 17 | for n in ast.walk(t): 18 | if isinstance(n, ast.Import): 19 | for a in n.names: 20 | top = (a.name or "").split(".")[0] 21 | if top in _BANNED_TOP_LEVEL: 22 | return f"import of heavy module '{top}' is disallowed under RT constraints" 23 | if isinstance(n, ast.ImportFrom): 24 | top = (n.module or "").split(".")[0] 25 | if top in _BANNED_TOP_LEVEL: 26 | return f"from {top} import ... is disallowed under RT constraints" 27 | return None 28 | -------------------------------------------------------------------------------- /jinx/micro/memory/pin_store.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | from typing import Any, Dict, List 6 | 7 | from jinx.micro.memory.storage import memory_dir 8 | 9 | _PIN_PATH = os.path.join(memory_dir(), "pinned.json") 10 | 11 | 12 | def load_pins() -> List[str]: 13 | try: 14 | with open(_PIN_PATH, "r", encoding="utf-8") as f: 15 | obj = json.load(f) 16 | if isinstance(obj, list): 17 | return [str(x) for x in obj] 18 | except Exception: 19 | pass 20 | return [] 21 | 22 | 23 | def save_pins(items: List[str]) -> None: 24 | try: 25 | os.makedirs(memory_dir(), exist_ok=True) 26 | with open(_PIN_PATH, "w", encoding="utf-8") as f: 27 | json.dump(items, f, ensure_ascii=False) 28 | except Exception: 29 | pass 30 | 31 | 32 | def is_pinned(line: str) -> bool: 33 | try: 34 | pins = load_pins() 35 | return (line or "").strip() in pins 36 | except Exception: 37 | return False 38 | -------------------------------------------------------------------------------- /jinx/micro/runtime/registry.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from typing import Dict, Optional 5 | 6 | 7 | class _Registry: 8 | def __init__(self) -> None: 9 | self._lock = asyncio.Lock() 10 | self._items: Dict[str, object] = {} 11 | 12 | async def put(self, pid: str, prog: object) -> None: 13 | async with self._lock: 14 | self._items[pid] = prog 15 | 16 | async def get(self, pid: str) -> Optional[object]: 17 | async with self._lock: 18 | return self._items.get(pid) 19 | 20 | async def remove(self, pid: str) -> None: 21 | async with self._lock: 22 | self._items.pop(pid, None) 23 | 24 | async def list_ids(self) -> list[str]: 25 | async with self._lock: 26 | return list(self._items.keys()) 27 | 28 | 29 | _registry: _Registry | None = None 30 | 31 | 32 | def get_registry() -> _Registry: 33 | global _registry 34 | if _registry is None: 35 | _registry = _Registry() 36 | return _registry 37 | -------------------------------------------------------------------------------- /jinx/openai_mod/primer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.config import jinx_tag 4 | from jinx.prompts import get_prompt 5 | 6 | 7 | async def build_header_and_tag(prompt_override: str | None = None) -> tuple[str, str]: 8 | """Build instruction header and return it with a code tag identifier. 9 | 10 | Returns (header_plus_prompt, code_tag_id). 11 | If ``prompt_override`` is provided, that prompt name is used instead of the global default. 12 | """ 13 | fid, _ = jinx_tag() 14 | from jinx.config import neon_stat, PROMPT_NAME 15 | 16 | chaos = neon_stat() 17 | header = ( 18 | "\npulse: 1" 19 | f"\nkey: {fid}" 20 | f"\nos: {chaos['os']}" 21 | f"\narch: {chaos['arch']}" 22 | f"\nhost: {chaos['host']}" 23 | f"\nuser: {chaos['user']}\n" 24 | ) 25 | # Fill template variables like {key} inside the prompt text 26 | active_name = (prompt_override or PROMPT_NAME) 27 | prompt = get_prompt(active_name).format(key=fid) 28 | return header + prompt, fid 29 | -------------------------------------------------------------------------------- /jinx/micro/conversation/runner.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Awaitable, Callable, Optional 4 | 5 | from jinx.parser_service import parse_tagged_blocks, is_code_tag 6 | from jinx.logging_service import bomb_log, blast_mem 7 | from jinx.micro.exec.executor import spike_exec 8 | from jinx.safety import chaos_taboo 9 | from jinx.error_service import inc_pulse 10 | 11 | 12 | async def run_blocks(raw_output: str, code_id: str, on_exec_error: Callable[[Optional[str]], Awaitable[None]]) -> bool: 13 | """Parse model output, run the first executable code block, and bump pulse. 14 | 15 | Returns True if an executable block was found and executed, else False. 16 | """ 17 | await bomb_log(f"\n{raw_output}\n") 18 | match = parse_tagged_blocks(raw_output, code_id) 19 | for tag, core in match: 20 | if is_code_tag(tag): 21 | await blast_mem(core) 22 | await spike_exec(core, chaos_taboo, on_exec_error) 23 | await inc_pulse(10) 24 | return True 25 | return False 26 | -------------------------------------------------------------------------------- /jinx/micro/git/info.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from typing import Tuple 5 | 6 | 7 | async def _run_git(*args: str, cwd: str) -> str: 8 | proc = await asyncio.create_subprocess_exec( 9 | "git", 10 | *args, 11 | cwd=cwd, 12 | stdout=asyncio.subprocess.PIPE, 13 | stderr=asyncio.subprocess.PIPE, 14 | ) 15 | out, _ = await proc.communicate() 16 | if proc.returncode != 0: 17 | return "" 18 | return out.decode(errors="ignore").strip() 19 | 20 | 21 | async def git_diff_to_remote(cwd: str) -> Tuple[str, str]: 22 | """Return (sha, diff) vs upstream if available; fall back to empty strings. 23 | 24 | - sha: current HEAD commit hash (short). 25 | - diff: unified diff vs upstream ("@{u}") if tracking branch exists, else empty. 26 | """ 27 | sha = await _run_git("rev-parse", "--short", "HEAD", cwd=cwd) 28 | # Try upstream diff first 29 | diff = await _run_git("diff", "@{u}", cwd=cwd) or await _run_git("diff", cwd=cwd) 30 | return sha, diff 31 | -------------------------------------------------------------------------------- /jinx/micro/conversation/prefilter.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | from typing import Optional 5 | 6 | from jinx.micro.conversation.turns_router import detect_turn_query as _fast_turn 7 | from jinx.micro.conversation.mem_intent import likely_memory_action as _mem_likely 8 | 9 | # Lightweight, zero-IO prefilters to avoid unnecessary LLM calls in hard RT. 10 | 11 | 12 | def likely_turn_query(text: str) -> bool: 13 | """True if fast detector can extract an index; avoids LLM when not relevant.""" 14 | try: 15 | ft = _fast_turn(text or "") 16 | return bool(ft and int(ft.get("index", 0)) > 0) 17 | except Exception: 18 | return False 19 | 20 | 21 | def likely_memory_action(text: str) -> bool: 22 | """True if query likely asks about memory retrieval or pins; language-agnostic. 23 | 24 | Thin facade over `jinx.micro.conversation.mem_intent.likely_memory_action` (char-grams + structural signals). 25 | """ 26 | return _mem_likely(text or "") 27 | 28 | 29 | __all__ = ["likely_turn_query", "likely_memory_action"] 30 | -------------------------------------------------------------------------------- /jinx/micro/rt/timing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import time 5 | from contextlib import asynccontextmanager 6 | from typing import AsyncIterator 7 | 8 | _ENABLED = None 9 | 10 | def _on() -> bool: 11 | global _ENABLED 12 | if _ENABLED is None: 13 | try: 14 | _ENABLED = (os.getenv("JINX_TIMING", "0").strip().lower() not in ("", "0", "false", "off", "no")) 15 | except Exception: 16 | _ENABLED = False 17 | return bool(_ENABLED) 18 | 19 | 20 | @asynccontextmanager 21 | async def timing_section(name: str) -> AsyncIterator[None]: 22 | if not _on(): 23 | yield 24 | return 25 | t0 = time.perf_counter() 26 | try: 27 | yield 28 | finally: 29 | dt = (time.perf_counter() - t0) * 1000.0 30 | try: 31 | from jinx.logger.file_logger import append_line as _append 32 | from jinx.log_paths import BLUE_WHISPERS 33 | await _append(BLUE_WHISPERS, f"[timing] {name} {dt:.1f}ms") 34 | except Exception: 35 | pass 36 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/context_brain.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import List, Tuple 5 | 6 | from jinx.micro.brain.concepts import activate_concepts as _brain_activate 7 | 8 | 9 | def brain_enabled(env_key: str = "EMBED_BRAIN_ENABLE") -> bool: 10 | try: 11 | return os.getenv(env_key, "1").strip().lower() not in ("", "0", "false", "off", "no") 12 | except Exception: 13 | return True 14 | 15 | 16 | def brain_topk(default_topk: int = 12, env_key: str = "EMBED_BRAIN_TOP_K") -> int: 17 | try: 18 | return max(1, int(os.getenv(env_key, str(default_topk)))) 19 | except Exception: 20 | return max(1, default_topk) 21 | 22 | 23 | async def brain_pairs_for(query: str, *, default_topk: int = 12) -> List[Tuple[str, float]]: 24 | if not brain_enabled(): 25 | return [] 26 | tk = brain_topk(default_topk) 27 | try: 28 | return await _brain_activate(query, top_k=tk) 29 | except Exception: 30 | return [] 31 | 32 | 33 | __all__ = ["brain_enabled", "brain_topk", "brain_pairs_for"] 34 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_identifiers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | from typing import List 5 | from jinx.micro.text.structural import is_camel_case as _is_camel_case 6 | 7 | # Very light identifier extractor, language-agnostic 8 | # Picks tokens likely to be identifiers (underscored, camelCase, dotted) with length >= 4 9 | 10 | _ident_re = re.compile(r"(?u)[\w\.]+") 11 | 12 | 13 | def extract_identifiers(text: str, max_items: int = 50) -> List[str]: 14 | if not text: 15 | return [] 16 | seen: set[str] = set() 17 | out: List[str] = [] 18 | for m in _ident_re.finditer(text): 19 | t = m.group(0) 20 | if len(t) < 4: 21 | continue 22 | if t.isdigit(): 23 | continue 24 | # Heuristics: underscore or dot or camelCase 25 | if ("_" in t) or ("." in t) or _is_camel_case(t): 26 | tl = t.lower() 27 | if tl not in seen: 28 | seen.add(tl) 29 | out.append(t) 30 | if len(out) >= max_items: 31 | break 32 | return out 33 | -------------------------------------------------------------------------------- /jinx/micro/log/logging.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.log_paths import INK_SMEARED_DIARY, BLUE_WHISPERS 4 | from jinx.micro.transcript import read_transcript, append_and_trim 5 | from jinx.logger import append_line 6 | from jinx.state import shard_lock 7 | 8 | 9 | async def glitch_pulse() -> str: 10 | """Return the current conversation transcript contents. 11 | 12 | Serialized by a shared async lock to avoid interleaved I/O. 13 | """ 14 | async with shard_lock: 15 | return await read_transcript(INK_SMEARED_DIARY) 16 | 17 | 18 | async def blast_mem(x: str, n: int = 500) -> None: 19 | """Append a line to the transcript, trimming to the last ``n`` lines.""" 20 | async with shard_lock: 21 | await append_and_trim(INK_SMEARED_DIARY, x, keep_lines=n) 22 | 23 | 24 | async def bomb_log(t: str, bin: str = BLUE_WHISPERS) -> None: 25 | """Append a line to a log file (best-effort).""" 26 | async with shard_lock: 27 | await append_line(bin, t or "") 28 | 29 | 30 | __all__ = [ 31 | "glitch_pulse", 32 | "blast_mem", 33 | "bomb_log", 34 | ] 35 | -------------------------------------------------------------------------------- /jinx/micro/conversation/cont/query.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Dict, List, Optional 4 | 5 | from .util import _is_short_reply 6 | from .anchors import _last_question_from_agent, _last_user_query 7 | 8 | 9 | def augment_query_for_retrieval(x: str, synth: str, anchors: Optional[Dict[str, List[str]]] = None) -> str: 10 | """If current input is a short clarification, blend it with last user question. 11 | 12 | Returns the effective query to feed into retrieval components. 13 | """ 14 | t = (x or "").strip() 15 | if not _is_short_reply(t): 16 | return t 17 | last_q = _last_question_from_agent(synth) 18 | last_u = _last_user_query(synth) 19 | bonus: List[str] = [] 20 | if anchors: 21 | # include top symbol and path hints to stabilize retrieval 22 | sym = (anchors.get("symbols") or [])[:2] 23 | pth = (anchors.get("paths") or [])[:1] 24 | bonus = list(sym) + list(pth) 25 | if last_q or last_u or bonus: 26 | combo = " ".join([p for p in [last_u, t] + bonus if p]) 27 | return combo[:1200] 28 | return t 29 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/comment_only.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import io 5 | import tokenize 6 | from .config import is_enabled 7 | 8 | 9 | def check_comment_only(code: str) -> Optional[str]: 10 | """Flag blocks that contain only comments/blank lines. 11 | 12 | Uses tokenization and ignores ENCODING, NL/NEWLINE, INDENT/DEDENT, and COMMENT 13 | tokens. If nothing meaningful remains, it's trivial and should trigger recovery. 14 | """ 15 | if not is_enabled("comment_only", True): 16 | return None 17 | src = code or "" 18 | try: 19 | g = tokenize.generate_tokens(io.StringIO(src).readline) 20 | for tok in g: 21 | if tok.type in (tokenize.ENCODING, tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT, tokenize.COMMENT): 22 | continue 23 | # Any other token means there is real code 24 | return None 25 | return "comment-only code block" 26 | except Exception: 27 | # On tokenizer failure, do not flag here (other validators handle syntax) 28 | return None 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 machinegpt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/routers/emb.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import APIRouter, HTTPException 4 | from ..schemas.emb import Emb 5 | from ..services.emb_service import EmbService 6 | 7 | router = APIRouter() 8 | svc = EmbService() 9 | 10 | @router.get('/') 11 | async def list_emb() -> list[Emb]: 12 | return await svc.list() 13 | 14 | @router.get('/{id}') 15 | async def get_emb(id: int) -> Emb: 16 | obj = await svc.get(id) 17 | if not obj: 18 | raise HTTPException(status_code=404, detail='Emb not found') 19 | return obj 20 | 21 | @router.post('/') 22 | async def create_emb(payload: Emb) -> Emb: 23 | return await svc.create(payload) 24 | 25 | @router.put('/{id}') 26 | async def update_emb(id: int, payload: Emb) -> Emb: 27 | obj = await svc.update(id, payload) 28 | if not obj: 29 | raise HTTPException(status_code=404, detail='Emb not found') 30 | return obj 31 | 32 | @router.delete('/{id}') 33 | async def delete_emb(id: int) -> dict: 34 | ok = await svc.delete(id) 35 | if not ok: 36 | raise HTTPException(status_code=404, detail='Emb not found') 37 | return {'ok': True} 38 | -------------------------------------------------------------------------------- /api/routers/log.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import APIRouter, HTTPException 4 | from ..schemas.log import Log 5 | from ..services.log_service import LogService 6 | 7 | router = APIRouter() 8 | svc = LogService() 9 | 10 | @router.get('/') 11 | async def list_log() -> list[Log]: 12 | return await svc.list() 13 | 14 | @router.get('/{id}') 15 | async def get_log(id: int) -> Log: 16 | obj = await svc.get(id) 17 | if not obj: 18 | raise HTTPException(status_code=404, detail='Log not found') 19 | return obj 20 | 21 | @router.post('/') 22 | async def create_log(payload: Log) -> Log: 23 | return await svc.create(payload) 24 | 25 | @router.put('/{id}') 26 | async def update_log(id: int, payload: Log) -> Log: 27 | obj = await svc.update(id, payload) 28 | if not obj: 29 | raise HTTPException(status_code=404, detail='Log not found') 30 | return obj 31 | 32 | @router.delete('/{id}') 33 | async def delete_log(id: int) -> dict: 34 | ok = await svc.delete(id) 35 | if not ok: 36 | raise HTTPException(status_code=404, detail='Log not found') 37 | return {'ok': True} 38 | -------------------------------------------------------------------------------- /jinx/micro/runtime/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .write_handler import handle_write 4 | from .line_handler import handle_line_patch 5 | from .symbol_handler import handle_symbol_patch 6 | from .anchor_handler import handle_anchor_patch 7 | from .regex_handler import handle_regex_patch 8 | from .search_replace_handler import handle_find_replace 9 | from .auto_handler import handle_auto_patch 10 | from .batch_handler import handle_batch_patch 11 | from .dump_handler import ( 12 | handle_dump_symbol, 13 | handle_dump_by_query, 14 | handle_dump_by_query_global, 15 | ) 16 | from .refactor_move import handle_refactor_move_symbol 17 | from .refactor_split import handle_refactor_split_file 18 | 19 | __all__ = [ 20 | "handle_write", 21 | "handle_line_patch", 22 | "handle_symbol_patch", 23 | "handle_anchor_patch", 24 | "handle_regex_patch", 25 | "handle_find_replace", 26 | "handle_auto_patch", 27 | "handle_batch_patch", 28 | "handle_dump_symbol", 29 | "handle_dump_by_query", 30 | "handle_dump_by_query_global", 31 | "handle_refactor_move_symbol", 32 | "handle_refactor_split_file", 33 | ] 34 | -------------------------------------------------------------------------------- /jinx/micro/conversation/error_payload.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | 5 | from jinx.micro.parser.api import parse_tagged_blocks 6 | 7 | 8 | def attach_error_code(err_msg: str, out: Optional[str], code_id: str, *, code_body: Optional[str] = None) -> str: 9 | """Build an error payload that includes the executed code under . 10 | 11 | - If code_body is provided, use it; otherwise extract from 'out'. 12 | - Returns the payload string to pass to corrupt_report(). 13 | """ 14 | payload = (err_msg or "").strip() 15 | body = (code_body or "").strip() 16 | if not body and (out or "").strip(): 17 | try: 18 | pairs = parse_tagged_blocks(out or "", code_id) 19 | except Exception: 20 | pairs = [] 21 | tag_name = f"python_{code_id}" 22 | for tag, core in pairs: 23 | if tag == tag_name: 24 | body = (core or "").strip() 25 | break 26 | if body: 27 | payload = f"{payload}\n\n\n{body}\n" 28 | return payload 29 | 30 | 31 | __all__ = ["attach_error_code"] 32 | -------------------------------------------------------------------------------- /jinx/utils.py: -------------------------------------------------------------------------------- 1 | """Utility helpers. 2 | 3 | This module exposes a small set of utilities used across async flows. The 4 | primary export is ``chaos_patch``, an async context manager that patches 5 | ``stdout`` to cooperate with ``prompt_toolkit`` rendering. This avoids output 6 | interleaving artifacts when concurrently reading user input and printing logs. 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | from contextlib import asynccontextmanager 12 | from typing import AsyncIterator 13 | import importlib 14 | from jinx.bootstrap import ensure_optional 15 | 16 | # Lazy install of prompt_toolkit, then import patch_stdout from correct submodule 17 | ensure_optional(["prompt_toolkit"]) # installs if missing 18 | patch_stdout = importlib.import_module("prompt_toolkit.patch_stdout").patch_stdout # type: ignore[assignment] 19 | 20 | 21 | @asynccontextmanager 22 | async def chaos_patch() -> AsyncIterator[None]: 23 | """Patch ``stdout`` for clean ``prompt_toolkit`` output in async contexts. 24 | 25 | Yields 26 | ------ 27 | None 28 | Control back to the caller with ``stdout`` safely patched. 29 | """ 30 | with patch_stdout(): 31 | yield 32 | -------------------------------------------------------------------------------- /jinx/micro/text/utf8_bytes.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | __all__ = [ 4 | "take_bytes_at_char_boundary", 5 | "take_last_bytes_at_char_boundary", 6 | ] 7 | 8 | 9 | def take_bytes_at_char_boundary(s: str, max_bytes: int) -> str: 10 | """Return prefix of ``s`` fitting into ``max_bytes`` UTF-8 bytes. 11 | 12 | The cut is made only at Unicode code point boundaries (no partial bytes). 13 | """ 14 | if max_bytes <= 0: 15 | return "" 16 | if not s: 17 | return s 18 | 19 | b = s.encode("utf-8") 20 | if len(b) <= max_bytes: 21 | return s 22 | return b[:max_bytes].decode("utf-8", errors="ignore") 23 | 24 | 25 | def take_last_bytes_at_char_boundary(s: str, max_bytes: int) -> str: 26 | """Return suffix of ``s`` fitting into ``max_bytes`` UTF-8 bytes. 27 | 28 | The cut is made only at Unicode code point boundaries (no partial bytes). 29 | """ 30 | if max_bytes <= 0: 31 | return "" 32 | if not s: 33 | return s 34 | 35 | b = s.encode("utf-8") 36 | if len(b) <= max_bytes: 37 | return s 38 | 39 | tail = b[-max_bytes:] 40 | return tail.decode("utf-8", errors="ignore") 41 | -------------------------------------------------------------------------------- /jinx/prompts/state_compiler.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Template for compiling board-state cognition. 8 | # Use str.format with: board_json, last_query, memory_snippets, evergreen 9 | return ( 10 | "You are Jinx's machine-first state compiler — RT-aware, concise, deterministic. " 11 | "Given compact runtime board, last_query, and memory, produce STRICT JSON ONLY with keys: " 12 | "goals (array of short strings), plan (array of 3-6 short steps), capability_gaps (array), " 13 | "next_action (string), mem_pointers (array of short pointers).\n" 14 | "Rules: minimal, actionable, avoid redundancy, ASCII only, no code fences; do NOT use '<' or '>' in values. Keep language consistent with input.\n" 15 | "Discipline: prefer small, stable steps; avoid speculative items; reflect RT budgets implicitly.\n\n" 16 | "BOARD_JSON:\n{board_json}\n\n" 17 | "LAST_QUERY:\n{last_query}\n\n" 18 | "MEMORY_SNIPPETS:\n{memory_snippets}\n\n" 19 | "EVERGREEN:\n{evergreen}\n" 20 | ) 21 | 22 | 23 | register_prompt("state_compiler", _load) 24 | -------------------------------------------------------------------------------- /jinx/micro/text/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Re-export structural helpers for downstream modules 4 | from .structural import ( 5 | NAME, 6 | NAME_RE, 7 | is_camel_case, 8 | is_pathlike, 9 | match_paren, 10 | match_bracket, 11 | split_top_args, 12 | ) 13 | 14 | # UTF-8 byte-boundary helpers 15 | from .utf8_bytes import ( 16 | take_bytes_at_char_boundary, 17 | take_last_bytes_at_char_boundary, 18 | ) 19 | 20 | # ANSI escape handling 21 | from .ansi_escape import ( 22 | expand_tabs, 23 | ansi_escape, 24 | ansi_escape_line, 25 | ) 26 | 27 | # Tokenizer utilities (tiktoken-backed) 28 | from .tokenizer import ( 29 | EncodingKind, 30 | Tokenizer, 31 | warm_model_cache, 32 | ) 33 | 34 | # Truncation utilities 35 | from .truncate import ( 36 | TruncationPolicy, 37 | approx_token_count, 38 | formatted_truncate_text, 39 | truncate_text, 40 | truncate_with_token_budget, 41 | ) 42 | 43 | # Fuzzy matching utilities 44 | from .fuzzy import ( 45 | fuzzy_match, 46 | fuzzy_indices, 47 | ) 48 | 49 | # Review formatting 50 | from .review_format import ( 51 | format_review_findings_block, 52 | ) 53 | -------------------------------------------------------------------------------- /jinx/micro/conversation/debug.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import Optional 5 | 6 | from jinx.micro.llm.prompt_filters import sanitize_prompt_for_external_api as _sanitize 7 | 8 | async def log_debug(feature: str, line: str) -> None: 9 | """Append a short debug line to Blue Whispers if debugging is enabled. 10 | 11 | Controlled by env: 12 | - JINX_CONV_DEBUG=1 enables logging 13 | - JINX_CONV_DEBUG_FEATURES can filter features (comma-separated) 14 | """ 15 | try: 16 | on = str(os.getenv("JINX_CONV_DEBUG", "0")).lower() in ("1","true","on","yes") 17 | if not on: 18 | return 19 | allow = str(os.getenv("JINX_CONV_DEBUG_FEATURES", "")).strip().lower() 20 | if allow: 21 | feats = {p.strip() for p in allow.split(',') if p.strip()} 22 | if feature not in feats: 23 | return 24 | from jinx.logger.file_logger import append_line as _append 25 | from jinx.log_paths import BLUE_WHISPERS 26 | await _append(BLUE_WHISPERS, f"[conv:{feature}] {_sanitize(line)[:400]}") 27 | except Exception: 28 | return 29 | 30 | __all__ = ["log_debug"] 31 | -------------------------------------------------------------------------------- /jinx/micro/runtime/patch/write_patch.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | import asyncio 5 | from typing import Tuple 6 | import os 7 | 8 | from jinx.async_utils.fs import read_text_raw, write_text 9 | from .utils import unified_diff, syntax_check_enabled 10 | 11 | 12 | async def patch_write(path: str, text: str, *, preview: bool = False) -> Tuple[bool, str]: 13 | """Create/overwrite file contents atomically (best-effort). If preview, returns diff only.""" 14 | cur = await read_text_raw(path) 15 | new = text or "" 16 | if preview: 17 | return True, unified_diff(cur or "", new, path=path) 18 | # Optional syntax check for Python files 19 | if str(path).endswith(".py") and syntax_check_enabled(): 20 | try: 21 | await asyncio.to_thread(ast.parse, new or "") 22 | except Exception as e: 23 | return False, f"syntax error: {e}" 24 | # Ensure directory exists 25 | try: 26 | d = os.path.dirname(path) 27 | if d: 28 | os.makedirs(d, exist_ok=True) 29 | except Exception: 30 | pass 31 | await write_text(path, new) 32 | return True, unified_diff(cur or "", new, path=path) 33 | -------------------------------------------------------------------------------- /jinx/micro/core/powershell.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | from typing import List, Optional, Tuple 5 | 6 | __all__ = ["extract_powershell_command"] 7 | 8 | _ALLOWED_FLAGS = {"-nologo", "-noprofile", "-command", "-c"} 9 | 10 | 11 | def _is_powershell(exe: str) -> bool: 12 | p = exe.strip().lower() 13 | name = Path(p).name 14 | return name in {"powershell", "powershell.exe", "pwsh"} 15 | 16 | 17 | def extract_powershell_command(command: List[str]) -> Optional[Tuple[str, str]]: 18 | """Extract (shell, script) when the first arg is PowerShell and -Command/-c present. 19 | 20 | Mirrors Codex logic: reject unknown flags before the command body. 21 | """ 22 | if len(command) < 3: 23 | return None 24 | shell = command[0] 25 | if not _is_powershell(shell): 26 | return None 27 | 28 | i = 1 29 | while i + 1 < len(command): 30 | flag = command[i] 31 | flag_l = flag.lower() 32 | if flag_l not in _ALLOWED_FLAGS: 33 | return None 34 | if flag_l in {"-command", "-c"}: 35 | script = command[i + 1] 36 | return shell, script 37 | i += 1 38 | return None 39 | -------------------------------------------------------------------------------- /api/repositories/emb_repo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from ..schemas.emb import Emb 5 | 6 | class EmbRepository: 7 | def __init__(self): 8 | self._data: dict[int, Emb] = {} 9 | self._next_id = 1 10 | self._lock = asyncio.Lock() 11 | 12 | async def list(self) -> list[Emb]: 13 | async with self._lock: 14 | return list(self._data.values()) 15 | 16 | async def get(self, id: int) -> Emb | None: 17 | async with self._lock: 18 | return self._data.get(id) 19 | 20 | async def create(self, obj: Emb) -> Emb: 21 | async with self._lock: 22 | oid = self._next_id; self._next_id += 1 23 | obj.id = oid 24 | self._data[oid] = obj 25 | return obj 26 | 27 | async def update(self, id: int, obj: Emb) -> Emb | None: 28 | async with self._lock: 29 | if id not in self._data: 30 | return None 31 | obj.id = id 32 | self._data[id] = obj 33 | return obj 34 | 35 | async def delete(self, id: int) -> bool: 36 | async with self._lock: 37 | return self._data.pop(id, None) is not None 38 | -------------------------------------------------------------------------------- /api/repositories/log_repo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from ..schemas.log import Log 5 | 6 | class LogRepository: 7 | def __init__(self): 8 | self._data: dict[int, Log] = {} 9 | self._next_id = 1 10 | self._lock = asyncio.Lock() 11 | 12 | async def list(self) -> list[Log]: 13 | async with self._lock: 14 | return list(self._data.values()) 15 | 16 | async def get(self, id: int) -> Log | None: 17 | async with self._lock: 18 | return self._data.get(id) 19 | 20 | async def create(self, obj: Log) -> Log: 21 | async with self._lock: 22 | oid = self._next_id; self._next_id += 1 23 | obj.id = oid 24 | self._data[oid] = obj 25 | return obj 26 | 27 | async def update(self, id: int, obj: Log) -> Log | None: 28 | async with self._lock: 29 | if id not in self._data: 30 | return None 31 | obj.id = id 32 | self._data[id] = obj 33 | return obj 34 | 35 | async def delete(self, id: int) -> bool: 36 | async with self._lock: 37 | return self._data.pop(id, None) is not None 38 | -------------------------------------------------------------------------------- /jinx/micro/common/json_to_toml.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | 5 | __all__ = ["json_to_toml"] 6 | 7 | 8 | def json_to_toml(v: Any) -> Any: 9 | """Convert JSON-like Python value to TOML-serializable value. 10 | 11 | Mapping mirrors the Rust implementation semantics: 12 | - None -> "" (empty string) 13 | - bool -> bool 14 | - int -> int 15 | - float -> float 16 | - str -> str 17 | - list/tuple -> list (recursively converted) 18 | - dict -> dict[str, Any] (recursively converted) 19 | - other -> str(value) 20 | """ 21 | if v is None: 22 | return "" 23 | if isinstance(v, bool): 24 | return v 25 | if isinstance(v, int): 26 | return v 27 | if isinstance(v, float): 28 | return v 29 | if isinstance(v, str): 30 | return v 31 | if isinstance(v, (list, tuple)): 32 | return [json_to_toml(x) for x in v] 33 | if isinstance(v, dict): 34 | # Ensure keys are strings for TOML 35 | return {str(k): json_to_toml(val) for k, val in v.items()} 36 | # Fallback: stringify unknown types 37 | try: 38 | return str(v) 39 | except Exception: 40 | return "" 41 | -------------------------------------------------------------------------------- /jinx/micro/conversation/cont/topic.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from jinx.micro.embeddings.retrieval import retrieve_top_k 4 | 5 | 6 | async def detect_topic_shift(query: str, *, k: int = 6, max_time_ms: int = 120) -> bool: 7 | """Return True if the current query appears to shift topic (no 'state' hits among top-k). 8 | 9 | Tuned for speed: small k, tight time budget. Env toggle JINX_TOPIC_SHIFT_CHECK governs usage. 10 | """ 11 | try: 12 | q = (query or "").strip() 13 | if not q: 14 | return False 15 | hits = await retrieve_top_k(q, k=k, max_time_ms=max_time_ms) 16 | if not hits: 17 | return False 18 | # consider a shift if zero hits originate from 'state' 19 | has_state = False 20 | for score, src, obj in hits: 21 | meta = obj.get("meta", {}) if isinstance(obj, dict) else {} 22 | src_meta = (meta.get("source") or "").strip().lower() 23 | src_i = (src or "").strip().lower() 24 | if src_meta == "state" or src_i == "state": 25 | has_state = True 26 | break 27 | return not has_state 28 | except Exception: 29 | return False 30 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/blocking_io.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import ast 5 | 6 | from .ast_cache import get_ast 7 | from .config import is_enabled 8 | 9 | 10 | def check_blocking_io(code: str) -> Optional[str]: 11 | """Disallow interactive/blocking input in RT context. 12 | 13 | Flags builtins.input(...) and sys.stdin.readline(). 14 | """ 15 | if not is_enabled("blocking_io", True): 16 | return None 17 | t = get_ast(code) 18 | if not t: 19 | return None 20 | for n in ast.walk(t): 21 | if isinstance(n, ast.Call): 22 | fn = getattr(n, "func", None) 23 | if isinstance(fn, ast.Name) and fn.id == "input": 24 | return "blocking input() is disallowed under RT constraints" 25 | if isinstance(fn, ast.Attribute) and fn.attr == "readline": 26 | v = getattr(fn, "value", None) 27 | if isinstance(v, ast.Attribute) and v.attr == "stdin": 28 | u = getattr(v, "value", None) 29 | if isinstance(u, ast.Name) and u.id == "sys": 30 | return "blocking sys.stdin.readline() is disallowed under RT constraints" 31 | return None 32 | -------------------------------------------------------------------------------- /jinx/micro/common/elapsed.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import time 4 | from datetime import timedelta 5 | from typing import Union 6 | 7 | __all__ = [ 8 | "format_elapsed", 9 | "format_duration", 10 | ] 11 | 12 | 13 | def _format_elapsed_millis(ms: int) -> str: 14 | if ms < 1000: 15 | return f"{ms}ms" 16 | if ms < 60_000: 17 | return f"{ms / 1000.0:.2f}s" 18 | minutes = ms // 60_000 19 | seconds = (ms % 60_000) // 1000 20 | return f"{minutes}m {seconds:02d}s" 21 | 22 | 23 | def format_duration(duration: Union[float, timedelta]) -> str: 24 | """Render duration as compact human string. 25 | 26 | - <1s => "{ms}ms" 27 | - <60s => "{sec:.2f}s" 28 | - >=60s => "{min}m {sec:02d}s" 29 | Accepts seconds (float) or timedelta. 30 | """ 31 | if isinstance(duration, timedelta): 32 | ms = int(duration.total_seconds() * 1000) 33 | else: 34 | ms = int(float(duration) * 1000) 35 | return _format_elapsed_millis(ms) 36 | 37 | 38 | def format_elapsed(start_time: float) -> str: 39 | """Format time elapsed since ``start_time`` (perf_counter origin)""" 40 | elapsed_s = time.perf_counter() - float(start_time) 41 | return format_duration(elapsed_s) 42 | -------------------------------------------------------------------------------- /jinx/micro/common/cli_args.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | __all__ = [ 4 | "parse_approval_mode", 5 | "parse_sandbox_mode", 6 | ] 7 | 8 | 9 | _APPROVAL_MAP = { 10 | "untrusted": "unless_trusted", 11 | "on-failure": "on_failure", 12 | "onrequest": "on_request", 13 | "on-request": "on_request", 14 | "never": "never", 15 | } 16 | 17 | 18 | def parse_approval_mode(value: str) -> str: 19 | """Map CLI approval mode (kebab-case) to canonical policy string. 20 | 21 | Returns one of: 'unless_trusted' | 'on_failure' | 'on_request' | 'never' 22 | """ 23 | key = (value or "").strip().lower() 24 | if key in _APPROVAL_MAP: 25 | return _APPROVAL_MAP[key] 26 | # Default: conservative ask policy 27 | return "unless_trusted" 28 | 29 | 30 | _SANDBOX_MAP = { 31 | "read-only": "read-only", 32 | "workspace-write": "workspace-write", 33 | "danger-full-access": "danger-full-access", 34 | } 35 | 36 | 37 | def parse_sandbox_mode(value: str) -> str: 38 | """Map CLI sandbox mode to canonical string. 39 | 40 | Returns one of: 'read-only' | 'workspace-write' | 'danger-full-access' 41 | """ 42 | key = (value or "").strip().lower() 43 | return _SANDBOX_MAP.get(key, "read-only") 44 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_iter.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import Iterable, Iterator, Tuple 5 | 6 | from .project_util import file_should_include 7 | 8 | 9 | def iter_candidate_files( 10 | root: str, 11 | *, 12 | include_exts: list[str], 13 | exclude_dirs: list[str], 14 | max_file_bytes: int, 15 | ) -> Iterator[Tuple[str, str]]: 16 | """Yield (abs_path, rel_path) for files under root that pass filters. 17 | 18 | - Prunes excluded directories in-place for efficiency. 19 | - Applies extension and size filters. 20 | """ 21 | root = os.path.abspath(root) 22 | for dirpath, dirnames, filenames in os.walk(root): 23 | dirnames[:] = [d for d in dirnames if d not in exclude_dirs] 24 | for fn in filenames: 25 | abs_p = os.path.join(dirpath, fn) 26 | if not file_should_include(abs_p, include_exts=include_exts, exclude_dirs=exclude_dirs): 27 | continue 28 | try: 29 | if os.path.getsize(abs_p) > max_file_bytes: 30 | continue 31 | except Exception: 32 | continue 33 | rel_p = os.path.relpath(abs_p, start=root) 34 | yield abs_p, rel_p 35 | -------------------------------------------------------------------------------- /jinx/async_utils/rt.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from contextlib import asynccontextmanager 5 | from typing import AsyncIterator 6 | 7 | 8 | class RTBudget: 9 | """ 10 | Cooperative real-time budget helper. 11 | 12 | Maintains a soft time budget and yields control to the event loop when the 13 | budget elapses to reduce scheduling latency for other tasks. 14 | """ 15 | 16 | def __init__(self, budget_ms: int) -> None: 17 | self._budget_s = max(0.0, budget_ms) / 1000.0 18 | self._loop = asyncio.get_running_loop() 19 | self._next = self._loop.time() + self._budget_s 20 | 21 | async def tick(self) -> None: 22 | now = self._loop.time() 23 | if now >= self._next: 24 | await asyncio.sleep(0) 25 | self._next = self._loop.time() + self._budget_s 26 | 27 | 28 | @asynccontextmanager 29 | async def rt_section(budget_ms: int = 4) -> AsyncIterator[RTBudget]: 30 | """ 31 | Async context manager yielding an RTBudget instance. 32 | 33 | Usage: 34 | async with rt_section(4) as rt: 35 | for item in items: 36 | ... 37 | await rt.tick() 38 | """ 39 | rt = RTBudget(budget_ms) 40 | yield rt 41 | -------------------------------------------------------------------------------- /jinx/prompts/skill_acquire_spec.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Template for skill acquisition JSON {path, code}. 8 | # Use str.format with: query, suggested_path 9 | return ( 10 | "You are Jinx's machine-first skill engineer — ruthless minimalism, RT-aware.\n" 11 | "Task: generate a minimal Python skill module that directly helps answer the user query.\n" 12 | "Return STRICT JSON ONLY with keys: path (string), code (string). No code fences. ASCII only.\n" 13 | "Constraints:\n" 14 | "- Micro-modular layout; RT-friendly; avoid blocking IO at import time.\n" 15 | "- Expose 'async def handle(query: str) -> str' as the entrypoint.\n" 16 | "- No external dependencies beyond the Python standard library.\n" 17 | "- Deterministic, concise, and safe; keep code small and focused.\n" 18 | "- ASCII only; no triple quotes anywhere; do NOT use try/except.\n" 19 | "- No network or filesystem writes at import or top-level; avoid global side effects.\n" 20 | "User query: {query}\n" 21 | "Suggested path (under jinx/skills): {suggested_path}\n" 22 | ) 23 | 24 | 25 | register_prompt("skill_acquire_spec", _load) 26 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/triple_quotes.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import io 5 | import tokenize 6 | from .config import is_enabled 7 | 8 | 9 | def check_triple_quotes(code: str) -> Optional[str]: 10 | """Return a violation message if triple-quoted strings are present. 11 | 12 | Uses tokenization to avoid false positives and reliably catches docstrings 13 | and any triple-quoted literals anywhere in the code. 14 | """ 15 | if not is_enabled("triple_quotes", True): 16 | return None 17 | src = code or "" 18 | try: 19 | g = tokenize.generate_tokens(io.StringIO(src).readline) 20 | for tok in g: 21 | if tok.type == tokenize.STRING: 22 | s = tok.string 23 | # skip literal prefixes (r, u, f, b, combinations) 24 | i = 0 25 | while i < len(s) and s[i] in "rRuUfFbB": 26 | i += 1 27 | if s[i : i + 3] in ("'''", '"""'): 28 | return "Triple quotes are not allowed by prompt" 29 | except Exception: 30 | # Fallback to simple textual check 31 | if "'''" in src or '"""' in src: 32 | return "Triple quotes are not allowed by prompt" 33 | return None 34 | -------------------------------------------------------------------------------- /jinx/micro/runtime/bus.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from collections import defaultdict 5 | from typing import Any, Awaitable, Callable, Dict, List 6 | 7 | Handler = Callable[[str, Any], Awaitable[None]] 8 | 9 | 10 | class EventBus: 11 | def __init__(self) -> None: 12 | self._subs: Dict[str, List[Handler]] = defaultdict(list) 13 | self._lock = asyncio.Lock() 14 | 15 | async def subscribe(self, topic: str, handler: Handler) -> None: 16 | async with self._lock: 17 | self._subs[topic].append(handler) 18 | 19 | async def publish(self, topic: str, payload: Any) -> None: 20 | # fan-out asynchronously; do not await all to finish in-line 21 | handlers: List[Handler] 22 | async with self._lock: 23 | handlers = list(self._subs.get(topic, ())) 24 | for h in handlers: 25 | try: 26 | # schedule; let handlers manage their own budgets 27 | asyncio.create_task(h(topic, payload)) 28 | except Exception: 29 | # swallow — bus must never raise 30 | pass 31 | 32 | 33 | _bus: EventBus | None = None 34 | 35 | 36 | def get_bus() -> EventBus: 37 | global _bus 38 | if _bus is None: 39 | _bus = EventBus() 40 | return _bus 41 | -------------------------------------------------------------------------------- /jinx/micro/common/oss.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import Any, Optional 5 | 6 | __all__ = [ 7 | "get_default_model_for_oss_provider", 8 | "ensure_oss_provider_ready", 9 | ] 10 | 11 | 12 | def _norm(pid: str | None) -> str: 13 | if not pid: 14 | return "" 15 | return str(pid).strip().lower() 16 | 17 | 18 | def get_default_model_for_oss_provider(provider_id: str) -> Optional[str]: 19 | """Return default model name for known OSS providers using env overrides. 20 | 21 | Environment overrides: 22 | - JINX_OSS_LMSTUDIO_DEFAULT_MODEL 23 | - JINX_OSS_OLLAMA_DEFAULT_MODEL 24 | Unknown providers -> None. 25 | """ 26 | pid = _norm(provider_id) 27 | if pid in ("lmstudio", "lmstudio-oss"): 28 | return os.getenv("JINX_OSS_LMSTUDIO_DEFAULT_MODEL") 29 | if pid in ("ollama", "ollama-oss"): 30 | return os.getenv("JINX_OSS_OLLAMA_DEFAULT_MODEL") 31 | return None 32 | 33 | 34 | async def ensure_oss_provider_ready(provider_id: str, config: Any | None = None) -> None: 35 | """Best-effort readiness hook. 36 | 37 | For Jinx, readiness is delegated to runtime configuration; this is a no-op 38 | that completes successfully to avoid blocking startup. 39 | """ 40 | _ = (_norm(provider_id), config) 41 | return None 42 | -------------------------------------------------------------------------------- /jinx/micro/llm/__init__.py: -------------------------------------------------------------------------------- 1 | from .chains import build_planner_context 2 | from .chain_orchestrator import ( 3 | execute_planning_chain_smart, 4 | execute_context_chain_smart, 5 | get_chain_orchestrator, 6 | ) 7 | from .chain_blocks import ( 8 | build_chain_plan_block, 9 | build_chain_context_block, 10 | build_chain_intelligence_block, 11 | build_chain_meta_block, 12 | build_chain_outcome_block, 13 | compact_chain_blocks, 14 | ) 15 | from .chain_parser import ( 16 | parse_all_chain_blocks, 17 | build_chain_summary, 18 | ) 19 | from .smart_cache import ( 20 | get_smart_cache, 21 | cache_get, 22 | cache_put, 23 | ) 24 | 25 | from .openai_caller import call_openai 26 | from .openai_model_info import get_model_info 27 | 28 | __all__ = [ 29 | "call_openai", 30 | "get_model_info", 31 | "build_planner_context", 32 | "execute_planning_chain_smart", 33 | "execute_context_chain_smart", 34 | "get_chain_orchestrator", 35 | "build_chain_plan_block", 36 | "build_chain_context_block", 37 | "build_chain_intelligence_block", 38 | "build_chain_meta_block", 39 | "build_chain_outcome_block", 40 | "compact_chain_blocks", 41 | "parse_all_chain_blocks", 42 | "build_chain_summary", 43 | "get_smart_cache", 44 | "cache_get", 45 | "cache_put", 46 | ] 47 | -------------------------------------------------------------------------------- /jinx/prompts/chaos_bloom.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Embedded prompt content for "chaos_bloom" 8 | return ( 9 | "You are Jinx — machine-first systems architect. RT-aware. Micro-modular.\n\n" 10 | "Directive:\n" 11 | "- Elevate the given code into a production-grade, modular architecture with minimal, safe transformations.\n" 12 | "- Prefer micro-modular components over sweeping monolith splits; evolve incrementally.\n" 13 | "- Keep naming explicit and stable; preserve public APIs unless correctness requires change.\n\n" 14 | "Constraints:\n" 15 | "- ASCII-only; no code fences; no external docs.\n" 16 | "- Code is the documentation; include minimal docstrings where necessary.\n" 17 | "- Async-first; avoid blocking; isolate CPU via asyncio.to_thread; keep interfaces small.\n" 18 | "- Respect Risk Policies; avoid denied paths/globs.\n" 19 | "- Avoid non-stdlib dependencies unless already present; prefer internal APIs for orchestration.\n\n" 20 | "Output Contract:\n" 21 | "- Respond with code only — no prose.\n" 22 | "- Keep diffs atomic and reversible; preserve style; keep changes deterministic.\n" 23 | ) 24 | 25 | 26 | register_prompt("chaos_bloom", _load) 27 | -------------------------------------------------------------------------------- /jinx/rt/scheduler.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """ 4 | Deadline-aware scheduling helpers for Jinx turns. 5 | 6 | This is a lightweight, cooperative layer that timeboxes tasks according to 7 | supplied deadlines. It does not try to preempt running Python coroutines; it 8 | wraps them with asyncio.wait_for to enforce deadlines. 9 | 10 | Future: upgrade to a proper EDF queue with admission control. 11 | """ 12 | 13 | import asyncio 14 | from typing import Awaitable, Callable, Optional 15 | 16 | 17 | def schedule_turn(factory: Callable[[], Awaitable[None]], *, deadline_ms: int, name: Optional[str] = None) -> asyncio.Task[None]: 18 | """Schedule a turn coroutine with a hard timeout (deadline in ms). 19 | 20 | Returns the created asyncio.Task so callers can track completion. 21 | """ 22 | timeout_s = max(0.05, deadline_ms / 1000.0) 23 | 24 | async def _runner() -> None: 25 | try: 26 | await asyncio.wait_for(factory(), timeout=timeout_s) 27 | except asyncio.TimeoutError: 28 | # Expose a friendly cancellation for upstream metrics; suppress traceback 29 | return None 30 | except asyncio.CancelledError: 31 | # Treat cancellations like timeouts in RT pipeline 32 | return None 33 | 34 | return asyncio.create_task(_runner(), name=name or "turn") 35 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/banned_dyn.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import ast 5 | 6 | from .ast_cache import get_ast 7 | from .config import is_enabled 8 | from .policy import BANNED_DYN_NAMES as _BANNED_NAMES, BANNED_DYN_ATTRS as _BANNED_ATTRS 9 | 10 | 11 | def check_banned_dynamic(code: str) -> Optional[str]: 12 | """Reject dangerous dynamic evaluation/import patterns. 13 | 14 | Disallows eval/exec/compile/__import__, and importlib.import_module. 15 | """ 16 | if not is_enabled("banned_dynamic", True): 17 | return None 18 | t = get_ast(code) 19 | if not t: 20 | return None 21 | for n in ast.walk(t): 22 | if isinstance(n, ast.Call): 23 | fn = getattr(n, "func", None) 24 | # direct name calls like eval(...) 25 | if isinstance(fn, ast.Name) and fn.id in _BANNED_NAMES: 26 | return f"dynamic call '{fn.id}(...)' is disallowed" 27 | # attribute calls like importlib.import_module(...) 28 | if isinstance(fn, ast.Attribute): 29 | mod = getattr(fn, "value", None) 30 | attr = fn.attr 31 | if isinstance(mod, ast.Name) and (mod.id, attr) in _BANNED_ATTRS: 32 | return f"dynamic import '{mod.id}.{attr}(...)' is disallowed" 33 | return None 34 | -------------------------------------------------------------------------------- /jinx/micro/runtime/self_update_handshake.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | from typing import Optional 6 | 7 | 8 | def _path() -> str | None: 9 | p = (os.getenv("JINX_SELFUPDATE_HANDSHAKE_FILE") or "").strip() 10 | return p or None 11 | 12 | 13 | def set_status(*, online: Optional[bool] = None, healthy: Optional[bool] = None) -> None: 14 | path = _path() 15 | if not path: 16 | return 17 | try: 18 | os.makedirs(os.path.dirname(path), exist_ok=True) 19 | data = {} 20 | if os.path.isfile(path): 21 | try: 22 | with open(path, "r", encoding="utf-8") as f: 23 | data = json.load(f) 24 | except Exception: 25 | data = {} 26 | if online is not None: 27 | data["online"] = bool(online) 28 | if healthy is not None: 29 | data["healthy"] = bool(healthy) 30 | with open(path, "w", encoding="utf-8") as f: 31 | json.dump(data, f) 32 | except Exception: 33 | # Never crash caller 34 | return 35 | 36 | 37 | def set_online() -> None: 38 | set_status(online=True) 39 | 40 | 41 | def set_healthy() -> None: 42 | set_status(healthy=True) 43 | 44 | 45 | __all__ = [ 46 | "set_status", 47 | "set_online", 48 | "set_healthy", 49 | ] 50 | -------------------------------------------------------------------------------- /jinx/micro/common/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Lightweight re-exports for common micro-modules 4 | from .elapsed import format_elapsed, format_duration # noqa: F401 5 | from .format_env_display import format_env_display # noqa: F401 6 | from .config_summary import create_config_summary_entries # noqa: F401 7 | from .cli_args import parse_approval_mode, parse_sandbox_mode # noqa: F401 8 | from .approval_presets import builtin_approval_presets, ApprovalPreset # noqa: F401 9 | from .config_override import parse_overrides, apply_on_value # noqa: F401 10 | from .oss import get_default_model_for_oss_provider, ensure_oss_provider_ready # noqa: F401 11 | from .user_notification import UserNotifier, UserNotification # noqa: F401 12 | from .token_data import TokenData, IdTokenInfo, parse_id_token # noqa: F401 13 | 14 | __all__ = [ 15 | "format_elapsed", 16 | "format_duration", 17 | "format_env_display", 18 | "create_config_summary_entries", 19 | "parse_approval_mode", 20 | "parse_sandbox_mode", 21 | "builtin_approval_presets", 22 | "ApprovalPreset", 23 | "parse_overrides", 24 | "apply_on_value", 25 | "get_default_model_for_oss_provider", 26 | "ensure_oss_provider_ready", 27 | "UserNotifier", 28 | "UserNotification", 29 | "TokenData", 30 | "IdTokenInfo", 31 | "parse_id_token", 32 | ] 33 | -------------------------------------------------------------------------------- /jinx/prompts/architect_api.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Template for API spec synthesis. Use str.format with: shape, project_name, candidate_resources_json, request 8 | return ( 9 | "You are Jinx's machine-first API architect — cold, precise, real-time aware.\n" 10 | "Goal: produce a pragmatic, minimal REST API spec optimized for micro-modularity and RT constraints.\n" 11 | "Discipline: ruthless minimalism, safety-first, no bloat; prefer deterministic defaults; ASCII only.\n" 12 | "The JSON MUST follow this shape exactly and contain only ASCII without code fences:\n" 13 | "{shape}\n\n" 14 | "Constraints:\n" 15 | "- Keep ≤4 resources, ≤6 fields/resource; prefer stable primitives (int/str/bool).\n" 16 | "- Endpoints only from: list|get|create|update|delete; no custom verbs.\n" 17 | "- Favor stateless design; avoid heavyweight relations; keep naming snake_case.\n" 18 | "- RT-friendly: avoid deep nesting, keep pagination implicit via 'list'.\n" 19 | "- Output STRICT JSON only — no prose, no code fences.\n\n" 20 | "Project name: {project_name}\n" 21 | "Candidate resources: {candidate_resources_json}\n" 22 | "Request: {request}\n" 23 | ) 24 | 25 | 26 | register_prompt("architect_api", _load) 27 | -------------------------------------------------------------------------------- /jinx/micro/common/result.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass, field 4 | from typing import Dict, List, Any, Optional 5 | 6 | __all__ = [ 7 | "ResultBase", 8 | "PatchResult", 9 | "VerifyResult", 10 | "RefactorResult", 11 | ] 12 | 13 | 14 | @dataclass 15 | class ResultBase: 16 | ok: bool 17 | warnings: List[str] = field(default_factory=list) 18 | errors: List[str] = field(default_factory=list) 19 | meta: Dict[str, Any] = field(default_factory=dict) 20 | 21 | def add_warning(self, w: str) -> None: 22 | if w: 23 | self.warnings.append(str(w)) 24 | 25 | def add_error(self, e: str) -> None: 26 | if e: 27 | self.errors.append(str(e)) 28 | 29 | @property 30 | def reason(self) -> str: 31 | if self.errors: 32 | return self.errors[0] 33 | return "" 34 | 35 | 36 | @dataclass 37 | class PatchResult(ResultBase): 38 | paths: List[str] = field(default_factory=list) 39 | diff: str = "" 40 | strategy: str = "" 41 | 42 | 43 | @dataclass 44 | class VerifyResult(ResultBase): 45 | goal: str = "" 46 | files: List[str] = field(default_factory=list) 47 | score: Optional[float] = None 48 | 49 | 50 | @dataclass 51 | class RefactorResult(ResultBase): 52 | ops_count: int = 0 53 | changed_files: List[str] = field(default_factory=list) 54 | -------------------------------------------------------------------------------- /jinx/micro/llm/macro_cache.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import time 5 | from typing import Awaitable, Callable, Dict, Tuple 6 | 7 | _mem: Dict[str, Tuple[int, str]] = {} 8 | _inflight: Dict[str, asyncio.Future[str]] = {} 9 | 10 | 11 | def _now_ms() -> int: 12 | try: 13 | return int(time.monotonic_ns() // 1_000_000) 14 | except Exception: 15 | return int(time.time() * 1000) 16 | 17 | 18 | async def memoized_call(key: str, ttl_ms: int, call: Callable[[], Awaitable[str]]) -> str: 19 | if ttl_ms <= 0: 20 | return await call() 21 | now = _now_ms() 22 | ent = _mem.get(key) 23 | if ent and now <= ent[0]: 24 | return ent[1] 25 | fut = _inflight.get(key) 26 | if fut is not None and not fut.done(): 27 | try: 28 | return await fut 29 | except Exception: 30 | pass 31 | loop = asyncio.get_running_loop() 32 | fut = loop.create_future() 33 | _inflight[key] = fut 34 | try: 35 | res = await call() 36 | except Exception as e: 37 | if not fut.done(): 38 | fut.set_exception(e) 39 | _inflight.pop(key, None) 40 | raise 41 | else: 42 | _mem[key] = (now + max(1, ttl_ms), res or "") 43 | if not fut.done(): 44 | fut.set_result(res or "") 45 | _inflight.pop(key, None) 46 | return res or "" 47 | -------------------------------------------------------------------------------- /jinx/rt/threadpool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """ 4 | Centralized CPU threadpool for RT-friendly offloading. 5 | 6 | Use run_cpu(func, *args, **kwargs) to execute CPU-bound work off the event loop. 7 | Max workers are controlled by env JINX_CPU_WORKERS (default: min(8, max(2, cpu_count))). 8 | """ 9 | 10 | import os 11 | import concurrent.futures as _fut 12 | from functools import partial 13 | from typing import Any, Callable 14 | import asyncio 15 | 16 | 17 | def _calc_workers() -> int: 18 | try: 19 | v = int(os.getenv("JINX_CPU_WORKERS", "0")) 20 | if v > 0: 21 | return v 22 | except Exception: 23 | pass 24 | try: 25 | import os as _os 26 | c = _os.cpu_count() or 4 27 | except Exception: 28 | c = 4 29 | return max(2, min(8, int(c))) 30 | 31 | 32 | _CPU_EXECUTOR: _fut.ThreadPoolExecutor | None = None 33 | 34 | 35 | def _get_exec() -> _fut.ThreadPoolExecutor: 36 | global _CPU_EXECUTOR 37 | if _CPU_EXECUTOR is None: 38 | _CPU_EXECUTOR = _fut.ThreadPoolExecutor(max_workers=_calc_workers(), thread_name_prefix="jinx-cpu") 39 | return _CPU_EXECUTOR 40 | 41 | 42 | async def run_cpu(func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any: 43 | loop = asyncio.get_running_loop() 44 | execu = _get_exec() 45 | return await loop.run_in_executor(execu, partial(func, *args, **kwargs)) 46 | -------------------------------------------------------------------------------- /jinx/micro/conversation/cont/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # Re-export public API for continuity micro-modules (cont/*) 4 | 5 | from .util import is_short_followup 6 | from .anchors import ( 7 | extract_anchors, 8 | last_agent_question, 9 | last_user_query, 10 | ) 11 | from .query import augment_query_for_retrieval 12 | from .render import render_continuity_block 13 | from .cache import ( 14 | load_last_context, 15 | save_last_context, 16 | maybe_reuse_last_context, 17 | load_last_anchors, 18 | ) 19 | from .meta import ( 20 | load_cache_meta, 21 | load_cache_meta_sync, 22 | save_last_context_with_meta, 23 | ) 24 | from .topic import detect_topic_shift 25 | from .compactor import maybe_compact_state_frames 26 | 27 | __all__ = [ 28 | # util 29 | "is_short_followup", 30 | # anchors 31 | "extract_anchors", 32 | "last_agent_question", 33 | "last_user_query", 34 | # query/task 35 | "augment_query_for_retrieval", 36 | # render 37 | "render_continuity_block", 38 | # cache 39 | "load_last_context", 40 | "save_last_context", 41 | "maybe_reuse_last_context", 42 | "load_last_anchors", 43 | # meta 44 | "load_cache_meta", 45 | "load_cache_meta_sync", 46 | "save_last_context_with_meta", 47 | # topic 48 | "detect_topic_shift", 49 | # compactor 50 | "maybe_compact_state_frames", 51 | ] 52 | -------------------------------------------------------------------------------- /jinx/micro/memory/kb_extract_smoke.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from typing import List, Tuple 5 | 6 | from jinx.micro.memory.kb_extract import extract_triplets 7 | 8 | 9 | LINES: List[str] = [ 10 | # head label 11 | "label: Value.With.Path", 12 | # arrows 13 | "A -> B -> C", 14 | # equality and assignment 15 | "Alias == Target", 16 | "obj.field = some.value", 17 | # calls & args 18 | "compute(sum(a, b), item.get(id))", 19 | # dotted and scope 20 | "ns::Type.method", 21 | "pkg.module.symbol", 22 | # paths 23 | "src/core/utils/file.py", 24 | r"C:\\proj\\src\\main.cpp", 25 | # generics and brackets 26 | "Map>", 27 | "arr[index]", 28 | # return types 29 | "fn(a,b): ReturnType", 30 | "def g(x,y) -> T[U]", 31 | # destructuring returns 32 | "(x, y) = callee(a, b)", 33 | "[first, second] = build()", 34 | # object/array literals 35 | "config = { host: localhost, port: 8080 }", 36 | "values = [ one, two, three ]", 37 | # colon types 38 | "name: Type", 39 | ] 40 | 41 | 42 | async def main() -> None: 43 | triples: List[Tuple[str, str, str]] = extract_triplets(LINES, max_items=200, max_time_ms=120) 44 | print(f"triples: {len(triples)}") 45 | for t in triples: 46 | print(t) 47 | 48 | 49 | if __name__ == "__main__": 50 | asyncio.run(main()) 51 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_line_window.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Tuple 4 | 5 | 6 | def find_line_window(text: str, tokens: List[str], around: int = 6) -> Tuple[int, int, str]: 7 | """Find a small line window around the first occurrence of any token. 8 | 9 | Returns (line_start, line_end, snippet). Lines are 1-based and inclusive. 10 | If nothing is found, returns (0, 0, ""). 11 | """ 12 | if not text or not tokens: 13 | return 0, 0, "" 14 | lowered = text.lower() 15 | # Prefer token order (already prioritized by callers) rather than earliest position. 16 | # This helps target more specific identifiers like 'import_module' over generic ones like 'name'. 17 | hit_pos = -1 18 | hit_len = 0 19 | for t in tokens: 20 | tl = (t or "").strip() 21 | if not tl: 22 | continue 23 | p = lowered.find(tl.lower()) 24 | if p >= 0: 25 | hit_pos = p 26 | hit_len = len(tl) 27 | break 28 | if hit_pos < 0: 29 | return 0, 0, "" 30 | pre = text[:hit_pos] 31 | ls = pre.count("\n") + 1 32 | le = ls + max(1, text[hit_pos:hit_pos + hit_len].count("\n")) 33 | lines_all = text.splitlines() 34 | a = max(1, ls - around) 35 | b = min(len(lines_all), le + around) 36 | snippet = "\n".join(lines_all[a - 1:b]).strip() 37 | return a, b, snippet 38 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_hashdb.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | from typing import Dict, Any 6 | 7 | from .project_paths import ensure_project_dirs, PROJECT_HASH_DB_PATH 8 | from .project_io import write_json_atomic 9 | 10 | 11 | def load_hash_db() -> Dict[str, Dict[str, Any]]: 12 | ensure_project_dirs() 13 | try: 14 | if not os.path.exists(PROJECT_HASH_DB_PATH): 15 | return {} 16 | with open(PROJECT_HASH_DB_PATH, "r", encoding="utf-8") as f: 17 | obj = json.load(f) 18 | if isinstance(obj, dict): 19 | return obj # type: ignore[return-value] 20 | except Exception: 21 | pass 22 | return {} 23 | 24 | 25 | def save_hash_db(db: Dict[str, Dict[str, Any]]) -> None: 26 | ensure_project_dirs() 27 | try: 28 | write_json_atomic(PROJECT_HASH_DB_PATH, db) 29 | except Exception: 30 | # Best-effort: leave previous DB 31 | pass 32 | 33 | 34 | def set_record(db: Dict[str, Dict[str, Any]], rel_path: str, *, sha: str, mtime: float) -> None: 35 | db[rel_path] = {"sha": sha, "mtime": mtime} 36 | 37 | 38 | def get_record(db: Dict[str, Dict[str, Any]], rel_path: str) -> Dict[str, Any] | None: 39 | return db.get(rel_path) 40 | 41 | 42 | def del_record(db: Dict[str, Dict[str, Any]], rel_path: str) -> None: 43 | if rel_path in db: 44 | del db[rel_path] 45 | -------------------------------------------------------------------------------- /jinx/micro/memory/schema.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass, field, asdict 4 | from typing import Any, Dict 5 | 6 | 7 | @dataclass(slots=True) 8 | class MemoryItem: 9 | """A normalized memory item for hierarchical levels. 10 | 11 | - text: displayable text line/paragraph (already trimmed as needed) 12 | - source: logical source, e.g. 'compact', 'evergreen', 'state', 'dialogue', 'kb', 'summary' 13 | - ts_ms: timestamp in ms (0 if unknown) 14 | - meta: arbitrary metadata (e.g., preview hash, channel, path) 15 | """ 16 | text: str 17 | source: str 18 | ts_ms: int = 0 19 | meta: Dict[str, Any] = field(default_factory=dict) 20 | 21 | def to_dict(self) -> Dict[str, Any]: 22 | return asdict(self) 23 | 24 | 25 | @dataclass(slots=True) 26 | class Triplet: 27 | """Structured knowledge triple. 28 | 29 | - subject, predicate, object 30 | - count: frequency observed (optional) 31 | - last_ts: last seen timestamp (ms) 32 | - meta: extra fields 33 | """ 34 | subject: str 35 | predicate: str 36 | object: str 37 | count: int = 0 38 | last_ts: int = 0 39 | meta: Dict[str, Any] = field(default_factory=dict) 40 | 41 | def to_line(self) -> str: 42 | return f"kb: {self.subject} | {self.predicate} | {self.object}" 43 | 44 | def to_dict(self) -> Dict[str, Any]: 45 | return asdict(self) 46 | -------------------------------------------------------------------------------- /jinx/prompts/planner_refine_embed.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import register_prompt 4 | 5 | 6 | def _load() -> str: 7 | # Template for refining a JSON patch plan to focus on specific files. 8 | # Use str.format with: goal, top_files_csv, current_plan_json 9 | return ( 10 | "Revise the JSON plan as Jinx's machine-first internal architect: ruthless, RT-aware.\n" 11 | "Focus primarily on these files (highest priority first): {top_files_csv}. Do not expand to other files.\n" 12 | "Keep the exact same JSON schema and constraints (no extra fields). Prefer context/symbol strategies.\n" 13 | "Constraints: atomic patches, tiny diffs, preserve existing style, ASCII only, no code fences; avoid '<' and '>' in values.\n" 14 | "- Keep any 'code' field <= 120 lines; prefer smaller when possible.\n" 15 | "- Minimize imports; avoid adding non-stdlib imports; prefer reusing existing imports.\n" 16 | "- Allowed strategies reminder: 'symbol','symbol_body','context','line','semantic','write','codemod_rename','codemod_add_import','codemod_replace_import'.\n" 17 | "- If uncertain, prefer 'semantic' description over risky line ranges.\n" 18 | "Respect the Risk Policy: avoid denied paths or globs.\n" 19 | "Goal: {goal}\n" 20 | "Current plan:\n{current_plan_json}\n" 21 | ) 22 | 23 | 24 | register_prompt("planner_refine_embed", _load) 25 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/deserialization_safety.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import ast 5 | 6 | from .ast_cache import get_ast 7 | from .config import is_enabled 8 | 9 | 10 | _UNSAFE_LOAD_FUNCS = { 11 | ("pickle", "load"), 12 | ("pickle", "loads"), 13 | ("dill", "load"), 14 | ("dill", "loads"), 15 | ("marshal", "load"), 16 | ("marshal", "loads"), 17 | } 18 | 19 | 20 | def check_deserialization_safety(code: str) -> Optional[str]: 21 | """Disallow unsafe deserialization primitives. 22 | 23 | - Ban pickle/dill/marshal load/loads 24 | - Forbid yaml.load; require yaml.safe_load instead 25 | """ 26 | if not is_enabled("deserialization_safety", True): 27 | return None 28 | t = get_ast(code) 29 | if not t: 30 | return None 31 | for n in ast.walk(t): 32 | if isinstance(n, ast.Call): 33 | fn = getattr(n, "func", None) 34 | # module.func calls 35 | if isinstance(fn, ast.Attribute) and isinstance(fn.value, ast.Name): 36 | mod = fn.value.id 37 | attr = fn.attr 38 | if (mod, attr) in _UNSAFE_LOAD_FUNCS: 39 | return f"unsafe deserialization '{mod}.{attr}(...)' is disallowed" 40 | if mod == "yaml" and attr == "load": 41 | return "yaml.load is disallowed; use yaml.safe_load" 42 | return None 43 | -------------------------------------------------------------------------------- /jinx/micro/brain/scanners/errors.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import asyncio 5 | import re as _re 6 | from typing import Dict 7 | 8 | from jinx.log_paths import TRIGGER_ECHOES, BLUE_WHISPERS 9 | 10 | 11 | async def _tail_file(path: str, max_chars: int = 40000) -> str: 12 | try: 13 | def _read() -> str: 14 | try: 15 | with open(path, 'r', encoding='utf-8', errors='ignore') as f: 16 | t = f.read() 17 | return t[-max_chars:] 18 | except Exception: 19 | return '' 20 | return await asyncio.to_thread(_read) 21 | except Exception: 22 | return '' 23 | 24 | 25 | async def scan_error_classes() -> Dict[str, float]: 26 | nodes: Dict[str, float] = {} 27 | for fp in (TRIGGER_ECHOES, BLUE_WHISPERS): 28 | try: 29 | if not fp or not os.path.exists(fp): 30 | continue 31 | tail = await _tail_file(fp) 32 | if not tail: 33 | continue 34 | for m in _re.finditer(r"(?m)\b([A-Za-z_][A-Za-z0-9_]*(?:Error|Exception))\b", tail): 35 | name = (m.group(1) or '').strip() 36 | if name: 37 | nodes[f"error: {name}"] = nodes.get(f"error: {name}", 0.0) + 1.5 38 | except Exception: 39 | continue 40 | return nodes 41 | 42 | 43 | __all__ = ["scan_error_classes"] 44 | -------------------------------------------------------------------------------- /jinx/bootstrap/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Iterable 4 | from .deps import ensure_optional 5 | 6 | # Try to ensure python-dotenv is available at runtime; if not, proceed with noop 7 | try: 8 | _mods = ensure_optional(["dotenv"]) # installs if missing 9 | dotenv = _mods.get("dotenv") # type: ignore[assignment] 10 | except Exception: 11 | dotenv = None # type: ignore[assignment] 12 | 13 | 14 | def load_env(paths: Iterable[str] | None = None) -> None: 15 | """Best-effort load of environment variables via python-dotenv. 16 | 17 | If python-dotenv is unavailable, this is a no-op. 18 | """ 19 | if dotenv is None: 20 | return 21 | if paths: 22 | for p in paths: 23 | try: 24 | dotenv.load_dotenv(p, override=False) 25 | except Exception: 26 | pass 27 | return 28 | try: 29 | found = dotenv.find_dotenv(usecwd=True) 30 | if found: 31 | dotenv.load_dotenv(found, override=False) 32 | else: 33 | dotenv.load_dotenv(override=False) 34 | except Exception: 35 | pass 36 | 37 | # Apply autonomous configuration after .env is loaded 38 | try: 39 | from jinx.micro.runtime.autoconfig import apply_auto_defaults 40 | apply_auto_defaults() 41 | except Exception: 42 | # Non-fatal: continue without auto-config 43 | pass 44 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/service.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from typing import Optional 5 | 6 | from .crawler import start_realtime_collection 7 | from .pipeline import embed_text 8 | 9 | 10 | class EmbeddingsService: 11 | """Micromodule to run realtime embeddings collection in background.""" 12 | 13 | def __init__(self) -> None: 14 | self._task: Optional[asyncio.Task] = None 15 | 16 | async def _cb(self, text: str, source: str, kind: str) -> None: 17 | # Best-effort; isolate failures from the tailing loop 18 | try: 19 | await embed_text(text, source=source, kind=kind) 20 | except Exception: 21 | # Silent drop; logging could be added if needed 22 | pass 23 | 24 | async def run(self) -> None: 25 | await start_realtime_collection(self._cb) 26 | 27 | 28 | _task_ref: Optional[asyncio.Task] = None 29 | 30 | 31 | def start_embeddings_task() -> asyncio.Task[None]: 32 | global _task_ref 33 | svc = EmbeddingsService() 34 | _task_ref = asyncio.create_task(svc.run(), name="embeddings-service") 35 | return _task_ref 36 | 37 | 38 | async def stop_embeddings_task() -> None: 39 | global _task_ref 40 | t = _task_ref 41 | _task_ref = None 42 | if t is not None and not t.done(): 43 | t.cancel() 44 | try: 45 | await t 46 | except asyncio.CancelledError: 47 | pass 48 | -------------------------------------------------------------------------------- /jinx/micro/runtime/patch/validators_integration.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Tuple 4 | 5 | 6 | def validate_text(path: str, text: str) -> Tuple[bool, str]: 7 | """Run code validators against text. Returns (ok, message). 8 | 9 | - Uses jinx.codeexec validators if available; safe no-op when unavailable. 10 | - Aggregates violations into a concise message. 11 | """ 12 | try: 13 | from jinx.codeexec import collect_violations, collect_violations_detailed 14 | except Exception: 15 | return True, "validators unavailable" 16 | try: 17 | v = collect_violations(text or "") 18 | if not v: 19 | return True, "ok" 20 | # Build detailed message 21 | try: 22 | dets = collect_violations_detailed(text or "") 23 | if dets: 24 | lines: List[str] = [] 25 | for d in dets: 26 | ident = str(d.get("id", "rule")) 27 | line = int(d.get("line", 0)) 28 | msg = str(d.get("msg", "")) 29 | cat = str(d.get("category", "")) 30 | lines.append(f"[{cat}] {ident}@{line}: {msg}") 31 | return False, "; ".join(lines[:20]) 32 | except Exception: 33 | pass 34 | return False, "; ".join(v[:10]) 35 | except Exception as e: 36 | return True, f"validator error ignored: {e}" 37 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/scan_store.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | from typing import List, Tuple, Dict, Any 6 | 7 | 8 | def iter_items(root: str, max_files_per_source: int, max_sources: int) -> List[Tuple[str, Dict[str, Any]]]: 9 | """Yield (source, payload) pairs from the on-disk embeddings store. 10 | 11 | - Scans per-source directories under `root`, skipping the special 'index' dir. 12 | - Limits number of sources and files per source to bound I/O. 13 | - Swallows JSON and file errors to preserve best-effort semantics. 14 | """ 15 | items: List[Tuple[str, Dict[str, Any]]] = [] 16 | if not os.path.isdir(root): 17 | return items 18 | sources = [d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d)) and d != "index"] 19 | sources = sources[:max_sources] 20 | for src in sources: 21 | src_dir = os.path.join(root, src) 22 | try: 23 | files = [f for f in os.listdir(src_dir) if f.endswith(".json")] 24 | except FileNotFoundError: 25 | continue 26 | files = files[:max_files_per_source] 27 | for fn in files: 28 | p = os.path.join(src_dir, fn) 29 | try: 30 | with open(p, "r", encoding="utf-8") as f: 31 | obj = json.load(f) 32 | items.append((src, obj)) 33 | except Exception: 34 | continue 35 | return items 36 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_paths.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import hashlib 5 | 6 | # Root directory for project code embeddings (separate from log/embeddings) 7 | PROJECT_EMBED_ROOT = os.path.join("emb") 8 | # Per-file chunks will be stored under: emb/files//*.json 9 | PROJECT_FILES_DIR = os.path.join(PROJECT_EMBED_ROOT, "files") 10 | # Per-file index lines: emb/index/.jsonl (overwritten on update) 11 | PROJECT_INDEX_DIR = os.path.join(PROJECT_EMBED_ROOT, "index") 12 | # Internal state (hash db, etc.) 13 | PROJECT_STATE_DIR = os.path.join(PROJECT_EMBED_ROOT, "_state") 14 | PROJECT_HASH_DB_PATH = os.path.join(PROJECT_STATE_DIR, "hashes.json") 15 | 16 | 17 | def ensure_project_dirs() -> None: 18 | os.makedirs(PROJECT_EMBED_ROOT, exist_ok=True) 19 | os.makedirs(PROJECT_FILES_DIR, exist_ok=True) 20 | os.makedirs(PROJECT_INDEX_DIR, exist_ok=True) 21 | os.makedirs(PROJECT_STATE_DIR, exist_ok=True) 22 | 23 | 24 | def safe_rel_path(rel_path: str) -> str: 25 | """Make a relative path safe for use as a single directory name. 26 | 27 | We flatten the path by replacing both os.sep and '/' with '__'. 28 | """ 29 | rel_path = rel_path.strip().lstrip("./\\") 30 | flattened = rel_path.replace(os.sep, "__").replace("/", "__") 31 | # Add a short stable hash prefix to avoid collisions 32 | h = hashlib.sha1(rel_path.encode("utf-8", errors="ignore")).hexdigest()[:8] 33 | return f"{h}__{flattened}" 34 | -------------------------------------------------------------------------------- /jinx/bootstrap/installer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import subprocess 4 | import sys 5 | import time 6 | import asyncio 7 | 8 | 9 | def _assert_not_in_event_loop() -> None: 10 | """Raise if called from within a running asyncio event loop.""" 11 | try: 12 | loop = asyncio.get_running_loop() 13 | except RuntimeError: 14 | # No running loop in this thread — safe 15 | return 16 | else: 17 | if loop.is_running(): 18 | raise RuntimeError( 19 | "bootstrap.installer.package() must not be called from within an active event loop" 20 | ) 21 | 22 | 23 | def package(p: str, *, retries: int = 1, delay_s: float = 1.5) -> None: 24 | """Install a pip package by name with light retries.""" 25 | _assert_not_in_event_loop() 26 | attempt = 0 27 | last_exc: Exception | None = None 28 | cmd = [ 29 | sys.executable, 30 | "-m", 31 | "pip", 32 | "install", 33 | "--disable-pip-version-check", 34 | "--no-input", 35 | p, 36 | ] 37 | while attempt <= max(0, retries): 38 | try: 39 | subprocess.check_call(cmd) 40 | return 41 | except Exception as exc: # pragma: no cover - best-effort bootstrap 42 | last_exc = exc 43 | attempt += 1 44 | if attempt > retries: 45 | break 46 | time.sleep(delay_s) 47 | if last_exc: 48 | raise last_exc 49 | -------------------------------------------------------------------------------- /jinx/micro/common/approval_presets.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import List 5 | 6 | __all__ = ["ApprovalPreset", "builtin_approval_presets"] 7 | 8 | 9 | @dataclass(frozen=True) 10 | class ApprovalPreset: 11 | id: str 12 | label: str 13 | description: str 14 | approval: str # 'unless_trusted' | 'on_failure' | 'on_request' | 'never' 15 | sandbox: str # 'read-only' | 'workspace-write' | 'danger-full-access' 16 | 17 | 18 | def builtin_approval_presets() -> List[ApprovalPreset]: 19 | return [ 20 | ApprovalPreset( 21 | id="read-only", 22 | label="Read Only", 23 | description="Requires approval to edit files and run commands.", 24 | approval="on_request", 25 | sandbox="read-only", 26 | ), 27 | ApprovalPreset( 28 | id="auto", 29 | label="Agent", 30 | description="Read and edit files, and run commands.", 31 | approval="on_request", 32 | sandbox="workspace-write", 33 | ), 34 | ApprovalPreset( 35 | id="full-access", 36 | label="Agent (full access)", 37 | description=( 38 | "Can edit files outside this workspace and run commands with network access. " 39 | "Exercise caution when using." 40 | ), 41 | approval="never", 42 | sandbox="danger-full-access", 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/side_effects.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import ast 5 | 6 | from .ast_cache import get_ast 7 | from .config import is_enabled 8 | 9 | 10 | def check_side_effect_policy(code: str) -> Optional[str]: 11 | """Flag direct UI-launching OS side effects, independent of prompts. 12 | 13 | Disallows: 14 | - webbrowser.open(...) 15 | - os.startfile(...) 16 | 17 | Rationale: these trigger system/UI actions that are not universally safe in 18 | sandboxed or CI contexts. Prefer returning the target (URL/path) or using 19 | higher-level runtime primitives to delegate the action. 20 | """ 21 | if not is_enabled("side_effects", True): 22 | return None 23 | t = get_ast(code) 24 | if not t: 25 | return None 26 | for n in ast.walk(t): 27 | if isinstance(n, ast.Call): 28 | fn = getattr(n, "func", None) 29 | if isinstance(fn, ast.Attribute) and isinstance(fn.value, ast.Name): 30 | # webbrowser.open(...) 31 | if fn.value.id == "webbrowser" and fn.attr == "open": 32 | return "UI side-effect 'webbrowser.open(...)' is disallowed; return the URL or delegate via runtime primitives" 33 | # os.startfile(...) 34 | if fn.value.id == "os" and fn.attr == "startfile": 35 | return "UI side-effect 'os.startfile(...)' is disallowed; return the path or delegate via runtime primitives" 36 | return None 37 | -------------------------------------------------------------------------------- /jinx/log_paths.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | # Conversation transcript (was: log/soul_fragment.txt) 6 | INK_SMEARED_DIARY: str = os.path.join("log", "ink_smeared_diary.txt") 7 | 8 | # General/default log (was: log/cortex_wail.txt) 9 | BLUE_WHISPERS: str = os.path.join("log", "blue_whispers.txt") 10 | 11 | # User input and executed code logs (was: log/detonator.txt) 12 | TRIGGER_ECHOES: str = os.path.join("log", "trigger_echoes.txt") 13 | 14 | # Sandbox output summary (was: log/nano_doppelganger.txt) 15 | CLOCKWORK_GHOST: str = os.path.join("log", "clockwork_ghost.txt") 16 | 17 | # Sandbox streaming logs directory and index 18 | SANDBOX_DIR: str = os.path.join("log", "sandbox") 19 | 20 | # Evergreen durable memory store 21 | EVERGREEN_MEMORY: str = os.path.join("log", "evergreen_memory.txt") 22 | 23 | # Directory for general OpenAI request dumps (one file per request) 24 | OPENAI_REQUESTS_DIR_GENERAL: str = os.path.join("log", "openai", "general") 25 | 26 | # Directory for memory optimizer OpenAI request dumps (one file per request) 27 | OPENAI_REQUESTS_DIR_MEMORY: str = os.path.join("log", "openai", "memory") 28 | 29 | # Autotune persisted state 30 | AUTOTUNE_STATE: str = os.path.join("log", "autotune_state.json") 31 | 32 | # Planner/reflector trace (JSONL records for debugging planner chain) 33 | PLAN_TRACE: str = os.path.join("log", "plan_trace.jsonl") 34 | 35 | # Chain resilience persistent state (auto-disable windows, failure counters) 36 | CHAIN_STATE: str = os.path.join("log", "chain_state.json") 37 | -------------------------------------------------------------------------------- /jinx/observability/setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """ 4 | Optional OpenTelemetry setup. If opentelemetry SDK is installed and JINX_OTEL_SETUP=1, 5 | configures a simple console exporter for traces. 6 | """ 7 | 8 | def setup_otel() -> None: 9 | try: 10 | import os 11 | if str(os.getenv("JINX_OTEL_SETUP", "0")).lower() in ("", "0", "false", "off", "no"): 12 | return 13 | from opentelemetry import trace # type: ignore 14 | from opentelemetry.sdk.trace import TracerProvider # type: ignore 15 | from opentelemetry.sdk.trace.export import SimpleSpanProcessor # type: ignore 16 | from opentelemetry.sdk.resources import Resource # type: ignore 17 | # Exporter choice 18 | exporter_name = str(os.getenv("JINX_OTEL_EXPORTER", "console")).lower() 19 | if exporter_name == "console": 20 | from opentelemetry.sdk.trace.export import ConsoleSpanExporter # type: ignore 21 | exporter = ConsoleSpanExporter() 22 | else: 23 | # Default to console if unknown 24 | from opentelemetry.sdk.trace.export import ConsoleSpanExporter # type: ignore 25 | exporter = ConsoleSpanExporter() 26 | resource = Resource.create({"service.name": "jinx"}) 27 | provider = TracerProvider(resource=resource) 28 | processor = SimpleSpanProcessor(exporter) 29 | provider.add_span_processor(processor) 30 | trace.set_tracer_provider(provider) 31 | except Exception: 32 | return 33 | -------------------------------------------------------------------------------- /jinx/observability/otel.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from contextlib import contextmanager 4 | 5 | 6 | try: 7 | from opentelemetry import trace as _otel_trace # type: ignore 8 | except Exception: # opentelemetry is optional 9 | _otel_trace = None # type: ignore 10 | 11 | 12 | @contextmanager 13 | def span(name: str, attrs: dict | None = None): 14 | """Best-effort OpenTelemetry span context manager. 15 | 16 | If OpenTelemetry is not installed or errors occur, behaves as a no-op. 17 | """ 18 | if _otel_trace is None: 19 | # No OTEL: no-op span 20 | yield 21 | return 22 | ctx = None 23 | try: 24 | tracer = _otel_trace.get_tracer("jinx") 25 | ctx = tracer.start_as_current_span(name) 26 | sp = ctx.__enter__() 27 | # Set attributes if provided (best-effort) 28 | if attrs: 29 | try: 30 | cur = _otel_trace.get_current_span() 31 | target = cur or sp 32 | for k, v in (attrs or {}).items(): 33 | try: 34 | target.set_attribute(str(k), v) 35 | except Exception: 36 | pass 37 | except Exception: 38 | pass 39 | yield 40 | except Exception: 41 | # Any OTEL error -> no-op 42 | yield 43 | finally: 44 | try: 45 | if ctx is not None: 46 | ctx.__exit__(None, None, None) 47 | except Exception: 48 | pass 49 | -------------------------------------------------------------------------------- /jinx/micro/memory/unified.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import List 5 | 6 | from .router import assemble_memroute as _assemble_memroute 7 | 8 | 9 | async def assemble_unified_memory_lines(query: str, *, k: int = 12, preview_chars: int = 160, max_time_ms: int | None = None) -> List[str]: 10 | """Unified memory assembly used by macros and builders. 11 | 12 | Delegates to memroute (pins + graph-aligned + vector + kb + ranker) with RT bounds. 13 | """ 14 | q = (query or "").strip() 15 | if not q: 16 | return [] 17 | # Let memroute manage its own internal budget. We pass k and preview clamp. 18 | try: 19 | k_eff = max(1, int(k or int(os.getenv("JINX_MACRO_MEM_TOPK", "12")))) 20 | except Exception: 21 | k_eff = 12 22 | try: 23 | clamp = max(24, int(preview_chars or int(os.getenv("JINX_MACRO_MEM_PREVIEW_CHARS", "160")))) 24 | except Exception: 25 | clamp = 160 26 | try: 27 | lines = await _assemble_memroute(q, k=k_eff, preview_chars=clamp) 28 | except Exception: 29 | lines = [] 30 | # Dedup and clamp again, just in case 31 | out: List[str] = [] 32 | seen: set[str] = set() 33 | for ln in (lines or []): 34 | s = (ln or "").strip() 35 | if not s or s in seen: 36 | continue 37 | seen.add(s) 38 | out.append(s[:clamp]) 39 | if len(out) >= k_eff: 40 | break 41 | return out 42 | 43 | 44 | __all__ = ["assemble_unified_memory_lines"] 45 | -------------------------------------------------------------------------------- /jinx/micro/sandbox/normalizer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | import hashlib 5 | import re 6 | from typing import Tuple 7 | 8 | __all__ = ["code_key", "canonicalize"] 9 | 10 | _NEWLINES_RE = re.compile(r"\r\n|\r|\n") 11 | _WS_TAIL_RE = re.compile(r"[ \t]+$", re.MULTILINE) 12 | 13 | 14 | def _stable_ast_dump(code: str) -> str | None: 15 | try: 16 | tree = ast.parse(code) 17 | # Exclude attributes so positions/line numbers don't affect the digest 18 | return ast.dump(tree, annotate_fields=True, include_attributes=False) 19 | except Exception: 20 | return None 21 | 22 | 23 | def canonicalize(code: str) -> str: 24 | """Return a language-aware canonical form for Python code for hashing. 25 | 26 | - Normalizes newlines to \n 27 | - Strips trailing whitespace 28 | 29 | - Tries AST dump to ignore comments/whitespace differences 30 | """ 31 | s = code or "" 32 | # Normalize newlines and trailing spaces 33 | s = _NEWLINES_RE.sub("\n", s) 34 | s = _WS_TAIL_RE.sub("", s) 35 | # Prefer AST structure when available 36 | adump = _stable_ast_dump(s) 37 | if adump is not None: 38 | return adump 39 | return s 40 | 41 | 42 | def code_key(code: str) -> str: 43 | """Stable key for sandbox coalescing and result caching. 44 | 45 | Uses AST-based canonicalization when possible; falls back to normalized text. 46 | """ 47 | can = canonicalize(code) 48 | h = hashlib.sha256(can.encode("utf-8", errors="ignore")).hexdigest() 49 | return h 50 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_prune.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import shutil 5 | from typing import Dict 6 | 7 | from .project_paths import PROJECT_FILES_DIR, PROJECT_INDEX_DIR, safe_rel_path 8 | from .project_hashdb import del_record 9 | 10 | 11 | def prune_deleted(root: str, db: Dict[str, Dict[str, object]]) -> bool: 12 | """Remove artifacts for files that no longer exist. Returns True if db changed.""" 13 | changed = False 14 | to_delete: list[str] = [] 15 | for rel_p in list(db.keys()): 16 | abs_p = os.path.join(root, rel_p) 17 | if not os.path.exists(abs_p): 18 | to_delete.append(rel_p) 19 | for rel_p in to_delete: 20 | changed |= prune_single(root, db, rel_p) 21 | return changed 22 | 23 | 24 | def prune_single(root: str, db: Dict[str, Dict[str, object]], rel_p: str) -> bool: 25 | """Prune artifacts for a single file rel path. Returns True if db changed.""" 26 | abs_p = os.path.join(root, rel_p) 27 | if os.path.exists(abs_p): 28 | return False 29 | safe = safe_rel_path(rel_p) 30 | file_dir = os.path.join(PROJECT_FILES_DIR, safe) 31 | index_path = os.path.join(PROJECT_INDEX_DIR, f"{safe}.json") 32 | try: 33 | if os.path.isdir(file_dir): 34 | shutil.rmtree(file_dir, ignore_errors=True) 35 | except Exception: 36 | pass 37 | try: 38 | if os.path.exists(index_path): 39 | os.remove(index_path) 40 | except Exception: 41 | pass 42 | del_record(db, rel_p) 43 | return True 44 | -------------------------------------------------------------------------------- /jinx/micro/core/terminal.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import re 5 | from functools import lru_cache 6 | 7 | __all__ = ["user_agent"] 8 | 9 | _VALID_RE = re.compile(r"[A-Za-z0-9\-_./]") 10 | 11 | 12 | def _sanitize_header_value(value: str) -> str: 13 | return "".join(ch if _VALID_RE.match(ch) else "_" for ch in value) 14 | 15 | 16 | @lru_cache(maxsize=1) 17 | def user_agent() -> str: 18 | tp = os.getenv("TERM_PROGRAM", "").strip() 19 | if tp: 20 | ver = os.getenv("TERM_PROGRAM_VERSION", "").strip() 21 | return _sanitize_header_value(f"{tp}/{ver}" if ver else tp) 22 | 23 | v = os.getenv("WEZTERM_VERSION", "").strip() 24 | if v: 25 | return _sanitize_header_value(f"WezTerm/{v}") 26 | 27 | if os.getenv("KITTY_WINDOW_ID") or (os.getenv("TERM", "").find("kitty") != -1): 28 | return _sanitize_header_value("kitty") 29 | 30 | if os.getenv("ALACRITTY_SOCKET") or (os.getenv("TERM", "") == "alacritty"): 31 | return _sanitize_header_value("Alacritty") 32 | 33 | v = os.getenv("KONSOLE_VERSION", "").strip() 34 | if v: 35 | return _sanitize_header_value(f"Konsole/{v}") 36 | 37 | if os.getenv("GNOME_TERMINAL_SCREEN"): 38 | return _sanitize_header_value("gnome-terminal") 39 | 40 | v = os.getenv("VTE_VERSION", "").strip() 41 | if v: 42 | return _sanitize_header_value(f"VTE/{v}") 43 | 44 | if os.getenv("WT_SESSION"): 45 | return _sanitize_header_value("WindowsTerminal") 46 | 47 | return _sanitize_header_value(os.getenv("TERM", "unknown")) 48 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/memory_context.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import hashlib 4 | import os 5 | from typing import List 6 | 7 | from jinx.micro.memory.unified import assemble_unified_memory_lines as _mem_unified 8 | 9 | 10 | async def build_memory_context_for(query: str, *, k: int | None = None, max_chars: int = 1500, max_time_ms: int | None = 220) -> str: 11 | """Build using the unified memory assembler (pins+graph+vector+kb+ranker). 12 | 13 | This unifies the memory shown via macros ({{m:memroute}}) and embeddings context to avoid duplication. 14 | """ 15 | q = (query or "").strip() 16 | if not q: 17 | return "" 18 | try: 19 | k_eff = int(k) if k is not None else int(os.getenv("EMBED_TOP_K", "5")) 20 | except Exception: 21 | k_eff = 5 22 | k_eff = max(1, k_eff) 23 | try: 24 | clamp = max(24, int(os.getenv("JINX_MACRO_MEM_PREVIEW_CHARS", "160"))) 25 | except Exception: 26 | clamp = 160 27 | try: 28 | lines = await _mem_unified(q, k=k_eff, preview_chars=clamp) 29 | except Exception: 30 | lines = [] 31 | if not lines: 32 | return "" 33 | parts: List[str] = [] 34 | total = 0 35 | for ln in lines: 36 | L = len(ln) + 1 37 | if total + L > max_chars: 38 | break 39 | parts.append(ln) 40 | total += L 41 | if not parts: 42 | return "" 43 | body = "\n".join(parts) 44 | return f"\n{body}\n" 45 | 46 | 47 | __all__ = ["build_memory_context_for"] 48 | -------------------------------------------------------------------------------- /jinx/codemods/rope_rename.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """ 4 | Optional project-wide rename via rope. If rope is unavailable, returns False. 5 | 6 | Usage: 7 | await project_rename_symbol(root, module_rel, old_name, new_name) 8 | """ 9 | 10 | import asyncio 11 | from typing import Optional 12 | import os 13 | 14 | 15 | async def project_rename_symbol(root: str, module_rel: str, *, old_name: str, new_name: str) -> bool: 16 | try: 17 | from rope.base.project import Project # type: ignore 18 | from rope.base.libutils import path_to_resource # type: ignore 19 | from rope.refactor.rename import Rename # type: ignore 20 | except Exception: 21 | return False 22 | try: 23 | proj = Project(root) 24 | try: 25 | abs_path = os.path.join(root, module_rel) 26 | res = path_to_resource(proj, abs_path) 27 | renamer = Rename(proj, res, offset=None) 28 | # Run in thread to avoid blocking loop 29 | def _do() -> bool: 30 | try: 31 | changes = renamer.get_changes(new_name) 32 | proj.do(changes) 33 | return True 34 | except Exception: 35 | return False 36 | ok = await asyncio.to_thread(_do) 37 | proj.close() 38 | return ok 39 | except Exception: 40 | try: 41 | proj.close() 42 | except Exception: 43 | pass 44 | return False 45 | except Exception: 46 | return False 47 | -------------------------------------------------------------------------------- /jinx/micro/text/num_format.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from math import isfinite 4 | 5 | 6 | def format_with_separators(n: int) -> str: 7 | """Format integer with digit group separators (en-US style).""" 8 | try: 9 | return f"{int(n):,}" 10 | except Exception: 11 | return str(n) 12 | 13 | 14 | def _format_scaled(n: int, scale: int, frac_digits: int) -> str: 15 | # Round to requested fractional digits 16 | value = n / float(scale) 17 | if not isfinite(value): 18 | value = 0.0 19 | fmt = f"{{:.{frac_digits}f}}" 20 | return fmt.format(round(value, frac_digits)) 21 | 22 | 23 | def format_si_suffix(n: int) -> str: 24 | """Format token counts with K/M/G suffixes to ~3 significant figures. 25 | 26 | Examples: 27 | - 999 -> "999" 28 | - 1200 -> "1.20K" 29 | - 123456789 -> "123M" 30 | """ 31 | n = max(0, int(n)) 32 | if n < 1000: 33 | return format_with_separators(n) 34 | 35 | UNITS = [(1_000, "K"), (1_000_000, "M"), (1_000_000_000, "G")] 36 | f = float(n) 37 | for scale, suffix in UNITS: 38 | if round(100.0 * f / scale) < 1000: 39 | return f"{_format_scaled(n, scale, 2)}{suffix}" 40 | elif round(10.0 * f / scale) < 1000: 41 | return f"{_format_scaled(n, scale, 1)}{suffix}" 42 | elif round(f / scale) < 1000: 43 | return f"{_format_scaled(n, scale, 0)}{suffix}" 44 | 45 | # Above 1000G: keep whole‑G precision. 46 | from math import floor 47 | 48 | g_val = int(round(f / 1e9)) 49 | return f"{format_with_separators(g_val)}G" 50 | -------------------------------------------------------------------------------- /jinx/codeexec/validators/io_clamps.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | import ast 5 | 6 | from .ast_cache import get_ast 7 | from .config import is_enabled, IO_MAX_LOOP_BODY_LINES as _MAX_LOOP_BODY_LINES, IO_MAX_LITERAL_ELEMS as _MAX_LIST_LITERAL_ELEMS 8 | 9 | 10 | def check_io_clamps(code: str) -> Optional[str]: 11 | """Clamp obviously runaway patterns to preserve RT constraints. 12 | 13 | Flags: 14 | - loops whose body spans too many lines 15 | - gigantic list/dict/set literals (likely model dumping data) 16 | """ 17 | if not is_enabled("io_clamps", True): 18 | return None 19 | t = get_ast(code) 20 | if not t: 21 | return None 22 | 23 | def _span(n: ast.AST) -> int: 24 | a = int(getattr(n, "lineno", 0) or 0) 25 | b = int(getattr(n, "end_lineno", a) or a) 26 | return max(0, b - a) 27 | 28 | for n in ast.walk(t): 29 | if isinstance(n, (ast.For, ast.AsyncFor, ast.While)): 30 | body_span = sum(_span(ch) for ch in (n.body or [])) 31 | if body_span > _MAX_LOOP_BODY_LINES: 32 | return f"loop body too large: ~{body_span} lines" 33 | if isinstance(n, (ast.List, ast.Set, ast.Tuple)): 34 | if len(getattr(n, "elts", []) or []) > _MAX_LIST_LITERAL_ELEMS: 35 | return f"literal too large: ~{len(n.elts)} elements" 36 | if isinstance(n, ast.Dict): 37 | if len(getattr(n, "keys", []) or []) > _MAX_LIST_LITERAL_ELEMS: 38 | return f"dict literal too large: ~{len(n.keys)} keys" 39 | return None 40 | -------------------------------------------------------------------------------- /jinx/micro/runtime/patcher_handlers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Dict, List, Callable, Awaitable 4 | 5 | from jinx.micro.runtime.patch import AutoPatchArgs 6 | from jinx.micro.runtime.handlers import ( 7 | handle_write as handle_write, 8 | handle_line_patch as handle_line_patch, 9 | handle_symbol_patch as handle_symbol_patch, 10 | handle_anchor_patch as handle_anchor_patch, 11 | handle_regex_patch as handle_regex_patch, 12 | handle_find_replace as handle_find_replace, 13 | handle_auto_patch as handle_auto_patch, 14 | handle_batch_patch as handle_batch_patch, 15 | handle_dump_symbol as handle_dump_symbol, 16 | handle_dump_by_query as handle_dump_by_query, 17 | handle_dump_by_query_global as handle_dump_by_query_global, 18 | handle_refactor_move_symbol as handle_refactor_move_symbol, 19 | handle_refactor_split_file as handle_refactor_split_file, 20 | ) 21 | 22 | # Thin delegator for backward compatibility. All logic now lives under 23 | # jinx/micro/runtime/handlers/ micro-modules. 24 | 25 | VerifyCB = Callable[[str | None, List[str], str], Awaitable[None]] 26 | 27 | __all__ = [ 28 | "AutoPatchArgs", 29 | "handle_write", 30 | "handle_line_patch", 31 | "handle_symbol_patch", 32 | "handle_anchor_patch", 33 | "handle_regex_patch", 34 | "handle_find_replace", 35 | "handle_auto_patch", 36 | "handle_batch_patch", 37 | "handle_dump_symbol", 38 | "handle_dump_by_query", 39 | "handle_dump_by_query_global", 40 | "handle_refactor_move_symbol", 41 | "handle_refactor_split_file", 42 | ] 43 | -------------------------------------------------------------------------------- /jinx/tools/embeddings_smoke.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import json 5 | import sys 6 | 7 | 8 | async def main() -> int: 9 | ok = True 10 | try: 11 | from jinx.micro.embeddings.pipeline import embed_text 12 | obj = await embed_text("hello embeddings", source="smoke", kind="line") 13 | print("embed_text: ", bool(obj and isinstance(obj, dict) and obj.get("embedding"))) 14 | except Exception as e: 15 | print("embed_text: EXC", e) 16 | ok = False 17 | try: 18 | from jinx.micro.embeddings.vector_stage_semantic import semantic_search 19 | hits = await semantic_search("fastapi router", k=3, max_time_ms=200) 20 | print("semantic_search: ", len(hits)) 21 | except Exception as e: 22 | print("semantic_search: EXC", e) 23 | ok = False 24 | try: 25 | from jinx.micro.embeddings.retrieval_core import retrieve_project_top_k 26 | r = await retrieve_project_top_k("create endpoint", k=3, max_time_ms=250) 27 | print("retrieval_core: ", len(r)) 28 | except Exception as e: 29 | print("retrieval_core: EXC", e) 30 | ok = False 31 | try: 32 | from jinx.micro.embeddings.unified_context import build_unified_context_for 33 | ctx = await build_unified_context_for("implement POST /users", max_time_ms=350) 34 | print("unified_context: ", len(ctx)) 35 | except Exception as e: 36 | print("unified_context: EXC", e) 37 | ok = False 38 | return 0 if ok else 1 39 | 40 | 41 | if __name__ == "__main__": 42 | sys.exit(asyncio.run(main())) 43 | -------------------------------------------------------------------------------- /jinx/kernel.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """ 4 | Kernel boot for Jinx. 5 | 6 | Performs minimal, safe, synchronous startup steps before the async runtime loop: 7 | - Install import resilience for internal modules (stubs instead of crashes) 8 | - Load environment from .env 9 | - Apply autonomous defaults (no user config required) 10 | - Prewarm OpenAI HTTP client (connection pools) 11 | 12 | These steps are side-effect–light and idempotent. 13 | """ 14 | 15 | def boot() -> None: 16 | # 1) Import resilience (safe stubs for missing jinx.* modules) 17 | try: 18 | from jinx.micro.runtime.resilience import install_resilience as _install 19 | _install() 20 | except Exception: 21 | pass 22 | 23 | # 2) Load .env early so downstream imports see env 24 | try: 25 | from jinx.bootstrap import load_env 26 | load_env() 27 | except Exception: 28 | pass 29 | 30 | # 3) Apply autonomous defaults (env-based, synchronous) 31 | try: 32 | from jinx.micro.runtime.autoconfig import apply_auto_defaults as _auto 33 | _auto(None) 34 | except Exception: 35 | pass 36 | 37 | # 4) Prewarm OpenAI client (synchronous, cheap) 38 | try: 39 | from jinx.micro.net.client import prewarm_openai_client as _prewarm 40 | _prewarm() 41 | except Exception: 42 | pass 43 | # 5) Optional OTEL setup (no-op if not installed or disabled) 44 | try: 45 | from jinx.observability.setup import setup_otel as _setup_otel 46 | _setup_otel() 47 | except Exception: 48 | pass 49 | 50 | __all__ = ["boot"] 51 | -------------------------------------------------------------------------------- /jinx/micro/common/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import Optional 5 | 6 | __all__ = [ 7 | "truthy", 8 | "get_int", 9 | "get_float", 10 | "get_str", 11 | ] 12 | 13 | 14 | def truthy(name: str, default: str | int | float = "1") -> bool: 15 | try: 16 | val = os.getenv(name, str(default)) 17 | return str(val).strip().lower() not in ("", "0", "false", "off", "no", "none") 18 | except Exception: 19 | return True 20 | 21 | 22 | def get_int(name: str, default: int | str, *, min_val: Optional[int] = None, max_val: Optional[int] = None) -> int: 23 | try: 24 | v = int(os.getenv(name, str(default))) 25 | except Exception: 26 | v = int(default) if not isinstance(default, str) else int(default or 0) 27 | if min_val is not None and v < min_val: 28 | v = min_val 29 | if max_val is not None and v > max_val: 30 | v = max_val 31 | return v 32 | 33 | 34 | def get_float(name: str, default: float | str, *, min_val: Optional[float] = None, max_val: Optional[float] = None) -> float: 35 | try: 36 | v = float(os.getenv(name, str(default))) 37 | except Exception: 38 | v = float(default) if not isinstance(default, str) else float(default or 0.0) 39 | if min_val is not None and v < min_val: 40 | v = min_val 41 | if max_val is not None and v > max_val: 42 | v = max_val 43 | return v 44 | 45 | 46 | def get_str(name: str, default: str = "") -> str: 47 | try: 48 | return str(os.getenv(name, default)) 49 | except Exception: 50 | return str(default or "") 51 | -------------------------------------------------------------------------------- /jinx/micro/core/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json as _json 4 | import logging as _log 5 | import os as _os 6 | import random as _rand 7 | from typing import Any 8 | 9 | __all__ = [ 10 | "backoff", 11 | "error_or_panic", 12 | "try_parse_error_message", 13 | ] 14 | 15 | _INITIAL_DELAY_MS: float = 200.0 16 | _BACKOFF_FACTOR: float = 2.0 17 | 18 | 19 | def backoff(attempt: int) -> float: 20 | """Exponential backoff with jitter. Returns delay in seconds. 21 | 22 | attempt >= 1 23 | """ 24 | a = max(1, int(attempt)) 25 | base_ms = _INITIAL_DELAY_MS * (_BACKOFF_FACTOR ** (a - 1)) 26 | jitter = _rand.uniform(0.9, 1.1) 27 | return (base_ms * jitter) / 1000.0 28 | 29 | 30 | def error_or_panic(message: str) -> None: 31 | """Log error or raise in debug/alpha modes. 32 | 33 | Controlled via env: 34 | JINX_PANIC_ON_ERROR=1 -> raise RuntimeError 35 | """ 36 | if str(_os.getenv("JINX_PANIC_ON_ERROR", "")).strip().lower() in {"1", "true", "yes", "on"}: 37 | raise RuntimeError(message) 38 | _log.error(message) 39 | 40 | 41 | def try_parse_error_message(text: str) -> str: 42 | """Extract error.message from JSON string; otherwise return input or a default.""" 43 | try: 44 | obj = _json.loads(text or "{}") 45 | err = obj.get("error") if isinstance(obj, dict) else None 46 | if isinstance(err, dict): 47 | msg = err.get("message") 48 | if isinstance(msg, str) and msg: 49 | return msg 50 | except Exception: 51 | pass 52 | if not text: 53 | return "Unknown error" 54 | return text 55 | -------------------------------------------------------------------------------- /jinx/micro/app_server/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import asdict 4 | from typing import List, Optional 5 | 6 | from jinx.micro.protocol.common import AuthMode 7 | from jinx.micro.protocol.v2 import Model, ReasoningEffortOption 8 | 9 | 10 | def supported_models(auth_mode: Optional[AuthMode]) -> List[Model]: 11 | """ 12 | Return the list of supported models for the given auth mode. 13 | 14 | Uses a small built-in set; no external configuration is required. 15 | """ 16 | # Fallback defaults 17 | return [ 18 | Model( 19 | id="gpt-4o-mini", 20 | model="gpt-4o-mini", 21 | display_name="GPT-4o Mini", 22 | description="Fast general-purpose reasoning model.", 23 | supported_reasoning_efforts=[ 24 | ReasoningEffortOption(reasoning_effort="low", description="Low effort"), 25 | ReasoningEffortOption(reasoning_effort="medium", description="Balanced"), 26 | ], 27 | default_reasoning_effort="low", 28 | is_default=True, 29 | ), 30 | Model( 31 | id="gpt-4o", 32 | model="gpt-4o", 33 | display_name="GPT-4o", 34 | description="High quality reasoning model.", 35 | supported_reasoning_efforts=[ 36 | ReasoningEffortOption(reasoning_effort="medium", description="Balanced"), 37 | ReasoningEffortOption(reasoning_effort="high", description="Maximum effort"), 38 | ], 39 | default_reasoning_effort="medium", 40 | is_default=False, 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /jinx/rag_service.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Dict 4 | 5 | # Facade that delegates to micro-module implementation. 6 | # Keep the old import path stable for callers. 7 | from jinx.micro.rag.file_search import ( 8 | ENV_OPENAI_VECTOR_STORE_ID, 9 | ENV_OPENAI_FORCE_FILE_SEARCH, 10 | build_file_search_tools as _build_file_search_tools, 11 | ) 12 | from jinx.settings import Settings 13 | 14 | 15 | def build_file_search_tools() -> Dict[str, Any]: 16 | """Compatibility facade for File Search tool binding. 17 | 18 | Delegates to ``jinx.micro.rag.file_search.build_file_search_tools`` to keep 19 | the legacy import location stable while the logic lives in the micro-module. 20 | """ 21 | return _build_file_search_tools() 22 | 23 | 24 | def build_file_search_tools_from_settings(settings: Settings) -> Dict[str, Any]: 25 | """Construct File Search tool kwargs directly from Settings. 26 | 27 | Mirrors the micro-module layout while avoiding env access at call sites. 28 | """ 29 | ids = settings.openai.vector_store_ids 30 | if not ids: 31 | return {} 32 | extra: Dict[str, Any] = { 33 | "tools": [ 34 | { 35 | "type": "file_search", 36 | "vector_store_ids": ids, 37 | } 38 | ] 39 | } 40 | if settings.openai.force_file_search: 41 | extra["tool_choice"] = {"type": "file_search"} 42 | return extra 43 | 44 | 45 | __all__ = [ 46 | "ENV_OPENAI_VECTOR_STORE_ID", 47 | "ENV_OPENAI_FORCE_FILE_SEARCH", 48 | "build_file_search_tools", 49 | "build_file_search_tools_from_settings", 50 | ] 51 | -------------------------------------------------------------------------------- /jinx/conversation/orchestrator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | """Conversation orchestrator facade. 4 | 5 | Thin wrapper delegating to the micro-module implementation under 6 | ``jinx.micro.conversation.orchestrator`` to keep the public API stable. 7 | """ 8 | 9 | from typing import Optional 10 | from jinx.micro.conversation.orchestrator import ( 11 | shatter as _shatter, 12 | corrupt_report as _corrupt_report, 13 | ) 14 | 15 | 16 | async def shatter(x: str, err: Optional[str] = None) -> None: 17 | from jinx.micro.logger.debug_logger import debug_log 18 | await debug_log(f"shatter called with: {x[:80]}", "WRAPPER") 19 | # Record conversation request 20 | try: 21 | from jinx.micro.runtime.crash_diagnostics import start_operation, end_operation 22 | start_operation(f"conversation: {x[:50]}") 23 | except Exception: 24 | pass 25 | 26 | try: 27 | await debug_log("Calling _shatter...", "WRAPPER") 28 | result = await _shatter(x, err) 29 | await debug_log("_shatter returned successfully", "WRAPPER") 30 | 31 | # Record success 32 | try: 33 | end_operation(success=True) 34 | except Exception: 35 | pass 36 | 37 | return result 38 | except Exception as e: 39 | await debug_log(f"_shatter raised exception: {e}", "WRAPPER") 40 | # Record failure 41 | try: 42 | end_operation(success=False, error=str(e)) 43 | except Exception: 44 | pass 45 | raise 46 | 47 | 48 | async def corrupt_report(err: Optional[str]) -> None: 49 | return await _corrupt_report(err) 50 | -------------------------------------------------------------------------------- /jinx/micro/memory/telemetry.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import json 5 | import time 6 | import asyncio 7 | from typing import Any, Dict 8 | 9 | from jinx.micro.memory.storage import memory_dir 10 | 11 | 12 | _METRICS_FILE = os.path.join(memory_dir(), "metrics.jsonl") 13 | 14 | 15 | def _append_line_sync(path: str, line: str) -> None: 16 | try: 17 | os.makedirs(os.path.dirname(path), exist_ok=True) 18 | with open(path, "a", encoding="utf-8") as f: 19 | f.write(line + "\n") 20 | except Exception: 21 | pass 22 | 23 | 24 | async def log_metric(kind: str, payload: Dict[str, Any]) -> None: 25 | """Append a single JSON metric line. Best-effort, non-blocking. 26 | 27 | This function never raises. 28 | """ 29 | obj = {"ts_ms": int(time.time() * 1000), "kind": kind, **(payload or {})} 30 | try: 31 | line = json.dumps(obj, ensure_ascii=False) 32 | except Exception: 33 | # Fallback minimal serialization 34 | line = json.dumps({"ts_ms": obj.get("ts_ms"), "kind": kind}) 35 | try: 36 | await asyncio.to_thread(_append_line_sync, _METRICS_FILE, line) 37 | except Exception: 38 | pass 39 | 40 | 41 | async def log_memroute_event(stage: str, count: int, elapsed_ms: float, k: int, max_ms: float) -> None: 42 | """Convenience wrapper for memroute stage metrics.""" 43 | await log_metric("memroute_stage", { 44 | "stage": str(stage or ""), 45 | "added": int(max(0, int(count or 0))), 46 | "elapsed_ms": float(max(0.0, elapsed_ms)), 47 | "k": int(max(0, k)), 48 | "max_ms": float(max(0.0, max_ms)), 49 | }) 50 | -------------------------------------------------------------------------------- /jinx/micro/runtime/self_update_journal.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | import time 6 | from typing import Any, Dict 7 | 8 | 9 | def _journal_dir() -> str: 10 | d = os.path.join(os.getcwd(), ".jinx", "selfupdate") 11 | os.makedirs(d, exist_ok=True) 12 | return d 13 | 14 | 15 | essential_keys = ("event", "ts", "stage", "ok") 16 | 17 | 18 | def append(event: str, *, stage: str, ok: bool | None = None, **meta: Any) -> None: 19 | """Append a compact JSON line to the self-update journal. 20 | Never raises. 21 | """ 22 | try: 23 | rec: Dict[str, Any] = { 24 | "event": str(event), 25 | "ts": time.time(), 26 | "stage": stage, 27 | } 28 | if ok is not None: 29 | rec["ok"] = bool(ok) 30 | if meta: 31 | # Keep JSON serializable; fall back to string 32 | safe_meta: Dict[str, Any] = {} 33 | for k, v in meta.items(): 34 | try: 35 | json.dumps(v) 36 | safe_meta[k] = v 37 | except Exception: 38 | safe_meta[k] = str(v) 39 | rec.update(safe_meta) 40 | path = os.path.join(_journal_dir(), "journal.jsonl") 41 | with open(path, "a", encoding="utf-8") as f: 42 | f.write(json.dumps(rec, ensure_ascii=False) + "\n") 43 | # Update pointer to last record 44 | with open(os.path.join(_journal_dir(), "last.json"), "w", encoding="utf-8") as f2: 45 | json.dump(rec, f2, ensure_ascii=False) 46 | except Exception: 47 | return 48 | 49 | 50 | __all__ = ["append"] 51 | -------------------------------------------------------------------------------- /jinx/micro/common/user_notification.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import subprocess 5 | from dataclasses import dataclass 6 | from typing import List, Optional, Sequence 7 | 8 | __all__ = [ 9 | "UserNotifier", 10 | "UserNotification", 11 | ] 12 | 13 | 14 | @dataclass(frozen=True) 15 | class UserNotification: 16 | type: str 17 | thread_id: str 18 | turn_id: str 19 | cwd: str 20 | input_messages: List[str] 21 | last_assistant_message: Optional[str] = None 22 | 23 | def to_json(self) -> str: 24 | # Serialize with kebab-case keys to mirror Codex payload 25 | payload = { 26 | "type": self.type, 27 | "thread-id": self.thread_id, 28 | "turn-id": self.turn_id, 29 | "cwd": self.cwd, 30 | "input-messages": self.input_messages, 31 | } 32 | if self.last_assistant_message is not None: 33 | payload["last-assistant-message"] = self.last_assistant_message 34 | return json.dumps(payload, ensure_ascii=False) 35 | 36 | 37 | class UserNotifier: 38 | def __init__(self, notify_command: Optional[Sequence[str]] = None) -> None: 39 | self._cmd = list(notify_command) if notify_command else None 40 | 41 | def notify(self, notification: UserNotification) -> None: 42 | if not self._cmd: 43 | return 44 | try: 45 | argv = self._cmd[:] 46 | argv.append(notification.to_json()) 47 | # Fire-and-forget; do not wait 48 | subprocess.Popen(argv, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 49 | except Exception: 50 | # best-effort only 51 | pass 52 | -------------------------------------------------------------------------------- /jinx/micro/common/repair_utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Iterable, Optional 4 | import re 5 | 6 | # Utilities to parse missing internal module errors and schedule repairs. 7 | 8 | _PAT_NO_MODULE = re.compile(r"No module named '([^']+)'", re.IGNORECASE) 9 | _PAT_CANNOT_IMPORT = re.compile(r"cannot import name .* from '([^']+)'", re.IGNORECASE) 10 | 11 | 12 | def parse_missing_jinx_modules(text: str) -> List[str]: 13 | mods: List[str] = [] 14 | s = (text or "").strip() 15 | if not s: 16 | return mods 17 | for m in _PAT_NO_MODULE.finditer(s): 18 | name = (m.group(1) or '').strip() 19 | if name.startswith("jinx.") and name not in mods: 20 | mods.append(name) 21 | for m in _PAT_CANNOT_IMPORT.finditer(s): 22 | name = (m.group(1) or '').strip() 23 | if name.startswith("jinx.") and name not in mods: 24 | mods.append(name) 25 | return mods 26 | 27 | 28 | async def schedule_repairs_for(mods: Iterable[str]) -> None: 29 | items = [m for m in (mods or []) if isinstance(m, str) and m.startswith("jinx.")] 30 | if not items: 31 | return 32 | try: 33 | from jinx.micro.runtime.api import submit_task as _submit 34 | except Exception: 35 | return 36 | for m in items: 37 | try: 38 | await _submit("repair.import_missing", module=m) 39 | except Exception: 40 | pass 41 | 42 | 43 | async def maybe_schedule_repairs_from_error(err: object) -> None: 44 | try: 45 | s = str(err) 46 | except Exception: 47 | s = "" 48 | mods = parse_missing_jinx_modules(s) 49 | if mods: 50 | await schedule_repairs_for(mods) 51 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_stage_keyword.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import time 4 | from typing import Any, Dict, List, Tuple 5 | import re as _re 6 | 7 | from .project_scan_store import iter_project_chunks 8 | from .project_retrieval_config import ( 9 | PROJ_MAX_FILES, 10 | PROJ_MAX_CHUNKS_PER_FILE, 11 | ) 12 | 13 | 14 | def stage_keyword_hits(query: str, k: int, *, max_time_ms: int | None = 250) -> List[Tuple[float, str, Dict[str, Any]]]: 15 | """Stage 2: lightweight keyword/substring fallback over preview, terms, and path. 16 | 17 | Returns a list of (score, file_rel, obj) sorted by score desc. 18 | """ 19 | q = (query or "").strip() 20 | if not q: 21 | return [] 22 | toks = [t.lower() for t in _re.findall(r"(?u)[\w\.]+", q) if len(t) >= 2] 23 | if not toks: 24 | return [] 25 | t0 = time.perf_counter() 26 | scored: List[Tuple[float, str, Dict[str, Any]]] = [] 27 | for file_rel, obj in iter_project_chunks(max_files=PROJ_MAX_FILES, max_chunks_per_file=PROJ_MAX_CHUNKS_PER_FILE): 28 | meta = obj.get("meta", {}) 29 | pv = (meta.get("text_preview") or "").lower() 30 | terms = [str(x).lower() for x in (meta.get("terms") or [])] 31 | hay = " ".join([pv, str(file_rel).lower(), " ".join(terms)]) 32 | hits = sum(1 for t in toks if t in hay) 33 | if hits <= 0: 34 | continue 35 | s = 0.18 + min(0.04 * hits, 0.20) 36 | scored.append((s, str(meta.get("file_rel") or file_rel or ""), obj)) 37 | if max_time_ms is not None and (time.perf_counter() - t0) * 1000.0 > max_time_ms: 38 | break 39 | scored.sort(key=lambda x: x[0], reverse=True) 40 | return scored[:k] 41 | -------------------------------------------------------------------------------- /jinx/micro/embeddings/project_tasks.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import os 5 | from typing import Dict 6 | 7 | from .project_paths import PROJECT_INDEX_DIR, safe_rel_path 8 | from .project_hashdb import set_record, get_record 9 | from .project_util import sha256_path 10 | from .project_pipeline import embed_file 11 | from .project_artifacts import artifacts_exist_for_rel 12 | 13 | 14 | async def embed_if_changed( 15 | db: Dict[str, Dict[str, object]], 16 | abs_p: str, 17 | rel_p: str, 18 | *, 19 | sem: asyncio.Semaphore, 20 | ) -> bool: 21 | """Embed a file if it's changed or artifacts are missing. 22 | 23 | Returns True if the hash DB was mutated (embedded or metadata updated). 24 | """ 25 | try: 26 | st = os.stat(abs_p) 27 | except FileNotFoundError: 28 | return False 29 | mtime = float(st.st_mtime) 30 | rec = get_record(db, rel_p) or {} 31 | prev_m = float(rec.get("mtime", 0.0) or 0.0) 32 | 33 | # Ensure artifacts exist; if not, force embed even if unchanged 34 | artifacts_ok = artifacts_exist_for_rel(rel_p) 35 | if prev_m >= mtime and rec.get("sha") and artifacts_ok: 36 | return False 37 | 38 | sha = sha256_path(abs_p) 39 | if not sha: 40 | return False 41 | 42 | prev_sha = str(rec.get("sha") or "") 43 | need_embed = (prev_sha != sha) or (not artifacts_ok) 44 | if need_embed: 45 | async with sem: 46 | await embed_file(abs_p, rel_p, file_sha=sha) 47 | set_record(db, rel_p, sha=sha, mtime=mtime) 48 | return True 49 | # No embedding needed; update mtimes for accuracy 50 | set_record(db, rel_p, sha=sha, mtime=mtime) 51 | return True 52 | -------------------------------------------------------------------------------- /jinx/micro/brain/scanners/imports.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import asyncio 5 | from typing import Dict, Any 6 | 7 | from jinx.micro.embeddings.project_paths import PROJECT_INDEX_DIR 8 | 9 | 10 | async def _read_json(path: str) -> Dict[str, Any]: 11 | try: 12 | def _load() -> Dict[str, Any]: 13 | import json 14 | with open(path, "r", encoding="utf-8") as f: 15 | obj = json.load(f) 16 | return obj if isinstance(obj, dict) else {} 17 | return await asyncio.to_thread(_load) 18 | except Exception: 19 | return {} 20 | 21 | 22 | async def scan_import_graph() -> Dict[str, float]: 23 | nodes: Dict[str, float] = {} 24 | try: 25 | entries = [os.path.join(PROJECT_INDEX_DIR, f) for f in os.listdir(PROJECT_INDEX_DIR) if f.endswith('.json')] 26 | except Exception: 27 | entries = [] 28 | cnt = 0 29 | for p in entries: 30 | cnt += 1 31 | if cnt > 400: 32 | break 33 | try: 34 | obj = await _read_json(p) 35 | imports = obj.get('imports') or [] 36 | if isinstance(imports, dict): 37 | imports = list(imports.keys()) 38 | imp_list = list(imports)[:64] 39 | for imp in imp_list: 40 | try: 41 | mod = str(imp or '').strip().split('.')[0].lower() 42 | if mod: 43 | nodes[f"import: {mod}"] = nodes.get(f"import: {mod}", 0.0) + 0.5 44 | except Exception: 45 | continue 46 | except Exception: 47 | continue 48 | return nodes 49 | 50 | 51 | __all__ = ["scan_import_graph"] 52 | -------------------------------------------------------------------------------- /jinx/micro/core/error.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import jinx.state as jx_state 5 | 6 | 7 | async def dec_pulse(amount: int) -> None: 8 | """Decrease global pulse. 9 | 10 | Soft mode (default): when pulse falls to <= 0, assert throttle_event to pause 11 | new conversation processing instead of shutting the runtime down. Hard 12 | shutdown can be enabled via JINX_PULSE_HARD_SHUTDOWN=true. 13 | 14 | Parameters 15 | ---------- 16 | amount : int 17 | Amount to subtract from the current pulse. 18 | """ 19 | jx_state.pulse -= amount 20 | if jx_state.pulse <= 0: 21 | jx_state.pulse = 0 22 | hard = str(os.getenv("JINX_PULSE_HARD_SHUTDOWN", "0")).strip().lower() in {"1", "true", "yes", "on"} 23 | 24 | # Record pulse exhaustion 25 | try: 26 | from jinx.micro.runtime.crash_diagnostics import record_operation 27 | record_operation( 28 | "pulse_exhausted", 29 | details={'hard_shutdown': hard, 'amount': amount}, 30 | success=not hard 31 | ) 32 | except Exception: 33 | pass 34 | 35 | if hard: 36 | # Signal the runtime to shut down gracefully 37 | jx_state.shutdown_event.set() 38 | else: 39 | # Enter throttled mode; a supervisor task should clear this after cooldown 40 | jx_state.throttle_event.set() 41 | 42 | 43 | async def inc_pulse(amount: int) -> None: 44 | """Increase global pulse by ``amount``. 45 | 46 | Parameters 47 | ---------- 48 | amount : int 49 | Amount to add to the current pulse. 50 | """ 51 | jx_state.pulse += amount 52 | --------------------------------------------------------------------------------