403Webshell
Server IP : 3.147.158.171  /  Your IP : 216.73.216.88
Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5
System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.21
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /tsai/repo/.claude/skills/refine-character/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/repo/.claude/skills/refine-character/SKILL.md
---
name: refine-character
description: Iterative article quality loop — creates an article on a production server, reviews content and images, posts a critique comment, then edits the character's description and/or search settings to improve future articles.
argument-hint: <env> <site_slug> <character_name> [iterations]
---

# Refine Character

Run an iterative article improvement loop for a character on a production WordPress site.

**Arguments:** $ARGUMENTS

---

## Goal: drive each character to convergence

The goal of this skill is not "run N iterations" — it is to drive the character to **convergence** and then stop.

- A character has **converged** when the most recent article clears every convergence criterion in Step 3 and no concrete failure mode remains to be verified fixed.
- Each iteration ends in one of two ways: a PATCH targeting a single failure mode, or a declaration of convergence with no edit.
- Running further iterations on a converged character is discouraged. If the user requested more iterations than the character needs, stop early and report convergence rather than burning iterations on a clean baseline.
- If a character is not converging after 3 iterations, surface that to the user — something structural may be wrong (search config, topic range too broad, conflict with another character).

Parse as:
- `env` — first token: `tsai` or `scike`
- `site_slug` — second token (e.g. `guitar`, `news`, `jpt`)
- `character_name` — remaining tokens before any integer, or a quoted string (e.g. `Scike`, `"Rockin Remy"`)
- `iterations` — optional trailing integer, default `1`

If `env` is missing or not `tsai`/`scike`, ask the user before proceeding.

---

## Environment constants

| env | SSH host | Repo path | TSAI_ENV | WP domain suffix |
|-----|----------|-----------|----------|-----------------|
| `tsai` | `ssh -i ~/.ssh/tsai.pem ec2-user@3.147.158.171` | `/tsai/repo` | `production` | `{site_slug}.turmansolutions.ai` |
| `scike` | `ssh -i ~/.ssh/tsai.pem ec2-user@scike.ai` | `/data/tsai` | `scike_ai` | `{site_slug}.scike.ai` |

Resolve from the table:
- `{ssh}` — SSH command for the env
- `{repo}` — repo path for the env
- `{tsai_env}` — TSAI_ENV value for the env
- `{wp_base_url}` — `https://{site_slug}.{domain_suffix}`

Additional constants (same for both envs):
- API venv: `{repo}/api/venv/`
- Local FastAPI base URL (on server): `http://localhost:8000`
- API route prefix: `/api/v1`
- WP credentials: read `WP_USER` / `WP_PASSWORD` from `{repo}/api/.env` on the server
- Comment author: name `John Turman`, email `jturman87@gmail.com`

---

## Pre-flight: resolve character slug and cb_host

Before the first iteration, do this once:

**1. Get cb_host and list characters to find slug and storageId** for the named character:

```bash
{ssh} \
  "cd {repo}/api && source venv/bin/activate && \
   TSAI_ENV={tsai_env} python3 -c \"
from app.services.site_config_service import SiteConfigService
svc = SiteConfigService()
site = svc.get_site_by_slug('{site_slug}')
if not site:
    slugs = sorted(s.slug for s in svc.get_all_sites())
    print('NOT_FOUND: ' + ', '.join(slugs))
else:
    print('CB_HOST:' + site.cb_host)
\""
```

If the output starts with `NOT_FOUND`, stop and tell the user which slugs are available.

**2. List characters** using the `cb_host` as the Origin header:
```bash
{ssh} \
  "curl -s -H 'Origin: https://{cb_host}' \
    'http://localhost:8000/api/v1/cms/characters/' | \
    python3 -c \"
import sys, json
data = json.load(sys.stdin)['data']
match = next((c for c in data if c['name'].lower() == '{character_name}'.lower()), None)
if match:
    print(json.dumps({'slug': match['slug'], 'storageId': match['storageId'], 'wp_user_id': (match.get('wp_user') or {}).get('id', '')}))
else:
    print('NOT FOUND')
\""
```

If the character is not found, stop and report.

---

## Each iteration

### Step 1 — Create article

SSH to the scike server and run `create_articles.py`. This typically takes 1–3 minutes; use a 5-minute timeout. Do not generate a featured image — the refinement loop focuses on text quality.

```bash
{ssh} \
  "cd {repo}/api && source venv/bin/activate && \
   python create_articles.py {site_slug} --character '{character_name}'"
```

If the command fails or prints an error, capture it, report it to the user, and stop this iteration.

---

### Step 2 — Find the new article URL

Query the WP REST API for the most recent post by this author (use the `wp_user_id` from pre-flight). Fetch:

```
GET {wp_base_url}/wp-json/wp/v2/posts?author={wp_user_id}&per_page=1&orderby=date&order=desc&_fields=id,link,title,date
```

Extract the `link` (canonical URL) and `id`. Confirm the `date` is from today — if not, warn the user that the article may not have been created.

---

### Step 3 — Review the article

#### Step 3a — Reliable word-count and structural check

Do this **first**, before WebFetch. WebFetch's summary-level claims about word count and structure are unreliable (it has hallucinated word counts off by nearly 2× on real articles). Fetch raw content and inspect it directly:

```bash
curl -s "{wp_base_url}/wp-json/wp/v2/posts/{post_id}?_fields=content" | python3 -c "
import sys, json, re, html
c = json.load(sys.stdin)['content']['rendered']
t = html.unescape(re.sub(r'<[^>]+>', ' ', c))
print('Words:', len(t.split()))
print('ULs:', c.count('<ul'), 'LIs:', c.count('<li'))
for h in re.findall(r'<h[23][^>]*>(.*?)</h[23]>', c):
    print('H:', re.sub(r'<[^>]+>', '', h))
print('--- Last 400 chars ---')
print(t.strip()[-400:])
"
```

Treat these numbers as ground truth for length and structure. Use WebFetch afterward for qualitative reading.

#### Step 3b — Known theme-level strings (NOT body artifacts)

Some strings appear on every article from the WordPress theme, not from the article body. Do not flag these as regressions:

- **scike theme footer:** `Generated by AI · Claude · claude-sonnet-4-6` appears at the tail of every post's rendered content
- Category/tag links and author byline *above* the article body (outside `<article>` body content) are theme chrome

Real body artifacts to watch for instead:

- `Written by {name}` or `in {Category}` appearing inside the article body itself (Will iter 2 had `Written by Will H. in Stars & Stellar Physics` as a body-text line)
- Quotation marks wrapping the character's own prose
- Labeled-list subheaders that break narrative flow (Gio iter 2 had a literal `**Instruments Named:**` subheader)

#### Step 3c — Convergence criteria

Read the article fully (WebFetch or raw content). Evaluate against each criterion — each one is a yes/no:

1. **Length** within the character's target range (or within an explicit tolerance the user has set)
2. **Voice** matches the declared persona — first-person where appropriate, named/specific sentences, no generic encyclopedia prose
3. **Scope** — subject is within the character's declared range; no trespassing on other authors' declared turf
4. **Specificity** — required concrete elements present (named works, instruments, dates, coordinates, mission names — whatever the character's brief demands)
5. **No body-text artifacts** (see Step 3b)
6. **Prior-iteration directive followed without overcorrection** — if the last PATCH added a rule, did the model obey the letter while violating the spirit? (e.g., "name the instruments" obeyed by adding a labeled instrument list)

**Decision:**

- **All six green** → declare convergence at Step 6. Skip the PATCH.
- **One red** → that one becomes the PATCH target for Step 6.
- **Multiple red** → PATCH the single highest-leverage failure only. Note the others in the iteration summary to verify on the next pass. Do not bundle.

---

### Step 4 — Post a comment

Get a fresh JWT token using the WP service account credentials from `api/.env` (`WP_USER` / `WP_PASSWORD`):
```bash
{ssh} "cd {repo}/api && source <(grep '^WP_' .env) && \
  curl -s -X POST '{wp_base_url}/wp-json/jwt-auth/v1/token' \
    --data-urlencode \"username=\$WP_USER\" \
    --data-urlencode \"password=\$WP_PASSWORD\""
```

Post a comment on the article (`post` = WP post ID from Step 2). The comment should read like genuine reader feedback — specific to what was written, not a structured critique. Reference actual content from the article. Be constructive.

```bash
curl -s -X POST "{wp_base_url}/wp-json/wp/v2/comments" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "post": {post_id},
    "author_name": "John Turman",
    "author_email": "jturman87@gmail.com",
    "content": "{comment_text}"
  }'
```

Immediately approve it:
```bash
curl -s -X PUT "{wp_base_url}/wp-json/wp/v2/comments/{comment_id}" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"status": "approved"}'
```

---

### Step 5 — Fetch the full character object

```bash
{ssh} \
  "curl -s 'http://localhost:8000/api/v1/cms/character/by-slug/{character_slug}'"
```

Read the full character JSON. Pay special attention to:
- `description` — the character's persona and writing brief
- `searchQuery` — Tavily search query (if research is enabled)
- `searchTopicType` — `general`, `news`, etc.
- `searchTimeRange` — recency filter (e.g. `month`, `year`)
- `searchDepth` — `basic` or `advanced`
- `chunksPerSource` — how much content is extracted per source
- `researchCurrentEvents` — whether research is active at all

---

### Step 6 — Edit the character (or declare convergence)

The outcome of Step 3c dictates which branch you take.

#### Step 6a — All criteria green: declare convergence

Do not PATCH. In the Step 7 summary, explicitly state **Convergence: converged**. Explain which criteria were evaluated and why no change was warranted. Recommend moving on to the next character.

#### Step 6b — One failure mode: PATCH one directive

**Principles — read these before writing the directive:**

- **One directive per PATCH.** Do not bundle multiple corrections. If the iter-1 edit tells the character to do three new things, the iter-2 article cannot cleanly test any one of them, and regressions cannot be attributed. Pick the single highest-leverage failure and patch only that.
- **Append, don't replace.** Add a sentence (or two) to the end of the existing `description`. Descriptions accrete directives across iterations — rewriting the whole description discards the signal from prior passes. Only rewrite if the original brief is itself the problem.
- **Name the overcorrection risk inline.** Before writing a directive, ask: "what is the dumbest way a model could follow this literally?" If the dumb reading is plausible, bake the counter-phrase into the directive. Examples:
  - "Name the instruments" → model responds with a literal `**Instruments Named:**` subheader. Write instead: "Name the instruments, but weave them into flowing prose — not labeled lists."
  - "Write longer articles" → model pads with filler. Write instead: "1500–2500 words of narrative prose — length should come from named evidence and extended scenes, not padding."
  - "Stay on pre-1940 topics" → model writes the same article over and over. Write instead: "Rotate across your pre-1940 range — Tycho/Kepler, Galileo, Herschel, the Harvard computers — so subjects don't repeat."
- **Directive should be falsifiable.** The next article should either clearly obey or clearly violate. Vague directives ("write with more verve") can't be tested.

**When to edit `description`:**
- Writing style, tone, focus, expertise level, or perspective is off
- Articles are too generic or fail to reflect the character's voice
- Character needs to emphasize specific subtopics or avoid others
- Character is trespassing on another author's declared scope

**When to edit search settings:**
- Articles are thin or poorly researched → try `searchDepth: advanced`, increase `chunksPerSource`
- Research is too stale → tighten `searchTimeRange`
- Wrong topics surfacing → rewrite `searchQuery` to be more precise
- Research isn't enabled but would help → set `researchCurrentEvents: true` and add a `searchQuery`

Modify only the fields that need changing. Keep all other fields exactly as they are.

Build the updated character JSON in full (all fields, not just the changed ones — the PATCH replaces the whole object).

PATCH the character. The body must be wrapped in `{"character": {...}}` per `PatchCharacterRequest`:
```bash
{ssh} \
  "curl -s -X PATCH 'http://localhost:8000/api/v1/cms/character/' \
    -H 'Origin: https://{cb_host}' \
    -H 'Content-Type: application/json' \
    -d '{\"character\": {full_character_json}}'"
```

Confirm the response shows the updated fields.

---

### Step 7 — Iteration summary

After each iteration, report:

1. **Article:** URL and one-line title
2. **Review findings:** 2–4 bullet points on what stood out (good and bad), framed against the six convergence criteria
3. **Comment posted:** first sentence of the comment
4. **Character changes:** exactly what was changed and the reasoning, or "none — converged"
5. **Convergence:** one of:
   - `converged` — no PATCH, move to the next character
   - `one directive patched — {short name}` — iterating
   - `multiple failure modes — patched {X}, deferred {Y, Z}` — iterating, note what was deferred
6. **Expected improvement:** what should be different in the next article (skip if converged)

If the character has converged, stop iterating — even if the user requested more iterations. Report convergence and recommend moving on.

If more iterations remain and the character has not converged, pause briefly and confirm before proceeding (unless the user said to run all iterations without pausing).

---

## Final summary and convergence status (after all iterations)

For each character that was iterated, report:

- **Character:** name
- **Iterations run:** count
- **Converged:** yes / no — and at which iteration
- **Net directives added:** one-line summary of each PATCH
- **Direction of improvement:** what got better across the arc

If any character did not converge within the iterations run, flag it explicitly and suggest what to try next (usually: a structural change like search config, or a scope/range conflict with another character on the same site).

Youez - 2016 - github.com/yon3zu
LinuXploit