lattice-cli 0.3.0__tar.gz → 0.3.1__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.
- {lattice_cli-0.3.0/src/lattice_cli.egg-info → lattice_cli-0.3.1}/PKG-INFO +25 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/README.md +24 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/pyproject.toml +1 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/__init__.py +1 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/agent.py +51 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/cli.py +1 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/parser.py +32 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/claude/lattice-workflow/SKILL.md +3 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/SKILL.md +3 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/generic/lattice-workflow/SKILL.md +3 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1/src/lattice_cli.egg-info}/PKG-INFO +25 -1
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/SOURCES.txt +2 -0
- lattice_cli-0.3.1/tests/test_constraint_type_errors.py +30 -0
- lattice_cli-0.3.1/tests/test_docs_examples.py +58 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/LICENSE +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/setup.cfg +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/__main__.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/constraints.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/formatters.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/ipog.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/model.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/claude/lattice-workflow/references/modeling-rules.md +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/claude/lattice-workflow/references/worked-examples.md +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/agents/openai.yaml +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/references/modeling-rules.md +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/references/worked-examples.md +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/generic/lattice-workflow/references/modeling-rules.md +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/generic/lattice-workflow/references/worked-examples.md +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/schema_text.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/dependency_links.txt +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/entry_points.txt +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/requires.txt +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/top_level.txt +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_agent_bootstrap_matrix.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_cli.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_constraints.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_examples.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_ipog.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_model.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_parser.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_schema_guidance_matrix.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_schema_text.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_self_hosting_matrix.py +0 -0
- {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_yaml_format.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lattice-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Deterministic combinatorial coverage for agent workflows and constrained variant surfaces.
|
|
5
5
|
Author-email: Tyler Klose <tylerklose@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -143,6 +143,30 @@ the invalid rule needs multiple antecedents.
|
|
|
143
143
|
|
|
144
144
|
`conditional` parameters are first-class schema features. The parser expands them into concrete parameter domains and the constraint engine enforces `N/A` semantics automatically.
|
|
145
145
|
|
|
146
|
+
A schema with two constraints:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
model_name: checkout
|
|
150
|
+
strength: 2
|
|
151
|
+
parameters:
|
|
152
|
+
payment_method: [credit_card, paypal, apple_pay]
|
|
153
|
+
region: [US, UK, DE]
|
|
154
|
+
browser:
|
|
155
|
+
values: [chrome, safari, firefox]
|
|
156
|
+
weights: [0.6, 0.3, 0.1]
|
|
157
|
+
constraints:
|
|
158
|
+
- type: invalid_pair
|
|
159
|
+
if: { parameter: payment_method, value: apple_pay }
|
|
160
|
+
then_not: { parameter: browser, value: firefox }
|
|
161
|
+
- type: conditional
|
|
162
|
+
parameter: card_type
|
|
163
|
+
values: [visa, mastercard]
|
|
164
|
+
parent: payment_method
|
|
165
|
+
applies_when: [credit_card]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Every constraint needs a `type` key. Run `lattice agent instructions` for an example of each type.
|
|
169
|
+
|
|
146
170
|
### Values keep their text
|
|
147
171
|
|
|
148
172
|
Every value in a schema (the model name, parameter names and values, and constraint references) is used exactly as written. In YAML, unquoted `1.10`, `0x1F`, `.inf` or `2026-10-26T10:00:00Z` stay as typed rather than being turned into numbers or dates. The exceptions:
|
|
@@ -115,6 +115,30 @@ the invalid rule needs multiple antecedents.
|
|
|
115
115
|
|
|
116
116
|
`conditional` parameters are first-class schema features. The parser expands them into concrete parameter domains and the constraint engine enforces `N/A` semantics automatically.
|
|
117
117
|
|
|
118
|
+
A schema with two constraints:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
model_name: checkout
|
|
122
|
+
strength: 2
|
|
123
|
+
parameters:
|
|
124
|
+
payment_method: [credit_card, paypal, apple_pay]
|
|
125
|
+
region: [US, UK, DE]
|
|
126
|
+
browser:
|
|
127
|
+
values: [chrome, safari, firefox]
|
|
128
|
+
weights: [0.6, 0.3, 0.1]
|
|
129
|
+
constraints:
|
|
130
|
+
- type: invalid_pair
|
|
131
|
+
if: { parameter: payment_method, value: apple_pay }
|
|
132
|
+
then_not: { parameter: browser, value: firefox }
|
|
133
|
+
- type: conditional
|
|
134
|
+
parameter: card_type
|
|
135
|
+
values: [visa, mastercard]
|
|
136
|
+
parent: payment_method
|
|
137
|
+
applies_when: [credit_card]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Every constraint needs a `type` key. Run `lattice agent instructions` for an example of each type.
|
|
141
|
+
|
|
118
142
|
### Values keep their text
|
|
119
143
|
|
|
120
144
|
Every value in a schema (the model name, parameter names and values, and constraint references) is used exactly as written. In YAML, unquoted `1.10`, `0x1F`, `.inf` or `2026-10-26T10:00:00Z` stay as typed rather than being turned into numbers or dates. The exceptions:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lattice-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Deterministic combinatorial coverage for agent workflows and constrained variant surfaces."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -25,6 +25,56 @@ DEFAULT_PROMPT = (
|
|
|
25
25
|
"rows; and use the rows to strengthen the plan, tests, fixtures, or visual review."
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
+
SCHEMA_REFERENCE = """
|
|
29
|
+
Schema format, with one example of each constraint type:
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
model_name: checkout
|
|
33
|
+
strength: 2
|
|
34
|
+
parameters:
|
|
35
|
+
user: [guest, member]
|
|
36
|
+
payment: [card, paypal, apple_pay, invoice]
|
|
37
|
+
device: [desktop, ios, android]
|
|
38
|
+
region: [us, eu]
|
|
39
|
+
currency: [usd, eur]
|
|
40
|
+
shipping: [standard, express, pickup]
|
|
41
|
+
constraints:
|
|
42
|
+
# invalid_pair: these two assignments never appear in the same row.
|
|
43
|
+
- type: invalid_pair
|
|
44
|
+
if: { parameter: payment, value: apple_pay }
|
|
45
|
+
then_not: { parameter: device, value: android }
|
|
46
|
+
# bidirectional: each assignment requires the other.
|
|
47
|
+
- type: bidirectional
|
|
48
|
+
pair:
|
|
49
|
+
- { parameter: region, value: eu }
|
|
50
|
+
- { parameter: currency, value: eur }
|
|
51
|
+
# forward_dep: the source requires the target; the target may appear elsewhere.
|
|
52
|
+
- type: forward_dep
|
|
53
|
+
source: { parameter: shipping, value: pickup }
|
|
54
|
+
target: { parameter: region, value: us }
|
|
55
|
+
# conditional: defines a child parameter that only applies for some parent values.
|
|
56
|
+
- type: conditional
|
|
57
|
+
parameter: card_type
|
|
58
|
+
values: [visa, mastercard]
|
|
59
|
+
parent: payment
|
|
60
|
+
applies_when: [card]
|
|
61
|
+
# forced: this combination must appear in at least one row.
|
|
62
|
+
- type: forced
|
|
63
|
+
assignments:
|
|
64
|
+
- { parameter: user, value: guest }
|
|
65
|
+
- { parameter: device, value: ios }
|
|
66
|
+
- { parameter: shipping, value: express }
|
|
67
|
+
# higher_order: the last assignment is excluded only when all of if_all hold.
|
|
68
|
+
- type: higher_order
|
|
69
|
+
if_all:
|
|
70
|
+
- { parameter: user, value: guest }
|
|
71
|
+
- { parameter: region, value: eu }
|
|
72
|
+
then_not: { parameter: shipping, value: pickup }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Every constraint needs a `type` key. Each assignment is `{ parameter: <name>, value: <value> }`.
|
|
76
|
+
"""
|
|
77
|
+
|
|
28
78
|
GENERIC_AGENT_INSTRUCTIONS = f"""# Lattice Agent Instructions
|
|
29
79
|
|
|
30
80
|
Lattice is harness-agnostic. Use these instructions in OpenClaw, Hermes, Codex, xAI-backed agents, Claude Code, or any agent that can run shell commands.
|
|
@@ -49,7 +99,7 @@ Prefer JSON or YAML on stdin:
|
|
|
49
99
|
cat schema.json | lattice validate
|
|
50
100
|
cat schema.json | lattice generate
|
|
51
101
|
```
|
|
52
|
-
"""
|
|
102
|
+
""" + SCHEMA_REFERENCE
|
|
53
103
|
|
|
54
104
|
|
|
55
105
|
@dataclass(frozen=True)
|
|
@@ -204,6 +204,12 @@ def _parse_constraints(
|
|
|
204
204
|
continue
|
|
205
205
|
|
|
206
206
|
constraint_type = raw_constraint.get("type")
|
|
207
|
+
if constraint_type is None:
|
|
208
|
+
errors.append(_missing_type_message(index, raw_constraint))
|
|
209
|
+
continue
|
|
210
|
+
if not isinstance(constraint_type, str):
|
|
211
|
+
errors.append(f"`constraints[{index}].type` must be a string, one of: {_CONSTRAINT_TYPE_LIST}.")
|
|
212
|
+
continue
|
|
207
213
|
if constraint_type == "invalid_pair":
|
|
208
214
|
if_assignment = _parse_assignment(raw_constraint.get("if"), parameters, f"`constraints[{index}].if`", errors)
|
|
209
215
|
then_not = _parse_assignment(raw_constraint.get("then_not"), parameters, f"`constraints[{index}].then_not`", errors)
|
|
@@ -316,7 +322,7 @@ def _parse_constraints(
|
|
|
316
322
|
parsed.append(HigherOrderConstraint(if_all=tuple(parsed_if_all), then_not=parsed_then_not))
|
|
317
323
|
continue
|
|
318
324
|
|
|
319
|
-
errors.append(f"`constraints[{index}]` has unknown type `{constraint_type}`.")
|
|
325
|
+
errors.append(f"`constraints[{index}]` has unknown type `{constraint_type}`. Use one of: {_CONSTRAINT_TYPE_LIST}.")
|
|
320
326
|
|
|
321
327
|
return parsed
|
|
322
328
|
|
|
@@ -388,6 +394,31 @@ def _parse_weights(
|
|
|
388
394
|
return tuple(parsed)
|
|
389
395
|
|
|
390
396
|
|
|
397
|
+
# Each constraint type and the keys written beside `type`.
|
|
398
|
+
_CONSTRAINT_FIELDS = {
|
|
399
|
+
"invalid_pair": "`if` and `then_not`",
|
|
400
|
+
"bidirectional": "`pair`",
|
|
401
|
+
"forward_dep": "`source` and `target`",
|
|
402
|
+
"conditional": "`parameter`, `values`, `parent` and `applies_when`",
|
|
403
|
+
"forced": "`assignments`",
|
|
404
|
+
"higher_order": "`if_all` and `then_not`",
|
|
405
|
+
}
|
|
406
|
+
_CONSTRAINT_TYPE_LIST = ", ".join(f"`{name}`" for name in _CONSTRAINT_FIELDS)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _missing_type_message(index: int, raw_constraint: dict[Any, Any]) -> str:
|
|
410
|
+
"""Explains a constraint without `type`. A type name used as a key, as in
|
|
411
|
+
`- invalid_pair: {...}`, gets the correct form spelled out."""
|
|
412
|
+
shorthand = next((key for key in raw_constraint if isinstance(key, str) and key in _CONSTRAINT_FIELDS), None)
|
|
413
|
+
if shorthand is None:
|
|
414
|
+
return f"`constraints[{index}]` is missing `type`. Use one of: {_CONSTRAINT_TYPE_LIST}."
|
|
415
|
+
return (
|
|
416
|
+
f"`constraints[{index}]` is missing `type`. Write `type: {shorthand}` as its own key, "
|
|
417
|
+
f"with {_CONSTRAINT_FIELDS[shorthand]} beside it. "
|
|
418
|
+
"Run `lattice agent instructions` for an example of each type."
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
391
422
|
_DECIMAL_TEXT = re.compile(r"[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?")
|
|
392
423
|
_INTEGER_TEXT = re.compile(r"[-+]?[0-9]+")
|
|
393
424
|
|
{lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/claude/lattice-workflow/SKILL.md
RENAMED
|
@@ -35,7 +35,9 @@ parameters:
|
|
|
35
35
|
payment: [card, apple_pay, paypal]
|
|
36
36
|
cart: [empty, single, multi]
|
|
37
37
|
constraints:
|
|
38
|
-
-
|
|
38
|
+
- type: invalid_pair
|
|
39
|
+
if: { parameter: payment, value: apple_pay }
|
|
40
|
+
then_not: { parameter: cart, value: empty }
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
One generated row (JSON):
|
{lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/SKILL.md
RENAMED
|
@@ -35,7 +35,9 @@ parameters:
|
|
|
35
35
|
payment: [card, apple_pay, paypal]
|
|
36
36
|
cart: [empty, single, multi]
|
|
37
37
|
constraints:
|
|
38
|
-
-
|
|
38
|
+
- type: invalid_pair
|
|
39
|
+
if: { parameter: payment, value: apple_pay }
|
|
40
|
+
then_not: { parameter: cart, value: empty }
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
One generated row (JSON):
|
{lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/generic/lattice-workflow/SKILL.md
RENAMED
|
@@ -35,7 +35,9 @@ parameters:
|
|
|
35
35
|
payment: [card, apple_pay, paypal]
|
|
36
36
|
cart: [empty, single, multi]
|
|
37
37
|
constraints:
|
|
38
|
-
-
|
|
38
|
+
- type: invalid_pair
|
|
39
|
+
if: { parameter: payment, value: apple_pay }
|
|
40
|
+
then_not: { parameter: cart, value: empty }
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
One generated row (JSON):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lattice-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Deterministic combinatorial coverage for agent workflows and constrained variant surfaces.
|
|
5
5
|
Author-email: Tyler Klose <tylerklose@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -143,6 +143,30 @@ the invalid rule needs multiple antecedents.
|
|
|
143
143
|
|
|
144
144
|
`conditional` parameters are first-class schema features. The parser expands them into concrete parameter domains and the constraint engine enforces `N/A` semantics automatically.
|
|
145
145
|
|
|
146
|
+
A schema with two constraints:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
model_name: checkout
|
|
150
|
+
strength: 2
|
|
151
|
+
parameters:
|
|
152
|
+
payment_method: [credit_card, paypal, apple_pay]
|
|
153
|
+
region: [US, UK, DE]
|
|
154
|
+
browser:
|
|
155
|
+
values: [chrome, safari, firefox]
|
|
156
|
+
weights: [0.6, 0.3, 0.1]
|
|
157
|
+
constraints:
|
|
158
|
+
- type: invalid_pair
|
|
159
|
+
if: { parameter: payment_method, value: apple_pay }
|
|
160
|
+
then_not: { parameter: browser, value: firefox }
|
|
161
|
+
- type: conditional
|
|
162
|
+
parameter: card_type
|
|
163
|
+
values: [visa, mastercard]
|
|
164
|
+
parent: payment_method
|
|
165
|
+
applies_when: [credit_card]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Every constraint needs a `type` key. Run `lattice agent instructions` for an example of each type.
|
|
169
|
+
|
|
146
170
|
### Values keep their text
|
|
147
171
|
|
|
148
172
|
Every value in a schema (the model name, parameter names and values, and constraint references) is used exactly as written. In YAML, unquoted `1.10`, `0x1F`, `.inf` or `2026-10-26T10:00:00Z` stay as typed rather than being turned into numbers or dates. The exceptions:
|
|
@@ -29,7 +29,9 @@ src/lattice_cli.egg-info/requires.txt
|
|
|
29
29
|
src/lattice_cli.egg-info/top_level.txt
|
|
30
30
|
tests/test_agent_bootstrap_matrix.py
|
|
31
31
|
tests/test_cli.py
|
|
32
|
+
tests/test_constraint_type_errors.py
|
|
32
33
|
tests/test_constraints.py
|
|
34
|
+
tests/test_docs_examples.py
|
|
33
35
|
tests/test_examples.py
|
|
34
36
|
tests/test_ipog.py
|
|
35
37
|
tests/test_model.py
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
9
|
+
sys.path.insert(0, str(ROOT / "src"))
|
|
10
|
+
|
|
11
|
+
from lattice.parser import ValidationError, load_model
|
|
12
|
+
|
|
13
|
+
FIXTURES = ROOT / "tests" / "fixtures"
|
|
14
|
+
# Shared with the Ruby implementation (test/fixtures/invalid and
|
|
15
|
+
# test/fixtures/golden/errors there), which must print the same messages.
|
|
16
|
+
CASES = ["constraint_missing_type", "constraint_type_shorthand", "constraint_type_not_string", "unknown_constraint_type"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConstraintTypeErrorTests(unittest.TestCase):
|
|
20
|
+
def test_messages_match_goldens(self) -> None:
|
|
21
|
+
for name in CASES:
|
|
22
|
+
with self.subTest(name=name):
|
|
23
|
+
golden = json.loads((FIXTURES / "golden_errors" / f"{name}.json").read_text())
|
|
24
|
+
with self.assertRaises(ValidationError) as raised:
|
|
25
|
+
load_model(str(FIXTURES / "invalid" / f"{name}.json"))
|
|
26
|
+
self.assertEqual(golden["errors"], raised.exception.errors)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
unittest.main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
9
|
+
sys.path.insert(0, str(ROOT / "src"))
|
|
10
|
+
|
|
11
|
+
from lattice.agent import GENERIC_AGENT_INSTRUCTIONS
|
|
12
|
+
from lattice.constraints import ConstraintEngine
|
|
13
|
+
from lattice.parser import _CONSTRAINT_FIELDS, ValidationError, parse_model_data
|
|
14
|
+
from lattice.schema_text import load_json, load_yaml
|
|
15
|
+
|
|
16
|
+
# Every schema shown to users or agents must be one Lattice accepts. Checks
|
|
17
|
+
# each ```yaml or ```json block that defines `parameters` in the shipped skill
|
|
18
|
+
# files, the README and `lattice agent instructions`.
|
|
19
|
+
FENCED_BLOCK = re.compile(r"^```(yaml|json)\n(.*?)^```", re.M | re.S)
|
|
20
|
+
|
|
21
|
+
SOURCES = {
|
|
22
|
+
str(path.relative_to(ROOT)): path.read_text()
|
|
23
|
+
for path in sorted([*(ROOT / "src/lattice/resources").rglob("*.md"), *(ROOT / ".codex").rglob("*.md")])
|
|
24
|
+
}
|
|
25
|
+
SOURCES["README.md"] = (ROOT / "README.md").read_text()
|
|
26
|
+
SOURCES["lattice agent instructions"] = GENERIC_AGENT_INSTRUCTIONS
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def schema_blocks():
|
|
30
|
+
for name, text in SOURCES.items():
|
|
31
|
+
for index, match in enumerate(FENCED_BLOCK.finditer(text), start=1):
|
|
32
|
+
if "parameters" in match.group(2):
|
|
33
|
+
yield name, index, match.group(1), match.group(2)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DocsExamplesTests(unittest.TestCase):
|
|
37
|
+
def test_finds_a_schema_in_every_skill_readme_and_the_agent_instructions(self) -> None:
|
|
38
|
+
found = {name for name, _index, _language, _body in schema_blocks()}
|
|
39
|
+
expected = {name for name in SOURCES if name.endswith("SKILL.md")} | {"README.md", "lattice agent instructions"}
|
|
40
|
+
self.assertEqual(set(), expected - found)
|
|
41
|
+
|
|
42
|
+
def test_agent_instructions_show_every_constraint_type(self) -> None:
|
|
43
|
+
for constraint_type in _CONSTRAINT_FIELDS:
|
|
44
|
+
self.assertIn(f"- type: {constraint_type}\n", GENERIC_AGENT_INSTRUCTIONS)
|
|
45
|
+
|
|
46
|
+
def test_every_schema_example_is_valid(self) -> None:
|
|
47
|
+
for name, index, language, body in schema_blocks():
|
|
48
|
+
with self.subTest(source=name, block=index):
|
|
49
|
+
data = load_json(body) if language == "json" else load_yaml(body)
|
|
50
|
+
try:
|
|
51
|
+
model = parse_model_data(data)
|
|
52
|
+
except ValidationError as error:
|
|
53
|
+
self.fail(f"{name} block {index} is not a valid schema: {' '.join(error.errors)}")
|
|
54
|
+
self.assertTrue(ConstraintEngine(model).can_complete({}), f"{name} block {index} has no valid rows")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|