sccc 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.
- sccc-0.3.0/PKG-INFO +11 -0
- sccc-0.3.0/pyproject.toml +16 -0
- sccc-0.3.0/setup.cfg +4 -0
- sccc-0.3.0/src/sccc/__init__.py +15 -0
- sccc-0.3.0/src/sccc/cli.py +86 -0
- sccc-0.3.0/src/sccc/forge.py +106 -0
- sccc-0.3.0/src/sccc/notation.py +19 -0
- sccc-0.3.0/src/sccc.egg-info/PKG-INFO +11 -0
- sccc-0.3.0/src/sccc.egg-info/SOURCES.txt +12 -0
- sccc-0.3.0/src/sccc.egg-info/dependency_links.txt +1 -0
- sccc-0.3.0/src/sccc.egg-info/entry_points.txt +2 -0
- sccc-0.3.0/src/sccc.egg-info/requires.txt +6 -0
- sccc-0.3.0/src/sccc.egg-info/top_level.txt +1 -0
- sccc-0.3.0/tests/test_sccc.py +83 -0
sccc-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sccc
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Skillchain Compiler-Compiler — chains ACs + CoRs + skills into one SKILL.md
|
|
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
|
|
9
|
+
Requires-Dist: rulecatcher==0.3.0
|
|
10
|
+
Requires-Dist: skillchain-compiler==0.3.0
|
|
11
|
+
Requires-Dist: click
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sccc"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Skillchain Compiler-Compiler — chains ACs + CoRs + skills into one SKILL.md"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = ["chaincompiler-accc==0.3.0", "corcc==0.3.0", "chaincompiler==0.3.0", "rulecatcher==0.3.0", "skillchain-compiler==0.3.0", "click"]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
sccc = "sccc.cli:main"
|
|
14
|
+
|
|
15
|
+
[tool.setuptools.packages.find]
|
|
16
|
+
where = ["src"]
|
sccc-0.3.0/setup.cfg
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""SCCC — Skillchain Compiler-Compiler. The highest composite: AC + CoR + skills.
|
|
2
|
+
|
|
3
|
+
Same *CC shape (catch → lint → compile → package as SKILL.md). Built on the
|
|
4
|
+
`skillchain` module. ACCC → CORCC → SCCC.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
__version__ = "0.3.0"
|
|
9
|
+
|
|
10
|
+
from .forge import SCLanguage, StepRef, forge, lint, package, parse_sequence, resolve_steps
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"forge", "lint", "package", "parse_sequence", "resolve_steps",
|
|
14
|
+
"SCLanguage", "StepRef", "__version__",
|
|
15
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""sccc — chain forged ACs + CoRs + regular skills into one SKILL.md rollup."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from . import forge, lint, package, resolve_steps
|
|
10
|
+
from .notation import SEED_SEQUENCES
|
|
11
|
+
|
|
12
|
+
_STUB_SKILL = """---
|
|
13
|
+
name: summarize
|
|
14
|
+
description: Condense the prior step's result into a short answer.
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Summarize the input into 3 sentences and state the single most important takeaway.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.group()
|
|
22
|
+
@click.version_option(message="sccc %(version)s")
|
|
23
|
+
def main() -> None:
|
|
24
|
+
"""Skillchain Compiler-Compiler — the highest composite (AC + CoR + skills)."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@main.command()
|
|
28
|
+
def demo() -> None:
|
|
29
|
+
"""Forge AC + CoR + a regular skill, chain them into one SKILL.md rollup."""
|
|
30
|
+
import accc
|
|
31
|
+
import corcc
|
|
32
|
+
from corcc.notation import EINSTEIN
|
|
33
|
+
|
|
34
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
35
|
+
db = str(Path(tmp) / "cc.db")
|
|
36
|
+
skills_dir = str(Path(tmp) / "skills")
|
|
37
|
+
out_dir = str(Path(tmp) / "dist")
|
|
38
|
+
|
|
39
|
+
click.echo("══ 1. FORGE + PACKAGE the parts (each a SKILL.md) ══")
|
|
40
|
+
accc.forge("debug-attention", ["[Symptom] ⇒ [Repro] ⇒ [Hypothesis] ⇒ |Localize|"], db=db)
|
|
41
|
+
ac_path = accc.package("debug-attention",
|
|
42
|
+
"[Symptom] ⇒ [Repro] ⇒ [Hypothesis] ⇒ |Localize|", out_dir=skills_dir)
|
|
43
|
+
persona = corcc.forge_persona(EINSTEIN, db=db)
|
|
44
|
+
cor_path = corcc.package(persona, out_dir=skills_dir)
|
|
45
|
+
skill_path = Path(skills_dir) / "summarize" / "SKILL.md"
|
|
46
|
+
skill_path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
skill_path.write_text(_STUB_SKILL, encoding="utf-8")
|
|
48
|
+
ac_slug, cor_slug = ac_path.parent.name, cor_path.parent.name
|
|
49
|
+
click.echo(f" AC → {ac_slug}/SKILL.md")
|
|
50
|
+
click.echo(f" CoR → {cor_slug}/SKILL.md")
|
|
51
|
+
click.echo(f" skill → summarize/SKILL.md (a regular skill)")
|
|
52
|
+
|
|
53
|
+
click.echo("\n══ 2. FORGE the SC sequence grammar (shape) ══")
|
|
54
|
+
scl = forge("debug-then-explain", SEED_SEQUENCES, db=db)
|
|
55
|
+
click.echo(f" forged scope={scl.scope}, {scl.rule_count} rules")
|
|
56
|
+
|
|
57
|
+
sequence = f"[ac:{ac_slug}] ⇒ [cor:{cor_slug}] ⇒ [skill:summarize] ⇒ |Answer|"
|
|
58
|
+
click.echo(f"\n══ 3. LINT the SC sequence (syntax) ══\n {sequence}")
|
|
59
|
+
v, fixes = lint(scl, sequence)
|
|
60
|
+
click.echo(f" → {'clean ✓' if not v else 'violations'}")
|
|
61
|
+
|
|
62
|
+
click.echo("\n══ 4. LINT a malformed sequence (dropped ⇒) ══")
|
|
63
|
+
bad = f"[ac:{ac_slug}] [skill:summarize] ⇒ |Answer|"
|
|
64
|
+
v2, fx2 = lint(scl, bad)
|
|
65
|
+
if v2:
|
|
66
|
+
click.echo(f" {bad}\n ✗ {v2[0].verdict}: expected {v2[0].expected_token!r}, "
|
|
67
|
+
f"found {v2[0].found_token!r}; fix insert {list(fx2[0].candidate_tokens)}")
|
|
68
|
+
|
|
69
|
+
click.echo("\n══ 5. RESOLVE steps (each must exist on disk) ══")
|
|
70
|
+
rows, missing = resolve_steps(sequence, skills_dir=skills_dir)
|
|
71
|
+
for i, kind, ref, status, path in rows:
|
|
72
|
+
click.echo(f" {'✓' if status=='ok' else '✗'} [{i}] {kind:5} {ref}")
|
|
73
|
+
|
|
74
|
+
click.echo("\n══ 6. PACKAGE — roll the chain up into one SKILL.md ══")
|
|
75
|
+
sc_path = package("debug-then-explain", sequence, out_dir=out_dir, skills_dir=skills_dir)
|
|
76
|
+
click.echo(f" wrote {sc_path}")
|
|
77
|
+
click.echo(" ── SKILL.md (rollup) ──")
|
|
78
|
+
click.echo("\n".join(" " + ln for ln in sc_path.read_text().splitlines()[:24]))
|
|
79
|
+
|
|
80
|
+
click.echo("\n══ 7. The capstone ══")
|
|
81
|
+
click.echo(" AC → CoR → SC: an attention template, a spoken reasoning chain, and a regular "
|
|
82
|
+
"skill, composed into ONE publishable skillchain. All SKILL.md. All syntax-checked.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
main()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""SCCC — the Skillchain Compiler-Compiler. Composes ACCC + CORCC + regular skills.
|
|
2
|
+
|
|
3
|
+
Same *CC shape: catch the SC sequence grammar → LINT its syntax → resolve each
|
|
4
|
+
step to a SKILL.md package (forged AC/CoR or regular skill) → compile the rollup
|
|
5
|
+
to `<name>/SKILL.md`. Built on the existing `skillchain` module for indexing,
|
|
6
|
+
resolution, and rollup compilation. Syntax + resolution only — never content.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
import skillchain as sc
|
|
15
|
+
|
|
16
|
+
from chaincompiler import slugify, learn # made FROM CC (CC re-exports APE's slugify/learn)
|
|
17
|
+
from chaincompiler import gate as cp_gate # the persistent grammar gate, re-exported by CC
|
|
18
|
+
from rulecatcher.db import connect
|
|
19
|
+
|
|
20
|
+
_STEP_RE = re.compile(r"\[(ac|cor|skill):([^\]]+)\]")
|
|
21
|
+
_HELD_RE = re.compile(r"\|([^|]+)\|")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class SCLanguage:
|
|
26
|
+
name: str
|
|
27
|
+
scope: str
|
|
28
|
+
db: str
|
|
29
|
+
rule_count: int
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class StepRef:
|
|
34
|
+
kind: str # ac | cor | skill
|
|
35
|
+
ref: str # the referenced package name
|
|
36
|
+
slug: str # resolved package slug
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def forge(name: str, sequences: list[str], *, db: str) -> SCLanguage:
|
|
40
|
+
"""Catch + ratify the SC sequence notation grammar (shape only)."""
|
|
41
|
+
scope = f"sc:{name}"
|
|
42
|
+
with connect(db) as cx:
|
|
43
|
+
adopted = learn(cx, sequences, scope=scope, rule_types=("next_kind",), label=f"sc:{name}")
|
|
44
|
+
return SCLanguage(name=name, scope=scope, db=db, rule_count=len(adopted))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def lint(language: SCLanguage, sequence: str) -> tuple[list, list]:
|
|
48
|
+
"""Syntax-lint an SC sequence against the ratified grammar (the *CC check)."""
|
|
49
|
+
with connect(language.db) as cx:
|
|
50
|
+
return cp_gate(cx, sequence, scope=language.scope)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse_sequence(sequence: str) -> tuple[list[StepRef], str | None]:
|
|
54
|
+
steps = [StepRef(kind=m.group(1), ref=m.group(2).strip(), slug=slugify(m.group(2).strip()))
|
|
55
|
+
for m in _STEP_RE.finditer(sequence)]
|
|
56
|
+
held = _HELD_RE.search(sequence)
|
|
57
|
+
return steps, (held.group(1).strip() if held else None)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _index(skills_dir: str | None):
|
|
61
|
+
roots = list(sc.SKILL_ROOTS)
|
|
62
|
+
if skills_dir:
|
|
63
|
+
roots.insert(0, Path(skills_dir))
|
|
64
|
+
return sc.index_skills(roots=roots)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def resolve_steps(sequence: str, *, skills_dir: str | None = None) -> tuple[list, list[str]]:
|
|
68
|
+
"""Resolve each step to a package. Returns (rows, missing)."""
|
|
69
|
+
steps, _ = parse_sequence(sequence)
|
|
70
|
+
idx = _index(skills_dir)
|
|
71
|
+
rows, missing = [], []
|
|
72
|
+
for i, step in enumerate(steps, 1):
|
|
73
|
+
rec = sc.resolve(step.slug, idx) or sc.resolve(step.ref, idx)
|
|
74
|
+
if rec:
|
|
75
|
+
rows.append((i, step.kind, step.ref, "ok", str(rec["path"])))
|
|
76
|
+
else:
|
|
77
|
+
rows.append((i, step.kind, step.ref, "MISSING", ""))
|
|
78
|
+
missing.append(f"{step.kind}:{step.ref}")
|
|
79
|
+
return rows, missing
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def package(name: str, sequence: str, *, out_dir: str, skills_dir: str | None = None,
|
|
83
|
+
description: str | None = None) -> Path:
|
|
84
|
+
"""Compile an SC sequence into a rollup `<name>/SKILL.md`.
|
|
85
|
+
|
|
86
|
+
Resolves AC/CoR/skill steps to their packages (validated present) and rolls
|
|
87
|
+
them up in order via the skillchain compiler.
|
|
88
|
+
"""
|
|
89
|
+
steps, held = parse_sequence(sequence)
|
|
90
|
+
spec = {
|
|
91
|
+
"name": slugify(name),
|
|
92
|
+
# NOTE: no ": " prefix in the default — write_skill (APE) does not YAML-escape
|
|
93
|
+
# the description; a plain scalar containing ": " emits unparseable frontmatter.
|
|
94
|
+
# (The step refs keep their `kind:name` form — colon-no-space is YAML-safe.)
|
|
95
|
+
"description": description or (
|
|
96
|
+
f"Skillchain — {' → '.join(f'{s.kind}:{s.ref}' for s in steps)}"
|
|
97
|
+
+ (f" → {held}" if held else "")
|
|
98
|
+
),
|
|
99
|
+
"steps": [{"skill": s.slug} for s in steps],
|
|
100
|
+
}
|
|
101
|
+
idx = _index(skills_dir)
|
|
102
|
+
ok, _rows, missing = sc.validate(spec, idx)
|
|
103
|
+
if not ok:
|
|
104
|
+
raise ValueError(f"cannot compile SC '{name}': unresolved steps {missing}")
|
|
105
|
+
pkg, _deps = sc.compile_package(spec, idx, out_dir)
|
|
106
|
+
return pkg / "SKILL.md"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""SC (skillchain) sequence notation.
|
|
2
|
+
|
|
3
|
+
An SC IS A sequence chaining ACs + CoRs + regular skills, converging on a result:
|
|
4
|
+
|
|
5
|
+
[ac:<name>] ⇒ [cor:<name>] ⇒ [skill:<name>] ⇒ ... ⇒ |Result|
|
|
6
|
+
|
|
7
|
+
Each `[kind:name]` step references a SKILL.md package on disk — a forged
|
|
8
|
+
attention chain (kind=ac), a forged chain-of-reasoning (kind=cor), or a regular
|
|
9
|
+
skill (kind=skill). Written in Dense Rune-Chain so it is syntax-linted like every
|
|
10
|
+
other *CC notation. SC is the highest composite: AC → CoR → SC.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
# Seed SC sequences (used to forge the SC notation grammar — the shape only).
|
|
15
|
+
SEED_SEQUENCES = [
|
|
16
|
+
"[ac:debug-attention] ⇒ [cor:think-like-einstein] ⇒ [skill:summarize] ⇒ |Answer|",
|
|
17
|
+
"[ac:debug-attention] ⇒ [skill:summarize] ⇒ |Answer|",
|
|
18
|
+
"[cor:think-like-einstein] ⇒ [skill:summarize] ⇒ |Answer|",
|
|
19
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sccc
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Skillchain Compiler-Compiler — chains ACs + CoRs + skills into one SKILL.md
|
|
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
|
|
9
|
+
Requires-Dist: rulecatcher==0.3.0
|
|
10
|
+
Requires-Dist: skillchain-compiler==0.3.0
|
|
11
|
+
Requires-Dist: click
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/sccc/__init__.py
|
|
3
|
+
src/sccc/cli.py
|
|
4
|
+
src/sccc/forge.py
|
|
5
|
+
src/sccc/notation.py
|
|
6
|
+
src/sccc.egg-info/PKG-INFO
|
|
7
|
+
src/sccc.egg-info/SOURCES.txt
|
|
8
|
+
src/sccc.egg-info/dependency_links.txt
|
|
9
|
+
src/sccc.egg-info/entry_points.txt
|
|
10
|
+
src/sccc.egg-info/requires.txt
|
|
11
|
+
src/sccc.egg-info/top_level.txt
|
|
12
|
+
tests/test_sccc.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sccc
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""SCCC tests: forging the sequence grammar, lint, resolution, rollup packaging."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import accc
|
|
7
|
+
import corcc
|
|
8
|
+
from corcc.notation import EINSTEIN
|
|
9
|
+
|
|
10
|
+
from sccc import forge, lint, package, parse_sequence, resolve_steps
|
|
11
|
+
from sccc.notation import SEED_SEQUENCES
|
|
12
|
+
|
|
13
|
+
_STUB = "---\nname: summarize\ndescription: condense\n---\n\nSummarize it.\n"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _setup(tmp_path: Path):
|
|
17
|
+
db = str(tmp_path / "cc.db")
|
|
18
|
+
skills = tmp_path / "skills"
|
|
19
|
+
accc.forge("debug-attention", ["[Symptom] ⇒ [Repro] ⇒ |Localize|"], db=db)
|
|
20
|
+
ac = accc.package("debug-attention", "[Symptom] ⇒ [Repro] ⇒ |Localize|", out_dir=str(skills))
|
|
21
|
+
persona = corcc.forge_persona(EINSTEIN, db=db)
|
|
22
|
+
cor = corcc.package(persona, out_dir=str(skills))
|
|
23
|
+
(skills / "summarize").mkdir(parents=True, exist_ok=True)
|
|
24
|
+
(skills / "summarize" / "SKILL.md").write_text(_STUB)
|
|
25
|
+
seq = f"[ac:{ac.parent.name}] ⇒ [cor:{cor.parent.name}] ⇒ [skill:summarize] ⇒ |Answer|"
|
|
26
|
+
return db, str(skills), seq
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_parse_sequence_extracts_typed_steps():
|
|
30
|
+
steps, held = parse_sequence("[ac:foo] ⇒ [cor:bar] ⇒ [skill:baz] ⇒ |Result|")
|
|
31
|
+
assert [(s.kind, s.ref) for s in steps] == [("ac", "foo"), ("cor", "bar"), ("skill", "baz")]
|
|
32
|
+
assert held == "Result"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_closure_a_skillchain_can_chain_another_skillchain(tmp_path: Path):
|
|
36
|
+
"""Everything is a skill dir, so an SC can reference an SC. The algebra closes."""
|
|
37
|
+
db, skills, seq = _setup(tmp_path)
|
|
38
|
+
# SC#1 compiled INTO the skills dir → it is itself an indexable skill dir.
|
|
39
|
+
forge("inner", [seq], db=db)
|
|
40
|
+
sc1 = package("inner", seq, out_dir=skills, skills_dir=skills)
|
|
41
|
+
assert sc1.exists()
|
|
42
|
+
# SC#2 references SC#1 as a plain skill (closure) + another skill.
|
|
43
|
+
seq2 = f"[skill:{sc1.parent.name}] ⇒ [skill:summarize] ⇒ |Done|"
|
|
44
|
+
_rows, missing = resolve_steps(seq2, skills_dir=skills)
|
|
45
|
+
assert missing == []
|
|
46
|
+
sc2 = package("outer", seq2, out_dir=skills, skills_dir=skills)
|
|
47
|
+
assert sc1.parent.name in sc2.read_text() # SC#2 rolls up SC#1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_forge_and_lint_sequence(tmp_path: Path):
|
|
51
|
+
db, skills, seq = _setup(tmp_path)
|
|
52
|
+
scl = forge("demo", SEED_SEQUENCES, db=db)
|
|
53
|
+
assert scl.rule_count > 0
|
|
54
|
+
assert lint(scl, seq)[0] == [] # well-formed
|
|
55
|
+
assert lint(scl, "[ac:foo] [skill:baz] ⇒ |R|")[0] # malformed (dropped ⇒)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_resolve_steps_finds_forged_packages(tmp_path: Path):
|
|
59
|
+
_db, skills, seq = _setup(tmp_path)
|
|
60
|
+
rows, missing = resolve_steps(seq, skills_dir=skills)
|
|
61
|
+
assert missing == []
|
|
62
|
+
assert all(status == "ok" for _, _, _, status, _ in rows)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_package_rolls_up_into_one_skill(tmp_path: Path):
|
|
66
|
+
_db, skills, seq = _setup(tmp_path)
|
|
67
|
+
out = tmp_path / "dist"
|
|
68
|
+
sc_path = package("debug-then-explain", seq, out_dir=str(out), skills_dir=skills)
|
|
69
|
+
text = sc_path.read_text()
|
|
70
|
+
assert sc_path.name == "SKILL.md"
|
|
71
|
+
assert "debug-attention" in text # AC step referenced
|
|
72
|
+
assert "thinklikeeinstein" in text # CoR step referenced
|
|
73
|
+
assert "summarize" in text # regular skill referenced
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_package_refuses_when_step_missing(tmp_path: Path):
|
|
77
|
+
db, skills, _seq = _setup(tmp_path)
|
|
78
|
+
bad_seq = "[skill:does-not-exist] ⇒ |R|"
|
|
79
|
+
try:
|
|
80
|
+
package("broken", bad_seq, out_dir=str(tmp_path / "d"), skills_dir=skills)
|
|
81
|
+
assert False, "should refuse to compile with a missing step"
|
|
82
|
+
except ValueError:
|
|
83
|
+
pass
|