|
2083 | 2083 | tool_input: |
2084 | 2084 | command: 'git push origin main # hook-bypass: cd-worktree-rule' |
2085 | 2085 | expected_exit: 0 |
| 2086 | + |
| 2087 | +- id: warn-version-readme-changelog-sync |
| 2088 | + description: Warn when a version manifest (package.json, plugin.json, marketplace.json) is being bumped without a parallel update to README.md + CHANGELOG.md — the visible-version surface drifts silently otherwise (DOJ-4064 defense-in-depth, sister rule to dojo-os repo-local post-write-version-readme-sync.sh) |
| 2089 | + applies_to: [Edit, Write, MultiEdit] |
| 2090 | + match: |
| 2091 | + # Anchor on start-of-string OR a leading "/" so the rule fires for both |
| 2092 | + # relative and absolute file paths. Covers the four manifest surfaces |
| 2093 | + # that ship a "version" field in this repo and most plugin repos: |
| 2094 | + # - package.json (npm manifest) |
| 2095 | + # - plugin.json (Claude Code plugin manifest at repo root) |
| 2096 | + # - marketplace.json (marketplace manifest at repo root) |
| 2097 | + # - .claude-plugin/plugin.json (Claude Code plugin manifest, namespaced) |
| 2098 | + # - .claude-plugin/marketplace.json (marketplace manifest, namespaced) |
| 2099 | + - field: file_path |
| 2100 | + pattern: '(^|/)(package\.json|plugin\.json|marketplace\.json|\.claude-plugin/plugin\.json|\.claude-plugin/marketplace\.json)$' |
| 2101 | + # Positive: the new content / new_string includes a "version": "X.Y.Z" line. |
| 2102 | + # Quote handling: JSON manifest files always use double quotes, so the |
| 2103 | + # pattern only needs to match the canonical `"version": "<digit>...` form. |
| 2104 | + # `[0-9]` as the first version character is intentional — we only warn on |
| 2105 | + # real semver-shaped bumps, not on edits that happen to remove the version |
| 2106 | + # field or add unrelated keys. |
| 2107 | + - field: content |
| 2108 | + pattern: '"version"[[:space:]]*:[[:space:]]*"[0-9]' |
| 2109 | + action: warn |
| 2110 | + bypass_marker: version-readme-changelog-sync |
| 2111 | + memory_ref: feedback_version_readme_changelog_sync.md |
| 2112 | + references: |
| 2113 | + - "DOJ-4064 — three-layer drift thesis (Cure 4: PreToolUse/PostToolUse hooks)" |
| 2114 | + - "Sister hook in dojo-os: .claude/hooks/post-write-version-readme-sync.sh" |
| 2115 | + - "make-no-mistakes-toolkit PR #21 — visible README version + reconstructed CHANGELOG" |
| 2116 | + message: | |
| 2117 | + WARN: a version manifest (package.json / plugin.json / marketplace.json) |
| 2118 | + is being written with a "version": "X.Y.Z" field, but this hook cannot |
| 2119 | + see whether README.md and CHANGELOG.md were updated in the same change. |
| 2120 | +
|
| 2121 | + When you bump the version in any manifest, also update in the same PR: |
| 2122 | + 1. README.md — the "Version: X.Y.Z" line at the top |
| 2123 | + 2. CHANGELOG.md — add a new entry under ## [X.Y.Z] - YYYY-MM-DD |
| 2124 | +
|
| 2125 | + Why this rule exists (DOJ-4064 three-layer drift thesis): |
| 2126 | + - The repo shipped 1.1.0 → 1.14.0 with NO visible version anywhere a |
| 2127 | + human would look (README, CHANGELOG, or git tags). PR #21 closed that |
| 2128 | + gap by reconstructing the CHANGELOG and adding a header version line. |
| 2129 | + - Without this rule, the same drift will reappear on the next bump. |
| 2130 | + - Defense-in-depth: the dojo-os repo has a sister PostToolUse hook |
| 2131 | + (.claude/hooks/post-write-version-readme-sync.sh) that catches the |
| 2132 | + same drift locally even if this toolkit isn't installed. |
| 2133 | +
|
| 2134 | + Suggested workflow: |
| 2135 | + bump version in manifest(s) → update README.md "Version: ..." line → |
| 2136 | + append CHANGELOG.md entry → commit all four files together. |
| 2137 | +
|
| 2138 | + If you're intentionally splitting the bump from the README/CHANGELOG |
| 2139 | + update (e.g., bumping a vendored sub-manifest's version that isn't user- |
| 2140 | + visible), add "# hook-bypass: version-readme-changelog-sync" inline in |
| 2141 | + a comment near the version field or pass it on the Write call. |
| 2142 | + tests: |
| 2143 | + - name: warns-on-package-json-version-bump |
| 2144 | + input: |
| 2145 | + tool_input: |
| 2146 | + file_path: 'package.json' |
| 2147 | + content: | |
| 2148 | + { |
| 2149 | + "name": "@example/pkg", |
| 2150 | + "version": "1.15.0" |
| 2151 | + } |
| 2152 | + expected_exit: 0 |
| 2153 | + expected_stderr_contains: 'warn-version-readme-changelog-sync' |
| 2154 | + - name: warns-on-plugin-json-version-bump |
| 2155 | + input: |
| 2156 | + tool_input: |
| 2157 | + file_path: '.claude-plugin/plugin.json' |
| 2158 | + content: | |
| 2159 | + { |
| 2160 | + "name": "make-no-mistakes", |
| 2161 | + "version": "1.15.0" |
| 2162 | + } |
| 2163 | + expected_exit: 0 |
| 2164 | + expected_stderr_contains: 'warn-version-readme-changelog-sync' |
| 2165 | + - name: warns-on-marketplace-json-version-bump |
| 2166 | + input: |
| 2167 | + tool_input: |
| 2168 | + file_path: '.claude-plugin/marketplace.json' |
| 2169 | + content: | |
| 2170 | + { |
| 2171 | + "name": "marketplace", |
| 2172 | + "version": "1.15.0", |
| 2173 | + "plugins": [{"version": "1.15.0"}] |
| 2174 | + } |
| 2175 | + expected_exit: 0 |
| 2176 | + expected_stderr_contains: 'warn-version-readme-changelog-sync' |
| 2177 | + - name: warns-on-absolute-path-package-json |
| 2178 | + input: |
| 2179 | + tool_input: |
| 2180 | + file_path: '/home/user/repo/package.json' |
| 2181 | + content: '{"version": "2.0.0"}' |
| 2182 | + expected_exit: 0 |
| 2183 | + expected_stderr_contains: 'warn-version-readme-changelog-sync' |
| 2184 | + - name: warns-on-root-plugin-json |
| 2185 | + input: |
| 2186 | + tool_input: |
| 2187 | + file_path: 'plugin.json' |
| 2188 | + content: '{"version": "1.0.1"}' |
| 2189 | + expected_exit: 0 |
| 2190 | + expected_stderr_contains: 'warn-version-readme-changelog-sync' |
| 2191 | + - name: warns-on-edit-of-package-json |
| 2192 | + input: |
| 2193 | + tool_input: |
| 2194 | + file_path: 'package.json' |
| 2195 | + old_string: '"version": "1.14.0"' |
| 2196 | + new_string: '"version": "1.15.0"' |
| 2197 | + expected_exit: 0 |
| 2198 | + expected_stderr_contains: 'warn-version-readme-changelog-sync' |
| 2199 | + - name: allows-package-json-write-without-version-field |
| 2200 | + input: |
| 2201 | + tool_input: |
| 2202 | + file_path: 'package.json' |
| 2203 | + content: | |
| 2204 | + { |
| 2205 | + "name": "@example/pkg", |
| 2206 | + "description": "no version field touched" |
| 2207 | + } |
| 2208 | + expected_exit: 0 |
| 2209 | + - name: allows-unrelated-json-file |
| 2210 | + input: |
| 2211 | + tool_input: |
| 2212 | + file_path: 'tsconfig.json' |
| 2213 | + content: '{"version": "1.0.0"}' |
| 2214 | + expected_exit: 0 |
| 2215 | + - name: allows-readme-write |
| 2216 | + input: |
| 2217 | + tool_input: |
| 2218 | + file_path: 'README.md' |
| 2219 | + content: '**Version: 1.15.0**' |
| 2220 | + expected_exit: 0 |
| 2221 | + - name: allows-changelog-write |
| 2222 | + input: |
| 2223 | + tool_input: |
| 2224 | + file_path: 'CHANGELOG.md' |
| 2225 | + content: '## [1.15.0] - 2026-05-14' |
| 2226 | + expected_exit: 0 |
| 2227 | + - name: allows-bypass-marker |
| 2228 | + input: |
| 2229 | + tool_input: |
| 2230 | + file_path: 'package.json' |
| 2231 | + content: | |
| 2232 | + { |
| 2233 | + "version": "1.15.0" |
| 2234 | + // hook-bypass: version-readme-changelog-sync |
| 2235 | + } |
| 2236 | + expected_exit: 0 |
0 commit comments