chaincompiler 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-0.3.0/PKG-INFO +11 -0
- chaincompiler-0.3.0/pyproject.toml +23 -0
- chaincompiler-0.3.0/setup.cfg +4 -0
- chaincompiler-0.3.0/src/chaincompiler/__init__.py +31 -0
- chaincompiler-0.3.0/src/chaincompiler/bridge.py +36 -0
- chaincompiler-0.3.0/src/chaincompiler/forge.py +59 -0
- chaincompiler-0.3.0/src/chaincompiler/persona.py +157 -0
- chaincompiler-0.3.0/src/chaincompiler/prime.py +41 -0
- chaincompiler-0.3.0/src/chaincompiler/skillpack.py +12 -0
- chaincompiler-0.3.0/src/chaincompiler.egg-info/PKG-INFO +11 -0
- chaincompiler-0.3.0/src/chaincompiler.egg-info/SOURCES.txt +15 -0
- chaincompiler-0.3.0/src/chaincompiler.egg-info/dependency_links.txt +1 -0
- chaincompiler-0.3.0/src/chaincompiler.egg-info/requires.txt +7 -0
- chaincompiler-0.3.0/src/chaincompiler.egg-info/top_level.txt +1 -0
- chaincompiler-0.3.0/tests/test_chainpress.py +70 -0
- chaincompiler-0.3.0/tests/test_persona.py +57 -0
- chaincompiler-0.3.0/tests/test_strict_gate.py +40 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chaincompiler
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: The printing press for agent cognition chains: learn → gate → compile → prime
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: honeyc==0.3.0
|
|
7
|
+
Requires-Dist: rulecatcher==0.3.0
|
|
8
|
+
Requires-Dist: agent-skilltree>=0.4.0
|
|
9
|
+
Requires-Dist: agent-prompt-engineering>=0.2.0
|
|
10
|
+
Provides-Extra: persona
|
|
11
|
+
Requires-Dist: glyphsteer==0.3.0; extra == "persona"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "chaincompiler"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "The printing press for agent cognition chains: learn → gate → compile → prime"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
# honeyc + the *CC are vendored in this monorepo (packages/); install.sh wires them.
|
|
11
|
+
# agent-prompt-engineering is the published prompt-engine BASE chaincompiler imports DOWN onto.
|
|
12
|
+
dependencies = ["honeyc==0.3.0", "rulecatcher==0.3.0", "agent-skilltree>=0.4.0", "agent-prompt-engineering>=0.2.0"]
|
|
13
|
+
|
|
14
|
+
# A lazy import is an optional extra (DESIGN.md §18): the `persona` front end imports glyphsteer inside its functions.
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
persona = ["glyphsteer==0.3.0"]
|
|
17
|
+
|
|
18
|
+
# NOTE: the `chaincompiler` CLI command moved to the `chainaios` package (its AIOS subcommands are
|
|
19
|
+
# *CC-coupled, which would make this BASE import the *CC). The command NAME is unchanged — see
|
|
20
|
+
# packages/chainaios/pyproject.toml [project.scripts].
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""chaincompiler — the ChainCompiler BASE: the generic compiler-compiler (DESIGN.md §2).
|
|
2
|
+
|
|
3
|
+
This package imports only DOWN (APE + honeyc + rulecatcher). It is the machine the *CC are MADE FROM:
|
|
4
|
+
- forge : the generic "forge a language" core (Language / forge / gate_language / grammar / package_skill)
|
|
5
|
+
- bridge : the HoneyC compile seam + the rulecatcher grammar seam (learn / gate, re-exported from APE)
|
|
6
|
+
- prime : build_prime — the re-injectable prime block
|
|
7
|
+
- skillpack / persona : SKILL.md packaging + the glyph-persona-program compiler
|
|
8
|
+
|
|
9
|
+
ACCC / CORCC / SCCC build ON this base (they import CC). CC NEVER imports the *CC. The Bandit AIOS
|
|
10
|
+
application that orchestrates AC+CoR+SC (construct_language / GBA / HBA / COG) lives ABOVE the *CC in the
|
|
11
|
+
`chainaios` package — it was extracted out of here precisely so this base no longer imports the *CC.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
__version__ = "0.3.0"
|
|
16
|
+
|
|
17
|
+
from .bridge import compile_chain, gate, learn
|
|
18
|
+
from .prime import build_prime
|
|
19
|
+
from .skillpack import skill_markdown, slugify, write_skill
|
|
20
|
+
# the generic compiler-compiler core — the machine the *CC are MADE FROM (DESIGN.md §2). Imports only APE.
|
|
21
|
+
from .forge import Language, forge, gate_language, grammar, package_skill
|
|
22
|
+
# re-export the APE cognition helper the *CC need, so they route through CC (not around it to APE):
|
|
23
|
+
from prompt_engineering import cor_skeleton
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"learn", "gate", "compile_chain", "build_prime",
|
|
27
|
+
"write_skill", "skill_markdown", "slugify",
|
|
28
|
+
# the forge core (ACCC/CORCC/SCCC import these DOWN from here):
|
|
29
|
+
"Language", "forge", "gate_language", "grammar", "package_skill", "cor_skeleton",
|
|
30
|
+
"__version__",
|
|
31
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""The HoneyC seam (compile a gated chain into lenses) + re-exports of the rulecatcher seam.
|
|
2
|
+
|
|
3
|
+
The rulecatcher seam — `learn` / `gate` / `grammar_lines` / `foreign_tokens` — MOVED DOWN to
|
|
4
|
+
`prompt_engineering.grammar` (REBUILD-SPEC.md §4 — single source of truth, D1). chaincompiler keeps the
|
|
5
|
+
HoneyC-wrapping `compile_chain` (HoneyC is chaincompiler's concern, §6) and RE-EXPORTS the grammar seam,
|
|
6
|
+
so `from chaincompiler.bridge import learn, gate, grammar_lines` and `from chaincompiler import learn,
|
|
7
|
+
gate` stay byte-stable.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from honeyc.normalize import normalize
|
|
12
|
+
from honeyc.parser import parse_text
|
|
13
|
+
from honeyc.render import render
|
|
14
|
+
|
|
15
|
+
# the rulecatcher seam now lives in the base (DOWN edge):
|
|
16
|
+
from prompt_engineering.grammar import (
|
|
17
|
+
ForeignToken,
|
|
18
|
+
foreign_tokens,
|
|
19
|
+
gate,
|
|
20
|
+
grammar_lines,
|
|
21
|
+
learn,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def compile_chain(chain: str) -> dict:
|
|
26
|
+
"""Compile a chain through HoneyC into semantic lenses."""
|
|
27
|
+
program = parse_text(chain)
|
|
28
|
+
return {
|
|
29
|
+
"readable": render(program, "readable"),
|
|
30
|
+
"triples": render(program, "triples"),
|
|
31
|
+
"prose": render(program, "prose"),
|
|
32
|
+
"statements": normalize(program),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ["compile_chain", "learn", "gate", "grammar_lines", "foreign_tokens", "ForeignToken"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""forge.py — the generic compiler-compiler core (the "forge a language" move).
|
|
2
|
+
|
|
3
|
+
This is the machine the *CC are *made FROM* (DESIGN.md §2: ACCC/CORCC/SCCC import CC). A forged
|
|
4
|
+
language = a rulecatcher SCOPE holding a ratified grammar; `forge` mints one, `gate_language` enforces it,
|
|
5
|
+
`grammar` reads it back, `package_skill` wraps a compiled body as a publishable `<name>/SKILL.md`. Each
|
|
6
|
+
*CC calls these with its own chain-kind prefix + render/parse and adds nothing to the mechanism.
|
|
7
|
+
|
|
8
|
+
CC imports only DOWN (APE + rulecatcher); it never imports the *CC.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
from prompt_engineering import write_skill, grammar_lines, learn
|
|
15
|
+
from prompt_engineering.grammar import gate as _gate
|
|
16
|
+
from rulecatcher.db import connect, list_rules
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Language:
|
|
21
|
+
"""A forged language: a rulecatcher scope holding a ratified grammar."""
|
|
22
|
+
name: str
|
|
23
|
+
scope: str
|
|
24
|
+
db: str
|
|
25
|
+
rule_count: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def forge(kind: str, name: str, examples: list[str], *, db: str, strict: bool = False) -> Language:
|
|
29
|
+
"""Mint a `kind` language for `name` from example chains (scope = `<kind>:<name>`).
|
|
30
|
+
|
|
31
|
+
strict=False → learn the SHAPE only (next_kind): any vocabulary is allowed.
|
|
32
|
+
strict=True → also pin the vocabulary (next_token): the gate enforces domain-specific tokens.
|
|
33
|
+
"""
|
|
34
|
+
scope = f"{kind}:{name}"
|
|
35
|
+
rule_types = ("next_kind", "next_token") if strict else ("next_kind",)
|
|
36
|
+
with connect(db) as cx:
|
|
37
|
+
adopted = learn(cx, examples, scope=scope, rule_types=rule_types, label=f"forge:{name}")
|
|
38
|
+
return Language(name=name, scope=scope, db=db, rule_count=len(adopted))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def gate_language(language: Language, candidate: str) -> tuple[list, list]:
|
|
42
|
+
"""Lint a candidate chain against a forged language's ratified grammar."""
|
|
43
|
+
with connect(language.db) as cx:
|
|
44
|
+
return _gate(cx, candidate, scope=language.scope)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def grammar(language: Language) -> list[str]:
|
|
48
|
+
"""The ratified grammar lines of a forged language."""
|
|
49
|
+
with connect(language.db) as cx:
|
|
50
|
+
return grammar_lines(list_rules(cx, "adopted", scope=language.scope))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def package_skill(name: str, body: str, *, out_dir: str, description: str,
|
|
54
|
+
extra: dict | None = None):
|
|
55
|
+
"""Wrap a compiled chain body as a publishable `<name>/SKILL.md` package."""
|
|
56
|
+
return write_skill(name, description, body, out_dir=out_dir, extra=extra)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__all__ = ["Language", "forge", "gate_language", "grammar", "package_skill"]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Persona compiler — the front-end that ingests a glyph-persona-program and emits
|
|
2
|
+
the artifacts the rest of ChainCompiler already eats.
|
|
3
|
+
|
|
4
|
+
A BizziBee-style prompt is not prose: it is a program in a cognition-DSL with three
|
|
5
|
+
layers, each of which a ChainCompiler tool already compiles —
|
|
6
|
+
|
|
7
|
+
[VarDefs]{ [🌸💧]=NectarWF, 📃=drilldown, … } → a glyphsteer LEGEND (glyph↔meaning)
|
|
8
|
+
🌸💧 → 🍯 → 🍹 (dense-rune chains) → HoneyC compiles / rulecatcher gates
|
|
9
|
+
⚙️0–5 (if / while / for / 🔁 / terminal) → a workflow (the SI executes; here: extracted)
|
|
10
|
+
[ROLE]…[/ROLE] (the persona wrapper) → a SKILL.md (the one type)
|
|
11
|
+
|
|
12
|
+
So this module parses the human-authored glyph-persona into: a `legend.json`, a list of
|
|
13
|
+
workflow steps, and a `<cogid>/SKILL.md` — putting the persona INSIDE the closed algebra.
|
|
14
|
+
Full execution of the ⚙️ control flow is the SI's job (ASPIRATIONAL: `persona run`).
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from .skillpack import slugify, write_skill
|
|
23
|
+
|
|
24
|
+
# operator glyphs that are NOT vocabulary entries (arrows / connectors)
|
|
25
|
+
_OPERATORS = set("→⇒⇔↔⟶⇄⟷=+/")
|
|
26
|
+
_NONASCII = re.compile(r"[^\x00-\x7f]+")
|
|
27
|
+
_IDENT = re.compile(r"[A-Za-z][A-Za-z0-9]*")
|
|
28
|
+
_HEADER = re.compile(r"^\[([A-Za-z][A-Za-z0-9]*)\]:\s*(.*)$", re.M)
|
|
29
|
+
_VARDEFS = re.compile(r"\[VarDefs\]:\s*\{(.*?)\}\s*(?:\n|$)", re.S)
|
|
30
|
+
_STEP = re.compile(r"⚙️([0-9][0-9.]*)\)\s*`?(.*)")
|
|
31
|
+
_CTRL = re.compile(r"\b(if|while|for|end)\b|🔁", re.I)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Persona:
|
|
36
|
+
name: str
|
|
37
|
+
description: str
|
|
38
|
+
fields: dict[str, str] = field(default_factory=dict)
|
|
39
|
+
legend: object = None # a glyphsteer.Vocabulary
|
|
40
|
+
steps: list[dict] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---- block parsing ----------------------------------------------------------
|
|
44
|
+
def parse_header(text: str) -> dict[str, str]:
|
|
45
|
+
"""The `[TAG]: value` header fields (ROLE, CogID, LAW, Mission, …)."""
|
|
46
|
+
return {k: v.strip() for k, v in _HEADER.findall(text)}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def vardefs_text(text: str) -> str:
|
|
50
|
+
m = _VARDEFS.search(text)
|
|
51
|
+
return m.group(1) if m else ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ---- VarDefs → legend -------------------------------------------------------
|
|
55
|
+
def _clean_glyph(run: str) -> str:
|
|
56
|
+
return "".join(ch for ch in run if ch not in _OPERATORS).strip()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def extract_legend(text: str):
|
|
60
|
+
"""Parse the VarDefs glyph↔name pairs into a glyphsteer Vocabulary (best-effort)."""
|
|
61
|
+
from glyphsteer import Axis, Vocabulary, author
|
|
62
|
+
raw = vardefs_text(text) or text
|
|
63
|
+
specs, gseen, nseen = [], set(), set()
|
|
64
|
+
for piece in re.split(r"[,\n]", raw):
|
|
65
|
+
glyph_runs = [g for g in (_clean_glyph(r) for r in _NONASCII.findall(piece)) if g]
|
|
66
|
+
names = _IDENT.findall(piece)
|
|
67
|
+
if not glyph_runs or not names:
|
|
68
|
+
continue
|
|
69
|
+
glyph, name = glyph_runs[0], names[0]
|
|
70
|
+
if glyph in gseen or name in nseen:
|
|
71
|
+
continue
|
|
72
|
+
try:
|
|
73
|
+
Vocabulary([Axis(name, glyph)]) # validates clean glyph + derivable tag
|
|
74
|
+
except ValueError:
|
|
75
|
+
continue
|
|
76
|
+
gseen.add(glyph); nseen.add(name)
|
|
77
|
+
specs.append({"name": name, "glyph": glyph, "description": piece.strip()})
|
|
78
|
+
return author(specs)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---- ⚙️ steps → workflow ----------------------------------------------------
|
|
82
|
+
def extract_workflow(text: str) -> list[dict]:
|
|
83
|
+
"""The numbered ⚙️N) steps, in order, with detected control-flow keywords."""
|
|
84
|
+
steps = []
|
|
85
|
+
for num, body in _STEP.findall(text):
|
|
86
|
+
body = body.strip().rstrip("`").strip()
|
|
87
|
+
ctrl = sorted({m.group(0).lower() for m in _CTRL.finditer(body)})
|
|
88
|
+
steps.append({"step": num, "ctrl": ctrl, "body": body[:140]})
|
|
89
|
+
return steps
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ---- compile ----------------------------------------------------------------
|
|
93
|
+
def parse_persona(text: str) -> Persona:
|
|
94
|
+
h = parse_header(text)
|
|
95
|
+
name = h.get("CogID") or h.get("ROLE") or "persona"
|
|
96
|
+
name = re.sub(r"[*]+$", "", name).strip()
|
|
97
|
+
return Persona(
|
|
98
|
+
name=name,
|
|
99
|
+
description=h.get("Mission", f"{name} persona"),
|
|
100
|
+
fields=h,
|
|
101
|
+
legend=extract_legend(text),
|
|
102
|
+
steps=extract_workflow(text),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _render_body(p: Persona) -> str:
|
|
107
|
+
from glyphsteer import render_legend
|
|
108
|
+
h = p.fields
|
|
109
|
+
out = [f"# {h.get('ROLE', p.name)} — compiled persona", ""]
|
|
110
|
+
for key in ("LAW", "Mission", "FnMx", "ActsLike", "OutputWrapper"):
|
|
111
|
+
if h.get(key):
|
|
112
|
+
out.append(f"- **{key}**: {h[key]}")
|
|
113
|
+
out += ["", "## Legend (the glyph vocabulary)", "```", render_legend(p.legend), "```", ""]
|
|
114
|
+
out += ["## Workflow (⚙️ steps)", ""]
|
|
115
|
+
for s in p.steps:
|
|
116
|
+
ctrl = f" _[{', '.join(s['ctrl'])}]_" if s["ctrl"] else ""
|
|
117
|
+
out.append(f"{s['step']}. {s['body']}{ctrl}")
|
|
118
|
+
out += ["", "> Terminal output is reached only after a multi-round sequence of the "
|
|
119
|
+
"looping steps (the ⚙️ workflow is executable via the SI — ASPIRATIONAL)."]
|
|
120
|
+
return "\n".join(out)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def lint_pipeline(p: Persona) -> dict | None:
|
|
124
|
+
"""If the legend + ChainCompiler grammar gate are available, lint a glyph chain
|
|
125
|
+
drawn from the legend (rulecatcher syntax gate). Returns a verdict or None."""
|
|
126
|
+
try:
|
|
127
|
+
from glyphsteer import GlyphGrammar
|
|
128
|
+
except Exception:
|
|
129
|
+
return None
|
|
130
|
+
glyphs = p.legend.glyphs
|
|
131
|
+
if len(glyphs) < 2:
|
|
132
|
+
return None
|
|
133
|
+
code = p.legend.code(glyphs[:3])
|
|
134
|
+
try:
|
|
135
|
+
with GlyphGrammar(p.legend, scope=f"persona_{slugify(p.name)}") as gg:
|
|
136
|
+
lint = gg.lint(code)
|
|
137
|
+
return {"chain": code, "verdict": lint.verdict}
|
|
138
|
+
except Exception:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def compile_persona(text: str, out_dir: str | Path) -> dict:
|
|
143
|
+
"""Compile a glyph-persona-program → `<cogid>/SKILL.md` + `legend.json`."""
|
|
144
|
+
from glyphsteer import save_legend
|
|
145
|
+
p = parse_persona(text)
|
|
146
|
+
skill_path = write_skill(p.name, p.description, _render_body(p), out_dir=out_dir,
|
|
147
|
+
extra={"glyphs": p.legend.code(p.legend.glyphs[:1]) or ""})
|
|
148
|
+
legend_path = skill_path.parent / "legend.json"
|
|
149
|
+
save_legend(p.legend, legend_path)
|
|
150
|
+
return {
|
|
151
|
+
"name": p.name,
|
|
152
|
+
"skill": str(skill_path),
|
|
153
|
+
"legend": str(legend_path),
|
|
154
|
+
"axes": len(p.legend.axes),
|
|
155
|
+
"steps": len(p.steps),
|
|
156
|
+
"gate": lint_pipeline(p),
|
|
157
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Build the re-injectable PRIME block — the output that closes the loop.
|
|
2
|
+
|
|
3
|
+
A prime is a context block that (a) tells the LLM the ratified grammar of the
|
|
4
|
+
notation so it generates in-shape, and (b) shows a compiled example so it knows
|
|
5
|
+
what the notation MEANS. Re-injecting the prime self-primes the model; every
|
|
6
|
+
chain it then emits is gated by the same grammar (bridge.gate).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .bridge import compile_chain, grammar_lines
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_prime(scope: str, adopted_rows: list, example_chain: str) -> str:
|
|
14
|
+
compiled = compile_chain(example_chain)
|
|
15
|
+
g = grammar_lines(adopted_rows)
|
|
16
|
+
|
|
17
|
+
sections = [
|
|
18
|
+
f'# PRIME — "{scope}" cognition-chain notation',
|
|
19
|
+
"",
|
|
20
|
+
"You think in this notation. Write cognition chains in the shape below; "
|
|
21
|
+
"every chain you emit is gated against the grammar (fix violations before continuing).",
|
|
22
|
+
"",
|
|
23
|
+
f"## Ratified grammar ({len(g)} rules, learned from examples)",
|
|
24
|
+
*(g or ["(none ratified yet)"]),
|
|
25
|
+
"",
|
|
26
|
+
"## A compiled chain (what one MEANS)",
|
|
27
|
+
f"chain: {example_chain.strip()}",
|
|
28
|
+
f"readable: {compiled['readable']}",
|
|
29
|
+
"",
|
|
30
|
+
"triples:",
|
|
31
|
+
*(f" {line}" for line in compiled["triples"].splitlines()),
|
|
32
|
+
]
|
|
33
|
+
if compiled["prose"].strip():
|
|
34
|
+
sections += ["", "prose:", f" {compiled['prose']}"]
|
|
35
|
+
sections += [
|
|
36
|
+
"",
|
|
37
|
+
"## Directive",
|
|
38
|
+
"Emit chains of this form. End a reasoning chain with a bounded |Predict| "
|
|
39
|
+
"(the kill-criterion). Stay inside the grammar; the gate will reject drift.",
|
|
40
|
+
]
|
|
41
|
+
return "\n".join(sections)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""chaincompiler.skillpack — the skill-packager, now living in the BASE.
|
|
2
|
+
|
|
3
|
+
MOVED DOWN to `prompt_engineering.skillpack` (REBUILD-SPEC.md §7 — single source of truth, D1). This
|
|
4
|
+
module now just re-exports it, so `from chaincompiler.skillpack import write_skill` and
|
|
5
|
+
`from chaincompiler import write_skill, slugify, skill_markdown` stay byte-stable for existing consumers
|
|
6
|
+
(archetype, the *CC tests).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from prompt_engineering.skillpack import slugify, skill_markdown, write_skill
|
|
11
|
+
|
|
12
|
+
__all__ = ["slugify", "skill_markdown", "write_skill"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chaincompiler
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: The printing press for agent cognition chains: learn → gate → compile → prime
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: honeyc==0.3.0
|
|
7
|
+
Requires-Dist: rulecatcher==0.3.0
|
|
8
|
+
Requires-Dist: agent-skilltree>=0.4.0
|
|
9
|
+
Requires-Dist: agent-prompt-engineering>=0.2.0
|
|
10
|
+
Provides-Extra: persona
|
|
11
|
+
Requires-Dist: glyphsteer==0.3.0; extra == "persona"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/chaincompiler/__init__.py
|
|
3
|
+
src/chaincompiler/bridge.py
|
|
4
|
+
src/chaincompiler/forge.py
|
|
5
|
+
src/chaincompiler/persona.py
|
|
6
|
+
src/chaincompiler/prime.py
|
|
7
|
+
src/chaincompiler/skillpack.py
|
|
8
|
+
src/chaincompiler.egg-info/PKG-INFO
|
|
9
|
+
src/chaincompiler.egg-info/SOURCES.txt
|
|
10
|
+
src/chaincompiler.egg-info/dependency_links.txt
|
|
11
|
+
src/chaincompiler.egg-info/requires.txt
|
|
12
|
+
src/chaincompiler.egg-info/top_level.txt
|
|
13
|
+
tests/test_chainpress.py
|
|
14
|
+
tests/test_persona.py
|
|
15
|
+
tests/test_strict_gate.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
chaincompiler
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rulecatcher.db import connect, list_rules
|
|
7
|
+
|
|
8
|
+
from chaincompiler.bridge import compile_chain, gate, learn
|
|
9
|
+
from chaincompiler.prime import build_prime
|
|
10
|
+
|
|
11
|
+
SEED = [
|
|
12
|
+
"[Observe] ⇒ [Frame] ⇒ [Integrate] ⇒ [Authenticate] ⇒ |Predict|",
|
|
13
|
+
"[Observe] ⇒ [Frame] ⇒ [Cohere] ⇒ [Authenticate] ⇒ |Predict|",
|
|
14
|
+
"[Observe] ⇒ [Frame] ⇒ [Integrate] ⇒ [Lever] ⇒ |Predict|",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _db(tmp):
|
|
19
|
+
return str(Path(tmp) / "t.db")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_learn_ratifies_shape_grammar():
|
|
23
|
+
with tempfile.TemporaryDirectory() as tmp, connect(_db(tmp)) as cx:
|
|
24
|
+
adopted = learn(cx, SEED, scope="cor")
|
|
25
|
+
assert adopted
|
|
26
|
+
# shape-only: every adopted rule is a kind rule, none pin vocabulary
|
|
27
|
+
assert all(r["rule_type"] == "next_kind" for r in adopted)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_gate_passes_new_vocabulary_in_grammar():
|
|
31
|
+
# a chain with step names never seen in the seed must still pass — it's a
|
|
32
|
+
# notation (shape), not a fixed vocabulary.
|
|
33
|
+
with tempfile.TemporaryDirectory() as tmp, connect(_db(tmp)) as cx:
|
|
34
|
+
learn(cx, SEED, scope="cor")
|
|
35
|
+
violations, _ = gate(cx, "[Scan] ⇒ [Map] ⇒ [Decide] ⇒ |Predict|", scope="cor")
|
|
36
|
+
assert violations == []
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_gate_catches_missing_connector():
|
|
40
|
+
with tempfile.TemporaryDirectory() as tmp, connect(_db(tmp)) as cx:
|
|
41
|
+
learn(cx, SEED, scope="cor")
|
|
42
|
+
violations, fixes = gate(cx, "[Observe] [Frame] ⇒ |Predict|", scope="cor")
|
|
43
|
+
assert violations
|
|
44
|
+
assert any("⇒" in f.candidate_tokens for f in fixes)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_compile_chain_yields_produces_and_bounded():
|
|
48
|
+
c = compile_chain(SEED[0])
|
|
49
|
+
assert "Observe produces Frame" in c["triples"]
|
|
50
|
+
assert "Predict bounded true" in c["triples"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_prime_contains_grammar_and_compiled_meaning():
|
|
54
|
+
with tempfile.TemporaryDirectory() as tmp, connect(_db(tmp)) as cx:
|
|
55
|
+
learn(cx, SEED, scope="cor")
|
|
56
|
+
adopted = list_rules(cx, "adopted", scope="cor")
|
|
57
|
+
block = build_prime("cor", adopted, SEED[0])
|
|
58
|
+
assert "Ratified grammar" in block
|
|
59
|
+
assert "produces" in block
|
|
60
|
+
assert "|Predict|" in block
|
|
61
|
+
assert "Directive" in block
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_loop_grammar_is_stable_under_new_vocabulary():
|
|
65
|
+
# closing the loop: fold a gated-clean new chain back in and re-learn;
|
|
66
|
+
# the shape grammar must NOT grow just because new step names appeared.
|
|
67
|
+
with tempfile.TemporaryDirectory() as tmp, connect(_db(tmp)) as cx:
|
|
68
|
+
first = learn(cx, SEED, scope="cor")
|
|
69
|
+
again = learn(cx, ["[Scan] ⇒ [Map] ⇒ [Decide] ⇒ |Predict|"], scope="cor", label="round2")
|
|
70
|
+
assert len(again) == len(first)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""The persona compiler: a glyph-persona-program → legend + workflow + SKILL.md."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
pytest.importorskip("glyphsteer")
|
|
7
|
+
|
|
8
|
+
from chaincompiler.persona import (compile_persona, extract_legend,
|
|
9
|
+
extract_workflow, parse_header, parse_persona)
|
|
10
|
+
|
|
11
|
+
EXAMPLE = Path(__file__).resolve().parents[1] / "examples" / "bizzibee.txt"
|
|
12
|
+
TEXT = EXAMPLE.read_text(encoding="utf-8")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_header_parsed():
|
|
16
|
+
h = parse_header(TEXT)
|
|
17
|
+
assert h["CogID"] == "BB"
|
|
18
|
+
assert h["Mission"].startswith("Produce RoyalJelly")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_legend_extracts_the_dense_runes():
|
|
22
|
+
V = extract_legend(TEXT)
|
|
23
|
+
names = {a.name for a in V.axes}
|
|
24
|
+
assert {"NectarWF", "RoyalJelly", "drilldown"} <= names
|
|
25
|
+
# the ZWJ rune 🌸💧 is captured as ONE glyph
|
|
26
|
+
nectar = V.by_name("NectarWF").glyph
|
|
27
|
+
assert "" in nectar and nectar.startswith("\U0001F338")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_workflow_steps_and_control_flow():
|
|
31
|
+
steps = extract_workflow(TEXT)
|
|
32
|
+
nums = [s["step"] for s in steps]
|
|
33
|
+
assert "0" in nums and "5" in nums # the bookend steps
|
|
34
|
+
allctrl = {c for s in steps for c in s["ctrl"]}
|
|
35
|
+
assert {"if", "while", "for"} <= allctrl # control flow detected
|
|
36
|
+
assert any("🔁" in s["ctrl"] for s in steps) # the loop-back
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_compile_writes_skill_and_legend(tmp_path: Path):
|
|
40
|
+
res = compile_persona(TEXT, tmp_path)
|
|
41
|
+
assert res["name"] == "BB"
|
|
42
|
+
assert res["axes"] >= 4 and res["steps"] >= 6
|
|
43
|
+
skill = Path(res["skill"])
|
|
44
|
+
legend = Path(res["legend"])
|
|
45
|
+
assert skill.exists() and skill.name == "SKILL.md"
|
|
46
|
+
assert legend.exists()
|
|
47
|
+
body = skill.read_text(encoding="utf-8")
|
|
48
|
+
assert "compiled persona" in body and "Legend" in body and "Workflow" in body
|
|
49
|
+
# the rulecatcher gate ran and the legend chain is well-formed
|
|
50
|
+
assert res["gate"] is None or res["gate"]["verdict"] in {"ok", "orthogonal", "syntax_break"}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_roundtrip_legend_is_loadable(tmp_path: Path):
|
|
54
|
+
from glyphsteer import load_legend
|
|
55
|
+
res = compile_persona(TEXT, tmp_path)
|
|
56
|
+
V = load_legend(res["legend"])
|
|
57
|
+
assert V.by_name("RoyalJelly") is not None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Metaformal self-test (frozen): the STRICT gate rejects foreign tokens at branch points.
|
|
2
|
+
|
|
3
|
+
A metaformal self-test caught the plain gate ADMITTING a foreign token `❌NOPE❌` (it's positional /
|
|
4
|
+
rule-triggered, and the position after `⇒` is a genuine branch point no confidence rule governs). The
|
|
5
|
+
fix: an opt-in closed-class vocabulary check (`gate(..., strict=True)`). This OBSERVES the fixed gate's
|
|
6
|
+
real verdicts — the substrate is the oracle. See ~/.claude/skills/metaformal-self-test.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from rulecatcher.db import connect
|
|
14
|
+
from chaincompiler.bridge import learn, gate
|
|
15
|
+
|
|
16
|
+
_EXAMPLES = ["[ac:a] ⇒ [cor:b] ⇒ |C|", "[ac:a] ⇒ [ac:b] ⇒ [cor:c] ⇒ |D|"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _gate(chain: str, *, strict: bool):
|
|
20
|
+
db = str(Path(tempfile.mkdtemp()) / "g.db")
|
|
21
|
+
with connect(db) as cx:
|
|
22
|
+
learn(cx, _EXAMPLES, scope="cog")
|
|
23
|
+
viol, _ = gate(cx, chain, scope="cog", strict=strict)
|
|
24
|
+
return viol
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_strict_gate_rejects_foreign_token_at_a_branch_point():
|
|
28
|
+
# OBSERVE: the foreign token is admitted plain (the gap), rejected strict (the fix)
|
|
29
|
+
assert len(_gate("[ac:x] ⇒ ❌NOPE❌ ⇒ |Z|", strict=False)) == 0 # the documented gap
|
|
30
|
+
assert len(_gate("[ac:x] ⇒ ❌NOPE❌ ⇒ |Z|", strict=True)) >= 1 # the fix: REJECT
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_strict_gate_still_admits_a_valid_chain():
|
|
34
|
+
# the fix must not false-positive a well-formed chain
|
|
35
|
+
assert len(_gate("[ac:x] ⇒ [cor:y] ⇒ |Z|", strict=True)) == 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_strict_gate_still_catches_structural_errors():
|
|
39
|
+
# missing arrows are caught by the positional rules, strict or not
|
|
40
|
+
assert len(_gate("[ac:x] [cor:y] |Z|", strict=True)) >= 1
|