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.5.1",
"version": "1.5.2",
"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 reports whether it Reduced, Kept or Raised effort against the model's current level — 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.5.2] - 2026-11-10

### Fixed

- The verdict now shows up at every effort level. The skill records it with one `printf` call, and a new PostToolUse hook shows that line to the user. At low effort the model used to keep the verdict in its thinking, so nothing was shown or reported until the hold blocked an edit.

## [1.5.1] - 2026-11-09

### Fixed
Expand Down
15 changes: 14 additions & 1 deletion plugins/agentic-advisor/hooks/agentic-advisor-trigger.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
# BACKSTOP - a code edit or subagent spawn is starting and this repo wasn't graded yet this
# session. Catches tasks that slipped past the prompt gate.
#
# PostToolUse (matcher Bash)
# SHOW - a `printf` verdict line ran: echo it as a systemMessage so the user sees it
# even when the Bash call is collapsed.
#
# This NEVER does LinearB work itself and NEVER blocks. It only injects an
# additionalContext nudge and always exits 0 (exit 2 on UserPromptSubmit would
# erase the user's prompt).
Expand Down Expand Up @@ -133,6 +137,15 @@ if [ -n "$session_id" ] && [ "${BASELINE_PCT:-0}" -gt 0 ] 2>/dev/null; then
fi

case "$event" in
PostToolUse)
cmd="$(jq -r '.tool_input.command // empty' <<<"$input" 2>/dev/null || true)"
case "$cmd" in *printf*"LinearB: "*) ;; *) exit 0 ;; esac
line="$(printf '%s\n' "$cmd" | grep -oE "> ${GRADED_RE}[^']*" | head -n 1)"
[ -n "$line" ] || exit 0
jq -cn --arg m "$line" '{systemMessage: $m}'
dbg "PostToolUse: showed the verdict line to the user"
;;

UserPromptSubmit)
prompt="$(jq -r '.prompt // .user_input // empty' <<<"$input" 2>/dev/null || true)"
[ -z "$prompt" ] && exit 0
Expand Down Expand Up @@ -234,7 +247,7 @@ case "$event" in
printf '%s' "$((n + 1))" > "$held" 2>/dev/null || exit 0
dbg "PreToolUse: held $tool until a verdict line exists (hold $((n + 1)))"
if skill_ran; then
reason="Your effort grade isn't recorded yet: print the verdict line itself, not a prose summary — > LinearB: <repo> — <Raised|Reduced> effort (<current> → <new level>; <evidence>) — <plan>, or > LinearB: <repo> — Kept effort (<current>; <evidence>) — <plan> (or run: printf '%s\\n' '> LinearB: …'). A skipped (not a code task) line doesn't count once you change code. Then make the same call again."
reason="Your effort grade isn't recorded yet (thinking and prose summaries don't count). Run this Bash call now with your grade filled in, then make the same call again: printf '%s\\n' '> LinearB: <repo> — <Raised|Reduced> effort (<current> → <new level>; <evidence>) — <plan>' (or '> LinearB: <repo> — Kept effort (<current>; <evidence>) — <plan>')."
else
reason="One quick first step before ${what}: run the \`agentic-advisor\` skill to grade this repo's effort.$skill_hint Print its verdict line (> LinearB: <repo> — <Raised|Reduced> effort (<current> → <new level>; <evidence>) — <plan>, or > LinearB: <repo> — Kept effort (<current>; <evidence>) — <plan>), then make the same call again; this hold repeats until it's printed. If LinearB isn't available it just works at medium effort."
fi
Expand Down
11 changes: 11 additions & 0 deletions plugins/agentic-advisor/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/agentic-advisor-trigger.sh"
}
]
}
],
"Stop": [
{
"hooks": [
Expand Down
15 changes: 11 additions & 4 deletions plugins/agentic-advisor/skills/agentic-advisor/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,23 @@ Your first Bash call starts with `printf 'enabled=%s effort=%s\n' "${LINEARB_AGE

## Output contract (read this first)

Right after the repo sweep, your first assistant text **must be this verdict line, verbatim in shape**, as a markdown blockquote:
Right after the repo sweep, your next step **must be one Bash call that prints this verdict line, verbatim in shape**:

```bash
printf '%s\n' '> LinearB: <repo> — <Raised|Reduced> effort (<current> → <new level>; <evidence>) — <plan>'
```

The two shapes:

> LinearB: <repo> — <Raised|Reduced> effort (<current> → <new level>; <evidence>) — <what you will do differently>
> LinearB: <repo> — Kept effort (<current>; <evidence>) — <what you will do differently>

- **A hook parses this exact line** to record the decision. Prose such as "repo health looks solid… I'll work harder" is not recognized: the decision is lost and nothing is reported.
- `Raised effort` or `Reduced effort`, then in the parentheses the model's current level, `→`, and the level to work at; `Kept effort` shows only the current level. Levels are lowercase (`low`, `medium`, `high`, `xhigh`, `max`); see Effort Change.
- Explanation may follow on later lines, but the verdict line comes first and is never reworded.
- Always use the `printf` Bash call, at every effort level: a hook records it and shows it to the user. Text alone can end up only in thinking, where nothing sees it.
- Explanation may follow, but the verdict comes first and is never reworded.
- **No code will change?** Print `> LinearB: <repo> — skipped (not a code task)` instead of a grade and stop (see the cheap exits). Nothing is reported.
- If you aren't writing visible text at this point, record it with one Bash call instead: `printf '%s\n' '> LinearB: <repo> — <Raised|Reduced> effort (<current> → <new level>; <evidence>) — <plan>'` (or `Kept effort (<current>; <evidence>)`). If neither exists, a hook reminds you on your next file read or edit.
- If it's missing, a hook blocks your first edit until the `printf` call runs.

Example: `> LinearB: api-service — Raised effort (medium → high; rework 3.4%, calm repo — but auth refactor with wide blast radius) — small behavior-preserving steps, tests green, confirm scope first.`

Expand Down Expand Up @@ -303,7 +310,7 @@ HIGH effort — repo looks fragile, be careful:

## Human-Facing Note

The moment the **repo sweep** (including the ticket lookup, if any) returns — and **before any other tool call, and before spawning any Explore/subagent, reading, searching, `ls`/`find`, or editing code** — your next output MUST be this exact verdict line, **formatted as a markdown blockquote** (start it with `> ` so the terminal renders it with the left bar / emphasis and it stands out). Emit it as assistant text — do NOT wrap it in an `echo`/shell command or code fence (that hides it in a collapsed tool block). The one exception is the exact `printf '%s\n' '> LinearB: …'` Bash call from the Output contract, when you aren't writing visible text or the hook asks for it. Do not paraphrase it into prose like "LOW effort signals confirmed". This is the **repo-level verdict**; it is fully knowable from the sweep alone, so nothing else may happen first. Format:
The moment the **repo sweep** (including the ticket lookup, if any) returns — and **before any other tool call, and before spawning any Explore/subagent, reading, searching, `ls`/`find`, or editing code** — your next step MUST be the `printf '%s\n' '> LinearB: …'` Bash call from the Output contract, printing this exact verdict line (starting with `> `). Use `printf`, not `echo`, and nothing else in that call; a hook shows the line to the user. Do not paraphrase it into prose like "LOW effort signals confirmed". This is the **repo-level verdict**; it is fully knowable from the sweep alone, so nothing else may happen first. Format:

```text
> LinearB: <repo> — <Raised|Reduced> effort (<current> → <new level>; <evidence>) — <what you will do differently>
Expand Down
41 changes: 40 additions & 1 deletion tests/agentic-advisor/hooks_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ teardown

# --- trigger: PreToolUse grade-before-edit hold ---
names_skill_file() { jq -e '.hookSpecificOutput.permissionDecisionReason | contains("/skills/agentic-advisor/SKILL.md")' <<<"$out" >/dev/null; }
wants_line() { jq -e '.hookSpecificOutput.permissionDecisionReason | contains("not a prose summary")' <<<"$out" >/dev/null; }
wants_line() { jq -e '.hookSpecificOutput.permissionDecisionReason | (contains("prose summaries") and contains("printf"))' <<<"$out" >/dev/null; }
setup
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
check "trigger: first source edit is held" denied
Expand Down Expand Up @@ -473,6 +473,45 @@ for bad in "> LinearB: widgets — Raised effort (medium" "> LinearB: widgets
teardown
done

# PostToolUse: a printf verdict line is shown to the user as a systemMessage.
bash_cmd() { jq -cn --arg c "$1" '{tool_name:"Bash",tool_input:{command:$c}}' | sed 's/^{/,/; s/}$//'; }
shown() { [ "$rc" -eq 0 ] && [ "$(jq -r '.systemMessage // empty' <<<"$out")" = "$1" ]; }
asks_printf() { jq -e '.hookSpecificOutput.permissionDecisionReason | contains("printf")' <<<"$out" >/dev/null; }
raised_to_high() { call_json | jq -se 'any(.[]; .tags.effort_change == "raised" and .tags.target_effort == "high")' >/dev/null; }
add_printf() { jq -cn --arg c "printf '%s\\n' '$1'" '{message:{role:"assistant",content:[{type:"tool_use",name:"Bash",input:{command:$c}}]}}' >> "$TRANSCRIPT"; }
V1='> LinearB: widgets — Raised effort (low → high; rework 11.0% NEEDS FOCUS) — smallest change, tests per field.'
V2='> LinearB: widgets — Kept effort (high; rework 2.3%) — focused fix.'
V3='> LinearB: widgets — Reduced effort (max -> medium; rework 2.3%) — focused fix.'
for v in "$V1" "$V2" "$V3"; do
setup
run "$TRIGGER" PostToolUse "$(bash_cmd "printf '%s\\n' '$v'")"
check "show: printf verdict is shown: ${v:12:40}" shown "$v"
teardown
done
setup
run "$TRIGGER" PostToolUse "$(bash_cmd "ls -la && git status")"
check "show: an unrelated Bash call shows nothing" silent
run "$TRIGGER" PostToolUse "$(bash_cmd "printf '%s\\n' '> LinearB: <repo> — <Raised|Reduced> effort (<current> → <new level>; <evidence>) — <plan>'")"
check "show: the template line itself shows nothing" silent
run "$TRIGGER" PostToolUse "$(bash_cmd "printf '%s\\n' '> LinearB: widgets — Raised effort (medium'")"
check "show: an incomplete verdict shows nothing" silent
LINEARB_AGENTIC_ADVISOR_ENABLED=off run "$TRIGGER" PostToolUse "$(bash_cmd "printf '%s\\n' '$V1'")"
check "show: off switch shows nothing" silent
teardown

# Hold asks for the printf call; a printf verdict then unblocks the edit and is reported once.
setup
add_skill
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
check "hold: reason asks for the printf call" asks_printf
add_printf "$V1"
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
check "hold: a printf verdict unblocks the edit" silent
run "$REPORT" Stop; await_calls 1
check "report: the printf verdict is reported once" one_call
check "report: it is a raised decision to high" raised_to_high
teardown


printf '\n%d passed, %d failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]
Loading