Skip to content
Open
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.5",
"version": "1.4.6",
"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
8 changes: 8 additions & 0 deletions plugins/agentic-advisor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [1.4.6] - 2026-10-21

### Changed

- A task that changes no code (reading, answering a question, reviewing a doc or screenshot, writing a comment) is no longer graded LOW: the skill prints `> LinearB: <repo> — skipped (not a code task)`, makes no calls, reports nothing, and the agent works with its normal care. LOW stays for trivial code edits (comments, formatting, renames).
- The skill states that the effort level governs code changes only and never lowers the care of reading, analysis or writing.
- "Skipped" means the task modifies no file; any file edit with no behavior change (code comments, doc files, formatting) is still LOW. The edit hold now waits for a real grade, so code work that follows a skipped run is graded first.

## [1.4.5] - 2026-10-18

### Changed
Expand Down
2 changes: 1 addition & 1 deletion plugins/agentic-advisor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The agent prints a one-line verdict before writing code, e.g.:
- **Effort bands** match LinearB's own rework benchmark: ELITE <3% / STRONG 3–6% → **LOW**; FAIR 6–7% → **MEDIUM**; NEEDS FOCUS >7% → **HIGH**. Effort = the highest-firing signal (rework, unreviewed merges, or a recent serious incident).
- **Task complexity:** the request itself is graded too (scope, breadth, tricky logic, blast radius), so a demanding change in a calm repo still gets real care. If the prompt or branch names a ticket, its type, priority and estimate from LinearB count too (a Highest-priority bug raises the grade). The final effort is the highest of repo health, task complexity and (when it runs) phase 2.
- **Phase 2 (file-grained):** on non-trivial changes to a fragile/sensitive area, it also checks local git history on the exact files — how much existing code was rewritten over the last 90 days (the main signal) and who owns it — to target where to concentrate care. Commit messages saying "fix"/"revert" count only as a weak hint.
- **Stays lean:** trivial edits (comments, docs, formatting, pure renames) skip everything and cap at LOW; the repo-health part of the verdict is cached per repo for 24 hours; the task and file checks are never cached and are redone whenever the skill runs.
- **Stays lean:** a task that modifies no file (questions, reviews, drafting a reply) is not graded at all; edits with no behavior change (code comments, doc files, formatting, pure renames) skip everything and cap at LOW; the repo-health part of the verdict is cached per repo for 24 hours; the task and file checks are never cached and are redone whenever the skill runs.

## Triggers

Expand Down
16 changes: 10 additions & 6 deletions plugins/agentic-advisor/hooks/agentic-advisor-trigger.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ skill_ran() {

# Is the LATEST advisor Skill call still missing its verdict (assistant text, or the
# hold's `printf` Bash call)? Prints "pending" or "ok"; prints nothing on parse failure.
verdict_state() {
jq -rs --arg n "agentic-advisor" --arg re 'LinearB: .+ (LOW|MEDIUM|HIGH) effort' '
VERDICT_RE='LinearB: .+ ((LOW|MEDIUM|HIGH) effort|skipped \(not a code task\))'
GRADED_RE='LinearB: .+ (LOW|MEDIUM|HIGH) effort'
verdict_state() { # $1 = the verdict regex required (default: a grade or a skip)
jq -rs --arg n "agentic-advisor" --arg re "${1:-$VERDICT_RE}" '
[.[] | select(.message.role? == "assistant") | .message.content? // [] | .[]?] as $c
| [$c | to_entries[] | select(.value.type? == "tool_use" and .value.name? == "Skill"
and ((.value.input.skill? // "") | tostring | contains($n))) | .key] as $sk
Expand Down Expand Up @@ -94,7 +96,7 @@ set_marker() { # $1 = a directory inside the repo
}

# Imperative, silent guidance — no permission question, no announcement.
guidance="MANDATORY FIRST STEP for this code task: before you read files, search, explore, spawn any subagent, or edit anything, you MUST invoke the \`agentic-advisor\` skill (via the Skill tool) to grade this repo's effort level. Do not start the task, do not touch code, until you have run it and printed its verdict line verbatim in this shape: > LinearB: <repo> — <LOW|MEDIUM|HIGH> effort (<evidence>) — <plan> (a hook parses that line; a prose summary is not recorded). This is required, not optional — do not skip it even if the task seems obvious. Invoke it directly (no need to ask the user or announce it). It degrades gracefully: if LinearB is unavailable, default to MEDIUM effort and continue."
guidance="MANDATORY FIRST STEP for this code task: before you read files, search, explore, spawn any subagent, or edit anything, you MUST invoke the \`agentic-advisor\` skill (via the Skill tool) to grade this repo's effort level. Do not start the task, do not touch code, until you have run it and printed its verdict line verbatim in this shape: > LinearB: <repo> — <LOW|MEDIUM|HIGH> effort (<evidence>) — <plan> (a hook parses that line; a prose summary is not recorded). This is required, not optional — do not skip it even if the task seems obvious. Invoke it directly (no need to ask the user or announce it). It degrades gracefully: if LinearB is unavailable, default to MEDIUM effort and continue. If the task will not change any code, print > LinearB: <repo> — skipped (not a code task) instead of a grade."

# Mark handled (this session+repo) and emit additionalContext for the event.
emit() { # $1 = hookEventName, $2 = context text
Expand Down Expand Up @@ -158,7 +160,7 @@ case "$event" in
state="$(verdict_state)"
if [ -n "$state" ] && : > "$vdone" 2>/dev/null && [ "$state" = "pending" ]; then
dbg "PreToolUse: reminded the agent to print the verdict line"
note "LinearB: your effort grade isn't recorded yet. It's only recorded from the verdict line, not from thinking, so print it now: > LinearB: <repo> — <LOW|MEDIUM|HIGH> effort (<evidence>) — <plan>. If you aren't writing text, run: printf '%s\\n' '> LinearB: <repo> — <LEVEL> effort (<evidence>) — <plan>'"
note "LinearB: your effort grade isn't recorded yet. It's only recorded from the verdict line, not from thinking, so print it now: > LinearB: <repo> — <LOW|MEDIUM|HIGH> effort (<evidence>) — <plan>. If you aren't writing text, run: printf '%s\\n' '> LinearB: <repo> — <LEVEL> effort (<evidence>) — <plan>'. If no code will change, print > LinearB: <repo> — skipped (not a code task) instead."
fi
fi
fi
Expand Down Expand Up @@ -196,8 +198,10 @@ 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. If a verdict names this repo, mark it handled so later prompts here aren't nudged.
if skill_ran; then
# Skill already graded this session -> allow the edit. A "skipped (not a code task)" line alone doesn't count: editing now is code work.
# If a verdict names this repo, mark it handled so later prompts here aren't nudged.
# "pending" only when parsed and no grade yet; a parse failure prints nothing and fails open.
if skill_ran && [ "$(verdict_state "$GRADED_RE")" != "pending" ]; 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}"
Expand Down
6 changes: 5 additions & 1 deletion plugins/agentic-advisor/skills/agentic-advisor/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Right after the repo sweep, your first assistant text **must be this verdict lin
- **A hook parses this exact line** to record the decision. Prose such as "repo health looks solid… I'll treat it as HIGH effort" is not recognized: the grade is lost and nothing is reported.
- The level is one word in capitals (`LOW`, `MEDIUM` or `HIGH`) followed immediately by the word `effort`.
- Explanation may follow on later lines, but the verdict line 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> — <LEVEL> effort (<evidence>) — <plan>'`. If neither exists, a hook reminds you on your next file read or edit.

Example: `> LinearB: api-service — HIGH effort (rework 3.4%, calm repo — but auth refactor with wide blast radius) — small behavior-preserving steps, tests green, confirm scope first.`
Expand All @@ -44,7 +45,8 @@ set -o pipefail # with curl -f, a failed request fails the whole pipeline inst

Two checks that prevent wasted work — do them before any LinearB call:

- **Trivial edit → skip everything.** If the task changes **no code behavior** (comments, docs, formatting/whitespace, a pure rename), skip signal gathering entirely: effort is **LOW**, add no defensive guards (there's no logic to protect), and don't call LinearB at all — this holds **even on a fragile repo**. Anything that changes behavior is **not** trivial and warrants the sweep below — note that a **constant-value change counts as behavioral** (e.g. a timeout, retry count, or feature-flag default), as does any conditional or logic edit.
- **Not a code task → no grade at all.** If the task will not modify any file (reading or explaining code, answering a question, reviewing a doc, ticket or screenshot, drafting a reply or message in chat), don't grade it: print `> LinearB: <repo> — skipped (not a code task)`, call nothing, and work with your normal care. A grade measures the risk of a code change; a LOW here would wrongly tell you to go light on accuracy. If the task later turns into a file edit, run the sweep then, before the first edit (a hook holds that edit until a real grade exists).
- **Trivial code edit → LOW, skip everything.** If the task edits any file but changes **no code behavior** (code comments, doc files, formatting/whitespace, a pure rename), skip signal gathering entirely: effort is **LOW**, add no defensive guards (there's no logic to protect), and don't call LinearB at all — this holds **even on a fragile repo**. Anything that changes behavior is **not** trivial and warrants the sweep below — note that a **constant-value change counts as behavioral** (e.g. a timeout, retry count, or feature-flag default), as does any conditional or logic edit.
- **Already graded recently → reuse (persistent 24h cache).** The repo-level verdict is daily-stable, so compute it at most **once per repo per ~24h**, not once per task/session — this is the main token/time saver. Cache file, keyed by repo root:

```text
Expand Down Expand Up @@ -260,6 +262,8 @@ Phase 2 was only ever deferred because it needs the target file paths. So:

## Coding Posture

The effort level governs **code changes only**: it never lowers the care, accuracy or thoroughness of reading, analysis, answers or writing.

Use the effort level to guide the implementation silently. The aim is to **spend effort proportional to repo health** — go light (saving tokens and time) on calm repos, dig in on fragile ones. Don't waste effort.

LOW effort — repo confirmed healthy, go light and token-efficient (an active repo can still be LOW — busy ≠ risky):
Expand Down
25 changes: 25 additions & 0 deletions tests/agentic-advisor/hooks_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ 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"; }
# The loaded SKILL.md lands in the transcript as a user message, example verdict line included.
add_skill_body() { printf '%s\n' '{"type":"user","isMeta":true,"message":{"role":"user","content":[{"type":"text","text":"Example: > LinearB: api-service — HIGH effort (rework 3.4%) — small steps."}]}}' >> "$TRANSCRIPT"; }
add_skipped() { printf '%s\n' '{"message":{"role":"assistant","content":[{"type":"text","text":"> LinearB: widgets — skipped (not a code task)"}]}}' >> "$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 ' '; }
Expand Down Expand Up @@ -171,6 +174,21 @@ run "$TRIGGER" PreToolUse "$(tool Read app.js)"
check "trigger: skill ran without verdict -> reminder, not a block" reminded
teardown

setup
add_skill; add_skipped
run "$TRIGGER" PreToolUse "$(tool Read app.js)"
check "trigger: a skipped (not a code task) line counts as answered -> no reminder" silent
teardown

setup
add_skill; add_skill_body; add_skipped
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
check "trigger: an edit after a skipped (not a code task) run is held until a real grade" denied
add_verdict
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
check "trigger: ...and goes through once the grade is printed" silent
teardown

setup
add_skill; add_verdict
run "$TRIGGER" PreToolUse "$(tool Edit app.js)"
Expand All @@ -195,6 +213,13 @@ await_calls 2 # decision + tokens, posted concurrently
check "report: SessionEnd backfills a missed DECISION" backfilled
teardown

setup
add_skill; add_skipped
run "$REPORT" SessionEnd
sleep 0.5 # a wrong post would arrive from a detached subshell
check "report: a skipped (not a code task) session reports nothing" no_calls
teardown

# Both exit before any background post, so no wait is needed.
setup
add_skill; add_verdict
Expand Down
Loading