Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/CodySwannGT/lisa --skill lisa-repair-intake명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
This skill should be used for any non-trivial request — features, bugs, stories, epics, spikes, or multi-step tasks. It accepts a ticket URL (Jira, Linear, GitHub), a file path containing a spec, or a plain-text prompt. It assembles an agent team, breaks the work into structured tasks, and manages the full lifecycle from research through implementation, code review, deploy, and empirical verification.
any non-trivial request —…
This skill should be used for any non-trivial request — features, bugs, stories, epics, spikes, or multi-step tasks. It accepts a ticket URL (Jira, Linear, GitHub), a file path containing a spec, or a plain-text prompt. It assembles an agent team, breaks the work into structured tasks, and manages the full lifecycle from research through implementation, code review, deploy, and empirical verification.
SKILL.md 표시 중
| name | lisa-repair-intake |
| description | Vendor-agnostic repair scanner… |
| allowed-tools | ["Skill","Bash","Read","Write","Edit"] |
Run one batch-repair cycle against the queue identified by $ARGUMENTS, or by merged GitHub
config when the queue is omitted and a GitHub source/tracker default is resolvable. Where lisa-intake
scans the ready role and moves work forward, repair-intake scans the stuck and
close-out roles and moves work unstuck or fully closed:
claimed, PRD
in_review) whose processing cycle died. It is technically "being worked" but nothing is
happening, so it sits ignored forever. (The vendor PRD intakes explicitly leave an errored PRD
in in_review "for the human to investigate from there" — that orphan is exactly what this
skill recovers.) For a stalled build, repair-intake first diagnoses why it stalled by
inspecting its PRs and deploys. A PR that already merged is recovered by applying the env
transition build-intake never got to (its merge gate left the item claimed when the merge landed
after its agent returned) — no re-dispatch. A PR that is merely behind its base (BEHIND, no
conflict) is re-synced in place with gh pr update-branch so the already-enabled auto-merge can
finally land — a clean rebase needs no human, and leaving it stranded is the exact gap that lets an
auto-merge PR sit unmerged forever. A true merge conflict is first given one bounded in-place
re-dispatch to the build agent — whose drive-pr-to-merge fix-mode loop resolves conflicts — because
a conflict, unlike a failing external check, is fixable by re-running the build; only a conflict that
survives that single attempt (or that the agent says needs design input) becomes a fix ticket. The
genuinely non-resolvable blockers — failing checks / unaddressed CodeRabbit or CHANGES_REQUESTED
review / a failed deploy — get a build-ready leaf fix ticket with the item moved to blocked (blocked
by that ticket) instead of blindly re-dispatching the agent, which would just churn against them.blocked whose blocker may now be gone. The blocker is
one of three classes, and repair re-checks all of them, not just dependencies: (a) an
is blocked by dependency has since closed; (b) a validation / quality-gate self-block —
the item was bounced to blocked by its own pre-flight verify/validate gate (missing
Validation Journey, Sign-in Required, Acceptance Criteria, etc.) with no dependency at all,
and a human has since edited the item to add what the gate demanded; or (c) clarifying
questions answered / an ambiguity research can now settle. A self-block (b) is the common
one missed by dependency-only re-checks: nothing else is blocking it, so re-running the same gate
against its current content is the only way to know it is now passable.status:done) but still open/active in the provider's native state. The inverse
also drifts on Linear: a leaf whose native state was auto-completed by a magic-word / branch-linkage
merge into a non-terminal env (Linear completes on merge to any branch, unlike GitHub's
default-branch-scoped close) while its derived status:* label is still intermediate — reconcile
it back to active via lisa-linear-sync Phase 4b per leaf-only-lifecycle.leaf-only-lifecycle. This covers the completed case (all children terminal → close the parent
out) and the intermediate-env case (all children shipped to an env like On Stg, but the
parent never advanced — including a parent left stranded in a status it should never carry).ready container — a parent/container (open child work, or a childless
Epic) wrongly carrying the build-ready role. This is a leaf-only-invariant violation
the build-intake claim gate deliberately leaves for a human; repair-intake reconciles it by
rolling the parent up from its children (with an audit note), so a container never sits in ready
indefinitely.ready label (prd-ready for a PRD, build status:ready for a ticket) so normal intake can see
it; if the later intake/implement gate finds the item incomplete, it moves the item to blocked.ticketed/other open non-product-owned
PRD, or a build Epic/Story container) whose children are discoverable — from the generated-work
section/comment for a PRD, or from body parentage (Parent: #<n> / Parent Epic: #<n>) for a build
container resolved via the documented hierarchy fallback — but whose native sub-issue list is missing
one or more of those children. This is the common shape when children were created by an external
generator (e.g. Codex) or an older write path that recorded parentage only in prose and never called
addSubIssue. repair-intake replays the prd-backlink / github-write-issue native-linking contract
and attaches the missing same-repo children idempotently, so rollup and the GitHub UI can rely on the
native graph again.This skill is the symmetric counterpart to lisa-intake. It reuses the same queue-detection,
the same agent-team orchestration, the same "don't ask, just run" confirmation policy, and the
same per-item surfaces the vendor intakes use (lisa:<source>-to-tracker dry-run for PRDs;
lisa:<tracker>-agent + the scanner's lifecycle transitions for build) — it differs in which
roles it scans and, for stalled/blocked work, that it skips the claim step (the item is already
claimed/blocked). Close-out candidates do not dispatch agents; they only reconcile terminal
lifecycle state with provider-native closure and rollup state.
/lisa:repair-intake [<queue>] [intake_mode=prd|build|both] [build_queue=owner/repo] [stale_after=2h] [max_candidates=100] [force=true]
| Token | Meaning | Default |
|---|---|---|
<queue> | Same queue identifier lisa-intake accepts (see Source dispatch). Optional only when merged config resolves a GitHub source/tracker and identity; otherwise required. | merged GitHub default when resolvable |
intake_mode | prd | build | both. Only meaningful for a GitHub org/repo (or bare github) that hosts both PRD and build label namespaces. both is unique to repair — a repair sweep usefully covers both lifecycles in one schedule. Absent → both when both namespaces exist, else whichever lifecycle exists. | both for dual GitHub queues; otherwise infer |
build_queue | GitHub build scan target used with literal github, especially intake_mode=both. Explicit owner/repo or URL for a build-only invocation wins over this token; otherwise this wins over github.queueRepo. It never redirects the PRD scan. | github.queueRepo, then github.org/github.repo |
stale_after | How long since the last state-changing transition into the in-progress role, or since the last human / PR-side forward-progress activity, before an in-progress item counts as stalled. Automation self-comments do not reset this clock. Accepts 24h, 90m, 2d, or 0 (treat any in-progress item as stalled — manual recovery, also the only way to resume work on a provider that exposes no reliable timestamp). Overrides config. | 2h |
max_candidates | Cap on how many stuck/close-out candidates to enumerate and evaluate. Repair every materially actionable candidate within this bounded set, then stop. Overrides config. | 100 |
force | true bypasses the loop-prevention backoff window (so a manual re-run re-attempts items even if their fingerprint is unchanged). It does not change the staleness rule — use stale_after=0 for that. |
Do NOT ask the caller whether to proceed. Once invoked, resolve the explicit or config-backed queue and run the cycle to completion. The caller (a human at the CLI or a scheduled cron) has already authorized the run by invoking the skill; re-prompting defeats the purpose of a background repair sweep.
Specifically forbidden:
blocked. Returning an item to blocked with a current, accurate note is a
valid outcome of the repair lifecycle, not a failure.max_candidates and the actionable subset inside that cap; the cost of stalling a scheduled
cron waiting on a human is unbounded.The only legitimate reasons to stop early:
Repair-intake follows the same orchestration contract as lisa-intake: it creates NO agent team and spawns NO named teammates. It is a bounded scanner/dispatcher — resolve the queue, evaluate staleness, pick the actionable candidates, and run each repair in the current session.
The reasoning is the same as Intake's: a repair that resumes stalled build work may culminate in a team-first lifecycle skill (lisa-implement, lisa-plan), and those skills can only create their agent team from the lead session — a spawned teammate cannot add named teammates (Claude teams are flat), so pushing the repair into a subagent strands the lifecycle skill without its team and collapses it into a single inline worker. Therefore:
lisa-<source>-to-tracker for a PRD, the <tracker>-agent workflow's gate skills for a build item).Agent spawn — so the lifecycle skill's team-first preamble fires exactly as a direct invocation would.Agent use is a bounded anonymous helper (Agent with name omitted) for scan-side legwork whose result returns directly to this session.Codex: the same contract applies — run the scan inline in the root session and invoke any lifecycle skill there so it can multi_agent_v1.spawn_agent its own team; do not spawn_agent the lifecycle flow itself. Other runtimes: apply the same rule through their equivalent delegation surface; if the runtime has no team/subagent tooling at all, the lifecycle skill's own no-team fallback handles it.
If a teammate inside an existing team somehow invokes this skill (it is a session entry point, not a nested flow), do not scan or repair from there: return a structured delegation-request to the team lead asking it to run the repair cycle in the lead session, and surface the misrouting.
Detect the queue type from $ARGUMENTS using the exact same detection and disambiguation
rules as lisa-intake — read that skill's "Source dispatch" section for the authoritative
table; the detection is identical and only the per-item action changes (repair instead of
claim-and-advance). The essentials, inlined here so this skill is self-complete:
If $ARGUMENTS is... | Queue / lifecycle | Source/tracker key | Candidates repaired |
|---|---|---|---|
| Notion database URL/ID | PRD (Notion) | source=notion | in_review, blocked, terminal/open PRDs, all-terminal generated-work rollups |
| Confluence space URL/key | PRD (Confluence) | source=confluence | in_review, blocked, terminal/open PRDs, all-terminal generated-work rollups |
| Confluence parent page URL/ID | PRD (Confluence, narrowed) | source=confluence | in_review, blocked, terminal/open PRDs, all-terminal generated-work rollups |
Linear workspace URL, team URL/key, or literal linear | PRD (Linear) | source=linear | in_review, blocked, terminal/open PRDs, all-terminal generated-work rollups |
GitHub repo URL / org/repo (PRD namespace) | PRD (GitHub) | source=github | in_review, blocked, terminal/open PRDs, missing PRD child links, all-terminal generated-work rollups |
GitHub repo URL / org/repo with tracker = github (build namespace) | Build (GitHub) | tracker=github | claimed, blocked, terminal/open issues, parent rollups (intermediate-env + all-terminal), stale-ready containers |
GitHub repo URL / org/repo with an open issue missing configured lifecycle labels | GitHub label normalization | per classified lifecycle | add configured prd.ready or build ready |
Literal github or omitted GitHub repo when merged config resolves a GitHub source/tracker and identity | GitHub; PRD scans identity, build scans build_queue → merged → identity; runs those two lane-specific targets |
Disambiguation (same as lisa-intake): a notion.so/notion.site URL → Notion; an Atlassian
/wiki/spaces/<KEY> URL → Confluence (with /pages/<id> → parent-page narrowing); a
linear.app workspace/team URL or literal linear → Linear; a github.com URL / <org>/<repo>
token / literal github → GitHub; a bare token matching the JIRA project-key regex → JIRA
(else try Confluence space, then Linear team); a string with JQL operators → JQL. A single-item
URL is out of scope — this skill is batch-only; repair one item by hand via lisa-implement
(build) or by re-running lisa:<source>-to-tracker (PRD).
For GitHub intake_mode=build (a build-only invocation), an explicit URL or owner/repo wins;
otherwise resolve build_queue, then local/global github.queueRepo, falling back to
github.org/github.repo. For intake_mode=both, the primary <queue> is the PRD identity/source
repo and build_queue controls the build lane, falling back through github.queueRepo to identity.
A short queueRepo is normalized to github.org. Thus both is a bounded split scan: PRD candidates
at the primary identity queue, build candidates at the resolved build queue, combined into one
summary. Never apply both lifecycle namespaces to the umbrella repo merely because
github.queueRepo is set. Repository identity remains distinct and continues to drive
repo:<current> filtering, writes, and automation names. If neither an explicit queue nor a
resolvable merged GitHub default exists, stop with the missing-input error.
Role names for every vendor are resolved from .lisa.config.json per the config-resolution
rule — never hardcode status/label strings. The relevant repair roles:
| Lifecycle | Vendor | In-progress role key | Blocked role key | Terminal / rollup role key |
|---|---|---|---|---|
| Build | JIRA | jira.workflow.claimed (In Progress) | jira.workflow.blocked (Blocked) | env-resolved jira.workflow.done |
| Build | GitHub | github.labels.build.claimed (status:in-progress) | github.labels.build.blocked (status:blocked) | env-resolved github.labels.build.done (status:done) |
| Build | Linear | linear.workflow.claimed (In Progress) | linear.workflow.blocked (Blocked) | env-resolved linear.workflow.done (Done) |
| PRD | Notion | notion.values.in_review (In Review) | notion.values.blocked (Blocked) | notion.values.shipped (Shipped) |
| PRD | GitHub | github.labels.prd.in_review (prd-in-review) | github.labels.prd.blocked (prd-blocked) | github.labels.prd.shipped (prd-shipped) |
| PRD | Linear | linear.labels.prd.in_review (prd-in-review) | linear.labels.prd.blocked (prd-blocked) | linear.labels.prd.shipped (prd-shipped) |
| PRD | Confluence | confluence.parents.in_review (page id) | confluence.parents.blocked (page id) | confluence.parents.shipped (page id) |
In addition to the lifecycle roles above, the build lifecycle defines the human_needed marker — an additive label (jira.labels.human_needed / github.labels.build.human_needed / linear.labels.build.human_needed, default Human Needed / human-needed) that rides alongside blocked when the block needs human-only input no agent or retry can supply (see config-resolution "Build markers"). repair-intake's interaction with the marker is asymmetric and is the whole point of the distinction below:
blocked blocked by that ticket, expecting the next cycle to self-heal. Those are not human_needed; if such an item arrives already carrying a stale human_needed marker, repair-intake clears it (the block is no longer waiting on a human).human_needed already — the agent owns that marker. repair-intake leaves it in place.Resolve with the standard role-read pattern (local overrides global, default fallback):
read_role() {
local path="$1" default="$2"
local local_v global_v
local_v=$(jq -r "${path} // empty" .lisa.config.local.json 2>/dev/null)
global_v=$(jq -r "${path} // empty" .lisa.config.json 2>/dev/null)
echo "${local_v:-${global_v:-$default}}"
}
# e.g. build/github:
CLAIMED=$(read_role '.github.labels.build.claimed' 'status:in-progress')
BLOCKED=$(read_role '.github.labels.build.blocked' 'status:blocked')
repair-intake stays vendor-neutral; concrete reads/writes go through the same layers the vendor
intakes use. Never call Atlassian MCP or acli directly — go through lisa-atlassian-access.
| Vendor | Reads (scan / comments / links) | Writes (transition / comment / close-out) | Re-dispatch / re-validate |
|---|---|---|---|
| JIRA (build) | lisa-atlassian-access search-issues / lisa-jira-read-ticket | lisa-atlassian-access transition / comment | lisa-jira-agent |
| GitHub (build) | gh issue list / gh issue view --json / gh pr list / GraphQL sub-issues | gh issue edit (labels) / gh issue comment / gh issue close --reason completed | lisa-github-agent |
| Linear (build) | Linear MCP list_issues / get_issue / list_comments | Linear MCP save_issue (labels) / save_comment | lisa-linear-agent |
| Notion (PRD) | lisa-notion-access (query, page comments) | lisa-notion-access write-page (status) / page comment | lisa-notion-to-tracker (dry-run) |
| GitHub (PRD) | gh issue list/view (PRD labels) / GraphQL sub-issues / generated-work section | gh issue edit / gh issue comment / gh issue close --reason completed | lisa-github-to-tracker (dry-run) |
| Linear (PRD) | Linear MCP list_projects / get_project (+ sentinel feedback issue) | Linear MCP save_project (labels) / save_comment | lisa-linear-to-tracker (dry-run) |
| Confluence (PRD) | lisa-atlassian-access CQL | lisa-atlassian-access page parentId update / comment | lisa-confluence-to-tracker (dry-run) |
An in-progress item (build claimed, PRD in_review) is stalled when the last
state-changing transition into the in-progress role, or the last human / PR-side forward-progress
activity after that transition, is older than the stale_after threshold. blocked items are NOT
gated on staleness — their repairability is judged on current blocker/answer state, not elapsed
time.
Automation self-comments are not forward progress and must not reset the staleness clock. Status
comments like [claude-build-intake] PR remains open..., [codex-build-intake] Follow-up pushed...,
or [lisa-repair-intake] ... may be useful audit notes, but they cannot make a claimed item fresh
forever. If a provider exposes a changelog/history surface, prefer the timestamp of the last
transition into the claimed/In-Progress role over the item's generic updated timestamp. When the
history surface is unavailable, ignore comments whose author/marker clearly belongs to Lisa or its
automation agents, and use the newest human comment/edit or PR-side progress event instead.
A build claimed leaf whose linked PR has already merged (state == MERGED) is likewise NOT
gated on staleness. A merged PR is a settled terminal state, not in-flight work: the only thing
missing is the env transition build-intake never applied (its merge gate left the item claimed
because the merge landed after its agent returned). The recovery is judged on PR merge state, not
elapsed time — and crucially post-merge activity does not defer it. A freshly-merged PR keeps
producing activity that the signal below would otherwise read as keep-alive (a queued/in-progress
release or deploy check-run, a post-merge CodeRabbit summary comment), so gating merged-PR recovery
on staleness strands a completed leaf in claimed for as long as that activity keeps the clock
warm — exactly the failure that leaves a shipped Sub-task showing status:in-progress for a day
while its parents roll up against it. Recover it regardless of recent activity (Build claimed
decision tree step 0, and the dedicated high-confidence ordering bucket).
$ARGUMENTS stale_after=<dur> (one-off override) — always wins. Parse Nh / Nm / Nd /
0 into hours..lisa.config.json intake.repair.staleAfterHours (durable project default).stale_after=0 means "treat any in-progress item as stalled" — a manual full-recovery lever,
and the only way to resume work on a provider that exposes no reliable activity timestamp.
Compute the item's newest eligible activity timestamp from the highest-priority signal the vendor
exposes, and compare it to now - stale_after:
claimed / In Progress, GitHub label event,
Linear state/label history, Notion/Confluence page move/status history).[claude-build-intake], [codex-build-intake], [lisa-build-intake], and
[lisa-repair-intake].updatedAt / last_edited_time / updated only when the provider cannot
expose transition/comment authorship and the timestamp is not known to be driven by automation
self-comments.If ANY of these is newer than the threshold, the item is active → record it as active and
skip it (read-only). For build claimed, an open PR with recent commits/checks is active. For
PRD in_review, a recent comment or page edit is active.
Count only forward-progress signals as keep-alive: new commits, a review that was just
requested or posted, an in-progress/queued check run, a fresh progress comment. A settled
blocker state — a failing/errored check run, CONFLICTING mergeability, a CHANGES_REQUESTED
review, an unaddressed CodeRabbit/reviewer change request, or a failed deployment — is NOT
keep-alive activity: it does not reset the staleness clock. The clock runs from the last genuine
progress event, so a PR that has been sitting failed/conflicted/awaiting-changes for longer than
stale_after counts as stalled and is diagnosed below.
A merged linked PR is the same kind of non-keep-alive signal, in the other direction: the work
is settled and complete, so its post-merge check-runs and summary comments must NOT count as
keep-alive either. A build claimed leaf with a merged PR is recovered regardless of the staleness
clock (see the Staleness model note above and the dedicated ordering bucket); it is never recorded
active and skipped on the strength of post-merge activity.
If a provider cannot expose any reliable timestamp, do not auto-resume its in-progress
items unless the caller passed stale_after=0. (Dependency-cleared blocked repair still
proceeds — it is judged on blocker state, not time.)
Apply per candidate. Continue through the ordered list until every candidate inside the
max_candidates cap has been evaluated. Each candidate may trigger a write (lifecycle transition,
native close/archive/complete, re-dispatch, or refreshed note), be recorded read-only, or be
recorded under Errors. Do not stop after the first write; the cap is the batch boundary.
claimed (stalled in-progress) → diagnose blocker, else resume in placeFirst check for an already-merged PR — this check is NOT gated on staleness. Read the item's
linked PR state before applying the staleness gate (see "Stuck-cause diagnosis" step 1–2 for
discovery). If state == MERGED, recover it immediately via step 0's merged-PR arm regardless of
elapsed time or recent post-merge activity (per the Staleness model's merged-PR exemption): a merged
PR is a completed leaf, and deferring it behind the staleness clock is what strands shipped work in
claimed.
Only if the PR is not merged does the staleness gate apply. Once it passes, diagnose why it stalled by inspecting the item's PRs and deploys (see "Stuck-cause diagnosis" below). A stalled build usually stalled for a concrete external reason, and re-dispatching the agent at it will not fix a PR that cannot merge or a deploy that failed — it just churns.
claimed because the merge landed after its agent
returned. Do not re-dispatch or file anything — apply the scanner's post-agent env-resolved
claimed → done transition directly (step 2 below, env-resolved), and record it. This is the
recovery arm for build-intake leaving merged-but-unadvanced items in claimed.claimed; a later cycle confirms the merge and transitions. Do not file a fix
ticket for a clean rebase.drive-pr-to-merge fix mode, which resolves conflicts). Only a conflict that
survives that single attempt — the same conflicting head still CONFLICTING on a later cycle — or
that the agent reports needs design input, falls through to the fix-ticket path (diagnosis step 5).CHANGES_REQUESTED /
unaddressed CodeRabbit; or a failed deploy) → do not dispatch the agent. File a build-ready leaf
fix ticket for the blocker, move this item claimed → blocked with an is blocked by link to that
ticket, and record it. The existing "Build blocked → unblock if cleared" path resumes this item on
a later cycle once the fix ticket is terminal — a self-healing loop. Skip the resume steps below.If the PR is healthy in-flight and no blocker is found, the work simply died mid-flight — run the same per-item sequence
the vendor build-intake runs, skipping the claim transition (the item is already claimed):
lisa-jira-agent / lisa-github-agent /
lisa-linear-agent (matching the queue's tracker) — with the item ref. If repair-intake is
running as a teammate rather than the lead/root agent, return a structured delegation-request
to the lead instead of spawning that named peer yourself; only the lead can add named teammates
in Claude's flat roster. This resumes the work in place, preserving its existing branch/PR and
prior comments.claimed → done,
where done is env-resolved exactly as lisa:<tracker>-build-intake resolves it (per
config-resolution env-keyed done: explicit target_env arg wins; else reverse-lookup the
env from the resulting PR's base branch via deploy.branches; if done is a map and env is
unresolvable, fail loudly — never guess). repair-intake owns this transition because it is
standing in for the scanner that never got to finish it.blocked
with a [lisa-repair-intake] note (see Loop prevention). When the surfaced blocker is something
only a human can supply (credentials, access/permissions, a product or scoping decision), the
item also carries the human_needed marker — the vendor agent's pre-flight gate applies it; if
repair-intake makes the block transition itself for such a reason, it adds the marker too. (A
block that another tracked ticket or retry will clear is not human-needed — that is the
auto-recoverable fix-ticket path above.)Do not reset stalled in-progress items to
ready. Reset throws away state, makes a partially-built item look freshly human-approved to the nextlisa-intakeclaim, and forces a two-cycle recovery. Resume in place.
Run this for every stalled claimed build item before considering an agent re-dispatch. The
goal is to distinguish "work died mid-flight, just resume it" from "work is blocked on a concrete
external state that resuming the agent cannot fix."
1. Find the associated PR(s) and deploy(s). From the item's linked PRs (GitHub: prefer the
native dev-link surface — gh issue view <n> --json closedByPullRequestsReferences — which lists
merged PRs that closed the issue, then gh pr list --search <issue-ref> --state all; JIRA:
dev-status / remote links; Linear: attachments and git-branch links) and the deploy(s) for the
resulting merge (the env-keyed deploy.branches mapping from config-resolution). The --state all
is load-bearing: gh pr list --search defaults to --state open, so a merged (closed) PR is
invisible on that surface — the exact state this recovery path exists to catch. A merged PR linked
only via search (no Closes # / native dev link) would otherwise never be discovered, and the leaf
would never recover. Read each PR with the vendor's native state, e.g. GitHub
gh pr view <n> --json state,mergedAt,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup,comments,reviews.
For reviews, derive the effective set from the latest non-dismissed review
per reviewer, ordered by submitted_at then id. Never count the raw history:
an older approval does not satisfy a later CHANGES_REQUESTED review by the
same reviewer. Use the same paginated REST reduction documented by
lisa-drive-pr-to-merge when the gh pr view projection is incomplete.
2. PR already merged → recover, don't re-dispatch. If state == MERGED, the build is effectively
complete and the only thing missing is the env transition the build-intake never applied (its merge
gate left the item claimed because the merge landed after its agent returned). Do not re-dispatch
or file anything: apply the scanner's post-agent env-resolved claimed → done transition (the
resume-sequence step 2, env-resolved from the merged PR's base branch) and record it as a repair
write. Where the env deploy is observable, confirm it did not fail first; a failed post-merge deploy
falls through to the blocker path (step 4).
3. PR only behind its base → re-sync in place (mechanical, not a blocker). If the PR is clean but
behind its base — mergeStateStatus == BEHIND while mergeable != CONFLICTING and no required check
is failing — it does not need a human. This is exactly the case that strands a PR forever: GitHub
auto-merge will not advance a BEHIND branch on its own, so a PR opened with --auto sits unmerged
until something rebases it. Delegate this mechanical nudge + classification to the
drive-pr-to-merge skill in report mode — the single source of truth for the
"ensure auto-merge + re-sync a clean BEHIND branch" primitive — so this scanner does not
re-implement it:
drive-pr-to-merge pr=<n> on_blocker=report
In report mode it ensures auto-merge is enabled and runs gh pr update-branch <n> only when the
PR is BEHIND-but-clean and the base branch's ruleset or classic branch protection requires
strict up-to-date status checks (strict_required_status_checks_policy / required_status_checks.strict).
If strict checks are off, it does not update the branch solely because the base moved; that avoids
CI cancellation storms in repos where updating the PR head restarts and cancels in-flight runs. It
never edits code, resolves threads, or dismisses reviews. It returns a classification (merged /
will-merge-after-resync / blocked:<reason>). On a merged / will-merge-after-resync result,
record this as a repair write (resynced), keep the item claimed, and move on — a later cycle sees
the now-CLEAN (or merged) PR and either lets auto-merge finish or applies the merged-PR recovery in
step 2. Only if gh pr update-branch itself reports a conflict it cannot apply does the PR become a
true conflict (step 4). Honor the backoff window so repeated cycles don't re-issue update-branch on
an unchanged head (Loop prevention). For JIRA/Linear items the PR is still the GitHub PR backing the
branch — operate on it the same way.
4. Classify as a blocker. Treat any of these as a real external blocker:
mergeable = CONFLICTING or mergeStateStatus = DIRTY (overlapping
changes a plain rebase cannot resolve), or gh pr update-branch (step 3) reported a conflict. A
merely BEHIND branch is not here — it was re-synced in step 3. Unlike the other classes below, a
conflict is resolvable by re-running the build, so step 5 gives it one in-place re-dispatch before
filing — see its conflict-first rule.statusCheckRollup has a FAILURE/ERROR/TIMED_OUT conclusion,
or mergeStateStatus = UNSTABLE/BLOCKED due to checks.reviewDecision = CHANGES_REQUESTED, or unresolved CodeRabbit
(or other reviewer) comments that request changes and have not been addressed by a newer commit.mergeStateStatus = BLOCKED for a reason other than
a transient check still running.A check that is still queued/in progress, or a CLEAN/HAS_HOOKS mergeable PR with no
outstanding change request, is not a blocker — that is normal in-flight state. (Such a PR with
recent check/commit activity would already have been caught as active by the staleness gate.)
5. On a blocker found → file a leaf fix ticket + block the item.
Conflict-first exception (try to resolve before filing). A true merge conflict — and only a
conflict, not failing checks, change requests, or a failed deploy — is fixable by re-running the build:
the vendor agent's drive-pr-to-merge fix-mode loop resolves conflicts. So before filing a fix ticket
for a conflict, give the item one in-place re-dispatch: run the resume-in-place sequence (steps 1–3
of the parent path above), which re-enters drive-pr-to-merge in fix mode against the existing PR.
Bound it to a single attempt per conflicting head — when you re-dispatch, post a [lisa-repair-intake] conflict-resolve-attempt: <item-ref>@<head-sha> marker keyed on the PR head SHA. On a later cycle, if
that marker already exists for the same head SHA and the PR is still CONFLICTING, the attempt
failed: stop retrying and file the fix ticket below. File immediately (skip the attempt) if the agent
reports the conflict needs design input. Every other blocker class files the fix ticket with no
re-dispatch. Honor the backoff window / state fingerprint (Loop prevention) so the re-dispatch is never
re-issued against an unchanged conflicting head.
lisa-tracker-write (the
vendor-neutral leaf writer + validation gate; never a vendor *-write-* skill directly),
issue_type: Bug for a failing-check/conflict/failed-deploy, Task for review-feedback
follow-up, build_ready: true so it auto-builds. The ticket MUST name: the blocked item + its
PR/deploy URL, the exact blocker (conflict / which checks failed with their logs link / which
change requests / which deploy run), three-audience description, and Gherkin acceptance criteria
for "PR is mergeable / deploy is green." Every created blocker fix ticket MUST end with the
rejection-detection operator footer as a visible prose line:
To stop this from being raised again, close it as **Not planned**. Close it as **Completed** if it was fixed — a later recurrence may be re-filed as a regression.claimed → blocked and add an is blocked by link to the
new fix ticket (vendor-native: JIRA issue link is blocked by; GitHub/Linear Blocked by: line
[lisa-repair-intake] note naming what it is blocked by and why. This block is
auto-recoverable — the fix ticket will build and close on its own — so do not add the
human_needed marker, and if the item already carries a stale human_needed label, remove it
here (it is no longer waiting on a human). The human_needed marker is reserved for blocks a
human must clear; this one a later cycle clears automatically.The item now sits in blocked; once the fix ticket reaches a terminal state, the Build
blocked → unblock if cleared path (next section) detects the cleared is blocked by
dependency and resumes the original in place — a self-healing loop.
Idempotency. Before filing, check for a fix ticket already carrying the marker
[lisa-repair-intake] blocker:<item-ref>/<blocker-key> (blocker-key is a stable slug of the
blocker, e.g. pr-1234/merge-conflict or pr-1234/checks-failing). Per the rejection-detection
rule's Proposal rejection memory section, that marker search MUST cover open AND closed
tickets (body-enumeration fallback on search-index lag): an open match → reference it and ensure
the is blocked by link is present rather than creating a duplicate; a match closed as not
planned (GitHub stateReason == "not_planned"; the config-resolved equivalent on JIRA/Linear) is
a human decline of that fix ticket — do not re-file it unless evidence postdates the
decline, and the re-filed ticket MUST carry BOTH the machine token (declined <date>; recurred <date> in <ref>) and the human acknowledgment sentence (You declined this on <date>. It has recurred (<date>, <ref>), so we're raising it once more for your review.); a match closed as
completed is a regression path, not a decline. Honor the backoff window and state fingerprint
(Loop prevention) so re-runs over the same unchanged blocker are no-ops.
blocked → re-evaluate, unblock if clearedis blocked by links, therefore nothing
to do." A self-block has zero dependencies by definition, yet is fully re-checkable.is blocked by dependency is cleared → move
blocked → claimed, then run the same agent-dispatch + post-agent claimed → done sequence as
the stalled-claimed path above (one-cycle recovery). If the agent re-blocks, move back to
blocked — a valid outcome.verify/validate gate failure (its [lisa-*] block note carries a gate marker + a "Missing
requirements" list and there is no open is blocked by dependency), re-run the same gate
against the item's current content via lisa-tracker-validate (read-only; the vendor-neutral
gate lisa:<tracker>-build-intake and lisa:<tracker>-write-* already use). This is the build
mirror of the PRD blocked → re-validate path below.
blocked → claimed and resume exactly
as in (2): agent-dispatch + post-agent claimed → done.blocked, but refresh the note with the current (usually smaller)
missing-requirement set so the human sees what remains. Because the fingerprint includes the
gate verdict + missing-requirement set (Loop prevention), a partial human fix changes the
fingerprint and re-checks next cycle, while a truly-unchanged gate result stays in backoff.For each build item that already carries the env-resolved true terminal done role but is still
native-open / active / unresolved:
[lisa-repair-intake]
note naming the incomplete child set.done role is the true final value per leaf-only-lifecycle and
config-resolution env-keyed done. Intermediate env labels (for example status:on-dev or
status:on-stg) are not terminal and must stay open.gh issue close <number> --repo <org>/<repo> --reason completed.statusCategory = Done, resolution set if required);
if not, transition through the configured terminal workflow path or report the missing setup.[lisa-repair-intake] note only when the native close-out changed state or when
an actionable setup error must be surfaced. Do not spam already-closed terminal items.A lifecycle label can lie in two ways, and historically only one of them had an owner:
| label | native state | owned by |
|---|---|---|
terminal done role | still open / active | the Build terminal-open section above |
non-terminal role (ready, claimed, blocked, or any other status: member) | closed / completed | nothing, until now |
A pass that walks one direction leaves the other accumulating silently. Measured on the Linear TUN
board: TUN-556 and TUN-503 both carry a terminal status:done while still natively open in
On Dev — invisible to intake and indistinguishable from handled, exactly like a bot-applied
status:in-progress on GitHub.
Resolve both directions in one pass with the shipped detector, which always reports
directionsWalked covering both, so a "clean" verdict is an assertion that both were examined.
Build the classifier input inside this cycle. Never consume a temp file another skill wrote.
repair-intake runs as its own cycle, so borrowing lisa-github-build-intake's scratch file fails
two ways: absent, the redirect fails and set -e aborts the whole cycle; stale from an earlier run
on a different item, the resolver classifies that other item and this section reports drifts: []
while directionsWalked still claims both directions were walked. That is precisely the
silent-clean verdict this section promises cannot happen.
TRUST_DIR=$(mktemp -d)
trap 'rm -rf "$TRUST_DIR"' EXIT
gh api "repos/<org>/<repo>/issues/<n>" > "$TRUST_DIR/issue.json"
# --paginate emits ONE ARRAY PER PAGE; --slurp + `add` flattens all of them so a
# label event on page two is not silently dropped.
gh api "repos/<org>/<repo>/issues/<n>/timeline?per_page=100" --paginate --slurp \
> "$TRUST_DIR/pages.json"
jq 'add // []' "$TRUST_DIR/pages.json" > "$TRUST_DIR/timeline.json"
# Same resolution `read_role` uses: local overrides global, both optional.
# Resolving a stale terminal `done` here would make the true terminal label look
# like `open-label-closed-state` — and that direction WRITES.
jq -s '(.[0] // {}) * (.[1] // {})' \
<(cat .lisa.config.json 2>/dev/null || echo '{}') \
<(cat .lisa.config.local.json 2>/dev/null || echo '{}') \
> "$TRUST_DIR/config.json"
jq -n --slurpfile i "$TRUST_DIR/issue.json" \
--slurpfile t "$TRUST_DIR/timeline.json" \
--slurpfile c "$TRUST_DIR/config.json" \
'{issue: $i[0], timeline: $t[0], config: $c[0]}' \
> "$TRUST_DIR/input.json"
node "${CLAUDE_PLUGIN_ROOT}/scripts/lifecycle-label-trust.mjs" < "$TRUST_DIR/input.json"
Untrusted labels are excluded from drifts before any repair runs — the classifier does this
itself, so repair must never act on a label the classifier refused to believe. Without it, a
bot-applied label on a natively closed item would be advanced to the terminal done role by the
open-label-closed-state repair, laundering the exact input the guard rejected into a real write.
The excluded array reports what was held back; surface it, do not repair it.
terminal-label-open-state → hand to the Build terminal-open → native close section above.
Do not restate its rules; the terminality test lives there (intermediate env rungs like
status:on-dev / status:on-stg are not terminal).open-label-closed-state → the previously unowned direction. The item is natively closed or
completed while still wearing a non-terminal lifecycle role. Advance the label to the env-resolved
terminal done role and post one idempotent [lisa-repair-intake] note. This is a write on
Lisa's own lifecycle surface, not a contest with another writer.
Apply the leaf/container check from the Lifecycle ownership guard section before this
branch: repair-intake owns container repair, so a natively-closed ready leaf is skipped
here and left to the build lane rather than claimed by this pass.status:* label (see config-resolution). A status:* label there is leftover cruft that no
repair direction reads, which is precisely why TUN-556 and TUN-503 rotted. Treat Linear's native
state as authoritative and remove the contradicting stale label; never move the state to match a
label.Membership in the lifecycle namespace is decided by the status: prefix, never a pinned member
list — the live family has drifted 7 → 6 members, and a literal set fails silently by simply not
matching the member it stopped covering.
status:* labels written by a bot actor within the plausibility window of the item's creation are
not lifecycle signals — see lisa-github-build-intake Phase 2b for the measurement and the
classifier. Report them here so an operator can see how much of the board is wearing a label nobody
chose:
untrusted array;unknownProvenance labels separately — trusted, but unattributable;This class is deliberately read-only and never counts toward the repair cap's actionable work.
For each parent/container item (an Epic, a Linear Project, or any item — of any type — with open child work),
reconcile its lifecycle state with the roll-up of its children — including the intermediate-env
case, not only fully-terminal close-out. This is the recovery-side complement to the forward
rollup the *-sync --rollup skills perform; it catches a parent that was never rolled up (or was
left in a status it should not carry, including a stale build-ready ready).
subIssues graph — the typical
case when the children carry Parent: #<n> / Parent Epic: #<n> in prose but were never attached
(external generators like Codex, or an older write path) — attach each missing same-repo child as a
native sub-issue using the identical idempotent addSubIssue contract the "GitHub PRD missing child
links" path documents below: dedupe by owner/repo#number, treat "already linked" as success, keep
cross-repo/cross-vendor children documented-only with a warning, and on subIssues/addSubIssue
unavailability record a capability warning and continue. A build parent attaches the children
resolved by its hierarchy (its Stories/Sub-tasks), not only empty-parent-token top-level work — the
PRD top-level-only restriction is a PRD rule, not a build one. Record repaired refs in the rollup
state fingerprint so repeated cycles do not re-post. Do this even when step 2 derives unchanged:
the native graph is what the GitHub UI rollup and progress bar depend on, independently of the
parent's status.leaf-only-lifecycle Parent status
rollup state machine, evaluated over the env ladder in-progress < dev < staging < production (the ordered keys of the env-keyed done map): any required child blocked →
blocked; else every required child shipped to some env → the least-advanced env among
them (e.g. all On Stg → On Stg); else any child started → claimed; else unchanged.
Optional / won't-do / not-planned children are terminal-but-dropped and do not hold the parent
open.
2a. When the derived state is blocked, classify the holds before writing anything. A bare
blocked on a container is a single bit, and it cannot tell a hold that waits on an external
event from one that waits on a person rewriting an acceptance criterion — the second never
clears on its own, so rendering them alike lets it accumulate silently ( →
; #3045). Feed the resolved child graph to the shared classifier:ROLLUP_DIR="$(mktemp -d)"
# Serialize the EXACT graph resolved in step 1 before any classifier reads it.
# RESOLVED_CHILD_GRAPH_JSON is the in-memory object from that read; do not
# re-query the tracker here or hand-author a smaller substitute.
printf '%s\n' "$RESOLVED_CHILD_GRAPH_JSON" \
| jq -e '{container, children, readError, renderedState, childTally}' \
> "$ROLLUP_DIR/graph.json"
test -s "$ROLLUP_DIR/graph.json"
# children[]: { ref, state, labels[], blockedBy[{ref, open}], children[] } — the graph
# step 1 already resolved, nested as deep as it was read. The script probes nothing.
jq -n --slurpfile g "$ROLLUP_DIR/graph.json" \
'{container: $g[0].container, children: $g[0].children, readError: $g[0].readError, renderedState: $g[0].renderedState, childTally: $g[0].childTally}' \
> "$ROLLUP_DIR/input.json"
node "${CLAUDE_PLUGIN_ROOT}/scripts/rollup-blocker-classification.mjs" \
--input="$ROLLUP_DIR/input.json" --since="<last recorded fingerprint>"
It exits non-zero and classifies nothing when the tracker could not be read, the container
has no children, or no child was readable. That is a repair failure to report, never an
all-clear and a strict no-write result: do not transition lifecycle state and do not
post/update the rollup note. Do not fall through to "no blocked children" on a non-zero exit. On success its
report names, per class, the blocking leaf, the path to it, and who must act — those lines
are what goes in the rollup note, verbatim. Never set the spec_defect marker yourself and
never infer a class from prose; a hold with nothing recorded classifies unknown, and the
report already asks a person to decide.
save-issue update that writes the resolved workflow stateId, never a lifecycle
label. For GitHub, resolve the lane before writing: PRD parents use the configured prd.*
roles, while build tickets use the configured build roles. Keep exactly one lifecycle label in
that selected namespace and never substitute or overlap the other namespace. When applying any
of these writes, remove conflicting stale lifecycle roles — including a stale ready the
parent should never carry. Post an idempotent [lisa-repair-intake] rollup note naming the derived
state and the child tally (honor the backoff window + fingerprint). Include that exact rendered
state and tally in the classifier input: its fingerprint deduplicates the complete note, not only
the blocker classes. When the derived state is
blocked, the note carries the classifier's per-class report — the blocking leaf, its path,
and its actor — so an operator never descends the tree by hand to find out which item and which
kind. Use the classifier's change.summary as the dedupe test: an unchanged verdict has nothing
new to say and the note is not re-posted.done. When — and only when — the derived
env is the production/terminal value, finalize through the provider-native mechanism (GitHub
gh issue close --reason completed, Linear move to Done state, JIRA resolved/closed verified at
statusCategory = Done). An intermediate-env rollup (On Dev/On Stg) advances the parent's
status but must not close it — it is still open per leaf-only-lifecycle.unchanged (children exist but none started) or the required set is
ambiguous / inaccessible, leave the parent as-is and record it as active or still_blocked
with the current child tally; never guess a transition.in_review (stalled in-progress) → re-run validate→routeAfter the staleness gate passes, run the same dry-run validate→route pipeline the vendor PRD
intake runs per item, targeted at this single PRD and skipping the claim (it is already
in_review):
lisa:<source>-to-tracker with dry_run: true and the PRD's URL (source = the queue's
PRD vendor: notion-to-tracker / confluence-to-tracker / linear-to-tracker /
github-to-tracker). This indirectly runs lisa-tracker-source-artifacts,
lisa-product-walkthrough, and the lisa-tracker-validate gate, returning a structured
PASS/FAIL report with prd_anchor snippets — the same report the PRD intake consumes.lisa:<source>-to-tracker with dry_run: false to write the tickets
(its full run already writes the PRD back-link via lisa-prd-backlink), run the
lisa-prd-ticket-coverage audit as the PRD intake does, then transition the PRD to its
ticketed role via the access layer.prd_anchor (page-level for
prd_anchor: null), tagged [lisa-repair-intake] (Loop prevention), and transition to
blocked.blocked → re-validate if new answers exist[lisa-repair-intake] note or the original blocked note. For Linear include the
sentinel feedback issue and anchored sub-issue comments; for Confluence include inline/footer
comments where the access layer exposes them; for Notion include page comments and
last_edited_time.lisa:<source>-to-tracker dry-run validate→route pipeline as
in PRD in_review above (skipping claim). PASS → ticketed; FAIL → refresh note, stay
blocked.blocked untouched (subject to the
backoff window — do not re-post an identical note).For each PRD source artifact that already carries the configured terminal source role (shipped
for generated-work completion, or a source-specific terminal role that the configured PRD source
declares closed-out) but is still native-open / active:
prd-lifecycle-rollup, unless the
source artifact is already in a stronger product-owned terminal role that explicitly permits
closure. Do not move a PRD out of draft or verified.--reason completed.verified; /lisa:verify-prd remains the only automated writer of the verified
role. This path only reconciles an already-terminal PRD with native closure.For each PRD in ticketed or another non-product-owned open PRD role whose generated top-level
work is fully terminal:
prd-ticket-coverage / the vendor PRD intake
does: native PRD children where supported, plus the durable generated-work section fallback.prd-lifecycle-rollup's vendor predicate. A generated Epic or
Story is terminal only when it has itself rolled up and closed out; do not re-derive its leaf
descendants directly when its own state is still open.shipped role.verified and does not run /lisa:verify-prd.For each open GitHub PRD in ticketed or another non-product-owned PRD role, compare the durable
generated-work fallback against the PRD's native sub-issue graph and repair missing native links.
This is the recovery counterpart to lisa-prd-backlink's GitHub native parent-linking section:
PRD intake/backlink should attach generated top-level work as native PRD children when possible,
but repair-intake must heal the graph when that write was skipped, failed, or later drifted.
Read the generated work exactly as PRD rollup does:
## Tickets / ## Generated Work section (lisa:gw tokens).Top-level work: block as a compatibility fallback. Do not scrape arbitrary
prose.Select only generated top-level work:
lisa:gw entries whose parent token is empty.Top-level work:.
Leaf Sub-tasks and descendant Stories are never direct PRD children.Restrict native repair to same-repo GitHub issues. Cross-repo or cross-vendor generated work stays documented-only; record a warning instead of failing.
Read the PRD's existing native sub-issues with the same GraphQL subIssues query documented by
lisa-prd-backlink / lisa-github-read-issue, and dedupe by child-ref
(owner/repo#number).
For each missing same-repo top-level child, resolve node IDs and call the same GitHub GraphQL
mutation as prd-backlink:
mutation($parentId:ID!,$childId:ID!){
addSubIssue(input:{issueId:$parentId,subIssueId:$childId}){issue{number}subIssue{number}}
}
Treat "already linked" duplicate rejections as success. If subIssues / addSubIssue is
unavailable, leave the documented generated-work fallback intact, record a capability warning, and
continue.
Post one idempotent [lisa-repair-intake] note when a missing native PRD child link is repaired
or when the native-link capability is unavailable. Include the generated top-level child set, the
pre-existing native child set, and repaired child refs in the state fingerprint so repeated cycles
do not spam comments.
Do not transition the PRD lifecycle merely because child links were repaired. Rollup/ship remains governed by the PRD rollup path after the child graph is complete.
For GitHub queues, enumerate open issues that have no configured Lisa lifecycle label in the
active lifecycle namespace(s). This is the repair path for issues created by older tools or humans
with labels like build-ready, or with no Lisa status label at all, that are invisible to
lisa-intake, whose scanner only reads the configured ready labels.
.lisa.config.json / .lisa.config.local.json:
draft, ready, in_review, blocked, ticketed, shipped,
verified when configured.ready, claimed, blocked, every env-resolved done value,
intermediate status labels where configured, and human_needed.intake_mode. If intake_mode=prd, only PRD-classified issues are normalized. If
intake_mode=build, every non-PRD issue is normalized as a build ticket. If intake_mode=both,
classify PRDs first and normalize all remaining issues as build tickets.prd, type:PRD, kind:prd), PRD structure
(## Problem, ## Goals, ## Validation Journey, generated-work/backlink sections), or
body/comment text that explicitly says prd-ready.bug, type:Bug, task, type:Task,
sub-task, type:Sub-task, improvement, type:Improvement, story, spike) or
body/comment text that explicitly says build-ready. When PRD signals are absent and the
selected lifecycle includes build, default to Build ticket even if no build type label is
present; build-intake/implement will validate the item and move it to blocked if required
sections are missing.intake_mode=prd
normalize as PRD; in intake_mode=build normalize as build; in intake_mode=both, prefer PRD
only when the body has PRD structure, otherwise build.The recovery net for the failure ready-role-filing prevents: an agent files a real defect it found
during other work, never gives it the ready role, and the ticket sits forever because build-intake
scans the ready lane and nothing else. Under that rule every filing declares either
build_ready: true or a human_gate reason; an item carrying neither is an incomplete handoff.
ready role for their lifecycle
and are not in a claimed / blocked / terminal role either. On GitHub that is the absence of the
configured build ready label; on JIRA and Linear it is a status/state outside the configured
ready role. Items whose role is simply "the tracker's default created lane" are the target.[lisa-human-gate] marker — those are deliberate holds and the rule
ratifies them. Exclude containers (their state rolls up per leaf-only-lifecycle), and exclude
[lisa-exploratory-qa]-marked findings, which are the rule's named human-gate exception even
on an older item written before the marker existed.ready role, or mark
it as a deliberate human gate. A filing whose readiness nobody declared is exactly the input the
gate model says a human should see, so guessing "it was probably meant to be ready" would
re-introduce the accidental-queue-entry failure this rule exists to eliminate.[lisa-repair-intake] note per item and include the finding in the
loop-prevention fingerprint so repeated cycles do not spam.This is distinct from the GitHub label-normalization repair above, which targets items carrying no Lisa lifecycle label at all (created by older tools or by hand) and does normalize them. This sweep targets items inside the Lisa lifecycle whose readiness was never declared.
A blocked build item is held by one or more of three blocker classes. Identify which are present
from the item's block note(s) and links, then clear-check each present class — an item with no
dependency is not automatically un-actionable; it may be a self-block that now passes.
lisa-tracker-read is a thin dispatcher that returns each vendor's bundle verbatim — there
is no normalized is blocked by field. Read the bundle, then extract blockers per vendor:
lisa-github-build-intake documents — Blocked by: #123,
qualified cross-repo refs (owner/repo#123), issue URLs in the body/comments — plus timeline
cross-reference events.lisa-jira-read-ticket returns and select the
is blocked by link type.get_issue and select
blocker relations.Then classify each blocker:
done role — not only the production terminal. The done role is configured
per-env as a {dev, staging, production} map (config-resolution), so On Dev, On Stg,
and production Done all mean the blocker's code is merged and deployed to ≥1 environment.
A post-build review role (e.g. Code Review) is likewise cleared — the change exists and is
in flight. An is blocked by link is a development dependency: it is satisfied once the
blocker's code is in trunk; it must NOT wait for the blocker to reach production. (This matches
the intake-path dependency-hold gate in lisa-intake-explain, which already treats
code-review / on-dev / on-stg / done as cleared — repair-intake must not be stricter.
In an env-staged workflow where Done means "in production", a strict production-terminal
check strands every dependent forever behind a blocker that is already merged and sitting at
On Stg / On Dev.)ready / claimed / unknown — code not yet in trunk) → still
blocking.Only re-dispatch when every parsed blocker is cleared. When in doubt, stay blocked — a false-negative (left blocked) is cheap; a false-positive (re-dispatched into a real blocker) wastes a build cycle.
A self-block has no is blocked by dependency: the build-intake/agent flow claimed the item,
its pre-flight verify/validate gate failed (missing Validation Journey, Sign-in Required,
Target Backend Environment, Repository, Out of Scope, Evidence manifest, weak Acceptance Criteria,
etc.), and the item was bounced to blocked carrying that gate's [lisa-*] note + "Missing
requirements" list. Detect it by: (1) the block note bears a known gate marker (e.g.
Pre-flight verify gate: BLOCKED, jira-verify / *-validate-*), and (2) there is no open
is blocked by dependency. (If both a dependency and a self-block are present, clear the
dependency via Class A first; the self-block re-check still gates the eventual re-dispatch.)
Clear-check by re-running the same gate against current content — lisa-tracker-validate
(read-only; never writes), which dispatches to lisa:<tracker>-validate-* exactly as the write
path and build-intake do, so the bar cannot drift:
Conservative, same as Class A: a still-failing gate is a real blocker — never re-dispatch a build
whose own gate has not yet passed. This is intentionally symmetric with the PRD blocked → re-validate path: PRDs re-run their dry-run validate→route when source content changes; builds
re-run tracker-validate when item content changes. The asymmetry where build blocked checked
only dependencies — leaving verify-gate self-blocks stranded forever after a human filled in the
missing sections — is the gap this class closes.
A block that research or a human answer can settle (no dependency, no failing gate). Re-check by
running the needed research (lisa-codebase-research / lisa-product-walkthrough) or detecting a
human comment/edit newer than the last [lisa-repair-intake] note. Resolved → proceed to
re-dispatch; else stay blocked.
human_needed hard-stops this path. Do not enter Class C at all. Before classifying a block as
Class C, check the marker: if the item carries human_needed, skip it, leave it blocked, and
record the skip reason in the run record (skipped: human_needed). This is a refusal to
re-evaluate, not a preserve-and-proceed — preserving the marker while overriding what it guards is
not a guard. The marker means a human must decide; no amount of research substitutes for a decision
that has not been made.
Match the marker robustly: the configured label resolves to human-needed (hyphen) while the
contract term is human_needed (underscore), and it can also appear in the block note's prose. Treat
hyphen and underscore as the same marker, case-insensitively, in both the label set and the note. A
literal single-spelling match silently defeats this guard.
"No blocker found" is inconclusive, never clearance. Research that finds nothing does not
satisfy the resolution condition. Absence of a tracked blocker is not evidence that the blocker is
gone — it is frequently evidence that the blocker was never the kind of thing a dependency link
records. An item whose research terminates in "nothing found" stays blocked. Only a positive
finding — the research answered the open question, or a human comment/edit newer than the last
[lisa-repair-intake] note supplied the answer — clears it.
Distinguish a decision-block from an ambiguity-block. Class C applies only where a discoverable
fact would settle the question. A block awaiting a choice between valid alternatives is not a
researchable ambiguity at any confidence level, and research against it is structurally incapable of
reaching the right answer: there is nothing to find, so it terminates in "no blocker found" — which,
before this guard, the next step read as clearance. If the blocker names two or more technically
valid options and asks which to take, it is a decision-block: leave it blocked, ensure the
human_needed marker is present, and do not research it.
Two conditions make a decision-block look exactly like a Class-C candidate: no dependency links (the blocker is a question, not a tracked ticket) and no failed validation gate (it is prose, not a red check). Together they read as "no blockers tracked, ambiguity present, go research it" — precisely the Class-C trigger, and precisely when the guard above must fire instead.
A block set by a deployed or runtime check that failed against a live environment — a smoke/E2E/health probe or manual reproduction that returned an error (an authenticated endpoint 500ing, a deploy health check red, a seeded-data assertion failing). This is neither a dependency nor a content gate: the item is correct but the environment it must verify against is broken.
Clear-check by reproducing the original failing check with the same context that set it — same auth identity/credentials, same environment, same route, same scope. The cardinal rule: never unblock on a probe weaker than the one that set the block. A signal that does not exercise the failed path is not a clear:
Conservative, same as the other classes: reproduces-clean → cleared; still-failing or not-reproducible-this-cycle → stay blocked. Because the cause is external (a deployed defect, not item content), the durable handling is the real external blocker path — file/keep a build-ready
false |
github.queueRepoboth| per lifecycle |
| per lifecycle above, plus GitHub ready-label normalization |
| JIRA project key or full JQL | Build (JIRA) | tracker=jira | claimed, blocked, terminal/closure verification, parent rollups (intermediate-env + all-terminal), stale-ready containers |
GETready → claimed → blockedblocked → claimed
and resume as in (2).blocked; refresh the note with the current observed result. Because
the root cause is external (a deployed defect, not item content), prefer filing/keeping a
build-ready fix ticket and an is blocked by link to it (the "real external blocker" path),
so a later cycle self-heals when that ticket goes terminal.blocked; do not unblock on the absence of a reproduction. If the missing
access is human-only, apply the human_needed marker. Never unblock a deployed-verification
blocker on a weaker signal than the one that set it.lisa-codebase-research / lisa-product-walkthrough); if resolved, proceed
as in (2).blocked.leaf-only-lifecycleready label (default prd-ready).ready label (default status:ready).
Keep any unofficial labels for auditability unless the project has explicitly configured one as a
conflicting lifecycle label. Do not claim the item or dispatch an agent in the same repair cycle;
normalization makes the next normal lisa-intake run pick it up.[lisa-repair-intake] note naming the classification and the configured label
applied. Include the normalization result in the loop-prevention fingerprint so repeated repair
cycles do not spam comments.