[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f1ix5b4tovpk62":3,"$f1hgjnca8fu6nn":58},{"sha":4,"committedAt":5,"fetchedAt":6,"source":7,"skill":8},"d545ab58bb89a5f88a08034a056af464abc229bf","2026-09-04T15:40:39.000Z","2026-09-04T15:49:35.573Z","github",{"slug":9,"name":10,"description":11,"tags":12,"author":16,"authorUrl":17,"requires":18,"badges":22,"fileCount":26,"totalBytes":27,"tree":28,"errors":57},"quality-hooks","Quality Hooks","Fail-closed Claude Code hooks: protect .env and credential files, lint after every edit, and flag an unwritten convention before context is compacted.",[13,14,15],"hooks","quality","safety","Patrity","https:\u002F\u002Fgithub.com\u002FPatrity",[19,20,21],"bash","jq","git",[23,13,24,25],"rules","settings","claude-md",6,12113,[29,42,48,51,54],{"name":13,"path":13,"type":30,"children":31},"dir",[32,38],{"name":33,"path":34,"type":35,"size":36,"kind":37},"lint-check.sh","hooks\u002Flint-check.sh","file",848,"text",{"name":39,"path":40,"type":35,"size":41,"kind":37},"protect-env.sh","hooks\u002Fprotect-env.sh",1083,{"name":23,"path":23,"type":30,"children":43},[44],{"name":45,"path":46,"type":35,"size":47,"kind":37},"hooks.md","rules\u002Fhooks.md",3167,{"name":49,"path":49,"type":35,"size":50,"kind":37},"CLAUDE.md",534,{"name":52,"path":52,"type":35,"size":53,"kind":37},"README.md",5117,{"name":55,"path":55,"type":35,"size":56,"kind":37},"settings.json",1364,[],{"path":52,"language":59,"size":53,"kind":37,"content":60,"frontmatterRaw":61,"body":62,"data":1149},"markdown","---\nname: Quality Hooks\ndescription: \"Fail-closed Claude Code hooks: protect .env and credential files, lint after every edit, and flag an unwritten convention before context is compacted.\"\ntags: [hooks, quality, safety]\nauthor: Patrity\nauthorUrl: https:\u002F\u002Fgithub.com\u002FPatrity\nrequires: [bash, jq, git]\n---\n\n# Quality Hooks\n\nThree checks the harness runs for you, so they hold on the turn where Claude is in a hurry.\n\n## What's inside\n\n| Path | Purpose |\n| --- | --- |\n| `hooks\u002Fprotect-env.sh` | `PreToolUse` on `Edit`\u002F`Write`: reads the tool call from stdin and refuses `.env*`, `credentials.json` and `secrets.*` — templates (`.env.example`, `.env.sample`) stay editable. |\n| `hooks\u002Flint-check.sh` | `PostToolUse` on `Edit`\u002F`Write`: runs `{{pm}} lint --quiet` and, while it is red, hands the failure back to Claude to fix in the same turn. |\n| `settings.json` | The wiring, including the `PreCompact` prompt hook that asks whether the session learned something worth writing down. |\n| `rules\u002Fhooks.md` | Fires on `.claude\u002Fhooks\u002F**` and `.claude\u002Fsettings.json` — the fail-closed pattern to copy when adding a check. |\n| `CLAUDE.md` | A pointer block to paste into your project's `CLAUDE.md`. |\n\n## Why fail-closed\n\nEvery wiring in `settings.json` runs its script if the file is on disk, and otherwise asks git\nwhether that script is *supposed* to exist:\n\n```bash\ns=\"$CLAUDE_PROJECT_DIR\u002F.claude\u002Fhooks\u002Fprotect-env.sh\"; if [ -f \"$s\" ]; then exec \"$s\"; fi; \\\ngit -C \"$CLAUDE_PROJECT_DIR\" cat-file -e HEAD:.claude\u002Fhooks\u002Fprotect-env.sh 2>\u002Fdev\u002Fnull && exit 2; exit 0\n```\n\nA tracked hook that has been deleted, renamed, or lost to a bad checkout exits 2 instead of\nsilently waving the action through: on `PreToolUse` that **refuses the edit**, and on `PostToolUse`\n— where the edit is already written — it tells Claude the gate is missing before it continues. A\nrepo that never installed the bundle is unaffected. The tempting one-liner —\n`[ ! -f \"$s\" ] || exec \"$s\"` — is the exact opposite: the gate vanishes the moment the file does.\n\n## Install\n\n```bash\nunzip quality-hooks.zip\nmkdir -p .claude\u002Fhooks .claude\u002Frules\ncp -R quality-hooks\u002Fhooks\u002F. .claude\u002Fhooks\u002F\nchmod +x .claude\u002Fhooks\u002F*.sh\ncp -R quality-hooks\u002Frules\u002F. .claude\u002Frules\u002F\ncat quality-hooks\u002FCLAUDE.md >> CLAUDE.md\n```\n\n`settings.json` is **merged**, not copied — you almost certainly have one already. The setup CLI\ndoes this for you (hook entries are unioned per event). By hand, copy the `hooks` key across into\n`.claude\u002Fsettings.json`, appending to the arrays that already exist for `PreToolUse`,\n`PostToolUse` and `PreCompact`.\n\nCommit the hooks with the executable bit set (`git ls-files -s .claude\u002Fhooks` must show `100755`).\nAn unexecutable hook exits non-zero for the wrong reason, which the harness treats as a hook error\n— it is logged, and the action goes ahead.\n\nExit 2 means two different things by event: on `PreToolUse` it refuses the tool call, and on\n`PostToolUse` the tool has already run, so the write stands and stderr is what Claude reads before\nit continues. `rules\u002Fhooks.md` has the full contract.\n\n## Placeholders\n\n`hooks\u002Flint-check.sh` contains `{{pm}}`. The setup CLI renders it from your answers; installing by\nhand, replace it with your package manager (`pnpm`, `npm`, `yarn`, `bun`). The script needs a\n`lint` script in `package.json` that accepts `--quiet`; in a monorepo where lint only exists at the\nroot, use `{{pm}} -w lint --quiet` instead.\n\n## Tuning the protected list\n\n`protect-env.sh` matches on the basename, in one `case` statement: every `.env*` file plus\n`credentials.json` and `secrets.*`. `.env.example` and `.env.sample` are exempted in the arm above\nit, so templates stay editable and do not go stale when config changes.\n\nThe pattern list is the whole policy — add your project's own (`*.pem`, `service-account.json`,\n`*.keystore`) to the blocking arm, and any further templates to the exempt arm.\n\n## The PreCompact hook\n\n`PreCompact` fires just before a long session is compressed, and this one is a `prompt` hook: the\nprompt and the hook input go to a separate fast model for a single turn, which answers\n`{\"ok\": true}` or `{\"ok\": false, \"reason\": \"…\"}`. It is asked one question — does this transcript\nhold a convention, pattern or gotcha that is not written down yet?\n\nThat model has no session context, no tools and no way to write a file, and `PreCompact` is not a\nblocking event: an `ok: false` reason is *surfaced to Claude*, nothing more. That is the whole\nvalue — the reminder arrives in the last moment where the context that learned the lesson still\nexists, and Claude can save it as a path-scoped rule or a skill before compaction takes it. The\nhook never writes the rule itself, and it never stops the compaction.\n\n## Requirements\n\n- `bash` and `jq` on PATH (`protect-env.sh` parses the tool call with `jq`).\n- `git`, for the missing-file guard. The guard is a no-op outside a git work tree.\n\n## Companion bundles\n\n- **[`docs-discipline`](\u002Fskill\u002Fdocs-discipline)** — the other half of \"write it down\": handovers and\n  a wiki-parity test for the knowledge that outgrows a rule.\n","name: Quality Hooks\ndescription: \"Fail-closed Claude Code hooks: protect .env and credential files, lint after every edit, and flag an unwritten convention before context is compacted.\"\ntags: [hooks, quality, safety]\nauthor: Patrity\nauthorUrl: https:\u002F\u002Fgithub.com\u002FPatrity\nrequires: [bash, jq, git]",{"type":63,"children":64},"root",[65,71,77,84,310,316,336,549,584,590,731,781,802,828,834,913,919,970,997,1003,1037,1064,1070,1115,1121,1143],{"type":66,"tag":67,"props":68,"children":69},"element","h1",{"id":9},[70],{"type":37,"value":10},{"type":66,"tag":72,"props":73,"children":74},"p",{},[75],{"type":37,"value":76},"Three checks the harness runs for you, so they hold on the turn where Claude is in a hurry.",{"type":66,"tag":78,"props":79,"children":81},"h2",{"id":80},"whats-inside",[82],{"type":37,"value":83},"What's inside",{"type":66,"tag":85,"props":86,"children":87},"table",{},[88,107],{"type":66,"tag":89,"props":90,"children":91},"thead",{},[92],{"type":66,"tag":93,"props":94,"children":95},"tr",{},[96,102],{"type":66,"tag":97,"props":98,"children":99},"th",{},[100],{"type":37,"value":101},"Path",{"type":66,"tag":97,"props":103,"children":104},{},[105],{"type":37,"value":106},"Purpose",{"type":66,"tag":108,"props":109,"children":110},"tbody",{},[111,190,232,256,287],{"type":66,"tag":93,"props":112,"children":113},{},[114,124],{"type":66,"tag":115,"props":116,"children":117},"td",{},[118],{"type":66,"tag":119,"props":120,"children":122},"code",{"className":121},[],[123],{"type":37,"value":40},{"type":66,"tag":115,"props":125,"children":126},{},[127,133,135,141,143,149,151,157,159,165,167,173,175,181,182,188],{"type":66,"tag":119,"props":128,"children":130},{"className":129},[],[131],{"type":37,"value":132},"PreToolUse",{"type":37,"value":134}," on ",{"type":66,"tag":119,"props":136,"children":138},{"className":137},[],[139],{"type":37,"value":140},"Edit",{"type":37,"value":142},"\u002F",{"type":66,"tag":119,"props":144,"children":146},{"className":145},[],[147],{"type":37,"value":148},"Write",{"type":37,"value":150},": reads the tool call from stdin and refuses ",{"type":66,"tag":119,"props":152,"children":154},{"className":153},[],[155],{"type":37,"value":156},".env*",{"type":37,"value":158},", ",{"type":66,"tag":119,"props":160,"children":162},{"className":161},[],[163],{"type":37,"value":164},"credentials.json",{"type":37,"value":166}," and ",{"type":66,"tag":119,"props":168,"children":170},{"className":169},[],[171],{"type":37,"value":172},"secrets.*",{"type":37,"value":174}," — templates (",{"type":66,"tag":119,"props":176,"children":178},{"className":177},[],[179],{"type":37,"value":180},".env.example",{"type":37,"value":158},{"type":66,"tag":119,"props":183,"children":185},{"className":184},[],[186],{"type":37,"value":187},".env.sample",{"type":37,"value":189},") stay editable.",{"type":66,"tag":93,"props":191,"children":192},{},[193,201],{"type":66,"tag":115,"props":194,"children":195},{},[196],{"type":66,"tag":119,"props":197,"children":199},{"className":198},[],[200],{"type":37,"value":34},{"type":66,"tag":115,"props":202,"children":203},{},[204,210,211,216,217,222,224,230],{"type":66,"tag":119,"props":205,"children":207},{"className":206},[],[208],{"type":37,"value":209},"PostToolUse",{"type":37,"value":134},{"type":66,"tag":119,"props":212,"children":214},{"className":213},[],[215],{"type":37,"value":140},{"type":37,"value":142},{"type":66,"tag":119,"props":218,"children":220},{"className":219},[],[221],{"type":37,"value":148},{"type":37,"value":223},": runs ",{"type":66,"tag":119,"props":225,"children":227},{"className":226},[],[228],{"type":37,"value":229},"{{pm}} lint --quiet",{"type":37,"value":231}," and, while it is red, hands the failure back to Claude to fix in the same turn.",{"type":66,"tag":93,"props":233,"children":234},{},[235,243],{"type":66,"tag":115,"props":236,"children":237},{},[238],{"type":66,"tag":119,"props":239,"children":241},{"className":240},[],[242],{"type":37,"value":55},{"type":66,"tag":115,"props":244,"children":245},{},[246,248,254],{"type":37,"value":247},"The wiring, including the ",{"type":66,"tag":119,"props":249,"children":251},{"className":250},[],[252],{"type":37,"value":253},"PreCompact",{"type":37,"value":255}," prompt hook that asks whether the session learned something worth writing down.",{"type":66,"tag":93,"props":257,"children":258},{},[259,267],{"type":66,"tag":115,"props":260,"children":261},{},[262],{"type":66,"tag":119,"props":263,"children":265},{"className":264},[],[266],{"type":37,"value":46},{"type":66,"tag":115,"props":268,"children":269},{},[270,272,278,279,285],{"type":37,"value":271},"Fires on ",{"type":66,"tag":119,"props":273,"children":275},{"className":274},[],[276],{"type":37,"value":277},".claude\u002Fhooks\u002F**",{"type":37,"value":166},{"type":66,"tag":119,"props":280,"children":282},{"className":281},[],[283],{"type":37,"value":284},".claude\u002Fsettings.json",{"type":37,"value":286}," — the fail-closed pattern to copy when adding a check.",{"type":66,"tag":93,"props":288,"children":289},{},[290,298],{"type":66,"tag":115,"props":291,"children":292},{},[293],{"type":66,"tag":119,"props":294,"children":296},{"className":295},[],[297],{"type":37,"value":49},{"type":66,"tag":115,"props":299,"children":300},{},[301,303,308],{"type":37,"value":302},"A pointer block to paste into your project's ",{"type":66,"tag":119,"props":304,"children":306},{"className":305},[],[307],{"type":37,"value":49},{"type":37,"value":309},".",{"type":66,"tag":78,"props":311,"children":313},{"id":312},"why-fail-closed",[314],{"type":37,"value":315},"Why fail-closed",{"type":66,"tag":72,"props":317,"children":318},{},[319,321,326,328,334],{"type":37,"value":320},"Every wiring in ",{"type":66,"tag":119,"props":322,"children":324},{"className":323},[],[325],{"type":37,"value":55},{"type":37,"value":327}," runs its script if the file is on disk, and otherwise asks git\nwhether that script is ",{"type":66,"tag":329,"props":330,"children":331},"em",{},[332],{"type":37,"value":333},"supposed",{"type":37,"value":335}," to exist:",{"type":66,"tag":337,"props":338,"children":342},"pre",{"className":339,"code":340,"language":19,"meta":341,"style":341},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","s=\"$CLAUDE_PROJECT_DIR\u002F.claude\u002Fhooks\u002Fprotect-env.sh\"; if [ -f \"$s\" ]; then exec \"$s\"; fi; \\\ngit -C \"$CLAUDE_PROJECT_DIR\" cat-file -e HEAD:.claude\u002Fhooks\u002Fprotect-env.sh 2>\u002Fdev\u002Fnull && exit 2; exit 0\n","",[343],{"type":66,"tag":119,"props":344,"children":345},{"__ignoreMap":341},[346,468],{"type":66,"tag":347,"props":348,"children":351},"span",{"class":349,"line":350},"line",1,[352,358,364,370,375,381,385,391,397,402,407,412,417,421,426,431,437,441,445,449,453,458,462],{"type":66,"tag":347,"props":353,"children":355},{"style":354},"--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8",[356],{"type":37,"value":357},"s",{"type":66,"tag":347,"props":359,"children":361},{"style":360},"--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583",[362],{"type":37,"value":363},"=",{"type":66,"tag":347,"props":365,"children":367},{"style":366},"--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF",[368],{"type":37,"value":369},"\"",{"type":66,"tag":347,"props":371,"children":372},{"style":354},[373],{"type":37,"value":374},"$CLAUDE_PROJECT_DIR",{"type":66,"tag":347,"props":376,"children":378},{"style":377},"--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF",[379],{"type":37,"value":380},"\u002F.claude\u002Fhooks\u002Fprotect-env.sh",{"type":66,"tag":347,"props":382,"children":383},{"style":366},[384],{"type":37,"value":369},{"type":66,"tag":347,"props":386,"children":388},{"style":387},"--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8",[389],{"type":37,"value":390},";",{"type":66,"tag":347,"props":392,"children":394},{"style":393},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit",[395],{"type":37,"value":396}," if",{"type":66,"tag":347,"props":398,"children":399},{"style":387},[400],{"type":37,"value":401}," [",{"type":66,"tag":347,"props":403,"children":404},{"style":360},[405],{"type":37,"value":406}," -f",{"type":66,"tag":347,"props":408,"children":409},{"style":366},[410],{"type":37,"value":411}," \"",{"type":66,"tag":347,"props":413,"children":414},{"style":354},[415],{"type":37,"value":416},"$s",{"type":66,"tag":347,"props":418,"children":419},{"style":366},[420],{"type":37,"value":369},{"type":66,"tag":347,"props":422,"children":423},{"style":387},[424],{"type":37,"value":425}," ];",{"type":66,"tag":347,"props":427,"children":428},{"style":393},[429],{"type":37,"value":430}," then",{"type":66,"tag":347,"props":432,"children":434},{"style":433},"--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF",[435],{"type":37,"value":436}," exec",{"type":66,"tag":347,"props":438,"children":439},{"style":366},[440],{"type":37,"value":411},{"type":66,"tag":347,"props":442,"children":443},{"style":354},[444],{"type":37,"value":416},{"type":66,"tag":347,"props":446,"children":447},{"style":366},[448],{"type":37,"value":369},{"type":66,"tag":347,"props":450,"children":451},{"style":387},[452],{"type":37,"value":390},{"type":66,"tag":347,"props":454,"children":455},{"style":393},[456],{"type":37,"value":457}," fi",{"type":66,"tag":347,"props":459,"children":460},{"style":387},[461],{"type":37,"value":390},{"type":66,"tag":347,"props":463,"children":465},{"style":464},"--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF",[466],{"type":37,"value":467}," \\\n",{"type":66,"tag":347,"props":469,"children":471},{"class":349,"line":470},2,[472,477,483,487,491,495,500,505,510,515,520,525,530,536,540,544],{"type":66,"tag":347,"props":473,"children":475},{"style":474},"--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0",[476],{"type":37,"value":21},{"type":66,"tag":347,"props":478,"children":480},{"style":479},"--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF",[481],{"type":37,"value":482}," -C",{"type":66,"tag":347,"props":484,"children":485},{"style":366},[486],{"type":37,"value":411},{"type":66,"tag":347,"props":488,"children":489},{"style":354},[490],{"type":37,"value":374},{"type":66,"tag":347,"props":492,"children":493},{"style":366},[494],{"type":37,"value":369},{"type":66,"tag":347,"props":496,"children":497},{"style":377},[498],{"type":37,"value":499}," cat-file",{"type":66,"tag":347,"props":501,"children":502},{"style":479},[503],{"type":37,"value":504}," -e",{"type":66,"tag":347,"props":506,"children":507},{"style":377},[508],{"type":37,"value":509}," HEAD:.claude\u002Fhooks\u002Fprotect-env.sh",{"type":66,"tag":347,"props":511,"children":512},{"style":360},[513],{"type":37,"value":514}," 2>",{"type":66,"tag":347,"props":516,"children":517},{"style":377},[518],{"type":37,"value":519},"\u002Fdev\u002Fnull",{"type":66,"tag":347,"props":521,"children":522},{"style":387},[523],{"type":37,"value":524}," &&",{"type":66,"tag":347,"props":526,"children":527},{"style":433},[528],{"type":37,"value":529}," exit",{"type":66,"tag":347,"props":531,"children":533},{"style":532},"--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF",[534],{"type":37,"value":535}," 2",{"type":66,"tag":347,"props":537,"children":538},{"style":387},[539],{"type":37,"value":390},{"type":66,"tag":347,"props":541,"children":542},{"style":433},[543],{"type":37,"value":529},{"type":66,"tag":347,"props":545,"children":546},{"style":532},[547],{"type":37,"value":548}," 0\n",{"type":66,"tag":72,"props":550,"children":551},{},[552,554,559,561,567,569,574,576,582],{"type":37,"value":553},"A tracked hook that has been deleted, renamed, or lost to a bad checkout exits 2 instead of\nsilently waving the action through: on ",{"type":66,"tag":119,"props":555,"children":557},{"className":556},[],[558],{"type":37,"value":132},{"type":37,"value":560}," that ",{"type":66,"tag":562,"props":563,"children":564},"strong",{},[565],{"type":37,"value":566},"refuses the edit",{"type":37,"value":568},", and on ",{"type":66,"tag":119,"props":570,"children":572},{"className":571},[],[573],{"type":37,"value":209},{"type":37,"value":575},"\n— where the edit is already written — it tells Claude the gate is missing before it continues. A\nrepo that never installed the bundle is unaffected. The tempting one-liner —\n",{"type":66,"tag":119,"props":577,"children":579},{"className":578},[],[580],{"type":37,"value":581},"[ ! -f \"$s\" ] || exec \"$s\"",{"type":37,"value":583}," — is the exact opposite: the gate vanishes the moment the file does.",{"type":66,"tag":78,"props":585,"children":587},{"id":586},"install",[588],{"type":37,"value":589},"Install",{"type":66,"tag":337,"props":591,"children":593},{"className":339,"code":592,"language":19,"meta":341,"style":341},"unzip quality-hooks.zip\nmkdir -p .claude\u002Fhooks .claude\u002Frules\ncp -R quality-hooks\u002Fhooks\u002F. .claude\u002Fhooks\u002F\nchmod +x .claude\u002Fhooks\u002F*.sh\ncp -R quality-hooks\u002Frules\u002F. .claude\u002Frules\u002F\ncat quality-hooks\u002FCLAUDE.md >> CLAUDE.md\n",[594],{"type":66,"tag":119,"props":595,"children":596},{"__ignoreMap":341},[597,610,633,657,686,708],{"type":66,"tag":347,"props":598,"children":599},{"class":349,"line":350},[600,605],{"type":66,"tag":347,"props":601,"children":602},{"style":474},[603],{"type":37,"value":604},"unzip",{"type":66,"tag":347,"props":606,"children":607},{"style":377},[608],{"type":37,"value":609}," quality-hooks.zip\n",{"type":66,"tag":347,"props":611,"children":612},{"class":349,"line":470},[613,618,623,628],{"type":66,"tag":347,"props":614,"children":615},{"style":474},[616],{"type":37,"value":617},"mkdir",{"type":66,"tag":347,"props":619,"children":620},{"style":479},[621],{"type":37,"value":622}," -p",{"type":66,"tag":347,"props":624,"children":625},{"style":377},[626],{"type":37,"value":627}," .claude\u002Fhooks",{"type":66,"tag":347,"props":629,"children":630},{"style":377},[631],{"type":37,"value":632}," .claude\u002Frules\n",{"type":66,"tag":347,"props":634,"children":636},{"class":349,"line":635},3,[637,642,647,652],{"type":66,"tag":347,"props":638,"children":639},{"style":474},[640],{"type":37,"value":641},"cp",{"type":66,"tag":347,"props":643,"children":644},{"style":479},[645],{"type":37,"value":646}," -R",{"type":66,"tag":347,"props":648,"children":649},{"style":377},[650],{"type":37,"value":651}," quality-hooks\u002Fhooks\u002F.",{"type":66,"tag":347,"props":653,"children":654},{"style":377},[655],{"type":37,"value":656}," .claude\u002Fhooks\u002F\n",{"type":66,"tag":347,"props":658,"children":660},{"class":349,"line":659},4,[661,666,671,676,681],{"type":66,"tag":347,"props":662,"children":663},{"style":474},[664],{"type":37,"value":665},"chmod",{"type":66,"tag":347,"props":667,"children":668},{"style":377},[669],{"type":37,"value":670}," +x",{"type":66,"tag":347,"props":672,"children":673},{"style":377},[674],{"type":37,"value":675}," .claude\u002Fhooks\u002F",{"type":66,"tag":347,"props":677,"children":678},{"style":464},[679],{"type":37,"value":680},"*",{"type":66,"tag":347,"props":682,"children":683},{"style":377},[684],{"type":37,"value":685},".sh\n",{"type":66,"tag":347,"props":687,"children":689},{"class":349,"line":688},5,[690,694,698,703],{"type":66,"tag":347,"props":691,"children":692},{"style":474},[693],{"type":37,"value":641},{"type":66,"tag":347,"props":695,"children":696},{"style":479},[697],{"type":37,"value":646},{"type":66,"tag":347,"props":699,"children":700},{"style":377},[701],{"type":37,"value":702}," quality-hooks\u002Frules\u002F.",{"type":66,"tag":347,"props":704,"children":705},{"style":377},[706],{"type":37,"value":707}," .claude\u002Frules\u002F\n",{"type":66,"tag":347,"props":709,"children":710},{"class":349,"line":26},[711,716,721,726],{"type":66,"tag":347,"props":712,"children":713},{"style":474},[714],{"type":37,"value":715},"cat",{"type":66,"tag":347,"props":717,"children":718},{"style":377},[719],{"type":37,"value":720}," quality-hooks\u002FCLAUDE.md",{"type":66,"tag":347,"props":722,"children":723},{"style":360},[724],{"type":37,"value":725}," >>",{"type":66,"tag":347,"props":727,"children":728},{"style":377},[729],{"type":37,"value":730}," CLAUDE.md\n",{"type":66,"tag":72,"props":732,"children":733},{},[734,739,741,746,748,753,755,760,762,767,769,774,775,780],{"type":66,"tag":119,"props":735,"children":737},{"className":736},[],[738],{"type":37,"value":55},{"type":37,"value":740}," is ",{"type":66,"tag":562,"props":742,"children":743},{},[744],{"type":37,"value":745},"merged",{"type":37,"value":747},", not copied — you almost certainly have one already. The setup CLI\ndoes this for you (hook entries are unioned per event). By hand, copy the ",{"type":66,"tag":119,"props":749,"children":751},{"className":750},[],[752],{"type":37,"value":13},{"type":37,"value":754}," key across into\n",{"type":66,"tag":119,"props":756,"children":758},{"className":757},[],[759],{"type":37,"value":284},{"type":37,"value":761},", appending to the arrays that already exist for ",{"type":66,"tag":119,"props":763,"children":765},{"className":764},[],[766],{"type":37,"value":132},{"type":37,"value":768},",\n",{"type":66,"tag":119,"props":770,"children":772},{"className":771},[],[773],{"type":37,"value":209},{"type":37,"value":166},{"type":66,"tag":119,"props":776,"children":778},{"className":777},[],[779],{"type":37,"value":253},{"type":37,"value":309},{"type":66,"tag":72,"props":782,"children":783},{},[784,786,792,794,800],{"type":37,"value":785},"Commit the hooks with the executable bit set (",{"type":66,"tag":119,"props":787,"children":789},{"className":788},[],[790],{"type":37,"value":791},"git ls-files -s .claude\u002Fhooks",{"type":37,"value":793}," must show ",{"type":66,"tag":119,"props":795,"children":797},{"className":796},[],[798],{"type":37,"value":799},"100755",{"type":37,"value":801},").\nAn unexecutable hook exits non-zero for the wrong reason, which the harness treats as a hook error\n— it is logged, and the action goes ahead.",{"type":66,"tag":72,"props":803,"children":804},{},[805,807,812,814,819,821,826],{"type":37,"value":806},"Exit 2 means two different things by event: on ",{"type":66,"tag":119,"props":808,"children":810},{"className":809},[],[811],{"type":37,"value":132},{"type":37,"value":813}," it refuses the tool call, and on\n",{"type":66,"tag":119,"props":815,"children":817},{"className":816},[],[818],{"type":37,"value":209},{"type":37,"value":820}," the tool has already run, so the write stands and stderr is what Claude reads before\nit continues. ",{"type":66,"tag":119,"props":822,"children":824},{"className":823},[],[825],{"type":37,"value":46},{"type":37,"value":827}," has the full contract.",{"type":66,"tag":78,"props":829,"children":831},{"id":830},"placeholders",[832],{"type":37,"value":833},"Placeholders",{"type":66,"tag":72,"props":835,"children":836},{},[837,842,844,850,852,858,859,865,866,872,873,879,881,887,889,895,897,903,905,911],{"type":66,"tag":119,"props":838,"children":840},{"className":839},[],[841],{"type":37,"value":34},{"type":37,"value":843}," contains ",{"type":66,"tag":119,"props":845,"children":847},{"className":846},[],[848],{"type":37,"value":849},"{{pm}}",{"type":37,"value":851},". The setup CLI renders it from your answers; installing by\nhand, replace it with your package manager (",{"type":66,"tag":119,"props":853,"children":855},{"className":854},[],[856],{"type":37,"value":857},"pnpm",{"type":37,"value":158},{"type":66,"tag":119,"props":860,"children":862},{"className":861},[],[863],{"type":37,"value":864},"npm",{"type":37,"value":158},{"type":66,"tag":119,"props":867,"children":869},{"className":868},[],[870],{"type":37,"value":871},"yarn",{"type":37,"value":158},{"type":66,"tag":119,"props":874,"children":876},{"className":875},[],[877],{"type":37,"value":878},"bun",{"type":37,"value":880},"). The script needs a\n",{"type":66,"tag":119,"props":882,"children":884},{"className":883},[],[885],{"type":37,"value":886},"lint",{"type":37,"value":888}," script in ",{"type":66,"tag":119,"props":890,"children":892},{"className":891},[],[893],{"type":37,"value":894},"package.json",{"type":37,"value":896}," that accepts ",{"type":66,"tag":119,"props":898,"children":900},{"className":899},[],[901],{"type":37,"value":902},"--quiet",{"type":37,"value":904},"; in a monorepo where lint only exists at the\nroot, use ",{"type":66,"tag":119,"props":906,"children":908},{"className":907},[],[909],{"type":37,"value":910},"{{pm}} -w lint --quiet",{"type":37,"value":912}," instead.",{"type":66,"tag":78,"props":914,"children":916},{"id":915},"tuning-the-protected-list",[917],{"type":37,"value":918},"Tuning the protected list",{"type":66,"tag":72,"props":920,"children":921},{},[922,927,929,935,937,942,944,949,950,955,957,962,963,968],{"type":66,"tag":119,"props":923,"children":925},{"className":924},[],[926],{"type":37,"value":39},{"type":37,"value":928}," matches on the basename, in one ",{"type":66,"tag":119,"props":930,"children":932},{"className":931},[],[933],{"type":37,"value":934},"case",{"type":37,"value":936}," statement: every ",{"type":66,"tag":119,"props":938,"children":940},{"className":939},[],[941],{"type":37,"value":156},{"type":37,"value":943}," file plus\n",{"type":66,"tag":119,"props":945,"children":947},{"className":946},[],[948],{"type":37,"value":164},{"type":37,"value":166},{"type":66,"tag":119,"props":951,"children":953},{"className":952},[],[954],{"type":37,"value":172},{"type":37,"value":956},". ",{"type":66,"tag":119,"props":958,"children":960},{"className":959},[],[961],{"type":37,"value":180},{"type":37,"value":166},{"type":66,"tag":119,"props":964,"children":966},{"className":965},[],[967],{"type":37,"value":187},{"type":37,"value":969}," are exempted in the arm above\nit, so templates stay editable and do not go stale when config changes.",{"type":66,"tag":72,"props":971,"children":972},{},[973,975,981,982,988,989,995],{"type":37,"value":974},"The pattern list is the whole policy — add your project's own (",{"type":66,"tag":119,"props":976,"children":978},{"className":977},[],[979],{"type":37,"value":980},"*.pem",{"type":37,"value":158},{"type":66,"tag":119,"props":983,"children":985},{"className":984},[],[986],{"type":37,"value":987},"service-account.json",{"type":37,"value":768},{"type":66,"tag":119,"props":990,"children":992},{"className":991},[],[993],{"type":37,"value":994},"*.keystore",{"type":37,"value":996},") to the blocking arm, and any further templates to the exempt arm.",{"type":66,"tag":78,"props":998,"children":1000},{"id":999},"the-precompact-hook",[1001],{"type":37,"value":1002},"The PreCompact hook",{"type":66,"tag":72,"props":1004,"children":1005},{},[1006,1011,1013,1019,1021,1027,1029,1035],{"type":66,"tag":119,"props":1007,"children":1009},{"className":1008},[],[1010],{"type":37,"value":253},{"type":37,"value":1012}," fires just before a long session is compressed, and this one is a ",{"type":66,"tag":119,"props":1014,"children":1016},{"className":1015},[],[1017],{"type":37,"value":1018},"prompt",{"type":37,"value":1020}," hook: the\nprompt and the hook input go to a separate fast model for a single turn, which answers\n",{"type":66,"tag":119,"props":1022,"children":1024},{"className":1023},[],[1025],{"type":37,"value":1026},"{\"ok\": true}",{"type":37,"value":1028}," or ",{"type":66,"tag":119,"props":1030,"children":1032},{"className":1031},[],[1033],{"type":37,"value":1034},"{\"ok\": false, \"reason\": \"…\"}",{"type":37,"value":1036},". It is asked one question — does this transcript\nhold a convention, pattern or gotcha that is not written down yet?",{"type":66,"tag":72,"props":1038,"children":1039},{},[1040,1042,1047,1049,1055,1057,1062],{"type":37,"value":1041},"That model has no session context, no tools and no way to write a file, and ",{"type":66,"tag":119,"props":1043,"children":1045},{"className":1044},[],[1046],{"type":37,"value":253},{"type":37,"value":1048}," is not a\nblocking event: an ",{"type":66,"tag":119,"props":1050,"children":1052},{"className":1051},[],[1053],{"type":37,"value":1054},"ok: false",{"type":37,"value":1056}," reason is ",{"type":66,"tag":329,"props":1058,"children":1059},{},[1060],{"type":37,"value":1061},"surfaced to Claude",{"type":37,"value":1063},", nothing more. That is the whole\nvalue — the reminder arrives in the last moment where the context that learned the lesson still\nexists, and Claude can save it as a path-scoped rule or a skill before compaction takes it. The\nhook never writes the rule itself, and it never stops the compaction.",{"type":66,"tag":78,"props":1065,"children":1067},{"id":1066},"requirements",[1068],{"type":37,"value":1069},"Requirements",{"type":66,"tag":1071,"props":1072,"children":1073},"ul",{},[1074,1105],{"type":66,"tag":1075,"props":1076,"children":1077},"li",{},[1078,1083,1084,1089,1091,1096,1098,1103],{"type":66,"tag":119,"props":1079,"children":1081},{"className":1080},[],[1082],{"type":37,"value":19},{"type":37,"value":166},{"type":66,"tag":119,"props":1085,"children":1087},{"className":1086},[],[1088],{"type":37,"value":20},{"type":37,"value":1090}," on PATH (",{"type":66,"tag":119,"props":1092,"children":1094},{"className":1093},[],[1095],{"type":37,"value":39},{"type":37,"value":1097}," parses the tool call with ",{"type":66,"tag":119,"props":1099,"children":1101},{"className":1100},[],[1102],{"type":37,"value":20},{"type":37,"value":1104},").",{"type":66,"tag":1075,"props":1106,"children":1107},{},[1108,1113],{"type":66,"tag":119,"props":1109,"children":1111},{"className":1110},[],[1112],{"type":37,"value":21},{"type":37,"value":1114},", for the missing-file guard. The guard is a no-op outside a git work tree.",{"type":66,"tag":78,"props":1116,"children":1118},{"id":1117},"companion-bundles",[1119],{"type":37,"value":1120},"Companion bundles",{"type":66,"tag":1071,"props":1122,"children":1123},{},[1124],{"type":66,"tag":1075,"props":1125,"children":1126},{},[1127,1141],{"type":66,"tag":562,"props":1128,"children":1129},{},[1130],{"type":66,"tag":1131,"props":1132,"children":1134},"a",{"href":1133},"\u002Fskill\u002Fdocs-discipline",[1135],{"type":66,"tag":119,"props":1136,"children":1138},{"className":1137},[],[1139],{"type":37,"value":1140},"docs-discipline",{"type":37,"value":1142}," — the other half of \"write it down\": handovers and\na wiki-parity test for the knowledge that outgrows a rule.",{"type":66,"tag":1144,"props":1145,"children":1146},"style",{},[1147],{"type":37,"value":1148},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"name":10,"description":11,"tags":1150,"author":16,"authorUrl":17,"requires":1151},[13,14,15],[19,20,21]]