Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/agentic-advisor/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentic-advisor",
"version": "1.4.2",
"version": "1.4.3",
"description": "Before writing code, grades how fragile the target is from LinearB health signals + local git history (rework, incidents, fix/revert density, code ownership) and holds the agent to a matching effort level (LOW/MEDIUM/HIGH) — lean on calm repos, defensive on fragile ones. Auto-triggers via a bundled hook and can report each effort decision to your LinearB org as usage telemetry.",
"author": {
"name": "LinearB",
Expand Down
6 changes: 6 additions & 0 deletions plugins/agentic-advisor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [1.4.3] - 2026-10-15

### Fixed

- No more false prompt nudges: "auto-fix" / "auto fix" / "autofix" no longer counts as a code task, and a repo that was already graded and worked on isn't nudged again (another repo in the same session still is).

## [1.4.2] - 2026-10-15

### Fixed
Expand Down
14 changes: 11 additions & 3 deletions plugins/agentic-advisor/hooks/agentic-advisor-trigger.sh
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ case "$event" in
if [ -n "$ticket" ]; then
# Tier 1: Jira ticket (primary).
msg="This looks like the start of a repo-scoped code task (Jira ticket ${ticket} referenced). ${guidance}"
elif printf '%s' "$prompt" | grep -qiE '\b(fix|fixes|fixing|fixed|implement|implementing|refactor|refactoring|hotfix|bug ?fix|debug|patch|endpoint|migration)\b'; then
elif printf '%s' "$prompt" | sed -E 's/[Aa][Uu][Tt][Oo][- ]?[Ff][Ii][Xx]//g' | grep -qiE '\b(fix|fixes|fixing|fixed|implement|implementing|refactor|refactoring|hotfix|bug ?fix|debug|patch|endpoint|migration)\b'; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧾 Readability - Complex Inline Pipeline: Extract the sed-stripped prompt into an intermediate variable before running the grep check, improving readability and making future keyword additions easier to review.

Suggested change
elif printf '%s' "$prompt" | sed -E 's/[Aa][Uu][Tt][Oo][- ]?[Ff][Ii][Xx]//g' | grep -qiE '\b(fix|fixes|fixing|fixed|implement|implementing|refactor|refactoring|hotfix|bug ?fix|debug|patch|endpoint|migration)\b'; then
elif stripped_prompt=$(printf '%s' "$prompt" | sed -E 's/[Aa][Uu][Tt][Oo][- ]?[Ff][Ii][Xx]//g') && printf '%s' "$stripped_prompt" | grep -qiE '\b(fix|fixes|fixing|fixed|implement|implementing|refactor|refactoring|hotfix|bug ?fix|debug|patch|endpoint|migration)\b'; then
Is this review accurate? Use 👍 or 👎 to rate it

If you want to tell us more, use /gs feedback e.g. /gs feedback this review doesn't make sense, I disagree, and it keeps repeating over and over

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping: style only, and the pipeline is a one-liner that matches the surrounding code.

# Tier 2: code-task keywords (fallback for ad-hoc work).
msg="This looks like a code-writing task. ${guidance}"
fi
Expand Down Expand Up @@ -196,8 +196,16 @@ case "$event" in
# (missing file path, non-git, missing transcript, or marker write failure).
git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0

# Skill already ran this session -> allow the edit.
skill_ran && exit 0
# Skill already ran this session -> allow the edit. If a verdict names this repo, mark it handled so later prompts here aren't nudged.
if skill_ran; then
set_marker "$dir"
# The verdict uses the repo's name: check the remote's name and the folder's (clones and worktrees can differ).
url="$(git -C "$dir" config --get remote.origin.url 2>/dev/null)"; url="${url%.git}"
for name in "${url##*[/:]}" "$(basename "$(git -C "$dir" rev-parse --show-toplevel 2>/dev/null)")"; do
[ -n "$name" ] && grep -qF "LinearB: $name " "$transcript" 2>/dev/null && { : > "$marker" 2>/dev/null || true; break; }
done
exit 0
fi
# Can't read the transcript -> can't confirm skill status -> fail open (allow).
[ -n "$transcript" ] && [ -f "$transcript" ] || exit 0

Expand Down
31 changes: 31 additions & 0 deletions tests/agentic-advisor/hooks_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ reminded() { [ "$rc" -eq 0 ] && jq -e '(.hookSpecificOutput.permissionDecision =
denied() { [ "$rc" -eq 0 ] && jq -e '.hookSpecificOutput.permissionDecision == "deny"' <<<"$out" >/dev/null; }
add_skill() { printf '%s\n' '{"timestamp":"2026-01-01T00:00:00Z","message":{"role":"assistant","model":"m","usage":{"output_tokens":10},"content":[{"type":"tool_use","name":"Skill","input":{"skill":"agentic-advisor:agentic-advisor"}}]}}' >> "$TRANSCRIPT"; }
add_verdict() { printf '%s\n' '{"timestamp":"2026-01-01T00:00:05Z","message":{"role":"assistant","model":"m","usage":{"output_tokens":20},"content":[{"type":"text","text":"> LinearB: widgets — MEDIUM effort (rework 6.5%) — focused diff."}]}}' >> "$TRANSCRIPT"; }
add_verdict_for() { printf '{"message":{"role":"assistant","content":[{"type":"text","text":"> LinearB: %s — MEDIUM effort (x) — y."}]}}\n' "$1" >> "$TRANSCRIPT"; }
# Reporter posts from a detached subshell; wait briefly for the stub to record calls.
ncalls() { find "$T/curl" -type f | wc -l | tr -d ' '; }
await_calls() { local _; for _ in 1 2 3 4 5 6 7 8 9 10; do [ "$(ncalls)" -ge "$1" ] && return; sleep 0.2; done; }
Expand All @@ -74,6 +75,36 @@ teardown
setup
run "$TRIGGER" UserPromptSubmit "$(prompt 'what time is it?')"
check "trigger: non-code prompt is silent" silent
run "$TRIGGER" UserPromptSubmit "$(prompt 'turn on Auto-fix for the PR')"
check "trigger: 'auto-fix' is not a code task" silent
run "$TRIGGER" UserPromptSubmit "$(prompt 'turn on auto fix')"
check "trigger: 'auto fix' is not a code task" silent
run "$TRIGGER" UserPromptSubmit "$(prompt 'turn on autofix and fix the login bug')"
check "trigger: a real fix next to autofix still nudges" nudged
teardown

setup
add_skill; add_verdict_for other-repo
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
run "$TRIGGER" UserPromptSubmit "$(prompt 'fix the login bug')"
check "trigger: an edit in a repo the verdict didn't name doesn't mark it handled" nudged
teardown

setup
add_skill; add_verdict_for widgets
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
run "$TRIGGER" UserPromptSubmit "$(prompt 'fix the login bug')"
check "trigger: a verdict naming the remote repo (folder named differently) marks it handled" silent
teardown

setup
add_skill; add_verdict_for repo
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
run "$TRIGGER" UserPromptSubmit "$(prompt 'fix the login bug')"
check "trigger: no nudge in a repo already graded and worked on" silent
REPO2="$T/repo2"; git init -q "$REPO2"
out="$(printf '{"hook_event_name":"UserPromptSubmit","session_id":"s1","cwd":"%s","transcript_path":"%s","prompt":"fix the login bug"}' "$REPO2" "$TRANSCRIPT" | "$TRIGGER")"; rc=$?
check "trigger: a second repo still nudges after the skill ran" nudged
teardown

for v in 0 false OFF no; do
Expand Down
Loading