name: rr description: Rahman Resources (rr) operator — context-aware skill for managing vertical slices across the rr monorepo and consumer projects. Detects cwd, runs the right verb. Replaces legacy /rr-prep, /rr-send, /rr-adopt skills (deleted 2026-05-16 with BSDL teardown).
/rr — Rahman Resources operator skill
Global skill. Works from ~/projects/resources/ (rr repo) AND from any consumer project. Detects context from cwd, then routes to the right action.
When user invokes
/rr— show status of current location (am I in rr or consumer? what's installed?)/rr list— show all available slices in rr catalog/rr info <slug>— full metadata for one slice/rr add <slug>— install slice from rr into consumer (or scaffold new in rr)/rr update <slug>— re-pull newer version (overwrite, warn on local edit)/rr lift <slug>— copy mature slice from consumer UP to rr (operator)/rr changelog— append a release entry covering current diff (mandatory before publish)/rr publish— bump CLI/MCP version + push + npm publish (operator, rr only)- Natural language: "bawa fitur cv-generator dari CareerPack ke rr", "install command-menu ke project ini", "update audit-log"
Step 1 — detect context
Run first:
pwd
test -d ./packages/cli && test -f ./CLAUDE.md && head -1 ./CLAUDE.md | grep -q "Rahman Resources" && echo "MODE: rr-repo" || echo "MODE: consumer"
Also check for sentinel files:
- rr-repo mode if cwd contains:
packages/cli/,frontend/slices/,CLAUDE.mdstarts with "# CLAUDE.md — Rahman Resources" - consumer mode if cwd contains:
slices/(nofrontend/prefix) ORfrontend/slices/but NOpackages/cli/ - unknown otherwise — ask user which mode
Show banner: ▸ /rr running in [rr-repo|consumer] mode — cwd=<path>
Step 2 — route by verb
Verb: (none) — show status
rr-repo mode:
ls frontend/slices/ | grep -v _ | wc -l # how many slices
grep -c "slug:" lib/content/slices.ts # catalog entries
git log --oneline -5 # recent activity
node packages/cli/scripts/validate-slice-parity.mjs 2>&1 | tail -3
consumer mode:
ls slices/ 2>/dev/null | head -20 # installed slices
ls shared/ 2>/dev/null | head -10 # cascaded shared
Verb: list
npx rahman-resources list slices
(Or, if rr-repo mode and no internet: grep slug: lib/content/slices.ts)
Verb: info
npx rahman-resources info <slug>
Verb: add
consumer mode:
npx rahman-resources add <slug>
Then verify:
ls slices/<slug>/
test -f slices/<slug>/slice.json && echo "✓ installed"
In rr-repo mode add is a no-op — use scaffold-slice instead (see below).
Verb: init
Bootstraps a fresh Next 16 + Convex + Tailwind 4 consumer project. Scaffolds package.json + tsconfig + Tailwind config + rr.json manifest
- shadcn components.json + auth wiring (no Clerk —
@convex-dev/auth).
npx rahman-resources init <app-name> # alias: npx rr init <app-name>
cd <app-name>
pnpm install
pnpm exec convex dev # if Convex backend wanted
Output rr.json is the consumer manifest schema (packages/cli/lib/rr-schema.json)
used by every subsequent rr add / rr update call.
Operator-only when scaffolding for someone else; otherwise users run themselves.
Verb: scaffold-slice (rr-repo mode only)
Creates a new slice skeleton under frontend/slices/<slug>/ with
the canonical layout (slice.json + slice.contract.ts + slice.manifest.json
- config.ts + index.ts + agent.md + components/ + lib/). Wires the new
slug into
lib/content/slices.tsas a TODO entry the operator fills in. Skips the cp-r ritual when the slice is greenfield (rare — preferliftfrom a consumer when source exists).
npx rahman-resources scaffold-slice <slug>
Pre-flight: pick a category (ui / data / auth / infra / ai /
content / payment / email / realtime / search). Decide
kind: ui (no Convex), full (Convex + UI), convex (backend
only).
After scaffolding:
- Fill in
slice.jsondescription + title + tags - Write
slice.contract.tswithdefineSliceContract({...}) - Add SliceEntry to
lib/content/slices.ts(REQUIRED —npm run audit:slicesgates) - Append changelog entry in
lib/content/changelog.ts - Run
node scripts/features/gen-slice-agent-md.mjs+node packages/cli/scripts/gen-manifest.mjs - Verify:
npm run validate:all
Verb: update
consumer mode:
npx rahman-resources update <slug>
If conflicts (local edits), CLI warns. Operator decides: keep local OR overwrite.
Verb: lift (consumer → rr)
ONLY from consumer mode. Promotes a mature slice from current consumer up to rr.
Pre-flight checklist (do BEFORE running):
- Slice has no Clerk imports
- Slice files all ≤200 LOC (audit-file-size hard gate)
- No hardcoded consumer-specific terms (project name, business strings)
- No cross-slice imports (must use
@/components/ui/*,@/shared/*, or@/features/<own-slug>/*) —@/components/templates/_shared/*is an explicit cross-slice peer prefix allowed for slices that depend on template-shared primitives (CRUD, landing-sections, pages) - Convex tables (if any) prefixed
<slug>_*withby_workspaceindex
Then:
# 1. Manual copy (the BSDL pipeline was torn down 2026-05-16; manual is the way)
SRC=$(pwd)/slices/<slug> # or frontend/slices/<slug> per consumer convention
DST=~/projects/resources/frontend/slices/<slug>
cp -r "$SRC" "$DST"
# 2. Rewrite imports in rr-side copy if needed
# Example: replace consumer-specific aliases with rr-standard
# (do this with sed or Edit tool per file)
# 3. Add metadata if missing
cd ~/projects/resources/frontend/slices/<slug>
# Required: slice.json (slug, version, deps, paths)
# Recommended: slice.contract.ts (typed DSL)
# For CLI distribution: slice.manifest.json
# 4. Validate
cd ~/projects/resources
npm run audit:slices
npm run validate:all
# 5. Update catalog
# Edit lib/content/slices.ts — add SliceEntry for <slug>
# Regenerate manifest (run from repo root — same path used everywhere else)
node packages/cli/scripts/gen-manifest.mjs
# 6. MANDATORY — append changelog entry (see Changelog discipline below)
# Edit lib/content/changelog.ts — add a release entry with bullet
# referencing { text: "<slug> — lifted from <consumer>", slug: "<slug>" }
# 7. Commit + push
git add frontend/slices/<slug> lib/content/slices.ts \
packages/cli/lib/manifest.json lib/content/changelog.ts
git commit -m "feat(<slug>): lift from <consumer-name>"
git push origin main
If user wants the lift published as a new CLI version, also bump version (see publish verb).
Verb: changelog (rr-repo mode)
MANDATORY before publish. Every wave/ship that touches a slice or
template needs an entry so consumers and the /changelog page can see
what changed and <RecentlyUpdatedBadge> lights up on detail pages.
Open lib/content/changelog.ts and prepend a new ChangelogEntry:
{
id: "<wave-or-version>", // anchor target — must match /changelog#<id>
version: "<wave-or-version>",
date: Date.parse("YYYY-MM-DD"),
kind: "feature" | "improvement" | "fix" | "chore" | "breaking",
title: "Short one-line summary",
body: "Why-not-what paragraph. Cover the spirit of the wave.",
groups: [
{
heading: "Slices touched",
bullets: [
{ text: "<slug> — what changed", slug: "<slug>" },
],
},
{
heading: "Templates touched",
bullets: [
{ text: "<slug> — what changed", slug: "<slug>", kind: "template" },
],
},
{
heading: "Site",
bullets: [
"Plain string for non-catalog notes (e.g. new helper added)",
],
},
],
},
Schema rules:
- Bullet
{ text, slug }(nokindfield) defaults tokind: "slice"→ links to/slices/<slug>. Setkind: "template"for layouts. hreffield can override the generated URL entirely (rare — for external docs links).- Plain strings still work for items that don't map to a catalog entry.
- The
idfield becomes the<RecentlyUpdatedBadge>deep-link target via/changelog#<id>(scroll-mt-24 already wired).
After editing, verify with the repo's real gate (don't hand-craft
grep -v filters for slices that may not exist):
npm run validate:all # tsc + slice/contract/manifest audits
Verb: publish (rr-repo mode only)
Operator-only. Bumps + publishes CLI and/or MCP to npm.
Before publishing — ensure the changelog entry exists for the
versions being published. CI does NOT enforce this yet but the
<RecentlyUpdatedBadge> will be wrong if you skip it.
⚠ prepublishOnly gate — TS↔slice.json parity (learned 2026-05-28).
packages/cli's prepublishOnly runs sync-skills --check +
validate + validate-slice --check + validate-slice-parity +
validate-structure. npm publish ABORTS on the first failure, so a
drift you didn't cause can block your publish. The two that bite:
validate-slice— everyfrontend/slices/*/slice.jsonmust satisfy the schema. Common break:deps.peersmust be[{slug,range,reason}]objects, NOT bare strings (peers: ["seo"]→ fails).validate-slice-parity— each slice'sversion+titleinlib/content/slices.ts(TS) must EXACTLY match itsslice.json. A version bump in one but not the other, or an em-dash vs slash title mismatch, fails the publish.
Pre-flight (run BEFORE asking the user for the OTP — fix any drift, commit, push, THEN publish):
cd packages/cli
node scripts/sync-skills.mjs --check && node scripts/validate.mjs \
&& node scripts/validate-slice.mjs --check \
&& node scripts/validate-slice-parity.mjs \
&& node scripts/validate-structure.mjs
echo "EXIT=$?" # must be 0 before publish
If another agent's in-progress slice (uncommitted WIP) trips the gate,
set it aside (stash the slices.ts entry + mv the untracked dirs to
/tmp), publish clean, then restore byte-for-byte — never publish a
manifest entry whose slice files aren't yet on main (npx rr add <that-slug> would break for users).
# CLI
cd packages/cli
npm version <patch|minor|major> --no-git-tag-version
cd ../..
git add packages/cli/package.json && git commit -m "chore(cli): bump <new-version>"
git push origin main
cd packages/cli && npm publish --otp=...
# MCP (similar)
cd packages/mcp
npm version <patch|minor|major> --no-git-tag-version
cd ../..
git add packages/mcp/package.json && git commit -m "chore(mcp): bump <new-version>"
git push origin main
cd packages/mcp && npm publish --otp=...
User runs OTP step. Never run npm publish autonomously.
Version bump rules:
- patch — bugfix only, no API change
- minor — additive feature
- major — breaking: removed command/URI/flag, changed default behavior
Changelog discipline (always apply)
Every PR/wave/lift that touches a catalog item MUST append a
ChangelogEntry in lib/content/changelog.ts BEFORE the commit lands
on main.
Why:
<RecentlyUpdatedBadge>on/slices/<slug>and/layouts/<slug>readslib/content/changelog-helpers.ts::getLatestUpdate(slug, kind)and shows "Updated 3d ago" → links to/changelog#<releaseId>.- Without an entry, consumers can't tell what's fresh — they lose track of which slices/templates just got polished.
Pattern — one entry per ship, even tiny ones:
{
id: "AO", // wave letter or version
version: "AO-wave",
date: Date.parse("2026-05-19"),
kind: "fix",
title: "Audit-log time formatter rounded down off-by-one",
body: "Fix only — no API change.",
groups: [{
heading: "Slices touched",
bullets: [
{ text: "audit-log — fmtRelative now uses Math.floor", slug: "audit-log" },
],
}],
},
Quick check (before push):
grep "date: Date.parse" lib/content/changelog.ts | head -3 # newest first?
node packages/cli/scripts/gen-manifest.mjs # manifest still clean
npx tsc --noEmit 2>&1 | head # types still happy
Live preview SSOT (when adding new slices/templates with previews)
Both /slices/[slug] and /layouts/[slug] use the same docs-shell
tabbed UI (Code / Public / Split / Admin / Prompt). Single helper:
import { buildPreviewManifest } from "@/components/site/preview";
const manifest = buildPreviewManifest({
title, subtitle,
publicPath, adminPath, // tabs conditional on presence
defaultSurface, defaultView, defaultZoom,
code: () => <CodeTab ... />, // your code tab body
prompt: () => <PromptTab ... />, // optional
extras: [...], // additional tabs
inspector, sourceRepo,
config, composePrompt, composePreviewSrc,
});
useFeatureManifest(manifest);
When you ship a new slice with both previewPath + adminPreviewPath
in lib/content/slices.ts, the tabbed shell renders automatically —
no per-page wiring needed.
Landing-sections schema (the canonical landing CRUD)
The landing-sections slice (also lives at
components/templates/_shared/landing/ for rr-internal use) is the
SSOT for admin-editable landing pages.
LandingSection fields:
id,kind(hero/features/pricing/blog/changelog/faq/portfolio/services/stats/newsletter/cta/testimonials/custom)title,subtitleorder(1-based; list has up/down arrows)enabledimageUrl+imageRatio(16:9 default, dropdown)bgImageUrl(auto soft scrim for readability)className(custom Tailwind appended to section wrapper)config(JSON for kind-specific extras:{badge},{columns},{limit})
DRY editor: every field defined ONCE in
components/templates/_shared/landing/landing-fields.ts::LANDING_FIELDS.
Both list-dialog and full-page editor consume it.
Per-template renderer pattern (every template's
slices/home/LandingRenderer.tsx):
case "hero":
return (
<LandingSectionShell section={section}>
<Hero ... image={section.imageUrl ? { url: section.imageUrl, ratio: section.imageRatio } : undefined} />
</LandingSectionShell>
);
When adding a new template: copy the pattern from any of the 7 existing
templates. Make sure state.landingSections is in your store with
sensible seed kinds (hero + features + cta minimum).
VersionWatcher (client redeploy detection)
components/system/VersionWatcher.tsx polls /api/version every 5min
- on focus/visibility. When deployed
BUILD_IDdiffers from bootNEXT_PUBLIC_BUILD_ID, sonner toast offers "Muat ulang" → hardReload (CacheStorage purge + cache-buster query). Mounted inapp/layout.tsx.
next.config.mjs bakes NEXT_PUBLIC_BUILD_ID at build time (env id /
deployment id / Date.now fallback).
For new consumer projects: copy components/system/VersionWatcher.tsx
app/api/version/route.ts+ the next.config snippet. Mount the component near<Toaster />.
Catalog — query live, never hardcode
The catalog drifts every wave, so this skill does NOT embed a slice
list (a baked snapshot rots — e.g. it once named database-io and a
mentions slice that no longer exist). Always read it live from the
SSOT instead:
# Slices + counts (SSOT = lib/content/slices.ts → manifest):
node packages/cli/scripts/gen-manifest.mjs >/dev/null
node -e "const m=require('./packages/cli/lib/manifest.json'); \
console.log('slices',m.slices.length,'| layouts',m.layouts.length,'| features',m.features.length)"
npx rahman-resources list slices # human-readable, from anywhere
# Templates (the `*-os` family) + layout snippets:
grep slug: lib/content/layouts.ts
ls components/templates/ # one dir per website template
Shared template facts (stable, safe to remember):
- Every
*-ostemplate shares landing-sections admin↔public live-sync via BroadcastChannel,<LandingSectionShell>per renderer kind, CRUD via<CrudListView>+<CrudRowDialog>with<FieldDef>schemas, and<RecentlyUpdatedBadge>on the detail header. - Layout snippets = standalone JSX recipes at
/layouts/<slug>(not full apps).
How other projects/agents should consume rr
When a consumer project sends data here (lift, harvest, sync, agent ping), they need to:
- Check the catalog first — run
npx rahman-resources list slicesOR scan the snapshot above. Don't duplicate an existing slice. - Match the slug — if their slice is conceptually the same as one in rr, use that exact slug. New name only when no overlap.
- Pre-flight before lift — run the 5-point checklist above (no Clerk, 200-LOC cap, no consumer-specific strings, allowed imports, Convex prefixed).
- Provide changelog text — the lifting operator MUST add an entry
to
lib/content/changelog.tsnaming their slug. Bullet shape:{ text: "<slug> — <what-changed-in-1-line>", slug: "<slug>" }. Without this the badge won't show on /slices/. - Mention if there's a peer — if their slice depends on another
rr slice (e.g. landing-sections needs CRUD shell), declare it in
slice.contract.tsunderrequires.peerswithrange+reason.
When AGENTS run against rr from outside:
- Source of truth for catalog:
lib/content/slices.ts(exportslices) +lib/content/layouts.ts. - Source of truth for changelog:
lib/content/changelog.ts(exportreleases). - Helper:
lib/content/changelog-helpers.ts::getLatestUpdate(slug, kind)→ returns latest entry referencing the slug. - Recently-shipped lookup:
isRecentlyUpdated(slug, kind, windowDays=14).
Source map per consumer (for lift verb)
Slice directory differs slightly per consumer. Detect from cwd:
| Consumer | Slice base | Notes |
|---|---|---|
superspace |
frontend/slices/ |
Largest (51 slices) — mostly business-locked |
notion-page-clone |
frontend/slices/ |
35 slices — biggest harvest opportunity |
rahmanef.com |
frontend/slices/ |
26 slices — many portable UI primitives |
content-rahmanef-com |
frontend/slices/ |
11 slices, pure rr consumer |
CareerPack |
frontend/src/slices/ |
Note: extra src/ segment |
cescadesigns |
(none yet) | Repo not yet sliced — defer harvest |
rc-samata-dash |
src/shared/components/ |
VersionWatcher pattern source |
Hard rules (always apply, never skip)
- No Clerk — auth =
@convex-dev/auth(rr mandate) - shadcn-only UI — raw
<button>,<dialog>, native<input type=date|file>forbidden - Copy-first, never greenfield — for new slice, always start from cp -r of source
- Solo dev → push direct to main, never open PR (see global rule in ~/.claude/CLAUDE.md)
- No .kitab.json — BSDL is dead. If you see
.kitab.jsonin any project, delete it. - No term "kitab" in new code/docs — say "rr" or "Rahman Resources"
- 200-LOC file cap — audit-file-size hard gate. Refactor into sub-files when crossing.
- bare .collect() forbidden in Convex queries — use
.withIndex(...).take(N). - Public Convex fn MUST have
argsvalidator — audit-bp P0. - Changelog entry required when touching a catalog item before push (see Changelog discipline above).
Output format
Always end with caveman bahasa Indonesia recap (per global feedback):
- What changed
- Where to look
- What NOT to expect
Compat note
This skill REPLACES three deleted skills (removed 2026-05-16 with BSDL teardown):
/rr-prep(was: preflight slice for harvest) → now folded intoliftverb pre-flight checklist/rr-send(was: BSDL bidir send) → now folded intoliftverb (manual cp-r flow)/rr-adopt(was: migrate to rahman-shared npm) → now: if user wants, justpnpm add rahman-shareddirectly, no skill needed