ralph-wiggum-marketer
Use this skill when the user asks to "analyze my content", "learn my writing style", "research competitors", "find content angles", "improve my blog", "write like me", "embody my brand voice", or mentions content strategy, voice analysis, competitive research, or iterative content improvement.
1实现原理 · 为什么它能做到
Stop hook 是循环真正的引擎,而不是 SKILL 文本:插件用 matcher "*" 注册 Claude Code 的 Stop 事件,命令指向插件内的 stop-hook.sh(timeout 30s)。agent 每次想结束回合时该脚本都会被宿主执行,脚本以 stdout 的 JSON 决定放行或把 agent 拽回下一轮——『不停歇』由宿主 hook 协议实现。
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh",
循环状态落在项目内的 .claude/ralph-marketer-loop.local.md:YAML frontmatter 携带 iteration / max_iterations / completion_promise,脚本用 grep+sed 读出并自增 iteration(先试 BSD 的 sed -i '' 再回退 GNU sed -i)。状态文件不存在即视为无活动循环,直接放行。
if [ ! -f "$LOOP_STATE_FILE" ]; then
终止条件有三重兜底:① 上一条助手消息(jq 读 stdin 的 .transcript[-1].content)命中 <promise>COMPLETION_PROMISE</promise> → 删状态文件并返回 {"decision":"allow"};② NEXT_ITERATION 超过 max_iterations 时同样删文件放行;③ iteration 字段非数字则判定状态文件损坏,打印告警、删除、放行,避免坏状态锁死会话。
"decision": "allow", "message": "Ralph loop completed successfully!"
未终止时脚本把拼好的 SYSTEM_MSG 以 {"decision": "block", "message": "$SYSTEM_MSG"} 输出,宿主据此拒绝停止、把 message 当作新一轮输入:消息写明第 N 轮/共 M 轮、提示『同样的任务再次喂给你,上一轮成果在文件与 git 历史里』、列出 prd.json 与 progress.txt 路径,并附完成信号格式。
"decision": "block",
状态与素材持久化在 SQLite(./data/content.db):init.js 先 mkdirSync(dirname(DB_PATH), { recursive: true }),开启 foreign_keys,再建覆盖 DISCOVER/LEARN/RESEARCH/IDEATE/WRITE-CRITIQUE-ITERATE-PUBLISH 全流程的 14 张表(founder_content、competitor_content、trends、research、communications、voice_profile、content_patterns、research_briefs、content_ideas、content_plan、drafts、critique_results、published、agent_log),状态字段用 CHECK 约束把管线状态机固化进 schema。
status TEXT DEFAULT 'planned' CHECK(status IN ('planned', 'researching', 'writing', 'critiquing', 'iterating', 'review', 'published', 'cancelled')),
seed.js 让项目开箱即有输入:先 DELETE FROM 各表清空,再注入样例 founder 内容、1 份 voice DNA(语气/句式/口头禅/回避模式)、竞品 gap、趋势、研究、公司 com 与 patterns,最后写一条 agent_log 'database_seeded'——全是虚构样例(example.com / TechCorp),用于拉起管线而非真实客户数据。
INSERT INTO voice_profile ( profile_name, tone, formality, sentence_patterns, paragraph_style,
任务分解走 PRD:templates/prd.json 以 userStories 数组给出 12 条故事(SETUP/PLAN/WRITE/REVIEW/PUBLISH/SOCIAL/NEWSLETTER/METRICS),每条含 description、acceptanceCriteria 列表、priority、passes、notes;agent 每轮取 priority 最小且 passes:false 的那条执行,完成后回写 passes:true 并向 progress.txt 追加日志。
"Create draft in drafts table linked to plan", "Draft is at least 800 words", "Includes all key messages from the communication",
跨轮记忆只靠文件与 git:每轮都是全新上下文窗口,记忆由 SQLite + prd.json + progress.txt + git 提交承载;progress.txt 模板要求每次完成 story 后追加『日期 / STORY-ID / 字数 / 数据库变更 / 产出文件 / learnings』,并保持 Codebase Patterns 段可复用。
Each iteration is a fresh context window. Memory persists through files.
四个 slash command 各司其职并做模型/工具分级:ralph-init 建目录、从 ${CLAUDE_PLUGIN_ROOT} 复制脚本与模板、npm install + npm run db:reset;ralph-marketer 定义迭代协议(model sonnet,allowed-tools 含 WebFetch);ralph-status 只读汇报(model haiku,仅 Bash/Read);ralph-cancel 删除状态文件停止循环。
rm -f .claude/ralph-marketer-loop.local.md 2>/dev/null && echo "Loop state cleared" || echo "No active loop found"
2核心能力
3外部依赖
| Kind | Dependency / Endpoint |
|---|---|
| package | better-sqlite3 ^11.0.0(唯一运行时依赖,同步 SQLite 驱动) |
| network | npm registry(安装依赖时拉包) |
| package | prebuild-install / node-gyp 链路(better-sqlite3 安装期原生模块构建,hasInstallScript=true) |
| cli | node |
| cli | npm(install / run db:reset / test) |
| cli | jq(stop-hook.sh 解析宿主传入的 transcript JSON;缺失时静默降级为空串) |
| cli | sed/grep/tr/rm(POSIX 工具链,hook 内解析与改写状态文件) |
| cli | git(每轮 add/commit,兼作记忆与回滚点) |
| network | WebFetch(宿主工具,仅 /ralph-marketer 命令显式授权;插件脚本自身无任何网络调用,竞品/趋势研究抓哪些公开网页由 agent 决定) |
4管控建议与判定依据
- scripts/: 执行本地脚本或命令: hooks/stop-hook.sh(Claude Code Stop 事件 matcher "*",timeout 30s;每次 agent 尝试结
- scripts/ 或 SKILL.md: 声明动态安装外部语言包依赖
- 网络端点: 声明外部网络访问: 间接网络面 1:依赖安装拉包——package-lock.json 记录 resolved https://registry.npmjs.org/be
- 本地工作目录: 文件读写与工件输出: data/content.db(SQLite):init.js 的 const DB_PATH = './data/content.db' 配合 mk
5第二遍独立确认
- [ok] Stop hook 注册与执行时机 — hooks/hooks.json 仅一条 hook:事件 Stop、matcher "*"、command ${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh、timeout 30;stop-hook.sh 逐行确认接收 stdin JSON、无状态文件即 exit 0、有状态文件才介入。
- [discrepancy] 「循环如何被启动」——第一遍默认成立,第二遍证伪 — 全仓 grep ralph-marketer-loop 只命中 3 处:stop-hook.sh 读取/更新、ralph-cancel.md 删除、.gitignore 忽略。没有任何命令或脚本创建该状态文件(ralph-marketer.md 全文仅描述迭代协议与 $ARGUMENTS,无写入指令)。结论:按仓库代码,/ralph-marketer 执行后若无外部手段创建状态文件,hook 不会拦停、循环不会启动——插件缺『arm loop』这一步,使用前需自行创建 .claude/ralph-marketer-loop.local.md(或依赖未入库的 ralph.sh,见下条)。
- [discrepancy] progress.txt 引用的 scripts/ralph/ralph.sh — templates/progress.txt 第 37 行 'Loop script: `scripts/ralph/ralph.sh`' 与 README 的『Ralph is a Bash loop』叙事互相印证,但 find 全量文件确认仓库内不存在 ralph.sh;实际循环由 Stop hook 实现。该引用是遗留/未入库说明,照模板找脚本会扑空。
- [unlocatable] 脚本内是否有网络调用或凭证读取 — 对 .js/.sh 检索 fetch/axios/http./API_KEY/api_key/process.env/curl/wget/token/secret 零命中——即『未发现任何一处』;但无法据此证明运行期绝对无网络(npm 安装与 agent 的 WebFetch 属外部工具面),故按 unlocatable 记录,并在 security.reason 中以这两条外部面说明档位。
- [discrepancy] jq 依赖未声明,缺失时完成信号静默失效 — stop-hook.sh 用 jq 提取 .transcript[-1].content,但 README/命令文档均未提 jq 依赖,脚本以 '2>/dev/null || echo ""' 静默降级——jq 缺失时 LAST_OUTPUT 恒为空,<promise>COMPLETE</promise> 永不触发,循环只能靠 max_iterations 或 /ralph-cancel 结束。属真实使用陷阱(安全上无害,安全上是『无法自停』的风险)。
- [ok] 状态文件容错与 sed -i 跨平台兼容 — iteration 非数字 → 告警 + rm 状态文件 + exit 0(防坏状态锁死);max_iterations 非数字 → 回退 999999;sed -i 先试 BSD 写法(sed -i '')再回退 GNU 写法;set -e 与 `||` 组合不会误退。
- [ok] 回注内容来源与拼装是否如实 — PROMPT=$(sed -n '/^---$/,/^---$/!p' "$LOOP_STATE_FILE") 取 frontmatter 之后全文;空则 cat scripts/ralph/prompt.md;再空则一行默认文案。最终 cat << EOF 输出 {"decision": "block", "message": "$SYSTEM_MSG"} —— 与 Claude Code Stop hook 协议相符(协议本身由宿主定义,不在本仓可校验范围)。
- [ok] SQLite 表数与 schema 完整性 — init.js 内共 14 个 CREATE TABLE(founder_content、competitor_content、trends、research、communications、voice_profile、content_patterns、research_briefs、content_ideas、content_plan、drafts、critique_results、published、agent_log),与其 console.log 自述一致;content_plan/drafts 有 CHECK 枚举与外键,foreign_keys 已开启,DB 路径相对 cwd。
6结论
525719530027f17d…