chaincompiler-si 0.3.0__py3-none-any.whl

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,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: chaincompiler-si
3
+ Version: 0.3.0
4
+ Summary: Self-Interpreter for the ChainCompiler chain dialect — an MCP that is also a skill
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: chaincompiler==0.3.0
7
+ Requires-Dist: chainaios==0.3.0
8
+ Requires-Dist: chaincompiler-accc==0.3.0
9
+ Requires-Dist: corcc==0.3.0
10
+ Requires-Dist: sccc==0.3.0
11
+ Requires-Dist: agent-skilltree>=0.4.0
12
+ Requires-Dist: fastmcp
@@ -0,0 +1,12 @@
1
+ si/__init__.py,sha256=LiAsjD1XqFcNXB9Xb6pRZIhMpUorjskBl0fHytJ7Ado,795
2
+ si/interp.py,sha256=FGCTluGe_zEBaXUhwm_DEA2kp6HVT9aHkiJedHPf7UQ,2460
3
+ si/mcp_tree.py,sha256=YQIm7SYEbgmrDEM9t_RCQe32mxqlYJ1mCkQKbJSlBBA,2081
4
+ si/runtime.py,sha256=2ZfRVBCZqGa_ySb77DE8qPTGDjtFcjKhQQBUwZGCEY4,2514
5
+ si/scaffold.py,sha256=TlwSCtB0GovfdpoVy8B5fCTqkN1faWOikT7vs8KWenQ,3077
6
+ si/server.py,sha256=uu5STYqrxdixvpvMs3JbOFl9BW7uXQ6TTNNj_aMXmMI,2925
7
+ si/skill/SKILL.md,sha256=ZVVcQ4T5g5pP84_lnhIG-34zxRbwqSC0ltSC_Fk6zJ0,1115
8
+ chaincompiler_si-0.3.0.dist-info/METADATA,sha256=gAh6iaQT2d3E12MO4BHc36bew92EILd4q8dj00Fvcwg,403
9
+ chaincompiler_si-0.3.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
10
+ chaincompiler_si-0.3.0.dist-info/entry_points.txt,sha256=xVUAifQ0n2exgnBr_RRWfcoM-4vReFS6FqztxRYNy_M,45
11
+ chaincompiler_si-0.3.0.dist-info/top_level.txt,sha256=Gefnx6gufmA_0rBNGHzBXTHNRIj_pTEQKMF9CbSP1mw,3
12
+ chaincompiler_si-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ si-server = si.server:main
@@ -0,0 +1 @@
1
+ si
si/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ """si — the Self-Interpreter for the ChainCompiler chain dialect.
2
+
3
+ A Python dialect: the interpreter defers to native Python and only injects the
4
+ ChainCompiler vocabulary. Ships as an MCP server (si.server) that is ALSO a skill
5
+ (si/skill/SKILL.md), and can turn skill trees / exchange masters into MCPs.
6
+ The execute arm of Compilero Bandito; the self-hosting capstone.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ __version__ = "0.3.0"
11
+
12
+ from .interp import interpret, make_env
13
+ from .mcp_tree import build_tree_server, tree_to_mcp
14
+ from .runtime import reachable, read_skill, walk
15
+ from .scaffold import scaffold_repo, validate_node
16
+
17
+ __all__ = [
18
+ "interpret", "make_env", "walk", "reachable", "read_skill",
19
+ "build_tree_server", "tree_to_mcp", "scaffold_repo", "validate_node", "__version__",
20
+ ]
si/interp.py ADDED
@@ -0,0 +1,58 @@
1
+ """The Self-Interpreter for the chain dialect.
2
+
3
+ The dialect is **a Python dialect** — so the interpreter does NOT re-wrap Python's
4
+ native ops (arithmetic, control flow, data). It runs the program with Python, and
5
+ only *injects the ChainCompiler vocabulary* as builtins: forge/gate/package the
6
+ chains, build/walk skill trees, build exchanges, turn trees into MCPs. A program
7
+ sets `result` (or the namespace is returned) — that's the value.
8
+
9
+ Self-interpreter: programs can themselves forge languages, build trees, and emit
10
+ MCPs — including ones that interpret further. ChainCompiler thereby runs itself.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+
17
+ def make_env(extra: dict[str, Any] | None = None) -> dict[str, Any]:
18
+ """The dialect's vocabulary — ChainCompiler primitives, injected as names."""
19
+ import accc
20
+ import chaincompiler
21
+ import chainaios
22
+ import corcc
23
+ import sccc
24
+ import skilltree
25
+
26
+ from . import runtime
27
+ from .mcp_tree import tree_to_mcp
28
+
29
+ env: dict[str, Any] = {
30
+ # construct (the AIOS top-door now lives in chainaios; write_skill stays in the CC base)
31
+ "construct_language": chainaios.construct_language,
32
+ "write_skill": chaincompiler.write_skill,
33
+ "forge_ac": accc.forge, "package_ac": accc.package, "gate_ac": accc.gate,
34
+ "forge_cor": corcc.forge_persona, "package_cor": corcc.package, "lint_cor": corcc.lint,
35
+ "forge_sc": sccc.forge, "package_sc": sccc.package, "lint_sc": sccc.lint,
36
+ # organize
37
+ "SkillTree": skilltree.SkillTree, "TreeNode": skilltree.TreeNode,
38
+ "materialize": skilltree.materialize, "validate_tree": skilltree.validate,
39
+ "build_exchange": skilltree.build_exchange, "load_exchange": skilltree.load_exchange,
40
+ # execute / search / emit
41
+ "walk": runtime.walk, "reachable": runtime.reachable, "read_skill": runtime.read_skill,
42
+ "tree_to_mcp": tree_to_mcp,
43
+ "result": None,
44
+ }
45
+ if extra:
46
+ env.update(extra)
47
+ return env
48
+
49
+
50
+ def interpret(source: str, *, env: dict[str, Any] | None = None) -> Any:
51
+ """Interpret a chain-dialect program. Returns `result` (or the eval of a single expr)."""
52
+ ns = make_env(env)
53
+ try:
54
+ code = compile(source, "<si>", "eval") # single expression?
55
+ return eval(code, ns)
56
+ except SyntaxError:
57
+ exec(compile(source, "<si>", "exec"), ns) # full program
58
+ return ns.get("result")
si/mcp_tree.py ADDED
@@ -0,0 +1,57 @@
1
+ """Turn a skill tree (or exchange master) into an MCP server.
2
+
3
+ `build_tree_server` makes a FastMCP server that exposes a materialized tree's
4
+ skills as tools (list / read / walk). `tree_to_mcp` writes a tiny launcher so any
5
+ tree or master becomes a runnable MCP — that's "make trees and masters into MCPs".
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ from . import runtime
12
+
13
+
14
+ def build_tree_server(root_dir: str | Path, root_name: str, *, name: str | None = None):
15
+ """A FastMCP server exposing the tree's skills. (Run with .run() for stdio.)"""
16
+ from fastmcp import FastMCP
17
+
18
+ root_dir = str(root_dir)
19
+ server = FastMCP(name or f"skilltree:{root_name}")
20
+
21
+ @server.tool()
22
+ def list_skills() -> list[str]:
23
+ """List every skill reachable from this tree's root."""
24
+ return runtime.reachable(root_dir, root_name)
25
+
26
+ @server.tool()
27
+ def read_skill(skill: str) -> str:
28
+ """Return the body of a skill in this tree by name."""
29
+ body = runtime.read_skill(root_dir, skill)
30
+ return body if body is not None else f"(no skill named {skill!r} in this tree)"
31
+
32
+ @server.tool()
33
+ def walk() -> dict:
34
+ """Return the full tree, traversed by following its breadcrumbs."""
35
+ return runtime.walk(root_dir, root_name)
36
+
37
+ return server
38
+
39
+
40
+ _LAUNCHER = '''#!/usr/bin/env python3
41
+ """Auto-generated MCP launcher for skill tree {name!r} (root {root!r})."""
42
+ from si.mcp_tree import build_tree_server
43
+
44
+ if __name__ == "__main__":
45
+ build_tree_server({root_dir!r}, {root!r}, name={name!r}).run()
46
+ '''
47
+
48
+
49
+ def tree_to_mcp(root_dir: str | Path, root_name: str, out_path: str | Path,
50
+ *, name: str | None = None) -> Path:
51
+ """Write a runnable MCP launcher that serves this tree/master. Returns its path."""
52
+ out = Path(out_path)
53
+ out.parent.mkdir(parents=True, exist_ok=True)
54
+ out.write_text(_LAUNCHER.format(root_dir=str(Path(root_dir).resolve()),
55
+ root=root_name, name=name or f"skilltree:{root_name}"),
56
+ encoding="utf-8")
57
+ return out
si/runtime.py ADDED
@@ -0,0 +1,62 @@
1
+ """The execute arm — walk a SkillTree by following its Read-breadcrumbs.
2
+
3
+ This is what "running" a tree means: start at the root SKILL.md, parse the
4
+ breadcrumb paths, and recurse. No new mechanism — the same breadcrumbs the
5
+ validator checks are the ones the runtime follows. (The breadcrumb verb is the
6
+ Read tool, not `cat`; the path is what we follow.)
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ import re
12
+
13
+ _CRUMB = re.compile(r"`([^`]+/SKILL\.md)`") # the backticked SKILL.md path, verb-agnostic
14
+ _COORD = re.compile(r"^\d+(?:\.\d+)*-") # a coordinate-addressed folder: `0.1-<name>`
15
+
16
+
17
+ def _skill_md(node_dir: Path, name: str) -> Path:
18
+ return Path(node_dir) / ".claude" / "skills" / name / "SKILL.md"
19
+
20
+
21
+ def _strip_front(text: str) -> str:
22
+ return text.split("---", 2)[-1].strip() if text.lstrip().startswith("---") else text.strip()
23
+
24
+
25
+ def _walk_md(md: Path) -> dict:
26
+ node = {"name": md.parent.name, "skill": str(md), "children": []}
27
+ if not md.is_file():
28
+ node["error"] = "missing"
29
+ return node
30
+ node_dir = md.parents[3] # <node>/.claude/skills/<name>/SKILL.md → <node>
31
+ for p in _CRUMB.findall(md.read_text(encoding="utf-8")):
32
+ child = Path(p)
33
+ if not child.is_absolute(): # a portable crumb is relative to the node's folder
34
+ child = node_dir / child # (SkillTree ≥0.4.0); a localized one is absolute
35
+ node["children"].append(_walk_md(child))
36
+ return node
37
+
38
+
39
+ def walk(root_dir: str | Path, root_name: str) -> dict:
40
+ """Traverse the tree from its root by following breadcrumbs. Returns a nested dict."""
41
+ return _walk_md(_skill_md(Path(root_dir), root_name))
42
+
43
+
44
+ def reachable(root_dir: str | Path, root_name: str) -> list[str]:
45
+ """Flat list of every skill name reachable from the root (the execute frontier)."""
46
+ out: list[str] = []
47
+ def rec(n):
48
+ out.append(n["name"])
49
+ for c in n["children"]:
50
+ rec(c)
51
+ rec(walk(root_dir, root_name))
52
+ return out
53
+
54
+
55
+ def read_skill(root_dir: str | Path, name: str) -> str | None:
56
+ """Search the tree for a skill by name; return its body (the search arm). A coordinate-addressed
57
+ tree names each skill's folder `<coord>-<name>`; the name matches with or without the coordinate."""
58
+ for md in Path(root_dir).rglob("SKILL.md"):
59
+ folder = md.parent.name
60
+ if folder == name or _COORD.sub("", folder) == name:
61
+ return _strip_front(md.read_text(encoding="utf-8"))
62
+ return None
si/scaffold.py ADDED
@@ -0,0 +1,71 @@
1
+ """P5.1 — scaffold a federated node repo from a skill tree.
2
+
3
+ When the SI emits an MCP, it can also stamp a ChainCompiler-SHAPED node repo: the
4
+ skill tree (breadcrumbs), a marketplace registry that **federates under a
5
+ parent**, a runnable MCP launcher, and a README. The fractal made real — each
6
+ emitted MCP becomes a repo like ours, its marketplace joining under ours.
7
+
8
+ Full CI mechanics (deploy workflow, site generator) are P5.2; this stamps the
9
+ load-bearing shape: tree + registry + MCP + README, all validatable.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from pathlib import Path
15
+
16
+ from skilltree import SkillTree, Violation, materialize
17
+ from skilltree import validate as validate_tree
18
+
19
+ from .mcp_tree import tree_to_mcp
20
+
21
+ _NODE_README = """# {name} — a ChainCompiler node
22
+
23
+ A self-contained skill tree, served as an MCP, federated under `{parent}`.
24
+
25
+ - **Walk it** — start at `.claude/skills/{root}/SKILL.md` and Read each breadcrumb to descend.
26
+ - **Serve it** — `python serve_mcp.py` (an MCP exposing this tree's skills).
27
+ - **Marketplace** — see `registry.json`; this node registers under its parent.
28
+ """
29
+
30
+
31
+ def scaffold_repo(name: str, tree: SkillTree | str | Path, out_dir: str | Path,
32
+ *, parent: str | None = None) -> Path:
33
+ """Stamp a federated node repo at <out_dir>/<name>. Returns the repo path."""
34
+ if isinstance(tree, (str, Path)):
35
+ tree = SkillTree.load(tree)
36
+ repo = Path(out_dir) / name
37
+ repo.mkdir(parents=True, exist_ok=True)
38
+
39
+ materialize(tree, repo) # the node's skill tree (breadcrumbs)
40
+ root = tree.root.name
41
+
42
+ (repo / "registry.json").write_text(json.dumps({ # marketplace, federated
43
+ "name": name,
44
+ "parent": parent, # null at the root; else a repo ref
45
+ "entries": [{"name": root, "kind": "tree", "manifest": "skilltree.json",
46
+ "trust": "unverified"}],
47
+ }, indent=2), encoding="utf-8")
48
+
49
+ tree_to_mcp(repo, root, repo / "serve_mcp.py", name=f"node:{name}") # the runnable MCP
50
+ (repo / "README.md").write_text(
51
+ _NODE_README.format(name=name, parent=parent or "(root)", root=root), encoding="utf-8")
52
+ return repo
53
+
54
+
55
+ def validate_node(repo: str | Path) -> list[Violation]:
56
+ """Validate a scaffolded node: the tree + the registry + the MCP launcher."""
57
+ repo = Path(repo)
58
+ out = list(validate_tree(repo))
59
+ reg = repo / "registry.json"
60
+ if not reg.is_file():
61
+ out.append(Violation("error", repo.name, "missing registry.json"))
62
+ else:
63
+ try:
64
+ data = json.loads(reg.read_text(encoding="utf-8"))
65
+ if "parent" not in data or "entries" not in data:
66
+ out.append(Violation("error", repo.name, "registry.json missing parent/entries"))
67
+ except json.JSONDecodeError:
68
+ out.append(Violation("error", repo.name, "registry.json is not valid JSON"))
69
+ if not (repo / "serve_mcp.py").is_file():
70
+ out.append(Violation("error", repo.name, "missing serve_mcp.py launcher"))
71
+ return out
si/server.py ADDED
@@ -0,0 +1,77 @@
1
+ """The Self-Interpreter as an MCP server (and it is also a skill — see si/skill/).
2
+
3
+ Exposes the chain dialect to any MCP client:
4
+ - si_interpret(source) run a chain-dialect program
5
+ - si_walk(root_dir, root_name) traverse a materialized tree
6
+ - si_reachable(root_dir, root_name) list reachable skills
7
+ - si_read_skill(root_dir, skill) fetch a skill body by name
8
+ - si_tree_to_mcp(root_dir, name, out) turn a tree/master into its own MCP
9
+ - si_search(root_dir, query) BM25 over a tree, optionally coordinate-scoped
10
+
11
+ Run: python -m si.server (or the `si-server` console script)
12
+
13
+ The tool functions stay plain callables (registered, not rebound) so they're
14
+ usable directly and in tests, not only over the wire.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import re
19
+
20
+ from fastmcp import FastMCP
21
+
22
+ from . import runtime
23
+ from .interp import interpret
24
+ from .mcp_tree import tree_to_mcp
25
+
26
+ mcp = FastMCP("self-interpreter")
27
+
28
+
29
+ def si_interpret(source: str) -> str:
30
+ """Interpret a ChainCompiler chain-dialect program; returns repr(result)."""
31
+ return repr(interpret(source))
32
+
33
+
34
+ def si_walk(root_dir: str, root_name: str) -> dict:
35
+ """Traverse a materialized skill tree by following its breadcrumbs."""
36
+ return runtime.walk(root_dir, root_name)
37
+
38
+
39
+ def si_reachable(root_dir: str, root_name: str) -> list[str]:
40
+ """List every skill reachable from a tree's root."""
41
+ return runtime.reachable(root_dir, root_name)
42
+
43
+
44
+ def si_read_skill(root_dir: str, skill: str) -> str:
45
+ """Search a tree for a skill by name and return its body."""
46
+ body = runtime.read_skill(root_dir, skill)
47
+ return body if body is not None else f"(no skill named {skill!r})"
48
+
49
+
50
+ def si_tree_to_mcp(root_dir: str, root_name: str, out_path: str) -> str:
51
+ """Turn a skill tree (or exchange master) into a runnable MCP launcher."""
52
+ return str(tree_to_mcp(root_dir, root_name, out_path))
53
+
54
+
55
+ def si_search(root_dir: str, query: str, scope_coord: str | None = None, limit: int = 10) -> list[dict]:
56
+ """Search the skill tree (BM25), optionally scoped to a coordinate subtree (e.g. '0.1'). SkillTree's search hands a
57
+ query's words to FTS5 unquoted, so it raises on a query with no word and on an operator word (`NOT`, `AND`); this
58
+ door sends it the query's words alone, lowercased — FTS5's operators are capitals and its index folds case, so the
59
+ words match as before — and answers a query with no word with nothing."""
60
+ words = re.findall(r"\w+", query.lower())
61
+ if not words:
62
+ return []
63
+ from skilltree import search_tree
64
+ return search_tree(root_dir, " ".join(words), scope_coord=scope_coord, limit=limit)
65
+
66
+
67
+ # Register as MCP tools without rebinding the names (keep them plain callables).
68
+ for _fn in (si_interpret, si_walk, si_reachable, si_read_skill, si_tree_to_mcp, si_search):
69
+ mcp.tool()(_fn)
70
+
71
+
72
+ def main() -> None:
73
+ mcp.run()
74
+
75
+
76
+ if __name__ == "__main__":
77
+ main()
si/skill/SKILL.md ADDED
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: self-interpreter
3
+ description: Run ChainCompiler chain-dialect programs and turn skill trees into MCPs. Use to forge languages, build/walk skill trees, or execute a cognition chain.
4
+ ---
5
+
6
+ # Self-Interpreter (SI)
7
+
8
+ The SI runs the **chain dialect** — a Python dialect whose vocabulary is the
9
+ ChainCompiler primitives (`forge_ac`, `forge_cor`, `forge_sc`, `construct_language`,
10
+ `SkillTree`, `TreeNode`, `materialize`, `walk`, `build_exchange`, `tree_to_mcp`, …).
11
+ It defers to native Python for everything else.
12
+
13
+ ## As an MCP
14
+
15
+ Start it: `python -m si.server`. Tools:
16
+
17
+ - `si_interpret(source)` — run a chain-dialect program; returns `repr(result)`.
18
+ - `si_walk(root_dir, root_name)` / `si_reachable(...)` — traverse a skill tree.
19
+ - `si_read_skill(root_dir, skill)` — fetch a skill body by name.
20
+ - `si_tree_to_mcp(root_dir, root_name, out)` — turn a tree/master into its own MCP.
21
+
22
+ ## Example program
23
+
24
+ ```python
25
+ tree = SkillTree(TreeNode("root", "sc", children=[TreeNode("a", "ac"), TreeNode("b", "cor")]))
26
+ materialize(tree, OUT)
27
+ result = reachable(OUT, "root") # the execute arm: walk the tree
28
+ ```