Skip to content

Commit 8f5b58b

Browse files
committed
chore(wheelhouse): cascade template@38f04dfe
Auto-applied by socket-wheelhouse sync-scaffolding into cascade-socket-bin-48625. 161 file(s) touched: - .claude/hooks/fleet/_shared/dated-citation.mts - .claude/hooks/fleet/_shared/foreign-paths.mts - .claude/hooks/fleet/alpha-sort-reminder/README.md - .claude/hooks/fleet/alpha-sort-reminder/index.mts - .claude/hooks/fleet/claude-segmentation-guard/README.md - .claude/hooks/fleet/commit-cadence-reminder/README.md - .claude/hooks/fleet/commit-cadence-reminder/index.mts - .claude/hooks/fleet/compound-lessons-reminder/index.mts - .claude/hooks/fleet/concurrent-cargo-build-guard/README.md - .claude/hooks/fleet/concurrent-cargo-build-guard/index.mts - .claude/hooks/fleet/concurrent-cargo-build-guard/test/index.test.mts - .claude/hooks/fleet/copy-on-select-hint-reminder/README.md - .claude/hooks/fleet/copy-on-select-hint-reminder/index.mts - .claude/hooks/fleet/copy-on-select-hint-reminder/test/index.test.mts - .claude/hooks/fleet/dated-citation-reminder/README.md - .claude/hooks/fleet/dated-citation-reminder/index.mts - .claude/hooks/fleet/dated-citation-reminder/package.json - .claude/hooks/fleet/dated-citation-reminder/test/index.test.mts - .claude/hooks/fleet/dated-citation-reminder/tsconfig.json - .claude/hooks/fleet/dirty-worktree-stop-reminder/README.md ... and 141 more
1 parent a89c012 commit 8f5b58b

161 files changed

Lines changed: 8734 additions & 701 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @file Shared "is this rule rationale a dated incident log?" matcher. The
3+
* dated-citation-reminder (PreToolUse, nudges at edit time) and the
4+
* rule-citations-are-generic check (`check --all`, blocks committed prose)
5+
* both gate on the same definition, so the two surfaces never drift on what
6+
* counts as a too-specific citation.
7+
*
8+
* The rule (CLAUDE.md "Compound lessons into rules"): when a rule / hook /
9+
* SKILL / doc cites the case that motivated it, write it GENERICALLY, framed
10+
* as an example ("e.g. a cascade that shipped without its reconciled
11+
* lockfile") — NOT as a dated incident log ("2026-06-07: pnpm 11.0.0 vs
12+
* 11.5.1 at SHA abc1234"). Dates, version deltas, percentages, and commit
13+
* SHAs age into a changelog and leak detail; the example shape is timeless.
14+
*
15+
* Scope: only RATIONALE prose is flagged — a line carrying a rationale marker
16+
* (`**Why:**`, "incident", "Past incident", "regression", "red-lined") that
17+
* ALSO carries a specificity token. A bare date elsewhere (a SHA-pin
18+
* `# <tag> (YYYY-MM-DD)` comment, a `# published: YYYY-MM-DD` soak annotation,
19+
* a `.gitmodules` `# name-version`, a CHANGELOG entry, a version constant in
20+
* code) is NOT rationale and is left alone — those dates are required by other
21+
* rules. Memory files are exempt at the path layer (see EXEMPT_PATH_RE).
22+
*/
23+
24+
// A line is "rationale" if it carries one of these markers. Only rationale
25+
// lines are candidates — this keeps the matcher off required-date annotations.
26+
const RATIONALE_MARKER_RE =
27+
/\*\*Why:\*\*|\b(?:past\s+)?incident\b|\bred-lined?\b|\bregressed?\b|\bregression\b/i
28+
29+
// Specificity tokens that turn a generic example into a dated incident log.
30+
const SPECIFICITY_PATTERNS: ReadonlyArray<{ label: string; regex: RegExp }> = [
31+
// ISO-8601 date — the loudest "this is a log entry, not an example" signal.
32+
{ label: 'ISO date (YYYY-MM-DD)', regex: /\b20\d\d-\d\d-\d\d\b/ },
33+
// Percentage delta (coverage 98.9%→99.15%, etc).
34+
{
35+
label: 'percentage delta',
36+
regex: /\b\d+(?:\.\d+)?%\s*(?:|->|to)\s*\d+(?:\.\d+)?%/,
37+
},
38+
// Version delta — two semver-ish versions joined by vs / → / -> ("11.4.0 vs
39+
// 11.3.0", "bump to 11.5.0"). A SINGLE version alone is not flagged (a rule
40+
// may legitimately name the version it targets); the delta framing is what
41+
// marks a changelog entry.
42+
{
43+
label: 'version delta',
44+
regex:
45+
/\bv?\d+\.\d+(?:\.\d+)?\s*(?:vs\.?||->|versus)\s*v?\d+\.\d+(?:\.\d+)?\b/i,
46+
},
47+
// Commit SHA (7–40 hex) named in rationale prose ("at SHA abc1234", "broke
48+
// at deadbeef"). Requires a sha-ish lead-in word so prose words like
49+
// "deceased" or hex-looking ids elsewhere don't false-fire.
50+
{
51+
label: 'commit SHA',
52+
regex: /\b(?:sha|commit|at)\s+[0-9a-f]{7,40}\b/i,
53+
},
54+
]
55+
56+
// Paths whose prose is NOT fleet-facing rule rationale, so dated citations are
57+
// fine there. Memory files keep absolute dates for recall; CHANGELOG has its
58+
// own date convention; lockstep headers + .gitmodules carry required version
59+
// stamps.
60+
export const EXEMPT_PATH_RE =
61+
/(?:^|\/)(?:CHANGELOG\.md|\.gitmodules|lockstep\.json)$|\/memory\/|\/\.claude\/(?:plans|reports)\//
62+
63+
export interface DatedCitationHit {
64+
readonly label: string
65+
readonly line: number
66+
readonly text: string
67+
}
68+
69+
/**
70+
* Scan prose for dated-incident citations. Returns one hit per offending
71+
* rationale line (first matching specificity token wins per line). `text` is
72+
* the trimmed offending line, truncated for display.
73+
*/
74+
export function findDatedCitations(content: string): DatedCitationHit[] {
75+
const lines = content.split('\n')
76+
const hits: DatedCitationHit[] = []
77+
for (let i = 0, { length } = lines; i < length; i += 1) {
78+
const line = lines[i]!
79+
if (!RATIONALE_MARKER_RE.test(line)) {
80+
continue
81+
}
82+
for (let j = 0, { length: pLen } = SPECIFICITY_PATTERNS; j < pLen; j += 1) {
83+
const pattern = SPECIFICITY_PATTERNS[j]!
84+
if (pattern.regex.test(line)) {
85+
const trimmed = line.trim()
86+
hits.push({
87+
label: pattern.label,
88+
line: i + 1,
89+
text: trimmed.length > 160 ? `${trimmed.slice(0, 157)}…` : trimmed,
90+
})
91+
break
92+
}
93+
}
94+
}
95+
return hits
96+
}
97+
98+
/**
99+
* True when `filePath` is a fleet-facing rule-prose surface whose citations
100+
* must be generic. Used by both the edit-time hook and the commit-time check.
101+
*/
102+
export function isRuleProseSurface(filePath: string): boolean {
103+
if (EXEMPT_PATH_RE.test(filePath)) {
104+
return false
105+
}
106+
return (
107+
/(?:^|\/)CLAUDE\.md$/.test(filePath) ||
108+
/(?:^|\/)docs\/claude\.md\/fleet\//.test(filePath) ||
109+
/(?:^|\/)\.claude\/skills\/.*\/SKILL\.md$/.test(filePath) ||
110+
/(?:^|\/)\.claude\/hooks\/fleet\/[^/]+\/README\.md$/.test(filePath)
111+
)
112+
}

.claude/hooks/fleet/_shared/foreign-paths.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import os from 'node:os'
2727
import path from 'node:path'
2828

2929
// Untracked-by-default path prefixes — kept in lock-step with
30-
// dirty-worktree-on-stop-reminder. Vendored / build-copied trees are
30+
// dirty-worktree-stop-reminder. Vendored / build-copied trees are
3131
// expected to be dirty and are never "another agent's work".
3232
const UNTRACKED_BY_DEFAULT_PREFIXES = [
3333
'additions/source-patched/',

.claude/hooks/fleet/alpha-sort-reminder/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ covers those surfaces per [`docs/claude.md/fleet/sorting.md`](../../../../docs/c
99

1010
| Surface | Detects | Key shape |
1111
| -------------------------------------------------- | ------------------------------------------------------------------------- | ----------- |
12-
| JSON / JSONC (`.json`, `.jsonc`, `.oxlintrc.json`) | runs of object keys at one indent, out of ASCII order | `"name": …` |
12+
| JSON / JSONC (`.json`, `.jsonc`, `.oxlintrc.json`) | runs of object keys at one indent, out of natural order | `"name": …` |
1313
| YAML (`.yml`, `.yaml`) | runs of mapping keys at one indent (`env:` / `with:` / matrix) | `name:` |
1414
| Markdown (`.md`, `.markdown`) | runs of `-`/`*` bullets out of order; bullets ending in ``/`...` | `- text` |
1515
| Bash (`.sh`, `.bash`) | runs of all-caps `NAME=…` assignments out of order (cache-key var blocks) | `NAME=…` |
1616

17-
Detection is conservative: **3+** adjacent siblings at the same indent, ASCII
18-
byte order only. False quiet beats false nag: a missed block is a review catch,
17+
Detection is conservative: **3+** adjacent siblings at the same indent, natural
18+
order (case-insensitive + numeric-aware, via lib's `naturalCompare`). False
19+
quiet beats false nag: a missed block is a review catch,
1920
while a wrong nag trains the agent to ignore the hook.
2021

2122
## Trigger

.claude/hooks/fleet/alpha-sort-reminder/index.mts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,23 @@
66
// `socket/sort-*` lint rules can't reach JSON / YAML / markdown / bash — this
77
// hook covers those surfaces per `docs/claude.md/fleet/sorting.md`:
88
//
9-
// - JSON / JSONC: runs of `"key":` lines at one indent, ASCII order.
9+
// - JSON / JSONC: runs of `"key":` lines at one indent, natural order.
1010
// - YAML: runs of `key:` mapping lines at one indent (env:/with:/matrix).
1111
// - Markdown: runs of `-`/`*` bullets; also flags trailing-ellipsis lines.
1212
// - Bash: runs of `NAME=...` assignments (cache-key var blocks).
1313
//
1414
// Detection is deliberately conservative: 3+ adjacent siblings at the same
15-
// indent, and only ASCII-comparison. False quiet beats false nag — a missed
15+
// indent, natural order (case-insensitive + numeric-aware, lib's
16+
// naturalCompare). False quiet beats false nag — a missed
1617
// block is a review catch, a wrong nag trains the agent to ignore the hook.
1718
// Always exits 0; the message is informational on stderr.
1819
//
1920

2021
import path from 'node:path'
2122
import process from 'node:process'
2223

24+
import { naturalCompare } from '@socketsecurity/lib-stable/sorts/natural'
25+
2326
import { readStdin } from '../_shared/transcript.mts'
2427

2528
type ToolInput = {
@@ -42,10 +45,12 @@ export interface SortFinding {
4245
// too little signal (and are often guard pairs); 3+ is unambiguously a list.
4346
const MIN_RUN = 3
4447

45-
// ASCII byte order, ascending. Returns true when already sorted.
48+
// Fleet natural order (case-insensitive + numeric-aware, via lib's
49+
// naturalCompare — the same comparator the socket/sort-* rules use). Returns
50+
// true when already sorted.
4651
function isAscadSorted(keys: readonly string[]): boolean {
4752
for (let i = 1; i < keys.length; i += 1) {
48-
if (keys[i - 1]! > keys[i]!) {
53+
if (naturalCompare(keys[i - 1]!, keys[i]!) > 0) {
4954
return false
5055
}
5156
}
@@ -203,7 +208,7 @@ function emit(filePath: string, findings: readonly SortFinding[]): void {
203208
lines.push(` • (${f.surface}) ${f.hint}`)
204209
}
205210
lines.push(
206-
' Sort sibling items alphanumerically (ASCII order) unless order is load-bearing.',
211+
' Sort sibling items alphanumerically (natural order) unless order is load-bearing.',
207212
' Fully re-sort the block when you touch it. See docs/claude.md/fleet/sorting.md.',
208213
)
209214
process.stderr.write(lines.join('\n') + '\n')

.claude/hooks/fleet/claude-segmentation-guard/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Every entry under those four directories must live under one of:
1212

1313
Top-level dangling entries like `.claude/skills/foo/SKILL.md` shadow the canonical `.claude/skills/fleet/foo/SKILL.md` copy and break skill resolution in unpredictable ways.
1414

15-
Past incident: 2026-06-01 fleet-wide audit found ~200 dangling entries across 10 reposevery fleet repo had at least 18 duplicate top-level skill directories shadowing their `fleet/<name>/` counterparts. The cleanup script (`node scripts/fleet/check/claude-dirs-are-segmented.mts --fix`) resolved them in bulk; this hook prevents the regression at edit time.
15+
Left unchecked, dangling top-level entries accumulate across the fleet — duplicate top-level skill directories shadow their `fleet/<name>/` counterparts and break resolution. The cleanup script (`node scripts/fleet/check/claude-dirs-are-segmented.mts --fix`) resolves them in bulk; this hook prevents the regression at edit time.
1616

1717
## What it blocks
1818

.claude/hooks/fleet/commit-cadence-reminder/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ At turn-end, in a linked worktree:
1313

1414
The worktree is scratch space — committing each step keeps work landable and rebases cheap, and the heavy gate runs once before merge rather than on every commit. Merging a worktree branch before the gate is green is how broken/unformatted/red changes reach the target branch. A reminder (not a block) because Stop hooks fire after the turn.
1515

16-
Stays quiet in the primary checkout — `dirty-worktree-on-stop-reminder` and `commit-pr-reminder` cover that case; this hook avoids double-nagging.
16+
Stays quiet in the primary checkout — `dirty-worktree-stop-reminder` and `commit-pr-reminder` cover that case; this hook avoids double-nagging.
1717

1818
## Bypass
1919

.claude/hooks/fleet/commit-cadence-reminder/index.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
// turn that created the state.
1919
//
2020
// Scope: only nudges inside a worktree (the workflow this rule targets). In the
21-
// primary checkout, dirty-worktree-on-stop-reminder + commit-pr-reminder cover
21+
// primary checkout, dirty-worktree-stop-reminder + commit-pr-reminder cover
2222
// the dirty/landing cases; this hook stays quiet there to avoid double-nagging.
2323
//
2424
//

.claude/hooks/fleet/compound-lessons-reminder/index.mts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
// two PRs, or two fleet repos — promote it to a rule instead of
1010
// fixing it again. Land it in CLAUDE.md, a `.claude/hooks/*`
1111
// block, or a skill prompt — pick the lowest-friction surface.
12-
// Always cite the original incident in a `**Why:**` line.
12+
// Cite the motivating case in a `**Why:**` line generically, as a
13+
// timeless example — not a dated incident log.
1314
//
1415
// Detection (any signal fires the warning, missing rule-promotion
1516
// evidence keeps it firing):
@@ -317,8 +318,9 @@ async function main(): Promise<void> {
317318
lines.push(
318319
' a `.claude/hooks/*` block, or a skill prompt — pick the lowest-',
319320
)
320-
lines.push(' friction surface. Always cite the original incident in a')
321-
lines.push(' `**Why:**` line.')
321+
lines.push(' friction surface. Cite the motivating case in a `**Why:**`')
322+
lines.push(' line GENERICALLY, as a timeless example — not a dated incident')
323+
lines.push(' log (no dates / version deltas / percentages / SHAs).')
322324
lines.push('')
323325
// If the rule is fleet-wide (not just this repo), it belongs in
324326
// socket-wheelhouse/template/. Help the user find the right path

.claude/hooks/fleet/concurrent-cargo-build-guard/README.md

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)