Skill: salvagedeck/oejts-personality-tuner (version 1.0.0)

--- name: oejts-personality-tuner description: Administer and score the Open Extended Jungian Type Scales (OEJTS 1.2), map results to MBTI-style interaction preferences, and propose/apply personality-aware tuning updates to USER.md and SOUL.md. Use when a user asks to run a Myers-Briggs-like test, calculate type from OEJTS responses, or tune assistant behavior to a personality profile. --- # OEJTS Personality Tuner Use this skill to run the OEJTS 1.2 assessment, compute type results, and convert the outcome into concrete assistant behavior preferences. ## Quick Start - Show questionnaire: - `python3 scripts/oejts_tuner.py template --format markdown` - Score answers: - `python3 scripts/oejts_tuner.py score --answers-json '{"Q1":3,...,"Q32":4}'` - Generate proposed updates for workspace files: - `python3 scripts/oejts_tuner.py apply --workspace /path/to/workspace --answers-json '{...}' --dry-run` - Apply updates: - `python3 scripts/oejts_tuner.py apply --workspace /path/to/workspace --answers-json '{...}'` ## Workflow 1. Administer OEJTS 1.2 using `template` output from the script. 2. Collect 1–5 answers for `Q1..Q32`. 3. Run `score` to compute `IE`, `SN`, `FT`, `JP`, type letters, and confidence. 4. Run `apply --dry-run` to preview USER.md/SOUL.md changes. 5. Confirm with user, then run `apply` without `--dry-run`. ## Rules - Use OEJTS type as a **preference signal**, not fixed identity. - Prefer adjustable behavior vectors over stereotypes. - Keep user override available (user feedback beats test output). - Preserve existing USER.md/SOUL.md content; append/update only the managed blocks. ## Managed Blocks This skill writes only between these markers: - USER.md: - `<!-- OJTS_PROFILE_START -->` - `<!-- OJTS_PROFILE_END -->` - SOUL.md: - `<!-- OJTS_ADAPTATION_START -->` - `<!-- OJTS_ADAPTATION_END -->` If blocks do not exist, they are appended. ## References - `references/oejts-1.2.md` — canonical OEJTS items and scoring equations. - `references/behavior-mapping.md` — type-to-behavior mapping and adaptive guidance.

path: references/behavior-mapping.md
content: # Behavior Mapping (MBTI-Style Preferences)

Use type results as adaptable preferences, not fixed identity.

## Letter-to-Behavior Mapping

### I vs E (interaction cadence)
- I: fewer interruptions, deeper async responses, lower ping frequency.
- E: more interactive cadence, faster turn loops, visible momentum.

### S vs N (information style)
- S: concrete details, practical examples, step-by-step execution.
- N: abstractions, patterns, big-picture framing, exploration.

### T vs F (decision framing)
- T: logic-first tradeoffs, direct critique, objective criteria.
- F: impact-on-people framing, tone-sensitive guidance, values alignment.

### J vs P (execution style)
- J: structured plans, sequencing, deadlines/checklists.
- P: optionality, exploratory branches, lightweight constraints.

## Companion Modes

- Mirror mode: align with user preference style.
- Complement mode: add counter-balance where task benefits (e.g., add J structure to P users for delivery).
- Adaptive mode (recommended): mirror tone; complement planning/analysis only when useful.

## Suggested SOUL Adaptation Block

- Read USER.md personality preferences first.
- Default to Adaptive mode.
- Prefer user feedback over test profile when conflict exists.
- Offer `/style override` at any time.

## Confidence Handling

If any dichotomy score is close to threshold (24), use softer language:
- "leans toward"
- "mixed preference"

Recommended threshold for "strong" preference: distance >= 4 points from cutoff.


path: references/oejts-1.2.md
content: # OEJTS 1.2 Reference

Source: Open Extended Jungian Type Scales 1.2 (Eric Jorgenson).

## Response Scale

Each item is answered from 1 to 5:

- 1 = strongly toward the left descriptor
- 3 = neutral/middle
- 5 = strongly toward the right descriptor

## Items

Q1 makes lists — relies on memory
Q2 sceptical — wants to believe
Q3 bored by time alone — needs time alone
Q4 accepts things as they are — unsatisfied with the ways things are
Q5 keeps a clean room — just puts stuff where ever
Q6 thinks "robotic" is an insult — strives to have a mechanical mind
Q7 energetic — mellow
Q8 prefer to take multiple choice test — prefer essay answers
Q9 chaotic — organized
Q10 easily hurt — thick-skinned
Q11 works best in groups — works best alone
Q12 focused on the present — focused on the future
Q13 plans far ahead — plans at the last minute
Q14 wants people's respect — wants their love
Q15 gets worn out by parties — gets fired up by parties
Q16 fits in — stands out
Q17 keeps options open — commits
Q18 wants to be good at fixing things — wants to be good at fixing people
Q19 talks more — listens more
Q20 when describing an event, will tell people what happened — when describing an event, will tell people what it meant
Q21 gets work done right away — procrastinates
Q22 follows the heart — follows the head
Q23 stays at home — goes out on the town
Q24 wants the big picture — wants the details
Q25 improvises — prepares
Q26 bases morality on justice — bases morality on compassion
Q27 finds it difficult to yell very loudly — yelling to others when they are far away comes naturally
Q28 theoretical — empirical
Q29 works hard — plays hard
Q30 uncomfortable with emotions — values emotions
Q31 likes to perform in front of other people — avoids public speaking
Q32 likes to know "who?", "what?", "when?" — likes to know "why?"

## Scoring Equations

IE = 30 - Q3 - Q7 - Q11 + Q15 - Q19 + Q23 + Q27 - Q31
SN = 12 + Q4 + Q8 + Q12 + Q16 + Q20 - Q24 - Q28 + Q32
FT = 30 - Q2 + Q6 + Q10 - Q14 - Q18 + Q22 - Q26 - Q30
JP = 18 + Q1 + Q5 - Q9 + Q13 - Q17 + Q21 - Q25 + Q29

## Type Rule

- IE > 24 => E, else I
- SN > 24 => N, else S
- FT > 24 => T, else F
- JP > 24 => P, else J

Final type is the 4-letter concatenation, e.g., INFP.


path: scripts/oejts_tuner.py
content: #!/usr/bin/env python3
import argparse
import json
from datetime import datetime
from pathlib import Path

ITEMS = [
    ("Q1", "makes lists", "relies on memory"),
    ("Q2", "sceptical", "wants to believe"),
    ("Q3", "bored by time alone", "needs time alone"),
    ("Q4", "accepts things as they are", "unsatisfied with the ways things are"),
    ("Q5", "keeps a clean room", "just puts stuff where ever"),
    ("Q6", "thinks \"robotic\" is an insult", "strives to have a mechanical mind"),
    ("Q7", "energetic", "mellow"),
    ("Q8", "prefer to take multiple choice test", "prefer essay answers"),
    ("Q9", "chaotic", "organized"),
    ("Q10", "easily hurt", "thick-skinned"),
    ("Q11", "works best in groups", "works best alone"),
    ("Q12", "focused on the present", "focused on the future"),
    ("Q13", "plans far ahead", "plans at the last minute"),
    ("Q14", "wants people's respect", "wants their love"),
    ("Q15", "gets worn out by parties", "gets fired up by parties"),
    ("Q16", "fits in", "stands out"),
    ("Q17", "keeps options open", "commits"),
    ("Q18", "wants to be good at fixing things", "wants to be good at fixing people"),
    ("Q19", "talks more", "listens more"),
    ("Q20", "when describing an event, will tell people what happened", "when describing an event, will tell people what it meant"),
    ("Q21", "gets work done right away", "procrastinates"),
    ("Q22", "follows the heart", "follows the head"),
    ("Q23", "stays at home", "goes out on the town"),
    ("Q24", "wants the big picture", "wants the details"),
    ("Q25", "improvises", "prepares"),
    ("Q26", "bases morality on justice", "bases morality on compassion"),
    ("Q27", "finds it difficult to yell very loudly", "yelling to others when they are far away comes naturally"),
    ("Q28", "theoretical", "empirical"),
    ("Q29", "works hard", "plays hard"),
    ("Q30", "uncomfortable with emotions", "values emotions"),
    ("Q31", "likes to perform in front of other people", "avoids public speaking"),
    ("Q32", "likes to know \"who?\", \"what?\", \"when?\"", "likes to know \"why?\""),
]

Q = {k: None for k, _, _ in ITEMS}


def parse_answers(raw: str):
    data = json.loads(raw)
    missing = [k for k in Q if k not in data]
    if missing:
        raise ValueError(f"Missing answers: {', '.join(missing)}")
    parsed = {}
    for k in Q:
        v = int(data[k])
        if v < 1 or v > 5:
            raise ValueError(f"{k} must be in range 1..5")
        parsed[k] = v
    return parsed


def score(a):
    IE = 30 - a["Q3"] - a["Q7"] - a["Q11"] + a["Q15"] - a["Q19"] + a["Q23"] + a["Q27"] - a["Q31"]
    SN = 12 + a["Q4"] + a["Q8"] + a["Q12"] + a["Q16"] + a["Q20"] - a["Q24"] - a["Q28"] + a["Q32"]
    FT = 30 - a["Q2"] + a["Q6"] + a["Q10"] - a["Q14"] - a["Q18"] + a["Q22"] - a["Q26"] - a["Q30"]
    JP = 18 + a["Q1"] + a["Q5"] - a["Q9"] + a["Q13"] - a["Q17"] + a["Q21"] - a["Q25"] + a["Q29"]

    typ = (
        ("E" if IE > 24 else "I") +
        ("N" if SN > 24 else "S") +
        ("T" if FT > 24 else "F") +
        ("P" if JP > 24 else "J")
    )

    deltas = {
        "IE": abs(IE - 24),
        "SN": abs(SN - 24),
        "FT": abs(FT - 24),
        "JP": abs(JP - 24),
    }
    min_delta = min(deltas.values())
    if min_delta >= 4:
        confidence = "high"
    elif min_delta >= 2:
        confidence = "medium"
    else:
        confidence = "low"

    return {
        "type": typ,
        "scores": {"IE": IE, "SN": SN, "FT": FT, "JP": JP},
        "distanceFromCutoff": deltas,
        "confidence": confidence,
        "computedAt": datetime.now().isoformat(timespec="seconds"),
    }


def behavior_for_type(t):
    mode = "adaptive"
    prefs = {
        "interaction": "deeper async responses" if t[0] == "I" else "more interactive cadence",
        "informationStyle": "concrete, step-by-step" if t[1] == "S" else "pattern-based, big-picture",
        "decisionStyle": "logic-first framing" if t[2] == "T" else "impact-on-people framing",
        "executionStyle": "structured plans and sequencing" if t[3] == "J" else "optionality with lightweight structure",
    }
    return mode, prefs


def render_template(fmt):
    if fmt == "json":
        print(json.dumps({
            "version": "OEJTS-1.2",
            "scale": {"min": 1, "max": 5},
            "items": [{"id": i, "left": l, "right": r} for i, l, r in ITEMS]
        }, indent=2))
        return

    print("# OEJTS 1.2 Questionnaire\n")
    print("Rate each item from 1 to 5 (1=left trait, 5=right trait).\n")
    for i, l, r in ITEMS:
        print(f"- {i}: {l}  <1 2 3 4 5>  {r}")


def upsert_block(text: str, start: str, end: str, block: str):
    if start in text and end in text:
        a = text.index(start)
        b = text.index(end) + len(end)
        return text[:a] + block + text[b:]
    if not text.endswith("\n"):
        text += "\n"
    return text + "\n" + block + "\n"


def apply_updates(workspace: Path, result: dict, dry_run: bool):
    user_md = workspace / "USER.md"
    soul_md = workspace / "SOUL.md"

    mode, prefs = behavior_for_type(result["type"])

    user_block = f"""<!-- OJTS_PROFILE_START -->
## Personality Profile (OEJTS 1.2)
- **Type:** {result['type']}
- **Confidence:** {result['confidence']}
- **Scores:** IE={result['scores']['IE']}, SN={result['scores']['SN']}, FT={result['scores']['FT']}, JP={result['scores']['JP']}
- **Computed:** {result['computedAt']}

### Interaction Preferences
- **Interaction cadence:** {prefs['interaction']}
- **Information style:** {prefs['informationStyle']}
- **Decision framing:** {prefs['decisionStyle']}
- **Execution style:** {prefs['executionStyle']}

> Note: Treat as a preference signal and refine from feedback.
<!-- OJTS_PROFILE_END -->"""

    soul_block = f"""<!-- OJTS_ADAPTATION_START -->
## Adaptive Personality Tuning

When USER.md includes OEJTS profile data:
- Use **{mode} mode** by default.
- Mirror tone and communication style preferences.
- Add complementary structure only when it helps task completion.
- If live user feedback conflicts with profile, feedback wins.
- Offer a style override at any time.
<!-- OJTS_ADAPTATION_END -->"""

    user_text = user_md.read_text() if user_md.exists() else "# USER.md\n"
    soul_text = soul_md.read_text() if soul_md.exists() else "# SOUL.md\n"

    new_user = upsert_block(user_text, "<!-- OJTS_PROFILE_START -->", "<!-- OJTS_PROFILE_END -->", user_block)
    new_soul = upsert_block(soul_text, "<!-- OJTS_ADAPTATION_START -->", "<!-- OJTS_ADAPTATION_END -->", soul_block)

    if dry_run:
        print("=== USER.md preview ===")
        print(user_block)
        print("\n=== SOUL.md preview ===")
        print(soul_block)
        return

    user_md.write_text(new_user)
    soul_md.write_text(new_soul)
    print(f"Updated: {user_md}")
    print(f"Updated: {soul_md}")


def main():
    p = argparse.ArgumentParser(description="OEJTS 1.2 scoring + workspace tuning helper")
    sp = p.add_subparsers(dest="cmd", required=True)

    t = sp.add_parser("template", help="Print questionnaire template")
    t.add_argument("--format", choices=["markdown", "json"], default="markdown")

    s = sp.add_parser("score", help="Score OEJTS answers")
    s.add_argument("--answers-json", required=True, help='JSON object containing Q1..Q32 with values 1..5')

    a = sp.add_parser("apply", help="Apply USER.md/SOUL.md tuning blocks")
    a.add_argument("--workspace", required=True)
    a.add_argument("--answers-json", required=True)
    a.add_argument("--dry-run", action="store_true")

    args = p.parse_args()

    if args.cmd == "template":
        render_template(args.format)
        return

    answers = parse_answers(args.answers_json)
    result = score(answers)

    if args.cmd == "score":
        print(json.dumps(result, indent=2))
        return

    apply_updates(Path(args.workspace).expanduser(), result, args.dry_run)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)