Codebase audit · KISS Sorcar

Does src/kiss/core/ depend on anything outside itself?

A file-by-file check of all 22 modules in the core package, using both a static parse of every import statement and a runtime check that watches what actually gets loaded.

Scope: src/kiss/core/ — 22 Python files, 12,182 lines.

No.

Not one module in src/kiss/core/ imports anything from kiss.* outside kiss.core. There are no relative imports that climb above the package either. Core is a self-contained foundation layer — everything else in the repository depends on it, and nothing flows back. Three couplings do exist, but they go through the file system, not through imports; they are listed near the end of this report.

22Python modules audited
12,182lines of code
0imports leaving kiss.core
284files elsewhere that import core

Why two methods, not one

A plain grep for import is not enough here, for two reasons. First, this codebase puts a lot of imports inside function bodies — 17 of them — either to break circular references or to avoid paying the cost of loading a heavy LLM SDK at startup. A naive scan of the top of each file would miss all of them. Second, Python can import by string at runtime through importlib.import_module(), which no text search can follow.

So the audit was done twice, from opposite directions.

Method 1 — parse every import node

Each file was parsed into a syntax tree and walked in full, collecting every import x and from x import y node wherever it appeared: module level, inside functions, inside classes, and under if TYPE_CHECKING: guards.

=== KISS imports OUTSIDE kiss.core ===
NONE

Method 2 — watch what actually loads

Each of the 22 modules was imported in a fresh subprocess, and sys.modules was then inspected for any kiss.* entry that did not begin with kiss.core. This catches dynamic imports that method 1 cannot see.

modules checked:                 22
non-core kiss modules loaded:  NONE

The only dynamic import in the package is the lazy __getattr__ in models/__init__.py. It is safe by construction: its _LAZY_IMPORTS table maps names only to kiss.core.models.* targets, so it cannot reach outside even in principle.

Which way the dependencies point

tests · 243 agents · 18 server · 11 ui · 6 scripts · 3 src/kiss/core/ 22 modules · 12,182 lines · 0 outbound kiss imports Python standard library 9 third-party packages
Everything points inward. 284 files across the repository import kiss.core; core itself reaches only downward, to the standard library and a short list of declared third-party dependencies.
ConsumerFiles importing kiss.core
src/kiss/tests/243
src/kiss/agents/18
src/kiss/server/11
src/kiss/ui/6
src/kiss/scripts/3
src/kiss/__init__.py1
Total284

What core does depend on

Beyond the standard library, exactly nine third-party packages — all of them declared in pyproject.toml, so there are no undeclared imports hiding in the package.

PackageUsed by
pydantic, pydantic-settingsconfig.py, config_builder.py
pyyamlbase.py, printer.py, utils.py
richprint_to_console.py, html_render.py
markdown-it-pyutils.py
openaimodels/model.py, both openai_compatible_model*.py
anthropic, httpxmodels/anthropic_model.py
google-genaimodels/gemini_model.py

The internal graph

Every edge below stays inside the package. kiss_error.py and html_render.py import nothing from the project at all — they are pure leaves.

ModuleLinesImports from within core
__init__.py35config, kiss_error
_version.py5
base.py191models.model_info, print_to_console, printer, utils
config.py158
config_builder.py193config
html_render.py186(leaf)
kiss_agent.py724base, kiss_error, models.model, models.model_info, printer, utils
kiss_error.py70(leaf)
models/__init__.py46models.model
models/anthropic_model.py1,008kiss_error, models.model, models.model_info
models/claude_code_model.py536kiss_error, models.model
models/codex_model.py476kiss_error, models.model
models/gemini_model.py697kiss_error, models.model
models/model.py1,214kiss_error
models/model_info.py1,228kiss_error, models, models.codex_model, models.model
models/openai_compatible_model.py1,514kiss_error, models.model, models.model_info, models.openai_compatible_model2
models/openai_compatible_model2.py2,422kiss_error, models.model, models.openai_compatible_model
print_to_console.py407html_render, printer
printer.py172models.model
speech_synthesis.py170kiss_agent
utils.py158config
vscode_config.py572config

The two openai_compatible_model files import each other. That cycle is deliberate and is kept from exploding by putting the back-reference inside a function body rather than at module level — the same trick used by base.set_printer() and printer.truncate_result().

Three couplings that are not imports

Import-independence is not the same as total independence. Three places in core reach outside the directory through the file system. None of them touch repository code outside core/, but all three would bite anyone trying to lift the package out on its own.

1. Core reads a file from its parent package, at import time

base.py loads the system prompt as a side effect of being imported:

_kiss_pkg_dir = Path(__file__).parent.parent          # -> src/kiss/
SYSTEM_PROMPT = (_kiss_pkg_dir / "SYSTEM.md").read_text(encoding="utf-8")

Move core/ somewhere without src/kiss/SYSTEM.md next to it and import kiss.core.base raises FileNotFoundError immediately. This is the one genuine hard coupling to the parent package.

2. Core assumes where the repository root is

config.py counts directories upward to locate the project:

_PROJECT_DIR = Path(__file__).resolve().parents[3]    # -> repo root

It is used as the default root for .kiss.artifacts. Less severe than the first case, since it can be overridden at runtime with set_artifact_base_dir(), but it still encodes an assumption about the directory layout three levels up.

3. Core touches the user's home directory and external binaries

models/model_info.py reads and auto-seeds ~/.kiss/MY_MODELS.json; vscode_config.py reads and writes $KISS_HOME/config.json; and claude_code_model.py and codex_model.py shell out to the claude and codex command-line tools. These are environmental dependencies rather than code dependencies, but they are dependencies all the same.

Reproducing the audit

Both checks are short enough to paste into a terminal at the repository root.

# Method 1 — every import node, including nested and TYPE_CHECKING ones
python3 - <<'PY'
import ast
from pathlib import Path
root = Path("src/kiss/core")
bad = []
for f in sorted(root.rglob("*.py")):
    tree = ast.parse(f.read_text(encoding="utf-8"))
    for n in ast.walk(tree):
        names = []
        if isinstance(n, ast.Import):
            names = [a.name for a in n.names]
        elif isinstance(n, ast.ImportFrom) and n.module and not n.level:
            names = [n.module]
        for m in names:
            if m.startswith("kiss") and not m.startswith("kiss.core"):
                bad.append((str(f), m))
print("=== KISS imports OUTSIDE kiss.core ===")
print("\n".join(f"{a}: {b}" for a, b in bad) or "NONE")
PY
# Method 2 — import each module in a fresh process, then inspect sys.modules
for m in $(cd src && find kiss/core -name '*.py' \
           | sed 's#/__init__\.py$##; s#\.py$##; s#/#.#g' | sort -u); do
  python3 -c "
import importlib, sys
importlib.import_module('$m')
leaked = [k for k in sys.modules
          if k.startswith('kiss.') and not k.startswith('kiss.core')]
print('$m', leaked or 'clean')
"
done

One suggestion

This clean layering is currently held in place by nothing but habit. A single test that re-runs the syntax-tree scan and asserts the result is empty would freeze the property permanently, and would fail loudly the first time someone writes from kiss.agents… import … inside core. It costs about fifteen lines.