Codebase audit · KISS Sorcar
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.
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.
kiss.coreA 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.
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
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.
kiss.core; core itself
reaches only downward, to the standard library and a short list of declared third-party dependencies.| Consumer | Files 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__.py | 1 |
| Total | 284 |
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.
| Package | Used by |
|---|---|
pydantic, pydantic-settings | config.py, config_builder.py |
pyyaml | base.py, printer.py, utils.py |
rich | print_to_console.py, html_render.py |
markdown-it-py | utils.py |
openai | models/model.py, both openai_compatible_model*.py |
anthropic, httpx | models/anthropic_model.py |
google-genai | models/gemini_model.py |
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.
| Module | Lines | Imports from within core |
|---|---|---|
__init__.py | 35 | config, kiss_error |
_version.py | 5 | — |
base.py | 191 | models.model_info, print_to_console, printer, utils |
config.py | 158 | — |
config_builder.py | 193 | config |
html_render.py | 186 | — (leaf) |
kiss_agent.py | 724 | base, kiss_error, models.model, models.model_info, printer, utils |
kiss_error.py | 70 | — (leaf) |
models/__init__.py | 46 | models.model |
models/anthropic_model.py | 1,008 | kiss_error, models.model, models.model_info |
models/claude_code_model.py | 536 | kiss_error, models.model |
models/codex_model.py | 476 | kiss_error, models.model |
models/gemini_model.py | 697 | kiss_error, models.model |
models/model.py | 1,214 | kiss_error |
models/model_info.py | 1,228 | kiss_error, models, models.codex_model, models.model |
models/openai_compatible_model.py | 1,514 | kiss_error, models.model, models.model_info, models.openai_compatible_model2 |
models/openai_compatible_model2.py | 2,422 | kiss_error, models.model, models.openai_compatible_model |
print_to_console.py | 407 | html_render, printer |
printer.py | 172 | models.model |
speech_synthesis.py | 170 | kiss_agent |
utils.py | 158 | config |
vscode_config.py | 572 | config |
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().
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.
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.
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.
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.
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
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.