OR Key
drop another .md file to compare - side-by-side diff against content

content

Turns your meetings and ideas into finished content, end to end.
description: "Triggers on prompt mention of 'content'."
personal 2 files 10 recent evals

What it does for you

Turns your meetings and ideas into finished content, end to end.

What it produces

A recent result, so you can see the kind of work it returns.

loading…

How to get it

These run inside the Snappy workspace. Want this working in your business? I set skills like this up with you, in one focused week.

Work with me
For developers how this skill is built, graded, and how it runs

at a glance- the short version

actorExported functions in state/lib/content.ts.
auditorNone wired yet - eval is manual (Robert review).
eval modeshape
categoryContent
stages3

what's inside - the parts that make up a skill 3/4 present

A skill is just a few plain-text files. Only the main one is required. The rest are optional, added as the work needs them. This is what the skill is made of; how it runs is just below.

The skill
state/skills/content/SKILL.md present
the skill itself, in plain text
The main file. It says what the skill is and lays out the steps in plain English.
Code
state/lib/content.ts present
code the skill can run
Reusable code this skill can call when it needs to.
Scripts
state/bin/content/ not present
helper scripts
Optional. Added when a skill has a few commands to run.
Loader
state/skills/content/AGENTS.md present
what the AI loads on the fly
Loaded automatically the moment this skill is needed. Kept short on purpose.

how it's graded - what counts as a good run 4 criteria · 2 deterministic · 2 judge

Each row is one thing a good run has to get right. deterministic means a quick check decides, pass or fail. judge means the AI reads the result and rates it. Grading each piece on its own (instead of one overall score) shows exactly where a run fell short, so the fix is obvious.

name
kind
check
calls_content_functions
judge
The skill calls at least one of the exported functions from 'state/lib/content.ts' based on the skill's input.
handles_required_inputs
deterministic
The skill execution does not error when 'getContentAtoms_input', 'listDrafts_input', and 'createDraft_input' are provided with valid string values.
log_entry_created
deterministic
A new row is appended to 'state/log/pending-eval.ndjson' for each skill execution.
matches_snapshot_output
judge
The output of the skill matches a pre-established snapshot for similar inputs, indicating consistent behavior.

how it runs - the shared frame every skill uses 5/5 present

Every skill runs the same way. One part does the work, a separate part checks it, and a short loader hands the AI exactly what it needs for the job. Anything this skill doesn't use shows a one-line note saying why, on purpose, not by accident.

makes the work The worker
present
Exported functions in state/lib/content.ts. the worker
Does the actual work. Whatever it produces is what gets checked next.
checks the work The reviewer
present
None wired yet - eval is manual (Robert review). the checker
A separate checker grades the work, so the part that made it can't approve its own work.
frame
learns Self-correction
present
fixes itself learns from gaps
When a run hits a gap, the skill gets edited on the spot [FIXED] or queued for a bigger rewrite [LOGGED], so it keeps getting better.
tidies up Background fixes
present
queued for rewrite runs in the background
Bigger fixes that can't be made on the spot get queued and rewritten in the background later.
remembers Run history
present
state/log/pending-eval.ndjson pending runs
Every run is written down here, then reviewed by hand each week.
Critical rules the things this skill must not get wrong
No must-not-break rules called out for this skill. Anything important lives in the writeup below.

what it has learned - fixes written back in over time sample

When a run hits something this skill didn't handle, the fix gets written back into the skill so it doesn't happen again. FIXED means it was corrected on the spot. LOGGED means it's queued for a bigger rewrite. Either way, the skill gets a little better and never makes the same mistake twice.

  1. Loading feedback rows…

how the work flows- who makes it, who checks it

actor Exported functions in state/lib/content.ts.
1 generator
invoke
actor = Exported functions in state/lib/content.ts.
import from `state/lib/content.ts` (e.g. `getContentAtoms`, `listDrafts`, `createDraft`, `tickDraft`)
auditor None wired yet - eval is manual (Robert review).
2 auditor
inspect
auditor = None wired yet - eval is manual (Robert review).
read-only `listDrafts()` smoke before any `createDraft` / `tickDraft
3 data
eval log
`state/log/pending-eval.ndjson` (manual eval — skill: "content")

SKILL.md- the skill, written out in plain English

content

Content production orchestrator for all snappy-* skills.

Ported from kernel snappy-content in Phase 0.5. See state/lib/content.ts for the full API surface.

Steps

  • getContentAtoms() - see state/lib/content.ts
  • listDrafts() - see state/lib/content.ts
  • createDraft() - see state/lib/content.ts
  • status() - see state/lib/content.ts
  • tickDraft() - see state/lib/content.ts
  • computeContentMetric() - see state/lib/content.ts
  • listContacts() - see state/lib/content.ts

Eval

Actor: the exported functions in state/lib/content.ts. Auditor: none wired yet - eval is manual (Robert review). File a state/log/pending-eval.ndjson row on each run.

Score convention:

OutcomeScore
Pass on first try1.0
Failed first, auto-fix applied, re-check passed0.5
Still failing or unrecoverable0.0

Gotchas

via the Phase 0.5 driver. Only these rewrites were applied: already in state/lib/)

  1. realpathSync(process.argv[1]) CLI guard wrapped in try/catch
  • See the kernel SKILL.md for the original long-form guidance if you need it

(read-only reference at the kernel path above).

Graduation

This skill is prose. Graduate by defining a deterministic auditor and flipping eval: auto.

Rubric

criteria:
  - name: calls_content_functions
    kind: judge
    check: "The skill calls at least one of the exported functions from 'state/lib/content.ts' based on the skill's input."
  - name: handles_required_inputs
    kind: deterministic
    check: "The skill execution does not error when 'getContentAtoms_input', 'listDrafts_input', and 'createDraft_input' are provided with valid string values."
  - name: log_entry_created
    kind: deterministic
    check: "A new row is appended to 'state/log/pending-eval.ndjson' for each skill execution."
  - name: matches_snapshot_output
    kind: judge
    check: "The output of the skill matches a pre-established snapshot for similar inputs, indicating consistent behavior."

AGENTS.md- what the AI loads when this skill comes up

content - loader

Per-turn rules for the content skill. Full reference: state/skills/content/SKILL.md. Do not skip these.

Critical Rules

_(no failures recorded yet - this skill is a Phase 0.5 prose port from kernel snappy-content. No hard-won rules have surfaced yet. Read state/skills/content/SKILL.md and state/lib/content.ts before invoking. The kernel had a stray realpathSync import bug - patched in port.)_

Commands

| ui dashboard | state/skills/content/resources/ui.openui | |invoke: import from state/lib/content.ts (e.g. getContentAtoms, listDrafts, createDraft, tickDraft) |verify: read-only listDrafts() smoke before any createDraft / tickDraft |eval log: state/log/pending-eval.ndjson (manual eval - skill: "content")

OpenUI Resource

  • Skill-owned OpenUI Lang resource: state/skills/content/resources/ui.openui. Read it before rendering or editing this skill's generated component surface.
  • Treat this resource as a first-class artifact of the skill, not a generic chat response. Improve it when the skill's user-facing output needs to become richer.
  • System resources compose OpenUI primitives and inherit SnappyChat tokens. Use ui_contract: branded in SKILL.md only for deliberate platform or client visuals.

Known Pitfalls

  • Drafts move through tickDraft() - don't mutate state directly, use the orchestrator fn so chain log captures the transition
  • Atoms come from content-mine (which has citation + Robert-signal gates) - if you build a draft from atoms, don't strip the source_ref

Self-Test

An agent reading this should correctly:

  1. [ ] Use state/lib/content.ts functions, not direct DB writes
  2. [ ] Call tickDraft() to advance state, not mutate fields by hand
  3. [ ] Log to pending-eval.ndjson until an auditor is wired

Self-report

If this loader fell short, append a line:

echo "[$(date -u +%FT%TZ)] content: <what was missing>" >> state/log/loader-feedback.log

<!-- kernel-ok: historical attribution for the PID-loader pattern. -->

Self-correcting loader (PID feedback)

This is the rule that makes loaders learn. Ported from snappy-kernel, where it demonstrably worked: the snappy-dom-cartographer skill mapped Skool admin UI once, every later ship-agent inherited the full map for free.

Before you finish, do two things:

1. Fix gaps (P - proportional). MANDATORY. If this loader didn't cover your case - if you had to read another file, run an undocumented command, work around a wrong selector, or discover a quirk - you MUST attempt an Edit to this AGENTS.md before you log.

  • Default action: edit this file. Surgical. Add the missing line, table

row, or code example where it belongs. No restructuring.

  • Only LOGGED is allowed when: the fix needs >10 lines, spans multiple

files, or requires a structural rewrite. In that case the state/regen/drain.sh queue picks it up asynchronously.

  • "I didn't have time" / "it's minor" / "the next agent will figure it out"

are NOT valid reasons. Minor gaps compound - that's the whole point of this loop.

  • Concrete example of a valid P-fix: discover agent-browser screenshot

takes path positionally not as --output <path>. One line in a table. Edit it in. Do not just log it.

  • The goal: the next agent never has to leave the loader.

2. Log the result.

echo "[$(date -u +%FT%TZ)] content: <what was missing or fixed> [FIXED|LOGGED]" >> state/log/loader-feedback.log
  • FIXED = you patched this loader inline (P-fix).
  • LOGGED = too large for inline; the PostToolUse enqueue + Stop-hook drain

will rewrite the loader from scratch on next session-end.

Do not skip this. Every agent run must leave the system better than it found it. The loader is the setpoint; you are the sensor; the gap is the error signal; closing the gap is the correction.

api.ts- the code it can call

#!/usr/bin/env npx tsx
/**
 * snappy-content/api.ts -- Content production orchestrator for all snappy-* skills.
 *
 * Re-exports from knowledge + provides helpers for querying the content engine DB
 * (content_atoms on rb-content-engine.fly.dev).
 *
 * Usage:
 *   npx tsx api.ts atoms draft        # list draft content atoms
 *   npx tsx api.ts atoms approved     # list approved content atoms
 *   npx tsx api.ts atoms archived     # list archived content atoms
 *
 * Or import as module:
 *   import { listContacts, getContentAtoms } from "./content.ts";
 */

import { env } from "./env.ts";
import { readdirSync, readFileSync, writeFileSync, appendFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import {
  checkTone,
  requireCitations,
  checkFlow,
} from "./positioning.ts";
import { realpathSync } from "fs";
import {
  schedulePost,
  patchDraft,
  getDraft,
  deleteDraft,
  listScheduled,
} from "./linkedin.ts";

// --- Re-exports from child skills ---

export { listContacts } from "./knowledge.ts";

// --- Content Engine DB ---

const CONTENT_ENGINE = "https://rb-content-engine.fly.dev/sql";

/**
 * Query the content engine DB for content atoms by status.
 * Returns rows from content_atoms table.
 */
export async function getContentAtoms(status = "draft") {
  const res = await fetch(CONTENT_ENGINE, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      query: "SELECT id, type, draft, speaker, topic, tags, frequency, status, times_used, created_at FROM content_atoms WHERE status = $1 ORDER BY created_at DESC LIMIT 50",
      params: [status],
    }),
  });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Content engine query failed (${res.status}): ${text}`);
  }
  return res.json();
}

// --- Content PID loop ---
// Mirrors the classroom loop: disk frontmatter is source of truth, pass-log
// is the append-only decision trace, Typefully is the publishing target.
// See data/README.md for the schema and invariants.

const THIS_DIR = dirname(fileURLToPath(import.meta.url));
const DRAFTS_DIR = join(THIS_DIR, "data", "drafts");
const PASS_LOG = join(THIS_DIR, "data", "pass-log.md");

export type DraftStatus = "drafted" | "scheduled" | "shipped" | "retired";

export type DraftFrontmatter = {
  slug: string;
  topic: string;
  status: DraftStatus;
  typefully_id: number;
  publish_at: string;
  iteration: number;
  last_tick_at: string;
  last_gate_check: "" | "pass" | "fail";
  last_gate_cite: "" | "pass" | "fail";
  last_gate_flow: "" | "pass" | "fail";
  flow_profile: string;
  media_id: string;
  cite_mode: "verbatim" | "practitioner";
  first_comment: string;
};

export type Draft = {
  path: string;
  fm: DraftFrontmatter;
  body: string;
};

function parseFm(raw: string): { fm: DraftFrontmatter; body: string } {
  const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
  if (!match) throw new Error("missing frontmatter block");
  const fm: Record<string, unknown> = {};
  for (const line of match[1].split("\n")) {
    const kv = line.match(/^(\w+):\s*(.*)$/);
    if (!kv) continue;
    let v: unknown = kv[2].trim().replace(/^["']|["']$/g, "");
    if (v === "true") v = true;
    else if (v === "false") v = false;
    else if (typeof v === "string" && /^-?\d+$/.test(v)) v = Number(v);
    fm[kv[1]] = v;
  }
  return { fm: fm as DraftFrontmatter, body: match[2] };
}

function serializeFm(fm: DraftFrontmatter, body: string): string {
  const order: (keyof DraftFrontmatter)[] = [
    "slug", "topic", "status", "typefully_id", "publish_at",
    "iteration", "last_tick_at", "last_gate_check", "last_gate_cite",
    "last_gate_flow", "flow_profile", "media_id", "cite_mode", "first_comment",
  ];
  const lines = order.map((k) => {
    const v = fm[k];
    if (typeof v === "string") return `${k}: "${v}"`;
    return `${k}: ${v}`;
  });
  return `---\n${lines.join("\n")}\n---\n${body.startsWith("\n") ? body : "\n" + body}`;
}

export function listDrafts(): Draft[] {
  let files: string[];
  try {
    files = readdirSync(DRAFTS_DIR).filter((f) => f.endsWith(".md"));
  } catch {
    return [];
  }
  return files.map((f) => {
    const path = join(DRAFTS_DIR, f);
    const raw = readFileSync(path, "utf8");
    const { fm, body } = parseFm(raw);
    return { path, fm, body };
  });
}

function writeDraft(d: Draft): void {
  writeFileSync(d.path, serializeFm(d.fm, d.body));
}

export function createDraft(
  slug: string,
  topic: string,
  body: string,
  opts: { flowProfile?: string; citeMode?: "verbatim" | "practitioner" } = {},
): Draft {
  const fm: DraftFrontmatter = {
    slug,
    topic,
    status: "drafted",
    typefully_id: 0,
    publish_at: "",
    iteration: 0,
    last_tick_at: "",
    last_gate_check: "",
    last_gate_cite: "",
    last_gate_flow: "",
    flow_profile: opts.flowProfile || "balanced",
    media_id: "",
    cite_mode: opts.citeMode || "verbatim",
    first_comment: "",
  };
  const path = join(DRAFTS_DIR, `${slug}.md`);
  const d: Draft = { path, fm, body };
  writeDraft(d);
  return d;
}

function logPass(entry: string): void {
  appendFileSync(PASS_LOG, "\n" + entry + "\n");
}

/** Read-only status view over the drafts directory. */
export function status() {
  const drafts = listDrafts();
  const buckets: Record<DraftStatus, Draft[]> = {
    drafted: [],
    scheduled: [],
    shipped: [],
    retired: [],
  };
  for (const d of drafts) {
    const s = d.fm.status;
    if (buckets[s]) buckets[s].push(d);
  }
  return {
    total: drafts.length,
    drafted: buckets.drafted.map((d) => ({ slug: d.fm.slug, topic: d.fm.topic, iteration: d.fm.iteration })),
    scheduled: buckets.scheduled.map((d) => ({ slug: d.fm.slug, topic: d.fm.topic, publish_at: d.fm.publish_at, typefully_id: d.fm.typefully_id, iteration: d.fm.iteration })),
    shipped: buckets.shipped.map((d) => ({ slug: d.fm.slug, topic: d.fm.topic, publish_at: d.fm.publish_at })),
    retired: buckets.retired.map((d) => ({ slug: d.fm.slug, topic: d.fm.topic })),
  };
}

function runGates(body: string, profile: string, citeMode: string = "verbatim") {
  const tone = checkTone(body);
  const cite = citeMode === "practitioner"
    ? { pass: true, reasons: [] as string[] }
    : requireCitations(body);
  const flow = checkFlow(body, { profile });
  return {
    check: tone.pass ? "pass" : "fail",
    cite: cite.pass ? "pass" : "fail",
    flow: flow.pass ? "pass" : "fail",
    reasons: [
      ...tone.violations.map((r) => `check: ${r}`),
      ...cite.reasons.map((r) => `cite: ${r}`),
      ...flow.violations.map((r) => `flow: ${r}`),
    ],
  } as const;
}

/**
 * Single PID tick for one draft. Idempotent. Returns a decision record.
 *
 * Rules:
 * - drafted + all gates pass → schedule to Typefully at `when`, write back id+publish_at.
 * - scheduled + text changed → patch Typefully in place (bumps iteration).
 * - scheduled + gates fail on current body → log gap, do not publish.
 * - shipped/retired → no-op.
 * - If `dryRun`, runs gates and decides but makes no remote or disk writes.
 */
export async function tickDraft(
  slug: string,
  opts: { when?: string; dryRun?: boolean } = {},
) {
  const drafts = listDrafts();
  const d = drafts.find((x) => x.fm.slug === slug);
  if (!d) throw new Error(`draft not found: ${slug}`);

  const now = new Date().toISOString();
  const profile = d.fm.flow_profile || "balanced";
  const citeMode = d.fm.cite_mode || "verbatim";
  const gates = runGates(d.body, profile, citeMode);

  const decision: {
    slug: string;
    action: "schedule" | "patch" | "noop" | "gap";
    gates: typeof gates;
    typefully_id?: number;
    publish_at?: string;
    note: string;
  } = { slug, action: "noop", gates, note: "" };

  if (d.fm.status === "shipped" || d.fm.status === "retired") {
    decision.note = `status=${d.fm.status}; nothing to do`;
    return decision;
  }

  if (gates.check !== "pass" || gates.cite !== "pass" || gates.flow !== "pass") {
    decision.action = "gap";
    decision.note = `gates failed: ${gates.reasons.join(" | ")}`;
    if (!opts.dryRun) {
      d.fm.last_tick_at = now;
      d.fm.last_gate_check = gates.check;
      d.fm.last_gate_cite = gates.cite;
      d.fm.last_gate_flow = gates.flow;
      writeDraft(d);
      logPass(tickEntry(d, decision));
    }
    return decision;
  }

  if (d.fm.status === "drafted") {
    const when = opts.when
      || d.fm.publish_at
      || new Date(Date.now() + 3 * 24 * 3600 * 1000).toISOString().replace(/\.\d+Z$/, "Z");
    decision.action = "schedule";
    decision.publish_at = when;
    decision.note = `schedule new draft at ${when}`;
    if (!opts.dryRun) {
      const mediaIds = d.fm.media_id ? [d.fm.media_id] : undefined;
      const firstComment = d.fm.first_comment || undefined;
      const created = (await schedulePost(stripCiteMarkup(d.body), when, true, mediaIds, firstComment)) as { id: number };
      d.fm.status = "scheduled";
      d.fm.typefully_id = created.id;
      d.fm.publish_at = when;
      d.fm.iteration += 1;
      d.fm.last_tick_at = now;
      d.fm.last_gate_check = "pass";
      d.fm.last_gate_cite = "pass";
      d.fm.last_gate_flow = "pass";
      decision.typefully_id = created.id;
      writeDraft(d);
      logPass(tickEntry(d, decision));
    }
    return decision;
  }

  // status === "scheduled"
  const remote = (await getDraft(d.fm.typefully_id)) as any;
  const remoteText: string = remote?.platforms?.linkedin?.posts?.[0]?.text ?? "";
  const desiredText = stripCiteMarkup(d.body);
  if (remoteText === desiredText) {
    decision.note = "scheduled text matches local; no patch needed";
    if (!opts.dryRun) {
      d.fm.last_tick_at = now;
      writeDraft(d);
      logPass(tickEntry(d, decision));
    }
    return decision;
  }

  decision.action = "patch";
  decision.typefully_id = d.fm.typefully_id;
  decision.note = `patching remote draft ${d.fm.typefully_id} in place`;
  if (!opts.dryRun) {
    const patchFirstComment = d.fm.first_comment || undefined;
    await patchDraft(d.fm.typefully_id, { text: desiredText, firstComment: patchFirstComment });
    d.fm.iteration += 1;
    d.fm.last_tick_at = now;
    writeDraft(d);
    logPass(tickEntry(d, decision));
  }
  return decision;
}

function stripCiteMarkup(body: string): string {
  return body
    .replace(/\[source:[^\]]*\]/g, "")
    .replace(/<\/?connective>/gi, "")
    .replace(/[ \t]+\n/g, "\n")
    .replace(/[ \t]{2,}/g, " ")
    .trim();
}

function tickEntry(d: Draft, decision: { action: string; note: string; gates: { check: string; cite: string; flow: string } }): string {
  const now = new Date().toISOString();
  return [
    `## ${now.slice(0, 10)} content-loop`,
    `- **Tick kind**: ${decision.action}`,
    `- **Draft**: ${d.fm.slug} (typefully_id=${d.fm.typefully_id || "none"})`,
    `- **Gates**: check ${decision.gates.check}, cite ${decision.gates.cite}, flow/${d.fm.flow_profile} ${decision.gates.flow}`,
    `- **Decision**: ${decision.note}`,
  ].join("\n");
}

// --- Metrics (Step 7a) ---

const STAGED_ACTIONS_LOG = `${process.env.HOME}/.claude/logs/staged-actions.ndjson`;

type StagedRun = { ts: string; name: string; action: string };

function readStagedRunsContent(): StagedRun[] {
  try {
    const lines = readFileSync(STAGED_ACTIONS_LOG, "utf-8").split("\n");
    const out: StagedRun[] = [];
    for (const line of lines) {
      if (!line.trim()) continue;
      try {
        const j = JSON.parse(line);
        if (typeof j?.name === "string" && typeof j?.ts === "string") {
          out.push({ ts: j.ts, name: j.name, action: j.action || "" });
        }
      } catch { /* skip malformed */ }
    }
    return out;
  } catch { return []; }
}

function withinLastDays(tsIso: string, days: number): boolean {
  const t = new Date(tsIso).getTime();
  if (isNaN(t)) return false;
  return t >= Date.now() - days * 86400_000;
}

export function computeContentMetric(name: string): number | null {
  const runs = readStagedRunsContent().filter((r) => withinLastDays(r.ts, 7));
  switch (name) {
    case "polish-per-week":
    case "content_polish_runs_per_week":
      return runs.filter((r) => r.name === "content-polish").length;
    case "polish-apply-rate":
    case "content_polish_apply_rate": {
      const polish = runs.filter((r) => r.name === "content-polish");
      if (!polish.length) return null;
      const delivered = polish.filter((r) => r.action === "delivered").length;
      return delivered / polish.length;
    }
    case "atoms-per-week":
    case "content_mine_atoms_per_week":
      return runs.filter((r) => r.name === "content-mine").length;
    default:
      return null;
  }
}

// --- CLI ---

if ((() => { try { return import.meta.url === `file://${realpathSync(process.argv[1])}`; } catch { return false; } })()) {
  (async () => {
    const [, , cmd, ...args] = process.argv;

    switch (cmd) {
      case "metrics": {
        const [name, ...rest] = args;
        if (!name) {
          console.error("Usage: api.ts metrics <name> [--json]");
          console.error("Names: polish-per-week, polish-apply-rate, atoms-per-week");
          process.exit(1);
        }
        const value = computeContentMetric(name);
        if (rest.includes("--json")) console.log(JSON.stringify({ value }));
        else console.log(value == null ? "null" : String(value));
        break;
      }
      case "atoms": {
        const status = args[0] || "draft";
        const data = await getContentAtoms(status);
        if (Array.isArray(data) && data.length) {
          for (const atom of data) {
            console.log(`[${atom.id}] ${atom.type || "untyped"} -- ${atom.topic || "(no topic)"} (${atom.status})`);
            if (atom.draft) console.log(`  ${atom.draft.slice(0, 120)}...`);
          }
        } else {
          console.log(JSON.stringify(data, null, 2));
        }
        break;
      }
      case "status": {
        const s = status();
        console.log(`[loop-status] total=${s.total}`);
        console.log(`  drafted:   ${s.drafted.length}`);
        for (const d of s.drafted) console.log(`    - ${d.slug} (iter=${d.iteration}) — ${d.topic}`);
        console.log(`  scheduled: ${s.scheduled.length}`);
        for (const d of s.scheduled) console.log(`    - ${d.slug} (iter=${d.iteration}) id=${d.typefully_id} at=${d.publish_at} — ${d.topic}`);
        console.log(`  shipped:   ${s.shipped.length}`);
        for (const d of s.shipped) console.log(`    - ${d.slug} at=${d.publish_at} — ${d.topic}`);
        console.log(`  retired:   ${s.retired.length}`);
        for (const d of s.retired) console.log(`    - ${d.slug} — ${d.topic}`);
        break;
      }
      case "tick": {
        let slug: string | undefined;
        let when: string | undefined;
        let dryRun = false;
        for (let i = 0; i < args.length; i++) {
          if (args[i] === "--when") { when = args[++i]; }
          else if (args[i] === "--dry-run") { dryRun = true; }
          else if (!slug) { slug = args[i]; }
        }
        if (!slug) { console.error("Usage: api.ts tick <slug> [--when ISO8601Z] [--dry-run]"); process.exit(1); }
        const decision = await tickDraft(slug, { when, dryRun });
        console.log(JSON.stringify(decision, null, 2));
        break;
      }
      case "tick-all": {
        const dryRun = args.includes("--dry-run");
        const results = [];
        for (const d of listDrafts()) {
          if (d.fm.status === "shipped" || d.fm.status === "retired") continue;
          results.push(await tickDraft(d.fm.slug, { dryRun }));
        }
        console.log(JSON.stringify(results, null, 2));
        break;
      }
      case "draft-new": {
        let slug: string | undefined;
        let topic = "";
        let bodyFile: string | undefined;
        let profile = "balanced";
        let citeMode: "verbatim" | "practitioner" | undefined;
        for (let i = 0; i < args.length; i++) {
          if (args[i] === "--topic") { topic = args[++i]; }
          else if (args[i] === "--body-file") { bodyFile = args[++i]; }
          else if (args[i] === "--profile") { profile = args[++i]; }
          else if (args[i] === "--cite-mode") { citeMode = args[++i] as "verbatim" | "practitioner"; }
          else if (!slug) { slug = args[i]; }
        }
        if (!slug || !bodyFile) { console.error("Usage: api.ts draft-new <slug> --body-file <path> --topic <topic> [--profile balanced] [--cite-mode verbatim|practitioner]"); process.exit(1); }
        const body = readFileSync(bodyFile, "utf8");
        const d = createDraft(slug, topic, body, { flowProfile: profile, citeMode });
        console.log(JSON.stringify({ slug: d.fm.slug, path: d.path, status: d.fm.status }));
        break;
      }
      default:
        console.log("Usage: npx tsx api.ts <atoms|status|tick|tick-all|draft-new> [...args]");
    }
  })();
}

scripts- helper scripts it can run

prose-only skill - 1 inline code block live in SKILL.md above (no state/bin/ sidecar yet).

how we check it- the checks, plus the last 10 runs

rubric shape schema-shape check (no inline rubric)
recent mean 1.00 · 10 runs actor/auditor: unverifiable
deps none declared
timestamp verb score primary_issue artifact
2026-04-25 04:11Z - 1.00 - -
2026-04-21 15:58Z - 1.00 - -
2026-04-21 15:56Z - 1.00 - -
2026-04-21 03:53Z - 1.00 - -
2026-04-25 04:11Z - 1.00 - -
2026-04-21 15:58Z - 1.00 - -
2026-04-21 15:56Z - 1.00 - -
2026-04-21 03:53Z - 1.00 - -
2026-04-25 04:11Z - 1.00 - -
2026-04-21 15:58Z - 1.00 - -