SLOPSHOPPER

git-guard

Denies Bash git commands that discard uncommitted work (checkout --, restore, reset --hard, clean -f)

newguard
★ 1v?no licenseupdated 2026-10-07hughescr/claude-code-config/my-plugins/git-guard
A shopper browsing a rack in a slop shop
README

git-guard

An in-process tool.call mod that denies Bash git commands which silently discard uncommitted work. It is the mod form of hooks/git-guard.sh and replaces it once cut over, saving the shell forks that script cost on every Bash call (about 34 ms per call, against under a microsecond here).

Status: staged only. Until Craig decides otherwise, the PreToolUse Bash entry in settings.json that runs hooks/git-guard.sh stays the enforcement of record. This plugin is inert until it is installed (marketplace update, then install). Activation, verification and rollback: my-plugins/MODS-ACTIVATION.md.

What it denies

Matched through git global options (git -C dir ..., git -c k=v ..., --git-dir, --work-tree, --namespace, --exec-path, --super-prefix=, --config-env=, -p, --paginate, -P, --no-pager, --no-optional-locks, --no-replace-objects, --literal-pathspecs, --glob-pathspecs, --noglob-pathspecs, --icase-pathspecs, --bare):

CommandAllowed exceptions
git checkout ... -- <path> (a bare -- token after checkout, no `\&;` between)git checkout main, -b, --track
git restorethe pure --staged form (no --worktree or -W anywhere)
git reset with --hard as a whole token anywhere in the command--soft, --mixed, --hard-not-really
git clean with a force flag (-f, -fd, -fdx, --force)any dry run (-n, --dry-run, even with --force)

Every deny reason ends with: "Undo your own edits by editing instead of discarding them; ask Craig for an exception if this destructive command is genuinely needed." A command that is empty or not a string passes through. tool.call fires for subagents as well, so there is no subagent filter.

How it matches

hooks/guard.ts is pure string logic (no claude-code imports). The shell script's [[:space:]] and \b are BSD grep and libc semantics, not Unicode properties, so the character classes are reproduced exactly:

  • [[:space:]] is JS \s minus U+FEFF (24 code points).
  • \b after a keyword uses hooks/wordchars.ts, a generated table of the characters BSD grep treats as word characters on this machine (706 ranges). tests/gen-git-guard-wordchars.ts regenerates it (bun my-plugins/git-guard/tests/gen-git-guard-wordchars.ts from ~/.claude); rerun it only after a macOS upgrade, while hooks/git-guard.sh still exists as the reference.
  • The git ... <subcommand> prefix is scanned token by token in linear time. The shell's regexes backtracked quadratically (git -C repeated 5,700 times, about 40 KB, took 5 s), which would have crossed the hook timeout and failed open. Every 1 MB adversarial input now finishes in well under 100 ms.

Preserved holes (parity with the script, to fix later in a separate, flagged change)

  • --staged anywhere in the command allows git restore; -S is not recognised, so git restore -S f is denied.
  • A dry-run-like token anywhere (even -name) allows git clean.
  • An unknown global option, or git -C reset ... (the value swallows reset), slips through. --git-dir= with an empty value breaks the option chain.
  • git -c clean.requireForce=false clean -d is allowed.
  • No defence against shell obfuscation (variables, quoting tricks, eval, scripts that run git inside).
  • Over-denies remain: git reset HEAD~1; echo --hard and git clean -d; rm -f x are denied.

Deliberate behaviour changes

  1. The shell's set -o pipefail with echo | grep -q could flakily miss a match on commands over about 64 KiB (SIGPIPE). The mod is deterministic.
  2. A non-string command passes through (unreachable under the Bash schema).

Tests

claude plugin test my-plugins/git-guard runs tests/guard.test.ts (the golden corpus in tests/corpus.ts through $.tool.call, deny-reason text, and 1 MB performance cases). Parity with the shell script was proven by running the original script and the mod over the same corpus plus random fuzz.

Source 3 files
hooks/register.ts 13 lines
1import type { Register } from 'claude-code'
2import { gitGuard } from './guard'
3
4// No subagent filter: tool.call fires for subagents too, as the PreToolUse settings hook did.
5export const register: Register = on => {
6  on('tool.call', { tool: 'Bash' }, ($, e, next) => {
7    const command: unknown = e.command
8    if (typeof command !== 'string') return next(e)
9    const reason = gitGuard(command)
10    return reason === undefined ? next(e) : { deny: reason }
11  })
12}
13
hooks/guard.ts 184 lines
1// Pure decision logic for the git-guard mod. No 'claude-code' imports, so bun can import it for the
2// oracle harness. Behavioural parity with the retired hooks/git-guard.sh (bash 3.2 + BSD grep 2.6.0,
3// LANG=en_US.UTF-8) is the bar; see README.md for the quirks that are preserved on purpose.
4//
5// Character classes (each verified exhaustively over every Unicode code point against /usr/bin/grep and
6// /bin/bash 3.2 =~):
7//   SP   [[:space:]]  = JS \s minus U+FEFF (24 code points including \n, all in the BMP)
8//   WORD grep's `\b` after a keyword ending in a word char = the generated WORD_CLASS table (NOT \p{L}\p{N};
9//        grep disagrees with Unicode properties on 9,658 code points). No \p{} is used anywhere, so the
10//        result does not depend on the engine's Unicode version.
11//   [a-zA-Z] stays ASCII.
12import { WORD_CLASS } from './wordchars'
13
14const SP = '[^\\S\\uFEFF]'
15const WORD_RE = new RegExp(`[${WORD_CLASS}]`, 'u')
16const WB = `(?![${WORD_CLASS}])`
17
18// JS \s minus U+FEFF, as char codes (hard-coded to keep module load cheap; the test suite checks this
19// against the regex-derived set).
20export const SP_CODE_LIST: readonly number[] = [
21  0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20, 0xa0, 0x1680,
22  0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
23  0x2028, 0x2029, 0x202f, 0x205f, 0x3000,
24]
25const SP_CODES: ReadonlySet<number> = new Set(SP_CODE_LIST)
26const isSP = (s: string, i: number): boolean => SP_CODES.has(s.charCodeAt(i))
27const SP_SPLIT = new RegExp(`${SP}+`, 'u')
28
29export const DENY_SUFFIX =
30  ' Undo your own edits by editing instead of discarding them; ask Craig for an exception if this destructive command is genuinely needed.'
31export const REASON_CHECKOUT = 'git checkout -- discards working-tree changes.' + DENY_SUFFIX
32export const REASON_RESTORE = 'git restore discards or overwrites working-tree content.' + DENY_SUFFIX
33export const REASON_RESET = 'git reset --hard discards working-tree and index changes.' + DENY_SUFFIX
34export const REASON_CLEAN = 'git clean with a force flag permanently deletes untracked files.' + DENY_SUFFIX
35
36/** A high surrogate not immediately followed by a low surrogate (no 'u' flag: code units are the point). */
37const UNPAIRED_HIGH_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])/
38
39/** What `$(jq -r ...)` leaves in bash 3.2: NUL bytes dropped, trailing newlines stripped. */
40export function shellNormalize(cmd: string): string {
41  return cmd.replaceAll('\0', '').replace(/\n+$/, '')
42}
43
44/** True when index i is at the end of the string or holds a character grep does not treat as a word char. */
45function boundaryAt(s: string, i: number): boolean {
46  if (i >= s.length) return true
47  // A lone low surrogate is a boundary (jq turns it into U+FFFD, which is not a word char). Unpaired high
48  // surrogates never reach here: gitGuard returns early on them.
49  return !WORD_RE.test(String.fromCodePoint(s.codePointAt(i)!))
50}
51
52// ------------------------------------------------------------------ git-prefix scanner
53const HIT_CHECKOUT = 1
54const HIT_RESTORE = 2
55const HIT_RESET = 4
56const HIT_CLEAN = 8
57
58const ONE_TOKEN_OPTS: ReadonlySet<string> = new Set([
59  '-p', '--paginate', '-P', '--no-pager', '--no-optional-locks', '--no-replace-objects',
60  '--literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs', '--bare',
61  '--exec-path',
62])
63/** `--opt=v` (1 token, v non-empty) or `--opt v` (2 tokens). */
64const VALUE_OPTS = ['--git-dir', '--work-tree', '--namespace'] as const
65/** `--opt=v` only (1 token, v non-empty). */
66const EQ_ONLY_OPTS = ['--super-prefix=', '--config-env=', '--exec-path='] as const
67
68/**
69 * How many tokens the git global option starting at this token consumes (0 = not a global option).
70 * Mirrors GIT_GOPT. Exact because every option in the pattern is followed by SP+, so each token start
71 * admits at most one option parse.
72 */
73function optTokens(tok: string): number {
74  if (tok === '-C' || tok === '-c') return 2
75  if (tok.length > 2 && (tok.startsWith('-C') || tok.startsWith('-c'))) return 1
76  if (ONE_TOKEN_OPTS.has(tok)) return 1
77  for (const o of VALUE_OPTS) {
78    if (tok === o) return 2
79    if (tok.length > o.length + 1 && tok.startsWith(o + '=')) return 1
80  }
81  for (const o of EQ_ONLY_OPTS) if (tok.length > o.length && tok.startsWith(o)) return 1
82  return 0
83}
84
85/**
86 * Bitmask of the subcommand patterns (`${GIT}checkout...--`, `${GIT}restore\b`, `${GIT}reset\b`,
87 * `${GIT}clean\b`) that match somewhere in the line. O(line length): replaces the original regexes, which
88 * backtrack quadratically.
89 */
90function gitHits(line: string): number {
91  const n = line.length
92  // Maximal non-SP runs.
93  const ts: number[] = []
94  const te: number[] = []
95  for (let i = 0; i < n; ) {
96    while (i < n && isSP(line, i)) i++
97    if (i >= n) break
98    const s = i
99    while (i < n && !isSP(line, i)) i++
100    ts.push(s)
101    te.push(i)
102  }
103  const T = ts.length
104  if (T < 2) return 0
105
106  // nextSep[i]: first index >= i holding '|', '&' or ';' (or n).
107  const nextSep = new Int32Array(n + 1)
108  nextSep[n] = n
109  for (let i = n - 1; i >= 0; i--) {
110    const c = line.charCodeAt(i)
111    nextSep[i] = c === 124 || c === 38 || c === 59 ? i : nextSep[i + 1]!
112  }
113  // nextDD[t]: smallest token index >= t whose text is exactly '--' (or T).
114  const nextDD = new Int32Array(T + 1)
115  nextDD[T] = T
116  for (let t = T - 1; t >= 0; t--) {
117    nextDD[t] = te[t]! - ts[t]! === 2 && line.startsWith('--', ts[t]!) ? t : nextDD[t + 1]!
118  }
119
120  // hit[t]: subcommands reachable when the subcommand sits at token t or after any chain of global options.
121  const hit = new Uint8Array(T + 1)
122  for (let t = T - 1; t >= 0; t--) {
123    const s = ts[t]!
124    const e = te[t]!
125    let h = 0
126    if (e - s === 8 && line.startsWith('checkout', s) && e < n) {
127      const u = nextDD[t + 1]!
128      if (u < T && nextSep[e]! >= ts[u]!) h |= HIT_CHECKOUT
129    }
130    if (line.startsWith('restore', s) && boundaryAt(line, s + 7)) h |= HIT_RESTORE
131    if (line.startsWith('reset', s) && boundaryAt(line, s + 5)) h |= HIT_RESET
132    if (line.startsWith('clean', s) && boundaryAt(line, s + 5)) h |= HIT_CLEAN
133    const c = optTokens(line.slice(s, e))
134    if (c > 0 && t + c < T) h |= hit[t + c]!
135    hit[t] = h
136  }
137
138  // A token ENDING in 'git' (no left boundary: 'digit', '/usr/bin/git', 'a;git' all count) starts a chain.
139  let out = 0
140  for (let t = 0; t + 1 < T; t++) {
141    if (te[t]! - ts[t]! >= 3 && line.startsWith('git', te[t]! - 3)) out |= hit[t + 1]!
142  }
143  return out
144}
145
146// ------------------------------------------------------------------ other patterns (linear)
147const RE_STAGED = new RegExp(`--staged${WB}`, 'u')
148const RE_WORKTREE = new RegExp(`(?:--worktree${WB}|-W${WB})`, 'u')
149const RE_HARD = new RegExp(`--hard(?:$|${SP})`, 'u')
150const ASCII_FLAG = /^-[a-zA-Z]*$/
151const isDry = (tok: string): boolean => tok === '--dry-run' || (ASCII_FLAG.test(tok) && tok.includes('n'))
152const isForce = (tok: string): boolean => tok === '--force' || (ASCII_FLAG.test(tok) && tok.includes('f'))
153
154/**
155 * The deny reason for a Bash command, or undefined when the guard has no objection. Never throws on a
156 * string input. Every grep in the original script is independent and per line, ORed over lines.
157 */
158export function gitGuard(raw: string): string | undefined {
159  // The shell hook fed JSON.stringify's output to jq, which rejects an escaped unpaired high surrogate
160  // ("Invalid \uXXXX\uXXXX surrogate pair escape"); that parse failure meant "no opinion". Match it.
161  // A lone low surrogate is accepted by jq (it becomes U+FFFD, a boundary, which the mod treats the same).
162  if (UNPAIRED_HIGH_SURROGATE.test(raw)) return undefined
163  const cmd = shellNormalize(raw)
164  if (cmd === '') return undefined
165  const lines = cmd.split('\n')
166
167  let hits = 0
168  for (const l of lines) hits |= gitHits(l)
169  const anyLine = (re: RegExp): boolean => lines.some(l => re.test(l))
170
171  if (hits & HIT_CHECKOUT) return REASON_CHECKOUT
172  if (hits & HIT_RESTORE) {
173    const pureStaged = anyLine(RE_STAGED) && !anyLine(RE_WORKTREE)
174    if (!pureStaged) return REASON_RESTORE
175  }
176  if (hits & HIT_RESET && anyLine(RE_HARD)) return REASON_RESET
177  if (hits & HIT_CLEAN) {
178    const toks = lines.flatMap(l => l.split(SP_SPLIT))
179    if (toks.some(isDry)) return undefined
180    if (toks.some(isForce)) return REASON_CLEAN
181  }
182  return undefined
183}
184
hooks/wordchars.ts 13 lines
1// GENERATED by my-plugins/git-guard/tests/gen-git-guard-wordchars.ts: do not edit by hand.
2// Generator command : bun my-plugins/git-guard/tests/gen-git-guard-wordchars.ts
3// macOS             : 27.0 (sw_vers -productVersion)
4// grep              : grep (BSD grep, GNU compatible) 2.6.0-FreeBSD (/usr/bin/grep --version)
5// Locale            : LANG=en_US.UTF-8
6// Content           : the regex character-class body for exactly the characters BSD grep treats as
7//                     word characters for `\b` on this machine: 706 ranges, 136785 code points.
8//                     It is a strict subset of [\p{L}\p{Nd}_] (9658 code points of that class are grep boundaries,
9//                     e.g. numerals such as \u00b2 and \u2163, and letters newer than macOS libc's Unicode tables).
10// Use               : new RegExp(`[${WORD_CLASS}]`, 'u')
11export const WORD_CLASS =
12  '\\u{30}-\\u{39}\\u{41}-\\u{5a}\\u{5f}\\u{61}-\\u{7a}\\u{aa}\\u{b5}\\u{ba}\\u{c0}-\\u{d6}\\u{d8}-\\u{f6}\\u{f8}-\\u{2c1}\\u{2c6}-\\u{2d1}\\u{2e0}-\\u{2e4}\\u{2ec}\\u{2ee}\\u{370}-\\u{374}\\u{376}-\\u{377}\\u{37a}-\\u{37d}\\u{37f}\\u{386}\\u{388}-\\u{38a}\\u{38c}\\u{38e}-\\u{3a1}\\u{3a3}-\\u{3f5}\\u{3f7}-\\u{481}\\u{48a}-\\u{52f}\\u{531}-\\u{556}\\u{559}\\u{560}-\\u{588}\\u{5d0}-\\u{5ea}\\u{5ef}-\\u{5f2}\\u{620}-\\u{64a}\\u{660}-\\u{669}\\u{66e}-\\u{66f}\\u{671}-\\u{6d3}\\u{6d5}\\u{6e5}-\\u{6e6}\\u{6ee}-\\u{6fc}\\u{6ff}\\u{710}\\u{712}-\\u{72f}\\u{74d}-\\u{7a5}\\u{7b1}\\u{7c0}-\\u{7ea}\\u{7f4}-\\u{7f5}\\u{7fa}\\u{800}-\\u{815}\\u{81a}\\u{824}\\u{828}\\u{840}-\\u{858}\\u{860}-\\u{86a}\\u{870}-\\u{887}\\u{889}-\\u{88e}\\u{8a0}-\\u{8c9}\\u{904}-\\u{939}\\u{93d}\\u{950}\\u{958}-\\u{961}\\u{966}-\\u{96f}\\u{971}-\\u{980}\\u{985}-\\u{98c}\\u{98f}-\\u{990}\\u{993}-\\u{9a8}\\u{9aa}-\\u{9b0}\\u{9b2}\\u{9b6}-\\u{9b9}\\u{9bd}\\u{9ce}\\u{9dc}-\\u{9dd}\\u{9df}-\\u{9e1}\\u{9e6}-\\u{9f1}\\u{9fc}\\u{a05}-\\u{a0a}\\u{a0f}-\\u{a10}\\u{a13}-\\u{a28}\\u{a2a}-\\u{a30}\\u{a32}-\\u{a33}\\u{a35}-\\u{a36}\\u{a38}-\\u{a39}\\u{a59}-\\u{a5c}\\u{a5e}\\u{a66}-\\u{a6f}\\u{a72}-\\u{a74}\\u{a85}-\\u{a8d}\\u{a8f}-\\u{a91}\\u{a93}-\\u{aa8}\\u{aaa}-\\u{ab0}\\u{ab2}-\\u{ab3}\\u{ab5}-\\u{ab9}\\u{abd}\\u{ad0}\\u{ae0}-\\u{ae1}\\u{ae6}-\\u{aef}\\u{af9}\\u{b05}-\\u{b0c}\\u{b0f}-\\u{b10}\\u{b13}-\\u{b28}\\u{b2a}-\\u{b30}\\u{b32}-\\u{b33}\\u{b35}-\\u{b39}\\u{b3d}\\u{b5c}-\\u{b5d}\\u{b5f}-\\u{b61}\\u{b66}-\\u{b6f}\\u{b71}\\u{b83}\\u{b85}-\\u{b8a}\\u{b8e}-\\u{b90}\\u{b92}-\\u{b95}\\u{b99}-\\u{b9a}\\u{b9c}\\u{b9e}-\\u{b9f}\\u{ba3}-\\u{ba4}\\u{ba8}-\\u{baa}\\u{bae}-\\u{bb9}\\u{bd0}\\u{be6}-\\u{bef}\\u{c05}-\\u{c0c}\\u{c0e}-\\u{c10}\\u{c12}-\\u{c28}\\u{c2a}-\\u{c39}\\u{c3d}\\u{c58}-\\u{c5a}\\u{c5d}\\u{c60}-\\u{c61}\\u{c66}-\\u{c6f}\\u{c80}\\u{c85}-\\u{c8c}\\u{c8e}-\\u{c90}\\u{c92}-\\u{ca8}\\u{caa}-\\u{cb3}\\u{cb5}-\\u{cb9}\\u{cbd}\\u{cdd}-\\u{cde}\\u{ce0}-\\u{ce1}\\u{ce6}-\\u{cef}\\u{cf1}-\\u{cf2}\\u{d04}-\\u{d0c}\\u{d0e}-\\u{d10}\\u{d12}-\\u{d3a}\\u{d3d}\\u{d4e}\\u{d54}-\\u{d56}\\u{d5f}-\\u{d61}\\u{d66}-\\u{d6f}\\u{d7a}-\\u{d7f}\\u{d85}-\\u{d96}\\u{d9a}-\\u{db1}\\u{db3}-\\u{dbb}\\u{dbd}\\u{dc0}-\\u{dc6}\\u{de6}-\\u{def}\\u{e01}-\\u{e30}\\u{e32}-\\u{e33}\\u{e40}-\\u{e46}\\u{e50}-\\u{e59}\\u{e81}-\\u{e82}\\u{e84}\\u{e86}-\\u{e8a}\\u{e8c}-\\u{ea3}\\u{ea5}\\u{ea7}-\\u{eb0}\\u{eb2}-\\u{eb3}\\u{ebd}\\u{ec0}-\\u{ec4}\\u{ec6}\\u{ed0}-\\u{ed9}\\u{edc}-\\u{edf}\\u{f00}\\u{f20}-\\u{f29}\\u{f40}-\\u{f47}\\u{f49}-\\u{f6c}\\u{f88}-\\u{f8c}\\u{1000}-\\u{102a}\\u{103f}-\\u{1049}\\u{1050}-\\u{1055}\\u{105a}-\\u{105d}\\u{1061}\\u{1065}-\\u{1066}\\u{106e}-\\u{1070}\\u{1075}-\\u{1081}\\u{108e}\\u{1090}-\\u{1099}\\u{10a0}-\\u{10c5}\\u{10c7}\\u{10cd}\\u{10d0}-\\u{10fa}\\u{10fc}-\\u{1248}\\u{124a}-\\u{124d}\\u{1250}-\\u{1256}\\u{1258}\\u{125a}-\\u{125d}\\u{1260}-\\u{1288}\\u{128a}-\\u{128d}\\u{1290}-\\u{12b0}\\u{12b2}-\\u{12b5}\\u{12b8}-\\u{12be}\\u{12c0}\\u{12c2}-\\u{12c5}\\u{12c8}-\\u{12d6}\\u{12d8}-\\u{1310}\\u{1312}-\\u{1315}\\u{1318}-\\u{135a}\\u{1380}-\\u{138f}\\u{13a0}-\\u{13f5}\\u{13f8}-\\u{13fd}\\u{1401}-\\u{166c}\\u{166f}-\\u{167f}\\u{1681}-\\u{169a}\\u{16a0}-\\u{16ea}\\u{16f1}-\\u{16f8}\\u{1700}-\\u{1711}\\u{171f}-\\u{1731}\\u{1740}-\\u{1751}\\u{1760}-\\u{176c}\\u{176e}-\\u{1770}\\u{1780}-\\u{17b3}\\u{17d7}\\u{17dc}\\u{17e0}-\\u{17e9}\\u{1810}-\\u{1819}\\u{1820}-\\u{1878}\\u{1880}-\\u{1884}\\u{1887}-\\u{18a8}\\u{18aa}\\u{18b0}-\\u{18f5}\\u{1900}-\\u{191e}\\u{1946}-\\u{196d}\\u{1970}-\\u{1974}\\u{1980}-\\u{19ab}\\u{19b0}-\\u{19c9}\\u{19d0}-\\u{19d9}\\u{1a00}-\\u{1a16}\\u{1a20}-\\u{1a54}\\u{1a80}-\\u{1a89}\\u{1a90}-\\u{1a99}\\u{1aa7}\\u{1b05}-\\u{1b33}\\u{1b45}-\\u{1b4c}\\u{1b50}-\\u{1b59}\\u{1b83}-\\u{1ba0}\\u{1bae}-\\u{1be5}\\u{1c00}-\\u{1c23}\\u{1c40}-\\u{1c49}\\u{1c4d}-\\u{1c7d}\\u{1c80}-\\u{1c88}\\u{1c90}-\\u{1cba}\\u{1cbd}-\\u{1cbf}\\u{1ce9}-\\u{1cec}\\u{1cee}-\\u{1cf3}\\u{1cf5}-\\u{1cf6}\\u{1cfa}\\u{1d00}-\\u{1dbf}\\u{1e00}-\\u{1f15}\\u{1f18}-\\u{1f1d}\\u{1f20}-\\u{1f45}\\u{1f48}-\\u{1f4d}\\u{1f50}-\\u{1f57}\\u{1f59}\\u{1f5b}\\u{1f5d}\\u{1f5f}-\\u{1f7d}\\u{1f80}-\\u{1fb4}\\u{1fb6}-\\u{1fbc}\\u{1fbe}\\u{1fc2}-\\u{1fc4}\\u{1fc6}-\\u{1fcc}\\u{1fd0}-\\u{1fd3}\\u{1fd6}-\\u{1fdb}\\u{1fe0}-\\u{1fec}\\u{1ff2}-\\u{1ff4}\\u{1ff6}-\\u{1ffc}\\u{2071}\\u{207f}\\u{2090}-\\u{209c}\\u{2102}\\u{2107}\\u{210a}-\\u{2113}\\u{2115}\\u{2119}-\\u{211d}\\u{2124}\\u{2126}\\u{2128}\\u{212a}-\\u{212d}\\u{212f}-\\u{2139}\\u{213c}-\\u{213f}\\u{2145}-\\u{2149}\\u{214e}\\u{2183}-\\u{2184}\\u{2c00}-\\u{2ce4}\\u{2ceb}-\\u{2cee}\\u{2cf2}-\\u{2cf3}\\u{2d00}-\\u{2d25}\\u{2d27}\\u{2d2d}\\u{2d30}-\\u{2d67}\\u{2d6f}\\u{2d80}-\\u{2d96}\\u{2da0}-\\u{2da6}\\u{2da8}-\\u{2dae}\\u{2db0}-\\u{2db6}\\u{2db8}-\\u{2dbe}\\u{2dc0}-\\u{2dc6}\\u{2dc8}-\\u{2dce}\\u{2dd0}-\\u{2dd6}\\u{2dd8}-\\u{2dde}\\u{2e2f}\\u{3005}-\\u{3006}\\u{3031}-\\u{3035}\\u{303b}-\\u{303c}\\u{3041}-\\u{3096}\\u{309d}-\\u{309f}\\u{30a1}-\\u{30fa}\\u{30fc}-\\u{30ff}\\u{3105}-\\u{312f}\\u{3131}-\\u{318e}\\u{31a0}-\\u{31bf}\\u{31f0}-\\u{31ff}\\u{3400}-\\u{4dbf}\\u{4e00}-\\u{a48c}\\u{a4d0}-\\u{a4fd}\\u{a500}-\\u{a60c}\\u{a610}-\\u{a62b}\\u{a640}-\\u{a66e}\\u{a67f}-\\u{a69d}\\u{a6a0}-\\u{a6e5}\\u{a717}-\\u{a71f}\\u{a722}-\\u{a788}\\u{a78b}-\\u{a7ca}\\u{a7d0}-\\u{a7d1}\\u{a7d3}\\u{a7d5}-\\u{a7d9}\\u{a7f2}-\\u{a801}\\u{a803}-\\u{a805}\\u{a807}-\\u{a80a}\\u{a80c}-\\u{a822}\\u{a840}-\\u{a873}\\u{a882}-\\u{a8b3}\\u{a8d0}-\\u{a8d9}\\u{a8f2}-\\u{a8f7}\\u{a8fb}\\u{a8fd}-\\u{a8fe}\\u{a900}-\\u{a925}\\u{a930}-\\u{a946}\\u{a960}-\\u{a97c}\\u{a984}-\\u{a9b2}\\u{a9cf}-\\u{a9d9}\\u{a9e0}-\\u{a9e4}\\u{a9e6}-\\u{a9fe}\\u{aa00}-\\u{aa28}\\u{aa40}-\\u{aa42}\\u{aa44}-\\u{aa4b}\\u{aa50}-\\u{aa59}\\u{aa60}-\\u{aa76}\\u{aa7a}\\u{aa7e}-\\u{aaaf}\\u{aab1}\\u{aab5}-\\u{aab6}\\u{aab9}-\\u{aabd}\\u{aac0}\\u{aac2}\\u{aadb}-\\u{aadd}\\u{aae0}-\\u{aaea}\\u{aaf2}-\\u{aaf4}\\u{ab01}-\\u{ab06}\\u{ab09}-\\u{ab0e}\\u{ab11}-\\u{ab16}\\u{ab20}-\\u{ab26}\\u{ab28}-\\u{ab2e}\\u{ab30}-\\u{ab5a}\\u{ab5c}-\\u{ab69}\\u{ab70}-\\u{abe2}\\u{abf0}-\\u{abf9}\\u{ac00}-\\u{d7a3}\\u{d7b0}-\\u{d7c6}\\u{d7cb}-\\u{d7fb}\\u{f900}-\\u{fa6d}\\u{fa70}-\\u{fad9}\\u{fb00}-\\u{fb06}\\u{fb13}-\\u{fb17}\\u{fb1d}\\u{fb1f}-\\u{fb28}\\u{fb2a}-\\u{fb36}\\u{fb38}-\\u{fb3c}\\u{fb3e}\\u{fb40}-\\u{fb41}\\u{fb43}-\\u{fb44}\\u{fb46}-\\u{fbb1}\\u{fbd3}-\\u{fd3d}\\u{fd50}-\\u{fd8f}\\u{fd92}-\\u{fdc7}\\u{fdf0}-\\u{fdfb}\\u{fe70}-\\u{fe74}\\u{fe76}-\\u{fefc}\\u{ff10}-\\u{ff19}\\u{ff21}-\\u{ff3a}\\u{ff41}-\\u{ff5a}\\u{ff66}-\\u{ffbe}\\u{ffc2}-\\u{ffc7}\\u{ffca}-\\u{ffcf}\\u{ffd2}-\\u{ffd7}\\u{ffda}-\\u{ffdc}\\u{10000}-\\u{1000b}\\u{1000d}-\\u{10026}\\u{10028}-\\u{1003a}\\u{1003c}-\\u{1003d}\\u{1003f}-\\u{1004d}\\u{10050}-\\u{1005d}\\u{10080}-\\u{100fa}\\u{10280}-\\u{1029c}\\u{102a0}-\\u{102d0}\\u{10300}-\\u{1031f}\\u{1032d}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039d}\\u{103a0}-\\u{103c3}\\u{103c8}-\\u{103cf}\\u{10400}-\\u{1049d}\\u{104a0}-\\u{104a9}\\u{104b0}-\\u{104d3}\\u{104d8}-\\u{104fb}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057a}\\u{1057c}-\\u{1058a}\\u{1058c}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105a1}\\u{105a3}-\\u{105b1}\\u{105b3}-\\u{105b9}\\u{105bb}-\\u{105bc}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107b0}\\u{107b2}-\\u{107ba}\\u{10800}-\\u{10805}\\u{10808}\\u{1080a}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083c}\\u{1083f}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089e}\\u{108e0}-\\u{108f2}\\u{108f4}-\\u{108f5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10980}-\\u{109b7}\\u{109be}-\\u{109bf}\\u{10a00}\\u{10a10}-\\u{10a13}\\u{10a15}-\\u{10a17}\\u{10a19}-\\u{10a35}\\u{10a60}-\\u{10a7c}\\u{10a80}-\\u{10a9c}\\u{10ac0}-\\u{10ac7}\\u{10ac9}-\\u{10ae4}\\u{10b00}-\\u{10b35}\\u{10b40}-\\u{10b55}\\u{10b60}-\\u{10b72}\\u{10b80}-\\u{10b91}\\u{10c00}-\\u{10c48}\\u{10c80}-\\u{10cb2}\\u{10cc0}-\\u{10cf2}\\u{10d00}-\\u{10d23}\\u{10d30}-\\u{10d39}\\u{10e80}-\\u{10ea9}\\u{10eb0}-\\u{10eb1}\\u{10f00}-\\u{10f1c}\\u{10f27}\\u{10f30}-\\u{10f45}\\u{10f70}-\\u{10f81}\\u{10fb0}-\\u{10fc4}\\u{10fe0}-\\u{10ff6}\\u{11003}-\\u{11037}\\u{11066}-\\u{1106f}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110af}\\u{110d0}-\\u{110e8}\\u{110f0}-\\u{110f9}\\u{11103}-\\u{11126}\\u{11136}-\\u{1113f}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111b2}\\u{111c1}-\\u{111c4}\\u{111d0}-\\u{111da}\\u{111dc}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122b}\\u{1123f}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128a}-\\u{1128d}\\u{1128f}-\\u{1129d}\\u{1129f}-\\u{112a8}\\u{112b0}-\\u{112de}\\u{112f0}-\\u{112f9}\\u{11305}-\\u{1130c}\\u{1130f}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132a}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133d}\\u{11350}\\u{1135d}-\\u{11361}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144a}\\u{11450}-\\u{11459}\\u{1145f}-\\u{11461}\\u{11480}-\\u{114af}\\u{114c4}-\\u{114c5}\\u{114c7}\\u{114d0}-\\u{114d9}\\u{11580}-\\u{115ae}\\u{115d8}-\\u{115db}\\u{11600}-\\u{1162f}\\u{11644}\\u{11650}-\\u{11659}\\u{11680}-\\u{116aa}\\u{116b8}\\u{116c0}-\\u{116c9}\\u{11700}-\\u{1171a}\\u{11730}-\\u{11739}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182b}\\u{118a0}-\\u{118e9}\\u{118ff}-\\u{11906}\\u{11909}\\u{1190c}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192f}\\u{1193f}\\u{11941}\\u{11950}-\\u{11959}\\u{119a0}-\\u{119a7}\\u{119aa}-\\u{119d0}\\u{119e1}\\u{119e3}\\u{11a00}\\u{11a0b}-\\u{11a32}\\u{11a3a}\\u{11a50}\\u{11a5c}-\\u{11a89}\\u{11a9d}\\u{11ab0}-\\u{11af8}\\u{11c00}-\\u{11c08}\\u{11c0a}-\\u{11c2e}\\u{11c40}\\u{11c50}-\\u{11c59}\\u{11c72}-\\u{11c8f}\\u{11d00}-\\u{11d06}\\u{11d08}-\\u{11d09}\\u{11d0b}-\\u{11d30}\\u{11d46}\\u{11d50}-\\u{11d59}\\u{11d60}-\\u{11d65}\\u{11d67}-\\u{11d68}\\u{11d6a}-\\u{11d89}\\u{11d98}\\u{11da0}-\\u{11da9}\\u{11ee0}-\\u{11ef2}\\u{11f02}\\u{11f04}-\\u{11f10}\\u{11f12}-\\u{11f33}\\u{11f50}-\\u{11f59}\\u{11fb0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12f90}-\\u{12ff0}\\u{13000}-\\u{1342f}\\u{13441}-\\u{13446}\\u{14400}-\\u{14646}\\u{16800}-\\u{16a38}\\u{16a40}-\\u{16a5e}\\u{16a60}-\\u{16a69}\\u{16a70}-\\u{16abe}\\u{16ac0}-\\u{16ac9}\\u{16ad0}-\\u{16aed}\\u{16b00}-\\u{16b2f}\\u{16b40}-\\u{16b43}\\u{16b50}-\\u{16b59}\\u{16b63}-\\u{16b77}\\u{16b7d}-\\u{16b8f}\\u{16e40}-\\u{16e7f}\\u{16f00}-\\u{16f4a}\\u{16f50}\\u{16f93}-\\u{16f9f}\\u{16fe0}-\\u{16fe1}\\u{16fe3}\\u{17000}-\\u{187f7}\\u{18800}-\\u{18cd5}\\u{18d00}-\\u{18d08}\\u{1aff0}-\\u{1aff3}\\u{1aff5}-\\u{1affb}\\u{1affd}-\\u{1affe}\\u{1b000}-\\u{1b122}\\u{1b132}\\u{1b150}-\\u{1b152}\\u{1b155}\\u{1b164}-\\u{1b167}\\u{1b170}-\\u{1b2fb}\\u{1bc00}-\\u{1bc6a}\\u{1bc70}-\\u{1bc7c}\\u{1bc80}-\\u{1bc88}\\u{1bc90}-\\u{1bc99}\\u{1d400}-\\u{1d454}\\u{1d456}-\\u{1d49c}\\u{1d49e}-\\u{1d49f}\\u{1d4a2}\\u{1d4a5}-\\u{1d4a6}\\u{1d4a9}-\\u{1d4ac}\\u{1d4ae}-\\u{1d4b9}\\u{1d4bb}\\u{1d4bd}-\\u{1d4c3}\\u{1d4c5}-\\u{1d505}\\u{1d507}-\\u{1d50a}\\u{1d50d}-\\u{1d514}\\u{1d516}-\\u{1d51c}\\u{1d51e}-\\u{1d539}\\u{1d53b}-\\u{1d53e}\\u{1d540}-\\u{1d544}\\u{1d546}\\u{1d54a}-\\u{1d550}\\u{1d552}-\\u{1d6a5}\\u{1d6a8}-\\u{1d6c0}\\u{1d6c2}-\\u{1d6da}\\u{1d6dc}-\\u{1d6fa}\\u{1d6fc}-\\u{1d714}\\u{1d716}-\\u{1d734}\\u{1d736}-\\u{1d74e}\\u{1d750}-\\u{1d76e}\\u{1d770}-\\u{1d788}\\u{1d78a}-\\u{1d7a8}\\u{1d7aa}-\\u{1d7c2}\\u{1d7c4}-\\u{1d7cb}\\u{1d7ce}-\\u{1d7ff}\\u{1df00}-\\u{1df1e}\\u{1df25}-\\u{1df2a}\\u{1e030}-\\u{1e06d}\\u{1e100}-\\u{1e12c}\\u{1e137}-\\u{1e13d}\\u{1e140}-\\u{1e149}\\u{1e14e}\\u{1e290}-\\u{1e2ad}\\u{1e2c0}-\\u{1e2eb}\\u{1e2f0}-\\u{1e2f9}\\u{1e4d0}-\\u{1e4eb}\\u{1e4f0}-\\u{1e4f9}\\u{1e7e0}-\\u{1e7e6}\\u{1e7e8}-\\u{1e7eb}\\u{1e7ed}-\\u{1e7ee}\\u{1e7f0}-\\u{1e7fe}\\u{1e800}-\\u{1e8c4}\\u{1e900}-\\u{1e943}\\u{1e94b}\\u{1e950}-\\u{1e959}\\u{1ee00}-\\u{1ee03}\\u{1ee05}-\\u{1ee1f}\\u{1ee21}-\\u{1ee22}\\u{1ee24}\\u{1ee27}\\u{1ee29}-\\u{1ee32}\\u{1ee34}-\\u{1ee37}\\u{1ee39}\\u{1ee3b}\\u{1ee42}\\u{1ee47}\\u{1ee49}\\u{1ee4b}\\u{1ee4d}-\\u{1ee4f}\\u{1ee51}-\\u{1ee52}\\u{1ee54}\\u{1ee57}\\u{1ee59}\\u{1ee5b}\\u{1ee5d}\\u{1ee5f}\\u{1ee61}-\\u{1ee62}\\u{1ee64}\\u{1ee67}-\\u{1ee6a}\\u{1ee6c}-\\u{1ee72}\\u{1ee74}-\\u{1ee77}\\u{1ee79}-\\u{1ee7c}\\u{1ee7e}\\u{1ee80}-\\u{1ee89}\\u{1ee8b}-\\u{1ee9b}\\u{1eea1}-\\u{1eea3}\\u{1eea5}-\\u{1eea9}\\u{1eeab}-\\u{1eebb}\\u{1fbf0}-\\u{1fbf9}\\u{20000}-\\u{2a6df}\\u{2a700}-\\u{2b739}\\u{2b740}-\\u{2b81d}\\u{2b820}-\\u{2cea1}\\u{2ceb0}-\\u{2ebe0}\\u{2f800}-\\u{2fa1d}\\u{30000}-\\u{3134a}\\u{31350}-\\u{323af}'
13