Skip to content
Merged
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
17 changes: 15 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to Milo are documented here.

## 0.3.1 — 2026-05-23

### Changed

- Bumped to `kida-templates>=0.9.0,<0.10.0`, refreshing the lockfile and release surface for Kida 0.9.
- Expanded the AGENTS.md steward network with verification-status signals, cross-cutting root concerns, known regression patterns, steward questions, and self-audit tracking. ([#steward-network](https://github.com/lbliii/milo-cli/issues/steward-network))
- Prepared the 0.3.1 release by aligning package metadata, changelog intent, and public site release notes.

### Fixed

- Strengthened `scripts/check_templates.py` to enforce Kida strict end-tag and fragile same-folder import checks in Milo's terminal-aware template environment, and cleaned the bundled component imports/error template to satisfy that gate.


## 0.3.0 — 2026-05-03

### Added
Expand All @@ -11,7 +24,7 @@ All notable changes to Milo are documented here.
- Added display-cell width template filters for terminal layouts: `cell_width`, `cell_fit`, `cell_pad`, `cell_rpad`, and `cell_truncate`. ([#terminal-cell-width](https://github.com/lbliii/milo-cli/issues/terminal-cell-width))
- Added display-cell exact topology filters for terminal templates: `rule_line`, `divider_line`, `bottom_rule`, `frame_line`, `rail_line`, `cell_fill`, `cell_meter`, `open_rule`, `open_rule_divider`, and `open_rule_end`. ([#terminal-open-rules](https://github.com/lbliii/milo-cli/issues/terminal-open-rules))
- Add silent-exception lint gate and `# silent: <reason>` annotations to prevent unlogged exception swallowing
- Adopt Kida capabilities: `inline_components=True` and `validate_calls=True` defaults in `get_env()`; `enable_capture` opt-in kwarg on `get_env()` for static-site / capture flows; new `milo components` subcommand listing bundled and user-defined template defs (with `--json` for tooling, `--path` to scan extra dirs); `milo.live` re-exports for `LiveRenderer`, `Spinner`, `stream_to_terminal`, `terminal_env`; `kida.get_optimal_workers` now sizes the gateway, registry, and saga executor pools by workload type (IO_BOUND for I/O fan-out, RENDER for saga effects); `{% flush %}` boundaries added to `pipeline_progress` and `pipeline_detail` defs to encode streaming contract; CI gains a template compile-check via `scripts/check_templates.py`; new `examples/liverender` shows `LiveRenderer` outside the App harness; new docs page `docs/build-apps/live`.
- Adopt Kida 0.7 capabilities: `inline_components=True` and `validate_calls=True` defaults in `get_env()`; `enable_capture` opt-in kwarg on `get_env()` for static-site / capture flows; new `milo components` subcommand listing bundled and user-defined template defs (with `--json` for tooling, `--path` to scan extra dirs); `milo.live` re-exports for `LiveRenderer`, `Spinner`, `stream_to_terminal`, `terminal_env`; `kida.get_optimal_workers` now sizes the gateway, registry, and saga executor pools by workload type (IO_BOUND for I/O fan-out, RENDER for saga effects); `{% flush %}` boundaries added to `pipeline_progress` and `pipeline_detail` defs to encode streaming contract; CI gains a template compile-check via `scripts/check_templates.py`; new `examples/liverender` shows `LiveRenderer` outside the App harness; new docs page `docs/build-apps/live`.
- Agent-first improvements: structured MCP validation errors with argument/constraint context, `form_schema()` introspection helper, `llms.txt` required/optional/default markers, `docs/agent-quickstart.md`, `docs/testing.md`, and `examples/greet/` test template.
- Agent-native affordances: `milo new <name>` scaffold (app.py, tests, conftest, README), `milo verify <path>` six-check self-diagnosis (imports, CLI located, commands registered, schemas generate, in-process MCP list, subprocess MCP transport), `function_to_schema(..., warn_missing_docs=True)` surfacing undocumented typed params, README examples index with drift lint, and a Python 3.14+ preflight on `milo` with an actionable install hint instead of ImportError.

Expand All @@ -21,7 +34,7 @@ All notable changes to Milo are documented here.
- Tightened steward guidance with contract checklists, evidence-backed finding format, collateral update rules, synthesis requirements, and parity-matrix expectations for cross-surface work. ([#steward-contract-checklists](https://github.com/lbliii/milo-cli/issues/steward-contract-checklists))
- Added scoped AGENTS.md steward guidance for core Milo domains so future agent work has explicit ownership, consultation, and safety routing.
- Adopt Python 3.14+ patterns: PEP 695 type aliases in middleware, match/case in form key handlers, frozen+slotted dataclasses in tests
- Bumped to `kida-templates>=0.9.0,<0.10.0`. Kida makes `strict_undefined=True` the default — milo's bundled templates already conformed, so no behaviour changes for callers using stock templates. User templates that relied on silent-undefined fallbacks now raise `UndefinedError` at render; opt back into the loose mode by passing `get_env(strict_undefined=False)`.
- Bumped to `kida-templates>=0.7.0,<0.8.0`. Kida 0.7 makes `strict_undefined=True` the default — milo's bundled templates already conformed, so no behaviour changes for callers using stock templates. User templates that relied on silent-undefined fallbacks now raise `UndefinedError` at render; opt back into the loose mode by passing `get_env(strict_undefined=False)`.

### Fixed

Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
PYTHON_VERSION ?= 3.14t
VENV_DIR ?= .venv

.PHONY: all help setup install test test-cov lint format ty bench docs-test ci clean build gh-release changelog changelog-draft
.PHONY: all help setup install test test-cov lint format ty bench docs-test ci clean build release-status gh-release changelog changelog-draft

all: help

Expand All @@ -24,6 +24,7 @@ help:
@echo " make docs-test - verify templates and tagged docs snippets"
@echo " make clean - remove build artifacts"
@echo " make build - uv build"
@echo " make release-status - verify release version/tag/changelog alignment"
@echo " make changelog - compile changelog.d/ fragments into CHANGELOG.md"
@echo " make changelog-draft - preview changelog without writing"
@echo " make gh-release - create GitHub release → triggers PyPI publish"
Expand Down Expand Up @@ -68,6 +69,9 @@ clean:
build:
uv build

release-status:
uv run python scripts/release_status.py --remote

Comment on lines +72 to +74
changelog:
uv run towncrier build --yes

Expand All @@ -77,6 +81,7 @@ changelog-draft:
# Create GitHub release from site release notes; triggers python-publish workflow → PyPI
# Strips YAML frontmatter (--- ... ---) from notes before passing to gh
gh-release:
uv run python scripts/release_status.py --remote --pypi
@VERSION=$$(grep -m1 '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/'); \
Comment on lines 83 to 85
PROJECT=$$(grep -m1 '^name = ' pyproject.toml | sed 's/name = "\(.*\)"/\1/'); \
NOTES="site/content/releases/$$VERSION.md"; \
Expand Down
4 changes: 0 additions & 4 deletions changelog.d/kida-0.9.changed.md

This file was deleted.

1 change: 0 additions & 1 deletion changelog.d/release-0.3.0.changed.md

This file was deleted.

5 changes: 0 additions & 5 deletions changelog.d/steward-network.changed.md

This file was deleted.

9 changes: 9 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,19 @@ uv run milo verify examples/greet/app.py
# Verify built-in templates and tagged docs snippets
make docs-test

# Verify release version, tag, changelog, and release-note alignment
make release-status

# With coverage (project enforces 80% floor)
make test-cov
```

For normal `src/` changes, CI requires a new non-empty `changelog.d/*.md`
fragment. For a release-cut PR, apply the `skip-changelog` label after
fragments have been compiled into `CHANGELOG.md` and the new
`site/content/releases/<version>.md` file. `make release-status` is the
release-side guard that catches leftover or empty fragments before publishing.

## Free-threading (Python 3.14t)

Milo runs its test suite with `PYTHON_GIL=0` on 3.14t builds so threading bugs
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "milo-cli"
version = "0.3.0"
version = "0.3.1"
description = "Template-driven CLI applications for free-threaded Python"
readme = "README.md"
requires-python = ">=3.14"
Expand Down
67 changes: 66 additions & 1 deletion scripts/check_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
configuration that ships at runtime. This catches unknown filters, unknown
globals, arity mismatches, and syntax errors that the upstream
``kida check`` CLI misses because it only knows HTML-autoescape filters.
It also mirrors Kida's strict end-tag and fragile-path checks inside that
Milo-aware environment.

Exit code 0 = clean, 1 = one or more templates failed to compile.
"""
Expand All @@ -14,6 +16,7 @@

import sys
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parent.parent
BUILTIN = ROOT / "src" / "milo" / "templates"
Expand All @@ -26,15 +29,77 @@ def _iter_templates(root: Path) -> list[Path]:

def _check_root(root: Path, label: str) -> list[str]:
from kida import FileSystemLoader
from kida.analysis.fragile_paths import check_fragile_paths
from kida.lexer import Lexer
from kida.parser import Parser

from milo.templates import get_env

def explicit_close_suggestion(block_type: str) -> str:
if block_type == "block":
return "{% endblock %}"
return f"{{% end{block_type} %}}"

def check_strict_closures(path: Path, rel: str, env: Any) -> list[str]:
source = path.read_text(encoding="utf-8")
lexer_config = getattr(env, "_lexer_config", None)
if lexer_config is None:
return [
f"[{label}] {rel}\n"
"lint/internal: kida environment no longer exposes lexer configuration; "
"strict end-tag lint could not run"
]
lexer = Lexer(source, lexer_config)
tokens = list(lexer.tokenize())
Comment on lines +43 to +53
parser = Parser(
tokens,
name=rel,
filename=str(path),
source=source,
autoescape=env.select_autoescape(rel),
)
parser.parse()
unified_end_closures = getattr(parser, "_unified_end_closures", None)
if unified_end_closures is None:
return [
f"[{label}] {rel}\n"
"lint/internal: kida parser no longer exposes unified end closures; "
"strict end-tag lint could not run"
]
errors: list[str] = []
for lineno, _col, closing in unified_end_closures:
want = explicit_close_suggestion(closing)
errors.append(
f"[{label}] {rel}:{lineno}\n"
f"strict: unified {{% end %}} closes '{closing}' — prefer {want}"
)
return errors

env = get_env(loader=FileSystemLoader(str(root)))
errors: list[str] = []
for path in _iter_templates(root):
rel = path.relative_to(root).as_posix()
try:
env.get_template(rel)
tmpl = env.get_template(rel)
errors.extend(check_strict_closures(path, rel, env))
ast = getattr(tmpl, "_optimized_ast", None)
if ast is None:
errors.append(
f"[{label}] {rel}\n"
"lint/internal: kida template no longer exposes optimized AST; "
"fragile-path lint could not run"
)
continue
errors.extend(
(
f"[{label}] {rel}:{issue.lineno}\n"
"lint/fragile-path: "
f'{{% {issue.statement} "{issue.target}" %}} '
"is in the same folder as the caller — "
f'prefer "{issue.suggestion}" so folder moves stay zero-edit'
)
for issue in check_fragile_paths(ast, rel)
)
except Exception as exc:
formatter = getattr(exc, "format_compact", None)
detail = formatter() if callable(formatter) else f"{type(exc).__name__}: {exc}"
Expand Down
Loading
Loading