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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,2 @@
1
+ [console_scripts]
2
+ archetype = archetype.cli:main
@@ -0,0 +1,2 @@
1
+ chaincompiler==0.3.0
2
+ click
@@ -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()