Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.py text eol=lf
66 changes: 54 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ jobs:
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: pip install -e ".[all,dev]"
- name: Trace synthetic Windows file ownership
if: runner.os == 'Windows'
run: python tests/native/windows-owned-file-diagnostics-v4.py
- name: Run tests (gate — deterministic, no network)
# Issue #426: exclude `network`-marked tests (real HuggingFace model
# downloads) from the gate so a transient HfHubHTTPError can't fail CI
Expand Down Expand Up @@ -115,17 +118,38 @@ jobs:

# Hunter F26: catch packaging regressions (missing py.typed, metadata
# errors, broken README rendering, sdist contents) in CI instead of
# at release time. Runs once on 3.12 only — duplicating across the
# matrix wastes CI minutes without additional signal.
# at release time. Exercise both MCP dependency boundaries on each OS.
build-check:
runs-on: ubuntu-latest
timeout-minutes: 10
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.12"]
mcp-version: [minimum, latest]
pydantic-version: [latest]
include:
- os: ubuntu-latest
python-version: "3.10"
mcp-version: minimum
pydantic-version: latest
# Exercise the MCP/Pydantic floor pair and the release that removed
# eval_type_backport. Other dependencies still use normal resolution.
- os: ubuntu-latest
python-version: "3.10"
mcp-version: minimum
pydantic-version: "2.11.0"
- os: ubuntu-latest
python-version: "3.12"
mcp-version: minimum
pydantic-version: "2.14.0"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python 3.12
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: pyproject.toml
- name: Install build tooling
Expand All @@ -134,10 +158,28 @@ jobs:
run: python -m build
- name: Verify package metadata
run: twine check --strict dist/*
# Issue #654 (M-83): actually install the built wheel and exercise the
# console entry point, so a broken/missing entry-point or import-time
# failure is caught here (not just at release).
- name: Install wheel and smoke-test console script
# Issue #757: an editable install or --help alone does not exercise
# the installed wheel's MCP handshake and serialized tool metadata.
- name: Install wheel and verify MCP compatibility
shell: python
env:
MCP_VERSION: ${{ matrix.mcp-version }}
PYDANTIC_VERSION: ${{ matrix.pydantic-version }}
run: |
pip install dist/*.whl
truememory-mcp --help
import os
from pathlib import Path
import subprocess
import venv

wheel, = Path("dist").glob("*.whl")
env_dir = Path(os.environ["RUNNER_TEMP"]) / "mcp-wheel-env"
venv.EnvBuilder(with_pip=True).create(env_dir)
python = env_dir / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
requirements = [str(wheel.resolve())]
if os.environ["MCP_VERSION"] == "minimum":
requirements.append("mcp[cli]==1.21.1")
if os.environ["PYDANTIC_VERSION"] != "latest":
requirements.append("pydantic==" + os.environ["PYDANTIC_VERSION"])
subprocess.run([str(python), "-m", "pip", "install", *requirements], check=True)
subprocess.run([str(python), "-m", "pip", "check"], check=True)
subprocess.run([str(python), "-I", "tests/mcp-wheel-smoke.py"], check=True)
35 changes: 34 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,17 @@ jobs:

build-check:
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
mcp-version: [minimum, latest]
pydantic-version: [latest]
include:
- mcp-version: minimum
pydantic-version: "2.11.0"
- mcp-version: minimum
pydantic-version: "2.14.0"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
Expand All @@ -46,6 +56,29 @@ jobs:
run: python -m build
- name: Verify package metadata
run: twine check --strict dist/*
- name: Install wheel and verify MCP compatibility
shell: python
env:
MCP_VERSION: ${{ matrix.mcp-version }}
PYDANTIC_VERSION: ${{ matrix.pydantic-version }}
run: |
import os
from pathlib import Path
import subprocess
import venv

wheel, = Path("dist").glob("*.whl")
env_dir = Path(os.environ["RUNNER_TEMP"]) / "mcp-wheel-env"
venv.EnvBuilder(with_pip=True).create(env_dir)
python = env_dir / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
requirements = [str(wheel.resolve())]
if os.environ["MCP_VERSION"] == "minimum":
requirements.append("mcp[cli]==1.21.1")
if os.environ["PYDANTIC_VERSION"] != "latest":
requirements.append("pydantic==" + os.environ["PYDANTIC_VERSION"])
subprocess.run([str(python), "-m", "pip", "install", *requirements], check=True)
subprocess.run([str(python), "-m", "pip", "check"], check=True)
subprocess.run([str(python), "-I", "tests/mcp-wheel-smoke.py"], check=True)

version-check:
runs-on: ubuntu-latest
Expand Down
84 changes: 59 additions & 25 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ if ($LASTEXITCODE -ne 0) {
}

# ---------- step 3: install truememory as a uv tool ----------
Say "installing $PKG_SPEC (~3-5 min on first run, downloads all tier models)..."
Say "installing $PKG_SPEC (~3-5 min on first run)..."
& uv tool uninstall truememory *> $null
if ($LASTEXITCODE -gt 1) {
Warn "uv tool uninstall returned $LASTEXITCODE — proceeding, but result may be partial"
Expand Down Expand Up @@ -118,6 +118,8 @@ if ($uvToolDir) {
}

# ---------- step 4: auto-configure Claude ----------
$setupStatus = "skipped"
$hookStatus = "skipped"
if ($env:TRUEMEMORY_SKIP_SETUP -eq "1") {
Say "skipping Claude setup (TRUEMEMORY_SKIP_SETUP=1)"
} elseif (-not $toolPython) {
Expand All @@ -126,41 +128,52 @@ if ($env:TRUEMEMORY_SKIP_SETUP -eq "1") {
} else {
Say "configuring Claude Code / Claude Desktop..."
& $toolPython -m truememory.mcp_server --setup
if ($LASTEXITCODE -ne 0) {
if ($LASTEXITCODE -eq 0) { $setupStatus = "command succeeded" }
else {
$setupStatus = "failed"
Warn "auto-setup returned non-zero (re-run: python -m truememory.mcp_server --setup)"
}

Say "installing hooks and CLAUDE.md instructions..."
& $toolPython -m truememory.ingest.cli install
if ($LASTEXITCODE -ne 0) {
if ($LASTEXITCODE -eq 0) { $hookStatus = "command succeeded" }
else {
$hookStatus = "failed"
Warn "hook install returned non-zero (re-run: python -m truememory.ingest.cli install)"
}
}

# ---------- step 5: pre-download models for all tiers ----------
Say "pre-downloading models for all tiers (Edge + Base + Pro)..."
Say " this takes 2-5 min but means tier switching just works afterward."
if (Test-Path $toolPython) {
Say " [1/3] Edge reranker (MiniLM-L-6-v2, ~22MB)..."
& $toolPython -c "from sentence_transformers import CrossEncoder; CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')"
if ($LASTEXITCODE -eq 0) { Ok " [1/3] Edge reranker ready" }
else { Warn " [1/3] Edge reranker download failed (search still works without it)" }

Say " [2/3] Base/Pro embedder (Qwen3-Embedding-0.6B, ~1.2GB)..."
& $toolPython -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('Qwen/Qwen3-Embedding-0.6B', truncate_dim=256)"
if ($LASTEXITCODE -eq 0) { Ok " [2/3] Base/Pro embedder ready" }
else { Warn " [2/3] Base/Pro embedder download failed (you can retry later or use Edge tier)" }

Say " [3/3] Base/Pro reranker (gte-modernbert, ~600MB)..."
& $toolPython -c "from sentence_transformers import CrossEncoder; CrossEncoder('Alibaba-NLP/gte-reranker-modernbert-base')"
if ($LASTEXITCODE -eq 0) { Ok " [3/3] Base/Pro reranker ready" }
else { Warn " [3/3] Base/Pro reranker download failed (you can retry later or use Edge tier)" }

Ok "all models pre-downloaded — tier switching is instant."
Say "pre-downloading the Edge reranker and Base/Pro models..."
Say " this can take 2-5 min; uncached models require network access."
$modelChecks = @(
@{ Label = "Edge reranker"; Status = "not checked"; Code = "from sentence_transformers import CrossEncoder; CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')" },
@{ Label = "Base/Pro embedder"; Status = "not checked"; Code = "from sentence_transformers import SentenceTransformer; SentenceTransformer('Qwen/Qwen3-Embedding-0.6B', truncate_dim=256)" },
@{ Label = "Base/Pro reranker"; Status = "not checked"; Code = "from sentence_transformers import CrossEncoder; CrossEncoder('Alibaba-NLP/gte-reranker-modernbert-base')" }
)
if ($toolPython -and (Test-Path -LiteralPath $toolPython)) {
foreach ($modelCheck in $modelChecks) {
Say " Pre-downloading $($modelCheck.Label)..."
$modelCheck.Status = "failed"
try {
& $toolPython -c $modelCheck.Code
if ($LASTEXITCODE -eq 0) { $modelCheck.Status = "ready" }
} catch {
Warn " $($modelCheck.Label) could not be loaded: $_"
}
if ($modelCheck.Status -eq "ready") {
Ok " $($modelCheck.Label) ready"
} else {
Warn " $($modelCheck.Label) pre-download failed"
Warn "Retry this model only: & (Join-Path (uv tool dir) 'truememory\Scripts\python.exe') -c `"$($modelCheck.Code)`""
}
}
} else {
Warn "could not locate tool Python at $toolPython — skipping model pre-download"
Warn "models will download on first use instead"
Warn "Model readiness is unverified. Locate the tool environment with: uv tool dir"
Warn "After locating its Python, run: python -m truememory.ingest.cli setup"
}
$modelReadyCount = @($modelChecks | Where-Object { $_.Status -eq "ready" }).Count

# ---------- done ----------
Write-Host ""
Expand All @@ -174,10 +187,31 @@ Write-Host @"
a sauron company
"@ -ForegroundColor Green

$installedVer = & $toolPython -c "from importlib.metadata import version; print(version('truememory'))" 2>$null
$installedVer = $null
if ($toolPython) {
try {
$installedVer = & $toolPython -c "from importlib.metadata import version; print(version('truememory'))" 2>$null
} catch {
$installedVer = $null
}
}
if (-not $installedVer) { $installedVer = "unknown" }
Write-Host ""
Ok "TrueMemory v$installedVer installed successfully."
Ok "TrueMemory v$installedVer package installed."
Say "Claude registration: $setupStatus"
Say "Hooks: $hookStatus"
Say "Model pre-download checks: $modelReadyCount/3 succeeded."
foreach ($modelCheck in $modelChecks) {
Say " $($modelCheck.Label): $($modelCheck.Status)"
}
if ($modelReadyCount -eq 3) {
Ok "All three requested model pre-download checks passed."
} else {
Warn "Model readiness incomplete; package installation is retained. Retry the failed or skipped checks before relying on those models."
}
Say "The Edge embedder was not checked by this pre-download step."
Say "Registration and hook command success do not verify runtime operation."
Say "Changing embedding models can still require re-embedding stored memories."
Write-Host ""
Write-Host " First time? Start a new Claude session and type:" -ForegroundColor Green
Write-Host ""
Expand Down
Loading
Loading