plugins/lisa-agy/skills/lisa-attribute-failure/SKILL.md
Event-triggered root-cause attribution for an arbitrary failure: decide, with cited evidence, whether the defect is Lisa's fault or the project's. Accepts a failure event (defect description, implicated files, rule/skill/hook in play) and returns a verdict of lisa | project | ambiguous plus the evidence relied on. Read-only — it never files, writes, or remediates; callers (lisa-doctor findings, the learning judgment gate, rework triage) consume the verdict. Extracted from lisa-doctor's upstream Lisa change-history diagnosis (#1494) so the same attribution procedure can run on ANY failure event, not only doctor config-audit findings.
npx skillsauth add codyswanngt/lisa lisa-attribute-failureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Attribute ONE failure event to Lisa or the project, with cited evidence. Failure event: $ARGUMENTS
Before a Lisa-caused failure can be routed upstream, something has to say — with evidence — "this was Lisa's fault, not the project's." A failure has two possible causes: the project drifted, or Lisa itself changed or shipped the defect. This skill must distinguish them instead of blaming the project by default, and equally must never blame Lisa without conclusive evidence. Wrong attribution in either direction is the failure mode; inconclusive evidence is always ambiguous, and ambiguous stays local and low-confidence — never upstream.
Accept the event as JSON or key=value fields. This procedure is event-triggered, not version-window-keyed: any failure can be attributed, not only a doctor config-audit finding.
defect — what went wrong, in plain language (required)implicated_files — the file paths where the defect lives or manifests (required when known)surface_in_play — the Lisa rule / skill / hook / template / workflow involved, if anyinstalled_version / latest_version — the Lisa version window, when the caller knows it; otherwise resolve it as signal 3 describesfailure_class — a short slug naming the class of failure (used by downstream filing for the root-cause key)Return exactly one verdict — lisa | project | ambiguous — plus the evidence it relied on:
## Attribution: [lisa | project | ambiguous]
**Signal:** [managed-surface | shipped-artifact-behavior | upstream-history | none-conclusive]
**Lisa surface at fault:** [path or rule/skill/hook name | n/a]
**Cited evidence:**
- [each item names the signal it came from and the concrete citation: file path + ownership proof, shipped artifact text, or commit sha/subject/version]
**Confidence note:** [why the evidence is conclusive, or exactly what was inconclusive]
Verdict rules:
lisa — at least one signal conclusively pins the defect on a Lisa-shipped surface or upstream change, and the evidence names that surface. Never emit lisa from degraded, truncated, or unverified history.project — the implicated files are project-owned, no shipped Lisa artifact drove the behavior, and the upstream-history window shows no relevant Lisa change. Cite the absence explicitly (which paths were checked, which window showed no relevant change).ambiguous — anything else: unresolvable version window, unreachable history, partial ownership, conflicting signals. Ambiguous is treated as local and low-confidence; nothing is escalated upstream from it.Resolve ownership of each implicated file the way doctor's config audit does:
lisa apply from a stack template (typescript/, expo/, nestjs/, cdk/, harper-fabric/, rails/ copy-overwrite trees), is a plugin-distributed rule/skill/hook/agent/command surface, carries Lisa governance markers or a Lisa version stamp, or is governed by package.lisa.json force keys. A defect in the shipped content of such a surface is Lisa's fault — any local edit would be overwritten on the next lisa apply.If the project locally modified a managed surface and the defect lives in the local modification, that is project drift, not a Lisa defect — signal 1 is negative for lisa and positive evidence for project.
Read the shipped artifact named in surface_in_play (the plugin-distributed rule, skill, hook, agent, or CI workflow as Lisa ships it — not a local fork). If following its instructions or configuration as shipped produces the observed defect, the verdict is lisa: cite the artifact path and the specific shipped text or configuration that drove the behavior. If the artifact is shipped correct and was misapplied locally, that is evidence for project.
This is the #1494 doctor procedure, unchanged in behavior — here it is the third signal, not the trigger. Whenever signals 1-2 are not conclusive on their own — and always before a definitive project verdict — pull Lisa's own git history for the version window and read what actually changed:
Resolve the version window. Use the caller-supplied installed_version/latest_version when present; otherwise determine the project's installed Lisa version (the @codyswann/lisa entry in package.json/lockfile, or the plugin version stamp on the active runtime) and the latest published version (npm view @codyswann/lisa version, or the update check's cached result). An unresolvable window makes this signal inconclusive — it can support ambiguous, never lisa.
Pull the upstream history for that window (read-only; no clone required when gh is available):
gh api "repos/CodySwannGT/lisa/compare/v<installed>...v<latest>" \
--paginate --slurp |
jq '{total_commits: .[0].total_commits, files: [.[0].files[]?.filename], commits: [.[].commits[]? | {sha, subject: (.commit.message | split("\n")[0]), api_url: .url, html_url, parents: [.parents[]?.sha]}]}'
--paginate fetches every page of commits, and --slurp gathers those pages into a single
array before the external jq projection runs. GitHub CLI does not permit its built-in --jq
flag together with --slurp, so keep the pipe as shown; without --slurp, paginated responses
are not one merged input. total_commits and files only need the first page
(files are capped at 300 and not repeated on later pages); commits flattens across all pages
while retaining each commit SHA and URLs needed for accurate follow-up.
After path-scoping identifies a candidate commit, fetch its targeted file-level diff context by the retained SHA rather than attributing from the subject alone:
gh api "repos/CodySwannGT/lisa/commits/<sha>" \
--jq '{sha, files: [.files[]? | select(.filename == "<relevant-path>" or (.filename | startswith("<relevant-prefix>/"))) | {filename, status, additions, deletions, changes, patch}]}'
Preserve the returned filename, status, counts, and available patch with the SHA in the
upstream-history projection. A missing or truncated patch is not proof of no relevant change;
use the compare diff fallback below or downgrade the attribution to ambiguous when
commit-level context cannot be established.
The compare endpoint paginates commits (250 without --paginate) and only lists changed files
on the first page, capped at 300 total — a large version window can silently drop commits or
files. If total_commits or the file count looks truncated, re-run with the
application/vnd.github.diff accept header (gh api ... -H "Accept: application/vnd.github.diff")
to pull the full patch text, or fall back to the bounded-fetch git log below. When completeness
still can't be established, say so in the evidence and return ambiguous rather than attributing
with unverified confidence.
Fallbacks, in order: gh api repos/CodySwannGT/lisa/commits?path=<template-path> for a
path-scoped view — note this endpoint has no way to bound results to the v<installed>..v<latest>
window, so treat its output as best-effort context only, not authoritative attribution; a
finite-depth, explicit-tag fetch, which is bounded to the two version refs and should be preferred
for definitive attribution; or the local marketplace/plugin cache checkout when the runtime has
one. For the fetch fallback, use a temporary directory and a fixed history ceiling:
lisa_history_dir="$(mktemp -d)"
git init "$lisa_history_dir"
git -C "$lisa_history_dir" remote add origin https://github.com/CodySwannGT/lisa.git
git -C "$lisa_history_dir" fetch --no-tags --filter=blob:none --depth=256 origin \
refs/tags/v<installed>:refs/tags/v<installed> \
refs/tags/v<latest>:refs/tags/v<latest>
git -C "$lisa_history_dir" merge-base --is-ancestor v<installed> v<latest>
git -C "$lisa_history_dir" log --format='%H%x09%s' v<installed>..v<latest> -- <paths>
git -C "$lisa_history_dir" show --format=fuller --stat --patch <relevant-sha> -- <paths>
The explicit tag refspecs, --no-tags, and finite --depth=256 make this fetch bounded. Do not
silently deepen beyond that ceiling. If merge-base --is-ancestor fails, the shallow window is
incomplete (or the tags are not on one ancestry line): do not make definitive attribution from
it. If none of the bounded sources are reachable, or only the unbounded path-scoped fallback is
reachable, report the gap in the evidence and return ambiguous — never fail the caller's flow
because history was unavailable or incomplete, and never let a degraded history produce lisa.
Scope the reading to what the failure touches. Filter the commit list to the paths that
generate the failing surface: the detected stacks' template dirs (typescript/, expo/, …),
plugins/src/base/ for skills/hooks/rules, scripts/ for governance scripts, and the shipped
config factories (src/configs/). A failure about a lint rule reads the lint-config commits,
not the whole log.
Attribute. When the upstream history shows Lisa changed the relevant contract (a tightened
lint rule, a renamed check context, a new required config key), the verdict is lisa — cite the
commit subject/version. When history shows no relevant upstream change and signals 1-2 are also
negative, the project drifted — the verdict is project, citing the absence of a relevant
upstream change as the evidence.
lisa verdict is the caller's flow (lisa-persist-learning, handoff-upstream disposition); doctor maps verdicts into its findings; remediation stays with whoever called.gh unavailable), or degraded history degrades to ambiguous with the gap named in the evidence — it never raises an error that stops the caller's build or audit.ambiguous instead. Evidence destined for upstream surfaces quotes ONLY Lisa-owned surface text (never host env values, tokens/credentials, PII, or proprietary host code — link the host issue instead of quoting it); the binding redaction procedure lives in lisa-persist-learning's handoff-upstream step.ambiguous is a terminal local verdict: low-confidence, no upstream filing, no durable local rule derived from it.tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.