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.
Files changed (44) hide show
  1. {lattice_cli-0.3.0/src/lattice_cli.egg-info → lattice_cli-0.3.1}/PKG-INFO +25 -1
  2. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/README.md +24 -0
  3. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/pyproject.toml +1 -1
  4. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/__init__.py +1 -1
  5. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/agent.py +51 -1
  6. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/cli.py +1 -1
  7. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/parser.py +32 -1
  8. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/claude/lattice-workflow/SKILL.md +3 -1
  9. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/SKILL.md +3 -1
  10. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/generic/lattice-workflow/SKILL.md +3 -1
  11. {lattice_cli-0.3.0 → lattice_cli-0.3.1/src/lattice_cli.egg-info}/PKG-INFO +25 -1
  12. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/SOURCES.txt +2 -0
  13. lattice_cli-0.3.1/tests/test_constraint_type_errors.py +30 -0
  14. lattice_cli-0.3.1/tests/test_docs_examples.py +58 -0
  15. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/LICENSE +0 -0
  16. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/setup.cfg +0 -0
  17. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/__main__.py +0 -0
  18. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/constraints.py +0 -0
  19. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/formatters.py +0 -0
  20. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/ipog.py +0 -0
  21. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/model.py +0 -0
  22. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/claude/lattice-workflow/references/modeling-rules.md +0 -0
  23. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/claude/lattice-workflow/references/worked-examples.md +0 -0
  24. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/agents/openai.yaml +0 -0
  25. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/references/modeling-rules.md +0 -0
  26. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/codex/lattice-workflow/references/worked-examples.md +0 -0
  27. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/generic/lattice-workflow/references/modeling-rules.md +0 -0
  28. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/resources/generic/lattice-workflow/references/worked-examples.md +0 -0
  29. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice/schema_text.py +0 -0
  30. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/dependency_links.txt +0 -0
  31. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/entry_points.txt +0 -0
  32. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/requires.txt +0 -0
  33. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/src/lattice_cli.egg-info/top_level.txt +0 -0
  34. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_agent_bootstrap_matrix.py +0 -0
  35. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_cli.py +0 -0
  36. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_constraints.py +0 -0
  37. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_examples.py +0 -0
  38. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_ipog.py +0 -0
  39. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_model.py +0 -0
  40. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_parser.py +0 -0
  41. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_schema_guidance_matrix.py +0 -0
  42. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_schema_text.py +0 -0
  43. {lattice_cli-0.3.0 → lattice_cli-0.3.1}/tests/test_self_hosting_matrix.py +0 -0
  44. {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.0
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.0"
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"
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.3.0"
5
+ __version__ = "0.3.1"
@@ -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)
@@ -389,7 +389,7 @@ def _run_agent_command(args: argparse.Namespace) -> int:
389
389
  )
390
390
  )
391
391
  else:
392
- print(GENERIC_AGENT_INSTRUCTIONS)
392
+ print(GENERIC_AGENT_INSTRUCTIONS, end="")
393
393
  return 0
394
394
 
395
395
  if args.agent_command == "doctor":
@@ -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
 
@@ -35,7 +35,9 @@ parameters:
35
35
  payment: [card, apple_pay, paypal]
36
36
  cart: [empty, single, multi]
37
37
  constraints:
38
- - invalid_pair: {payment: apple_pay, cart: empty}
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):
@@ -35,7 +35,9 @@ parameters:
35
35
  payment: [card, apple_pay, paypal]
36
36
  cart: [empty, single, multi]
37
37
  constraints:
38
- - invalid_pair: {payment: apple_pay, cart: empty}
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):
@@ -35,7 +35,9 @@ parameters:
35
35
  payment: [card, apple_pay, paypal]
36
36
  cart: [empty, single, multi]
37
37
  constraints:
38
- - invalid_pair: {payment: apple_pay, cart: empty}
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.0
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