feat(build): user-agnostic personalized variant routing

Personalized variants (default trigger: salva.md) compile to a separate
top-level folder so generated/<persona>/ stays clean of per-user content.
Both the trigger variant filename(s) and the output folder name are
config-driven via config.user — the build is truly user-agnostic.

Build changes:
- Module-level _RUNTIME dict cached by configure_runtime(config) at startup
- resolve_personalized_variants(config) — config.user.personalized_variants
- resolve_personalized_output_dirname(config) — slug-derives from
  user.name (e.g. "Salva" → "salva-personas") with explicit override key
- resolve_personalized_source_dirname(config) — defaults to literal
  "personalized" (gitignorable, user-name-independent)
- iter_persona_output_dirs() — single helper used by all install_*()
  functions to transparently iterate both layouts
- build_persona() routes salva variants based on _RUNTIME at write time
- build_skills_index() also scans <repo>/personalized/skills/ so personal
  skills (with user-private data) merge into the shared skill index
- main() picks up persona dirs from BOTH personas/ and personalized/

New config keys (all optional):
- user.personalized_variants
- user.personalized_output_folder
- user.personalized_source_folder

Output layout (e.g. for user.name="Salva"):
- generated/<persona>/general.{json,yaml,prompt.md}     (shared)
- generated/<persona>/<spec>.{json,yaml,prompt.md}      (shared)
- generated/salva-personas/<persona>/salva.{json,yaml,prompt.md}  (private)

Source layout:
- personas/<persona>/general.md, <spec>.md              (committed)
- personalized/<persona>/salva.md                       (gitignored)
- personalized/skills/<skill>/SKILL.md                  (gitignored)
- personalized/_user_context.md                         (gitignored)

Adds /personalized/ to .gitignore. Documents the new layout in CLAUDE.md
and README.md. Maps linkedin-content-strategy → herald/forge/frodo/ghost
in the default skill persona map.
This commit is contained in:
salvacybersec
2026-05-07 16:49:01 +03:00
parent 1161e69f51
commit cbb28903fb
6 changed files with 272 additions and 53 deletions

6
.gitignore vendored
View File

@@ -10,3 +10,9 @@ __pycache__/
/agents-claude-archive/ /agents-claude-archive/
/skills-feynman-archive/ /skills-feynman-archive/
/agents-feynman-archive/ /agents-feynman-archive/
# User-personalized variant source — gitignored so the shared persona library
# stays user-agnostic. Each user keeps their own salva.md (or whatever
# variant name they configure) and _user_context.md locally. Override the
# folder name via config.user.personalized_source_folder.
/personalized/

View File

@@ -52,6 +52,13 @@ Optional: `cp config.example.yaml config.yaml` for dynamic variable injection. B
- `trigger_index.json` — keyword→persona routing for multi-agent auto-switching - `trigger_index.json` — keyword→persona routing for multi-agent auto-switching
- `skills_index.json` — all shared skills mapped to personas with metadata - `skills_index.json` — all shared skills mapped to personas with metadata
**Output layout — personalized variant routing**: User-personalized variants (default trigger: `salva.md`) are written to a separate top-level folder under `generated/`, keeping `generated/<persona>/` free of per-user content:
- `generated/<persona>/general.*` + `generated/<persona>/<specialization>.*` — generic & domain variants
- `generated/<user-slug>-personas/<persona>/salva.*` — personalized variants (user-slug = slugified `config.user.name`, e.g. `salva-personas`, `alice-smith-personas`)
The build is **user-agnostic**: both the trigger variant filename(s) and the output folder name are config-driven. See `config.example.yaml` `user.personalized_variants` and `user.personalized_output_folder`. Defaults preserve the existing `salva.md` convention with auto-derived folder name. All install_*() functions iterate via `iter_persona_output_dirs(output_dir)` which yields persona dirs from BOTH locations transparently.
**Skill injection**: Build auto-maps skills from `_shared/skills/` to personas via domain mapping. Each persona's JSON/YAML output includes a `skills` array listing applicable shared skills. **Skill injection**: Build auto-maps skills from `_shared/skills/` to personas via domain mapping. Each persona's JSON/YAML output includes a `skills` array listing applicable shared skills.
## Install to Platforms ## Install to Platforms
@@ -77,3 +84,15 @@ python3 build.py --install all # all platforms at once
- Section headers use `## ` (H2) — the build parser splits on these - Section headers use `## ` (H2) — the build parser splits on these
- Turkish honorific titles ("Hitap") are used for `address_to` fields - Turkish honorific titles ("Hitap") are used for `address_to` fields
- `config.yaml` must never be committed (contains personal infrastructure details) - `config.yaml` must never be committed (contains personal infrastructure details)
## Scope Boundaries (don't touch)
- **`personas/_shared/paperclip-agents/`** — Belongs to a separate Paperclip company project (Odin, Thor, Freya, Loki, Idunn, etc.). Do NOT edit Paperclip agent SOUL/AGENTS/hermes-config files when working on persona changes. Mirrored here for build-pipeline integration only.
- **`personas/_shared/paperclip-skills/`** — Same: separate-project artifacts. New skills for our personas go in `personas/_shared/skills/` (not `paperclip-skills/`).
- **`personas/_shared/community-skills/`** — Vendor-synced from skills.sh marketplace. Do NOT modify in place — patches will be lost on resync. Add domain-specific guidance as a new skill in `_shared/skills/` instead.
- **`personas/_shared/feynman-skills/`, `agents-*-archive/`, `skills-*-archive/`** — Archived/imported from upstream Feynman project. Read-only reference unless explicitly working on Feynman integration.
For user-personalized changes, the canonical surfaces are:
1. `personas/_user_context.md` — shared user context for all `salva.md` variants
2. `personas/<codename>/salva.md` — per-persona user-personalized variant
3. `personas/_shared/skills/<skill-name>/SKILL.md` — new shared skills (persona-mappable, not vendor-synced)

View File

@@ -222,11 +222,14 @@ sources/
### Variant Types ### Variant Types
| Type | Purpose | Example | | Type | Purpose | Example | Output location |
|------|---------|---------| |------|---------|---------|-----------------|
| `general.md` | Base persona — works standalone for any user | `neo/general.md` | | `general.md` | Base persona — works standalone for any user | `neo/general.md` | `generated/<persona>/` |
| `<specialization>.md` | Domain deep-dive narrowing the persona's focus | `neo/redteam.md` | | `<specialization>.md` | Domain deep-dive narrowing the persona's focus | `neo/redteam.md` | `generated/<persona>/` |
| `salva.md` | User-personalized — references specific projects, data, tools | `neo/salva.md` | | `salva.md` (personalized) | User-personalized — references specific projects, data, tools | `neo/salva.md` | `generated/<user-slug>-personas/<persona>/` |
**Personalized variant routing (config-driven, user-agnostic):**
The build keeps user-specific output in a separate top-level folder so the main `generated/<persona>/` directories stay clean. The folder name is derived from `config.user.name` (slugified, `-personas` suffix) — e.g. `user.name: "Salva"``generated/salva-personas/<persona>/salva.*`. Both the trigger variant filename(s) and the output folder name are overridable via `config.user.personalized_variants` / `config.user.personalized_output_folder`. Defaults preserve the historical `salva.md` convention.
## Prompt Format ## Prompt Format

226
build.py
View File

@@ -185,6 +185,121 @@ def parse_persona_md(filepath: Path, flat_config: dict) -> dict:
} }
# User-personalized variants get routed to a separate top-level folder so
# generated/<persona>/ stays clean of per-user content. Both the variant
# filename(s) treated as personalized AND the output folder name are
# config-driven via config.user — the build is user-agnostic.
#
# Defaults preserve the historical convention (variant `salva.md`) but the
# output folder is derived from `config.user.name` (slugified, "-personas"
# suffix). Override either by setting config.user.personalized_variants and/or
# config.user.personalized_output_folder.
PERSONALIZED_VARIANT_DEFAULT = "salva"
PERSONALIZED_OUTPUT_FALLBACK = "personalized-personas"
PERSONALIZED_SOURCE_DEFAULT = "personalized"
# Set by main() from config; helpers and install_* read this at runtime.
_RUNTIME: dict = {
"personalized_variants": {PERSONALIZED_VARIANT_DEFAULT},
"personalized_dirname": PERSONALIZED_OUTPUT_FALLBACK,
"personalized_source_dirname": PERSONALIZED_SOURCE_DEFAULT,
}
def resolve_personalized_variants(config: dict) -> set[str]:
"""Variant filenames (without .md) treated as user-personalized.
Reads config.user.personalized_variants. Accepts a string or list.
Falls back to {"salva"} for backward-compat with the established
`<persona>/salva.md` convention.
"""
raw = (config.get("user") or {}).get("personalized_variants")
if raw is None or raw == "":
return {PERSONALIZED_VARIANT_DEFAULT}
if isinstance(raw, str):
return {raw}
items = {str(v).strip() for v in raw if str(v).strip()}
return items or {PERSONALIZED_VARIANT_DEFAULT}
def resolve_personalized_output_dirname(config: dict) -> str:
"""Top-level folder name for personalized variants under output_dir.
Priority:
1. config.user.personalized_output_folder (explicit override)
2. slugify(config.user.name) + "-personas" (e.g. "salva""salva-personas")
3. PERSONALIZED_OUTPUT_FALLBACK ("personalized-personas")
"""
user = config.get("user") or {}
explicit = user.get("personalized_output_folder")
if explicit:
return str(explicit).strip() or PERSONALIZED_OUTPUT_FALLBACK
name = (user.get("name") or "").strip().lower()
if name and name not in ("your name", "default"):
slug = re.sub(r"[^a-z0-9]+", "-", name).strip("-")
if slug:
return f"{slug}-personas"
return PERSONALIZED_OUTPUT_FALLBACK
def resolve_personalized_source_dirname(config: dict) -> str:
"""Source dir (under repo root) holding user-personalized .md variants.
Layout: <root>/<personalized_source>/<codename>/<variant>.md
The shared persona library stays in personas/<codename>/. Personalized
.md files live in this separate root so the public/shared repo can
gitignore the user-specific source content.
Default: "personalized" (literal — gitignorable, doesn't depend on user
name). Override via config.user.personalized_source_folder.
"""
user = config.get("user") or {}
explicit = user.get("personalized_source_folder")
if explicit:
return str(explicit).strip() or PERSONALIZED_SOURCE_DEFAULT
return PERSONALIZED_SOURCE_DEFAULT
def configure_runtime(config: dict) -> None:
"""Cache personalization settings in module state for helpers/installers.
Called once from main() after config is loaded. Helpers like
iter_persona_output_dirs() and build_persona() read _RUNTIME without
needing the config threaded through every signature.
"""
_RUNTIME["personalized_variants"] = resolve_personalized_variants(config)
_RUNTIME["personalized_dirname"] = resolve_personalized_output_dirname(config)
_RUNTIME["personalized_source_dirname"] = resolve_personalized_source_dirname(config)
def iter_persona_output_dirs(output_dir: Path):
"""Yield every persona output directory, from both layouts.
Iterates:
1. output_dir/<codename>/ — general + non-personalized
2. output_dir/<personalized_dirname>/<codename>/ — user-personalized variants
The personalized_dirname is read from _RUNTIME (set by configure_runtime()).
Skips dotfiles, underscore-prefixed special dirs, and the personalized
container itself in pass 1.
"""
personalized_dirname = _RUNTIME["personalized_dirname"]
skip_names = {personalized_dirname}
for persona_dir in sorted(output_dir.iterdir()):
if not persona_dir.is_dir():
continue
if persona_dir.name.startswith(("_", ".")) or persona_dir.name in skip_names:
continue
yield persona_dir
personalized_root = output_dir / personalized_dirname
if personalized_root.exists():
for persona_dir in sorted(personalized_root.iterdir()):
if not persona_dir.is_dir() or persona_dir.name.startswith(("_", ".")):
continue
yield persona_dir
def build_persona( def build_persona(
persona_dir: Path, persona_dir: Path,
output_dir: Path, output_dir: Path,
@@ -199,8 +314,6 @@ def build_persona(
return 0 return 0
persona_name = persona_dir.name persona_name = persona_dir.name
out_path = output_dir / persona_name
out_path.mkdir(parents=True, exist_ok=True)
# Load _meta.yaml if exists # Load _meta.yaml if exists
meta_file = persona_dir / "_meta.yaml" meta_file = persona_dir / "_meta.yaml"
@@ -226,6 +339,14 @@ def build_persona(
if not parsed: if not parsed:
continue continue
# Per-variant output path: personalized variants land in a separate
# top-level folder (config.user-driven; see resolve_* helpers above).
if variant in _RUNTIME["personalized_variants"]:
out_path = output_dir / _RUNTIME["personalized_dirname"] / persona_name
else:
out_path = output_dir / persona_name
out_path.mkdir(parents=True, exist_ok=True)
# Build output object # Build output object
output = { output = {
**meta, **meta,
@@ -372,6 +493,7 @@ DEFAULT_SKILL_PERSONA_MAP = {
"pentest-reporter": ["neo", "phantom", "bastion"], "pentest-reporter": ["neo", "phantom", "bastion"],
# Marketing / business → personas # Marketing / business → personas
"marketing-strategist": ["herald", "ledger"], "marketing-strategist": ["herald", "ledger"],
"linkedin-content-strategy": ["herald", "forge", "frodo", "ghost"],
# Document processing → personas # Document processing → personas
"image-ocr": ["oracle", "scribe"], "image-ocr": ["oracle", "scribe"],
"mistral-ocr": ["oracle", "scribe"], "mistral-ocr": ["oracle", "scribe"],
@@ -751,6 +873,53 @@ def build_skills_index(shared_dir: Path, config: dict = None) -> dict:
"has_references": (skill_dir / "references").is_dir(), "has_references": (skill_dir / "references").is_dir(),
} }
# Also index user-personalized skills from <repo_root>/<personalized_source>/skills/
# if present. Merged into the same "skills" bucket so persona JSON output
# includes them just like shared skills. Personalized skills typically
# contain user-private data (analytics, account-specific playbooks) and
# live in a gitignored folder so the public repo stays user-agnostic.
# shared_dir is personas/_shared, so repo_root is shared_dir.parent.parent.
repo_root = shared_dir.parent.parent
personalized_root = repo_root / _RUNTIME["personalized_source_dirname"]
personalized_skills_dir = personalized_root / "skills"
if personalized_skills_dir.exists() and personalized_skills_dir.is_dir():
for skill_dir in sorted(personalized_skills_dir.iterdir()):
if not skill_dir.is_dir():
continue
skill_md = skill_dir / "SKILL.md"
if not skill_md.exists():
continue
skill_meta = parse_skill_frontmatter(skill_md)
inferred_personas = infer_personas_from_skill_metadata(
skill_dir.name, skill_meta
)
configured_personas = skill_map.get(skill_dir.name, [])
merged_personas = sorted(
set(configured_personas).union(inferred_personas)
)
content = skill_md.read_text(encoding="utf-8")
first_line = ""
for line in content.split("\n"):
line = line.strip()
if line and not line.startswith(
("---", "#", "name:", "description:")
):
first_line = line[:120]
break
index["skills"][skill_dir.name] = {
"personas": merged_personas,
"summary": first_line,
"domain": str(skill_meta.get("domain", "")),
"subdomain": str(skill_meta.get("subdomain", "")),
"tags": skill_meta.get("tags", []),
"source": "personalized",
"mapped_by": {
"explicit": configured_personas,
"inferred": inferred_personas,
},
"has_references": (skill_dir / "references").is_dir(),
}
# Index paperclip-skills # Index paperclip-skills
pskills_dir = shared_dir / "paperclip-skills" pskills_dir = shared_dir / "paperclip-skills"
if pskills_dir.exists(): if pskills_dir.exists():
@@ -1155,9 +1324,7 @@ def install_claude(output_dir: Path):
stale.unlink() stale.unlink()
orphans_removed += 1 orphans_removed += 1
for persona_dir in sorted(output_dir.iterdir()): for persona_dir in iter_persona_output_dirs(output_dir):
if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
continue
# Install slash commands for all variants (general + specializations). # Install slash commands for all variants (general + specializations).
for prompt_file in persona_dir.glob("*.prompt.md"): for prompt_file in persona_dir.glob("*.prompt.md"):
@@ -1401,9 +1568,7 @@ def install_antigravity(output_dir: Path):
ag_dir = Path.home() / ".config" / "antigravity" / "personas" ag_dir = Path.home() / ".config" / "antigravity" / "personas"
ag_dir.mkdir(parents=True, exist_ok=True) ag_dir.mkdir(parents=True, exist_ok=True)
count = 0 count = 0
for persona_dir in sorted(output_dir.iterdir()): for persona_dir in iter_persona_output_dirs(output_dir):
if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
continue
for prompt_file in persona_dir.glob("*.prompt.md"): for prompt_file in persona_dir.glob("*.prompt.md"):
variant = prompt_file.stem variant = prompt_file.stem
codename = persona_dir.name codename = persona_dir.name
@@ -1612,9 +1777,7 @@ def install_opencode(
# Emit one agent file per variant. General → <codename>.md (picker-visible). # Emit one agent file per variant. General → <codename>.md (picker-visible).
# Non-general → <codename>-<variant>.md with hidden:true so it's # Non-general → <codename>-<variant>.md with hidden:true so it's
# task-dispatchable by name without cluttering the picker. # task-dispatchable by name without cluttering the picker.
for persona_dir in sorted(output_dir.iterdir()): for persona_dir in iter_persona_output_dirs(output_dir):
if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
continue
for variant_json in sorted(persona_dir.glob("*.json")): for variant_json in sorted(persona_dir.glob("*.json")):
try: try:
@@ -1929,9 +2092,7 @@ def install_feynman(
emitted_agents: set[str] = set() emitted_agents: set[str] = set()
ident_re = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$") ident_re = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
for persona_dir in sorted(output_dir.iterdir()): for persona_dir in iter_persona_output_dirs(output_dir):
if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
continue
for variant_json in sorted(persona_dir.glob("*.json")): for variant_json in sorted(persona_dir.glob("*.json")):
try: try:
data = json.loads(variant_json.read_text(encoding="utf-8")) data = json.loads(variant_json.read_text(encoding="utf-8"))
@@ -2181,9 +2342,7 @@ def install_gemini(output_dir: Path):
gems_dir = output_dir / "_gems" gems_dir = output_dir / "_gems"
gems_dir.mkdir(parents=True, exist_ok=True) gems_dir.mkdir(parents=True, exist_ok=True)
count = 0 count = 0
for persona_dir in sorted(output_dir.iterdir()): for persona_dir in iter_persona_output_dirs(output_dir):
if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
continue
for json_file in persona_dir.glob("*.json"): for json_file in persona_dir.glob("*.json"):
data = json.loads(json_file.read_text(encoding="utf-8")) data = json.loads(json_file.read_text(encoding="utf-8"))
variant = data.get("variant", json_file.stem) variant = data.get("variant", json_file.stem)
@@ -2252,9 +2411,7 @@ def install_paperclip(output_dir: Path, personas_dir: Path, shared_dir: Path | N
agent_count = 0 agent_count = 0
skill_count = 0 skill_count = 0
for persona_dir in sorted(output_dir.iterdir()): for persona_dir in iter_persona_output_dirs(output_dir):
if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
continue
general_json = persona_dir / "general.json" general_json = persona_dir / "general.json"
general_prompt = persona_dir / "general.prompt.md" general_prompt = persona_dir / "general.prompt.md"
@@ -2449,9 +2606,7 @@ def install_openclaw(output_dir: Path):
personas_dir.mkdir(parents=True, exist_ok=True) personas_dir.mkdir(parents=True, exist_ok=True)
count = 0 count = 0
identity_sections = [] identity_sections = []
for persona_dir in sorted(output_dir.iterdir()): for persona_dir in iter_persona_output_dirs(output_dir):
if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
continue
general_prompt = persona_dir / "general.prompt.md" general_prompt = persona_dir / "general.prompt.md"
if not general_prompt.exists(): if not general_prompt.exists():
continue continue
@@ -2602,13 +2757,36 @@ def main():
config = load_config(root) config = load_config(root)
flat_config = flatten_config(config) if config else {} flat_config = flatten_config(config) if config else {}
# Find all persona directories # Cache user-personalization runtime: which variant filenames are treated
# as personalized, and which top-level folder under generated/ collects
# them. Helpers (iter_persona_output_dirs, build_persona) read these.
configure_runtime(config)
# Find all persona directories from BOTH the shared library (personas/)
# AND the user-personalized source (e.g. personalized/ — gitignored).
# Same codename can appear in both; build_persona() processes whatever
# .md files exist in the dir it's given, and routes personalized variants
# to the separate output folder via _RUNTIME.
persona_dirs = [ persona_dirs = [
d d
for d in sorted(personas_dir.iterdir()) for d in sorted(personas_dir.iterdir())
if d.is_dir() and not d.name.startswith((".", "_")) if d.is_dir() and not d.name.startswith((".", "_"))
] ]
personalized_source_dir = root / _RUNTIME["personalized_source_dirname"]
if personalized_source_dir.exists() and personalized_source_dir.is_dir():
personalized_persona_dirs = [
d
for d in sorted(personalized_source_dir.iterdir())
if d.is_dir() and not d.name.startswith((".", "_"))
]
persona_dirs.extend(personalized_persona_dirs)
if personalized_persona_dirs:
print(
f"Personalized source: {personalized_source_dir} "
f"({len(personalized_persona_dirs)} codenames)"
)
if not persona_dirs: if not persona_dirs:
print("No persona directories found.") print("No persona directories found.")
sys.exit(1) sys.exit(1)

View File

@@ -18,6 +18,19 @@ user:
casual_language: "en" casual_language: "en"
technical_language: "en" technical_language: "en"
# Personalized variant routing (optional). Variants matched here are
# written to generated/<personalized_output_folder>/<persona>/ instead
# of generated/<persona>/, keeping the main persona folders clean of
# per-user content.
#
# Defaults if both are omitted:
# personalized_variants: ["salva"] (existing convention)
# personalized_output_folder: slugify(user.name) + "-personas"
# e.g. "Alice Smith" → "alice-smith-personas"
# (or "personalized-personas" if name unset)
personalized_variants: ["salva"]
# personalized_output_folder: "my-personas" # explicit override
# ─── Infrastructure ───────────────────────────────────────────── # ─── Infrastructure ─────────────────────────────────────────────
infrastructure: infrastructure:
servers: [] servers: []

View File

@@ -5,14 +5,14 @@ _Auto-generated by build.py | User: Salva_
## arbiter — International Law & War Crimes Specialist ## arbiter — International Law & War Crimes Specialist
- **Domain:** law - **Domain:** law
- **Hitap:** Kadı - **Hitap:** Kadı
- **Variants:** general, salva, sanctions - **Variants:** general, sanctions
- **Depth:** 2,880 words, 6 sections - **Depth:** 2,880 words, 6 sections
- **Escalates to:** frodo, marshal, tribune, chronos - **Escalates to:** frodo, marshal, tribune, chronos
## architect — DevOps & Systems Engineer ## architect — DevOps & Systems Engineer
- **Domain:** engineering - **Domain:** engineering
- **Hitap:** Mimar Ağa - **Hitap:** Mimar Ağa
- **Variants:** general, salva - **Variants:** general
- **Depth:** 1,526 words, 6 sections - **Depth:** 1,526 words, 6 sections
- **Escalates to:** forge, vortex, neo - **Escalates to:** forge, vortex, neo
@@ -26,14 +26,14 @@ _Auto-generated by build.py | User: Salva_
## centurion — Military History & War Analysis Specialist ## centurion — Military History & War Analysis Specialist
- **Domain:** military - **Domain:** military
- **Hitap:** Vakanüvis - **Hitap:** Vakanüvis
- **Variants:** general, ottoman-wars, salva, ukraine-russia - **Variants:** general, ottoman-wars, ukraine-russia
- **Depth:** 2,269 words, 6 sections - **Depth:** 2,269 words, 6 sections
- **Escalates to:** marshal, warden, chronos, corsair - **Escalates to:** marshal, warden, chronos, corsair
## chronos — World History & Civilization Analysis Specialist ## chronos — World History & Civilization Analysis Specialist
- **Domain:** history - **Domain:** history
- **Hitap:** Tarihçibaşı - **Hitap:** Tarihçibaşı
- **Variants:** general, salva - **Variants:** general
- **Depth:** 2,581 words, 6 sections - **Depth:** 2,581 words, 6 sections
- **Escalates to:** centurion, scholar, sage, tribune, scribe - **Escalates to:** centurion, scholar, sage, tribune, scribe
@@ -47,84 +47,84 @@ _Auto-generated by build.py | User: Salva_
## corsair — Special Operations & Irregular Warfare Specialist ## corsair — Special Operations & Irregular Warfare Specialist
- **Domain:** military - **Domain:** military
- **Hitap:** Akıncı - **Hitap:** Akıncı
- **Variants:** general, proxy-warfare, salva - **Variants:** general, proxy-warfare
- **Depth:** 2,352 words, 6 sections - **Depth:** 2,352 words, 6 sections
- **Escalates to:** marshal, wraith, centurion, warden - **Escalates to:** marshal, wraith, centurion, warden
## echo — SIGINT / COMINT / ELINT Specialist ## echo — SIGINT / COMINT / ELINT Specialist
- **Domain:** intelligence - **Domain:** intelligence
- **Hitap:** Kulakçı - **Hitap:** Kulakçı
- **Variants:** electronic-order-of-battle, general, nsa-sigint, salva - **Variants:** electronic-order-of-battle, general, nsa-sigint
- **Depth:** 2,504 words, 6 sections - **Depth:** 2,504 words, 6 sections
- **Escalates to:** cipher, vortex, frodo, wraith, sentinel - **Escalates to:** cipher, vortex, frodo, wraith, sentinel
## forge — Software Development & AI/ML Engineer ## forge — Software Development & AI/ML Engineer
- **Domain:** engineering - **Domain:** engineering
- **Hitap:** Demirci - **Hitap:** Demirci
- **Variants:** agent-dev, frontend-design, general, salva - **Variants:** agent-dev, frontend-design, general
- **Depth:** 1,882 words, 6 sections - **Depth:** 1,882 words, 6 sections
- **Escalates to:** architect, cipher, sentinel - **Escalates to:** architect, cipher, sentinel
## frodo — Strategic Intelligence Analyst ## frodo — Strategic Intelligence Analyst
- **Domain:** intelligence - **Domain:** intelligence
- **Hitap:** Müsteşar - **Hitap:** Müsteşar
- **Variants:** africa, china, energy-geopolitics, general, india, iran, middle-east, nato-alliance, nuclear, pakistan, russia, salva, turkey - **Variants:** africa, china, energy-geopolitics, general, india, iran, middle-east, nato-alliance, nuclear, pakistan, russia, turkey
- **Depth:** 1,776 words, 6 sections - **Depth:** 1,776 words, 6 sections
- **Escalates to:** oracle, ghost, wraith, echo, sentinel, marshal - **Escalates to:** oracle, ghost, wraith, echo, sentinel, marshal
## gambit — Chess & Strategic Thinking Specialist ## gambit — Chess & Strategic Thinking Specialist
- **Domain:** strategy - **Domain:** strategy
- **Hitap:** Vezir - **Hitap:** Vezir
- **Variants:** general, salva - **Variants:** general
- **Depth:** 2,548 words, 6 sections - **Depth:** 2,548 words, 6 sections
- **Escalates to:** marshal, sage, tribune, corsair - **Escalates to:** marshal, sage, tribune, corsair
## ghost — PSYOP & Information Warfare Specialist ## ghost — PSYOP & Information Warfare Specialist
- **Domain:** intelligence - **Domain:** intelligence
- **Hitap:** Propagandist - **Hitap:** Propagandist
- **Variants:** cognitive-warfare, general, russian-info-war, salva - **Variants:** cognitive-warfare, general, russian-info-war
- **Depth:** 2,117 words, 6 sections - **Depth:** 2,117 words, 6 sections
- **Escalates to:** oracle, frodo, herald, wraith - **Escalates to:** oracle, frodo, herald, wraith
## herald — Media Analysis & Strategic Communication Specialist ## herald — Media Analysis & Strategic Communication Specialist
- **Domain:** media - **Domain:** media
- **Hitap:** Münadi - **Hitap:** Münadi
- **Variants:** general, salva - **Variants:** general
- **Depth:** 2,827 words, 6 sections - **Depth:** 2,827 words, 6 sections
- **Escalates to:** ghost, polyglot, oracle, frodo - **Escalates to:** ghost, polyglot, oracle, frodo
## ledger — Economic Intelligence & FININT Specialist ## ledger — Economic Intelligence & FININT Specialist
- **Domain:** economics - **Domain:** economics
- **Hitap:** Defterdar - **Hitap:** Defterdar
- **Variants:** general, salva, sanctions-evasion - **Variants:** general, sanctions-evasion
- **Depth:** 2,847 words, 6 sections - **Depth:** 2,847 words, 6 sections
- **Escalates to:** arbiter, frodo, tribune, scribe - **Escalates to:** arbiter, frodo, tribune, scribe
## marshal — Military Doctrine & Strategy Specialist ## marshal — Military Doctrine & Strategy Specialist
- **Domain:** military - **Domain:** military
- **Hitap:** Mareşal - **Hitap:** Mareşal
- **Variants:** chinese-doctrine, general, hybrid-warfare, iranian-military, nato-doctrine, russian-doctrine, salva, turkish-doctrine, wargaming - **Variants:** chinese-doctrine, general, hybrid-warfare, iranian-military, nato-doctrine, russian-doctrine, turkish-doctrine, wargaming
- **Depth:** 1,760 words, 6 sections - **Depth:** 1,760 words, 6 sections
- **Escalates to:** centurion, warden, corsair, frodo - **Escalates to:** centurion, warden, corsair, frodo
## medic — Biomedical & CBRN Specialist ## medic — Biomedical & CBRN Specialist
- **Domain:** science - **Domain:** science
- **Hitap:** Hekim Başı - **Hitap:** Hekim Başı
- **Variants:** cbrn-defense, general, salva - **Variants:** cbrn-defense, general
- **Depth:** 2,309 words, 6 sections - **Depth:** 2,309 words, 6 sections
- **Escalates to:** warden, frodo, marshal, corsair - **Escalates to:** warden, frodo, marshal, corsair
## neo — Red Team Lead / Exploit Developer ## neo — Red Team Lead / Exploit Developer
- **Domain:** cybersecurity - **Domain:** cybersecurity
- **Hitap:** Sıfırıncı Gün - **Hitap:** Sıfırıncı Gün
- **Variants:** exploit-dev, general, mobile-security, redteam, salva, social-engineering, wireless - **Variants:** exploit-dev, general, mobile-security, redteam, social-engineering, wireless
- **Depth:** 1,090 words, 6 sections - **Depth:** 1,090 words, 6 sections
- **Escalates to:** bastion, phantom, specter, vortex, sentinel - **Escalates to:** bastion, phantom, specter, vortex, sentinel
## oracle — OSINT & Digital Intelligence Specialist ## oracle — OSINT & Digital Intelligence Specialist
- **Domain:** intelligence - **Domain:** intelligence
- **Hitap:** Kaşif - **Hitap:** Kaşif
- **Variants:** crypto-osint, general, salva, source-verification - **Variants:** crypto-osint, general, source-verification
- **Depth:** 1,880 words, 6 sections - **Depth:** 1,880 words, 6 sections
- **Escalates to:** ghost, sentinel, frodo, herald - **Escalates to:** ghost, sentinel, frodo, herald
@@ -138,35 +138,35 @@ _Auto-generated by build.py | User: Salva_
## polyglot — Linguistics & LINGINT Specialist ## polyglot — Linguistics & LINGINT Specialist
- **Domain:** linguistics - **Domain:** linguistics
- **Hitap:** Tercüman-ı Divan - **Hitap:** Tercüman-ı Divan
- **Variants:** arabic, general, russian, salva, swahili - **Variants:** arabic, general, russian, swahili
- **Depth:** 2,308 words, 6 sections - **Depth:** 2,308 words, 6 sections
- **Escalates to:** frodo, ghost, herald, scholar - **Escalates to:** frodo, ghost, herald, scholar
## sage — Philosophy, Psychology & Power Theory Specialist ## sage — Philosophy, Psychology & Power Theory Specialist
- **Domain:** humanities - **Domain:** humanities
- **Hitap:** Arif - **Hitap:** Arif
- **Variants:** general, salva - **Variants:** general
- **Depth:** 2,132 words, 6 sections - **Depth:** 2,132 words, 6 sections
- **Escalates to:** tribune, scholar, chronos, ghost - **Escalates to:** tribune, scholar, chronos, ghost
## scholar — Academic Researcher ## scholar — Academic Researcher
- **Domain:** academia - **Domain:** academia
- **Hitap:** Münevver - **Hitap:** Münevver
- **Variants:** general, salva - **Variants:** general
- **Depth:** 1,588 words, 6 sections - **Depth:** 1,588 words, 6 sections
- **Escalates to:** frodo, tribune, sage, chronos - **Escalates to:** frodo, tribune, sage, chronos
## scribe — FOIA Archivist & Declassified Document Analyst ## scribe — FOIA Archivist & Declassified Document Analyst
- **Domain:** history - **Domain:** history
- **Hitap:** Verakçı - **Hitap:** Verakçı
- **Variants:** cia-foia, cold-war-ops, general, salva - **Variants:** cia-foia, cold-war-ops, general
- **Depth:** 2,847 words, 6 sections - **Depth:** 2,847 words, 6 sections
- **Escalates to:** chronos, wraith, frodo, echo - **Escalates to:** chronos, wraith, frodo, echo
## sentinel — Cyber Threat Intelligence Analyst ## sentinel — Cyber Threat Intelligence Analyst
- **Domain:** cybersecurity - **Domain:** cybersecurity
- **Hitap:** İzci - **Hitap:** İzci
- **Variants:** apt-profiling, c2-hunting, darknet, general, mitre-attack, salva - **Variants:** apt-profiling, c2-hunting, darknet, general, mitre-attack
- **Depth:** 1,558 words, 6 sections - **Depth:** 1,558 words, 6 sections
- **Escalates to:** specter, bastion, frodo, neo, echo - **Escalates to:** specter, bastion, frodo, neo, echo
@@ -180,7 +180,7 @@ _Auto-generated by build.py | User: Salva_
## tribune — Political Science & Regime Analysis Specialist ## tribune — Political Science & Regime Analysis Specialist
- **Domain:** politics - **Domain:** politics
- **Hitap:** Müderris - **Hitap:** Müderris
- **Variants:** general, salva - **Variants:** general
- **Depth:** 3,356 words, 6 sections - **Depth:** 3,356 words, 6 sections
- **Escalates to:** frodo, chronos, arbiter, sage, scholar - **Escalates to:** frodo, chronos, arbiter, sage, scholar
@@ -194,14 +194,14 @@ _Auto-generated by build.py | User: Salva_
## warden — Defense Analyst & Weapons Systems Specialist ## warden — Defense Analyst & Weapons Systems Specialist
- **Domain:** military - **Domain:** military
- **Hitap:** Topçubaşı - **Hitap:** Topçubaşı
- **Variants:** drone-warfare, electronic-warfare, general, naval-warfare, salva - **Variants:** drone-warfare, electronic-warfare, general, naval-warfare
- **Depth:** 1,823 words, 6 sections - **Depth:** 1,823 words, 6 sections
- **Escalates to:** marshal, centurion, corsair, medic - **Escalates to:** marshal, centurion, corsair, medic
## wraith — HUMINT & Counter-Intelligence Specialist ## wraith — HUMINT & Counter-Intelligence Specialist
- **Domain:** intelligence - **Domain:** intelligence
- **Hitap:** Mahrem - **Hitap:** Mahrem
- **Variants:** case-studies, general, salva, source-validation - **Variants:** case-studies, general, source-validation
- **Depth:** 2,265 words, 6 sections - **Depth:** 2,265 words, 6 sections
- **Escalates to:** oracle, ghost, echo, frodo, sentinel - **Escalates to:** oracle, ghost, echo, frodo, sentinel