skill2framework 0.3.1__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,19 @@
1
+ """skill2framework — the chapter rung of the ladder (the skill2framework chain's deterministic glue).
2
+
3
+ A FRAMEWORK is a chapter: Blog 1 (narrative) + Blog 2 (deep dive) + a plugin +
4
+ the {aios}-volume index skill, folded into the author's SkillTome. The LLM
5
+ stages are the six `skills/skill2framework/*` prompt-skills; this package is
6
+ the deterministic machinery they run:
7
+
8
+ journey.JourneyCore + render_blog1 — the Blog-1 model + renderer (fill, never hand-write)
9
+ chapter.assemble_chapter — blogs → self-contained chapter dir (links/CTAs/manifest)
10
+ plugin.package_plugin — framework skill + chapter → installable plugin dir
11
+ fold.fold_into_tome — the terminal bind (delegates to skilltree.tome.fold)
12
+ """
13
+ from .chapter import assemble_chapter
14
+ from .fold import fold_into_tome
15
+ from .journey import JourneyCore, render_blog1
16
+ from .plugin import package_plugin
17
+
18
+ __all__ = ["JourneyCore", "render_blog1", "assemble_chapter", "package_plugin",
19
+ "fold_into_tome"]
@@ -0,0 +1,73 @@
1
+ """chapter.py — assemble a framework CHAPTER from two rendered blog posts.
2
+
3
+ A chapter = Blog 1 (narrative) + Blog 2 (deep dive), copied VERBATIM into one
4
+ self-contained dir, wired with relative cross-links + CTAs, plus a `chapter.md`
5
+ manifest (what the framework skill and the tome point at). The blogs' prose is
6
+ never rewritten — only the link/CTA block and the manifest are added.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+
13
+ # "## Chapter links", not "## Links": render_blog1 already emits a "## Links" section
14
+ # when the core carries URLs — the first dogfood produced a doubled heading. Distinct
15
+ # heading = no collision, and the chapter block stays a pure addition.
16
+ def _links_block(entries: list[str], cta: str) -> str:
17
+ return "\n\n## Chapter links\n\n" + "\n".join(f"- {e}" for e in entries) + f"\n\n> **{cta}**\n"
18
+
19
+
20
+ def assemble_chapter(blog1_path: str | Path, blog2_path: str | Path, *,
21
+ chapter_dir: str | Path, framework_name: str,
22
+ plugin_url: str = "", skill_links: list[str] | None = None,
23
+ abstract: str = "") -> dict:
24
+ """Copy blog1/blog2 into `chapter_dir`, append the cross-link/CTA block to
25
+ each, and write the `chapter.md` manifest. Returns a report of what was
26
+ written. Idempotent: re-running overwrites the chapter dir's three files.
27
+ `skill_links` entries are "Name|url" pairs.
28
+ """
29
+ b1, b2 = Path(blog1_path), Path(blog2_path)
30
+ if not b1.is_file():
31
+ return {"ok": False, "error": f"blog1 not found: {b1}"}
32
+ if not b2.is_file():
33
+ return {"ok": False, "error": f"blog2 not found: {b2}"}
34
+ out = Path(chapter_dir)
35
+ out.mkdir(parents=True, exist_ok=True)
36
+ skills = skill_links or []
37
+
38
+ def skill_entries() -> list[str]:
39
+ entries = []
40
+ for pair in skills:
41
+ name, _, url = pair.partition("|")
42
+ entries.append(f"Skill — {name.strip()}: {url.strip() or '(link pending)'}")
43
+ return entries
44
+
45
+ blog1 = b1.read_text(encoding="utf-8").rstrip()
46
+ blog1 += _links_block(
47
+ ["Deep dive — how it works: ./blog2.md"]
48
+ + ([f"Get the plugin: {plugin_url}"] if plugin_url else [])
49
+ + skill_entries(),
50
+ cta=f"Install the plugin and run it yourself: {plugin_url or './blog2.md'}")
51
+ (out / "blog1.md").write_text(blog1 + "\n", encoding="utf-8")
52
+
53
+ blog2 = b2.read_text(encoding="utf-8").rstrip()
54
+ blog2 += _links_block(
55
+ ["The story — why this exists: ./blog1.md"]
56
+ + ([f"Get the plugin: {plugin_url}"] if plugin_url else []),
57
+ cta=f"Read the story, then install: {plugin_url or './blog1.md'}")
58
+ (out / "blog2.md").write_text(blog2 + "\n", encoding="utf-8")
59
+
60
+ manifest = [f"# {framework_name} — chapter", ""]
61
+ if abstract:
62
+ manifest += [abstract, ""]
63
+ manifest += ["## Contents", "",
64
+ "1. The narrative: ./blog1.md",
65
+ "2. The deep dive: ./blog2.md", ""]
66
+ if plugin_url:
67
+ manifest += [f"Plugin: {plugin_url}", ""]
68
+ if skills:
69
+ manifest += ["## Skills", ""] + [f"- {e}" for e in skill_entries()] + [""]
70
+ (out / "chapter.md").write_text("\n".join(manifest), encoding="utf-8")
71
+
72
+ return {"ok": True, "chapter_dir": str(out),
73
+ "files": ["blog1.md", "blog2.md", "chapter.md"]}
@@ -0,0 +1,29 @@
1
+ """fold.py — the terminal stage: fold a framework into the author's SkillTome.
2
+
3
+ Delegates to skilltree's Move-1 bind op (`skilltree.tome.fold`, agent-skilltree
4
+ >= 0.3.0): the tome is a SkillTree whose volume nodes carry `frameworks` rows in
5
+ the manifest, rendered as the generated `## Frameworks` table. Folding is a CODE
6
+ op — idempotent, one row per framework, the manifest is the source of truth —
7
+ not an LLM hand-editing a table. ("nomicon" is the legacy alias for the
8
+ SkillVolume/SkillTome this folds into.)
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+ from skilltree.tome import fold as _tome_fold
15
+
16
+
17
+ def fold_into_tome(framework_skill_dir: str | Path, *, volume: str,
18
+ tome_root: str | Path, what_for: str | None = None,
19
+ name: str | None = None, relative: bool = True) -> dict:
20
+ """Register a framework's skill dir as a row on a tome volume node.
21
+
22
+ framework_skill_dir the framework skill dir (holds SKILL.md — the {aios}-volume)
23
+ volume the volume node in the tome tree (name or coord)
24
+ tome_root the materialized tome SkillTree root (holds skilltree.json)
25
+ relative default True: rows are stored holder-relative so a
26
+ repo-committed tome validates/projects from any checkout
27
+ """
28
+ return _tome_fold(framework_skill_dir, into=volume, tree_root=tome_root,
29
+ what_for=what_for, name=name, relative=relative)
@@ -0,0 +1,65 @@
1
+ """journey.py — the JourneyCore model + the deterministic Blog-1 renderer.
2
+
3
+ The BLOG ORGAN law: the narrative blog is never hand-written — an agent FILLS
4
+ this model from the AIOS's real journey (its CLAUDE.md / README / dev log) and
5
+ runs `render_blog1`, so the output shape is deterministic and every datum
6
+ renders exactly once. This is the DIY (dependency-free) renderer; the fields
7
+ are the Hero-arc set the skill2framework pipeline fills.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+
13
+
14
+ @dataclass
15
+ class JourneyCore:
16
+ journey_name: str # the AIOS/framework this is the journey OF
17
+ domain: str # free-form domain label ("publishing", "health", …)
18
+ hook: str # AUTHORED opening sentence (never string-derived)
19
+ status_quo: str # "I was …" — the painful state BEFORE
20
+ obstacle: str # "I identified … when …" — the blocker + the moment seen
21
+ overcome: str # "I finally … and tried …" — the shift + what was built
22
+ accomplishment: str # "… and now …" — the result as feeling + proof
23
+ the_boon: str # the ONE transferable reframe (not the artifact)
24
+ demo_description: str # what a demo would show
25
+ why_this_matters: str # the meta-level
26
+ universal_application: str # how a reader applies it to THEIR domain
27
+ github_url: str = "" # the code
28
+ plugin_url: str = "" # the installable face
29
+ deep_dive_url: str = "" # Blog 2, when known (the chapter step wires it later)
30
+ skill_urls: list[str] = field(default_factory=list) # "Name|url" pairs
31
+ hashtags: list[str] = field(default_factory=list)
32
+
33
+
34
+ def render_blog1(core: JourneyCore) -> str:
35
+ """Render Blog 1 (the narrative chapter-opener) from a filled JourneyCore.
36
+
37
+ Deterministic template: the authored hook opens, each field renders once, the
38
+ links/CTA block closes. Optional fields render only when present.
39
+ """
40
+ lines: list[str] = [f"# {core.journey_name} — the story", ""]
41
+ lines += [f"*{core.hook}*", ""]
42
+ lines += ["## Where I was", "", core.status_quo, ""]
43
+ lines += ["## The wall", "", core.obstacle, ""]
44
+ lines += ["## The turn", "", core.overcome, ""]
45
+ lines += ["## Where I am now", "", core.accomplishment, ""]
46
+ lines += ["## The boon — what transfers", "", core.the_boon, ""]
47
+ lines += ["## See it", "", core.demo_description, ""]
48
+ lines += ["## Why this matters", "", core.why_this_matters, ""]
49
+ lines += ["## Take action", "", core.universal_application, ""]
50
+
51
+ links: list[str] = []
52
+ if core.deep_dive_url:
53
+ links.append(f"- Deep dive — how it works: {core.deep_dive_url}")
54
+ if core.plugin_url:
55
+ links.append(f"- Get the plugin: {core.plugin_url}")
56
+ if core.github_url and core.github_url != core.plugin_url:
57
+ links.append(f"- The code: {core.github_url}")
58
+ for pair in core.skill_urls:
59
+ name, _, url = pair.partition("|")
60
+ links.append(f"- Skill — {name.strip()}: {url.strip() or '(link pending)'}")
61
+ if links:
62
+ lines += ["## Links", ""] + links + [""]
63
+ if core.hashtags:
64
+ lines += [" ".join(f"#{h.lstrip('#')}" for h in core.hashtags), ""]
65
+ return "\n".join(lines)
@@ -0,0 +1,62 @@
1
+ """plugin.py — package a framework as a Claude Code plugin (its operational face).
2
+
3
+ A framework package is ALWAYS a plugin. This assembles a valid plugin dir from
4
+ the generated framework skill (the {aios}-volume index/router) + its chapter,
5
+ and repaths the skill's chapter links to the bundled copy so the plugin is
6
+ self-contained. Only the chapter link paths are rewritten — never the prose.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ import shutil
13
+ from pathlib import Path
14
+
15
+
16
+ def package_plugin(framework_skill_path: str | Path, chapter_dir: str | Path, *,
17
+ out_dir: str | Path, name: str, author: str = "",
18
+ description: str = "", version: str = "0.1.0") -> dict:
19
+ """Build `<out_dir>/` as a plugin: plugin.json + skills/<name>/SKILL.md +
20
+ the chapter bundled under resources/chapter/, chapter links repathed to the
21
+ bundle. Returns a report with the repath count and a resolution check."""
22
+ skill_src = Path(framework_skill_path)
23
+ chap = Path(chapter_dir)
24
+ if not skill_src.is_file():
25
+ return {"ok": False, "error": f"framework skill not found: {skill_src}"}
26
+ if not (chap / "chapter.md").is_file():
27
+ return {"ok": False, "error": f"no chapter.md in chapter dir: {chap}"}
28
+
29
+ out = Path(out_dir)
30
+ skill_dir = out / "skills" / name
31
+ res_dir = skill_dir / "resources" / "chapter"
32
+ res_dir.mkdir(parents=True, exist_ok=True)
33
+
34
+ for f in ("blog1.md", "blog2.md", "chapter.md"):
35
+ if (chap / f).is_file():
36
+ shutil.copy2(chap / f, res_dir / f)
37
+
38
+ # repath: any PATH that ends in a chapter file — a token containing a "/" before
39
+ # the filename, or a bare-filename markdown link TARGET — becomes the bundled
40
+ # relative reference. Bare filenames in display text (e.g. the "[blog1.md]" label
41
+ # of a link) are NOT paths and stay untouched (the first dogfood's repath mangled
42
+ # them). Nothing else in the body changes.
43
+ body = skill_src.read_text(encoding="utf-8")
44
+ body, n1 = re.subn(r"[^\s()`\[\]]*/(blog1\.md|blog2\.md|chapter\.md)\b",
45
+ r"./resources/chapter/\1", body)
46
+ body, n2 = re.subn(r"\((blog1\.md|blog2\.md|chapter\.md)\)",
47
+ r"(./resources/chapter/\1)", body)
48
+ n = n1 + n2
49
+ (skill_dir / "SKILL.md").write_text(body, encoding="utf-8")
50
+
51
+ manifest = {"name": name, "version": version,
52
+ "description": description or f"The {name} framework plugin.",
53
+ "author": author}
54
+ (out / "plugin.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
55
+
56
+ # resolution check: every bundled reference in the written skill must exist
57
+ dead = [m.group(0) for m in
58
+ re.finditer(r"\./resources/chapter/(?:blog1|blog2|chapter)\.md",
59
+ (skill_dir / "SKILL.md").read_text(encoding="utf-8"))
60
+ if not (skill_dir / m.group(0)).is_file()]
61
+ return {"ok": not dead, "plugin_dir": str(out), "repathed": n,
62
+ "dead_refs": dead}
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: skill2framework
3
+ Version: 0.3.1
4
+ Summary: The chapter rung: blogs → chapter → plugin → fold into the SkillTome (the skill2framework chain's deterministic glue)
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: agent-skilltree>=0.4.0
10
+ Dynamic: license-file
11
+
12
+ # skill2framework — the chapter rung: a finished agent turned into a chapter of a SkillTome
13
+
14
+ `skill2framework` is the chapter rung of [ChainCompiler](https://github.com/sancovp/chaincompiler), a compiler-compiler for
15
+ cognition. It is the deterministic part of turning a finished agent into a chapter of its author's SkillTome. The
16
+ `skill2framework` skills the ChainCompiler plugin ships write two posts about the agent — a narrative post and a deep
17
+ dive — and this package does everything after them:
18
+
19
+ - `assemble_chapter(blog1, blog2, chapter_dir=…, framework_name=…)` makes the two posts one self-contained chapter folder,
20
+ its links, calls to action and manifest included;
21
+ - `package_plugin(framework_skill, chapter_dir, out_dir=…, name=…)` packages the framework's skill and its chapter as an
22
+ installable plugin, repathing the chapter's links into the bundle and checking that each resolves;
23
+ - `fold_into_tome(framework_skill_dir, volume=…, tome_root=…)` folds the framework into the tome, through
24
+ `agent-skilltree`'s `tome.fold`;
25
+ - `JourneyCore` and `render_blog1` are the narrative post's model and its renderer, so the post is filled in and never
26
+ written by hand.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install skill2framework
32
+ ```
33
+
34
+ It brings `agent-skilltree` from PyPI. To work on it, clone the [repository](https://github.com/sancovp/chaincompiler)
35
+ and run `./install.sh`, which installs all fifteen packages editable.
36
+
37
+ ## Use
38
+
39
+ ```python
40
+ from skill2framework import assemble_chapter, fold_into_tome, package_plugin
41
+ ```
42
+
43
+ Each call takes the files the one before it wrote; the `skill2framework` skills in the plugin run them in order.
44
+
45
+ ## Its design
46
+
47
+ The repository's [`DESIGN.md`](https://github.com/sancovp/chaincompiler/blob/main/DESIGN.md) §13.
48
+
49
+ ## License
50
+
51
+ MIT.
@@ -0,0 +1,10 @@
1
+ skill2framework/__init__.py,sha256=PZNkeovCG3hnjgQHC2HKTgLdMtwlz2Zto-F2qI7HDDI,1040
2
+ skill2framework/chapter.py,sha256=DLXvQZ86rFykTlwJjfmbs5CzGYE37IOBKN4v-DwpgPU,3312
3
+ skill2framework/fold.py,sha256=b4zyVdrR5ywz13n0_N3poaeesdHFCssVABpT_jNZ4OY,1481
4
+ skill2framework/journey.py,sha256=Q9WOsewWKeTzqb2NO_2Qug9R_kqQ_M3XZGAQQBCQP6U,3383
5
+ skill2framework/plugin.py,sha256=J2bkauvkEEvoqh6GPagB1jTaIYkudz9glQYFi-JQXW0,3019
6
+ skill2framework-0.3.1.dist-info/licenses/LICENSE,sha256=ffOw8Kmv_QBeCF_DLeFnCZ-t36WSZJ7ePU8nsibXqZ8,1064
7
+ skill2framework-0.3.1.dist-info/METADATA,sha256=cOebxZb7zHCPFTZMFYeFc19eZSD--6sJcPaCSK6HFX8,2149
8
+ skill2framework-0.3.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
9
+ skill2framework-0.3.1.dist-info/top_level.txt,sha256=dGGUD8JzKiOreZcYUpJmEU69HFogxulL--jqPblfv30,16
10
+ skill2framework-0.3.1.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sancovp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ skill2framework