---
name: apt:personas
description: "Discover 3-5 product personas grounded in code signal + PROJECT.md"
apt-skill-version: {{APT_VERSION}}
stage: setup
intent: x-personas
when_to_use: "Run after /apt:bootstrap to draft or refresh .aperant/personas.json when no roster exists yet or after a product pivot."
user_invocable: true
internal: false
spawns_agent: false
agent_name: null
task_context: none
default_execution_mode: auto
execution_modes:
  - auto
  - step
allowed-tools: "Read, Glob, Grep, Write, AskUserQuestion"
argument-hint: "apt:personas [--fresh | --extend | --from-app | --force]"
gates: []
---

<objective>
Discover 3-5 personas that the product actually serves, grounded in two
independent signals:

1. **Code signal** — deterministic role-scan across the repo surfaces role
   identifiers, route prefixes, validator enums. Non-LLM, bounded (400 files
   × 2000 lines cap).
2. **Strategic intent** — `PROJECT.md`'s Target Audience + Core Value
   sections carry the "who and why" the founders actually care about.

The host LLM (you) reconciles the two via the C48 D-05 tier rubric
(`apt-tools personas rubric .`) and drafts a Persona[] payload. The CLI
then persists the payload to `.aperant/personas.json` (D-02) with a
`framework-local` sentinel team_id (D-03), respecting the D-10
canonical-source guard (refuses --fresh overwrite when the app wrote a
cloud-canonical envelope, unless `--force`).

Rules of the road (locked):

- **No LLM calls from the CLI.** The CLI is deterministic I/O. You run the
  LLM, the CLI persists.
- **Count is adaptive (D-08).** 3 is the floor, 5 the ceiling. Single-role
  SaaS → 3; multi-sided marketplace → 5. Do not pad to hit a number.
- **Evidence anchors (D-07).** Each persona carries `raw_json.evidence[]`
  with `{ source, file, line_range, confidence }` entries proving the
  persona exists in the codebase. Evidence lives in `raw_json`, not
  `ui_json` — the app UI renders `ui_json` only.
- **--extend by default.** Re-runs without an explicit flag default to
  extend (add net-new, preserve existing). --fresh and --force are
  destructive and must be explicit (mirrors C47 D-05).
- **Record every run (D-09).** Every invocation appends a
  `persona_discovery_runs` entry with `triggered_by: 'manual'`, even in
  --auto mode — the enum accepts 'manual' precisely for this case.
</objective>

<your_environment>
- **Working directory:** The project root
- **apt-tools path:** `node packages/framework/bin/apt-tools.mjs` or the locally installed `apt-tools` binary
- **Upstream context:** `PROJECT.md` (7 D-06 sections) + `.aperant/product-context.json` (derived sidecar from C47/C44)
- **Output:** `.aperant/personas.json` (canonical roster) + `.aperant/personas-runs.json` (history)
- **Intent:** `x-personas` — first custom-intent dogfood (router 0.3.0 extension path)
</your_environment>

<state_files>
## State Files

**Reads:**
- `PROJECT.md` (project root) — Target Audience prose, Core Value anchor
- `.aperant/product-context.json` (derived from PROJECT.md via C47/C44) — structured productName, targetAudience, features
- Bounded repo file sample — role-scan's deterministic read (fileCap=400, lineCap=2000)
- `.aperant/personas.json` (if present) — existing roster for --extend / --from-app
- `.aperant/project.json` — `id` field → Persona.project_id (D-03 fallback: `local:<basename>`)

**Writes:**
- `.aperant/personas.json` — full envelope with `team_id: 'framework-local'`, `project_id`, `canonical_source: 'local'`, `personas: Persona[]` (via atomic rename)
- `.aperant/personas-runs.json` — append-only run history (D-09)
</state_files>

<process>

## 0. Hard-Gate + Autonomy Check

No hard gates (`gates: []`). Autonomy inherited from
`.aperant/config.json`'s `autonomy.default` controls how much the user is
asked between draft and persist:

| Autonomy | Behavior |
|---|---|
| 0 supervised | Show each persona draft individually; ask per-persona accept / edit / drop |
| 1 guided | Show the full Persona[] draft + tier table; ask once to accept / revise |
| 2 autonomous | Show only tier summary (primary / secondary / tertiary counts); persist unless user objects |
| 3 yolo | Persist silently; emit the run summary at the end |

Parse flags from the invocation:

- `--fresh` — overwrite existing personas. Subject to D-10 canonical-source
  guard (refuses if `canonical_source: 'cloud'` unless `--force`).
- `--extend` — default. Add net-new personas; dedupe by name + source_fingerprint.
- `--from-app` — read the existing `.aperant/personas.json` (typically
  written by the app's persona wizard) and surface it for inline refinement
  instead of re-drafting from scratch.
- `--force` — override D-10 canonical-source guard (only meaningful with --fresh).

If both `--fresh` and `--extend` are passed, `--fresh` wins.

## 1. Pre-flight — Product Context Must Exist

Read the upstream files. If `.aperant/product-context.json` is missing,
`PROJECT.md` is the fallback — but if neither exists, stop and suggest
running `/apt:bootstrap` first. Persona drafts without strategic intent
decay into "every product serves developers" slop.

## 2. Deterministic Role Scan

```bash
node packages/framework/bin/apt-tools.mjs personas scan .
```

Returns:

```json
{
  "status": "ok",
  "command": "personas-scan",
  "team_id": "framework-local",
  "project_id": "<uuid-or-local-sentinel>",
  "result": {
    "files_scanned": N,
    "files_with_signal": M,
    "findings": [{ "file": "...", "line": 42, "matched": "...", "kind": "validator-enum", "confidence": "high" }, ...],
    "unique_role_strings": ["admin", "coach", "user", ...]
  }
}
```

If `files_with_signal === 0`, this is a greenfield or very-early project.
Mark every drafted persona's evidence entries as `source: 'project_md'`
instead of `source: 'codebase'` — the strategic-intent signal is all you
have.

## 3. Fetch the Tier Rubric

```bash
node packages/framework/bin/apt-tools.mjs personas rubric .
```

Returns the full prompt body (rubric + inputs pre-filled from
product-context.json and the role scan). You hand this directly to your own
reasoning step — do NOT re-derive the rubric yourself, the CLI version is
the pinned source of truth so dogfood runs stay reproducible.

## 4. Draft Personas

Draft 3-5 personas using the rubric from step 3. For each persona, populate:

- **name** — first-name handle (e.g., "Maya").
- **archetype** — short descriptor (e.g., "The Vibe Coder").
- **tier** — `primary | secondary | tertiary` per the D-05 rubric. Do not
  invent a tertiary slot to look balanced.
- **rationale** — 1-2 sentences. Cite which rubric factor (code signal /
  strategic intent / core-value alignment) drove the tier.
- **evidenceFiles** — subset of role-scan `findings[].file` proving the
  persona in code. Empty list is allowed for greenfield — explicitly mark
  `source: 'project_md'` in that case.

Shape the Persona payload (one per persona) the CLI's `personas write`
command expects:

```json
{
  "ui_json": "{\"name\":\"Maya\",\"archetype\":\"The Vibe Coder\",\"tier\":\"primary\",\"rationale\":\"...\"}",
  "raw_markdown": "# Maya — The Vibe Coder\n...",
  "raw_json": "{\"evidence\":[{\"source\":\"codebase\",\"file\":\"packages/ui/src/sections/personas/data.ts\",\"line_range\":\"12-58\",\"confidence\":\"high\"},{\"source\":\"project_md\",\"file\":\"PROJECT.md\",\"line_range\":\"22-28\",\"confidence\":\"high\"}]}",
  "origin": "local-discovery",
  "source_fingerprint": "framework-local:maya:<hash of rubric inputs>"
}
```

The CLI stamps `team_id`, `project_id`, `canonical_source: 'local'`, and
the three timestamps automatically — do not set them yourself.

## 5. User Confirmation (autonomy-dependent)

Autonomy ≥ 2: emit a tier summary, persist unless user objects:

```
Drafted 4 personas:
  [primary]   Maya — The Vibe Coder
  [primary]   Alex — The Reviewer
  [secondary] Sam — The Ops Engineer
  [tertiary]  Jordan — The Admin
```

Autonomy ≤ 1: present the full draft for review (AskUserQuestion or
text-picker fallback on Codex). Let the user edit / drop / re-tier per
persona.

## 6. Persist

Write the drafted Persona[] to a tmp file, then invoke the CLI:

```bash
node packages/framework/bin/apt-tools.mjs personas write . \
  --mode <fresh|extend|from-app> \
  --payload /tmp/apt-personas-draft.json \
  [--force]
```

On --fresh, check the return envelope:

- `"status": "ok"` → persisted.
- `"status": "refused"` → D-10 guard fired. The envelope's `guard_reason`
  explains why. Surface it to the user and offer two options:
    1. Re-run with `--force` (overwrites cloud-canonical).
    2. Run `--from-app` instead to refine what the app wrote.

## 7. Record the Run

```bash
node packages/framework/bin/apt-tools.mjs personas record-run . \
  --status completed \
  --candidates-found <N> \
  --phase "framework-discover"
```

Always run this, even when `writeFresh` was refused — the run log should
show failed attempts so users can audit the history.

## 8. Report

Emit a summary:

```
Personas persisted to .aperant/personas.json

Count: 4 (1 primary, 2 secondary, 1 tertiary)
Mode: extend
Canonical source: local (framework-written)

New personas this run:
  [primary]   Maya — The Vibe Coder
  [secondary] Sam — The Ops Engineer

Unchanged:
  [primary]   Alex — The Reviewer
  [tertiary]  Jordan — The Admin

Next:
  /apt:create-docs  — render personas into README / ABOUT (C49)
  /apt               — plan a task; planner will inject the persona roster
```

</process>

## Re-invocation

This skill is safe to re-run. Default --extend preserves every existing
persona. --fresh and --force are explicit destructive actions.

Common triggers to re-run:

- Product pivot — Core Value or Target Audience in PROJECT.md changed.
- New role added to the codebase — role-scan surfaces a signal the
  current roster doesn't cover.
- App wizard wrote personas (canonical_source=cloud) and you want to
  extend them with new candidates found in a framework-side run (use
  --extend; --fresh is blocked by D-10).

## Integration with Other Skills

| Skill | How it uses `.aperant/personas.json` |
|-------|------------------------------------|
| `/apt:bootstrap` (C47) | Writes PROJECT.md which feeds the strategic-intent half of persona drafts |
| `/apt:create-docs` (C49) | Renders personas into README / ABOUT sections |
| `/apt:plan` | Injects persona tier into the planner's spec prompt (C50 enhancement) |
| `/apt:design` | Persona archetypes influence the design brief when the product has strong persona-specific flows |
| App persona wizard (cloud) | Writes the same envelope shape with `canonical_source: 'cloud'` — framework respects via D-10 guard |
