Engineering-craft skills that sharpen the edge: red-green TDD, flaky-test audit, pre-decision doubt review, merge-conflict resolution, skill linting, and a…

Engineering-craft skills that sharpen the edge before you cut. Six small, composable skills — each carries a deterministic self-verify, so "done" is a checkable fact.
Docs live at the repository root, which is not shipped to the install path — these are absolute so they resolve from the plugin cache too:
Local to this plugin:
hooks/register.ts 29 lines1import type { Register } from 'claude-code'
2import { flaggedUnits } from './claim-check.ts'
3
4const MAX_SHOWN = 10
5const MAX_REASON_CHARS = 2000
6
7function claimReason(reply: string): string | undefined {
8 const flagged = flaggedUnits(reply).map(([number, unit]) => `line ${number}: "${unit}"`)
9 if (!flagged.length) return undefined
10 const shown = flagged.slice(0, MAX_SHOWN)
11 if (flagged.length > MAX_SHOWN) shown.push(`… and ${flagged.length - MAX_SHOWN} more`)
12 const body = [...shown.join('\n')].slice(0, MAX_REASON_CHARS).join('')
13 return (
14 'Unbacked claim in the reply:\n' +
15 `${body}\n` +
16 'Name the exit code, cite the source, or label it advisory / model-judgment / opt-in — or drop the blocks / enforces / gates / denies / prevents / refuses verb.'
17 )
18}
19
20export const register: Register = (on) => {
21 on('classic.Stop', async ($, e, next) => {
22 const result = await next(e)
23 if (e.stop_hook_active) return result
24 const reason = claimReason(e.last_assistant_message ?? '')
25 if (!reason) return result
26 return { ...result, additionalContext: [...(result.additionalContext ?? []), reason] }
27 })
28}
29hooks/claim-check.ts 62 lines1import { splitLines, strip, unicodeRegex } from './text.ts'
2
3const CLAIM = unicodeRegex(
4 '(?:`[^`]+`' +
5 '|\\b(?:the|this)\\s+(?:runner|hook|script|gate|check(?:er)?|guard|linter)\\b)' +
6 '\\s+(?:\\w[\\w-]*\\s+){0,3}?' +
7 '(?:hard-)?(?:blocks|enforces|gates|denies|prevents|refuses)\\b' +
8 '(?!\\s+\\w+ed\\b)',
9 'i',
10)
11
12const BACKED = unicodeRegex(
13 '\\bexits?\\s+\\d+\\b|\\bexit\\s+cod\\w*\\b|\\breturn(?:s|ing)?\\s+exit\\b' +
14 '|\\bnon-zero\\b|\\bexit-code\\b|https?://|#\\d{2,}' +
15 '|(?<![\\w./~-])(?:[\\w.~-]*/[\\w./~-]*' +
16 '|[\\w.~-]*\\.(?:[cm]?[jt]sx?|py|sh|zsh|fish|md|json|jsonc|toml|ya?ml|tmpl|rs|go|rb|lua|css|html|txt)):\\d+\\b',
17 'i',
18)
19
20const LABELLED = unicodeRegex(
21 '\\badvisory\\b|\\bmodel[\\s-]judge?ment\\b|\\bopt-in\\b|\\bnag\\b' +
22 '|\\bnon-blocking\\b|\\bnever blocks\\b|\\bblocks nothing\\b|\\bdoes not block\\b' +
23 '|\\bnot a gate\\b' +
24 '|\\bcode\\s*[—–-]\\s*`[^`]+`',
25 'i',
26)
27
28const SENTENCE_END = unicodeRegex('(?<=[.!?])\\s+')
29const SHOWN_MAX = 110
30
31function units(text: string): Array<[number, string]> {
32 const found: Array<[number, string]> = []
33 splitLines(text).forEach((line, index) => {
34 const stripped = strip(line)
35 if (!stripped) return
36 if (stripped.startsWith('|')) {
37 found.push([index + 1, stripped])
38 return
39 }
40 for (const piece of stripped.split(SENTENCE_END)) {
41 if (strip(piece)) found.push([index + 1, strip(piece)])
42 }
43 })
44 return found
45}
46
47function shown(unit: string): string {
48 const points = [...unit]
49 if (points.length <= SHOWN_MAX) return unit
50 return points.slice(0, SHOWN_MAX - 3).join('') + '...'
51}
52
53export function flaggedUnits(text: string): [number, string][] {
54 return units(text)
55 .filter(([, unit]) => CLAIM.test(unit) && !BACKED.test(unit) && !LABELLED.test(unit))
56 .map(([number, unit]) => [number, shown(unit)])
57}
58
59export function scanText(text: string, label: string): string[] {
60 return flaggedUnits(text).map(([number, unit]) => `${label}:${number}: ${unit}`)
61}
62hooks/text.ts 74 lines1const WORD_SET = '\\p{L}\\p{N}_'
2const SPACE_SET = '\\t\\n\\v\\f\\r\\x1c-\\x1f \\x85\\xa0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000'
3const DIGIT_SET = '\\p{Nd}'
4const WORD = `[${WORD_SET}]`
5const BOUNDARY = `(?:(?<=${WORD})(?!${WORD})|(?<!${WORD})(?=${WORD}))`
6const LINE_BREAK = /\r\n|[\n\r\v\f\x1c-\x1e\x85\u2028\u2029]/
7const DECIMAL = /^\p{Nd}$/u
8const INTEGER = /^[+-]?\p{Nd}+(?:_\p{Nd}+)*$/u
9const EDGE_SPACE = new RegExp(`^[${SPACE_SET}]+|[${SPACE_SET}]+$`, 'gu')
10
11const IN_CLASS: Record<string, string> = { w: WORD_SET, s: SPACE_SET, d: DIGIT_SET }
12const OUTSIDE: Record<string, string> = {
13 w: WORD,
14 s: `[${SPACE_SET}]`,
15 d: `[${DIGIT_SET}]`,
16 b: BOUNDARY,
17}
18
19export function unicodeRegex(source: string, flags = ''): RegExp {
20 let out = ''
21 let inClass = false
22 for (let i = 0; i < source.length; i++) {
23 const char = source[i] ?? ''
24 if (char === '\\') {
25 const next = source[i + 1] ?? ''
26 i++
27 const table = inClass ? IN_CLASS : OUTSIDE
28 out += table[next] ?? `\\${next}`
29 continue
30 }
31 if (char === '[' && !inClass) inClass = true
32 else if (char === ']' && inClass) inClass = false
33 out += char
34 }
35 return new RegExp(out, `${flags}u`)
36}
37
38export function strip(text: string): string {
39 return text.replace(EDGE_SPACE, '')
40}
41
42export function splitLines(text: string): string[] {
43 if (!text) return []
44 const lines = text.split(LINE_BREAK)
45 if (lines[lines.length - 1] === '') lines.pop()
46 return lines
47}
48
49function digitValue(char: string): number {
50 let point = char.codePointAt(0) ?? 0
51 const original = point
52 while (DECIMAL.test(String.fromCodePoint(point - 1))) point -= 1
53 return (original - point) % 10
54}
55
56export function pythonInt(raw: string): number | undefined {
57 const text = strip(raw)
58 if (!INTEGER.test(text)) return undefined
59 const sign = text.startsWith('-') ? -1 : 1
60 const digits = [...text.replace(/^[+-]/, '').replaceAll('_', '')]
61 return sign * digits.reduce((total, char) => total * 10 + digitValue(char), 0)
62}
63
64export function byCodePoint(a: string, b: string): number {
65 const left = [...a]
66 const right = [...b]
67 for (let i = 0; i < Math.min(left.length, right.length); i++) {
68 const x = left[i]?.codePointAt(0) ?? 0
69 const y = right[i]?.codePointAt(0) ?? 0
70 if (x !== y) return x - y
71 }
72 return left.length - right.length
73}
74