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.
- skill2framework/__init__.py +19 -0
- skill2framework/chapter.py +73 -0
- skill2framework/fold.py +29 -0
- skill2framework/journey.py +65 -0
- skill2framework/plugin.py +62 -0
- skill2framework-0.3.1.dist-info/METADATA +51 -0
- skill2framework-0.3.1.dist-info/RECORD +10 -0
- skill2framework-0.3.1.dist-info/WHEEL +5 -0
- skill2framework-0.3.1.dist-info/licenses/LICENSE +21 -0
- skill2framework-0.3.1.dist-info/top_level.txt +1 -0
|
@@ -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"]}
|
skill2framework/fold.py
ADDED
|
@@ -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,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
|