unslop

GitHub

用于移除代码和文档中AI生成的冗余模式,如过度抽象、防御性检查、无用注释及错误处理。适用于提交前审查或重构AI生成代码,提升代码简洁性与可读性。

.claude/skills/unslop/SKILL.md nodetool-ai/nodetool

Trigger Scenarios

用户说unslop this 提交前审查自己的diff 重构web/electron/mobile/packages下的AI生成代码

Install

npx skills add nodetool-ai/nodetool --skill unslop -g -y
More Options

Non-standard path

npx skills add https://github.com/nodetool-ai/nodetool/tree/main/.claude/skills/unslop -g -y

Use without installing

npx skills use nodetool-ai/nodetool@unslop

指定 Agent (Claude Code)

npx skills add nodetool-ai/nodetool --skill unslop -a claude-code -g -y

安装 repo 全部 skill

npx skills add nodetool-ai/nodetool --all -g -y

预览 repo 内 skill

npx skills add nodetool-ai/nodetool --list

SKILL.md

Frontmatter
{
    "name": "unslop",
    "description": "Strip \"AI slop\" from code and prose in this repo — over-engineered abstractions, defensive checks on trusted paths, narrating comments, dead error handlers, redundant types, useEffect-for-derived-data, raw MUI imports, whole-store Zustand subscriptions, throat-clearing prose. Use when reviewing your own diff before commit, when the user says \"unslop this\", or when refactoring AI-generated code in `web\/`, `electron\/`, `mobile\/`, or `packages\/`. Triggers on TypeScript\/React\/Zustand\/MUI\/TanStack Query files. Tailored to NodeTool's stack (React 19, Zustand 5, MUI v7 + ui_primitives, TanStack Query v5, Vitest\/Jest, Drizzle)."
}

Unslop

A pre-commit pass that removes patterns LLMs add reflexively but humans wouldn't write. Apply it to your own diff before claiming a task is done. The goal isn't to shorten the diff — it's to delete code whose absence would not be missed.

This skill complements (does not replace) AGENTS.md and web/src/components/ui_primitives/STRATEGY.md. Those define the rules. This skill defines the patterns to actively hunt and remove.

Unslop is a quality pass, not a bug hunt. For correctness review of a diff, branch, or PR — including the NodeTool-specific landmines — use code-review; a full pre-merge pass runs both.

How to use

  1. After making changes, run git diff and read every added line through the lenses below.
  2. For each "slop" you find, delete or rewrite it. Don't leave a // removed X comment behind.
  3. Re-run npm run typecheck && npm run lint && npm run test — unslopping must not break anything.
  4. If you delete an abstraction, also delete its tests, types, and exports.

Categories

# Category Lens
1 General code slop "Would removing this confuse a future reader?"
2 Comment slop "Does the WHY survive without the comment?"
3 Error-handling slop "Can this error actually happen here?"
4 TypeScript slop "Is this type narrowing/casting load-bearing?"
5 React 19 slop "Is this hook earning its keep?"
6 Zustand slop "Does this component re-render only when it should?"
7 MUI / styling slop "Is this a primitive or a theme value?"
8 TanStack Query slop "Why isn't this useQuery?"
9 Test slop "Does this test catch a real regression?"
10 Prose slop "Did a human write this sentence?"

1. General code slop

Hunt for:

  • Speculative abstractions. A Strategy interface with one implementation. A factory that returns one type. Three near-identical lines beat a premature helper.
  • Half-finished features behind unused flags or if (false) branches. Delete them.
  • Re-exports of things nothing imports. Run a search before claiming "this might be useful elsewhere".
  • Backwards-compatibility shims for code paths you just changed. If callers are all in this repo, update the callers.
  • _unused parameter renames for vars that could simply be removed.
  • // TODO for issues you could fix in this PR, or worse, // removed X — see commit Y.

Bad:

function getNodeLabel(node: Node, _opts?: LabelOptions): string {
  // TODO: support i18n later
  return node.label ?? node.id;
}

Good:

function getNodeLabel(node: Node): string {
  return node.label ?? node.id;
}

2. Comment slop

Default to no comments. Only write one when the WHY is non-obvious — a hidden constraint, a workaround for a specific bug, a subtle invariant.

Delete on sight:

  • Comments that restate the next line: // increment counter, // loop over nodes, // return the result.
  • File or function "summary" docstrings that name-rephrase the identifier (/** Get node label. Returns the node's label. */).
  • "Added by" / "fixes #123" / "used by FooComponent" annotations — that belongs in the commit message and PR description, not the source.
  • Multi-line block comments above functions in this codebase. One short line max.
  • Section banners like // ============ HELPERS ============.

Bad:

/**
 * Adds a node to the workflow.
 * @param node - The node to add.
 * @returns void
 */
// Used by NodeEditor.tsx (added in PR #2790)
function addNode(node: Node): void {
  this.nodes.push(node); // push the node
}

Good:

function addNode(node: Node): void {
  this.nodes.push(node);
}

3. Error-handling slop

Trust internal code. Validate at boundaries (user input, network, file system, IPC, child processes) — not between functions inside the same package.

Hunt for:

  • try/catch that re-throws with a slightly different message (or worse, swallows silently with no comment).
  • Defensive if (!x) return for values that the type system already guarantees are present.
  • "Just in case" ?. on values that aren't optional in the type.
  • try { ... } catch { return null } followed by callers that just check for null — propagate the error.
  • Custom Error subclasses with no extra fields and no distinct catch handler anywhere.

NodeTool rule: throw Error objects, not strings; comment intentionally empty catch blocks (the comment is the WHY).

Bad:

function getNode(id: string): Node | null {
  try {
    const node = nodeStore.get(id);
    if (!node) {
      return null;
    }
    return node;
  } catch (e) {
    console.error("Failed to get node:", e);
    return null;
  }
}

Good:

function getNode(id: string): Node | undefined {
  return nodeStore.get(id);
}

4. TypeScript slop

NodeTool runs strict mode. Lean on it.

Hunt for:

  • any, as any, as unknown as Foo. If you needed it, the upstream type is wrong — fix that instead.
  • Explicit return-type annotations that exactly match what TypeScript would infer ((): string => "x").
  • Foo | undefined | null when callers always pass one or the other.
  • Type guards for variants the discriminated union already covers.
  • Re-declared types that already live in @nodetool-ai/protocol or the generated web/src/api.ts.
  • interface FooProps {} for a component with one inline-typed prop.
  • // @ts-ignore / // @ts-expect-error without a one-line explanation of the actual reason (and a link to the bug if external).

Bad:

const handleClick = (e: any): void => {
  if (e && e.target) {
    onSelect(e.target.value as string);
  }
};

Good:

const handleClick = (e: ChangeEvent<HTMLInputElement>) => {
  onSelect(e.target.value);
};

5. React 19 slop

This repo uses React 19. Many older idioms are now slop.

Hunt for:

  • useEffect to derive state from props. Compute it during render. useMemo only if the computation is genuinely expensive or referential stability matters.
  • useEffect to sync to a parent prop — lift state up or use a key.
  • useCallback / useMemo "just in case" when the value isn't passed to a memoized child or used as a hook dep.
  • React.memo on every component. Only when profiling shows wasted renders on stable props.
  • forwardRef — React 19 passes ref as a normal prop. Drop the wrapper.
  • useState for values derived from one prop — just use the prop.
  • Inline arrow handlers passed to a React.memo'd child — wrap with useCallback or stop memoizing the child.
  • Empty useEffect(() => {}, []) or effects whose only job is setX(props.x).

Bad:

const NodeBadge = forwardRef<HTMLDivElement, Props>(({ node }, ref) => {
  const [label, setLabel] = useState("");
  useEffect(() => {
    setLabel(node.label.toUpperCase());
  }, [node.label]);
  const handleClick = useCallback(() => onSelect(node.id), [node.id]);
  return <div ref={ref} onClick={handleClick}>{label}</div>;
});

Good:

function NodeBadge({ node, ref, onSelect }: Props) {
  return (
    <div ref={ref} onClick={() => onSelect(node.id)}>
      {node.label.toUpperCase()}
    </div>
  );
}

6. Zustand slop

NodeTool uses Zustand 5. Multi-key object selectors need useShallow (from zustand/react/shallow); the legacy (selector, shallow) second-argument form was removed in v5. The rules in web/src/stores/AGENTS.md are not optional.

Hunt for:

  • const store = useFooStore() (whole-store subscription) — replace with a selector.
  • Multi-key selections that return a new object every render but omit useShallow — every render re-runs subscribers.
  • useFooStore.getState().x inside render bodies (only acceptable inside event handlers / effects).
  • New WebSocket(...) instances anywhere in the web app — use GlobalWebSocketManager.
  • Components subscribing to a slice and then doing more filtering in render — push the filter into the selector.
  • A new store for state that belongs on an existing single-domain store.

Bad:

const { nodes, edges, selectedId } = useNodeStore();
const selected = nodes.find(n => n.id === selectedId);

Good:

const selected = useNodeStore(
  state => state.nodes.find(n => n.id === state.selectedId)
);

7. MUI / styling slop

NodeTool uses MUI v7 wrapped by web/src/components/ui_primitives/. Raw MUI imports are slop in component files.

Hunt for:

  • import { Typography, Button, IconButton, Tooltip, CircularProgress, Chip, Dialog, Alert, Divider, Paper, Skeleton, Tabs, Drawer, Breadcrumbs, Select, Switch, TextField } from "@mui/material" outside ui_primitives/ and editor_ui/. Use the primitives.
  • Inline sx={{ display: "flex", flexDirection: "column" }} — use <FlexColumn> / <FlexRow>.
  • Hardcoded hex colors, pixel paddings, or font sizes — use theme values, SPACING, GAP, PADDING.
  • Hardcoded border radii (4, 10, 18px), transition strings ("all 200ms ease"), or z-indexes (9999) — use BORDER_RADIUS, MOTION, Z_INDEX from ui_primitives (token reference: docs/DESIGN.md).
  • styled() calls in component files — styled() only belongs inside ui_primitives/.
  • New <Typography variant="..."> — use Text, Label, or Caption.
  • Reaching past primitives into MUI internals (e.g., MuiButton-root overrides) when a primitive prop already exists.

If no primitive fits, add one to ui_primitives/ rather than importing raw MUI in your feature file. See STRATEGY.md.

Bad:

import { Typography, CircularProgress } from "@mui/material";
<div style={{ display: "flex", padding: 16, color: "#888" }}>
  <CircularProgress size={20} />
  <Typography variant="body2">Loading…</Typography>
</div>

Good:

import { FlexRow, LoadingSpinner, Text } from "@/components/ui_primitives";
<FlexRow sx={{ p: 2 }}>
  <LoadingSpinner size="sm" />
  <Text variant="body2" color="muted">Loading…</Text>
</FlexRow>

8. TanStack Query slop

Server state lives in web/src/serverState/. Anything fetched from the backend goes through TanStack Query v5.

Hunt for:

  • useEffect(() => { fetch(...) }, []) for backend data — convert to useQuery.
  • Flat string query keys (["workflow"]) — keys must be hierarchical: ["workflows", workflowId].
  • Mutations that don't invalidateQueries for affected keys.
  • enabled: true (default) on queries with conditional inputs — pass enabled: !!id instead of bailing inside queryFn.
  • Manual loading/error state with useState next to a query — read isPending / error from the query.
  • Mixing Zustand and TanStack Query for the same data (e.g., caching the workflow list in a Zustand store).

Bad:

const [workflow, setWorkflow] = useState<Workflow | null>(null);
useEffect(() => {
  if (id) fetch(`/api/workflows/${id}`).then(r => r.json()).then(setWorkflow);
}, [id]);

Good:

const { data: workflow } = useQuery({
  queryKey: ["workflows", id],
  queryFn: () => api.workflows.get(id),
  enabled: !!id,
});

9. Test slop

Tests must verify behavior. Tests that mirror the implementation rot the moment you refactor.

Hunt for:

  • getByTestId when getByRole / getByLabelText / getByText would work.
  • Snapshot tests of large components — they assert "the markup didn't change" and mostly just record bugs.
  • Mocks of internal modules (mocking your own utils/ is a smell). Mock at the network/IO boundary.
  • expect(true).toBe(true) smoke tests, or tests that only assert "didn't throw".
  • Tests describing implementation: it("calls setNodes"), it("uses useEffect"). Describe behavior: it("highlights the selected node").
  • await new Promise(r => setTimeout(r, 1000)) — use waitFor / findBy*.
  • Re-implementing the production logic inside the test fixture.

Bad:

it("calls setNodes when add is clicked", () => {
  const setNodes = jest.fn();
  render(<NodeEditor setNodes={setNodes} />);
  fireEvent.click(screen.getByTestId("add-btn"));
  expect(setNodes).toHaveBeenCalled();
});

Good:

it("adds a new node when the user clicks Add Node", async () => {
  render(<NodeEditor />);
  await userEvent.click(screen.getByRole("button", { name: /add node/i }));
  expect(await screen.findByRole("listitem", { name: /untitled/i })).toBeVisible();
});

10. Prose slop

Applies to commit messages, PR descriptions, README/AGENTS.md edits, code comments, error messages, and chat-facing text.

Banned openers and crutches (delete or rewrite):

  • Here's the thing, Let me be clear, It turns out, Make no mistake, Worth noting that, In summary,
  • Let that sink in, Full stop, That said,
  • delve, leverage, navigate, robust, seamless, intuitive, vibrant, tapestry, landscape, testament, pivotal, deep dive, unleash
  • Certainly! / Of course! / Absolutely! as a reply opener
  • "Not just X. Y." binary contrasts
  • "X. Y. Z." formulaic three-beats when one would do
  • Numbered lists padded to three items because three feels balanced

Style checks:

  • Active voice. Name the actor: "the workflow runner emits…", not "a message is emitted…".
  • Concrete verbs over hedges: "this fails when X" beats "this might cause issues with X".
  • One idea per sentence. Vary sentence length.
  • For commit messages: imperative mood, why over what. The diff already shows what.

Bad:

This PR delves into the node selection landscape and leverages a robust new approach to seamlessly handle the intuitive interaction. Worth noting that it lays the foundation for future enhancements.

Good:

Fixes a race where rapid clicks could leave two nodes marked selected. The selection store now tracks a single id instead of a Set; multi-select moved to a separate store.


Self-review checklist

Run through this before declaring a task done. Treat any "yes" as a slop sighting to fix.

  • Did I add a comment that restates the code, names the PR, or describes a removed feature?
  • Did I add a try/catch whose error path can't actually trigger here?
  • Did I write any, as any, or as unknown as to silence the compiler?
  • Did I add useEffect to compute a value from props/state I already have?
  • Did I useCallback/useMemo/React.memo without a memoized consumer or measurable cost?
  • Did I subscribe to a whole Zustand store (const s = useFooStore()) or skip useShallow on a multi-key selector?
  • Did I import a raw MUI component into a non-primitive file, or hardcode a color, spacing, radius, font size, or transition?
  • Did I write a useEffect+fetch instead of useQuery?
  • Does any new test assert implementation rather than user-visible behavior?
  • Are there // TODO, // removed, // added by, or "useful elsewhere" leftovers?
  • Could three near-identical lines have been left as-is instead of becoming a helper?
  • Does my prose contain any banned openers or filler ("delve", "robust", "seamlessly", "Here's the thing")?
  • After all this, do typecheck, lint, and test still pass?

If everything is "no", the diff is unslopped.

Sources

Patterns synthesized from these community skills, adapted to NodeTool's stack:

Version History

  • a6a7e57 Current 2026-08-20 09:51

Same Skill Collection

.claude/skills/ask-matt/SKILL.md
.claude/skills/ast-grep-outline/SKILL.md
.claude/skills/ast-grep/SKILL.md
.claude/skills/codebase-design/SKILL.md
.claude/skills/companion-clis/SKILL.md
.claude/skills/diagnosing-bugs/SKILL.md
.claude/skills/domain-modeling/SKILL.md
.claude/skills/flash/SKILL.md
.claude/skills/implement/SKILL.md
.claude/skills/improve-codebase-architecture/SKILL.md
.claude/skills/nodetool-api-reference/SKILL.md
.claude/skills/nodetool-browser-agent/SKILL.md
.claude/skills/nodetool-chat-cli/SKILL.md
.claude/skills/nodetool-custom-node-developer/SKILL.md
.claude/skills/nodetool-deployment/SKILL.md
.claude/skills/nodetool-model-provider-config/SKILL.md
.claude/skills/nodetool-rag-indexing/SKILL.md
.claude/skills/nodetool-troubleshooter/SKILL.md
.claude/skills/nodetool-workflow-builder/SKILL.md
.claude/skills/prototype/SKILL.md
.claude/skills/research/SKILL.md
.claude/skills/resolving-merge-conflicts/SKILL.md
.claude/skills/setup-matt-pocock-skills/SKILL.md
.claude/skills/tdd/SKILL.md
.claude/skills/to-spec/SKILL.md
.claude/skills/to-tickets/SKILL.md
.claude/skills/triage/SKILL.md
.claude/skills/wayfinder/SKILL.md
.claude/skills/wizard/SKILL.md
.claude/skills/yts806379-everything-claude-code-e2e-testing/SKILL.md
packages/sandbox-packs/sandbox-aws/SKILL.md
packages/sandbox-packs/sandbox-chrono/SKILL.md
packages/sandbox-packs/sandbox-color/SKILL.md
packages/sandbox-packs/sandbox-csv/SKILL.md
packages/sandbox-packs/sandbox-dates/SKILL.md
packages/sandbox-packs/sandbox-decimal/SKILL.md
packages/sandbox-packs/sandbox-diff/SKILL.md
packages/sandbox-packs/sandbox-docx/SKILL.md
packages/sandbox-packs/sandbox-dsl/SKILL.md
packages/sandbox-packs/sandbox-epub/SKILL.md
packages/sandbox-packs/sandbox-exif/SKILL.md
packages/sandbox-packs/sandbox-expr/SKILL.md
packages/sandbox-packs/sandbox-fabric/SKILL.md
packages/sandbox-packs/sandbox-flow/SKILL.md
packages/sandbox-packs/sandbox-gif/SKILL.md
packages/sandbox-packs/sandbox-html/SKILL.md
packages/sandbox-packs/sandbox-ics/SKILL.md
packages/sandbox-packs/sandbox-jmespath/SKILL.md
packages/sandbox-packs/sandbox-mammoth/SKILL.md
packages/sandbox-packs/sandbox-markdown/SKILL.md

Metadata

Files
0
Version
a6a7e57
Hash
033ef8fe
Indexed
2026-08-20 09:51

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-25 08:09
浙ICP备14020137号-1 $방문자$