ai-forge-cli 0.1.3__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.3/src/ai_forge_cli.egg-info → ai_forge_cli-0.1.5}/PKG-INFO +1 -1
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/README.md +9 -5
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/pyproject.toml +1 -1
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5/src/ai_forge_cli.egg-info}/PKG-INFO +1 -1
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/__init__.py +1 -1
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/commands/init.py +3 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/walker.py +50 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/tests/test_cli.py +2 -2
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/tests/test_init.py +2 -2
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/tests/test_walker.py +138 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/LICENSE +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/setup.cfg +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/SOURCES.txt +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/dependency_links.txt +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/entry_points.txt +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/requires.txt +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/ai_forge_cli.egg-info/top_level.txt +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/__main__.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/bundle.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/commands/__init__.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/commands/base.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/commands/context.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/commands/find.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/commands/inspect.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/commands/list_cmd.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/commands/update.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/common.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/forge.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/src/cli/index.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/tests/test_find.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/tests/test_index.py +0 -0
- {ai_forge_cli-0.1.3 → ai_forge_cli-0.1.5}/tests/test_update.py +0 -0
|
@@ -21,7 +21,7 @@ The agent drives the interview. The specs drive the code.
|
|
|
21
21
|
|
|
22
22
|
## What it is
|
|
23
23
|
|
|
24
|
-
A six-layer YAML spec system, a Python CLI for context assembly, and **
|
|
24
|
+
A six-layer YAML spec system, a Python CLI for context assembly, and **eleven agent skills** that take a human from a vague product idea to working, audited, hardened, validated code — with the agent asking questions and the human answering, not the reverse.
|
|
25
25
|
|
|
26
26
|
Built on the premise that **people explain systems well under questioning but poorly when cold-prompted**. forge inverts the default "human prompts agent → agent implements" loop into "agent interviews human → structured spec emerges → agent implements from spec".
|
|
27
27
|
|
|
@@ -42,6 +42,8 @@ Runs in **Claude Code**, **OpenAI Codex CLI**, and any **agentskills.io-compatib
|
|
|
42
42
|
↓
|
|
43
43
|
forge-compose → L4 composition (flows + journeys from completed atoms)
|
|
44
44
|
↓
|
|
45
|
+
forge-cast → repo hydration (existing codebase → draft Forge corpus + uncertainty report)
|
|
46
|
+
↓
|
|
45
47
|
forge-audit → quality gate (seven audit passes, severity-ranked findings)
|
|
46
48
|
↓
|
|
47
49
|
forge-armour → security hardening (trust model, policies, abuse-case review)
|
|
@@ -68,7 +70,7 @@ uv venv --python 3.13 .venv && uv pip install -e . pytest
|
|
|
68
70
|
./scripts/install-skills.sh install
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
This wires the `forge` binary into `~/.local/bin/` and symlinks the
|
|
73
|
+
This wires the `forge` binary into `~/.local/bin/` and symlinks the eleven skills into `~/.claude/skills/`, `~/.codex/skills/`, and `~/.agents/skills/` — discoverable by every supported client.
|
|
72
74
|
|
|
73
75
|
### Verify
|
|
74
76
|
|
|
@@ -110,7 +112,7 @@ forge init
|
|
|
110
112
|
✓ .forge/
|
|
111
113
|
✓ 6 spec subdirectories
|
|
112
114
|
✓ 12 schema templates → .forge/templates/
|
|
113
|
-
✓
|
|
115
|
+
✓ 33/33 skill symlinks → .claude/skills/, .codex/skills/, .agents/skills/
|
|
114
116
|
|
|
115
117
|
───── Next steps ─────
|
|
116
118
|
|
|
@@ -149,7 +151,7 @@ Full CLI guide: [`docs/cli-guide.md`](docs/cli-guide.md).
|
|
|
149
151
|
|
|
150
152
|
---
|
|
151
153
|
|
|
152
|
-
## The
|
|
154
|
+
## The eleven skills
|
|
153
155
|
|
|
154
156
|
| Skill | Role | Input | Output |
|
|
155
157
|
|---|---|---|---|
|
|
@@ -157,6 +159,7 @@ Full CLI guide: [`docs/cli-guide.md`](docs/cli-guide.md).
|
|
|
157
159
|
| **forge-decompose** | Structural extractor | One bounded module | Exhaustive atom stubs (four-pass extraction) |
|
|
158
160
|
| **forge-atom** | Contract specifier | One atom stub | Complete L3 spec + L0 cascades + module completions |
|
|
159
161
|
| **forge-compose** | Composition specifier | Completed atoms + project decisions | L4 flow/journey specs with explicit boundary/retry/compensation/idempotency decisions |
|
|
162
|
+
| **forge-cast** | Hydration specifier | Existing non-Forge codebase | Draft Forge corpus plus evidence-backed uncertainty report and clarification questions |
|
|
160
163
|
| **forge-audit** | Challenger / reviewer | Completed specs | Severity-ranked findings with inline edits; seven audit passes |
|
|
161
164
|
| **forge-armour** | Security challenger | Audited specs | Security hardening pass, trust-model capture, approved project/module/atom security edits |
|
|
162
165
|
| **forge-implement** | Orchestrator | Audited spec corpus | Code + tests, dep-graph parallel, test-before-impl isolation |
|
|
@@ -196,11 +199,12 @@ forge/
|
|
|
196
199
|
│ ├── cli/ Python CLI package (the forge command)
|
|
197
200
|
│ ├── templates/ L0-L5 schema templates (symlinked into projects by forge init)
|
|
198
201
|
│ └── example/ Working example spec corpus (used by tests)
|
|
199
|
-
├── .agents/skills/ The
|
|
202
|
+
├── .agents/skills/ The 11 forge skills (installed into agent clients)
|
|
200
203
|
│ ├── forge-discover/
|
|
201
204
|
│ ├── forge-decompose/
|
|
202
205
|
│ ├── forge-atom/
|
|
203
206
|
│ ├── forge-compose/
|
|
207
|
+
│ ├── forge-cast/
|
|
204
208
|
│ ├── forge-audit/
|
|
205
209
|
│ ├── forge-armour/
|
|
206
210
|
│ ├── forge-implement/
|
|
@@ -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"
|
|
@@ -125,6 +125,7 @@ SKILL_NAMES = (
|
|
|
125
125
|
"forge-decompose",
|
|
126
126
|
"forge-atom",
|
|
127
127
|
"forge-compose",
|
|
128
|
+
"forge-cast",
|
|
128
129
|
"forge-audit",
|
|
129
130
|
"forge-armour",
|
|
130
131
|
"forge-implement",
|
|
@@ -341,6 +342,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
341
342
|
print(f" {_bold('\"I want to build a tool that does X\"')}")
|
|
342
343
|
print(f" {_bold('\"Decompose the PAY module into atoms\"')}")
|
|
343
344
|
print(f" {_bold('\"Compose flows and journeys from completed atoms\"')}")
|
|
345
|
+
print(f" {_bold('\"Cast this existing repo into Forge docs\"')}")
|
|
344
346
|
print(f" {_bold('\"Audit the specs before implementation\"')}")
|
|
345
347
|
print(f" {_bold('\"Harden the specs for security before implementation\"')}")
|
|
346
348
|
print(f" {_bold('\"Validate the implementation against specs\"')}")
|
|
@@ -350,6 +352,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
350
352
|
+ _color(_FIRE_PRIMARY, "/forge-decompose") + " "
|
|
351
353
|
+ _color(_FIRE_PRIMARY, "/forge-atom") + " "
|
|
352
354
|
+ _color(_FIRE_PRIMARY, "/forge-compose") + " "
|
|
355
|
+
+ _color(_FIRE_PRIMARY, "/forge-cast") + " "
|
|
353
356
|
+ _color(_FIRE_PRIMARY, "/forge-audit") + " "
|
|
354
357
|
+ _color(_FIRE_PRIMARY, "/forge-armour") + " "
|
|
355
358
|
+ _color(_FIRE_PRIMARY, "/forge-implement") + " "
|
|
@@ -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:
|
|
@@ -32,7 +32,7 @@ def test_version_flag_prints_version_and_exits_0():
|
|
|
32
32
|
|
|
33
33
|
def test_version_string_prefers_ai_forge_cli_distribution(monkeypatch):
|
|
34
34
|
versions = {
|
|
35
|
-
"ai-forge-cli": "0.1.
|
|
35
|
+
"ai-forge-cli": "0.1.4",
|
|
36
36
|
"forge-ai-cli": "0.1.1",
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ def test_version_string_prefers_ai_forge_cli_distribution(monkeypatch):
|
|
|
42
42
|
raise metadata.PackageNotFoundError
|
|
43
43
|
|
|
44
44
|
monkeypatch.setattr(forge_mod.metadata, "version", fake_version)
|
|
45
|
-
assert forge_mod._version_string() == "0.1.
|
|
45
|
+
assert forge_mod._version_string() == "0.1.4"
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
# ---------- context ----------
|
|
@@ -143,7 +143,7 @@ def test_resolve_forge_sources_uses_cached_repo_when_local_skills_missing(monkey
|
|
|
143
143
|
|
|
144
144
|
def test_installed_cli_version_prefers_ai_forge_cli_distribution(monkeypatch):
|
|
145
145
|
versions = {
|
|
146
|
-
"ai-forge-cli": "0.1.
|
|
146
|
+
"ai-forge-cli": "0.1.4",
|
|
147
147
|
"forge-ai-cli": "0.1.1",
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -153,4 +153,4 @@ def test_installed_cli_version_prefers_ai_forge_cli_distribution(monkeypatch):
|
|
|
153
153
|
raise init_cmd.metadata.PackageNotFoundError
|
|
154
154
|
|
|
155
155
|
monkeypatch.setattr(init_cmd.metadata, "version", fake_version)
|
|
156
|
-
assert init_cmd._installed_cli_version() == "0.1.
|
|
156
|
+
assert init_cmd._installed_cli_version() == "0.1.4"
|
|
@@ -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
|