personacc 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.
- personacc-0.3.0/PKG-INFO +8 -0
- personacc-0.3.0/README.md +81 -0
- personacc-0.3.0/pyproject.toml +13 -0
- personacc-0.3.0/setup.cfg +4 -0
- personacc-0.3.0/src/personacc/__init__.py +32 -0
- personacc-0.3.0/src/personacc/forge.py +390 -0
- personacc-0.3.0/src/personacc/notation.py +129 -0
- personacc-0.3.0/src/personacc.egg-info/PKG-INFO +8 -0
- personacc-0.3.0/src/personacc.egg-info/SOURCES.txt +11 -0
- personacc-0.3.0/src/personacc.egg-info/dependency_links.txt +1 -0
- personacc-0.3.0/src/personacc.egg-info/requires.txt +3 -0
- personacc-0.3.0/src/personacc.egg-info/top_level.txt +1 -0
- personacc-0.3.0/tests/test_personacc.py +142 -0
personacc-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: personacc
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Persona Compiler-Compiler — compiles a persona bundle (description blocks + attention chains + ≥1 chain-of-reasoning that uses them) and seats it into make_gba
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: chaincompiler-accc==0.3.0
|
|
7
|
+
Requires-Dist: corcc==0.3.0
|
|
8
|
+
Requires-Dist: chaincompiler==0.3.0
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# PersonaCC — Persona Compiler-Compiler
|
|
2
|
+
|
|
3
|
+
The **persona** compiler of the cognition-chain stack: `ACCC → CORCC → PersonaCC`.
|
|
4
|
+
|
|
5
|
+
## The ratified definition
|
|
6
|
+
|
|
7
|
+
> **A persona = a specific set of DESCRIPTION BLOCKS + a set of ACs + at least one
|
|
8
|
+
> CoR that USES them.**
|
|
9
|
+
|
|
10
|
+
ACCC forges an **attention chain** (inner, silent — *how to think*). CORCC forges a
|
|
11
|
+
**chain-of-reasoning** (outer, spoken — the persona-melt gauge). PersonaCC forges
|
|
12
|
+
the whole **persona**: the description blocks that say who the agent is, the
|
|
13
|
+
attention chains it thinks along, and the one-or-more CoRs that use them. Its
|
|
14
|
+
source language is the 2023 SDNA-template block grammar (`[KIND] … [/KIND]`).
|
|
15
|
+
|
|
16
|
+
## The bundle (`notation.py`)
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
Block(kind="role", body="You are …") # kind is a FREE string — new kinds are legal
|
|
20
|
+
PersonaBundle(
|
|
21
|
+
name="…",
|
|
22
|
+
blocks=(Block(...), …), # description blocks (may be empty)
|
|
23
|
+
acs=("[Focus] ⇒ [Focus] ⇒ |Held|", …), # attention chains (accc notation)
|
|
24
|
+
cors=(persona_spec, …), # ≥1 corcc.PersonaSpec (the reasoning core)
|
|
25
|
+
blurb="…",
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`PersonaSpec` is really the **COR SPEC** (a half-persona — the spoken move-chain);
|
|
30
|
+
PersonaCC builds the full persona *above* it. New block kinds are legal by design
|
|
31
|
+
(the template-template rule): the grammar never closes over a fixed enum.
|
|
32
|
+
|
|
33
|
+
## The *CC shape (`forge.py`)
|
|
34
|
+
|
|
35
|
+
- **`forge_bundle(bundle, db=…)`** — forge each AC via ACCC, each CoR via CORCC;
|
|
36
|
+
run the notation lint + the uses-them gate. Returns a `ForgedBundle`.
|
|
37
|
+
- **`lint(bundle)`** — notation/structure lint: every AC is a well-formed rune
|
|
38
|
+
chain, every CoR chain parses, ≥1 CoR, blocks well-formed.
|
|
39
|
+
- **`uses_them(bundle, strict=False)`** — the check that makes "…that USES them"
|
|
40
|
+
mechanical: every CoR move name must be a focus in a bundle AC **or** the CoR's
|
|
41
|
+
own derived chain. The escape hatch (a CoR's own derived chain always contains
|
|
42
|
+
its own moves) means a well-formed bundle never hard-fails in the default mode —
|
|
43
|
+
uncovered moves surface as **warnings** ("declared but not wired to an AC").
|
|
44
|
+
`strict=True` drops the escape hatch: an uncovered move is a **violation**.
|
|
45
|
+
(Weakened to a warning by default because the seed/proof bundles' real attention
|
|
46
|
+
atoms legitimately don't re-list every CoR move.)
|
|
47
|
+
- **`package(bundle, out_dir)`** — compile to `<name>/SKILL.md` (blocks as
|
|
48
|
+
sections + the AC set + each CoR's PRIME) via `chaincompiler.write_skill`.
|
|
49
|
+
- **`seat(bundle)`** — return exactly the kwargs
|
|
50
|
+
`chainaios.make_gba(domain, root, **seat(bundle))` accepts:
|
|
51
|
+
`{atoms, blurb, role_block, persona}`. Only `cors[0]` is seated today; seating
|
|
52
|
+
additional CoRs is future work.
|
|
53
|
+
|
|
54
|
+
## TOTAL SYNTAXIFICATION (the doctrine)
|
|
55
|
+
|
|
56
|
+
One notation all the way down: **every persona block can be made with AC syntax, and
|
|
57
|
+
natural language never appears free — only as a component the specific AC syntax
|
|
58
|
+
licenses** (e.g. a condition gloss inside a focus). Blocks therefore carry a **grade**
|
|
59
|
+
(`Block.grade`): `prose` (soup — free NL, tolerated *only* for migration) → `chain`
|
|
60
|
+
(code — real chain syntax). Detection is honest — for the default `rune` language it
|
|
61
|
+
uses accc's **real parser** (`parse_ac`), not a regex; a body is a chain iff it parses.
|
|
62
|
+
The **melt-gauge** that CORCC runs on a CoR (forge the language, gate the text, drift =
|
|
63
|
+
melting) now runs on the whole persona: `forge.lint_blocks(bundle)` gates every
|
|
64
|
+
chain-graded block (a hard issue if it melts) and warns on every prose block
|
|
65
|
+
("unsyntactified block: <kind>"). A malformed chain-block fires the same gate a melting
|
|
66
|
+
CoR does. The endpoint: the melt-gauge covers the entire persona, not just its CoRs.
|
|
67
|
+
|
|
68
|
+
**TYPE ⊥ SYNTAX.** A block is a triple — (type-`kind`, `lang`, `body` string): the
|
|
69
|
+
label is orthogonal to the language is orthogonal to the string. The typed content is
|
|
70
|
+
canonical; **every string is a *render*** of it in some chain language, and
|
|
71
|
+
re-rendering one typed corpus across languages IS the de-lore/masking mechanism —
|
|
72
|
+
myth-lang and research-lang are two render targets of the same typed persona. A
|
|
73
|
+
language is lintable once its melt-gauge is registered (`register_lang_gate(name, fn)`;
|
|
74
|
+
`LANG_GATES` holds `rune` by default); until then its blocks grade `prose` ("no gate
|
|
75
|
+
registered for lang=<x>"). To emit a conformant string, the LLM needs only the type
|
|
76
|
+
label + which syntax.
|
|
77
|
+
|
|
78
|
+
## The library
|
|
79
|
+
|
|
80
|
+
`personapack` is the LIBRARY of pre-bound `PersonaBundle`s (Bodhisattva, Aegis,
|
|
81
|
+
SamayaReviewer, …), each with an honest activation grade and a receipt.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "personacc"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Persona Compiler-Compiler — compiles a persona bundle (description blocks + attention chains + ≥1 chain-of-reasoning that uses them) and seats it into make_gba"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = ["chaincompiler-accc==0.3.0", "corcc==0.3.0", "chaincompiler==0.3.0"]
|
|
11
|
+
|
|
12
|
+
[tool.setuptools.packages.find]
|
|
13
|
+
where = ["src"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""PersonaCC — Persona Compiler-Compiler. The syntax compiler for a PERSONA.
|
|
2
|
+
|
|
3
|
+
THE RATIFIED DEFINITION: a persona = a set of DESCRIPTION BLOCKS + a set of ACs +
|
|
4
|
+
at least one CoR that USES them. PersonaCC compiles that bundle: catch the
|
|
5
|
+
notation → lint (form + "uses them") → compile to a markdown body → package as
|
|
6
|
+
`<name>/SKILL.md` → SEAT into `chainaios.make_gba(persona=…)`. Composes ACCC (the
|
|
7
|
+
attention chains) + CORCC (the chains of reasoning). ACCC → CORCC → PersonaCC.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
__version__ = "0.3.0"
|
|
12
|
+
|
|
13
|
+
from .notation import Block, PersonaBundle
|
|
14
|
+
from .forge import (
|
|
15
|
+
ForgedBundle,
|
|
16
|
+
LANG_GATES,
|
|
17
|
+
forge_bundle,
|
|
18
|
+
lint,
|
|
19
|
+
lint_blocks,
|
|
20
|
+
package,
|
|
21
|
+
register_lang_gate,
|
|
22
|
+
render_blocks,
|
|
23
|
+
role_block,
|
|
24
|
+
seat,
|
|
25
|
+
uses_them,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Block", "PersonaBundle", "ForgedBundle",
|
|
30
|
+
"forge_bundle", "lint", "lint_blocks", "uses_them", "package", "seat",
|
|
31
|
+
"render_blocks", "role_block", "register_lang_gate", "LANG_GATES", "__version__",
|
|
32
|
+
]
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""PersonaCC — the Persona Compiler-Compiler. Composes ACCC + CORCC.
|
|
2
|
+
|
|
3
|
+
Same *CC shape as ACCC / CORCC / SCCC: catch the notation, LINT it (so the model
|
|
4
|
+
never emits a malformed persona), compile it to a markdown body, and PACKAGE that
|
|
5
|
+
as `<name>/SKILL.md`. PersonaCC judges no content — only the SYNTAX and the
|
|
6
|
+
STRUCTURE of the bundle (the ratified definition: blocks + ACs + ≥1 CoR that uses
|
|
7
|
+
them).
|
|
8
|
+
|
|
9
|
+
Where it sits: ACCC forges an attention chain; CORCC forges a CoR (embedding an
|
|
10
|
+
AC); PersonaCC forges the whole PERSONA (a set of blocks + ACs + ≥1 CoR that uses
|
|
11
|
+
them) and SEATS it into `chainaios.make_gba(persona=…)`. ACCC → CORCC → PersonaCC.
|
|
12
|
+
|
|
13
|
+
The headline check is `uses_them` — the lint that makes the "…that USES them"
|
|
14
|
+
clause of the definition mechanically checkable. See its docstring for the rule
|
|
15
|
+
and the honest weakening.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
import tempfile
|
|
22
|
+
|
|
23
|
+
from accc import Language as ACLanguage
|
|
24
|
+
from accc import forge as ac_forge
|
|
25
|
+
from accc import gate as ac_gate
|
|
26
|
+
from accc import parse_ac
|
|
27
|
+
from corcc import Persona, cor_chain, forge_persona, inner_template, prime as cor_prime
|
|
28
|
+
from chaincompiler import slugify, write_skill # made FROM CC (CC re-exports APE's write_skill/slugify)
|
|
29
|
+
|
|
30
|
+
from .notation import Block, PersonaBundle
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ── notation helpers ─────────────────────────────────────────────────────────
|
|
34
|
+
def _focus_set(chains: list[str]) -> set[str]:
|
|
35
|
+
"""Every focus (ordered `[Focus]` term AND the `|Held|`) across the chains."""
|
|
36
|
+
foci: set[str] = set()
|
|
37
|
+
for ch in chains:
|
|
38
|
+
try:
|
|
39
|
+
ordered, held = parse_ac(ch)
|
|
40
|
+
except Exception:
|
|
41
|
+
continue # malformed chains are caught by `lint`, not here
|
|
42
|
+
foci.update(ordered)
|
|
43
|
+
if held:
|
|
44
|
+
foci.add(held)
|
|
45
|
+
return foci
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _derived_focus_set(bundle: PersonaBundle) -> set[str]:
|
|
49
|
+
"""The union of every CoR's OWN derived chain foci (= all its move names)."""
|
|
50
|
+
return _focus_set([cor_chain(cor) for cor in bundle.cors])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ── the notation lint (syntax + structure) ───────────────────────────────────
|
|
54
|
+
def lint(bundle: PersonaBundle) -> list[str]:
|
|
55
|
+
"""Syntax/structure lint of a persona bundle's NOTATION (the *CC check).
|
|
56
|
+
|
|
57
|
+
Checks, without judging any content:
|
|
58
|
+
- every AC string is a well-formed rune attention chain (parseable, has a
|
|
59
|
+
bounded `|Held|`);
|
|
60
|
+
- every CoR's derived chain is well-formed;
|
|
61
|
+
- the bundle has ≥1 CoR and every block has a non-empty kind.
|
|
62
|
+
|
|
63
|
+
Returns a list of human-readable issue strings (empty = clean notation). This
|
|
64
|
+
is the "is it well-formed?" gate; the "does the CoR use the ACs?" gate is the
|
|
65
|
+
separate `uses_them` below.
|
|
66
|
+
"""
|
|
67
|
+
issues: list[str] = []
|
|
68
|
+
if not bundle.cors:
|
|
69
|
+
issues.append("bundle has no CoR (a persona needs ≥1 CoR)")
|
|
70
|
+
for i, ac in enumerate(bundle.acs):
|
|
71
|
+
try:
|
|
72
|
+
_, held = parse_ac(ac)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
issues.append(f"AC[{i}] {ac!r}: not a well-formed attention chain ({e})")
|
|
75
|
+
continue
|
|
76
|
+
if not held:
|
|
77
|
+
issues.append(f"AC[{i}] {ac!r}: no bounded |Held| convergence")
|
|
78
|
+
for cor in bundle.cors:
|
|
79
|
+
chain = cor_chain(cor)
|
|
80
|
+
try:
|
|
81
|
+
parse_ac(chain)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
issues.append(f"CoR {cor.name!r} derived chain {chain!r}: malformed ({e})")
|
|
84
|
+
for b in bundle.blocks:
|
|
85
|
+
if not b.kind.strip():
|
|
86
|
+
issues.append("a block has an empty kind")
|
|
87
|
+
# TOTAL SYNTAXIFICATION: a chain-graded block that MELTS its own gauge is a hard
|
|
88
|
+
# notation issue (folded in here). Prose blocks are only WARNINGS (soup is tolerated
|
|
89
|
+
# for migration), so they ride the warnings channel of `lint_blocks` — folding them
|
|
90
|
+
# in as issues would wrongly fail every migration-era bundle.
|
|
91
|
+
block_issues, _block_warnings = lint_blocks(bundle)
|
|
92
|
+
issues.extend(block_issues)
|
|
93
|
+
return issues
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ── the language-gate registry (TYPE ⊥ SYNTAX) ───────────────────────────────
|
|
97
|
+
# A block is (type, LANGUAGE, string). Each chain LANGUAGE carries its own melt-gauge:
|
|
98
|
+
# a callable `gate(body: str) -> list[str]` returning violation strings (empty = clean).
|
|
99
|
+
# "rune" is the built-in honeyc Dense Rune-Chain gauge; new languages register here.
|
|
100
|
+
|
|
101
|
+
def _rune_gate(body: str) -> list[str]:
|
|
102
|
+
"""The 'rune' language gate = the block melt-gauge (the SAME gauge CORCC uses on a CoR).
|
|
103
|
+
|
|
104
|
+
CORCC forges an accc Language from a CoR's canonical `cor_chain(spec)` and gates the
|
|
105
|
+
spoken CoR text against it (`corcc.lint` = `ac_gate(persona.inner, cor_text)`); drift
|
|
106
|
+
out of the syntax = the persona "melting". The block analogue: the block IS its own
|
|
107
|
+
notation, so we forge the Language from the block's CLEAN rune skeleton (the canonical
|
|
108
|
+
chain `parse_ac` recovers) and gate the RAW body against it. A clean chain equals its
|
|
109
|
+
skeleton → 0 violations; a body that hides prose between foci, misplaces the `|Held|`,
|
|
110
|
+
or drops a `⇒` drifts from the skeleton → the gate fires. The forge needs a scratch DB
|
|
111
|
+
(the gate is only defined against a forged Language — the forge-backed check, stated
|
|
112
|
+
plainly), so a throwaway temp DB is used and discarded per call.
|
|
113
|
+
"""
|
|
114
|
+
foci, held = parse_ac(body) # body is rune-chain-graded, so this succeeds
|
|
115
|
+
canonical = " ⇒ ".join(f"[{f}]" for f in foci)
|
|
116
|
+
if held:
|
|
117
|
+
canonical += f" ⇒ |{held}|"
|
|
118
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
119
|
+
language = ac_forge("rune-block-gate", [canonical], db=str(Path(tmp) / "rune-gate.db"))
|
|
120
|
+
violations, _warnings = ac_gate(language, body)
|
|
121
|
+
if violations:
|
|
122
|
+
return [f"{len(violations)} violation(s) vs its rune skeleton {canonical!r}"]
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
LANG_GATES: dict[str, callable] = {"rune": _rune_gate}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def register_lang_gate(name: str, fn) -> None:
|
|
130
|
+
"""Register a melt-gauge for a chain LANGUAGE: `fn(body: str) -> list[str]` (violations).
|
|
131
|
+
|
|
132
|
+
TYPE ⊥ SYNTAX: registering a gate makes a language LINTABLE — blocks written in it
|
|
133
|
+
then grade "chain" and are melt-gauged like any rune block. Until a language has a
|
|
134
|
+
gate it is unlintable = soup-grade (its blocks grade "prose")."""
|
|
135
|
+
LANG_GATES[name] = fn
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def lint_blocks(bundle: PersonaBundle) -> tuple[list[str], list[str]]:
|
|
139
|
+
"""Block-level syntaxification gate → `(issues, warnings)`, dispatched by `lang`.
|
|
140
|
+
|
|
141
|
+
For each description block, split on its `grade`:
|
|
142
|
+
- "chain" → run the language's registered melt-gauge (`LANG_GATES[block.lang]`, the
|
|
143
|
+
same gauge CORCC uses on a CoR). A chain block that drifts out of its language's
|
|
144
|
+
syntax is a hard ISSUE.
|
|
145
|
+
- "prose" → a WARNING: "unsyntactified block: <kind>" for an unparsed rune body, or
|
|
146
|
+
"no gate registered for lang=<x>" for a block in an unknown (unlintable) language.
|
|
147
|
+
Tolerated soup, but flagged so the migration to one-notation-all-the-way-down is
|
|
148
|
+
visible.
|
|
149
|
+
|
|
150
|
+
Mirrors `uses_them`'s (violations, warnings) split.
|
|
151
|
+
"""
|
|
152
|
+
issues: list[str] = []
|
|
153
|
+
warnings: list[str] = []
|
|
154
|
+
for b in bundle.blocks:
|
|
155
|
+
if b.grade == "chain":
|
|
156
|
+
gate = LANG_GATES.get(b.lang)
|
|
157
|
+
if gate is None: # defensive: grade==chain implies a gate exists, but be safe
|
|
158
|
+
warnings.append(f"no gate registered for lang={b.lang}")
|
|
159
|
+
continue
|
|
160
|
+
for v in gate(b.body):
|
|
161
|
+
issues.append(f"block {b.kind!r} (lang={b.lang}) MELTS the gate: {v}")
|
|
162
|
+
elif b.lang != "rune" and b.lang not in LANG_GATES:
|
|
163
|
+
warnings.append(f"no gate registered for lang={b.lang}")
|
|
164
|
+
else:
|
|
165
|
+
warnings.append(f"unsyntactified block: {b.kind}")
|
|
166
|
+
return issues, warnings
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ── the "…that USES them" lint (the headline check) ──────────────────────────
|
|
170
|
+
def uses_them(bundle: PersonaBundle, *, strict: bool = False) -> tuple[list[str], list[str]]:
|
|
171
|
+
"""Check the "…that USES them" clause of the persona definition.
|
|
172
|
+
|
|
173
|
+
THE RULE (as ratified):
|
|
174
|
+
Every CoR move name must appear as a FOCUS in at least one of the bundle's
|
|
175
|
+
AC chains, OR in the CoR's OWN derived chain (`corcc.cor_chain(cor)`).
|
|
176
|
+
|
|
177
|
+
Honest implementation note (the weakening, stated loudly). A CoR's own derived
|
|
178
|
+
chain is `[m0] ⇒ [m1] ⇒ … ⇒ |m_last|` — so it ALWAYS contains every one of
|
|
179
|
+
that CoR's move names. Taken literally, the "OR the CoR's own derived chain"
|
|
180
|
+
escape hatch means a well-formed bundle can NEVER hard-fail. That is by design
|
|
181
|
+
for the seed/proof bundles, whose ACs (their real attention atoms) legitimately
|
|
182
|
+
do NOT re-list every CoR move — failing them would be wrong. So:
|
|
183
|
+
|
|
184
|
+
- DEFAULT (`strict=False`): the escape hatch is ON. A move grounded by a real
|
|
185
|
+
bundle AC is clean; a move grounded ONLY by its CoR's own derived chain is
|
|
186
|
+
a WARNING ("declared but not wired to an attention chain"); a move grounded
|
|
187
|
+
by NEITHER is a hard VIOLATION (only reachable via a structurally broken
|
|
188
|
+
spec). This is the "weaken to a warning and SAY SO" mode.
|
|
189
|
+
|
|
190
|
+
- STRICT (`strict=True`): the escape hatch is OFF. A move must be a focus in
|
|
191
|
+
a real bundle AC or it is a VIOLATION. This is the teeth: it fires on any
|
|
192
|
+
bundle whose CoR is not actually wired to its attention chains.
|
|
193
|
+
|
|
194
|
+
Returns `(violations, warnings)` — lists of human-readable strings.
|
|
195
|
+
"""
|
|
196
|
+
ac_foci = _focus_set(list(bundle.acs))
|
|
197
|
+
violations: list[str] = []
|
|
198
|
+
warnings: list[str] = []
|
|
199
|
+
for cor in bundle.cors:
|
|
200
|
+
own_foci = _focus_set([cor_chain(cor)])
|
|
201
|
+
for move in cor.moves:
|
|
202
|
+
n = move.name
|
|
203
|
+
if n in ac_foci:
|
|
204
|
+
continue # grounded by a real bundle AC — fully wired
|
|
205
|
+
if strict:
|
|
206
|
+
violations.append(
|
|
207
|
+
f"{cor.name}.{n}: CoR move is not a focus in any bundle AC (strict)")
|
|
208
|
+
elif n in own_foci:
|
|
209
|
+
warnings.append(
|
|
210
|
+
f"{cor.name}.{n}: grounded only by the CoR's own derived chain, "
|
|
211
|
+
"not surfaced as a focus in any bundle AC")
|
|
212
|
+
else:
|
|
213
|
+
violations.append(
|
|
214
|
+
f"{cor.name}.{n}: move appears in neither the bundle ACs nor the "
|
|
215
|
+
"CoR's own derived chain (structurally broken)")
|
|
216
|
+
return violations, warnings
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ── forge ────────────────────────────────────────────────────────────────────
|
|
220
|
+
@dataclass
|
|
221
|
+
class ForgedBundle:
|
|
222
|
+
"""The compiled artifacts of a persona bundle."""
|
|
223
|
+
bundle: PersonaBundle
|
|
224
|
+
ac_languages: list[ACLanguage] # one forged AC language per bundle AC (via ACCC)
|
|
225
|
+
personas: list[Persona] # one forged persona per CoR (via CORCC)
|
|
226
|
+
uses_violations: list[str] = field(default_factory=list)
|
|
227
|
+
uses_warnings: list[str] = field(default_factory=list)
|
|
228
|
+
notation_issues: list[str] = field(default_factory=list) # includes any melted chain-block issues
|
|
229
|
+
block_warnings: list[str] = field(default_factory=list) # prose (unsyntactified) blocks — tolerated
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def name(self) -> str:
|
|
233
|
+
return self.bundle.name
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def clean(self) -> bool:
|
|
237
|
+
return not self.notation_issues and not self.uses_violations
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def forge_bundle(bundle: PersonaBundle, *, db: str, strict_uses: bool = False) -> ForgedBundle:
|
|
241
|
+
"""Forge a persona bundle: forge each AC via ACCC, forge each CoR via CORCC.
|
|
242
|
+
|
|
243
|
+
- Each AC string is forged into its own `accc` Language (ACCC → the `ac:` kind).
|
|
244
|
+
- Each CoR (`corcc.PersonaSpec`) is forged into a `corcc.Persona` (which itself
|
|
245
|
+
forges the CoR's inner attention chain — CORCC composes ACCC).
|
|
246
|
+
- The bundle is linted (`lint`) for notation validity and gated by `uses_them`
|
|
247
|
+
for the "…that uses them" clause.
|
|
248
|
+
|
|
249
|
+
`strict_uses` selects the `uses_them` mode (see its docstring). This never
|
|
250
|
+
raises on a lint failure — it records the issues on the `ForgedBundle` so the
|
|
251
|
+
caller decides; that mirrors the other *CC (lint reports, it does not throw).
|
|
252
|
+
"""
|
|
253
|
+
notation_issues = lint(bundle) # already includes melted chain-block issues
|
|
254
|
+
_block_issues, block_warnings = lint_blocks(bundle) # recover the prose-block warnings
|
|
255
|
+
ac_languages = [
|
|
256
|
+
ac_forge(f"{slugify(bundle.name)}-ac{i}", [ac], db=db)
|
|
257
|
+
for i, ac in enumerate(bundle.acs)
|
|
258
|
+
]
|
|
259
|
+
personas = [forge_persona(cor, db=db) for cor in bundle.cors]
|
|
260
|
+
violations, warnings = uses_them(bundle, strict=strict_uses)
|
|
261
|
+
return ForgedBundle(
|
|
262
|
+
bundle=bundle, ac_languages=ac_languages, personas=personas,
|
|
263
|
+
uses_violations=violations, uses_warnings=warnings, notation_issues=notation_issues,
|
|
264
|
+
block_warnings=block_warnings,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _safe_prime(persona: Persona) -> str:
|
|
269
|
+
"""CORCC's persona PRIME, with a cue-free fallback.
|
|
270
|
+
|
|
271
|
+
Some CoR specs recovered from the proof GBAs have no per-move `cues` persisted
|
|
272
|
+
(the original build scripts weren't retained — honest under-specification, not
|
|
273
|
+
invented). CORCC's cue-based paragraph skeleton can't render those, so fall
|
|
274
|
+
back to a cue-free prime (inner chain + the ordered moves)."""
|
|
275
|
+
try:
|
|
276
|
+
return cor_prime(persona)
|
|
277
|
+
except Exception:
|
|
278
|
+
spec = persona.spec
|
|
279
|
+
moves = " → ".join(m.name for m in spec.moves)
|
|
280
|
+
return "\n".join([
|
|
281
|
+
f'# PRIME — persona "{spec.name}"',
|
|
282
|
+
f"({spec.blurb})" if spec.blurb else "",
|
|
283
|
+
"",
|
|
284
|
+
"## Inner attention chain (use silently)",
|
|
285
|
+
inner_template(persona),
|
|
286
|
+
"",
|
|
287
|
+
"## Chain-of-reasoning moves (perform in order; the last is the held convergence)",
|
|
288
|
+
moves,
|
|
289
|
+
"",
|
|
290
|
+
"_(per-move cue phrases were not persisted for this recovered persona.)_",
|
|
291
|
+
])
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ── render / package ─────────────────────────────────────────────────────────
|
|
295
|
+
def render_blocks(bundle: PersonaBundle) -> str:
|
|
296
|
+
"""The description blocks rendered as markdown sections (one `##` per block)."""
|
|
297
|
+
out: list[str] = []
|
|
298
|
+
for b in bundle.blocks:
|
|
299
|
+
title = b.kind.replace("_", " ").strip().title()
|
|
300
|
+
out.append(f"## {title}")
|
|
301
|
+
out.append("")
|
|
302
|
+
out.append(b.body.strip())
|
|
303
|
+
out.append("")
|
|
304
|
+
return "\n".join(out).strip()
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def role_block(bundle: PersonaBundle) -> str | None:
|
|
308
|
+
"""Assemble a CLAUDE.md-ready role block from the description blocks.
|
|
309
|
+
|
|
310
|
+
Returns None when the bundle carries no blocks (a degenerate persona whose
|
|
311
|
+
identity is its CoR — `make_gba` then derives the role from the persona moves).
|
|
312
|
+
"""
|
|
313
|
+
if not bundle.blocks:
|
|
314
|
+
return None
|
|
315
|
+
header = f"# {bundle.name}"
|
|
316
|
+
if bundle.blurb:
|
|
317
|
+
header += f"\n\n{bundle.blurb}"
|
|
318
|
+
body = render_blocks(bundle)
|
|
319
|
+
return f"{header}\n\n{body}".strip()
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def package(bundle: PersonaBundle, out_dir: str, *, db: str | None = None,
|
|
323
|
+
description: str | None = None) -> Path:
|
|
324
|
+
"""Compile the persona bundle to a publishable `<name>/SKILL.md` package.
|
|
325
|
+
|
|
326
|
+
The body renders: the description blocks as sections + the AC set + each CoR's
|
|
327
|
+
re-injectable PRIME (from CORCC). Written via `chaincompiler.write_skill`.
|
|
328
|
+
A `db` is needed to forge each CoR's prime; if omitted a temp DB is used.
|
|
329
|
+
"""
|
|
330
|
+
desc = description or (
|
|
331
|
+
f"Persona (blocks + attention chains + chain-of-reasoning): {bundle.blurb}."
|
|
332
|
+
if bundle.blurb else f"Persona bundle {bundle.name}."
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def _build(db_path: str) -> str:
|
|
336
|
+
personas = [forge_persona(cor, db=db_path) for cor in bundle.cors]
|
|
337
|
+
parts: list[str] = [
|
|
338
|
+
"This is a **persona** — a set of DESCRIPTION BLOCKS, a set of ATTENTION CHAINS "
|
|
339
|
+
"(how to think), and one or more CHAINS OF REASONING (spoken, ending in a decision) "
|
|
340
|
+
"that use them. Adopt the blocks; think along the attention chains; perform the CoR.",
|
|
341
|
+
"",
|
|
342
|
+
]
|
|
343
|
+
blocks_md = render_blocks(bundle)
|
|
344
|
+
if blocks_md:
|
|
345
|
+
parts += ["# Description blocks", "", blocks_md, ""]
|
|
346
|
+
parts += ["# Attention chains (how to think — silent templates)", ""]
|
|
347
|
+
if bundle.acs:
|
|
348
|
+
parts.append("```rune")
|
|
349
|
+
parts += [ac.strip() for ac in bundle.acs]
|
|
350
|
+
parts.append("```")
|
|
351
|
+
else:
|
|
352
|
+
parts.append("_(none — this persona's chains are carried by its CoR)_")
|
|
353
|
+
parts.append("")
|
|
354
|
+
parts += ["# Chains of reasoning (spoken; you MUST perform these, in order)", ""]
|
|
355
|
+
for p in personas:
|
|
356
|
+
parts.append(_safe_prime(p))
|
|
357
|
+
parts.append("")
|
|
358
|
+
return "\n".join(parts).rstrip()
|
|
359
|
+
|
|
360
|
+
if db is None:
|
|
361
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
362
|
+
body = _build(str(Path(tmp) / "personacc.db"))
|
|
363
|
+
else:
|
|
364
|
+
body = _build(db)
|
|
365
|
+
return write_skill(bundle.name, desc, body, out_dir=out_dir, extra={"kind": "persona-bundle"})
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# ── seat — the make_gba seam ─────────────────────────────────────────────────
|
|
369
|
+
def seat(bundle: PersonaBundle) -> dict:
|
|
370
|
+
"""Return exactly the kwargs `chainaios.make_gba(domain, root, **seat(bundle))` accepts.
|
|
371
|
+
|
|
372
|
+
{
|
|
373
|
+
"atoms": list(bundle.acs), # the attention chains become the GBA's atoms
|
|
374
|
+
"blurb": bundle.blurb,
|
|
375
|
+
"role_block": role_block(bundle), # rendered from the description blocks (or None)
|
|
376
|
+
"persona": bundle.cors[0], # the first CoR is seated as the GBA's CoR
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
FUTURE WORK: only `cors[0]` is seated. A GBA seats exactly one CoR persona
|
|
380
|
+
today (the melt-gauge is one ordered move-chain), so multi-CoR bundles have
|
|
381
|
+
their remaining CoRs compiled into the SKILL.md by `package` but not seated.
|
|
382
|
+
Seating additional CoRs (e.g. as sub-personas / a routed ensemble) is future
|
|
383
|
+
work — it needs a `make_gba` that carries more than one persona seam.
|
|
384
|
+
"""
|
|
385
|
+
return {
|
|
386
|
+
"atoms": list(bundle.acs),
|
|
387
|
+
"blurb": bundle.blurb,
|
|
388
|
+
"role_block": role_block(bundle),
|
|
389
|
+
"persona": bundle.cors[0],
|
|
390
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""PersonaCC notation — the PersonaBundle: what a persona actually IS.
|
|
2
|
+
|
|
3
|
+
THE RATIFIED DEFINITION (the author):
|
|
4
|
+
|
|
5
|
+
a persona = a specific set of DESCRIPTION BLOCKS
|
|
6
|
+
+ a set of ACs (attention chains)
|
|
7
|
+
+ at least one CoR that USES them.
|
|
8
|
+
|
|
9
|
+
PersonaCC is the SYNTAX compiler for that bundle. Its source language is the 2023
|
|
10
|
+
SDNA-template block grammar: a persona prompt is a stack of `[KIND] … [/KIND]`
|
|
11
|
+
DESCRIPTION BLOCKS (role, description, approach, wrapper, invocation-terms, …)
|
|
12
|
+
wrapped around a reasoning core. Here that core is made rigorous: the reasoning
|
|
13
|
+
core is one-or-more `corcc.PersonaSpec` CoRs, each grounded in a set of `accc`
|
|
14
|
+
attention chains.
|
|
15
|
+
|
|
16
|
+
Two atoms compose UP into a bundle:
|
|
17
|
+
- `Block` — one description block. `kind` is a free string; NEW block
|
|
18
|
+
kinds are legal by design (the template-template rule: the
|
|
19
|
+
block grammar is extensible, not a closed enum).
|
|
20
|
+
- `PersonaBundle` — the whole persona: blocks + ACs + ≥1 CoR + a blurb.
|
|
21
|
+
|
|
22
|
+
Note on the type below CORCC's `PersonaSpec`: that type is really the *COR SPEC*
|
|
23
|
+
(a half-persona — the spoken reasoning move-chain only). PersonaCC does NOT rename
|
|
24
|
+
it; it builds the FULL persona ABOVE it, adding the description blocks and the
|
|
25
|
+
attention chains the CoR is meant to use.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
|
|
31
|
+
from accc import parse_ac # accc's REAL chain parser — used for the honest is_chain check
|
|
32
|
+
from corcc.notation import PersonaSpec # NOTE: really the COR SPEC (half-persona). Do not rename.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class Block:
|
|
37
|
+
"""One description block of a persona prompt (an SDNA-template block).
|
|
38
|
+
|
|
39
|
+
TYPE ⊥ SYNTAX (the author's law): a block is really a triple — (type-`kind`, `lang`,
|
|
40
|
+
`body` string). The TYPE (what the block IS, e.g. "wrapper") is orthogonal to the
|
|
41
|
+
SYNTAX it is rendered in; the same block type renders differently in different
|
|
42
|
+
chain LANGUAGES (one uses only `⇒`, another `-> * + =` in sequence). The `body`
|
|
43
|
+
string is one RENDER of the canonical typed content in a chosen language.
|
|
44
|
+
|
|
45
|
+
- `kind` is a FREE STRING (e.g. "role", "description", "approach", "wrapper",
|
|
46
|
+
"invocation_terms" — or anything new). New kinds are legal by design; the
|
|
47
|
+
block grammar is a template-template, so it never closes over a fixed enum.
|
|
48
|
+
- `lang` names the chain language the `body` is written in. "rune" (the default)
|
|
49
|
+
is the honeyc Dense Rune-Chain that accc parses/gates. Other languages are
|
|
50
|
+
lintable only once a gate is registered for them (`forge.register_lang_gate`).
|
|
51
|
+
- `body` is the block's string (a chain in `lang`, or — soup-grade — prose).
|
|
52
|
+
"""
|
|
53
|
+
kind: str
|
|
54
|
+
body: str
|
|
55
|
+
lang: str = "rune"
|
|
56
|
+
|
|
57
|
+
def __post_init__(self) -> None:
|
|
58
|
+
if not self.kind or not self.kind.strip():
|
|
59
|
+
raise ValueError("Block.kind must be a non-empty string")
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_chain(self) -> bool:
|
|
63
|
+
"""True iff this block's body is a conformant chain IN ITS OWN `lang`.
|
|
64
|
+
|
|
65
|
+
Keyed to `lang`:
|
|
66
|
+
- "rune" — HONEST DETECTION via accc's REAL parser (not a regex). accc
|
|
67
|
+
exposes `parse_ac`, which runs the HoneyC parser and raises unless the
|
|
68
|
+
body contains a well-formed attention chain (`[Focus] ⇒ … ⇒ |Held|`).
|
|
69
|
+
That clean parse-check exists, so we use it directly; the
|
|
70
|
+
forge-in-a-throwaway-db fallback is NOT needed and is deliberately not
|
|
71
|
+
used. (Broad `except` because a non-chain body can trip the parser with
|
|
72
|
+
parser-internal errors, not only ValueError.)
|
|
73
|
+
- any OTHER lang — we cannot parse an arbitrary language, so it is a chain
|
|
74
|
+
iff a gate is REGISTERED for that lang (an unknown language is unlintable
|
|
75
|
+
= soup-grade). The gate itself (the melt-gauge) runs later in
|
|
76
|
+
`forge.lint_blocks`.
|
|
77
|
+
"""
|
|
78
|
+
if self.lang == "rune":
|
|
79
|
+
try:
|
|
80
|
+
parse_ac(self.body)
|
|
81
|
+
return True
|
|
82
|
+
except Exception:
|
|
83
|
+
return False
|
|
84
|
+
from .forge import LANG_GATES # lazy: forge imports notation; avoid a cycle
|
|
85
|
+
return self.lang in LANG_GATES
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def grade(self) -> str:
|
|
89
|
+
"""The syntaxification grade of this block:
|
|
90
|
+
|
|
91
|
+
- "chain" — the body is real chain syntax in its `lang` (code-grade; melt-gaugeable).
|
|
92
|
+
- "prose" — free natural language / an unlintable language (soup-grade; TOLERATED
|
|
93
|
+
for migration only).
|
|
94
|
+
|
|
95
|
+
This is the TOTAL-SYNTAXIFICATION gauge at the block level: every persona block
|
|
96
|
+
should eventually grade "chain" (one notation all the way down); NL is licensed
|
|
97
|
+
only as a component the syntax admits, never as a free-standing block.
|
|
98
|
+
"""
|
|
99
|
+
return "chain" if self.is_chain else "prose"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class PersonaBundle:
|
|
104
|
+
"""A persona: DESCRIPTION BLOCKS + ACs + ≥1 CoR that uses them.
|
|
105
|
+
|
|
106
|
+
- `name` : the persona's name (also the SKILL.md slug).
|
|
107
|
+
- `blocks` : the description blocks (may be empty for a degenerate persona
|
|
108
|
+
whose identity is carried entirely by its CoR — e.g. the seeds).
|
|
109
|
+
- `acs` : attention-chain strings in the shared Dense-Rune notation
|
|
110
|
+
(`[Focus] ⇒ [Focus] ⇒ |Held|`), each parseable by `accc`.
|
|
111
|
+
- `cors` : one or more `corcc.PersonaSpec` CoRs. ≥1 is REQUIRED — a persona
|
|
112
|
+
with no reasoning core is not a persona.
|
|
113
|
+
- `blurb` : one-line description.
|
|
114
|
+
"""
|
|
115
|
+
name: str
|
|
116
|
+
blocks: tuple[Block, ...]
|
|
117
|
+
acs: tuple[str, ...]
|
|
118
|
+
cors: tuple[PersonaSpec, ...]
|
|
119
|
+
blurb: str = ""
|
|
120
|
+
|
|
121
|
+
def __post_init__(self) -> None:
|
|
122
|
+
if not self.cors:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"PersonaBundle {self.name!r} needs ≥1 CoR — a persona = description "
|
|
125
|
+
"blocks + ACs + at least one CoR that uses them (the ratified definition)."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def block_kinds(self) -> tuple[str, ...]:
|
|
129
|
+
return tuple(b.kind for b in self.blocks)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: personacc
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Persona Compiler-Compiler — compiles a persona bundle (description blocks + attention chains + ≥1 chain-of-reasoning that uses them) and seats it into make_gba
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: chaincompiler-accc==0.3.0
|
|
7
|
+
Requires-Dist: corcc==0.3.0
|
|
8
|
+
Requires-Dist: chaincompiler==0.3.0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/personacc/__init__.py
|
|
4
|
+
src/personacc/forge.py
|
|
5
|
+
src/personacc/notation.py
|
|
6
|
+
src/personacc.egg-info/PKG-INFO
|
|
7
|
+
src/personacc.egg-info/SOURCES.txt
|
|
8
|
+
src/personacc.egg-info/dependency_links.txt
|
|
9
|
+
src/personacc.egg-info/requires.txt
|
|
10
|
+
src/personacc.egg-info/top_level.txt
|
|
11
|
+
tests/test_personacc.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
personacc
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""PersonaCC tests: bundle validity, the uses-them lint, notation lint, package, seat.
|
|
2
|
+
|
|
3
|
+
Self-contained — uses corcc seed CoRs (no personapack dependency). The
|
|
4
|
+
"passes on BODHISATTVA" half of the uses-them requirement is tested in
|
|
5
|
+
personapack (where BODHISATTVA lives)."""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from corcc import cor_chain
|
|
13
|
+
from corcc.notation import EINSTEIN, BANDIT
|
|
14
|
+
|
|
15
|
+
import personacc as pc
|
|
16
|
+
from personacc import Block, PersonaBundle
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ── a helper: a well-wired bundle whose acs = the CoR's own derived chain ──────
|
|
20
|
+
def _wired(spec=EINSTEIN, blocks=()):
|
|
21
|
+
return PersonaBundle(name=spec.name, blocks=blocks, acs=(cor_chain(spec),),
|
|
22
|
+
cors=(spec,), blurb=spec.blurb)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_bundle_requires_at_least_one_cor():
|
|
26
|
+
with pytest.raises(ValueError):
|
|
27
|
+
PersonaBundle(name="Empty", blocks=(), acs=("[A] ⇒ [B] ⇒ |C|",), cors=(), blurb="")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_block_kind_is_extensible():
|
|
31
|
+
# ANY kind string is legal (the template-template rule) — no closed enum.
|
|
32
|
+
b = Block(kind="a-brand-new-kind", body="hi")
|
|
33
|
+
assert b.kind == "a-brand-new-kind"
|
|
34
|
+
with pytest.raises(ValueError):
|
|
35
|
+
Block(kind=" ", body="x")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_blocks_render_as_sections():
|
|
39
|
+
bundle = _wired(blocks=(Block("role", "You are X."), Block("invocation_terms", "a · b")))
|
|
40
|
+
md = pc.render_blocks(bundle)
|
|
41
|
+
assert "## Role" in md and "You are X." in md
|
|
42
|
+
assert "## Invocation Terms" in md and "a · b" in md
|
|
43
|
+
rb = pc.role_block(bundle)
|
|
44
|
+
assert rb is not None and "# ThinkLikeEinstein" in rb
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_role_block_none_when_no_blocks():
|
|
48
|
+
assert pc.role_block(_wired(blocks=())) is None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_notation_lint_catches_malformed_ac():
|
|
52
|
+
good = _wired()
|
|
53
|
+
assert pc.lint(good) == []
|
|
54
|
+
broken = PersonaBundle(name="Bad", blocks=(), acs=("this is not a rune chain",),
|
|
55
|
+
cors=(BANDIT,), blurb="")
|
|
56
|
+
issues = pc.lint(broken)
|
|
57
|
+
assert issues, "a malformed AC string should be flagged by the notation lint"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_uses_them_clean_when_acs_cover_moves():
|
|
61
|
+
# acs = the CoR's own derived chain -> every move is a focus in a real AC.
|
|
62
|
+
v, w = pc.uses_them(_wired())
|
|
63
|
+
assert v == [] and w == []
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_uses_them_fires_on_deliberately_broken_bundle():
|
|
67
|
+
# acs do NOT cover the CoR's moves -> strict fires as VIOLATIONS, default as WARNINGS.
|
|
68
|
+
broken = PersonaBundle(name="Broken", blocks=(),
|
|
69
|
+
acs=("[Alpha] ⇒ [Beta] ⇒ |Gamma|",), cors=(EINSTEIN,), blurb="")
|
|
70
|
+
v_strict, _ = pc.uses_them(broken, strict=True)
|
|
71
|
+
assert v_strict, "strict uses-them must fire on a CoR not wired to any AC"
|
|
72
|
+
v_default, w_default = pc.uses_them(broken) # weakened mode
|
|
73
|
+
assert v_default == [] and w_default, "default mode reports warnings, not violations"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_package_writes_skill_md(tmp_path: Path):
|
|
77
|
+
path = pc.package(_wired(blocks=(Block("role", "You are Einstein."),)), out_dir=str(tmp_path))
|
|
78
|
+
assert path.name == "SKILL.md"
|
|
79
|
+
text = path.read_text()
|
|
80
|
+
assert "persona-bundle" in text # frontmatter kind
|
|
81
|
+
assert "Attention chains" in text # the AC set section
|
|
82
|
+
assert "PRIME" in text # each CoR's prime is rendered
|
|
83
|
+
assert cor_chain(EINSTEIN) in text # the CoR chain is present
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_seat_returns_make_gba_kwargs():
|
|
87
|
+
kw = pc.seat(_wired(blocks=(Block("role", "You are Einstein."),)))
|
|
88
|
+
assert set(kw) == {"atoms", "blurb", "role_block", "persona"}
|
|
89
|
+
assert kw["atoms"] == [cor_chain(EINSTEIN)]
|
|
90
|
+
assert kw["persona"] is EINSTEIN
|
|
91
|
+
assert kw["role_block"] is not None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ── block grades + the block melt-gauge (TOTAL SYNTAXIFICATION) ────────────────
|
|
95
|
+
def test_block_grades_chain_and_prose():
|
|
96
|
+
chain = Block("wrapper", "[Output] ⇒ [Adorn] ⇒ |Wrapped|")
|
|
97
|
+
assert chain.is_chain and chain.grade == "chain"
|
|
98
|
+
prose = Block("role", "You are a helpful wrapper.")
|
|
99
|
+
assert not prose.is_chain and prose.grade == "prose"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_lint_blocks_chain_clean_prose_warns():
|
|
103
|
+
bundle = _wired(blocks=(Block("wrapper", "[Output] ⇒ [Adorn] ⇒ |Wrapped|"),
|
|
104
|
+
Block("role", "You are X.")))
|
|
105
|
+
issues, warnings = pc.lint_blocks(bundle)
|
|
106
|
+
assert issues == [] # the chain block lints clean
|
|
107
|
+
assert any("unsyntactified block: role" in w for w in warnings)
|
|
108
|
+
# a clean chain block + prose warnings ⇒ lint() stays empty (prose is tolerated soup).
|
|
109
|
+
assert pc.lint(bundle) == []
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_lint_blocks_malformed_chain_fires_gate():
|
|
113
|
+
bad = _wired(blocks=(Block("wrapper", "[Output] ⇒ [Adorn] this then blah ⇒ |Wrapped|"),))
|
|
114
|
+
assert bad.blocks[0].grade == "chain" # it still parses as a chain…
|
|
115
|
+
issues, _ = pc.lint_blocks(bad)
|
|
116
|
+
assert issues, "a malformed chain-block must fire its melt-gauge"
|
|
117
|
+
assert pc.lint(bad), "…and the melted-block issue folds into lint()"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_unknown_lang_grades_prose_until_gate_registered():
|
|
121
|
+
# TYPE ⊥ SYNTAX: an unregistered language is unlintable = soup-grade, until a gate lands.
|
|
122
|
+
from personacc.forge import LANG_GATES, register_lang_gate
|
|
123
|
+
b = Block("wrapper", "A -> B -> C", lang="arrowlang")
|
|
124
|
+
assert b.grade == "prose" # no gate registered yet
|
|
125
|
+
_, warnings = pc.lint_blocks(_wired(blocks=(b,)))
|
|
126
|
+
assert any("no gate registered for lang=arrowlang" in w for w in warnings)
|
|
127
|
+
try:
|
|
128
|
+
register_lang_gate("arrowlang", lambda body: [] if "->" in body else ["missing ->"])
|
|
129
|
+
assert b.grade == "chain" # now lintable ⇒ chain-graded
|
|
130
|
+
issues, _ = pc.lint_blocks(_wired(blocks=(b,)))
|
|
131
|
+
assert issues == [] # "A -> B -> C" contains "->" ⇒ clean
|
|
132
|
+
finally:
|
|
133
|
+
LANG_GATES.pop("arrowlang", None)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_seat_smoke_builds_a_gba(tmp_path: Path):
|
|
137
|
+
# seat() kwargs actually drive make_gba into a closed GBA (uses EINSTEIN — full cues).
|
|
138
|
+
from chainaios.gba import make_gba
|
|
139
|
+
bundle = _wired(blocks=(Block("role", "You are Einstein."),))
|
|
140
|
+
g = make_gba("physics", tmp_path / "aios", **pc.seat(bundle))
|
|
141
|
+
assert g.claude_md.exists()
|
|
142
|
+
assert g.closed
|