ai-forge-cli 0.1.4__tar.gz → 0.1.5__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.
- {ai_forge_cli-0.1.4/src/ai_forge_cli.egg-info → ai_forge_cli-0.1.5}/PKG-INFO +1 -1
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/pyproject.toml +1 -1
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5/src/ai_forge_cli.egg-info}/PKG-INFO +1 -1
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/__init__.py +1 -1
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/walker.py +50 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/tests/test_walker.py +138 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/LICENSE +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/README.md +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/setup.cfg +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/SOURCES.txt +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/dependency_links.txt +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/entry_points.txt +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/requires.txt +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/top_level.txt +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/__main__.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/bundle.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/commands/__init__.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/commands/base.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/commands/context.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/commands/find.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/commands/init.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/commands/inspect.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/commands/list_cmd.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/commands/update.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/common.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/forge.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/src/cli/index.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/tests/test_cli.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/tests/test_find.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/tests/test_index.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/tests/test_init.py +0 -0
- {ai_forge_cli-0.1.4 → ai_forge_cli-0.1.5}/tests/test_update.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Forge CLI — context walker for the L0-L5 spec system."""
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.5"
|
|
@@ -113,6 +113,13 @@ def expand_atom(idx: Index, atom: Entry, unresolved: list[str]) -> OrderedDict[s
|
|
|
113
113
|
for aid in sorted(called_atom_ids):
|
|
114
114
|
called_sigs[aid] = _atom_signature(idx, aid, unresolved)
|
|
115
115
|
|
|
116
|
+
shaped_fields = OrderedDict()
|
|
117
|
+
shaped_fields[atom.id] = _atom_shaped_fields(atom.data)
|
|
118
|
+
for aid in sorted(called_atom_ids):
|
|
119
|
+
entry = idx.get(aid)
|
|
120
|
+
if entry and entry.kind == "atom":
|
|
121
|
+
shaped_fields[aid] = _atom_shaped_fields(entry.data)
|
|
122
|
+
|
|
116
123
|
l0_slice = _build_l0_slice(
|
|
117
124
|
idx,
|
|
118
125
|
type_ids=type_ids,
|
|
@@ -134,6 +141,7 @@ def expand_atom(idx: Index, atom: Entry, unresolved: list[str]) -> OrderedDict[s
|
|
|
134
141
|
bundle["policies_applied"] = policies_applied
|
|
135
142
|
bundle["l3_atom"] = atom.data
|
|
136
143
|
bundle["called_atom_signatures"] = called_sigs
|
|
144
|
+
bundle["shaped_fields"] = shaped_fields
|
|
137
145
|
if training_artifact is not None:
|
|
138
146
|
bundle["training_artifact"] = training_artifact
|
|
139
147
|
bundle["l4_callers"] = callers
|
|
@@ -238,6 +246,7 @@ def expand_journey(idx: Index, journey: Entry, unresolved: list[str]) -> Ordered
|
|
|
238
246
|
|
|
239
247
|
handler_atoms: OrderedDict[str, Any] = OrderedDict()
|
|
240
248
|
invoked_flows: OrderedDict[str, Any] = OrderedDict()
|
|
249
|
+
shaped_fields: OrderedDict[str, Any] = OrderedDict()
|
|
241
250
|
aggregated_type_ids: set[str] = set()
|
|
242
251
|
aggregated_errors: set[str] = set()
|
|
243
252
|
aggregated_constants: set[str] = set()
|
|
@@ -250,6 +259,7 @@ def expand_journey(idx: Index, journey: Entry, unresolved: list[str]) -> Ordered
|
|
|
250
259
|
entry = idx.get(aid)
|
|
251
260
|
if entry:
|
|
252
261
|
handler_atoms[aid] = entry.data
|
|
262
|
+
shaped_fields[aid] = _atom_shaped_fields(entry.data)
|
|
253
263
|
spec = entry.data.get("spec") or {}
|
|
254
264
|
aggregated_type_ids |= _collect_type_ids(spec)
|
|
255
265
|
aggregated_errors |= _collect_error_codes(spec)
|
|
@@ -297,6 +307,7 @@ def expand_journey(idx: Index, journey: Entry, unresolved: list[str]) -> Ordered
|
|
|
297
307
|
bundle["l4_journey"] = journey.data
|
|
298
308
|
bundle["handler_atoms"] = handler_atoms
|
|
299
309
|
bundle["invoked_orchestrations"] = invoked_flows
|
|
310
|
+
bundle["shaped_fields"] = shaped_fields
|
|
300
311
|
bundle["l5_operations"] = idx.l5
|
|
301
312
|
return bundle
|
|
302
313
|
|
|
@@ -309,6 +320,7 @@ def expand_flow(idx: Index, flow: Entry, unresolved: list[str]) -> OrderedDict[s
|
|
|
309
320
|
sequence = flow.data.get("sequence") or []
|
|
310
321
|
|
|
311
322
|
step_sigs: OrderedDict[str, Any] = OrderedDict()
|
|
323
|
+
shaped_fields: OrderedDict[str, Any] = OrderedDict()
|
|
312
324
|
aggregated_type_ids: set[str] = set()
|
|
313
325
|
aggregated_errors: set[str] = set()
|
|
314
326
|
aggregated_markers: set[str] = set()
|
|
@@ -326,6 +338,7 @@ def expand_flow(idx: Index, flow: Entry, unresolved: list[str]) -> OrderedDict[s
|
|
|
326
338
|
step_sigs[aid] = _atom_signature(idx, aid, unresolved)
|
|
327
339
|
entry = idx.get(aid)
|
|
328
340
|
if entry and entry.kind == "atom":
|
|
341
|
+
shaped_fields[aid] = _atom_shaped_fields(entry.data)
|
|
329
342
|
spec = entry.data.get("spec") or {}
|
|
330
343
|
aggregated_type_ids |= _collect_type_ids(spec)
|
|
331
344
|
aggregated_errors |= _collect_error_codes(spec)
|
|
@@ -354,6 +367,7 @@ def expand_flow(idx: Index, flow: Entry, unresolved: list[str]) -> OrderedDict[s
|
|
|
354
367
|
bundle["l2_entry_points"] = entry_points
|
|
355
368
|
bundle["l4_orchestration"] = flow.data
|
|
356
369
|
bundle["step_atom_signatures"] = step_sigs
|
|
370
|
+
bundle["shaped_fields"] = shaped_fields
|
|
357
371
|
bundle["l5_operations"] = idx.l5
|
|
358
372
|
return bundle
|
|
359
373
|
|
|
@@ -392,8 +406,12 @@ def expand_artifact(idx: Index, artifact: Entry, unresolved: list[str]) -> Order
|
|
|
392
406
|
unresolved.append(sid)
|
|
393
407
|
|
|
394
408
|
consumers = OrderedDict()
|
|
409
|
+
shaped_fields: OrderedDict[str, Any] = OrderedDict()
|
|
395
410
|
for cid in artifact.data.get("consumers") or []:
|
|
396
411
|
consumers[cid] = _atom_signature(idx, cid, unresolved)
|
|
412
|
+
entry = idx.get(cid)
|
|
413
|
+
if entry and entry.kind == "atom":
|
|
414
|
+
shaped_fields[cid] = _atom_shaped_fields(entry.data)
|
|
397
415
|
|
|
398
416
|
l0_slice = _build_l0_slice(
|
|
399
417
|
idx,
|
|
@@ -415,6 +433,7 @@ def expand_artifact(idx: Index, artifact: Entry, unresolved: list[str]) -> Order
|
|
|
415
433
|
bundle["producer_atom_signature"] = producer_sig
|
|
416
434
|
bundle["source_artifacts"] = source_artifacts
|
|
417
435
|
bundle["consumer_signatures"] = consumers
|
|
436
|
+
bundle["shaped_fields"] = shaped_fields
|
|
418
437
|
return bundle
|
|
419
438
|
|
|
420
439
|
|
|
@@ -437,6 +456,37 @@ def _atom_signature(idx: Index, atom_id: str, unresolved: list[str]) -> dict[str
|
|
|
437
456
|
}
|
|
438
457
|
|
|
439
458
|
|
|
459
|
+
def _atom_shaped_fields(atom_data: dict[str, Any]) -> OrderedDict[str, Any]:
|
|
460
|
+
spec = atom_data.get("spec") or {}
|
|
461
|
+
shaped = OrderedDict()
|
|
462
|
+
for section_name, fields in (
|
|
463
|
+
("input", spec.get("input")),
|
|
464
|
+
("output.success", (spec.get("output") or {}).get("success")),
|
|
465
|
+
("props", spec.get("props")),
|
|
466
|
+
("local_state", spec.get("local_state")),
|
|
467
|
+
("input_distribution", spec.get("input_distribution")),
|
|
468
|
+
("output_distribution", spec.get("output_distribution")),
|
|
469
|
+
):
|
|
470
|
+
_collect_shaped_fields(fields, section_name, shaped)
|
|
471
|
+
return shaped
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _collect_shaped_fields(obj: Any, prefix: str, out: OrderedDict[str, Any]) -> None:
|
|
475
|
+
if not isinstance(obj, dict):
|
|
476
|
+
return
|
|
477
|
+
if "type" in obj and "shape" in obj and isinstance(obj["shape"], dict):
|
|
478
|
+
out[prefix] = {
|
|
479
|
+
"type": obj.get("type"),
|
|
480
|
+
"nullable": obj.get("nullable"),
|
|
481
|
+
"shape": obj.get("shape"),
|
|
482
|
+
}
|
|
483
|
+
return
|
|
484
|
+
for key, value in obj.items():
|
|
485
|
+
if isinstance(value, dict):
|
|
486
|
+
child = f"{prefix}.{key}" if prefix else key
|
|
487
|
+
_collect_shaped_fields(value, child, out)
|
|
488
|
+
|
|
489
|
+
|
|
440
490
|
def _resolve(idx: Index, entity_id: str, expected_kind: str, unresolved: list[str]) -> Any:
|
|
441
491
|
entry = idx.get(entity_id)
|
|
442
492
|
if entry is None or entry.kind != expected_kind:
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""Walker tests against src/example/ fixtures."""
|
|
2
2
|
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
3
5
|
from cli import walker
|
|
6
|
+
from cli.index import Entry, Index
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
# ---------- Atom ----------
|
|
@@ -66,6 +69,141 @@ def test_atom_unresolved_signatures(idx):
|
|
|
66
69
|
assert aid in unresolved
|
|
67
70
|
|
|
68
71
|
|
|
72
|
+
def test_atom_context_preserves_shape_metadata_for_target_and_called_signatures():
|
|
73
|
+
target_input_shape = {
|
|
74
|
+
"kind": "tagged_union",
|
|
75
|
+
"discriminator": "form",
|
|
76
|
+
"variants": [
|
|
77
|
+
{"form": "broadcast", "value": "all"},
|
|
78
|
+
{"form": "session", "pattern": "session:<int>"},
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
target_output_shape = {
|
|
82
|
+
"kind": "enum",
|
|
83
|
+
"values": ["queued", "sent"],
|
|
84
|
+
}
|
|
85
|
+
called_output_shape = {
|
|
86
|
+
"kind": "json_schema",
|
|
87
|
+
"schema": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": {"type": "integer"},
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
idx = Index(spec_dir=Path("."))
|
|
94
|
+
idx.l1 = {"failure": {"propagation": {}}}
|
|
95
|
+
idx.l5 = {}
|
|
96
|
+
idx.entries = {
|
|
97
|
+
"MSG": Entry(
|
|
98
|
+
id="MSG",
|
|
99
|
+
kind="module",
|
|
100
|
+
data={
|
|
101
|
+
"id": "MSG",
|
|
102
|
+
"owned_atoms": ["atm.msg.send_message", "atm.msg.resolve_targets"],
|
|
103
|
+
"policies": [],
|
|
104
|
+
"interface": {"entry_points": []},
|
|
105
|
+
},
|
|
106
|
+
),
|
|
107
|
+
"atm.msg.send_message": Entry(
|
|
108
|
+
id="atm.msg.send_message",
|
|
109
|
+
kind="atom",
|
|
110
|
+
data={
|
|
111
|
+
"id": "atm.msg.send_message",
|
|
112
|
+
"kind": "PROCEDURAL",
|
|
113
|
+
"owner_module": "MSG",
|
|
114
|
+
"description": "Send a message to a resolved target set.",
|
|
115
|
+
"spec": {
|
|
116
|
+
"input": {
|
|
117
|
+
"target": {
|
|
118
|
+
"type": "string",
|
|
119
|
+
"nullable": False,
|
|
120
|
+
"shape": target_input_shape,
|
|
121
|
+
},
|
|
122
|
+
"body": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"nullable": False,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
"output": {
|
|
128
|
+
"success": {
|
|
129
|
+
"delivery_status": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"nullable": False,
|
|
132
|
+
"shape": target_output_shape,
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"errors": [],
|
|
136
|
+
},
|
|
137
|
+
"logic": ["CALL atm.msg.resolve_targets"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
),
|
|
141
|
+
"atm.msg.resolve_targets": Entry(
|
|
142
|
+
id="atm.msg.resolve_targets",
|
|
143
|
+
kind="atom",
|
|
144
|
+
data={
|
|
145
|
+
"id": "atm.msg.resolve_targets",
|
|
146
|
+
"kind": "PROCEDURAL",
|
|
147
|
+
"owner_module": "MSG",
|
|
148
|
+
"description": "Resolve a target selector into session ids.",
|
|
149
|
+
"spec": {
|
|
150
|
+
"input": {
|
|
151
|
+
"target": {
|
|
152
|
+
"type": "string",
|
|
153
|
+
"nullable": False,
|
|
154
|
+
"shape": target_input_shape,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
"output": {
|
|
158
|
+
"success": {
|
|
159
|
+
"resolved_targets": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"nullable": False,
|
|
162
|
+
"shape": called_output_shape,
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"errors": [],
|
|
166
|
+
},
|
|
167
|
+
"logic": [],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
bundle, unresolved = walker.walk(idx, "atm.msg.send_message")
|
|
174
|
+
|
|
175
|
+
assert unresolved == []
|
|
176
|
+
assert bundle["l3_atom"]["spec"]["input"]["target"]["shape"] == target_input_shape
|
|
177
|
+
assert bundle["l3_atom"]["spec"]["output"]["success"]["delivery_status"]["shape"] == target_output_shape
|
|
178
|
+
called_sig = bundle["called_atom_signatures"]["atm.msg.resolve_targets"]
|
|
179
|
+
assert called_sig["input"]["target"]["shape"] == target_input_shape
|
|
180
|
+
assert called_sig["output"]["success"]["resolved_targets"]["shape"] == called_output_shape
|
|
181
|
+
assert bundle["shaped_fields"]["atm.msg.send_message"] == {
|
|
182
|
+
"input.target": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"nullable": False,
|
|
185
|
+
"shape": target_input_shape,
|
|
186
|
+
},
|
|
187
|
+
"output.success.delivery_status": {
|
|
188
|
+
"type": "string",
|
|
189
|
+
"nullable": False,
|
|
190
|
+
"shape": target_output_shape,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
assert bundle["shaped_fields"]["atm.msg.resolve_targets"] == {
|
|
194
|
+
"input.target": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"nullable": False,
|
|
197
|
+
"shape": target_input_shape,
|
|
198
|
+
},
|
|
199
|
+
"output.success.resolved_targets": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"nullable": False,
|
|
202
|
+
"shape": called_output_shape,
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
69
207
|
# ---------- Module ----------
|
|
70
208
|
|
|
71
209
|
def test_module_bundle_shape(idx):
|
|
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
|