Skip to content

Commit ce33cc5

Browse files
authored
Merge pull request #1 from kurikomi-labs/release-0.2.0
Release 0.2.0 — Phase 7 (Sybil binding, queue, forget) + CI
2 parents 76f5078 + 68bba31 commit ce33cc5

4 files changed

Lines changed: 111 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: CI
2+
3+
# Run the test suite + a packaging check on every push to main and every PR.
4+
# This is the safety net for a public repo: nothing regresses silently, and the
5+
# branch ruleset can require the `test` check before a merge.
6+
7+
on:
8+
push:
9+
branches: [main]
10+
pull_request:
11+
12+
permissions:
13+
contents: read
14+
15+
concurrency:
16+
# cancel superseded runs on the same ref so a rapid push doesn't queue duplicates
17+
group: ci-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
jobs:
21+
test:
22+
name: test (py${{ matrix.python-version }})
23+
runs-on: ubuntu-latest
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
# match pyproject's supported range
28+
python-version: ["3.10", "3.11", "3.12", "3.13"]
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- uses: actions/setup-python@v5
33+
with:
34+
python-version: ${{ matrix.python-version }}
35+
36+
- name: Install (editable + dev + crypto + smart extras)
37+
run: |
38+
python -m pip install --upgrade pip
39+
# Install the optional extras so the signed-pool tests (pynacl), real-hash
40+
# tests (blake3), and semantic tests (sentence-transformers) run as written.
41+
# NOTE: this means CI does NOT currently exercise the zero-dependency
42+
# graceful-degradation paths (blake2b/unsigned/keyword) — several tests
43+
# assume the optional deps are present rather than skipif-guarding their
44+
# absence. Adding a second minimal-install matrix leg to cover the
45+
# degradation paths is a tracked follow-up (see docs/03-roadmap.md).
46+
pip install -e ".[dev,crypto,smart]"
47+
48+
- name: Run tests
49+
run: python -m pytest -q
50+
51+
package:
52+
name: build + twine check
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v4
56+
- uses: actions/setup-python@v5
57+
with:
58+
python-version: "3.12"
59+
- name: Build sdist + wheel and validate metadata
60+
run: |
61+
python -m pip install --upgrade pip build twine
62+
python -m build
63+
python -m twine check dist/*
64+
- name: Verify the runtime prompt ships in the wheel
65+
# guards the packaging bug class we nearly shipped: komi/engine/prompts/distill.md
66+
# is loaded at runtime, so it MUST be inside the wheel or pip installs can't distill.
67+
run: |
68+
python - <<'PY'
69+
import glob, zipfile, sys
70+
whl = glob.glob("dist/*.whl")[0]
71+
names = zipfile.ZipFile(whl).namelist()
72+
assert any(n.endswith("engine/prompts/distill.md") for n in names), \
73+
"distill.md missing from wheel — pip-installed distillation would break"
74+
print("ok: distill.md present in", whl)
75+
PY

docs/03-roadmap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@
6363
removal-PR path.
6464
- 🔜 Plugin-marketplace distribution; docs site. *(external polish.)*
6565
- 🔜 GitHub branch protection on both repos (UI; require the "Verify learnings" check).
66+
- 🔜 CI: add a minimal-install matrix leg (no optional extras) to actually exercise
67+
the zero-dependency degradation paths (blake2b/unsigned/keyword). The current CI
68+
installs `[dev,crypto,smart]` so several signing/embedding tests run as written;
69+
proving the dep-absent paths needs either skipif-guards or a second install
70+
profile. (The engine *is* zero-dep; this is a test-coverage gap, not a code gap.)
6671

6772
## Known gaps / honest notes
6873
- Recall ranking is semantic (embeddings) when the model is installed, keyword FTS otherwise; both feed the same blend + a corroboration bonus.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "komi-learn"
7-
version = "0.1.0"
7+
version = "0.2.0"
88
description = "A continuous, zero-friction learning layer for AI agents — learns about you and improves its own skills across every session, with an anonymized, provenance-verified global knowledge pool."
99
readme = "README.md"
1010
requires-python = ">=3.10"

tests/test_installer.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,26 @@ def home(tmp_path, monkeypatch):
2525

2626
@pytest.fixture
2727
def working_model(monkeypatch):
28-
"""Mock verify_model() to a verified pass, so install proceeds past the gate."""
29-
ok = reqmod.Requirement("model", True, True, "mock model verified")
30-
monkeypatch.setattr(reqmod, "verify_model", lambda **kw: ok)
31-
# doctor and setup import verify_model from this module at call time
32-
return ok
28+
"""Make install proceed past the strict gate, regardless of what's installed on
29+
the test runner. We mock BOTH verify_model() AND collect(): a bare CI runner has
30+
no `claude` CLI (a REQUIRED check), so mocking only the model still leaves the
31+
gate tripped and every post-gate test fails (this is what CI caught). Mocking
32+
collect() to return all-passing required reqs makes these tests hermetic — they
33+
exercise post-gate install behavior, not the host's environment."""
34+
ok_model = reqmod.Requirement("model", True, True, "mock model verified")
35+
monkeypatch.setattr(reqmod, "verify_model", lambda **kw: ok_model)
36+
37+
def _all_pass(*, api_key=None, pool=False):
38+
return [
39+
reqmod.Requirement("python", True, True, "mock python ok"),
40+
reqmod.Requirement("claude-cli", True, True, "mock claude CLI ok"),
41+
ok_model,
42+
reqmod.Requirement("git", True, False, "mock git ok"),
43+
reqmod.Requirement("signing", True, False, "mock signing ok"),
44+
]
45+
monkeypatch.setattr(reqmod, "collect", _all_pass)
46+
# setup.py imports these names from reqmod at call time, so patching reqmod is enough
47+
return ok_model
3348

3449

3550
def _settings(home):
@@ -83,11 +98,16 @@ def test_install_creates_hooks_and_config(home, working_model):
8398

8499

85100
def test_install_uses_absolute_python_path(home, working_model):
101+
import os
86102
setup.install()
87103
s = _settings(home)
88104
cmd = next(h["command"] for e in s["hooks"]["SessionStart"] for h in e["hooks"]
89105
if "komi.adapters" in h["command"])
90-
assert cmd.split()[0] not in ("python", "python3", '"python"')
106+
# the contract is an ABSOLUTE interpreter path (so the hook can't break on a
107+
# PATH mismatch), not merely "not the literal string python". Strip surrounding
108+
# quotes the command may add for paths-with-spaces, then require absoluteness.
109+
interp = cmd.split(" -m ")[0].strip().strip('"')
110+
assert os.path.isabs(interp), f"hook interpreter not absolute: {interp!r}"
91111

92112

93113
def test_install_merges_not_clobbers(home, working_model):
@@ -122,7 +142,10 @@ def test_install_self_heals_stale_hook_command(home, working_model):
122142
cmds = [h["command"] for e in s["hooks"]["SessionStart"] for h in e["hooks"]
123143
if "komi.adapters" in h["command"]]
124144
assert len(cmds) == 1
125-
assert cmds[0].split()[0] not in ("python", "python3")
145+
# the stale bare-`python` command must have been upgraded to an absolute path
146+
import os
147+
interp = cmds[0].split(" -m ")[0].strip().strip('"')
148+
assert os.path.isabs(interp), f"stale command not healed to absolute path: {interp!r}"
126149

127150

128151
def test_install_stores_api_key(home, working_model):

0 commit comments

Comments
 (0)