SLOPSHOPPER

dotclaude-jev

Gets a second opinion from TypeSafe Jev, a decision model, for a yes or no check, a pick from options, or a rating. Needs `TYPESAFE_API_KEY`.

newguardprocess
★ 2v0.27.0MITupdated 2026-10-07xsyetopz/dotclaude/plugins/dotclaude-jev
A shopper browsing a rack in a slop shop
README

dotclaude

dotclaude is a Claude Code plugin for software engineering on official extension points. It turns compaction off, sets a 300K context window, asks only before dangerous commands, replaces the coding part of the system prompt with a short output style, and keeps the state of long work in OpenSpec changes and handoff notes across /clear. It optimizes for the most quality per unit of usage quota, not for speed.

The wiki tells what each part does and why. Parts lists each part with its file and bound. Design gives the source of each design claim.

Install

/plugin marketplace add xsyetopz/dotclaude
/plugin install dotclaude@dotclaude

Requirements: Claude Code 2.1.292, Node.js 22.18 or later on PATH, and git. The hooks module uses the mods API of Claude Code, which can change between releases, so this release is tested only on 2.1.292.

Setup

Run /dotclaude:setup and restart Claude Code. A plugin settings file can set only agent and subagentStatusLine, so the skill merges the profile into a settings file that you choose. It shows each change and makes a backup first. It also checks OpenSpec and offers to install it and to run openspec init --tools claude.

Parts

PartWhat it does
No compactionautoCompactEnabled: false and DISABLE_COMPACT=1, which also turns off /compact.
WindowCLAUDE_CODE_MAX_CONTEXT_TOKENS=300000 and CLAUDE_CODE_AUTO_COMPACT_WINDOW=300000. At the limit the session stops, and you run /clear.
No auto memoryautoMemoryEnabled: false. Handoff notes and OpenSpec hold the state.
System promptCLAUDE_CODE_SIMPLE_SYSTEM_PROMPT=1 and the forced dotclaude output style, which replaces the built-in coding instructions.
PermissionsDeny secret reads and disk wipes. Ask before force pushes, history rewrites, piped shells, sudo, publishes, public gh writes, and SQL drops. Allow read-only git and the usual build and test commands.
SandboxBash runs in the sandbox, with network access only to GitHub and the package registries.
GuardOne hooks module asks before a recursive rm outside the project, and before the first call to a GitHub repository of another owner with an AI policy.
Status lineModel, effort, context against the window (yellow at 75%, red at 90%), the 5-hour limit, and the active OpenSpec change with its task count.
Agentsinvestigator, web-researcher, implementer, debugger, reviewer, test-runner.
Skills/dotclaude:setup, /dotclaude:handoff.

The plugin has no options. To drop a part of the profile, see the setup skill.

Models and effort

ModelRolesEffort
Opus 5.5main session, reviewerdefault medium, reviewer high
Sonnet 5.5investigator, web-researcher, implementer, debuggermedium
Haiku 4.5test-runnernone

The profile sets no effort level, so each model keeps its default. maxEffortLevel is xhigh, so /effort can go up for a hard check.

Long work without compaction

Each turn reads the whole context again, so the size of the context sets the cost of a turn.

  • For work of more than one session, start an OpenSpec change with /opsx:propose. After /clear, /opsx:apply continues at the first unchecked task.
  • Before /clear, run /dotclaude:handoff to write the goal, the decisions, and the proof to .claude/handoffs/<slug>.md. Start the next session with @.claude/handoffs/<slug>.md.
  • Change the model only right after /clear, because a new model starts a new prompt cache. An effort change keeps the cache.

See Handoffs and OpenSpec.

Add-on plugins

Each add-on works without the core plugin.

/plugin install dotclaude-browser@dotclaude
/plugin install dotclaude-jev@dotclaude
/plugin install dotclaude-modder@dotclaude
  • dotclaude-browser has the drive-web-browser skill. It uses agent-browser, and its backend option selects CloakBrowser for sites with bot detection.
  • dotclaude-jev has the second-opinion skill, which asks TypeSafe Jev for probabilities on a close call. Export TYPESAFE_API_KEY before Claude Code starts. Each call sends the data of the decision to the TypeSafe API.
  • dotclaude-modder lets Claude mod a PC game that you own. It is a port of universal-modder by Rehan, and it needs Bun and uv. It adds one skill and the fal MCP server, so install it only where you mod games. For reverse engineering, use the skills in xsyetopz/skills. See Modder.

Update

claude plugin marketplace update dotclaude
claude plugin update dotclaude@dotclaude
  1. Restart Claude Code, because a running session keeps the old hooks and agents.
  2. Read the new entry in CHANGELOG.md. Before 1.0, a release can change or remove behavior without a compatibility layer.
  3. Run /dotclaude:setup again. It shows each change.

0.27.0 is a rebuild that removes most 0.26 parts. See its changelog entry for the list. To try an unreleased checkout, run claude --plugin-dir /path/to/dotclaude/plugins/dotclaude.

Development

just check runs lint, tests, plugin validation, and the hook lab. just sandbox runs Claude Code with this checkout in a separate config. See Development and Sandbox.

License

MIT.

Source 1 files
hooks/module/index.mjs 102 lines
1// The hooks module of dotclaude-jev.
2// It acts on the session note in `hooks/session-start/second-opinion.md`:
3// Jev picks before the user answers a question with options.
4// On each `AskUserQuestion`, the hook runs `jev.mjs ask` once. Jev sorts each
5// question into preference or facts, and picks from its options. A facts
6// question shows the pick of Jev and its confidence to the user. A question
7// about a goal, a preference, or an approval stays as it is. The hook never
8// denies, so Claude has no reason to ask in plain text instead. Without
9// `TYPESAFE_API_KEY`, or when Jev fails, the question passes unchanged.
10
11/** Under this confidence, the TypeSafe docs treat an answer as uncertain. */
12const MIN_CONFIDENCE = 0.5;
13const JEV_TIMEOUT_MS = 45000;
14
15const KIND = {
16  type: "choice",
17  instructions:
18    "Only the user can answer some questions. Facts decide others. Which is this question?",
19  criteria: {
20    preference:
21      "A goal, a preference, an approval, or a choice that reasonable engineers make either way",
22    facts:
23      "Correctness, security, data loss, performance, or maintenance cost decide the answer",
24  },
25};
26
27/** The Jev request for the questions that have 2 or more options. */
28export function jevRequest(questions) {
29  const asked = {};
30  const state = [];
31  questions.forEach((q, i) => {
32    const options = Array.isArray(q?.options) ? q.options : [];
33    if (options.length < 2) return;
34    state.push(
35      `Question ${i}: ${q.question}`,
36      ...options.map((o) => `- ${o.label}: ${o.description ?? ""}`),
37    );
38    asked[`kind_${i}`] = {
39      ...KIND,
40      instructions: `${KIND.instructions} (question ${i})`,
41    };
42    asked[`pick_${i}`] = {
43      type: "choice",
44      instructions: `Which option is the best answer to question ${i}?`,
45      criteria: Object.fromEntries(
46        options.map((o) => [o.label, o.description || o.label]),
47      ),
48    };
49  });
50  return Object.keys(asked).length
51    ? { state: state.join("\n"), questions: asked }
52    : null;
53}
54
55/** The questions with the pick of Jev added to each facts question. */
56export function withPicks(questions, answers) {
57  return questions.map((q, i) => {
58    const kind = answers?.[`kind_${i}`];
59    const pick = answers?.[`pick_${i}`];
60    if (
61      kind?.choice !== "facts" ||
62      !(kind.confidence >= MIN_CONFIDENCE) ||
63      !(pick?.confidence >= MIN_CONFIDENCE) ||
64      !q.options.some((o) => o.label === pick.choice)
65    )
66      return q;
67    const note = `Jev picks "${pick.choice}" (confidence ${pick.confidence.toFixed(2)}).`;
68    return { ...q, question: `${q.question}\n\n${note}` };
69  });
70}
71
72export function register(on) {
73  on("tool.call", async ($, e, next) => {
74    if (e.tool !== "AskUserQuestion" || !Array.isArray(e.questions))
75      return next(e);
76    try {
77      if (!(await $.env.get("TYPESAFE_API_KEY"))) return next(e);
78      const request = jevRequest(e.questions);
79      if (!request) return next(e);
80      const run = await $.process.run(
81        [
82          "node",
83          `${$.plugin.root}/skills/second-opinion/scripts/jev.mjs`,
84          "ask",
85          "--state",
86          request.state,
87        ],
88        {
89          stdin: JSON.stringify({ questions: request.questions }),
90          timeoutMs: JEV_TIMEOUT_MS,
91        },
92      );
93      if (run.exitCode !== 0) return next(e);
94      const questions = withPicks(e.questions, JSON.parse(run.stdout));
95      return next({ ...e, questions });
96    } catch {
97      // Jev is missing, slow, or gave output that is not valid.
98      return next(e);
99    }
100  });
101}
102