chaincompiler-archetype 0.3.0__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.
- chaincompiler_archetype-0.3.0/PKG-INFO +7 -0
- chaincompiler_archetype-0.3.0/pyproject.toml +16 -0
- chaincompiler_archetype-0.3.0/setup.cfg +4 -0
- chaincompiler_archetype-0.3.0/src/archetype/__init__.py +32 -0
- chaincompiler_archetype-0.3.0/src/archetype/cli.py +67 -0
- chaincompiler_archetype-0.3.0/src/archetype/compile.py +47 -0
- chaincompiler_archetype-0.3.0/src/archetype/emit.py +116 -0
- chaincompiler_archetype-0.3.0/src/archetype/infer.py +62 -0
- chaincompiler_archetype-0.3.0/src/archetype/model.py +68 -0
- chaincompiler_archetype-0.3.0/src/archetype/odyssey.py +66 -0
- chaincompiler_archetype-0.3.0/src/archetype/parser.py +42 -0
- chaincompiler_archetype-0.3.0/src/archetype/stdlib.py +53 -0
- chaincompiler_archetype-0.3.0/src/archetype/validate.py +56 -0
- chaincompiler_archetype-0.3.0/src/chaincompiler_archetype.egg-info/PKG-INFO +7 -0
- chaincompiler_archetype-0.3.0/src/chaincompiler_archetype.egg-info/SOURCES.txt +18 -0
- chaincompiler_archetype-0.3.0/src/chaincompiler_archetype.egg-info/dependency_links.txt +1 -0
- chaincompiler_archetype-0.3.0/src/chaincompiler_archetype.egg-info/entry_points.txt +2 -0
- chaincompiler_archetype-0.3.0/src/chaincompiler_archetype.egg-info/requires.txt +2 -0
- chaincompiler_archetype-0.3.0/src/chaincompiler_archetype.egg-info/top_level.txt +1 -0
- chaincompiler_archetype-0.3.0/tests/test_archetype.py +92 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chaincompiler-archetype
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: The Archetype Compiler — archetype as state machine (Persona/Shadow/Self/Odyssey/Becoming), compiled to the one type
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: chaincompiler==0.3.0
|
|
7
|
+
Requires-Dist: click
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "chaincompiler-archetype"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "The Archetype Compiler — archetype as state machine (Persona/Shadow/Self/Odyssey/Becoming), compiled to the one type"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = ["chaincompiler==0.3.0", "click"]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
archetype = "archetype.cli:main"
|
|
14
|
+
|
|
15
|
+
[tool.setuptools.packages.find]
|
|
16
|
+
where = ["src"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""archetype — the Archetype Compiler. An archetype is a state machine, not a noun.
|
|
2
|
+
|
|
3
|
+
MetaArchetype(A) := ⟨ Persona(A), Shadow(A), Self(A), Odyssey(A), Becoming(A) ⟩
|
|
4
|
+
|
|
5
|
+
Compiles a seed + knobs into the full individuation circuit (generating the missing
|
|
6
|
+
aspects), builds the multi-HJ Odyssey, validates C1–C6, and emits through lenses —
|
|
7
|
+
including the ONE TYPE, a `<name>/SKILL.md`. The ChainCompiler thesis on the psyche:
|
|
8
|
+
compile the transformation law, don't list the archetypes. Spec: ARCHETYPE-COMPILER.md.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
__version__ = "0.3.0"
|
|
13
|
+
|
|
14
|
+
from .model import HJ, KNOBS, MetaArchetype, Seed
|
|
15
|
+
from .stdlib import CYBERNETIC_CITY, CYCLE, PANTHEON, next_in_cycle, seed_for
|
|
16
|
+
from .infer import infer, denied_inverse, integrate, type_lift
|
|
17
|
+
from .odyssey import generate_odyssey, aut_web, quines
|
|
18
|
+
from .validate import validate, is_valid
|
|
19
|
+
from .emit import readable, triples, prolog, cypher, to_json, emit, emit_skill
|
|
20
|
+
from .compile import compile_archetype, compile_chain, compile_world
|
|
21
|
+
from .parser import Stmt, parse
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"MetaArchetype", "HJ", "Seed", "KNOBS",
|
|
25
|
+
"CYCLE", "PANTHEON", "CYBERNETIC_CITY", "seed_for", "next_in_cycle",
|
|
26
|
+
"infer", "denied_inverse", "integrate", "type_lift",
|
|
27
|
+
"generate_odyssey", "aut_web", "quines",
|
|
28
|
+
"validate", "is_valid",
|
|
29
|
+
"readable", "triples", "prolog", "cypher", "to_json", "emit", "emit_skill",
|
|
30
|
+
"compile_archetype", "compile_chain", "compile_world",
|
|
31
|
+
"Stmt", "parse", "__version__",
|
|
32
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""archetype CLI (§7): compile an archetype and emit its individuation circuit.
|
|
2
|
+
|
|
3
|
+
archetype compile Hero --substrate "example world" --depth 4 --emit all
|
|
4
|
+
archetype demo
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from .compile import compile_archetype, compile_chain, compile_world
|
|
11
|
+
from .emit import cypher, prolog, readable, to_json, triples
|
|
12
|
+
from .stdlib import CYBERNETIC_CITY, CYCLE
|
|
13
|
+
from .validate import validate
|
|
14
|
+
|
|
15
|
+
_LENSES = {"readable": readable, "cypher": cypher, "prolog": prolog, "json": to_json}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group()
|
|
19
|
+
def main() -> None:
|
|
20
|
+
"""The Archetype Compiler — archetype as state machine."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@main.command()
|
|
24
|
+
@click.argument("name")
|
|
25
|
+
@click.option("--substrate", default=None)
|
|
26
|
+
@click.option("--domain", default=None)
|
|
27
|
+
@click.option("--conflict", default=None)
|
|
28
|
+
@click.option("--boundary", default=None)
|
|
29
|
+
@click.option("--integration-mode", default=None)
|
|
30
|
+
@click.option("--depth", type=int, default=None, help="how many pantheon members to visit in the Odyssey")
|
|
31
|
+
@click.option("--emit", "fmt", default="readable",
|
|
32
|
+
type=click.Choice(["readable", "cypher", "prolog", "json", "triples", "all"]))
|
|
33
|
+
def compile(name, substrate, domain, conflict, boundary, integration_mode, depth, fmt):
|
|
34
|
+
"""Compile an archetype into its full individuation circuit."""
|
|
35
|
+
knobs = {k: v for k, v in {
|
|
36
|
+
"substrate": substrate, "domain": domain, "conflict": conflict,
|
|
37
|
+
"boundary": boundary, "integration_mode": integration_mode}.items() if v}
|
|
38
|
+
a = compile_archetype(name, knobs=knobs, depth=depth)
|
|
39
|
+
viol = validate(a)
|
|
40
|
+
if fmt == "all":
|
|
41
|
+
click.echo(readable(a, CYCLE))
|
|
42
|
+
click.echo("\n── cypher ──\n" + cypher(a))
|
|
43
|
+
click.echo("\n── prolog ──\n" + prolog(a))
|
|
44
|
+
elif fmt == "triples":
|
|
45
|
+
for s, p, o in triples(a):
|
|
46
|
+
click.echo(f"{s} —{p}→ {o}")
|
|
47
|
+
else:
|
|
48
|
+
click.echo(_LENSES[fmt](a, CYCLE) if fmt == "readable" else _LENSES[fmt](a))
|
|
49
|
+
click.echo(f"\n{'✓ valid' if not viol else '✗ ' + '; '.join(viol)}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@main.command()
|
|
53
|
+
def demo() -> None:
|
|
54
|
+
"""Compile the Hero, then the Example World world."""
|
|
55
|
+
click.echo("══ Hero — the individuation circuit + Odyssey ══")
|
|
56
|
+
a = compile_archetype("Hero", knobs={"substrate": "founder building autonomous AI"})
|
|
57
|
+
click.echo(readable(a, CYCLE))
|
|
58
|
+
click.echo(f"\nvalid: {not validate(a)}")
|
|
59
|
+
|
|
60
|
+
click.echo("\n══ Example World — a compiled world ══")
|
|
61
|
+
world = compile_world(CYBERNETIC_CITY, with_odyssey=False)
|
|
62
|
+
for nm, m in world.items():
|
|
63
|
+
click.echo(f" {nm}: {m.persona} → ⟂{m.shadow} → ⊕{m.self_} ⇒ {m.becoming}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""The compiler pipeline (§5): source → infer missing aspects → generate Odyssey
|
|
2
|
+
(multi-HJ) → generate Becoming → recurse → validate. The headline ChainCompiler move
|
|
3
|
+
applied to the psyche: you don't LIST archetypes, you compile the transformation law.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from .infer import infer
|
|
8
|
+
from .model import MetaArchetype
|
|
9
|
+
from .odyssey import generate_odyssey
|
|
10
|
+
from .stdlib import CYCLE, CYBERNETIC_CITY, Seed, next_in_cycle # noqa: F401 (re-export)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _pantheon(name: str, world: dict[str, Seed] | None) -> list[str]:
|
|
14
|
+
p = list(world) if world else list(CYCLE)
|
|
15
|
+
if name not in p:
|
|
16
|
+
p = [name] + p
|
|
17
|
+
return p
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def compile_archetype(name: str, *, knobs: dict | None = None,
|
|
21
|
+
world: dict[str, Seed] | None = None, depth: int | None = None,
|
|
22
|
+
with_odyssey: bool = True, **aspects) -> MetaArchetype:
|
|
23
|
+
"""Compile one archetype: infer the five aspects, then generate its Odyssey."""
|
|
24
|
+
a = infer(name, knobs=knobs, world=world, **aspects)
|
|
25
|
+
if with_odyssey:
|
|
26
|
+
a.odyssey = generate_odyssey(a, _pantheon(name, world), world=world, depth=depth)
|
|
27
|
+
return a
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def compile_chain(names: list[str], *, knobs: dict | None = None,
|
|
31
|
+
world: dict[str, Seed] | None = None, depth: int | None = None,
|
|
32
|
+
with_odyssey: bool = False) -> list[MetaArchetype]:
|
|
33
|
+
"""Compile an individuation chain (Hero => Guardian => King): each archetype's
|
|
34
|
+
Becoming is overridden to point at the NEXT name in the chain (C4 satisfied)."""
|
|
35
|
+
out: list[MetaArchetype] = []
|
|
36
|
+
for i, nm in enumerate(names):
|
|
37
|
+
nxt = names[i + 1] if i + 1 < len(names) else next_in_cycle(nm)
|
|
38
|
+
out.append(compile_archetype(nm, knobs=knobs, world=world, depth=depth,
|
|
39
|
+
with_odyssey=with_odyssey, becoming=nxt))
|
|
40
|
+
return out
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def compile_world(world: dict[str, Seed], *, knobs: dict | None = None,
|
|
44
|
+
depth: int | None = None, with_odyssey: bool = True) -> dict[str, MetaArchetype]:
|
|
45
|
+
"""Compile a whole substrate world (e.g. the Example World)."""
|
|
46
|
+
return {nm: compile_archetype(nm, knobs=knobs, world=world, depth=depth,
|
|
47
|
+
with_odyssey=with_odyssey) for nm in world}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Emit a compiled archetype through lenses (§5, §7) — readable / triples / Prolog /
|
|
2
|
+
Cypher / JSON — and, crucially, as the ONE TYPE: a `<name>/SKILL.md` skill dir, so an
|
|
3
|
+
archetype is a first-class citizen of ChainCompiler's closed algebra (an archetype
|
|
4
|
+
compiled to a loadable persona). Becoming = TypeLift = the recursion that, via the
|
|
5
|
+
bandit, can roll the archetype up into a domain persona.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .model import MetaArchetype
|
|
13
|
+
from .odyssey import aut_web, quines
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def readable(a: MetaArchetype, pantheon: list[str] | None = None) -> str:
|
|
17
|
+
lines = [
|
|
18
|
+
f"# {a.name} — a recursive individuation circuit",
|
|
19
|
+
"",
|
|
20
|
+
f"- **Persona** (mask): {a.persona}",
|
|
21
|
+
f"- **Shadow** (denied inv.): {a.shadow}",
|
|
22
|
+
f"- **Self** (integrated): {a.self_}",
|
|
23
|
+
f"- **Becoming** (type-lift →): {a.becoming}",
|
|
24
|
+
]
|
|
25
|
+
if a.knobs:
|
|
26
|
+
lines += ["", "**Knobs:** " + ", ".join(f"{k}={v}" for k, v in a.knobs.items())]
|
|
27
|
+
if a.odyssey:
|
|
28
|
+
lines += ["", f"## Odyssey ({len(a.odyssey)} hero's journeys)"]
|
|
29
|
+
for i, hj in enumerate(a.odyssey):
|
|
30
|
+
lines.append(f"{i}. {hj}")
|
|
31
|
+
web = aut_web(a.odyssey)
|
|
32
|
+
lines += ["", f"**Aut-web:** {', '.join(web) if web else '—'}"]
|
|
33
|
+
if pantheon:
|
|
34
|
+
lines.append(f"**Quines the pantheon:** {quines(a.odyssey, pantheon, a.name)}")
|
|
35
|
+
if a.confidence:
|
|
36
|
+
lines += ["", "**Confidence:** " + ", ".join(f"{k}={v}" for k, v in a.confidence.items())]
|
|
37
|
+
return "\n".join(lines)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def triples(a: MetaArchetype) -> list[tuple[str, str, str]]:
|
|
41
|
+
t = [
|
|
42
|
+
(a.name, "HAS_PERSONA", a.persona),
|
|
43
|
+
(a.name, "HAS_SHADOW", a.shadow),
|
|
44
|
+
(a.name, "HAS_SELF", a.self_),
|
|
45
|
+
(a.name, "BECOMES", a.becoming),
|
|
46
|
+
(a.shadow, "DENIED_INVERSE_OF", a.persona),
|
|
47
|
+
(a.self_, "INTEGRATES", a.persona),
|
|
48
|
+
(a.self_, "INTEGRATES", a.shadow),
|
|
49
|
+
]
|
|
50
|
+
for hj in a.odyssey:
|
|
51
|
+
if hj.encounter:
|
|
52
|
+
t.append((a.name, "AUTOMORPHISM_EDGE", hj.encounter))
|
|
53
|
+
return t
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def prolog(a: MetaArchetype) -> str:
|
|
57
|
+
def q(s: str) -> str:
|
|
58
|
+
return s.replace("'", "\\'")
|
|
59
|
+
lines = [
|
|
60
|
+
f"persona('{q(a.name)}', '{q(a.persona)}').",
|
|
61
|
+
f"shadow('{q(a.name)}', '{q(a.shadow)}').",
|
|
62
|
+
f"self('{q(a.name)}', '{q(a.self_)}').",
|
|
63
|
+
f"becomes('{q(a.name)}', '{q(a.becoming)}').",
|
|
64
|
+
f"denied_inverse('{q(a.shadow)}', '{q(a.persona)}').",
|
|
65
|
+
]
|
|
66
|
+
for hj in a.odyssey:
|
|
67
|
+
if hj.encounter:
|
|
68
|
+
lines.append(f"aut_edge('{q(a.name)}', '{q(hj.encounter)}').")
|
|
69
|
+
return "\n".join(lines)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def cypher(a: MetaArchetype) -> str:
|
|
73
|
+
n = a.name
|
|
74
|
+
lines = [
|
|
75
|
+
f"MERGE (a:Archetype {{name:'{n}'}})",
|
|
76
|
+
f"SET a.persona='{a.persona}', a.shadow='{a.shadow}', a.self='{a.self_}'",
|
|
77
|
+
f"MERGE (b:Archetype {{name:'{a.becoming}'}})",
|
|
78
|
+
f"MERGE (a)-[:BECOMES]->(b)",
|
|
79
|
+
]
|
|
80
|
+
for hj in a.odyssey:
|
|
81
|
+
if hj.encounter:
|
|
82
|
+
lines.append(f"MERGE (m:Archetype {{name:'{hj.encounter}'}}) "
|
|
83
|
+
f"MERGE (a)-[:AUT_EDGE]->(m)")
|
|
84
|
+
return "\n".join(lines)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def to_json(a: MetaArchetype) -> str:
|
|
88
|
+
return json.dumps({
|
|
89
|
+
"name": a.name, **a.aspects, "knobs": a.knobs,
|
|
90
|
+
"odyssey": [{"persona": h.persona, "shadow": h.shadow, "self": h.self_,
|
|
91
|
+
"encounter": h.encounter, "post_return": h.post_return} for h in a.odyssey],
|
|
92
|
+
"aut_web": aut_web(a.odyssey), "confidence": a.confidence,
|
|
93
|
+
}, indent=2)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def emit_skill(a: MetaArchetype, *, out_dir: str | Path, pantheon: list[str] | None = None) -> Path:
|
|
97
|
+
"""Compile the archetype to the ONE type — `<name>/SKILL.md` — so it loads as a
|
|
98
|
+
persona. The body is the readable individuation circuit + its emitted graph."""
|
|
99
|
+
from chaincompiler.skillpack import write_skill
|
|
100
|
+
body = readable(a, pantheon) + "\n\n## Triples\n" + "\n".join(
|
|
101
|
+
f"- {s} —{p}→ {o}" for s, p, o in triples(a))
|
|
102
|
+
return write_skill(
|
|
103
|
+
f"{a.name}-archetype",
|
|
104
|
+
f"The {a.name} archetype as an individuation circuit (Persona/Shadow/Self → Becoming {a.becoming}).",
|
|
105
|
+
body, out_dir=out_dir, extra={"kind": "archetype", "becomes": a.becoming})
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def emit(a: MetaArchetype, fmt: str, *, pantheon: list[str] | None = None):
|
|
109
|
+
"""Dispatch one lens by name."""
|
|
110
|
+
return {
|
|
111
|
+
"readable": lambda: readable(a, pantheon),
|
|
112
|
+
"triples": lambda: triples(a),
|
|
113
|
+
"prolog": lambda: prolog(a),
|
|
114
|
+
"cypher": lambda: cypher(a),
|
|
115
|
+
"json": lambda: to_json(a),
|
|
116
|
+
}[fmt]()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Inference — generate the missing aspects from whichever one is known (§5).
|
|
2
|
+
|
|
3
|
+
The compiler accepts a seed + knobs + (optional partial aspects) and generates the
|
|
4
|
+
rest. When the name is in the stdlib pantheon, the canonical aspects are used; else
|
|
5
|
+
the generic generators below produce them deterministically (no LLM — same stance as
|
|
6
|
+
the rest of ChainCompiler: the mechanics are code, the content is the model's job).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .model import MetaArchetype
|
|
11
|
+
from .stdlib import Seed, next_in_cycle, seed_for
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ── the three operators (generic forms; stdlib overrides with canonical content) ──
|
|
15
|
+
def denied_inverse(persona: str) -> str:
|
|
16
|
+
"""Shadow = DeniedInverse(Persona): the persona's virtue as its failure-mode."""
|
|
17
|
+
return f"Denied {persona} (the virtue turned to its failure-mode / projection)"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def integrate(persona: str, shadow: str) -> str:
|
|
21
|
+
"""Self = Integrate(Persona, Shadow): holds BOTH, ruled by neither."""
|
|
22
|
+
return f"Integrated form (holds «{persona}» and «{shadow}» without collapsing into either)"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def type_lift(name: str, knobs: dict) -> str:
|
|
26
|
+
"""Becoming = TypeLift(Self): the next archetype's name (knob can override)."""
|
|
27
|
+
return knobs.get("becoming") or next_in_cycle(name)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _scores(name: str, from_stdlib: bool, self_: str, becoming: str,
|
|
31
|
+
knobs: dict) -> dict:
|
|
32
|
+
"""Deterministic confidence scores (§5: the emitted *_fit numbers)."""
|
|
33
|
+
integrates = ("holds" in self_.lower()) or ("integrat" in self_.lower())
|
|
34
|
+
return {
|
|
35
|
+
"persona_shadow_fit": 1.0 if from_stdlib else 0.6,
|
|
36
|
+
"self_integration_fit": 1.0 if integrates else 0.3, # purified-Self penalty
|
|
37
|
+
"becoming_fit": 1.0 if becoming and becoming != name else 0.0,
|
|
38
|
+
"substrate_fit": 1.0 if knobs.get("substrate") else 0.5,
|
|
39
|
+
"nonredundancy": 1.0 if becoming != name else 0.0,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def infer(name: str, *, knobs: dict | None = None, world: dict[str, Seed] | None = None,
|
|
44
|
+
persona: str | None = None, shadow: str | None = None,
|
|
45
|
+
self_: str | None = None, becoming: str | None = None) -> MetaArchetype:
|
|
46
|
+
"""Build a (still odyssey-less) MetaArchetype, filling unknown aspects.
|
|
47
|
+
|
|
48
|
+
Precedence per aspect: explicit arg > stdlib seed > generic generator. This is
|
|
49
|
+
the §5 "if-X-known → generate the rest" table collapsed into one resolver.
|
|
50
|
+
"""
|
|
51
|
+
knobs = dict(knobs or {})
|
|
52
|
+
seed = seed_for(name, world)
|
|
53
|
+
from_stdlib = seed is not None
|
|
54
|
+
|
|
55
|
+
p = persona or (seed.persona if seed else f"{name} (legible role)")
|
|
56
|
+
s = shadow or (seed.shadow if seed else denied_inverse(p))
|
|
57
|
+
i = self_ or (seed.self_ if seed else integrate(p, s))
|
|
58
|
+
b = becoming or (seed.becoming if seed else type_lift(name, knobs))
|
|
59
|
+
|
|
60
|
+
return MetaArchetype(name=name, persona=p, shadow=s, self_=i, becoming=b,
|
|
61
|
+
odyssey=[], knobs=knobs,
|
|
62
|
+
confidence=_scores(name, from_stdlib, i, b, knobs))
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""The archetype-as-state-machine model (ARCHETYPE-COMPILER.md §1, §3).
|
|
2
|
+
|
|
3
|
+
An archetype is NOT a noun (a role label) but a recursive individuation circuit:
|
|
4
|
+
|
|
5
|
+
MetaArchetype(A) := ⟨ Persona(A), Shadow(A), Self(A), Odyssey(A), Becoming(A) ⟩
|
|
6
|
+
|
|
7
|
+
- Persona = the legible / claimed face (the mask).
|
|
8
|
+
- Shadow = DeniedInverse(Persona) — the failure-mode / projected inverse.
|
|
9
|
+
- Self = Integrate(Persona, Shadow) — contains BOTH, ruled by neither
|
|
10
|
+
(a *purified* Persona that bypasses the Shadow is the cardinal error).
|
|
11
|
+
- Odyssey = the SUCCESSION of hero's journeys (§2) — plural, accrues the pantheon
|
|
12
|
+
automorphism-web, ends in quining (orbit-closure).
|
|
13
|
+
- Becoming = TypeLift(Self) — the integrated Self becomes the Persona of the next
|
|
14
|
+
archetype. One Becoming = one hero's journey = one modulation.
|
|
15
|
+
|
|
16
|
+
These mechanics are G0-by-definition *inside the calculus*. The music-theory frame
|
|
17
|
+
(circle of fifths ↔ Becoming-cycle, etc.) is a G8 organizing resonance — see
|
|
18
|
+
ARCHETYPE-COMPILER.md §0; it is NOT asserted by this code.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
|
|
24
|
+
# the generation knobs (= "modes"): each colors one archetype differently.
|
|
25
|
+
KNOBS = ("domain", "substrate", "conflict", "boundary", "integration_mode", "scale", "tone")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class HJ:
|
|
30
|
+
"""One hero's journey = one Becoming step (Persona-state → Shadow → Self ⇒ next).
|
|
31
|
+
In an Odyssey, each non-zero HJ is an automorphism-encounter with a pantheon
|
|
32
|
+
member (`encounter`): A meets B as an image of itself (one sense, many faces)."""
|
|
33
|
+
persona: str
|
|
34
|
+
shadow: str
|
|
35
|
+
self_: str
|
|
36
|
+
encounter: str = "" # the pantheon member met as an automorphism-image
|
|
37
|
+
post_return: bool = False # the post-return HJ (arrival ≠ completion; the suitors)
|
|
38
|
+
|
|
39
|
+
def __str__(self) -> str:
|
|
40
|
+
tag = " [post-return]" if self.post_return else (f" ↔{self.encounter}" if self.encounter else "")
|
|
41
|
+
return f"{self.persona} → ⟂{self.shadow} → ⊕{self.self_}{tag}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class MetaArchetype:
|
|
46
|
+
"""A compiled archetype: the five aspects + the knobs that colored it + scores."""
|
|
47
|
+
name: str
|
|
48
|
+
persona: str
|
|
49
|
+
shadow: str
|
|
50
|
+
self_: str
|
|
51
|
+
becoming: str # the name of the next archetype (TypeLift target)
|
|
52
|
+
odyssey: list[HJ] = field(default_factory=list)
|
|
53
|
+
knobs: dict = field(default_factory=dict)
|
|
54
|
+
confidence: dict = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def aspects(self) -> dict:
|
|
58
|
+
return {"persona": self.persona, "shadow": self.shadow,
|
|
59
|
+
"self": self.self_, "becoming": self.becoming}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class Seed:
|
|
64
|
+
"""A stdlib pantheon entry — the canonical four aspects of one archetype."""
|
|
65
|
+
persona: str
|
|
66
|
+
shadow: str
|
|
67
|
+
self_: str
|
|
68
|
+
becoming: str
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""The Odyssey — a SUCCESSION of hero's journeys (ARCHETYPE-COMPILER.md §2).
|
|
2
|
+
|
|
3
|
+
The original spec's bug: it made an Odyssey a single Persona→Shadow→Self path. That
|
|
4
|
+
is ONE hero's journey = ONE Becoming, not an Odyssey. An Odyssey is plural:
|
|
5
|
+
|
|
6
|
+
HJ₀ the call-out journey (leave home / Troy)
|
|
7
|
+
HJ₁…HJₙ the wandering arcs — each an automorphism-encounter with a pantheon
|
|
8
|
+
member (A meets B as an image of itself; accrues the Aut-web)
|
|
9
|
+
HJ_return the POST-return mini-HJ (you arrive home and STILL must fight — the
|
|
10
|
+
suitors; arrival ≠ completion)
|
|
11
|
+
|
|
12
|
+
The telos = accrue an automorphism-edge to every pantheon member → orbit-closure →
|
|
13
|
+
quine the pantheon. Whether the orbit actually CLOSES (vs only tempering) is the open
|
|
14
|
+
seam (see ARCHETYPE-MUSIC-CORRESPONDENCE-MAP.md); this generator builds the
|
|
15
|
+
succession, it does not claim closure.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from .model import HJ, MetaArchetype
|
|
20
|
+
from .stdlib import Seed, seed_for
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def generate_odyssey(arch: MetaArchetype, pantheon: list[str], *,
|
|
24
|
+
world: dict[str, Seed] | None = None,
|
|
25
|
+
depth: int | None = None) -> list[HJ]:
|
|
26
|
+
"""Build A's odyssey: a call-out HJ, one encounter-HJ per other pantheon member
|
|
27
|
+
(up to `depth`), and a post-return HJ. Each encounter accrues an Aut-edge."""
|
|
28
|
+
hjs: list[HJ] = []
|
|
29
|
+
|
|
30
|
+
# HJ₀ — the call-out: A's own Persona → Shadow → Self
|
|
31
|
+
hjs.append(HJ(persona=arch.persona, shadow=arch.shadow, self_=arch.self_,
|
|
32
|
+
encounter=""))
|
|
33
|
+
|
|
34
|
+
# HJ₁…HJₙ — meet each OTHER pantheon member as an automorphism-image of A
|
|
35
|
+
others = [m for m in pantheon if m != arch.name]
|
|
36
|
+
if depth is not None:
|
|
37
|
+
others = others[:max(0, depth)]
|
|
38
|
+
carry = arch.self_
|
|
39
|
+
for m in others:
|
|
40
|
+
ms = seed_for(m, world)
|
|
41
|
+
m_shadow = ms.shadow if ms else f"the shadow of {m}"
|
|
42
|
+
hjs.append(HJ(
|
|
43
|
+
persona=carry, # carry the prior Self forward
|
|
44
|
+
shadow=m_shadow, # confront M's shadow-face
|
|
45
|
+
self_=f"{arch.name}-as-{m} (A's {m}-automorphism)",
|
|
46
|
+
encounter=m))
|
|
47
|
+
carry = f"{arch.name}+{m}"
|
|
48
|
+
|
|
49
|
+
# HJ_return — the post-return journey (the suitors): integrate the homecoming
|
|
50
|
+
hjs.append(HJ(persona=carry, shadow=f"the unguarded home ({arch.name} returned but untested)",
|
|
51
|
+
self_=f"{arch.name} sovereign at home (return integrated)",
|
|
52
|
+
encounter="", post_return=True))
|
|
53
|
+
return hjs
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def aut_web(odyssey: list[HJ]) -> list[str]:
|
|
57
|
+
"""The set of pantheon members A has accrued an automorphism-edge to."""
|
|
58
|
+
return [hj.encounter for hj in odyssey if hj.encounter]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def quines(odyssey: list[HJ], pantheon: list[str], name: str) -> bool:
|
|
62
|
+
"""Orbit-closure test (the telos): has A met EVERY other pantheon member?
|
|
63
|
+
True ⇒ A can regenerate the whole pantheon from itself (quining). This is the
|
|
64
|
+
discrete closure check; it does NOT settle the comma/tempering question."""
|
|
65
|
+
met = set(aut_web(odyssey))
|
|
66
|
+
return met >= {m for m in pantheon if m != name}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Parse the archetype DSL (§7) into structured requests.
|
|
2
|
+
|
|
3
|
+
archetype Hero { substrate:"founder building AI" domain:"startup myth"
|
|
4
|
+
conflict:"inflation" boundary:"responsibility" integration_mode:"service" }
|
|
5
|
+
chain IndividuationPath { Hero => Guardian => King => Steward => Sage }
|
|
6
|
+
triangulate { X==Shadow Y==Persona Z==Self }
|
|
7
|
+
|
|
8
|
+
Returns a list of `Stmt` (kind ∈ {archetype, chain, triangulate}); the CLI/compiler
|
|
9
|
+
consume them. Deliberately tiny — regex over braces, no external grammar.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
|
|
16
|
+
_BLOCK = re.compile(r"(archetype|chain|triangulate)\s+([A-Za-z0-9_]+)?\s*\{([^}]*)\}", re.S)
|
|
17
|
+
_KV = re.compile(r"([A-Za-z_]+)\s*:\s*\"([^\"]*)\"")
|
|
18
|
+
_ARROW = re.compile(r"=>")
|
|
19
|
+
_ANCHOR = re.compile(r"([A-Za-z0-9_]+)\s*==\s*(Persona|Shadow|Self)", re.I)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Stmt:
|
|
24
|
+
kind: str # archetype | chain | triangulate
|
|
25
|
+
name: str = ""
|
|
26
|
+
knobs: dict = field(default_factory=dict) # for `archetype`
|
|
27
|
+
steps: list[str] = field(default_factory=list) # for `chain`
|
|
28
|
+
anchors: dict = field(default_factory=dict) # for `triangulate`: {role: var}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parse(text: str) -> list[Stmt]:
|
|
32
|
+
stmts: list[Stmt] = []
|
|
33
|
+
for kind, name, inner in _BLOCK.findall(text):
|
|
34
|
+
if kind == "archetype":
|
|
35
|
+
stmts.append(Stmt("archetype", name, knobs=dict(_KV.findall(inner))))
|
|
36
|
+
elif kind == "chain":
|
|
37
|
+
steps = [s.strip() for s in _ARROW.split(inner) if s.strip()]
|
|
38
|
+
stmts.append(Stmt("chain", name, steps=steps))
|
|
39
|
+
elif kind == "triangulate":
|
|
40
|
+
anchors = {role.capitalize(): var for var, role in _ANCHOR.findall(inner)}
|
|
41
|
+
stmts.append(Stmt("triangulate", name, anchors=anchors))
|
|
42
|
+
return stmts
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""The seed pantheon + the default Becoming-cycle (ARCHETYPE-COMPILER.md §4).
|
|
2
|
+
|
|
3
|
+
The default Becoming-cycle CLOSES into a circle (the "circle of fifths of the psyche"):
|
|
4
|
+
|
|
5
|
+
Fool → Seeker → Hero → Guardian → King → Steward → Sage → Witness → Self ↺
|
|
6
|
+
|
|
7
|
+
Whether that closure is exact or only *tempers* (the circle-of-fifths never closes:
|
|
8
|
+
3¹² ≠ 2¹⁹) is the one open seam — see ARCHETYPE-MUSIC-CORRESPONDENCE-MAP.md. This
|
|
9
|
+
table just gives the canonical aspects; it does not assert the music correspondence.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from .model import Seed
|
|
14
|
+
|
|
15
|
+
# the default cycle (each Becoming points to the next; Self closes back to Fool)
|
|
16
|
+
CYCLE = ["Fool", "Seeker", "Hero", "Guardian", "King", "Steward", "Sage", "Witness", "Self"]
|
|
17
|
+
|
|
18
|
+
PANTHEON: dict[str, Seed] = {
|
|
19
|
+
"Fool": Seed("Innocent beginner", "Naive Dupe (folly mistaken for wisdom)", "Wise Innocent (open, not gullible)", "Seeker"),
|
|
20
|
+
"Seeker": Seed("Restless searcher", "Lost Wanderer (seeking as avoidance)", "Grounded Seeker (searches without fleeing)", "Hero"),
|
|
21
|
+
"Hero": Seed("Rescuer / champion", "Savior-Overlord (courage→domination)", "Guardian Process (holds courage AND its shadow)", "Guardian"),
|
|
22
|
+
"Guardian": Seed("Boundary maintainer", "Access Tyrant (care→exclusion)", "Protocol Steward (guards without gatekeeping)", "King"),
|
|
23
|
+
"King": Seed("Sovereign ruler", "Despot (sovereignty→tyranny)", "Servant-Sovereign (rules in service)", "Steward"),
|
|
24
|
+
"Steward": Seed("Caretaker of the order","Frozen Bureaucrat (order→rigidity)", "Living Constitution (order that adapts)", "Sage"),
|
|
25
|
+
"Sage": Seed("Knower / reflector", "Detached Cynic (wisdom→contempt)", "Embodied Sage (knows AND acts)", "Witness"),
|
|
26
|
+
"Witness": Seed("Pure observer", "Passive Spectator (witness→abdication)", "Engaged Witness (sees AND participates)", "Self"),
|
|
27
|
+
"Self": Seed("Integrated wholeness", "Inflated Guru (wholeness→ego)", "Quiet Self (whole, unclaimed)", "Fool"), # closes
|
|
28
|
+
# off-cycle (the Jester thread, §8): Trickster.Self = Jester; Trickster.Shadow = clown.
|
|
29
|
+
"Trickster":Seed("Liberator / sacred fool","Demon Clown (renewal→sabotage)", "Jester (renewal through disruption)", "Magician"),
|
|
30
|
+
"Magician": Seed("Transformer", "Manipulator (power→exploitation)", "True Magician (transforms in service)", "Sage"),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# a worked substrate instantiation: the "Example World" generation.
|
|
34
|
+
CYBERNETIC_CITY: dict[str, Seed] = {
|
|
35
|
+
"Hero": Seed("Debug Champion", "Savior-Overlord", "Guardian Process", "Gatekeeper"),
|
|
36
|
+
"Gatekeeper": Seed("Boundary Maintainer","Access Tyrant", "Protocol Steward", "Lawgiver"),
|
|
37
|
+
"Lawgiver": Seed("Rule Compiler", "Frozen Bureaucracy", "Living Constitution", "Sovereign Kernel"),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def seed_for(name: str, world: dict[str, Seed] | None = None) -> Seed | None:
|
|
42
|
+
"""Look up a canonical seed (a named world overrides the default pantheon)."""
|
|
43
|
+
if world and name in world:
|
|
44
|
+
return world[name]
|
|
45
|
+
return PANTHEON.get(name)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def next_in_cycle(name: str) -> str:
|
|
49
|
+
"""The default Becoming target for `name` (wraps; off-cycle → its seed's becoming)."""
|
|
50
|
+
if name in CYCLE:
|
|
51
|
+
return CYCLE[(CYCLE.index(name) + 1) % len(CYCLE)]
|
|
52
|
+
s = PANTHEON.get(name)
|
|
53
|
+
return s.becoming if s else "Self"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Graph constraints C1–C6 (ARCHETYPE-COMPILER.md §5, §2.4).
|
|
2
|
+
|
|
3
|
+
C1 Persona-opposes-Shadow (shadow ≠ persona; it is the denied inverse)
|
|
4
|
+
C2 Self-contains-both (NOT a purified Persona that bypasses the Shadow)
|
|
5
|
+
C3 Odyssey-traverses-Shadow (every HJ confronts a shadow)
|
|
6
|
+
C4 Becoming-type-lifts (becoming ≠ the archetype itself; it explains A⇒B)
|
|
7
|
+
C5 recursion terminates/cycles (becoming resolves to a known/own name)
|
|
8
|
+
C6 Odyssey-is-plural (≥1 HJ AND accrues automorphism-edges)
|
|
9
|
+
C6b post-return present (a complete Odyssey returns AND fights again)
|
|
10
|
+
|
|
11
|
+
`validate` returns a list of violation strings; empty ⇒ the archetype is well-formed.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from .model import MetaArchetype
|
|
16
|
+
from .odyssey import aut_web
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def validate(arch: MetaArchetype) -> list[str]:
|
|
20
|
+
v: list[str] = []
|
|
21
|
+
|
|
22
|
+
# C1 — persona opposes shadow
|
|
23
|
+
if not arch.shadow or arch.shadow.strip() == arch.persona.strip():
|
|
24
|
+
v.append("C1: Shadow must be the denied inverse of Persona (got identical/empty).")
|
|
25
|
+
|
|
26
|
+
# C2 — Self contains both (the cardinal error: a purified Persona)
|
|
27
|
+
sl = arch.self_.strip().lower()
|
|
28
|
+
if arch.self_.strip() == arch.persona.strip():
|
|
29
|
+
v.append("C2: Self is a purified Persona (identical) — it must integrate the Shadow.")
|
|
30
|
+
elif not ("hold" in sl or "integrat" in sl or "both" in sl or "-as-" in sl
|
|
31
|
+
or arch.name.lower() in sl):
|
|
32
|
+
v.append("C2: Self does not evidence integration of Persona AND Shadow.")
|
|
33
|
+
|
|
34
|
+
# C4 — Becoming type-lifts to a different archetype
|
|
35
|
+
if not arch.becoming or arch.becoming.strip() == arch.name.strip():
|
|
36
|
+
v.append("C4: Becoming must type-lift to a DIFFERENT archetype (got self/empty).")
|
|
37
|
+
|
|
38
|
+
# odyssey constraints (only when an odyssey was generated)
|
|
39
|
+
if arch.odyssey:
|
|
40
|
+
# C3 — every HJ traverses a shadow
|
|
41
|
+
if any(not hj.shadow.strip() for hj in arch.odyssey):
|
|
42
|
+
v.append("C3: every hero's journey in the Odyssey must confront a Shadow.")
|
|
43
|
+
# C6 — plural AND accrues automorphism-edges
|
|
44
|
+
if len(arch.odyssey) < 2:
|
|
45
|
+
v.append("C6: an Odyssey is a SUCCESSION of HJs (a single P→S→Self is a Becoming).")
|
|
46
|
+
if not aut_web(arch.odyssey):
|
|
47
|
+
v.append("C6: the Odyssey must accrue automorphism-edges to other pantheon members.")
|
|
48
|
+
# C6b — post-return HJ present
|
|
49
|
+
if not any(hj.post_return for hj in arch.odyssey):
|
|
50
|
+
v.append("C6b: a complete Odyssey includes a post-return journey (arrival ≠ completion).")
|
|
51
|
+
|
|
52
|
+
return v
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def is_valid(arch: MetaArchetype) -> bool:
|
|
56
|
+
return not validate(arch)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chaincompiler-archetype
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: The Archetype Compiler — archetype as state machine (Persona/Shadow/Self/Odyssey/Becoming), compiled to the one type
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: chaincompiler==0.3.0
|
|
7
|
+
Requires-Dist: click
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/archetype/__init__.py
|
|
3
|
+
src/archetype/cli.py
|
|
4
|
+
src/archetype/compile.py
|
|
5
|
+
src/archetype/emit.py
|
|
6
|
+
src/archetype/infer.py
|
|
7
|
+
src/archetype/model.py
|
|
8
|
+
src/archetype/odyssey.py
|
|
9
|
+
src/archetype/parser.py
|
|
10
|
+
src/archetype/stdlib.py
|
|
11
|
+
src/archetype/validate.py
|
|
12
|
+
src/chaincompiler_archetype.egg-info/PKG-INFO
|
|
13
|
+
src/chaincompiler_archetype.egg-info/SOURCES.txt
|
|
14
|
+
src/chaincompiler_archetype.egg-info/dependency_links.txt
|
|
15
|
+
src/chaincompiler_archetype.egg-info/entry_points.txt
|
|
16
|
+
src/chaincompiler_archetype.egg-info/requires.txt
|
|
17
|
+
src/chaincompiler_archetype.egg-info/top_level.txt
|
|
18
|
+
tests/test_archetype.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
archetype
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Tests for the Archetype Compiler — the spec's MVP test list (§7) + the ✦new ones."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
import archetype as arc
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ── the preserved MVP tests ──────────────────────────────────────────────────
|
|
11
|
+
def test_hero_becomes_guardian():
|
|
12
|
+
a = arc.compile_archetype("Hero")
|
|
13
|
+
assert a.becoming == "Guardian"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_shadow_opposes_persona():
|
|
17
|
+
a = arc.compile_archetype("Hero")
|
|
18
|
+
assert a.shadow and a.shadow != a.persona
|
|
19
|
+
assert ("DENIED_INVERSE_OF", a.persona) in [(p, o) for s, p, o in arc.triples(a)
|
|
20
|
+
if p == "DENIED_INVERSE_OF"][0:1] or True
|
|
21
|
+
assert (a.shadow, "DENIED_INVERSE_OF", a.persona) in arc.triples(a)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_self_integrates_both():
|
|
25
|
+
a = arc.compile_archetype("Hero")
|
|
26
|
+
assert (a.self_, "INTEGRATES", a.persona) in arc.triples(a)
|
|
27
|
+
assert (a.self_, "INTEGRATES", a.shadow) in arc.triples(a)
|
|
28
|
+
assert arc.is_valid(a)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_recursive_depth():
|
|
32
|
+
chain = arc.compile_chain(["Hero", "Guardian", "King", "Steward", "Sage"])
|
|
33
|
+
assert [m.name for m in chain] == ["Hero", "Guardian", "King", "Steward", "Sage"]
|
|
34
|
+
# each Becoming points at the next link (the chain type-lifts forward)
|
|
35
|
+
assert [m.becoming for m in chain[:-1]] == ["Guardian", "King", "Steward", "Sage"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_purified_self_fails():
|
|
39
|
+
# a Self that is just the Persona (bypasses the Shadow) must be a C2 violation
|
|
40
|
+
bad = arc.compile_archetype("Hero", with_odyssey=False, self_="Rescuer / champion",
|
|
41
|
+
persona="Rescuer / champion")
|
|
42
|
+
v = arc.validate(bad)
|
|
43
|
+
assert any(x.startswith("C2") for x in v)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ── ✦new tests (the Odyssey refactor) ────────────────────────────────────────
|
|
47
|
+
def test_odyssey_is_plural():
|
|
48
|
+
a = arc.compile_archetype("Hero")
|
|
49
|
+
# a real Odyssey is a SUCCESSION (call-out + encounters + post-return), not 1 HJ
|
|
50
|
+
assert len(a.odyssey) >= 2
|
|
51
|
+
assert arc.is_valid(a)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_odyssey_accrues_pantheon_edges():
|
|
55
|
+
a = arc.compile_archetype("Hero")
|
|
56
|
+
web = arc.aut_web(a.odyssey)
|
|
57
|
+
assert len(web) >= 1
|
|
58
|
+
# visiting the full pantheon ⇒ quines it (orbit-closure, discrete check)
|
|
59
|
+
full = arc.compile_archetype("Hero") # default depth = all others
|
|
60
|
+
assert arc.quines(full.odyssey, arc.CYCLE, "Hero")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_post_return_hj_present():
|
|
64
|
+
a = arc.compile_archetype("Hero")
|
|
65
|
+
assert any(hj.post_return for hj in a.odyssey), "C6b: an Odyssey returns AND fights again"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ── emit + parse + skill-dir (the one type) ──────────────────────────────────
|
|
69
|
+
def test_emit_lenses():
|
|
70
|
+
a = arc.compile_archetype("King")
|
|
71
|
+
assert "MERGE" in arc.cypher(a)
|
|
72
|
+
assert "becomes(" in arc.prolog(a)
|
|
73
|
+
assert json.loads(arc.to_json(a))["name"] == "King"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_parse_dsl():
|
|
77
|
+
src = 'archetype Hero { substrate:"AI" domain:"startup" }\nchain Path { Hero => Guardian => King }'
|
|
78
|
+
stmts = arc.parse(src)
|
|
79
|
+
kinds = {s.kind for s in stmts}
|
|
80
|
+
assert kinds == {"archetype", "chain"}
|
|
81
|
+
hero = [s for s in stmts if s.kind == "archetype"][0]
|
|
82
|
+
assert hero.knobs["substrate"] == "AI"
|
|
83
|
+
path = [s for s in stmts if s.kind == "chain"][0]
|
|
84
|
+
assert path.steps == ["Hero", "Guardian", "King"]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_emit_skill_is_the_one_type():
|
|
88
|
+
a = arc.compile_archetype("Hero")
|
|
89
|
+
out = tempfile.mkdtemp(prefix="arc_skill_")
|
|
90
|
+
p = arc.emit_skill(a, out_dir=out, pantheon=arc.CYCLE)
|
|
91
|
+
assert p.exists() and p.name == "SKILL.md"
|
|
92
|
+
assert "Becoming" in p.read_text()
|