feat: community edition of recruitment-consultant
Some checks failed
eval / structure (push) Has been cancelled

This commit is contained in:
skillfactor-pipeline
2026-07-10 03:37:44 +02:00
commit fda858439f
22 changed files with 2425 additions and 0 deletions

49
.gitea/workflows/eval.yml Normal file
View File

@@ -0,0 +1,49 @@
name: eval
# Structural eval: a skill package must be complete and within limits before
# it can be merged. Complements sanitize.yml (content gate).
on:
pull_request:
push:
branches: [main]
jobs:
structure:
runs-on: windows
steps:
- uses: actions/checkout@v4
- name: Validate package structure
shell: python
run: |
import json, os, sys
errors = []
REQUIRED = [
"SKILL.md",
"manifest.json",
"references/profile.md",
"references/tasks.md",
"references/skills.md",
"references/tools.md",
]
for f in REQUIRED:
if not os.path.isfile(f):
errors.append(f"missing required file: {f}")
if os.path.isfile("SKILL.md"):
lines = open("SKILL.md", encoding="utf-8").read().splitlines()
if len(lines) > 300:
errors.append(f"SKILL.md too long: {len(lines)} lines (max 300)")
if not lines or lines[0].strip() != "---":
errors.append("SKILL.md missing frontmatter")
if os.path.isfile("manifest.json"):
m = json.load(open("manifest.json", encoding="utf-8"))
for key in ("name", "version", "layer", "ids", "sources", "attribution"):
if key not in m:
errors.append(f"manifest.json missing key: {key}")
if errors:
print("EVAL FAILED:")
print("\n".join(f"- {e}" for e in errors))
sys.exit(1)
print("eval: package structure OK.")

View File

@@ -0,0 +1,53 @@
name: sanitize
# PR gate: block personal data, secrets and tenant-internal references from
# entering the shared skill library. Every layer boundary is a gate.
# Deliberately PR-only: INSIDE a private tenant repo internal details are
# legitimate; the gate guards the boundary to the shared layers.
on:
pull_request:
jobs:
pii-scan:
runs-on: windows
steps:
- uses: actions/checkout@v4
- name: Scan for PII / secrets / internal URLs
shell: python
run: |
import os, re, sys
RULES = [
("e-mail address", re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")),
("phone number", re.compile(r"(?<![\w/.-])\+?\d[\d ()\-]{8,}\d(?![\w/])")),
("secret assignment", re.compile(r"(?i)(password|passwd|secret|api[_-]?key|token)\s*[:=]\s*['\"]?[A-Za-z0-9_\-]{6,}")),
("internal URL/host", re.compile(r"(?i)(\bintranet\b|\.internal\b|\.local\b|\.corp\b|\b10\.\d{1,3}\.\d{1,3}\.\d{1,3}\b|\b192\.168\.\d{1,3}\.\d{1,3}\b)")),
("person name marker", re.compile(r"(?i)\b(hiring manager|contact):\s*[A-Z][a-z]+ [A-Z][a-z]+")),
]
ALLOW = re.compile(r"(?i)(esco\.ec\.europa\.eu|onetcenter\.org|data\.europa\.eu|creativecommons\.org)")
findings = []
for root, dirs, files in os.walk("."):
dirs[:] = [d for d in dirs if d not in (".git", ".gitea")]
for name in files:
path = os.path.join(root, name)
try:
text = open(path, encoding="utf-8", errors="ignore").read()
except OSError:
continue
for lineno, line in enumerate(text.splitlines(), 1):
if ALLOW.search(line):
continue
for label, rx in RULES:
m = rx.search(line)
if not m:
continue
# ISO-Datumsangaben (2026-07-06) sind keine Telefonnummern
if label == "phone number" and re.fullmatch(r"\d{4}-\d{2}-\d{2}", m.group(0)):
continue
findings.append(f"{path}:{lineno}: {label}: {line.strip()[:120]}")
if findings:
print("SANITIZE GATE FAILED — remove/anonymize before publishing:")
print("\n".join(findings))
sys.exit(1)
print("sanitize: clean — no PII, secrets or internal references found.")