mklang 0.5.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. mklang-0.5.4/.env.example +11 -0
  2. mklang-0.5.4/.github/workflows/ci.yml +53 -0
  3. mklang-0.5.4/.github/workflows/docs.yml +20 -0
  4. mklang-0.5.4/.github/workflows/release.yml +108 -0
  5. mklang-0.5.4/.gitignore +25 -0
  6. mklang-0.5.4/CHANGELOG.md +328 -0
  7. mklang-0.5.4/CONTRIBUTING.md +95 -0
  8. mklang-0.5.4/LICENSE +201 -0
  9. mklang-0.5.4/PKG-INFO +265 -0
  10. mklang-0.5.4/README.md +234 -0
  11. mklang-0.5.4/ROADMAP.md +123 -0
  12. mklang-0.5.4/SPEC.md +931 -0
  13. mklang-0.5.4/config/runtime.example.yaml +143 -0
  14. mklang-0.5.4/config/runtime.schema.json +73 -0
  15. mklang-0.5.4/conformance/README.md +85 -0
  16. mklang-0.5.4/conformance/cases/accumulate.yaml +26 -0
  17. mklang-0.5.4/conformance/cases/budget-exhausted.yaml +26 -0
  18. mklang-0.5.4/conformance/cases/budget-fanout-charging.yaml +45 -0
  19. mklang-0.5.4/conformance/cases/call-failed.yaml +35 -0
  20. mklang-0.5.4/conformance/cases/call.yaml +34 -0
  21. mklang-0.5.4/conformance/cases/cost-exhausted.yaml +36 -0
  22. mklang-0.5.4/conformance/cases/escalate-routing.yaml +27 -0
  23. mklang-0.5.4/conformance/cases/fail-gate.yaml +23 -0
  24. mklang-0.5.4/conformance/cases/fanout-over.yaml +35 -0
  25. mklang-0.5.4/conformance/cases/fanout-sample-index.yaml +27 -0
  26. mklang-0.5.4/conformance/cases/hook-before-prose.yaml +48 -0
  27. mklang-0.5.4/conformance/cases/hook-false-falls-through.yaml +38 -0
  28. mklang-0.5.4/conformance/cases/judge-routing.yaml +34 -0
  29. mklang-0.5.4/conformance/cases/judge-unparseable-fallback.yaml +27 -0
  30. mklang-0.5.4/conformance/cases/judge-unparseable-halt.yaml +35 -0
  31. mklang-0.5.4/conformance/cases/linear.yaml +27 -0
  32. mklang-0.5.4/conformance/cases/no-gate-matched.yaml +23 -0
  33. mklang-0.5.4/conformance/cases/repair-exhausted-reroutes.yaml +22 -0
  34. mklang-0.5.4/conformance/cases/repair.yaml +23 -0
  35. mklang-0.5.4/conformance/cases/tool-state-output-deposit.yaml +38 -0
  36. mklang-0.5.4/conformance/cases/tool-unknown-halts.yaml +23 -0
  37. mklang-0.5.4/docs/adr/0001-capability-tiers.md +27 -0
  38. mklang-0.5.4/docs/adr/0002-fanout-llm-reducer.md +26 -0
  39. mklang-0.5.4/docs/adr/0003-provider-agnostic.md +24 -0
  40. mklang-0.5.4/docs/adr/0004-llm-as-runtime-gates.md +35 -0
  41. mklang-0.5.4/docs/adr/0005-reasoning-first-class.md +25 -0
  42. mklang-0.5.4/docs/adr/0006-code-hook-gates.md +35 -0
  43. mklang-0.5.4/docs/adr/0007-resumable-checkpoints.md +48 -0
  44. mklang-0.5.4/docs/adr/0008-hitl-escalate-suspend.md +40 -0
  45. mklang-0.5.4/docs/adr/0009-conformance-suite.md +52 -0
  46. mklang-0.5.4/docs/adr/0010-llm-assisted-lint.md +123 -0
  47. mklang-0.5.4/docs/adr/0011-mcp-server-surface.md +94 -0
  48. mklang-0.5.4/docs/experiments/gate-divergence.md +98 -0
  49. mklang-0.5.4/docs/patterns.md +141 -0
  50. mklang-0.5.4/examples/expense_approval.mk +89 -0
  51. mklang-0.5.4/examples/hook_gates.mk +49 -0
  52. mklang-0.5.4/examples/map_reduce.mk +39 -0
  53. mklang-0.5.4/examples/react.mk +62 -0
  54. mklang-0.5.4/examples/research.mk +86 -0
  55. mklang-0.5.4/examples/self_consistency.mk +61 -0
  56. mklang-0.5.4/examples/summarize_doc.mk +23 -0
  57. mklang-0.5.4/examples/triage.mk +139 -0
  58. mklang-0.5.4/examples/triage.test.yaml +54 -0
  59. mklang-0.5.4/mkdocs.yml +46 -0
  60. mklang-0.5.4/pyproject.toml +77 -0
  61. mklang-0.5.4/schema/mklang.schema.json +269 -0
  62. mklang-0.5.4/scripts/build-docs.sh +51 -0
  63. mklang-0.5.4/scripts/gate_divergence.py +294 -0
  64. mklang-0.5.4/src/mklang/__init__.py +17 -0
  65. mklang-0.5.4/src/mklang/checkpoint.py +112 -0
  66. mklang-0.5.4/src/mklang/cli.py +406 -0
  67. mklang-0.5.4/src/mklang/config.py +50 -0
  68. mklang-0.5.4/src/mklang/data/mklang.schema.json +269 -0
  69. mklang-0.5.4/src/mklang/engine.py +570 -0
  70. mklang-0.5.4/src/mklang/errors.py +36 -0
  71. mklang-0.5.4/src/mklang/hooks.py +86 -0
  72. mklang-0.5.4/src/mklang/interpolate.py +44 -0
  73. mklang-0.5.4/src/mklang/lint.py +140 -0
  74. mklang-0.5.4/src/mklang/llm/__init__.py +5 -0
  75. mklang-0.5.4/src/mklang/llm/anthropic.py +129 -0
  76. mklang-0.5.4/src/mklang/llm/base.py +110 -0
  77. mklang-0.5.4/src/mklang/llm/mock.py +47 -0
  78. mklang-0.5.4/src/mklang/llm/openai_compat.py +137 -0
  79. mklang-0.5.4/src/mklang/loader.py +174 -0
  80. mklang-0.5.4/src/mklang/model.py +103 -0
  81. mklang-0.5.4/src/mklang/providers.py +75 -0
  82. mklang-0.5.4/src/mklang/registry.py +22 -0
  83. mklang-0.5.4/src/mklang/scripttest.py +219 -0
  84. mklang-0.5.4/src/mklang/tools.py +135 -0
  85. mklang-0.5.4/tests/test_anthropic.py +158 -0
  86. mklang-0.5.4/tests/test_checkpoint.py +355 -0
  87. mklang-0.5.4/tests/test_conformance.py +30 -0
  88. mklang-0.5.4/tests/test_cookbook.py +44 -0
  89. mklang-0.5.4/tests/test_engine_mock.py +541 -0
  90. mklang-0.5.4/tests/test_golden.py +82 -0
  91. mklang-0.5.4/tests/test_hardening.py +303 -0
  92. mklang-0.5.4/tests/test_hitl.py +220 -0
  93. mklang-0.5.4/tests/test_hooks.py +199 -0
  94. mklang-0.5.4/tests/test_interpolate.py +18 -0
  95. mklang-0.5.4/tests/test_lint.py +156 -0
  96. mklang-0.5.4/tests/test_live.py +69 -0
  97. mklang-0.5.4/tests/test_loader_checks.py +189 -0
  98. mklang-0.5.4/tests/test_providers.py +39 -0
  99. mklang-0.5.4/tests/test_release.py +108 -0
  100. mklang-0.5.4/tests/test_remediation_002.py +94 -0
  101. mklang-0.5.4/tests/test_schema.py +38 -0
  102. mklang-0.5.4/tests/test_scripttest.py +115 -0
  103. mklang-0.5.4/tests/test_tools.py +108 -0
  104. mklang-0.5.4/uv.lock +736 -0
@@ -0,0 +1,11 @@
1
+ # Copy to .env and fill in the keys for the providers you use.
2
+ # .env is gitignored; NEVER commit real keys. Names match config/runtime.example.yaml.
3
+
4
+ DEEPSEEK_API_KEY=
5
+ ANTHROPIC_API_KEY=
6
+ OPENAI_API_KEY=
7
+ OPENROUTER_API_KEY=
8
+ XAI_API_KEY=
9
+ MISTRAL_API_KEY=
10
+ # GEMINI_API_KEY=
11
+ # LOCAL_API_KEY= # usually unused for Ollama/vLLM
@@ -0,0 +1,53 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: astral-sh/setup-uv@v5
13
+ - name: Lint
14
+ run: uv run --extra dev ruff check src tests
15
+ - name: Unit tests (MockLLM — no network)
16
+ run: uv run --extra dev pytest -q
17
+ - name: Validate example machines against the schema
18
+ run: >
19
+ uv run --with check-jsonschema
20
+ check-jsonschema --default-filetype yaml
21
+ --schemafile schema/mklang.schema.json examples/*.mk
22
+ - name: Validate the runtime config against its schema
23
+ run: >
24
+ uv run --with check-jsonschema
25
+ check-jsonschema --default-filetype yaml
26
+ --schemafile config/runtime.schema.json config/runtime.example.yaml
27
+ - name: Semantic check + static analysis on examples
28
+ run: |
29
+ uv run mklang check examples/*.mk
30
+ uv run mklang lint --strict examples/*.mk
31
+ - name: Deterministic scenario tests (scripted LLM — no API keys)
32
+ run: uv run mklang test examples/triage.mk --script examples/triage.test.yaml
33
+ - name: Build sdist + wheel
34
+ run: uv build
35
+
36
+ live-smoke:
37
+ # Low-budget smoke against real providers; each test skips itself when its
38
+ # key is absent, so forks without secrets stay green.
39
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
40
+ runs-on: ubuntu-latest
41
+ needs: test
42
+ env:
43
+ DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
44
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: astral-sh/setup-uv@v5
48
+ - name: Live smoke (active provider)
49
+ run: MKLANG_LIVE=1 uv run --extra dev pytest -q -rs tests/test_live.py
50
+ - name: Live smoke (anthropic)
51
+ run: >
52
+ MKLANG_LIVE=1 MKLANG_LIVE_PROVIDER=anthropic
53
+ uv run --all-extras pytest -q -rs tests/test_live.py
@@ -0,0 +1,20 @@
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ deploy:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v5
17
+ - name: Assemble docs source
18
+ run: bash scripts/build-docs.sh
19
+ - name: Build and deploy to GitHub Pages
20
+ run: uvx --with mkdocs-material mkdocs gh-deploy --force --strict
@@ -0,0 +1,108 @@
1
+ name: release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: release-${{ github.event.release.tag_name }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ quality-and-build:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ ref: ${{ github.event.release.tag_name }}
21
+ - uses: astral-sh/setup-uv@v5
22
+ - name: Verify tag matches package version
23
+ env:
24
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
25
+ run: |
26
+ package_version="$(uv run python -c 'import mklang; print(mklang.__version__)')"
27
+ test "$RELEASE_TAG" = "v$package_version"
28
+ - name: Lint and unit tests
29
+ run: |
30
+ uv run --extra dev ruff check src tests scripts
31
+ uv run --extra dev pytest -q
32
+ - name: Validate examples and runtime config
33
+ run: |
34
+ uv run --with check-jsonschema check-jsonschema --default-filetype yaml --schemafile schema/mklang.schema.json examples/*.mk
35
+ uv run --with check-jsonschema check-jsonschema --default-filetype yaml --schemafile config/runtime.schema.json config/runtime.example.yaml
36
+ uv run mklang check examples/*.mk
37
+ uv run mklang lint --strict examples/*.mk
38
+ uv run mklang test examples/triage.mk --script examples/triage.test.yaml
39
+ - name: Build docs strictly
40
+ run: |
41
+ bash scripts/build-docs.sh
42
+ uvx --with mkdocs-material mkdocs build --strict
43
+ - name: Build and validate distributions
44
+ run: |
45
+ uv build
46
+ uvx twine check dist/*
47
+ - name: Smoke-test the wheel in a clean environment
48
+ run: |
49
+ uv venv /tmp/mklang-release-smoke
50
+ uv pip install --python /tmp/mklang-release-smoke/bin/python dist/*.whl
51
+ /tmp/mklang-release-smoke/bin/python -c 'import importlib.resources, mklang; assert importlib.resources.files("mklang").joinpath("data/mklang.schema.json").is_file(); print(mklang.__version__)'
52
+ /tmp/mklang-release-smoke/bin/mklang --help
53
+ - uses: actions/upload-artifact@v4
54
+ with:
55
+ name: distributions
56
+ path: dist/*
57
+ if-no-files-found: error
58
+
59
+ live-matrix:
60
+ runs-on: ubuntu-latest
61
+ env:
62
+ DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
63
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
64
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
65
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
66
+ OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
67
+ XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
68
+ MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
69
+ steps:
70
+ - uses: actions/checkout@v4
71
+ with:
72
+ ref: ${{ github.event.release.tag_name }}
73
+ - uses: astral-sh/setup-uv@v5
74
+ - name: Required DeepSeek smoke
75
+ run: MKLANG_LIVE=1 MKLANG_LIVE_PROVIDER=deepseek uv run --all-extras pytest -q tests/test_live.py
76
+ - name: Required OpenAI smoke
77
+ run: MKLANG_LIVE=1 MKLANG_LIVE_PROVIDER=openai uv run --all-extras pytest -q tests/test_live.py
78
+ - name: Cross-provider gate agreement
79
+ run: |
80
+ mkdir -p live-results
81
+ uv run --all-extras python scripts/gate_divergence.py \
82
+ --providers deepseek,openai,anthropic,google,openrouter,xai,mistral \
83
+ --repeats 3 \
84
+ --require-providers deepseek,openai \
85
+ --min-agreement 1.0 \
86
+ --jsonl live-results/runs.jsonl \
87
+ --summary-json live-results/summary.json
88
+ - uses: actions/upload-artifact@v4
89
+ if: always()
90
+ with:
91
+ name: live-provider-report
92
+ path: live-results/
93
+ if-no-files-found: warn
94
+
95
+ publish:
96
+ needs: [quality-and-build, live-matrix]
97
+ runs-on: ubuntu-latest
98
+ environment: pypi
99
+ permissions:
100
+ contents: read
101
+ id-token: write
102
+ steps:
103
+ - uses: actions/download-artifact@v4
104
+ with:
105
+ name: distributions
106
+ path: dist
107
+ - name: Publish distributions to PyPI
108
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,25 @@
1
+ # Secrets — never commit
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.egg-info/
10
+ .eggs/
11
+ build/
12
+ dist/
13
+ .venv/
14
+ venv/
15
+ .pytest_cache/
16
+ .ruff_cache/
17
+ .mypy_cache/
18
+
19
+ # OS / editor
20
+ .DS_Store
21
+ *.swp
22
+
23
+ # Generated docs site
24
+ site-src/
25
+ site/
@@ -0,0 +1,328 @@
1
+ # Changelog
2
+
3
+ All notable changes to mklang are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ **Two version lines** are tracked separately:
7
+
8
+ - **Spec version** — the language, declared per-file via the `mklang:` field
9
+ (currently `"0.2"`).
10
+ - **Package version** — the reference interpreter / tooling, SemVer in
11
+ `pyproject.toml` (currently `0.5.4`).
12
+
13
+ ## [0.5.4] — 2026-07-16
14
+
15
+ Release-readiness pass. The language stays **0.2** and runtime semantics are
16
+ unchanged; this release makes the existing interpreter reproducibly distributable.
17
+
18
+ ### Added
19
+
20
+ - **Trusted Publishing release workflow.** A published GitHub Release now gates
21
+ publication on the full offline suite, strict docs, artifact validation, a
22
+ clean-wheel installation smoke, and the live provider matrix. The publish job
23
+ consumes the already-tested artifacts and uses short-lived PyPI OIDC credentials.
24
+ - **Enforceable live-matrix reporting.** `scripts/gate_divergence.py` can require
25
+ named providers, enforce a minimum agreement rate, write a summary artifact,
26
+ and distinguishes a missing-key skip from a provider/runtime failure.
27
+ - **Release provenance tests.** CI pins package metadata and `mklang.__version__`
28
+ to the same value and tests the required-provider gate offline.
29
+
30
+ ### Changed
31
+
32
+ - DeepSeek and OpenAI are the blocking release providers (three divergence runs,
33
+ agreement `1.0`). Anthropic, Google, OpenRouter, xAI, and Mistral are attempted
34
+ when credentials are configured and remain informational.
35
+
36
+ ## [0.5.3] — 2026-07-16
37
+
38
+ Third remediation pass (Follow-up 003): closes the residue Remediation 002
39
+ delivered without declaring. The language stays **0.2**; no `.mk` needs changes.
40
+
41
+ ### Added
42
+
43
+ - **`mklang test` — deterministic machine testing without API keys (R3-1).**
44
+ A new subcommand runs a machine against a script of named scenarios with a
45
+ **scripted LLM** (produce texts + judge picks) and scripted tools/hooks —
46
+ fully deterministic, no provider or key. Per-scenario PASS/FAIL with a minimal
47
+ diff (first mismatched key, expected vs actual); exit 0 iff all pass. The
48
+ scripted LLM, `hooks:`/`tools:` bindings, and expectation matcher now live once
49
+ in `src/mklang/scripttest.py`, shared with the conformance runner (all 21 cases
50
+ green through it, unchanged). Ships `examples/triage.test.yaml` (happy path +
51
+ KB-empty escalation). Docs: README "Test your machine without API keys",
52
+ `docs/patterns.md`, `conformance/README.md` cross-reference. CI runs
53
+ `uv run mklang test examples/triage.mk --script examples/triage.test.yaml`
54
+ in the unit-test job (no API keys); the same scenarios are also covered by
55
+ `tests/test_scripttest.py` in the normal pytest run.
56
+ - **Static budget-feasibility check (R3-2).** `mklang check`/`lint` now BFS the
57
+ shortest path (in states) from `entry` to a gate `to: END`. `budget` below it is
58
+ a guaranteed `budget-exhausted` halt, reported as error `budget-infeasible`;
59
+ `budget < shortest + 2` warns (no headroom for a single repair). Fan-out states
60
+ count as 1 (branch counts are data-dependent), so the check is a lower bound —
61
+ the message says so. SPEC §7 documents it next to the charging rule. Host
62
+ pre-validation only; run semantics unchanged.
63
+ - **Dotted second-segment lint on inline context maps (R3-3, completes F7).** When
64
+ a `{{root.key}}` root resolves to an inline dict literal in `context:`
65
+ (`ticket: {body: …}`), the second segment is now validated against the map's
66
+ keys, so `{{ticket.bod}}` is flagged. Skipped for state outputs and runtime
67
+ roots (`human`/`item`/`index`) whose shape is unknowable; deeper than segment 2
68
+ stays out of scope.
69
+ - **Schema-copy identity test (R3-4).** One test asserts `schema/mklang.schema.json`
70
+ and the packaged copy `src/mklang/data/mklang.schema.json` are byte-identical,
71
+ with a failure message naming the sync direction (repo `schema/` is the source).
72
+ - **ADR 0010 — LLM-assisted lint (R3-5, Proposed, design only).** An opt-in
73
+ `mklang lint --llm` that would generate synthetic outputs and measure
74
+ gate-selection stability/overlap (reusing `gate_divergence.py`) to catch
75
+ ambiguous prose `when` conditions. Documents cost model, determinism caveats,
76
+ relation to the conformance suite, and why it is out of 0.5.x. No code.
77
+
78
+ ## [0.5.2] — 2026-07-16
79
+
80
+ Second remediation pass. The language stays **0.2**; no `.mk` needs changes.
81
+
82
+ ### Changed (observable behavior)
83
+
84
+ - **Default judge model now follows each state's tier (F1).** Previously every
85
+ gate — including the highest-stakes gates on `reasoning` states (refund
86
+ thresholds, legal matters, human escalation) — was judged by the cheapest
87
+ (`fast`) model, silently deviating from SPEC §2.1. Gate judging now uses the
88
+ state's own effective tier by default. **This changes which model judges your
89
+ gates.** To restore the previous single-model behavior, set the provider
90
+ `judge:` key in the runtime config — it is now an explicit, opt-in *global*
91
+ override, no longer the default (it ships commented out in
92
+ `config/runtime.example.yaml`). The chosen judge model is recorded in the trace
93
+ as `judge_model` on every `gate_via: llm` step. Gate-divergence numbers
94
+ collected before this change are not comparable with those after; re-run
95
+ `scripts/gate_divergence.py` (now with a `--judge-tier` flag) to refresh them.
96
+
97
+ ### Fixed
98
+
99
+ - **Strict judge-reply parsing (F2).** The bare-number fallback no longer grabs
100
+ the *first* digit anywhere in the reply (a verbose judge's "Condition 1 fails…"
101
+ misread as choice 1). Parse order is now: strict JSON, then a whole-reply bare
102
+ number, then the **last** number in the reply (models conclude with the answer).
103
+ The last two are traced as `judge_parse` (anomaly-adjacent, not a fallback). The
104
+ judge system prompt now forbids extra numbers; SPEC §5 constrains conformant
105
+ judges to terse instruct-style replies.
106
+ - **`sample` branch diversity (F3).** Each `sample: N` branch now sees its own
107
+ `{{index}}` (0-based), so a prompt can say "you are branch {{index}}, take a
108
+ different approach" (Tree-of-Thought, debate) instead of relying on temperature
109
+ alone. `{{index}}` is now available in both fan-out forms; `{{item}}` remains
110
+ `over`-only.
111
+
112
+ ### Added
113
+
114
+ - **`unresolved-interpolation` lint rule (F7).** `mklang lint` flags any `{{path}}`
115
+ whose first segment no `context:` key / state `output:` / (inside a fan-out)
116
+ `item`/`index` provides — the silent-typo bug (`{{kb_answr}}` → empty string).
117
+ `item`/`index` referenced outside a fan-out state are flagged too. First-segment
118
+ only (dotted tails can't be checked statically); warning by default, error under
119
+ `--strict`.
120
+ - **`--strict` rejects unsupported `mklang:` versions (F6).** An unknown language
121
+ version stays a warning by default but becomes a hard error (`version-unsupported`)
122
+ under `mklang check --strict` / `lint --strict` / the new `run --strict`.
123
+ - **`0600` checkpoints (F5).** Checkpoint files (full blackboard as plaintext JSON)
124
+ are written owner-only. SPEC §11 now lists checkpoints as an asset and documents
125
+ the plaintext-at-rest surface (host-side mitigation; encryption is a v0.2
126
+ non-goal). The `--checkpoint` help notes the plaintext content.
127
+ - **Conformance coverage for hook precedence and `tool` states (F8).** The case
128
+ format gains scripted `hooks:` (boolean sequences) and `tools:` (list or
129
+ `{input-substring: output}` map) bindings, plus `expect.error_prefix`. New cases:
130
+ `hook-before-prose`, `hook-false-falls-through`, `tool-state-output-deposit`,
131
+ `tool-unknown-halts`, `fanout-sample-index` (F3), `budget-fanout-charging` (F4).
132
+ Suite: 15 → 21 cases.
133
+ - **SPEC §7** now states the fan-out step-charging rule (`max(1, len(branches))`)
134
+ explicitly, with the map-reduce budget-sizing implication and a worked example;
135
+ `docs/patterns.md` and ROADMAP note the possible v0.3 `budget`/`branch_budget`
136
+ split.
137
+
138
+ > **Deferred from Remediation 002, delivered in 0.5.3:** author-facing scripted
139
+ > testing (`mklang test`), budget-feasibility check, dotted-segment lint,
140
+ > schema-identity test, ADR 0010.
141
+
142
+ ## [0.5.1] — 2026-07-16
143
+
144
+ ### Fixed
145
+
146
+ - **Judge silent clamp** — out-of-range or 0-based `{"choice": k}` replies are
147
+ no longer clamped to a valid gate. `parse_choice` returns `None` for OOR;
148
+ adapters raise `JudgeUnparseable`; the engine does not re-clamp. Soft-fallback
149
+ to `otherwise` (or hard-halt `judge-unparseable`) is traced via
150
+ `judge_fallback` / `judge_raw` — never a mute misroute with only `gate_via: llm`.
151
+ - **Showcase honesty (`triage.mk`)** — `search_kb` and `send_reply` are real
152
+ `tool:` states (host stubs); generative states no longer claim tool use or
153
+ “confirm the send.” Same honesty pass on `research.mk` (no fake `web_search`).
154
+ - **README** — dropped “no host code required”; documented that tools/hooks are
155
+ host-supplied; centered conformance + portable spec as the differentiator.
156
+
157
+ ### Added
158
+
159
+ - Builtins / entry points: `search_kb`, `send_reply` (deterministic stubs).
160
+ - **SPEC §5** — normative 1-based judge protocol; OOR = anomaly; documented
161
+ `JUDGE_CONTEXT_CHARS` (4000) truncation.
162
+ - **SPEC §11 Threat model (v0.2)** — injection surface, partial mitigations,
163
+ explicit non-goals (declare rather than deny).
164
+ - Reference produce temperature defaults documented (non-normative).
165
+ - **Gate-divergence experiment** — `scripts/gate_divergence.py` +
166
+ `docs/experiments/gate-divergence.md` (cross-provider agreement scaffold).
167
+ - ADR 0004 honesty note: prose-gate reliability is empirical.
168
+
169
+ ### Changed
170
+
171
+ - Positioning: “writable by non-programmers” softened to prose-first / readable;
172
+ production needs developer judgment for tools, hooks, and untrusted inputs.
173
+
174
+ ## [0.5.0] — 2026-07-16
175
+
176
+ ### Added
177
+
178
+ - **Conformance suite** (ADR 0009) — `conformance/cases/*.yaml`:
179
+ implementation-neutral cases (machine + scripted LLM + expected outcome) that
180
+ pin the language semantics; any second interpreter must pass them with its own
181
+ runner. Reference runner in `tests/test_conformance.py` (15 cases).
182
+ - **`mklang lint`** — `check` plus static analysis: dead gates after
183
+ `otherwise`, repair-only states, outputs nobody reads (terminal/judged states
184
+ exempt), template roots nothing provides (`--strict` exits 1 on findings).
185
+ - **Provider adapters as entry-point plugins** — group `mklang.providers`
186
+ (factory `(ProviderConfig) -> LLM`); builtin `anthropic`, unknown names fall
187
+ back to the OpenAI-compatible adapter. Completes the plugin story
188
+ (tools/hooks/providers).
189
+ - **Gated live smoke tests** (`tests/test_live.py`) — opt-in via `MKLANG_LIVE=1`,
190
+ provider-agnostic: they run the config's `active` provider (override with
191
+ `MKLANG_LIVE_PROVIDER`) and skip when its key is missing. All providers,
192
+ Anthropic included, share the same path — no provider-specific test code.
193
+ - **CI + docs site** — extended GitHub Actions workflow (tests, ruff, schema +
194
+ semantic checks, lint, build, gated live smoke on main) and an mkdocs-material
195
+ site assembled from the repo's canonical markdown (`scripts/build-docs.sh`,
196
+ deployed to GitHub Pages).
197
+
198
+ ### Changed
199
+
200
+ - SPEC termination paragraph: the END-reachability validator is enforced (it
201
+ already was — the prose still said "SHOULD").
202
+ - Packaging metadata: classifiers, corrected repository URLs, author contact.
203
+
204
+ ## [0.4.0] — 2026-07-16
205
+
206
+ ### Added
207
+
208
+ - **Human-in-the-loop** (ADR 0008) — with `--hitl` (requires `--checkpoint`), a
209
+ fired `escalate` gate **suspends** at the handler state instead of just routing:
210
+ the envelope records `reason: "escalated"` and `hitl: true`. Reply via
211
+ **`mklang resume ck.json --set human.reply="…"`** — values land in the innermost
212
+ frame's context so the handler can interpolate `{{human.reply}}`. Library API:
213
+ `run(..., escalate_suspend=True)`; mutate `frames[-1]["ctx"]` before resuming.
214
+ Default off — escalate-as-routing (tier cascades) is unaffected; escalate to
215
+ `END` and fan-out branches never suspend. Language unchanged (spec stays 0.2).
216
+
217
+ ## [0.3.0] — 2026-07-16
218
+
219
+ ### Added
220
+
221
+ - **Resumable runs / checkpoints** (ADR 0007) — opt-in: with `--checkpoint PATH`,
222
+ budget exhaustion (`budget-exhausted` / `cost-exhausted`) **suspends** instead of
223
+ halting and writes a JSON checkpoint (frames = blackboard + position + spend per
224
+ `call` level, machine sha256). New subcommand **`mklang resume <checkpoint>`**
225
+ (`--max-tokens`, `--machine`, `--force`) continues as if uninterrupted — golden
226
+ round-trip tested, nested `call` included. New `RunResult` status `"suspended"`
227
+ with `frames`; library API `run(..., suspendable=, resume=)`; CLI exit code 3.
228
+ Fan-out branches never suspend. Language unchanged (spec stays 0.2).
229
+
230
+ ### Fixed
231
+
232
+ - Docs drift pass: SPEC pseudo-schema includes `hook` / `hooks:`; comparison table and
233
+ philosophy updated for host hooks; ADR 0002/0004 no longer refer to code-hooks as
234
+ future-only; ROADMAP test count aligned (~70).
235
+ - Stale `__version__` in `mklang/__init__.py` (was pinned at 0.2.0).
236
+
237
+ ## [0.2.2] — 2026-07-16
238
+
239
+ ### Added
240
+
241
+ - **Code-hook gates** — optional `hook: <name>` on a gate evaluates a host predicate
242
+ `(context, output) -> bool` without the LLM (ADR 0006). Top-level `hooks:`
243
+ declarations; CLI builtins (`auto_approve_ok`, …); example `hook_gates.mk`. Trace
244
+ records `gate_via: hook|llm|otherwise`.
245
+ - **Tool / hook plugin registry** — entry-point groups `mklang.tools` and
246
+ `mklang.hooks`; `load_tool_registry()` / `load_hook_registry()` merge builtins with
247
+ third-party plugins (later keys win). Documented in CONTRIBUTING / patterns.
248
+
249
+ ### Changed
250
+
251
+ - Default runtime provider is **DeepSeek** (`active: deepseek` in
252
+ `config/runtime.example.yaml`); README quickstart and status aligned. Live smoke
253
+ re-verified on DeepSeek (`expense_approval.mk` → `done`).
254
+
255
+ ## [0.2.1] — 2026-07-16
256
+
257
+ Correctness hardening and multi-provider polish on top of the v0.2 core.
258
+
259
+ ### Fixed
260
+
261
+ - **Sub-machine halt propagation** — a `call` whose child halts (budget, fail, …)
262
+ now halts the parent as `call-failed: <child-error>` with nested `sub_trace`,
263
+ instead of continuing as `done` with `result=None`.
264
+ - **Judge sees reasoning** — when `reason: true`, the private chain-of-thought is
265
+ passed to `LLM.judge` (SPEC §4.5 / §6), not only recorded in the trace.
266
+ - **Anthropic adapter parity** — transient retry with backoff, wrap API failures as
267
+ `ProviderError`, apply `temperature` when thinking is off, structured JSON judge
268
+ (shared `parse_choice` with OpenAI-compat).
269
+ - **Pre-run tier validation** — `mklang run` rejects machines that need a tier missing
270
+ from the provider map; engine KeyError messages name the missing tier.
271
+ - **Shared `cost_budget` across `call`** — sub-machines inherit the remaining token
272
+ budget (no unbounded child spend while the parent still looks under budget).
273
+ - **Judge unparseable is no longer silent** — adapters raise `JudgeUnparseable`; the
274
+ engine soft-falls back only to an eligible `otherwise` (trace: `judge_fallback`),
275
+ else halts with `judge-unparseable`.
276
+ - **`over` missing path / wrong type** — hard error (empty list still OK per SPEC).
277
+ - **Fan-out branch `call` halt** — preserves `sub_trace` and child token usage.
278
+ - Engine exception-safety (clean `halt`, isolated fan-out branches); empty-`eligible`
279
+ no longer crashes; `--set` accepts JSON (lists for `over`); `load_registry` skips
280
+ malformed siblings; `.env` discovered from cwd.
281
+
282
+ ### Added
283
+
284
+ - **Formal `tool` states** — a state can invoke a host-registered callable so tool
285
+ observations re-enter the context (real ReAct, not prose-simulated). Optional
286
+ top-level `tools:` block; built-in demo tools (`calc`, `search`).
287
+ - **Cost accounting** — per-step token usage in the trace, a run total, and an
288
+ optional `cost_budget` (halt `cost-exhausted`).
289
+ - **Error taxonomy** — `refusal`, `provider-error`, `call-failed`,
290
+ `judge-unparseable` halt reasons; typed adapter exceptions (`CallFailed`,
291
+ `JudgeUnparseable`, …).
292
+ - **Structured-output judge** — OpenAI-compatible and Anthropic adapters judge via
293
+ JSON `{"choice": N}` with a regex fallback path in `parse_choice`.
294
+ - Per-tier `params` applied to the model (effort / thinking / reasoning_effort),
295
+ best-effort with drop-on-error; pre-run validation in `mklang run`; richer
296
+ `mklang check` (dead states, unproduced `result`, catch-all warning, version
297
+ advisory).
298
+ - Schema bundled as package data; `mklang: "0.2"` field; transient-error retry.
299
+ - Apache-2.0 `LICENSE`, `CONTRIBUTING.md`, this changelog.
300
+ - Quality: golden-trace and cookbook-conformance tests; Anthropic adapter unit tests
301
+ (params / refusal / usage / retry / judge). **Live Anthropic e2e still deferred**
302
+ (no `ANTHROPIC_API_KEY` in the release environment). Live-tested path remains
303
+ DeepSeek.
304
+ - Docs: SPEC header aligned to **0.2**; ROADMAP shipped items synced.
305
+
306
+ ## [0.2.0] — core v0.2 + reference interpreter
307
+
308
+ ### Added
309
+
310
+ - **Language core v0.2**: `reason` (traced chain-of-thought), `accumulate`
311
+ (list-append), fan-out (`sample` / `over`), sub-machine `call`, top-level `result`.
312
+ - Patterns cookbook (`SPEC.md §10`) mapping CoT / ReAct / Reflexion / Self-consistency
313
+ / Tree-of-Thought / Plan-Execute / Debate / Map-Reduce / Router / speculative cascade.
314
+ - **Reference interpreter** (`src/mklang/`): loader + validator, `{{}}` interpolation,
315
+ multi-provider LLM adapters (native Anthropic + generic OpenAI-compatible), run loop,
316
+ nested trace, `mklang run` / `mklang check` CLI. Live-tested on DeepSeek.
317
+ - JSON Schema `oneOf {generative | call}`; examples `self_consistency`, `map_reduce`
318
+ (+ `summarize_doc`), `react`.
319
+
320
+ ## [0.1] — language definition
321
+
322
+ ### Added
323
+
324
+ - The mklang concept: an LLM-driven state machine where each state has four faces
325
+ (`structure`, `prompt`, `execution`, `gates`), gates are transitions, and the LLM is
326
+ the runtime. Provider-agnostic capability tiers (`fast` / `balanced` / `reasoning`).
327
+ - `SPEC.md`, JSON Schema, multi-provider runtime config, examples `triage`, `research`,
328
+ `expense_approval`.
@@ -0,0 +1,95 @@
1
+ # Contributing to mklang
2
+
3
+ Thanks for your interest. mklang is a small, opinionated project: a language spec
4
+ plus a reference interpreter. Keep changes coherent with the design in
5
+ [`SPEC.md`](./SPEC.md) and the decisions recorded in [`docs/adr/`](./docs/adr).
6
+
7
+ ## Dev setup
8
+
9
+ ```bash
10
+ uv run --extra dev pytest -q # unit + conformance (no network — MockLLM)
11
+ MKLANG_LIVE=1 uv run --extra dev pytest -q tests/test_live.py # opt-in live smoke (active provider; MKLANG_LIVE_PROVIDER=… to override)
12
+ uv run --extra dev ruff check src tests
13
+ uv run mklang check examples/*.mk # schema + semantic validation
14
+ uv run mklang lint --strict examples/*.mk # + static analysis
15
+ uv run mklang test examples/triage.mk --script examples/triage.test.yaml # scripted scenarios, no API keys
16
+ ```
17
+
18
+ `pytest` already runs the [conformance suite](./conformance/README.md)
19
+ (`tests/test_conformance.py` over `conformance/cases/*.yaml`). `mklang test` is
20
+ the same case format for **author-facing** scenario scripts next to a machine.
21
+
22
+ Secrets live in `.env` (gitignored); copy `.env.example` and add provider keys for
23
+ live runs. The example runtime defaults to **DeepSeek** (`DEEPSEEK_API_KEY` +
24
+ `active: deepseek`). Never commit a key.
25
+
26
+ ### Plugin tools / hooks / providers
27
+
28
+ Third-party packages can register callables without patching core:
29
+
30
+ ```toml
31
+ # in the plugin package's pyproject.toml
32
+ [project.entry-points."mklang.tools"]
33
+ my_search = "mypkg.tools:search"
34
+
35
+ [project.entry-points."mklang.hooks"]
36
+ amount_ok = "mypkg.hooks:amount_ok"
37
+
38
+ [project.entry-points."mklang.providers"]
39
+ my_vendor = "mypkg.providers:factory" # (ProviderConfig) -> LLM
40
+ ```
41
+
42
+ - Tools: `(dict) -> str` (tool-state observations).
43
+ - Hooks: `(context: dict, output) -> bool` (gate predicates).
44
+ - Providers: factory `(ProviderConfig) -> LLM`; unknown names fall back to the
45
+ OpenAI-compatible adapter.
46
+
47
+ The CLI loads `load_tool_registry()` / `load_hook_registry()` / the provider
48
+ registry (builtins + entry points). Library users may still pass explicit
49
+ `tools=` / `hooks=` to `run()`.
50
+
51
+ ## The change checklist
52
+
53
+ A change to the **language** must land as a coherent set — in this order:
54
+
55
+ 1. **`SPEC.md`** — describe the behavior (the spec is the source of truth).
56
+ 2. **`schema/mklang.schema.json`** — update the structural schema, then re-bundle the
57
+ package copy: `cp schema/mklang.schema.json src/mklang/data/mklang.schema.json`
58
+ (a test asserts the two stay identical).
59
+ 3. **Interpreter** — `src/mklang/` (model, loader/validator, engine, adapters, CLI).
60
+ 4. **Conformance** — if the change touches language semantics (SPEC §5–§7), add or
61
+ update a case under `conformance/cases/` (ADR 0009).
62
+ 5. **Examples** — add/adjust a machine in `examples/` that exercises the feature;
63
+ where gate paths matter, a sibling `*.test.yaml` for `mklang test` is welcome.
64
+ 6. **Tests** — deterministic coverage with `MockLLM` in `tests/`; keep `ruff` clean.
65
+ 7. **Docs** — `README.md`, `docs/patterns.md`, `CHANGELOG.md`, and `ROADMAP.md`.
66
+
67
+ A change to the **interpreter only** (no language change) skips steps 1–2 and 4
68
+ unless the host tooling surface needs a new conformance-facing scripted binding.
69
+
70
+ ## Design decisions (ADRs)
71
+
72
+ Non-trivial or contentious decisions get a short ADR in `docs/adr/NNNN-title.md`
73
+ (Context / Decision / Consequences). See the existing ADRs in `docs/adr/`
74
+ (0001–0011) for the format. Reference the ADR in your PR.
75
+
76
+ ## Versioning
77
+
78
+ - **Spec version** (`mklang:` field) changes when the _language_ changes.
79
+ - **Package version** (`pyproject.toml`, SemVer) changes when the _interpreter/tooling_
80
+ changes. Record both in `CHANGELOG.md`.
81
+
82
+ ## Releases
83
+
84
+ Releases are provenance-bound: update `pyproject.toml` and `mklang.__version__`
85
+ together, record the package release in `CHANGELOG.md`, and publish a GitHub
86
+ Release whose tag is exactly `v<package-version>`. The release workflow runs the
87
+ offline suite, strict docs/package checks, and the required live-provider gate;
88
+ only its previously tested artifacts reach PyPI through the protected `pypi`
89
+ environment and Trusted Publishing. Do not upload a locally rebuilt artifact for
90
+ an existing tag.
91
+
92
+ ## Non-goals (don't propose these)
93
+
94
+ Pinning a concrete provider/model inside a `.mk` — machines route by capability tier
95
+ only (ADR 0003). See `SPEC.md §9` for the current non-goals.