709 lines
28 KiB
Python
709 lines
28 KiB
Python
"""Local talent-market helpers for importing and hiring agency agents."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import yaml
|
|
from yaml import YAMLError
|
|
|
|
from opc.core.config import EmployeeConfig, OPCConfig, TalentTemplateConfig
|
|
|
|
|
|
_FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n?", re.DOTALL)
|
|
|
|
_CATEGORY_DESCRIPTIONS: dict[str, str] = {
|
|
"academic": "Academic research and scholarly analysis across humanities and social science disciplines.",
|
|
"design": "Visual design, brand systems, UX thinking, and creative asset direction.",
|
|
"engineering": "Software engineering, architecture, implementation, integration, and technical delivery.",
|
|
"examples": "Reference or demo-style prompts that illustrate processes rather than specialized staffing.",
|
|
"finance": "Finance, investment research, financial modeling, valuation, accounting, tax, and portfolio analysis.",
|
|
"general": "General-purpose execution support for broad, lightweight, or fallback work across roles.",
|
|
"game-development": "Game systems, content, technical art, narrative, and interactive development.",
|
|
"marketing": "Audience growth, messaging, campaigns, content strategy, and go-to-market execution.",
|
|
"paid-media": "Performance marketing, media buying, attribution, and campaign optimization.",
|
|
"product": "Product strategy, prioritization, discovery, user insight, and roadmap decisions.",
|
|
"project-management": "Planning, delivery coordination, process management, and operational tracking.",
|
|
"sales": "Pipeline development, customer discovery, solution positioning, and deal support.",
|
|
"spatial-computing": "XR, visionOS, 3D interfaces, immersive experiences, and spatial product work.",
|
|
"specialized": "Specialized domain experts for niche industries, compliance, process design, and custom operations.",
|
|
"strategy": "High-level planning, operating models, and cross-functional execution playbooks.",
|
|
"support": "Operational support, reporting, compliance, maintenance, and service continuity.",
|
|
"testing": "QA, validation, benchmarking, audit, and evidence-based quality checks.",
|
|
}
|
|
_CATEGORY_PREFIXES = tuple(sorted(_CATEGORY_DESCRIPTIONS.keys(), key=len, reverse=True))
|
|
_IGNORED_TEMPLATE_STEMS = {
|
|
"readme",
|
|
"integration-readme",
|
|
"integrations-readme",
|
|
"pull_request_template",
|
|
"issue_template",
|
|
"contributing",
|
|
"changelog",
|
|
"license",
|
|
}
|
|
_NON_TALENT_RECURSIVE_DIRS = {"integrations", "scripts"}
|
|
|
|
|
|
def _slugify(value: str) -> str:
|
|
slug = re.sub(r"[^A-Za-z0-9._:-]+", "-", value.strip().lower()).strip("-")
|
|
return slug or "talent"
|
|
|
|
|
|
def _tokenize_text(text: str) -> list[str]:
|
|
return re.findall(r"[a-z0-9][a-z0-9+-]{2,}", text.lower())
|
|
|
|
|
|
def _extract_frontmatter(text: str) -> tuple[dict[str, Any], str]:
|
|
match = _FRONTMATTER_RE.match(text)
|
|
if not match:
|
|
return {}, text.strip()
|
|
frontmatter_text = match.group(1)
|
|
try:
|
|
frontmatter = yaml.safe_load(frontmatter_text) or {}
|
|
except YAMLError:
|
|
frontmatter = _parse_relaxed_frontmatter(frontmatter_text)
|
|
body = text[match.end():].strip()
|
|
return frontmatter, body
|
|
|
|
|
|
def _parse_relaxed_frontmatter(frontmatter_text: str) -> dict[str, Any]:
|
|
"""Parse simple frontmatter that looks YAML-like but isn't strictly valid YAML."""
|
|
parsed: dict[str, Any] = {}
|
|
active_key: str | None = None
|
|
|
|
for raw_line in frontmatter_text.splitlines():
|
|
line = raw_line.rstrip()
|
|
stripped = line.strip()
|
|
if not stripped or stripped.startswith("#"):
|
|
continue
|
|
if stripped.startswith("- ") and active_key:
|
|
current = parsed.setdefault(active_key, [])
|
|
if not isinstance(current, list):
|
|
current = [str(current)]
|
|
current.append(stripped[2:].strip())
|
|
parsed[active_key] = current
|
|
continue
|
|
if ":" not in line:
|
|
continue
|
|
key, value = line.split(":", 1)
|
|
active_key = key.strip()
|
|
parsed[active_key] = _coerce_frontmatter_value(value.strip())
|
|
|
|
return parsed
|
|
|
|
|
|
def _coerce_frontmatter_value(value: str) -> Any:
|
|
if not value:
|
|
return ""
|
|
if value.startswith(('"', "'", "[", "{")):
|
|
try:
|
|
return yaml.safe_load(value)
|
|
except YAMLError:
|
|
return value.strip().strip("\"'")
|
|
lowered = value.lower()
|
|
if lowered in {"true", "false"}:
|
|
return lowered == "true"
|
|
return value.strip().strip("\"'")
|
|
|
|
|
|
def _infer_category_from_stem(stem: str) -> str | None:
|
|
normalized = stem.strip().lower()
|
|
for category in _CATEGORY_PREFIXES:
|
|
if normalized == category or normalized.startswith(f"{category}-"):
|
|
return category
|
|
return None
|
|
|
|
|
|
def _should_ignore_template_path(path: Path) -> bool:
|
|
filename = path.name.strip().lower()
|
|
stem = path.stem.strip().lower().lstrip(".")
|
|
if filename.startswith("."):
|
|
return True
|
|
if stem in _IGNORED_TEMPLATE_STEMS:
|
|
return True
|
|
if stem.endswith("-readme") or stem.endswith("_readme"):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _has_named_frontmatter_template(path: Path) -> bool:
|
|
try:
|
|
frontmatter, _ = _extract_frontmatter(path.read_text(encoding="utf-8"))
|
|
except OSError:
|
|
return False
|
|
return bool(str(frontmatter.get("name", "")).strip())
|
|
|
|
|
|
def _looks_like_prompt_path(ref: str) -> bool:
|
|
value = str(ref or "").strip()
|
|
if not value:
|
|
return False
|
|
if "\n" in value:
|
|
return False
|
|
if len(value) > 260:
|
|
return False
|
|
if value.startswith(("You ", "Act ", "Focus ", "Review ", "Write ")):
|
|
return False
|
|
if " " in value and "/" not in value and "\\" not in value:
|
|
return False
|
|
return True
|
|
|
|
|
|
def resolve_prompt_refs(refs: list[str], opc_home: Path) -> list[str]:
|
|
resolved: list[str] = []
|
|
for ref in refs:
|
|
value = str(ref or "").strip()
|
|
if not value:
|
|
continue
|
|
if _looks_like_prompt_path(value):
|
|
try:
|
|
path = Path(value)
|
|
if not path.is_absolute():
|
|
path = opc_home / path
|
|
if path.exists() and path.is_file():
|
|
resolved.append(path.read_text(encoding="utf-8").strip())
|
|
continue
|
|
except OSError:
|
|
pass
|
|
resolved.append(value)
|
|
return [item for item in resolved if item]
|
|
|
|
|
|
def _is_placeholder_employee(employee: EmployeeConfig) -> bool:
|
|
metadata = dict(employee.metadata or {})
|
|
return bool(metadata.get("is_default_employee") or metadata.get("is_fallback_employee"))
|
|
|
|
|
|
class HireError(Exception):
|
|
"""Base class for recoverable failures in TalentMarket.hire_template."""
|
|
|
|
|
|
class RoleAlreadyHiredError(HireError):
|
|
"""Raised when a role already has a non-placeholder employee."""
|
|
|
|
def __init__(self, role_id: str, existing_employee_id: str) -> None:
|
|
self.role_id = role_id
|
|
self.existing_employee_id = existing_employee_id
|
|
super().__init__(
|
|
f"Role '{role_id}' already has employee '{existing_employee_id}'.",
|
|
)
|
|
|
|
|
|
class TalentMarket:
|
|
"""Imports talent templates from local markdown files and hires employees."""
|
|
|
|
def __init__(self, opc_home: Path, config: OPCConfig) -> None:
|
|
self.opc_home = opc_home
|
|
self.config = config
|
|
|
|
def list_templates(self) -> list[TalentTemplateConfig]:
|
|
return self.list_available_templates()
|
|
|
|
def list_employees(self) -> list[EmployeeConfig]:
|
|
return sorted(self.config.org.employees, key=lambda item: (item.role_id, item.name.lower()))
|
|
|
|
def get_template(self, template_id: str) -> TalentTemplateConfig | None:
|
|
normalized = str(template_id or "").strip()
|
|
if not normalized:
|
|
return None
|
|
return next((item for item in self.list_available_templates() if item.id == normalized), None)
|
|
|
|
def list_available_templates(self) -> list[TalentTemplateConfig]:
|
|
"""Return every template the talent market can hire from the talent catalog."""
|
|
templates_by_id: dict[str, TalentTemplateConfig] = {}
|
|
for template in self.scan_local_talent():
|
|
templates_by_id[template.id] = template
|
|
try:
|
|
from opc.market.talent_presets import get_all_talent_presets
|
|
|
|
for raw in get_all_talent_presets():
|
|
template = self._template_from_preset(raw)
|
|
templates_by_id.setdefault(template.id, template)
|
|
except Exception:
|
|
pass
|
|
return sorted(templates_by_id.values(), key=lambda item: (item.category, item.name.lower()))
|
|
|
|
def ensure_template_available(self, template_id: str) -> TalentTemplateConfig | None:
|
|
"""Resolve a template from the talent catalog without mutating org config."""
|
|
normalized = str(template_id or "").strip()
|
|
if not normalized:
|
|
return None
|
|
return self.get_template(normalized)
|
|
|
|
def build_employee_id(self, *, role_id: str, template_id: str) -> str:
|
|
_ = role_id
|
|
return str(template_id or "").strip()
|
|
|
|
@staticmethod
|
|
def _attach_employee_to_role(employee: EmployeeConfig, role_id: str) -> EmployeeConfig:
|
|
normalized_role_id = str(role_id or "").strip()
|
|
if not normalized_role_id:
|
|
return employee
|
|
metadata = dict(employee.metadata or {})
|
|
for key in ("home_role_ids", "staffed_role_ids"):
|
|
values = [
|
|
str(item).strip()
|
|
for item in list(metadata.get(key, []) or [])
|
|
if str(item).strip()
|
|
]
|
|
if normalized_role_id not in values:
|
|
values.append(normalized_role_id)
|
|
metadata[key] = values
|
|
metadata.setdefault("home_role_id", str(employee.role_id or normalized_role_id).strip() or normalized_role_id)
|
|
employee.metadata = metadata
|
|
return employee
|
|
|
|
def _remove_placeholders_for_role(self, role_id: str) -> None:
|
|
normalized_role_id = str(role_id or "").strip()
|
|
if not normalized_role_id:
|
|
return
|
|
self.config.org.employees = [
|
|
item
|
|
for item in self.config.org.employees
|
|
if not (item.role_id == normalized_role_id and _is_placeholder_employee(item))
|
|
]
|
|
|
|
def build_candidate_summary(self, template: TalentTemplateConfig) -> dict[str, Any]:
|
|
return {
|
|
"template_id": template.id,
|
|
"name": template.name,
|
|
"description": template.description,
|
|
"category": template.category,
|
|
"category_description": self.describe_category(template.category),
|
|
}
|
|
|
|
def describe_category(self, category: str) -> str:
|
|
normalized = (category or "").strip().lower()
|
|
if normalized in _CATEGORY_DESCRIPTIONS:
|
|
return _CATEGORY_DESCRIPTIONS[normalized]
|
|
return f"{normalized.replace('-', ' ').strip().title()} related talent and execution support."
|
|
|
|
def list_category_catalog(self) -> list[dict[str, Any]]:
|
|
counts: dict[str, int] = {}
|
|
for template in self.list_available_templates():
|
|
category = (template.category or "general").strip().lower() or "general"
|
|
counts[category] = counts.get(category, 0) + 1
|
|
catalog = [
|
|
{
|
|
"category": category,
|
|
"description": self.describe_category(category),
|
|
"template_count": count,
|
|
}
|
|
for category, count in counts.items()
|
|
]
|
|
return sorted(catalog, key=lambda item: (item["category"] != "engineering", item["category"]))
|
|
|
|
def list_templates_by_categories(self, *, categories: list[str]) -> list[TalentTemplateConfig]:
|
|
selected = {(category or "").strip().lower() for category in categories if str(category).strip()}
|
|
if not selected:
|
|
return []
|
|
return [
|
|
template for template in self.list_available_templates()
|
|
if (template.category or "general").strip().lower() in selected
|
|
]
|
|
|
|
def shortlist_templates_by_categories(
|
|
self,
|
|
*,
|
|
categories: list[str],
|
|
role_descriptions: list[str],
|
|
limit: int = 6,
|
|
) -> list[TalentTemplateConfig]:
|
|
selected = {(category or "").strip().lower() for category in categories if str(category).strip()}
|
|
templates = [
|
|
template for template in self.list_available_templates()
|
|
if not selected or (template.category or "general").strip().lower() in selected
|
|
]
|
|
if not templates:
|
|
return []
|
|
need_tokens = set(_tokenize_text(" ".join(role_descriptions)))
|
|
scored: list[tuple[float, TalentTemplateConfig]] = []
|
|
for template in templates:
|
|
template_tokens = set(_tokenize_text(f"{template.name} {template.description}"))
|
|
score = float(len(need_tokens & template_tokens))
|
|
if template.description:
|
|
score += 0.25
|
|
scored.append((score, template))
|
|
scored.sort(key=lambda item: (-item[0], item[1].name.lower()))
|
|
return [template for _, template in scored[:limit]]
|
|
|
|
def search_templates_for_need(
|
|
self,
|
|
*,
|
|
role_id: str,
|
|
domains: list[str],
|
|
role_descriptions: list[str],
|
|
limit: int = 5,
|
|
) -> list[TalentTemplateConfig]:
|
|
scored: list[tuple[float, TalentTemplateConfig]] = []
|
|
role_text = " ".join(role_descriptions).lower()
|
|
need_tokens = {
|
|
role_id.lower(),
|
|
*[domain.lower() for domain in domains],
|
|
*re.findall(r"[a-z0-9][a-z0-9+-]{2,}", role_text),
|
|
}
|
|
for template in self.list_available_templates():
|
|
template_tokens = {
|
|
template.category.lower(),
|
|
template.name.lower(),
|
|
*[domain.lower() for domain in template.domains],
|
|
*[tag.lower() for tag in template.tags],
|
|
*re.findall(r"[a-z0-9][a-z0-9+-]{2,}", template.description.lower()),
|
|
}
|
|
score = 0.0
|
|
if role_id.lower() in template_tokens:
|
|
score += 6.0
|
|
score += float(len(need_tokens & template_tokens))
|
|
if template.preferred_external_agent:
|
|
score += 0.5
|
|
if score > 0:
|
|
scored.append((score, template))
|
|
scored.sort(key=lambda item: (-item[0], item[1].name.lower()))
|
|
return [template for _, template in scored[:limit]]
|
|
|
|
def import_from_repo(self, repo_path: Path) -> list[TalentTemplateConfig]:
|
|
repo_root = repo_path.expanduser().resolve()
|
|
imported: list[TalentTemplateConfig] = []
|
|
for category_dir in sorted(repo_root.iterdir()):
|
|
if not category_dir.is_dir():
|
|
continue
|
|
for markdown_path in self._iter_repo_template_paths(category_dir):
|
|
template = self._parse_template(markdown_path, repo_root)
|
|
if template is None:
|
|
continue
|
|
self._write_prompt(template.id, markdown_path)
|
|
imported.append(template)
|
|
|
|
unique = {template.id: template for template in imported if template.id}
|
|
return sorted(unique.values(), key=lambda item: (item.category, item.name.lower()))
|
|
|
|
def _iter_repo_template_paths(self, category_dir: Path) -> list[Path]:
|
|
top_level = category_dir.name.strip().lower()
|
|
paths: list[Path] = []
|
|
for markdown_path in sorted(category_dir.rglob("*.md")):
|
|
if _should_ignore_template_path(markdown_path):
|
|
continue
|
|
if markdown_path.parent == category_dir:
|
|
paths.append(markdown_path)
|
|
continue
|
|
if top_level in _NON_TALENT_RECURSIVE_DIRS:
|
|
continue
|
|
if _has_named_frontmatter_template(markdown_path):
|
|
paths.append(markdown_path)
|
|
return paths
|
|
|
|
def _resolve_template(self, template_id: str) -> TalentTemplateConfig | None:
|
|
return self.get_template(template_id)
|
|
|
|
def hire_template(
|
|
self,
|
|
template_id: str,
|
|
role_id: str,
|
|
*,
|
|
employee_name: str | None = None,
|
|
employee_id: str | None = None,
|
|
) -> EmployeeConfig:
|
|
template = self.ensure_template_available(template_id)
|
|
if template is None:
|
|
raise ValueError(f"Unknown talent template `{template_id}`.")
|
|
|
|
employee_name = (employee_name or template.name).strip()
|
|
if not employee_name:
|
|
raise ValueError("Employee name cannot be empty.")
|
|
|
|
displaced_ids = {
|
|
item.employee_id
|
|
for item in self.config.org.employees
|
|
if item.role_id == role_id and _is_placeholder_employee(item)
|
|
}
|
|
chosen_id = self.build_employee_id(role_id=role_id, template_id=template.id)
|
|
if any(
|
|
item.employee_id == chosen_id and item.employee_id not in displaced_ids
|
|
for item in self.config.org.employees
|
|
):
|
|
existing = next(item for item in self.config.org.employees if item.employee_id == chosen_id)
|
|
if not _is_placeholder_employee(existing):
|
|
self._attach_employee_to_role(existing, role_id)
|
|
self._remove_placeholders_for_role(role_id)
|
|
return existing
|
|
raise ValueError(f"Employee `{chosen_id}` already exists.")
|
|
|
|
new_employee = EmployeeConfig(
|
|
employee_id=chosen_id,
|
|
template_id=template.id,
|
|
name=employee_name,
|
|
role_id=role_id,
|
|
description=template.description,
|
|
category=template.category,
|
|
domains=[],
|
|
tags=list(template.tags),
|
|
prompt_refs=[template.prompt_ref] if template.prompt_ref else [],
|
|
skill_refs=[],
|
|
preferred_external_agent=template.preferred_external_agent,
|
|
metadata={
|
|
"source_repo": template.source_repo,
|
|
"source_path": template.source_path,
|
|
"source_revision": template.source_revision,
|
|
"talent_template_name": template.name,
|
|
"talent_template_category": template.category,
|
|
"home_role_id": role_id,
|
|
"home_role_ids": [role_id],
|
|
"staffed_role_ids": [role_id],
|
|
},
|
|
)
|
|
self.config.org.employees = [
|
|
item for item in self.config.org.employees if item.employee_id not in displaced_ids
|
|
] + [
|
|
new_employee,
|
|
]
|
|
return new_employee
|
|
|
|
def ensure_hire_template(
|
|
self,
|
|
template_id: str,
|
|
role_id: str,
|
|
*,
|
|
employee_name: str | None = None,
|
|
employee_id: str | None = None,
|
|
) -> EmployeeConfig:
|
|
template = self.ensure_template_available(template_id)
|
|
if template is None:
|
|
raise ValueError(f"Unknown talent template `{template_id}`.")
|
|
_ = employee_id
|
|
desired_id = self.build_employee_id(role_id=role_id, template_id=template.id)
|
|
existing = next((item for item in self.config.org.employees if item.employee_id == desired_id), None)
|
|
if existing and not _is_placeholder_employee(existing):
|
|
self._attach_employee_to_role(existing, role_id)
|
|
self._remove_placeholders_for_role(role_id)
|
|
return existing
|
|
|
|
resolved_name = (employee_name or template.name).strip()
|
|
if not resolved_name:
|
|
raise ValueError("Employee name cannot be empty.")
|
|
|
|
displaced_ids = {
|
|
item.employee_id
|
|
for item in self.config.org.employees
|
|
if item.role_id == role_id
|
|
and _is_placeholder_employee(item)
|
|
}
|
|
if existing is not None:
|
|
displaced_ids.add(existing.employee_id)
|
|
|
|
new_employee = EmployeeConfig(
|
|
employee_id=desired_id,
|
|
template_id=template.id,
|
|
name=resolved_name,
|
|
role_id=role_id,
|
|
description=template.description,
|
|
category=template.category,
|
|
domains=[],
|
|
tags=list(template.tags),
|
|
prompt_refs=[template.prompt_ref] if template.prompt_ref else [],
|
|
skill_refs=[],
|
|
preferred_external_agent=template.preferred_external_agent,
|
|
metadata={
|
|
"source_repo": template.source_repo,
|
|
"source_path": template.source_path,
|
|
"source_revision": template.source_revision,
|
|
"talent_template_name": template.name,
|
|
"talent_template_category": template.category,
|
|
"home_role_id": role_id,
|
|
"home_role_ids": [role_id],
|
|
"staffed_role_ids": [role_id],
|
|
},
|
|
)
|
|
self.config.org.employees = [
|
|
item for item in self.config.org.employees if item.employee_id not in displaced_ids
|
|
] + [
|
|
new_employee,
|
|
]
|
|
return new_employee
|
|
|
|
def scan_local_talent(self) -> list[TalentTemplateConfig]:
|
|
"""Scan ``prompts/talent/*.md`` and return the local talent catalog."""
|
|
talent_dir = self.opc_home / "prompts" / "talent"
|
|
if not talent_dir.is_dir():
|
|
return []
|
|
found: dict[str, TalentTemplateConfig] = {}
|
|
for md_path in sorted(talent_dir.glob("*.md")):
|
|
tpl = self._parse_template(md_path, talent_dir)
|
|
if tpl:
|
|
found[tpl.id] = tpl
|
|
return sorted(found.values(), key=lambda item: (item.category, item.name.lower()))
|
|
|
|
def _template_from_preset(self, data: dict[str, Any]) -> TalentTemplateConfig:
|
|
template_id = str(data.get("id") or data.get("template_id") or "").strip()
|
|
name = str(data.get("name") or template_id).strip() or template_id
|
|
return TalentTemplateConfig(
|
|
id=template_id,
|
|
name=name,
|
|
description=str(data.get("description", "") or ""),
|
|
category=str(data.get("category", "") or ""),
|
|
domains=[
|
|
str(item).strip()
|
|
for item in list(data.get("domains", []) or [])
|
|
if str(item).strip()
|
|
],
|
|
tags=[
|
|
str(item).strip()
|
|
for item in list(data.get("tags", []) or [])
|
|
if str(item).strip()
|
|
],
|
|
prompt_ref=str(data.get("prompt_ref", "") or ""),
|
|
preferred_external_agent=data.get("preferred_external_agent"),
|
|
source_repo="builtin",
|
|
source_path=str(data.get("source_path", "") or ""),
|
|
source_revision=str(data.get("source_revision", "") or "builtin"),
|
|
metadata={
|
|
key: value
|
|
for key, value in data.items()
|
|
if key not in {"id", "template_id", "name", "description", "category", "domains", "tags", "prompt_ref", "preferred_external_agent", "source_path", "source_revision"}
|
|
},
|
|
)
|
|
|
|
def import_local_templates(self, template_ids: list[str]) -> list[TalentTemplateConfig]:
|
|
"""Resolve selected templates from the local talent directory."""
|
|
available = {t.id: t for t in self.scan_local_talent()}
|
|
imported: list[TalentTemplateConfig] = []
|
|
for tid in template_ids:
|
|
tpl = available.get(tid)
|
|
if tpl:
|
|
imported.append(tpl)
|
|
return sorted(imported, key=lambda t: (t.category, t.name.lower()))
|
|
|
|
def _parse_template(self, path: Path, repo_root: Path) -> TalentTemplateConfig | None:
|
|
if _should_ignore_template_path(path):
|
|
return None
|
|
text = path.read_text(encoding="utf-8")
|
|
frontmatter, body = _extract_frontmatter(text)
|
|
# If no frontmatter, derive name from first H1 heading or filename
|
|
if not frontmatter or not frontmatter.get("name"):
|
|
h1_match = re.match(r"^#\s+(.+)", body or text, re.MULTILINE)
|
|
derived_name = h1_match.group(1).strip() if h1_match else path.stem.replace("-", " ").title()
|
|
if not derived_name:
|
|
return None
|
|
frontmatter = dict(frontmatter) if frontmatter else {}
|
|
frontmatter["name"] = derived_name
|
|
category = self._resolve_template_category(path, repo_root, frontmatter)
|
|
rel_path = str(path.relative_to(repo_root))
|
|
name = str(frontmatter.get("name", path.stem)).strip() or path.stem
|
|
description = str(frontmatter.get("description", "")).strip()
|
|
template_id = self._build_template_id(path, repo_root, frontmatter, category)
|
|
domains = [
|
|
str(item).strip()
|
|
for item in list(frontmatter.get("domains", []) or [])
|
|
if str(item).strip()
|
|
]
|
|
raw_tags = [
|
|
str(item).strip()
|
|
for item in list(frontmatter.get("tags", []) or [])
|
|
if str(item).strip()
|
|
]
|
|
tags = raw_tags or self._infer_tags(category, name, rel_path)
|
|
if repo_root.name.strip().lower() == "talent" and path.parent == repo_root:
|
|
prompt_ref = f"prompts/talent/{path.name}"
|
|
else:
|
|
prompt_ref = f"prompts/talent/{template_id}.md"
|
|
preferred_external_agent = self._infer_preferred_external_agent(body, category)
|
|
metadata = {
|
|
key: value
|
|
for key, value in frontmatter.items()
|
|
if key not in {"id", "name", "description", "domains", "tags", "category"}
|
|
}
|
|
return TalentTemplateConfig(
|
|
id=template_id,
|
|
name=name,
|
|
description=description,
|
|
category=category,
|
|
domains=domains,
|
|
tags=tags,
|
|
prompt_ref=prompt_ref,
|
|
preferred_external_agent=preferred_external_agent,
|
|
source_repo=str(repo_root),
|
|
source_path=rel_path,
|
|
source_revision="local",
|
|
metadata=metadata,
|
|
)
|
|
|
|
def _resolve_template_category(self, path: Path, repo_root: Path, frontmatter: dict[str, Any]) -> str:
|
|
explicit = str(frontmatter.get("category", "")).strip().lower()
|
|
if explicit:
|
|
return explicit
|
|
|
|
parent_category = path.parent.name.strip().lower()
|
|
repo_root_name = repo_root.name.strip().lower()
|
|
try:
|
|
first_part = path.relative_to(repo_root).parts[0].strip().lower()
|
|
except (IndexError, ValueError):
|
|
first_part = ""
|
|
if first_part in _CATEGORY_DESCRIPTIONS:
|
|
return first_part
|
|
if parent_category and parent_category != repo_root_name:
|
|
return parent_category
|
|
|
|
inferred = _infer_category_from_stem(path.stem)
|
|
if inferred:
|
|
return inferred
|
|
|
|
if repo_root_name == "talent":
|
|
return "general"
|
|
return parent_category or "general"
|
|
|
|
def _build_template_id(
|
|
self,
|
|
path: Path,
|
|
repo_root: Path,
|
|
frontmatter: dict[str, Any],
|
|
category: str,
|
|
) -> str:
|
|
explicit = frontmatter.get("id")
|
|
if explicit:
|
|
return _slugify(str(explicit))
|
|
|
|
if path.parent.name.strip().lower() == repo_root.name.strip().lower():
|
|
inferred = _infer_category_from_stem(path.stem)
|
|
if inferred == category:
|
|
return _slugify(path.stem)
|
|
|
|
return _slugify(f"{category}-{path.stem}")
|
|
|
|
def _write_prompt(self, template_id: str, source_path: Path) -> None:
|
|
text = source_path.read_text(encoding="utf-8")
|
|
prompt_dir = self.opc_home / "prompts" / "talent"
|
|
prompt_dir.mkdir(parents=True, exist_ok=True)
|
|
prompt_path = prompt_dir / f"{template_id}.md"
|
|
prompt_path.write_text(text.rstrip() + "\n", encoding="utf-8")
|
|
|
|
def _infer_domains(
|
|
self,
|
|
category: str,
|
|
name: str,
|
|
description: str,
|
|
body: str,
|
|
rel_path: str,
|
|
) -> list[str]:
|
|
_ = (category, name, description, body, rel_path)
|
|
return []
|
|
|
|
def _infer_tags(self, category: str, name: str, rel_path: str) -> list[str]:
|
|
parts = [category.lower(), *re.findall(r"[a-z0-9][a-z0-9+-]{2,}", f"{name} {rel_path}".lower())]
|
|
tags: list[str] = []
|
|
for part in parts:
|
|
if part not in tags:
|
|
tags.append(part)
|
|
return tags[:10]
|
|
|
|
def _infer_preferred_external_agent(self, body: str, category: str) -> str | None:
|
|
text = f"{category}\n{body}".lower()
|
|
if "opencode" in text or "open code" in text:
|
|
return "opencode"
|
|
if any(token in text for token in ("code", "engineering", "terminal", "implementation", "repository")):
|
|
return "codex"
|
|
if any(token in text for token in ("infrastructure", "deployment", "operations", "visionos", "cursor")):
|
|
return "cursor"
|
|
return None
|