| 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.md starts with "# CLAUDE.md — Rahman Resources"
- consumer mode if cwd contains:
slices/ (no frontend/ prefix) OR frontend/slices/ but NO packages/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
grep -c "slug:" lib/content/slices.ts
git log --oneline -5
node packages/cli/scripts/validate-slice-parity.mjs 2>&1 | tail -3
consumer mode:
ls slices/ 2>/dev/null | head -20
ls shared/ 2>/dev/null | head -10
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>
cd <app-name>
pnpm install
pnpm exec convex dev
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.ts as a TODO entry the operator fills
in. Skips the cp-r ritual when the slice is greenfield (rare — prefer
lift from 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.json description + title + tags
- Write
slice.contract.ts with defineSliceContract({...})
- Add SliceEntry to
lib/content/slices.ts (REQUIRED — npm run audit:slices gates)
- 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>_* with by_workspace index
Then:
SRC=$(pwd)/slices/<slug>
DST=~/projects/resources/frontend/slices/<slug>
cp -r "$SRC" "$DST"
cd ~/projects/resources/frontend/slices/<slug>
cd ~/projects/resources
npm run audit:slices
npm run validate:all
node packages/cli/scripts/gen-manifest.mjs
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>",
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 } (no kind field) defaults to kind: "slice" →
links to /slices/<slug>. Set kind: "template" for layouts.
href field 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
id field 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
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 — every frontend/slices/*/slice.json must satisfy
the schema. Common break: deps.peers must be [{slug,range,reason}]
objects, NOT bare strings (peers: ["seo"] → fails).
validate-slice-parity — each slice's version + title in
lib/content/slices.ts (TS) must EXACTLY match its slice.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=$?"
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).
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=...
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>
reads lib/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",
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
node packages/cli/scripts/gen-manifest.mjs
npx tsc --noEmit 2>&1 | head
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,
defaultSurface, defaultView, defaultZoom,
code: () => <CodeTab ... />,
prompt: () => <PromptTab ... />,
extras: [...],
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, subtitle
order (1-based; list has up/down arrows)
enabled
imageUrl + 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_ID differs from boot
NEXT_PUBLIC_BUILD_ID, sonner toast offers "Muat ulang" → hardReload
(CacheStorage purge + cache-buster query). Mounted in
app/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:
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
grep slug: lib/content/layouts.ts
ls components/templates/
Shared template facts (stable, safe to remember):
- Every
*-os template 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 slices
OR 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.ts naming 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.ts under requires.peers with range + reason.
When AGENTS run against rr from outside:
- Source of truth for catalog:
lib/content/slices.ts (export
slices) + lib/content/layouts.ts.
- Source of truth for changelog:
lib/content/changelog.ts (export
releases).
- 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.json in 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
args validator — 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 into lift verb pre-flight checklist
/rr-send (was: BSDL bidir send) → now folded into lift verb (manual cp-r flow)
/rr-adopt (was: migrate to rahman-shared npm) → now: if user wants, just pnpm add rahman-shared directly, no skill needed