소스 정보
- 저장소
- generative-computing/mellea-skills-compiler
- 최근 소스 활동
- 2026년 8월 21일 11:27
- 감지된 SKILL.md 언어
- 영어
- 스타
- 46
- 포크
- 8
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/generative-computing/mellea-skills-compiler --skill mellea-fy-generate명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | mellea-fy-generate |
| description | # Melleafy Steps 3 + 5: Skeleton Emission and Body Generation |
| metadata | {"user-invocable":true,"disable-model-invocation":true} |
Version: 4.3.0 (2026-04-28) | Prereq: dependency_plan.json (Step 2.5 complete) | Produces: Populated Python package
Output path rule (Rule OUT-3): All
.pyfiles (pipeline.py,config.py,schemas.py,main.py, etc.) are written inside<package_name>/.pyproject.tomlis the only file written at the skill root (NOT inside<package_name>/). Seemellea-fy.md§Output directory layout for the full tree.
Step 3 emits skeleton files (imports, signatures, docstring placeholders) from the element mapping and dependency plan. Step 5 invokes the LLM to fill in every code body. Read /mellea-fy-behaviours before generating any code — the Known Behaviours mitigations must be baked into every generated file.
| File | When generated |
|---|---|
pipeline.py | Always |
schemas.py | Always (at minimum contains Final[str] placeholder if no schemas found) |
config.py | Always (persona text, model ID, loop budgets from dependency_plan.json:bundle entries) |
requirements.py | When any VALIDATE_OUTPUT elements exist |
slots.py | When any EXTRACT or CLASSIFY elements map to @generative |
tools.py | When any C6 element has disposition real_impl |
constrained_slots.py | When any C6 element has disposition stub or delegate_to_runtime |
mobjects.py | When any TRANSFORM or QUERY elements exist |
loader.py | When any C3 element has disposition load_from_disk |
main.py | Always (CLI entry point) |
pyproject.toml | Always |
SETUP.md | When any C4, C5, C9, non-bundled C6, C7, non-default C8, or host-needing modality |
README.md | Always |
melleafy.json | Always (skeleton in Step 3; finalised in Step 6) |
dependencies.yaml | When any C6/C7/C8 entry is non-bundle |
The run_pipeline function signature and main.py shape vary by modality from classification.json:
| Modality | Entry point shape |
|---|---|
synchronous_oneshot | run_pipeline(*params) -> OutputSchema — simple function call |
streaming | run_pipeline(*params) -> Iterator[str] — generator yielding tokens |
conversational_session | run_pipeline(session_id: str, *params) -> OutputSchema — session-keyed |
review_gated | run_pipeline(*params) -> ReviewRequest — returns for human approval |
scheduled | run_pipeline() -> None — no user-provided params; data fetched internally |
event_triggered | run_pipeline(event: dict) -> None — event payload as input |
heartbeat | run_pipeline(state: dict) -> dict — stateful loop, returns updated state |
realtime_media | run_pipeline(stream: Iterator) -> Iterator — streaming I/O |
Rule 3-1 — run_pipeline parameter type annotations: Every parameter in the generated run_pipeline function signature MUST have an explicit Python type annotation. If the source spec declares a type for a parameter (e.g. from a typed function signature, a schema field, or an explicit type note in the spec text), use that type. If the source spec is untyped or ambiguous, default to str. Do not emit bare parameter names (e.g. company_domain) — emit company_domain: str instead. This applies to both required and optional (defaulted) parameters.
Companion directories from the skill root (scripts/, references/, assets/) are mirrored into <package_name>/ deterministically by the compile pipeline, before mellea-fy runs. The model does not perform the copy — it is plumbing handled by mellea_skills_compiler.compile.compiler._mirror_companion_dirs. By the time Step 3 begins, any companion directory that existed at the skill root is already present at <package_name>/<dir>/ and can be referenced directly.
The model's responsibility is path-resolution discipline: any code emitted in Step 5 that loads or invokes a bundled asset MUST resolve its path package-relatively via Path(__file__).parent / "<dir>/<file>", not via a user-supplied repo_root argument or the process working directory. This invariant is what makes the generated package self-contained — a pip install-ed package, or one invoked from any cwd, finds its bundled assets via Python's own module-location machinery.
Step 7's bundled-asset-path-resolution lint catches violations at validation time. The pyproject.toml template (below) declares these directories under [tool.setuptools.package-data] so the mirrored copies ship with the installed wheel.
config.py: C1 and C2 bundle entries become Final[str] constants under # === C1: Identity === and # === C2: Operating Rules === section headers. C8 bundle entries (model ID, backend) also here. Every constant gets a # PROVENANCE: <source_file>:<source_lines> comment.
In Step 5, the model emits ONLY JSON intermediate/config_emission.json conforming to schemas/config_emission.schema.json — not Python source. The writer config_writer.py reads this JSON and renders the final config.py file.
C8 backend rule (CRITICAL):
BACKENDandMODEL_IDvalues MUST be read fromintermediate/runtime_directive.json(fields.backendand.model_idrespectively). Read the file, extract the two values, and include them in theconfig_emission.jsonoutput exactly as they appear inruntime_directive.json. Do not invent alternatives or use values from any other source. The Step 7runtime-defaults-boundlint enforces this — divergence from theruntime_directive.jsonvalues is a hard failure.
JSON the model emits to intermediate/config_emission.json:
{
"constants": [
{
"name": "PREFIX_TEXT",
"value": "<persona text from SOUL.md>",
"type": "str",
"category": "C1",
"provenance": { "source_file": "SOUL.md", "source_lines": "1-45" }
},
{
"name": "BACKEND",
"value": "ollama",
"type": "str",
"category": "C8"
},
{
"name": "MODEL_ID",
"value"
schemas.py: One Pydantic BaseModel per SCHEMA element. Field descriptions pulled from the spec's output format description. For two-step pattern: include both the simplified raw schema and the full schema.
requirements.py: One Requirement object per VALIDATE_OUTPUT element. Group by spec section. Structural checks use simple_validate(); semantic checks use bare Requirement(description=...). Include check_only=True for negative constraints.
slots.py: One @generative function per EXTRACT/CLASSIFY element. Return types:
-> Literal[...] — Ollama supports constrained decoding, so always use the typed return.-> str with "Set result to a comma-separated string of..." docstring — never -> list[str]. Split in pipeline.py: [p.strip() for p in raw.split(",") if p.strip()] if raw.strip() else []BaseModel (model fields provide the JSON structure; no bare-output risk)Docstrings on all @generative slots MUST reference result explicitly (e.g. "Set result to..."). Never use "Reply with exactly one word", "Output only", "Return only". Simple schemas (≤4 fields, no Literal constraints, no nested models) go here directly. Complex schemas: slot extracts simplified version, Step 5 generates the m.instruct enrichment step inline in pipeline.py.
@generative definition convention: The decorator forbids m as a parameter name — passing it in the definition raises ValueError at import time. Function body must be .... m is passed as the first positional argument only at call time in pipeline.py.
# CORRECT — no m in definition, body is ...
@generative
def extract_sentiment(text: str) -> str:
"""Set `result` to one of: positive, negative, neutral."""
...
# WRONG — raises ValueError: cannot create a generative slot with disallowed parameter names: ['m']
@generative
def extract_sentiment(m, text: str) -> str:
...
Calling convention in pipeline.py (unchanged — m passed as first positional arg):
with start_session(BACKEND, MODEL_ID) as m:
sentiment = extract_sentiment(m, text=user_input)
tools.py (when any C6 has disposition real_impl):
ALLOWED_DOMAINS or ALLOWED_COMMANDSbuild_api_params() or equivalent mapping spec-level names to API parameter namesscripts/bash/check-prerequisites.sh), resolve the script path package-relatively. Use Path(__file__).parent / "scripts/<...>" — never Path(repo_root) / "scripts/<...>" and never rely on the process working directory. Example: script_path = Path(__file__).parent / "scripts" / "bash" / "check-prerequisites.sh". The mirror is established by Step 3a-pre, so the path is guaranteed to resolve at runtime regardless of where the package is invoked from.ALLOWED_DOMAINS = ["api.example.com"]
HTTP_TIMEOUT = 10
def http_get(url: str) -> str:
"""Fetch content from a URL. Validates against domain allowlist."""
from urllib.parse import urlparse
parsed = urlparse(url)
if parsed.hostname not in ALLOWED_DOMAINS:
raise ValueError(f"Domain '{parsed.hostname}' not in allowlist")
# ... execute with timeout and error handling
Multi-mode subprocess wrappers: if a shell script exposes multiple modes (e.g. --check-prompt, --check-url, --check-command), inspect how each mode receives its input before generating the wrapper:
input=$(cat) in the script body) → call subprocess.run(..., input=target, capture_output=True, text=True). Do NOT append target to the command list.target to the command list as before.Apply this per-mode distinction at the call site in pipeline.py too: every branch calling the wrapper must forward the input correctly for that mode. A branch that omits target= for a stdin-consuming mode is a silent false-negative bug.
constrained_slots.py (when any C6 has disposition stub or delegate_to_runtime):
ConstrainedGenerativeSlot, constrained decorator, filter_actions locallyReactTool / ReactToolbox locallyNotImplementedError with implementation instructionsslots.py with constrained() — does NOT duplicate thempipeline.py (standard structure):
One function per ORCHESTRATE workflow
with start_session(BACKEND, MODEL_ID) as m: context manager
Calls slots, requirements, and mobjects from other files
DECIDE logic: Python if/elif/else wrapping Mellea calls
MUST thread the user input / per-call value into m.instruct(description=...) via user_variables + a {{ key }} Jinja placeholder in the description text — e.g. m.instruct("Classify: {{ user_query }} ...", user_variables={"user_query": str(user_query)}, ...). Reserve grounding_context for document or reference material the description text explicitly cites — that is the channel the docs reserve for it (see https://docs.mellea.ai/how-to/working-with-data, which pairs user_variables={"query": ...} with grounding_context={"doc0": doc0, ...}). The per-backend prompt template (e.g. mellea/templates/prompts/granite/Instruction.jinja2) renders the grounding_context block under the header "Write the response by aligning with the facts and items in the following grounding context" — that framing fits supporting documents, not the value the model must directly extract from or classify; with format=PydanticModel constrained decoding, putting the primary input there produces silent extraction failures.
MUST convert any non-string grounding_context value with str().
MUST use format=PydanticModel for every m.instruct() that produces structured output
MUST parse the thunk after every m.instruct(format=Model) before accessing any field or calling any Pydantic method. m.instruct() returns a ComputedModelOutputThunk — NOT a Pydantic model. Direct field access (thunk.field_name) or .model_dump() raises AttributeError. Always include _parse_instruct_result and _safe_parse_with_fallback helpers in pipeline.py and call them immediately after every m.instruct(format=Model) call:
Canonical Mellea import paths — use these exact paths; do not guess or infer alternatives:
from mellea import start_session, generative
from mellea.stdlib.sampling import RepairTemplateStrategy
from mellea.stdlib.requirements import req, check, simple_validate
Rule 5-2 — Import path grounding. Fallback only — primary enforcement is via invariant 3 above. Before writing any
from mellea.X import Ystatement, verify that the module pathmellea.Xexists inintermediate/mellea_api_ref.json:.modules. Any path not present there is invalid and must not be generated.Common error pattern: generating shortened paths that do not exist (e.g.
mellea.model_options) when the symbol lives deeper in the hierarchy (e.g.mellea.backends.model_options). The.moduleskey is the ground truth — consult it, not training knowledge, for import paths.The KB entries in
/mellea-fy-behavioursalready show the canonical import for each KB-relevant symbol. For symbols not covered by a KB entry, derive the import path frommellea_api_ref.json:.modules.
Rule 5-4 — Stdlib function signature grounding. Fallback only — primary enforcement is via invariant 3 above. Before emitting any call to a
mellea.stdlib.*function, verify the function's argument count and keyword parameter names against the known-signature list below. Do not infer signatures by analogy to similar functions in other libraries (e.g. do not assume(fn, error_message)forms that exist inpytestorpydanticbut not in Mellea).Known signatures (static fallback when
mellea_api_ref.jsonis absent orgrounding_unavailable: true):
Function Module Signature simple_validatemellea.stdlib.requirementssimple_validate(fn)— 1 positional argument onlyreqmellea.stdlib.requirementsreq(description, *, validation_fn=None)— 1 required positional, 1 optional keywordcheckmellea.stdlib.requirementscheck(requirement, output)— 2 positional argumentsCommon error pattern:
simple_validate(_check_fn, "error message")— the two-argument form is invalid.simple_validatewraps the validator function; the error message, if needed, is handled inside the validator function itself. The correct call issimple_validate(_check_fn).For any
mellea.stdlib.*function not in the table above, derive its signature fromintermediate/mellea_api_ref.json:.modules.<module>.<symbol>.signaturebefore emitting the call.
Pipeline structure by tool involvement:
P0 — No tools: pure pipeline.py calling slots.py and requirements.py.
P4 — Tools provide input: main.py gathers pre-pipeline data, passes as parameters to run_pipeline().
P2 — Pipeline calls tools (deterministic):
intent_thunk = m.instruct(format=IntentSchema, ...) — result is a ComputedModelOutputThunk, NOT a Pydantic object; MUST parse immediately: intent = _safe_parse_with_fallback(intent_thunk, IntentSchema, query_type="out_of_scope", ...)if intent.query_type == "out_of_scope": return (deterministic, no tool call — intent here is the parsed Pydantic object, not the thunk)TEMPLATES[intent.query_type].format(...) → tool_fn(url_or_params)response_thunk = m.instruct(format=ResponseSchema, ...) → response = _safe_parse_with_fallback(response_thunk, ResponseSchema, ...) with raw tool output as grounding
MUST use two separate start_session() calls for steps 1 and 4 (schema priming).P3 — Pipeline calls tools (LLM-directed):
m.react() with a toolbox, or m.instruct() with ModelOption.TOOLSTOOL_PRE/POST_INVOKE hooks fire automatically for governancepyproject.toml (always):
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"
[project]
name = "<package-name>"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"mellea[hooks]>=0.4.2",
"pydantic>=2.0",
]
[project.scripts]
<package-name> = "<package_module>.main:main"
# Rule OUT-6 — declare mirrored companion directories as package data so
# bundled scripts/references/assets ship with the installed wheel. Include
# only the directories that exist after Step 3a-pre's mirror.
[tool.setuptools.package-data]
"<package_module>" = ["scripts/**/*", "references/**/*", "assets/**/*"]
Note: the openai-agents package is NOT added to dependencies for Agents SDK source specs — the generated package uses Mellea, not the Agents SDK.
Step 5 fills every skeleton placeholder with real code. For config.py, the model emits JSON and the writer config_writer.py renders Python source (invariant 1).
Parallelization strategy: Step 5 is structured in three phases to maximize throughput:
In repair mode, only re-generate the files listed in intermediate/step_7_report.json failure entries; unchanged files pass through without re-generation.
Read once; apply throughout all file generation.
1. config.py output is JSON, not Python source. Emit a JSON object conforming to schemas/config_emission.schema.json. The deterministic writer config_writer.py renders the file — do not write Python source for config.py directly.
2. All other files output Python source. Generate one file per LLM invocation (Rule 5-3). Wait for each file's body before starting the next.
3. Before generating any file, consult intermediate/mellea_api_ref.json:
.modules — valid mellea.* paths for imports.modules.<module>.<symbol>.signature — exact signature for any mellea.stdlib.* symbol (nested under .modules).forbidden_param_names — disallowed @generative parameter names.compatibility — Mellea-version-gated workarounds to injectIf grounding_unavailable: true, fall back to the KB patterns in /mellea-fy-behaviours and the static signature tables in Rules 5-2 and 5-4.
4. Use canonical fixture pairs from intermediate/fixtures_emission.json as concrete examples (already produced by Step 4) for the file being generated. Use these as the reference for correct Mellea usage, not training memory.
5. Behavioral guidance is in /mellea-fy-behaviours. Read it once before generating any file body.
6. Step 7 lint failures, not these instructions, are the source of truth for correctness. Generate per spec; let the repair loop correct lint failures rather than anticipating every possible check.
Rule 5-3 — File-level batching: Generate all code bodies for a given output file in a single LLM invocation. Do not make one invocation per element. For each file in the skeleton (e.g.
pipeline.py,config.py,slots.py,tools.py), issue one invocation that generates the complete file contents, guided by all relevant element mapping entries for that file. KB5 schema priming concerns do not apply to melleafy compilation calls (KB5 governs Mellea pipeline sessions inside compiled skills, not the compilation process itself).
Each invocation uses a prompt template with all element-specific mapping entries for that file as variable substitution.
Before generating any body, include in the context:
Phase A (parallel batch — single turn with parallel tool calls):
Generate all of these simultaneously in one turn. They are mutually independent at generation time:
schemas.py — Pydantic models (referenced by Phase B)intermediate/config_emission.json — config json (referenced in Step 5)requirements.py — requirement functions (referenced by Phase B)slots.py — @generative slot bodies (referenced by Phase B)tools.py / constrained_slots.py — tool implementations (referenced by Phase B)mobjects.py — mified object definitions (referenced by Phase B)loader.py — file loader functions (referenced by Phase B)Phase B (sequential, after Phase A):
pipeline.py — the orchestrating pipeline (references all Phase A symbols)Phase C (sequential, after Phase B):
main.py — CLI entry point (references pipeline.py)Within Phase A, each file should be generated with its own LLM invocation, dispatched in parallel using the tool-call parallelism available in your response. Do not wait for one file to complete before starting the next — issue all Phase A invocations in the same turn.
# In pipeline.py — bounded remediation loop
MAX_REMEDIATION_ITERATIONS: Final[int] = 3 # in config.py
patched_code = original_code
remediation_count = 0
verdict = initial_verdict
while not verdict.passed and remediation_count < MAX_REMEDIATION_ITERATIONS:
# Modification step: generate a fix
with start_session(BACKEND, MODEL_ID) as m_fix:
fix = m_fix.instruct(
"Generate a minimal patch for this code:\n{{ current_code }}\n\n"
"Issue to address:\n{{ verdict }}",
user_variables={
"current_code": str(patched_code),
"verdict": str(verdict.model_dump()),
},
format=CodeFix,
strategy=RepairTemplateStrategy(loop_budget=LOOP_BUDGET),
)
fix_obj = _parse_instruct_result(fix, CodeFix)
patched_code = fix_obj.patched_code
remediation_count += 1
# Re-evaluation step
with start_session(BACKEND, MODEL_ID) as m_eval:
verdict = _parse_instruct_result(
m_eval.instruct(
"Re-evaluate this code:\n{{ code }}",
user_variables={"code": str(patched_code)},
format=Verdict,
),
Verdict,
)
After writing pipeline.py and tools.py, cross-reference every model.field_name access against the model's field definitions in schemas.py. Accessing a field that doesn't exist raises AttributeError at runtime. Correct field access patterns are shown in the fixture examples injected via the grounding context — use those as the reference for how generated code should access schema fields.
When 3+ independent slots read the same input, call them sequentially on a single session — do NOT use asyncio.gather() with shared sessions (concurrent session sharing is unsafe in Mellea):
with start_session(BACKEND, MODEL_ID) as m:
# Same BaseModel return type — safe in one session
primary_findings = extract_primary_findings(m, code_text=code)
secondary_findings = extract_secondary_findings(m, code_text=code)
config_issues = extract_config_issues(m, code_text=code)
When Step 2 mapped an element to the two-step pattern:
# Step 1: @generative extracts simplified data (already in slots.py)
raw_paths = extract_raw_attack_paths(m, code_text=code, threat_summary=summary)
# Step 2: m.instruct() structures into full schema with repair strategy
if raw_paths:
paths_thunk = m.instruct(
"Enrich these attack paths with risk ratings, impact, and likelihood assessments.",
model_options={ModelOption.SYSTEM_PROMPT: PREFIX_TEXT},
grounding_context={
"raw_attack_paths": str([p.model_dump() for p in raw_paths]),
"code_text": code,
},
format=AttackPathList,
strategy=RepairTemplateStrategy(loop_budget=LOOP_BUDGET),
)
attack_paths = _safe_parse_with_fallback(paths_thunk, AttackPathList, paths=[]).paths
For realisation (2) — pipeline parameter:
def run_pipeline(
user_query: str, # from CONVERSE element
reference_context: str = "", # from TOOL_INPUT or loader
) -> OutputSchema:
...
For realisation (3) — stub:
def _get_user_approval(draft: str) -> str:
"""Interactive approval step — requires host adapter. See SETUP.md §7."""
raise NotImplementedError(
"This step requires a host adapter for interactive user input. "
"Implement this function or provide the approved draft as a parameter."
)
melleafy.json skeleton (Step 3, finalised in Step 6){
"format_version": "1.0",
"manifest_version": "1.1.0",
"package_name": "<package_name>",
"generated_at": "<ISO timestamp>",
"melleafy_version": "4.0.0",
"source_runtime": "<from classification.json>",
"modality": "<from classification.json>",
"archetype": "<from classification.json>",
"categories_resolved": "<populated in Step 6>",
"entry_signature": "<populated in Step 6>",
"pipeline_parameters": "<populated in Step 6>",
"declared_env_vars": []
}
intermediate/mellea_api_ref.json was consulted before code body generation (or grounding_unavailable: true was noted and KB fallback used)intermediate/fixtures_emission.json (Step 4) were used as grounding context for each generated file (invariant 4)Invoked by the top-level repair loop (see mellea-fy.md) after a Step 7 Tier 1 or structural Tier 2 failure. Distinct from a normal Step 5 invocation in three ways:
Scope: read intermediate/step_7_report.json. Generate only the files listed under failing lint entries. Pass all files with no failures through unchanged.
Additional context per failing file: inject the exact lint failure entries as a structured block before the generation context:
LINT FAILURES IN <filename> (repair round <N>):
[<lint_id>] line <L> col <C>: <message>
Cap: repair mode may be invoked at most twice (repair_round ∈ {1, 2}). If Step 7 still fails after round 2, do not generate further — return control to the top-level orchestrator, which halts and preserves .melleafy-partial/.
def _parse_instruct_result(thunk, model_class):
return model_class.model_validate_json(thunk.value)
def _safe_parse_with_fallback(thunk, model_class, **fallback_kwargs):
try:
return model_class.model_validate_json(thunk.value)
except Exception:
return model_class(**fallback_kwargs)
MUST use model_options={ModelOption.SYSTEM_PROMPT: PREFIX_TEXT} to establish persona on m.instruct() calls (KB7 — prefix= is an output prefix, not a system prompt)
SHOULD use RepairTemplateStrategy(loop_budget=LOOP_BUDGET) when requirements include structural validation_fn