Skip to content

Commit 7e92b02

Browse files
lapc506claude
andauthored
feat(hooks): warn on Greptile review extraction by created_at — use updated_at + Last reviewed commit footer (#26)
Greptile App on GitHub edits the same review comment in-place on each re-review (does NOT post a new comment per pass). So: - comment.created_at stays frozen at original posting - comment.updated_at is what moves on re-review - the <sub>Last reviewed commit: ...(commit/HASH)</sub> footer is the authoritative source for "which commit this review covers" Recurring buggy patterns Claude writes (verified twice — most recently CIV-728 PR #114 forensic 2026-05-25 where the agent picked a stale 3/5 review over the actual 5/5 HEAD review): 1. gh api .../comments --jq '... | sort_by(.created_at) ...' 2. greptile ... | head -1 / greptile ... | tail -1 3. (.body | capture("/commit/(?<sha>[0-9a-f]+)")) -- grabs first /commit/ URL anywhere in body, not the "Last reviewed commit:" footer hash New rule warn-greptile-review-extraction-by-created-at fires on all three patterns when the same command also references "greptile" and pulls from gh api .../comments, and does NOT fire when the corrective signals (Last reviewed commit / updated_at) are present. Action is `warn` (not `block`) — there are legitimate cases for creation-order audits. Bypass marker: greptile-extraction-acknowledged. The warning message paste-includes the corrective pattern so future Claude sessions get the right code via the hook output, not via memory. Bumps version 1.16.0 -> 1.17.0 across package.json, .claude-plugin/ plugin.json, .claude-plugin/marketplace.json, README.md header. CHANGELOG.md gains a 1.17.0 entry. 9 new tests added; full hook test suite passes 219/219. Memories: - feedback_greptile_match_head_not_chronology.md - feedback_tail_with_desc_ordering.md Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 378471f commit 7e92b02

7 files changed

Lines changed: 339 additions & 5 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
33
"name": "make-no-mistakes",
4-
"version": "1.16.0",
4+
"version": "1.17.0",
55
"description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, and stash secrets via OS-native prompts. One plugin to make no mistakes.",
66
"owner": {
77
"name": "Luis Andres Pena Castillo",
@@ -11,7 +11,7 @@
1111
{
1212
"name": "make-no-mistakes",
1313
"description": "Dev lifecycle orchestrator: disciplined Linear issue execution with worktree isolation, PR review with Greptile gating, team release sync, E2E test generation and execution, test suite previewer, security pentesting, MoSCoW + RICE prioritization, cross-platform secret stash via OS-native GUI prompts (zenity / kdialog / osascript / Get-Credential), and session management. 18 commands, 6 auto-activating skills, 2 specialized agents.",
14-
"version": "1.16.0",
14+
"version": "1.17.0",
1515
"author": {
1616
"name": "Luis Andres Pena Castillo",
1717
"email": "lapc506@users.noreply.github.com"

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "make-no-mistakes",
3-
"version": "1.16.0",
3+
"version": "1.17.0",
44
"description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, stash secrets, and enforce manifest-driven tool-call hooks. One plugin to make no mistakes.",
55
"author": {
66
"name": "Luis Andres Pena Castillo",

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
1919
## [Unreleased]
2020

21+
## [1.17.0] - 2026-05-25
22+
23+
### Added
24+
- **New PreToolUse rule `warn-greptile-review-extraction-by-created-at`
25+
(Bash).** Warns when a command extracts Greptile review state from
26+
`gh api .../comments` using chronology-based patterns that silently
27+
return stale data on re-reviews. The motivating bug (verified twice,
28+
most recently CIV-728 PR #114 forensic 2026-05-25): Greptile App
29+
EDITS the same review comment in-place on each re-review, so
30+
`comment.created_at` is frozen at original posting and only
31+
`comment.updated_at` moves. Three buggy patterns now trigger the
32+
warning:
33+
- `sort_by(.created_at)` / `select` on `.created_at` inside a jq
34+
expression over Greptile comments.
35+
- `greptile` + `head -N` / `tail -N` on the same command (implicit
36+
chronology assumption — "first match wins").
37+
- `capture("/commit/(?<sha>[0-9a-f]+)")` over a Greptile body — grabs
38+
the FIRST `/commit/` URL anywhere in the body (often a permalink
39+
quoted inside a finding), not the authoritative
40+
`<sub>Last reviewed commit: ...(commit/HASH)</sub>` footer hash.
41+
42+
The rule's warning message paste-includes the corrective pattern:
43+
pull HEAD via `gh pr view --json headRefOid`, filter Greptile
44+
comments to those whose `Last reviewed commit:` footer matches HEAD,
45+
then `sort_by(.updated_at) | last`. Action is `warn` (not `block`)
46+
— there are legitimate reasons to look at creation order (e.g.
47+
auditing posting cadence). Bypass marker:
48+
`greptile-extraction-acknowledged`.
49+
50+
Memories: `feedback_greptile_match_head_not_chronology.md`,
51+
`feedback_tail_with_desc_ordering.md`.
52+
2153
## [1.16.0] - 2026-05-20
2254

2355
### Added

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# make-no-mistakes
22

3-
**Version: 1.16.0** · [CHANGELOG](./CHANGELOG.md) · [Marketplace](https://github.com/DojoCodingLabs/make-no-mistakes-toolkit)
3+
**Version: 1.17.0** · [CHANGELOG](./CHANGELOG.md) · [Marketplace](https://github.com/DojoCodingLabs/make-no-mistakes-toolkit)
44

55
The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, and manage sessions. One plugin to make no mistakes.
66

hooks/rules/rules.json

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2785,5 +2785,126 @@
27852785
"expected_exit": 0
27862786
}
27872787
]
2788+
},
2789+
{
2790+
"id": "warn-greptile-review-extraction-by-created-at",
2791+
"description": "Warn when a Bash command extracts Greptile review state but uses created_at / chronology / first-commit-URL capture — Greptile edits the same comment in-place, so updated_at + the \"Last reviewed commit:\" footer hash are the authoritative signals (memory feedback_greptile_match_head_not_chronology.md)",
2792+
"applies_to": [
2793+
"Bash"
2794+
],
2795+
"match": [
2796+
{
2797+
"field": "command",
2798+
"pattern": "gh[[:space:]]+api\\b.*\\bcomments\\b"
2799+
},
2800+
{
2801+
"field": "command",
2802+
"pattern": "greptile",
2803+
"flags": "i"
2804+
},
2805+
{
2806+
"field": "command",
2807+
"pattern": "(\\bcreated_at\\b|\\b(head|tail)[[:space:]]+-n?[[:space:]]*[0-9]+|capture\\([^)]*/commit/)"
2808+
},
2809+
{
2810+
"field": "command",
2811+
"not_pattern": "(Last reviewed commit|\\bupdated_at\\b)"
2812+
}
2813+
],
2814+
"action": "warn",
2815+
"bypass_marker": "greptile-extraction-acknowledged",
2816+
"memory_ref": "feedback_greptile_match_head_not_chronology.md",
2817+
"references": [
2818+
"feedback_tail_with_desc_ordering.md — sister rule on DESC|tail bug",
2819+
"CIV-728 PR #114 forensic 2026-05-25 — stale 3/5 review picked over 5/5 HEAD review"
2820+
],
2821+
"message": "WARN: Bash command extracts Greptile review state with a chronology-based\npattern (created_at / head -N / tail -N / capture(.../commit/...)).\n\nGreptile App on GitHub EDITS the same review comment in-place on each\nre-review. As a result:\n - comment.created_at is FROZEN at original posting; it DOES NOT\n change when Greptile re-analyzes a new commit.\n - comment.updated_at IS what moves with each re-review.\n - The \"<sub>Last reviewed commit: ...(commit/HASH)</sub>\" footer in\n the body is the authoritative source for \"which commit this review\n is about\" — NOT the first /commit/ URL captured anywhere in the\n body (which is often a permalink quoted inside a finding).\n\nUse this pattern instead (copy-paste-able):\n\n HEAD=$(gh pr view <N> --json headRefOid --jq '.headRefOid')\n export SHORT=${HEAD:0:10}\n gh api \"repos/<owner>/<repo>/issues/<N>/comments\" --jq '\n [.[] | select(.user.login | test(\"greptile\";\"i\")) | {\n updated: .updated_at,\n commit: ((.body | split(\"Last reviewed commit:\")[1] // \"\" |\n split(\"commit/\")[1] // \"\" | split(\")\")[0])[0:10]),\n score: ((.body | capture(\"<h3>Confidence Score: (?<s>[0-9]/5)</h3>\").s) // \"?\")\n }] | map(select(.commit == env.SHORT)) | sort_by(.updated) | last'\n\nThis:\n 1. Pulls the PR's actual HEAD SHA up front.\n 2. Filters Greptile comments to ONLY those whose \"Last reviewed\n commit:\" footer matches HEAD (so a stale older review can't win).\n 3. Among the matching reviews, picks the latest by updated_at.\n\nPer feedback_greptile_match_head_not_chronology.md: out-of-order parallel\nre-runs make creation order misleading. Per feedback_tail_with_desc_\nordering.md: same failure mode (trust an implicit order without\nvalidating it).\n\nBypass marker (greptile-extraction-acknowledged) ONLY for legitimate\ncases (e.g., you explicitly WANT the creation order to audit posting\ncadence, not the latest state).\n",
2822+
"tests": [
2823+
{
2824+
"name": "warns-on-sort-by-created-at-greptile-comments",
2825+
"input": {
2826+
"tool_input": {
2827+
"command": "gh api repos/o/r/issues/1/comments --jq '[.[] | select(.user.login | test(\"greptile\";\"i\"))] | sort_by(.created_at) | last'"
2828+
}
2829+
},
2830+
"expected_exit": 0,
2831+
"expected_stderr_contains": "warn-greptile-review-extraction-by-created-at"
2832+
},
2833+
{
2834+
"name": "warns-on-greptile-grep-then-tail",
2835+
"input": {
2836+
"tool_input": {
2837+
"command": "gh api repos/o/r/issues/1/comments --jq '.[].body' | grep -i greptile | tail -1"
2838+
}
2839+
},
2840+
"expected_exit": 0,
2841+
"expected_stderr_contains": "warn-greptile-review-extraction-by-created-at"
2842+
},
2843+
{
2844+
"name": "warns-on-greptile-grep-then-head",
2845+
"input": {
2846+
"tool_input": {
2847+
"command": "gh api repos/o/r/issues/1/comments --jq '.[] | select(.body | test(\"greptile\";\"i\"))' | grep 'Confidence Score' | head -1"
2848+
}
2849+
},
2850+
"expected_exit": 0,
2851+
"expected_stderr_contains": "warn-greptile-review-extraction-by-created-at"
2852+
},
2853+
{
2854+
"name": "warns-on-capture-commit-sha-from-greptile-body",
2855+
"input": {
2856+
"tool_input": {
2857+
"command": "gh api repos/o/r/issues/1/comments --jq '[.[] | select(.user.login | test(\"greptile\";\"i\")) | {commit: (.body | capture(\"/commit/(?<sha>[0-9a-f]+)\").sha)}]'"
2858+
}
2859+
},
2860+
"expected_exit": 0,
2861+
"expected_stderr_contains": "warn-greptile-review-extraction-by-created-at"
2862+
},
2863+
{
2864+
"name": "allows-correct-pattern-updated-at-and-last-reviewed-commit",
2865+
"input": {
2866+
"tool_input": {
2867+
"command": "gh api \"repos/o/r/issues/1/comments\" --jq '\n [.[] | select(.user.login | test(\"greptile\";\"i\")) | {\n updated: .updated_at,\n commit: ((.body | split(\"Last reviewed commit:\")[1] // \"\" | split(\"commit/\")[1] // \"\" | split(\")\")[0])[0:10]),\n score: ((.body | capture(\"<h3>Confidence Score: (?<s>[0-9]/5)</h3>\").s) // \"?\")\n }] | sort_by(.updated) | last'\n"
2868+
}
2869+
},
2870+
"expected_exit": 0
2871+
},
2872+
{
2873+
"name": "allows-non-greptile-comments-call",
2874+
"input": {
2875+
"tool_input": {
2876+
"command": "gh api repos/o/r/issues/1/comments --jq '[.[] | .body] | sort_by(.created_at)'"
2877+
}
2878+
},
2879+
"expected_exit": 0
2880+
},
2881+
{
2882+
"name": "allows-greptile-with-updated-at-and-sort",
2883+
"input": {
2884+
"tool_input": {
2885+
"command": "gh api repos/o/r/issues/1/comments --jq '[.[] | select(.user.login | test(\"greptile\";\"i\"))] | sort_by(.updated_at) | last'"
2886+
}
2887+
},
2888+
"expected_exit": 0
2889+
},
2890+
{
2891+
"name": "allows-gh-search-greptile-not-api-comments",
2892+
"input": {
2893+
"tool_input": {
2894+
"command": "gh search prs --label greptile-approved"
2895+
}
2896+
},
2897+
"expected_exit": 0
2898+
},
2899+
{
2900+
"name": "allows-bypass-marker",
2901+
"input": {
2902+
"tool_input": {
2903+
"command": "gh api repos/o/r/issues/1/comments --jq '[.[] | select(.user.login | test(\"greptile\";\"i\"))] | sort_by(.created_at) | last' # hook-bypass: greptile-extraction-acknowledged"
2904+
}
2905+
},
2906+
"expected_exit": 0
2907+
}
2908+
]
27882909
}
27892910
]

0 commit comments

Comments
 (0)