@codyswann/lisa 2.239.1 → 2.241.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/core/learnings-entry.d.ts +9 -0
  2. package/dist/core/learnings-entry.d.ts.map +1 -1
  3. package/dist/core/learnings-entry.js +4 -2
  4. package/dist/core/learnings-entry.js.map +1 -1
  5. package/dist/core/learnings-writer.d.ts +33 -0
  6. package/dist/core/learnings-writer.d.ts.map +1 -1
  7. package/dist/core/learnings-writer.js +75 -1
  8. package/dist/core/learnings-writer.js.map +1 -1
  9. package/dist/sync/registry.d.ts.map +1 -1
  10. package/dist/sync/registry.js +7 -0
  11. package/dist/sync/registry.js.map +1 -1
  12. package/package.json +1 -1
  13. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa/.codex-plugin/skills/lisa-attribute-failure/SKILL.md +142 -0
  16. package/plugins/lisa/.codex-plugin/skills/lisa-attribute-failure/agents/openai.yaml +4 -0
  17. package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +28 -77
  18. package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +18 -0
  19. package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +18 -0
  20. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +18 -0
  21. package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +61 -8
  22. package/plugins/lisa/commands/attribute-failure.md +6 -0
  23. package/plugins/lisa/commands/persist-learning.md +1 -1
  24. package/plugins/lisa/rules/eager/claim-archaeology.md +37 -0
  25. package/plugins/lisa/rules/reference/claim-archaeology.md +142 -0
  26. package/plugins/lisa/rules/reference/project-learnings.md +18 -0
  27. package/plugins/lisa/skills/lisa-attribute-failure/SKILL.md +142 -0
  28. package/plugins/lisa/skills/lisa-attribute-failure/agents/openai.yaml +4 -0
  29. package/plugins/lisa/skills/lisa-doctor/SKILL.md +28 -77
  30. package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +18 -0
  31. package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +18 -0
  32. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +18 -0
  33. package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +62 -9
  34. package/plugins/lisa-agy/commands/lisa/attribute-failure.md +6 -0
  35. package/plugins/lisa-agy/commands/lisa/persist-learning.md +1 -1
  36. package/plugins/lisa-agy/plugin.json +1 -1
  37. package/plugins/lisa-agy/skills/lisa-attribute-failure/SKILL.md +142 -0
  38. package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +28 -77
  39. package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +18 -0
  40. package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +18 -0
  41. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +18 -0
  42. package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +62 -9
  43. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  46. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-copilot/commands/lisa/attribute-failure.md +6 -0
  50. package/plugins/lisa-copilot/commands/lisa/persist-learning.md +1 -1
  51. package/plugins/lisa-copilot/rules/eager/claim-archaeology.md +37 -0
  52. package/plugins/lisa-copilot/rules/reference/claim-archaeology.md +142 -0
  53. package/plugins/lisa-copilot/rules/reference/project-learnings.md +18 -0
  54. package/plugins/lisa-copilot/skills/lisa-attribute-failure/SKILL.md +142 -0
  55. package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +28 -77
  56. package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +18 -0
  57. package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +18 -0
  58. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +18 -0
  59. package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +62 -9
  60. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-cursor/commands/lisa/attribute-failure.md +6 -0
  62. package/plugins/lisa-cursor/commands/lisa/persist-learning.md +1 -1
  63. package/plugins/lisa-cursor/rules/claim-archaeology-reference.mdc +147 -0
  64. package/plugins/lisa-cursor/rules/claim-archaeology.mdc +42 -0
  65. package/plugins/lisa-cursor/rules/project-learnings-reference.mdc +18 -0
  66. package/plugins/lisa-cursor/skills/lisa-attribute-failure/SKILL.md +142 -0
  67. package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +28 -77
  68. package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +18 -0
  69. package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +18 -0
  70. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +18 -0
  71. package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +62 -9
  72. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-expo-agy/plugin.json +1 -1
  75. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  79. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  80. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  84. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  85. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  89. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  90. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  95. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  99. package/plugins/lisa-rails-agy/plugin.json +1 -1
  100. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  104. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  105. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  109. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  110. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  112. package/plugins/src/base/commands/attribute-failure.md +6 -0
  113. package/plugins/src/base/commands/persist-learning.md +1 -1
  114. package/plugins/src/base/rules/eager/claim-archaeology.md +37 -0
  115. package/plugins/src/base/rules/reference/claim-archaeology.md +142 -0
  116. package/plugins/src/base/rules/reference/project-learnings.md +18 -0
  117. package/plugins/src/base/skills/lisa-attribute-failure/SKILL.md +142 -0
  118. package/plugins/src/base/skills/lisa-doctor/SKILL.md +28 -77
  119. package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +18 -0
  120. package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +18 -0
  121. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +18 -0
  122. package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +62 -9
@@ -0,0 +1,147 @@
1
+ ---
2
+ description: "Claim-Time Archaeology"
3
+ alwaysApply: false
4
+ ---
5
+
6
+ # Claim-Time Archaeology
7
+
8
+ Lisa lifecycles are ONE-WAY — a done issue never reopens, so residual failures come back as NEW issues, and the causal link "issue B exists because issue A was done wrong" is invisible unless someone digs. Reopening terminal issues is out of scope, so claim-time archaeology is the only way to recover that link: at claim time, determine whether the item being claimed is round 2 of a past failure, and if so, what specifically went wrong the first time.
9
+
10
+ It is a **single vendor-neutral contract** consumed by all three build-intake skills (`lisa-jira-build-intake`, `lisa-github-build-intake`, `lisa-linear-build-intake`). Each vendor arm cites this slug in its claim step rather than growing its own archaeology, exactly as the arms cite `leaf-only-lifecycle`, `repo-scope-split`, and `rejection-detection`. One slug is what keeps an ancestor found on JIRA from being missed on Linear.
11
+
12
+ ## Seam and sequencing — after rejection detection, before the claim transition
13
+
14
+ The three build-intake skills share a uniform claim phase: `3a.0` repo-scope gate → `3a` leaf-only claim gate → `3b` Claim → `3c` run lifecycle (culminating in `lisa-implement`) → `3d` transition to done.
15
+
16
+ Within `3b`, the pre-transition window runs two passes in a fixed order:
17
+
18
+ 1. **`rejection-detection` runs first** (top of `3b`, before the relabel — it needs the current-lane signal that the relabel destroys).
19
+ 2. **Archaeology runs second** — after the rejection classification exists, still **before the relabel/transition** `$READY → $CLAIMED`.
20
+
21
+ The ordering is load-bearing: rejection-detection's classification is an **input** to archaeology's. A `rejection-reclaim` detected in pass 1 flows straight into archaeology's classification — it is reused, **not re-derived**. Archaeology never re-reads transition history to second-guess the rejection detector; forking that signal would guarantee drift between the two passes.
22
+
23
+ **`lisa-implement` is NOT the seam** — it never sees the claim. Archaeology belongs to the build-intake claim phase, like the two gates before it.
24
+
25
+ ## Ancestry signals
26
+
27
+ Three signal sources, tried in order of cheapness. Every query counts against the cost budget below.
28
+
29
+ ### 1. Tracker metadata (typed relations)
30
+
31
+ The cheapest and most reliable signal: the relations the vendor read skills already parse. Read them from the context bundle the intake flow already fetched — do not re-fetch:
32
+
33
+ - The typed relation lines — `Blocks` / `Blocked by` / `Relates to` / `Duplicates` / `Cloned from` — that `lisa-github-read-issue`, `lisa-jira-read-ticket`, and `lisa-linear-read-issue` parse into the relations table of their context bundles.
34
+ - GitHub's native `closingIssuesReferences` (PR↔issue closure links) and timeline cross-references, surfaced by the same `lisa-github-read-issue` GraphQL read. JIRA issue links and Linear native relations (`blocks` / `blocked_by` / `relates_to` / `duplicates`) are the vendor equivalents, read through the access layers (`integration-access-layer`) — never a direct vendor API call.
35
+
36
+ A relation pointing at a **closed, done** issue whose shipped work plausibly covers this issue's surface is an ancestor candidate. An "introduced by"-shaped link (this issue references the PR or issue that shipped the defect) is the strongest form.
37
+
38
+ ### 2. Text similarity (bounded, lexical)
39
+
40
+ **The honest bound, stated plainly: no embedding machinery exists in Lisa, and none is introduced here. This signal is lexical overlap over tracker search primitives — not semantic similarity — and it will miss paraphrased descriptions.** That is acceptable: it exists to catch the common case of a new issue describing a defect in something recently shipped, using roughly the words the shipping issue used.
41
+
42
+ Scope: **recently-closed** issues (closed within the recent window the budget affords, newest first) **touching the same implicated files** where file paths are named or inferable, ranked by **title/label overlap** with the issue being claimed. The primitives:
43
+
44
+ - **GitHub** — `gh search issues "<key terms>" --repo <org>/<repo> --state closed --sort updated` (and `--label` narrowing where labels overlap).
45
+ - **JIRA** — `lisa-atlassian-access operation: search-issues jql: "project = <P> AND statusCategory = Done AND resolved >= -30d AND text ~ \"<key terms>\" ORDER BY resolved DESC"`.
46
+ - **Linear** — `lisa-linear-access operation: list-issues` filtered to completed state types, matched client-side on title/label overlap.
47
+
48
+ A hit is a candidate only when the overlap is specific (shared distinctive terms, same component labels, same files named) — generic word overlap alone never promotes an ancestor.
49
+
50
+ ### 3. Git ancestry (deterministic, machine-readable)
51
+
52
+ For the files the issue implicates (named in the body, or inferred from the similarity hits), answer "which PR last shipped this file" with **direct deterministic git commands**:
53
+
54
+ ```bash
55
+ git log --follow --format='%H %aI %s' -n 5 -- <file> # last commits touching the file
56
+ git blame -L <range> --line-porcelain <file> # who last shipped the implicated lines
57
+ # The PR that shipped the file. --full-history is required: path-limited git log
58
+ # simplifies away merge commits by default, silently dropping the merge-PR answer.
59
+ # Two --grep patterns (OR'd) cover both merge conventions: classic merge commits
60
+ # ("Merge pull request #<n>") and squash/rebase merges (subject ending "(#<n>)").
61
+ # POSIX BRE only — GNU-only \+ silently matches nothing on BSD/macOS git.
62
+ git log --full-history --grep "Merge pull request #" --grep "(#[0-9][0-9]*)" --format='%H %aI %s' -n 5 -- <file>
63
+ ```
64
+
65
+ Keep the result **parseable**: a `{file, sha, pr, date}` tuple per implicated file (PR number extracted from the subject — `Merge pull request #<n>` for merge commits, the trailing `(#<n>)` for squash/rebase merges; empty when the history matches neither convention). The PR maps back to its issue via `closingIssuesReferences` / the PR body's issue reference.
66
+
67
+ **Do NOT delegate this to the `git-history-analyzer` agent.** That agent can answer the question, but it returns a **prose report with no machine-readable contract** (nothing downstream can reliably parse it), it is explicitly forbidden from judging past decisions, and it reads the local repo only. For programmatic claim-time archaeology, run the deterministic query directly and keep the `{file, sha, pr, date}` result.
68
+
69
+ ## Learning-loop exclusion (scan-side — a learning artifact is never an ancestor)
70
+
71
+ This flow produces learning PRs, candidate comments, and upstream handoffs. Those artifacts touch the same files and reference the same issues as the failures they describe — which makes them **near-perfect false-positive ancestors**. Without an explicit exclusion the flow learns from itself, recursively.
72
+
73
+ Before any candidate is promoted to ancestor, exclude every artifact carrying any of these markers or labels — such an artifact is **never an ancestor**, no matter how strong its other signals:
74
+
75
+ - `[lisa-learning-drop]`
76
+ - `[lisa-learning-pr]`
77
+ - `[lisa-learning-upstream-handoff]`
78
+ - `[lisa-rejection-candidate]`
79
+ - `[lisa-archaeology-candidate]` (this rule's own producer tag — archaeology's output must not seed the next claim's input)
80
+ - the `learning:needs-triage` label
81
+
82
+ This is the **scan-side** half of the no-learning-loops guard; `rejection-detection` carries the symmetric **trigger-side** half ("a learning artifact is never a rejection-reflection trigger").
83
+
84
+ ## Classification
85
+
86
+ Exactly one of three states:
87
+
88
+ | Classification | Condition |
89
+ |---|---|
90
+ | `rejection-reclaim` | The `rejection-detection` pass classified this claim `rejection-reclaim`. Taken directly from that result — reused, never re-derived here. Its reflection path (the `[lisa-rejection-candidate]` candidate) already covers the learning; archaeology adds nothing on top. |
91
+ | `retry-of-done-issue` | Not a rejection-reclaim, AND an ancestry signal (§ above, post-exclusion) names a closed done issue whose shipped work this issue exists to fix. |
92
+ | `fresh` | Everything else: no ancestor, weak/inconclusive signals, budget exhausted, or the pass errored. |
93
+
94
+ Classification itself is **stateless** — a pure function of the signals read this pass, holding no cache or stored state between claims. Re-running it on the same inputs yields the same answer; idempotency of the *side effect* (the candidate) is carried by marker dedupe below.
95
+
96
+ ## Candidate derivation (`retry-of-done-issue` only)
97
+
98
+ An ancestor alone teaches nothing — "B relates to A" is trivia. The learning lives in the **delta**: the gap between what agent A actually did and what issue B proves was actually needed.
99
+
100
+ 1. **Reconstruct what the ancestor shipped**, through the access layers: its merged PR (diff, description), the review threads on that PR, and the evidence comments on the ancestor issue.
101
+ 2. **Derive ONE candidate learning citing the delta** — **what was done** versus **what this issue proves was needed**. The shape is "A shipped X; B proves Y was required; the mistake was assuming X sufficed" — never "A had a bug". A **vague summary** that does not name the specific mistake is worthless and must be rejected (produce nothing rather than noise).
102
+ 3. **Route it to `lisa-persist-learning`** exactly like the rejection-reflection path: candidate fields (rule, why, provenance linking the ancestor issue + its PR + this issue, evidence links, scope hint, triggering issue) with fingerprint `sll4-sha1(rule\ntriggering_issue)[:12]`.
103
+
104
+ ### Graceful degrade — `lisa-persist-learning` unavailable
105
+
106
+ Same fallback pattern as the rejection path, with this rule's own distinct marker. Record the candidate as a comment on the claimed item carrying a **visible prose line** plus the marker (a bare marker renders as an empty comment bubble):
107
+
108
+ ```text
109
+ Recorded a candidate learning from this retry's ancestry (queued for the judgment gate): <one-line candidate rule>.
110
+ <!-- [lisa-archaeology-candidate] key=<issue>::<ancestor> -->
111
+ ```
112
+
113
+ The marker line is verbatim — the dedupe contract keys on it, not on the prose.
114
+
115
+ ### Idempotency — marker dedupe
116
+
117
+ The key is `<issue>::<ancestor>` (the claimed item's ref, `::`, the ancestor's ref — the `::` separator keeps the key unambiguous when vendor refs themselves contain hyphens, e.g. `PROJ-123`; it is stable across re-claims of the same pair). Before producing a candidate, search for an existing `[lisa-archaeology-candidate]` comment/artifact carrying this exact key — match on the **marker, never the title** (the `lisa-github-write-prd` Phase 2 discipline). Dedupe is per **(issue, ancestor) pair**: re-claiming an issue whose archaeology already resolved the same ancestor finds the marker and short-circuits — no duplicate candidate for that pair. A re-claim that resolves a **different** ancestor is new evidence and may legitimately produce a second candidate under its own key — that is intended, not a dedupe failure.
118
+
119
+ ### `fresh` produces silence
120
+
121
+ A `fresh` classification produces **no candidate and zero comments**. Silence is the correct output — emitting a low-value candidate on every claim is precisely the rule-pollution failure mode the learning loop names as its existential risk.
122
+
123
+ ## Cost budget — enforced here, configured in one place
124
+
125
+ Archaeology is speculative digging on the critical path of every claim. The budget is what makes that safe.
126
+
127
+ - **`archaeology.maxSteps`** — the maximum number of tracker/git queries one archaeology pass may spend, read from `.lisa.config.json`:
128
+
129
+ ```bash
130
+ MAX_STEPS=$(jq -r '.archaeology.maxSteps // 8' .lisa.config.json 2>/dev/null || echo 8)
131
+ ```
132
+
133
+ The conservative default is **8** — enough for the metadata read (free, already fetched), one or two similarity searches, and git ancestry over a handful of implicated files, and small enough that a fruitless dig on a large repo ends quickly. `lisa sync` seeds the key (registry default), and **this rule pair is the single documented place** for what the budget means — do not restate its semantics in the vendor skills.
134
+ - **`archaeology.maxSeconds`** — optional wall-clock ceiling for the whole pass, read the same way (`jq -r '.archaeology.maxSeconds // empty'`); unset means steps alone bound the pass.
135
+
136
+ **Budget exhaustion is a NORMAL outcome, not an error.** When the pass hits either ceiling with no confident ancestor, it classifies `fresh` and the claim proceeds immediately — no retry, no escalation, no blocking warning.
137
+
138
+ ## Never block the claim
139
+
140
+ The invariant everything above hangs on: **archaeology never blocks the claim**. By construction:
141
+
142
+ - Weak or inconclusive signals → degrade to `fresh`, claim proceeds.
143
+ - Budget exhausted → degrade to `fresh`, claim proceeds.
144
+ - The pass throws or errors (tracker outage, malformed history, missing config) → the exception is caught, classification degrades to `fresh`, and the **claim still proceeds** — a crash inside a speculative bonus feature must never strand a ready issue in the queue.
145
+ - Unreadable ancestor evidence on a genuine retry → no candidate produced, the item is still implemented — degraded, not stopped.
146
+
147
+ Headless-safe throughout: no interactive prompts, safe under intake crons.
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: "Claim-Time Archaeology (load-bearing)"
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Claim-Time Archaeology (load-bearing)
7
+
8
+ Lisa lifecycles are one-way — a done issue never reopens, so a residual failure comes back as a **new** issue with no visible link to the issue that shipped it. Archaeology recovers that link at claim time: the claiming agent learns it is working on round 2 of a past failure, and what specifically went wrong the first time.
9
+
10
+ **One vendor-neutral contract, cited by every build-intake arm** (the `leaf-only-lifecycle` / `repo-scope-split` / `rejection-detection` precedent: one shared slug, never three divergent implementations).
11
+
12
+ ## When it runs
13
+
14
+ In build-intake step 3b, **AFTER the rejection-detection classification and BEFORE the relabel/transition** `$READY → $CLAIMED`. Rejection detection runs first; its classification is an **input** to archaeology — a detected `rejection-reclaim` passes straight through, never re-derived.
15
+
16
+ ## Classify the claimed item
17
+
18
+ Return exactly one of:
19
+
20
+ - **`rejection-reclaim`** — taken directly from the `rejection-detection` result. Reuse it; do not re-derive.
21
+ - **`retry-of-done-issue`** — an ancestry signal names a closed done issue whose shipped work this issue exists to fix.
22
+ - **`fresh`** — no ancestor found, signals weak/inconclusive, budget exhausted, or the pass errored. The default and the safe degrade.
23
+
24
+ ## Ancestry signals (summary — full bindings in the reference body)
25
+
26
+ 1. **Tracker metadata** — the typed relations the read skills already parse (Blocks / Blocked by / Relates to / Duplicates / Cloned from, `closingIssuesReferences`, cross-references).
27
+ 2. **Text similarity** — tracker search primitives over recently-closed issues touching the same implicated files, ranked by title/label overlap. Lexical only; no embedding machinery exists.
28
+ 3. **Git ancestry** — deterministic `git log --follow` / `git blame` / merge-commit queries yielding a parseable `{file, sha, pr, date}` result. Never delegate this to the prose-report `git-history-analyzer` agent.
29
+
30
+ ## Learning-loop exclusion (scan-side)
31
+
32
+ An artifact this flow produced is **never** an ancestor. Exclude anything carrying `[lisa-learning-drop]`, `[lisa-learning-pr]`, `[lisa-learning-upstream-handoff]`, `[lisa-rejection-candidate]`, or `[lisa-archaeology-candidate]` markers, or the `learning:needs-triage` label.
33
+
34
+ ## Cost budget — never block the claim
35
+
36
+ The pass runs inside a hard budget: `.lisa.config.json` `archaeology.maxSteps` (default **8** tracker/git queries; optional `archaeology.maxSeconds`). Budget exhausted, weak signals, or an exception → classify `fresh` and proceed. Archaeology is a bonus layered on the claim; it **never blocks the claim**. Exceeding the budget degrades to `fresh` — a normal outcome, not an error.
37
+
38
+ ## On `retry-of-done-issue`
39
+
40
+ Reconstruct what the ancestor's PR shipped (merged PR, review threads, evidence comments) and derive **ONE** candidate learning citing the **delta** between what was done and what this issue proves was needed — routed to `lisa-persist-learning` exactly like the rejection-reflection path. Fallback when that skill is absent: a comment with a visible prose line plus `<!-- [lisa-archaeology-candidate] key=<issue>::<ancestor> -->` (marker-dedupe; re-claims produce no duplicate). `fresh` → no candidate, zero comments.
41
+
42
+ Full contract (signal bindings, classification table, candidate derivation, budget mechanics): [reference/claim-archaeology.md](claim-archaeology-reference.mdc).
@@ -25,6 +25,24 @@ Each persisted entry has seven fields:
25
25
  - `last_confirmed`
26
26
  - `confidence`
27
27
 
28
+ ## Claim-time confirmation (`last_confirmed`)
29
+
30
+ `last_confirmed` is advanced at claim time by the build-intake flows (step
31
+ 3c.2 of `lisa-{jira,github,linear}-build-intake`) when an entry's rule
32
+ **demonstrably applied** during a claim — the rule was explicitly cited or
33
+ observably followed in the claim's plan or diff. Presence in the eagerly
34
+ loaded context is NOT application: every entry is present in every session,
35
+ so counting mere presence would confirm everything on every claim and defeat
36
+ decay entirely.
37
+
38
+ The bump goes only through `confirmLearningEntry` from
39
+ `@codyswann/lisa/learnings`: a surgical, lock-protected, atomic write that
40
+ advances only `last_confirmed` (re-validated against the
41
+ `>= first_learned` invariant), returns a structured no-op for a missing
42
+ entry or file instead of throwing, and is idempotent within a claim (a
43
+ same-date repeat returns `unchanged`). A failed bump is reported and never
44
+ blocks the build.
45
+
28
46
  Only entries accepted by the executable contract may influence the session. A
29
47
  missing file is expected and silent. Malformed Markdown, invalid JSONL, unsafe
30
48
  paths, non-canonical content, or over-budget documents produce one readable
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: lisa-attribute-failure
3
+ description: "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."
4
+ allowed-tools: ["Skill", "Bash", "Read", "Glob", "Grep"]
5
+ ---
6
+
7
+ # Attribute Failure
8
+
9
+ Attribute ONE failure event to Lisa or the project, with cited evidence. Failure event: $ARGUMENTS
10
+
11
+ 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.**
12
+
13
+ ## Input — the failure event
14
+
15
+ 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.
16
+
17
+ - `defect` — what went wrong, in plain language (required)
18
+ - `implicated_files` — the file paths where the defect lives or manifests (required when known)
19
+ - `surface_in_play` — the Lisa rule / skill / hook / template / workflow involved, if any
20
+ - `installed_version` / `latest_version` — the Lisa version window, when the caller knows it; otherwise resolve it as signal 3 describes
21
+ - `failure_class` — a short slug naming the class of failure (used by downstream filing for the root-cause key)
22
+
23
+ ## Output — the verdict
24
+
25
+ Return exactly one verdict — `lisa` | `project` | `ambiguous` — plus the evidence it relied on:
26
+
27
+ ```text
28
+ ## Attribution: [lisa | project | ambiguous]
29
+
30
+ **Signal:** [managed-surface | shipped-artifact-behavior | upstream-history | none-conclusive]
31
+ **Lisa surface at fault:** [path or rule/skill/hook name | n/a]
32
+ **Cited evidence:**
33
+ - [each item names the signal it came from and the concrete citation: file path + ownership proof, shipped artifact text, or commit sha/subject/version]
34
+ **Confidence note:** [why the evidence is conclusive, or exactly what was inconclusive]
35
+ ```
36
+
37
+ Verdict rules:
38
+
39
+ - **`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.
40
+ - **`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).
41
+ - **`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.
42
+
43
+ ## The three signals (evaluate in order)
44
+
45
+ ### Signal 1 — the defect lives in a Lisa-managed surface
46
+
47
+ Resolve ownership of each implicated file the way doctor's config audit does:
48
+
49
+ - **Copy-overwrite / managed**: the path is populated by `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`.
50
+ - **Create-only**: the local copy is project-owned after scaffolding, but if the defect is faithful to the template as shipped, the template is still Lisa's fault (every newly scaffolded repo inherits it). Cite the template origin, and note the local copy will not be overwritten.
51
+ - **Project-owned**: not from a Lisa template and not Lisa-managed — signal 1 is negative; continue.
52
+
53
+ 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`.
54
+
55
+ ### Signal 2 — a shipped Lisa rule/skill/hook drove the wrong behavior
56
+
57
+ 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`.
58
+
59
+ ### Signal 3 — the upstream-history window shows Lisa changed the relevant contract
60
+
61
+ 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:
62
+
63
+ 1. **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`.
64
+ 2. **Pull the upstream history for that window** (read-only; no clone required when `gh` is available):
65
+
66
+ ```bash
67
+ gh api "repos/CodySwannGT/lisa/compare/v<installed>...v<latest>" \
68
+ --paginate --slurp |
69
+ 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]}]}'
70
+ ```
71
+
72
+ `--paginate` fetches every page of commits, and `--slurp` gathers those pages into a single
73
+ array before the external `jq` projection runs. GitHub CLI does not permit its built-in `--jq`
74
+ flag together with `--slurp`, so keep the pipe as shown; without `--slurp`, paginated responses
75
+ are not one merged input. `total_commits` and `files` only need the first page
76
+ (files are capped at 300 and not repeated on later pages); `commits` flattens across all pages
77
+ while retaining each commit SHA and URLs needed for accurate follow-up.
78
+
79
+ After path-scoping identifies a candidate commit, fetch its targeted file-level diff context by
80
+ the retained SHA rather than attributing from the subject alone:
81
+
82
+ ```bash
83
+ gh api "repos/CodySwannGT/lisa/commits/<sha>" \
84
+ --jq '{sha, files: [.files[]? | select(.filename == "<relevant-path>" or (.filename | startswith("<relevant-prefix>/"))) | {filename, status, additions, deletions, changes, patch}]}'
85
+ ```
86
+
87
+ Preserve the returned filename, status, counts, and available patch with the SHA in the
88
+ upstream-history projection. A missing or truncated `patch` is not proof of no relevant change;
89
+ use the compare diff fallback below or downgrade the attribution to `ambiguous` when
90
+ commit-level context cannot be established.
91
+
92
+ The compare endpoint paginates commits (250 without `--paginate`) and only lists changed files
93
+ on the first page, capped at 300 total — a large version window can silently drop commits or
94
+ files. If `total_commits` or the file count looks truncated, re-run with the
95
+ `application/vnd.github.diff` accept header (`gh api ... -H "Accept: application/vnd.github.diff"`)
96
+ to pull the full patch text, or fall back to the bounded-fetch `git log` below. When completeness
97
+ still can't be established, say so in the evidence and return `ambiguous` rather than attributing
98
+ with unverified confidence.
99
+
100
+ Fallbacks, in order: `gh api repos/CodySwannGT/lisa/commits?path=<template-path>` for a
101
+ path-scoped view — note this endpoint has no way to bound results to the `v<installed>..v<latest>`
102
+ window, so treat its output as best-effort context only, not authoritative attribution; a
103
+ finite-depth, explicit-tag fetch, which is bounded to the two version refs and should be preferred
104
+ for definitive attribution; or the local marketplace/plugin cache checkout when the runtime has
105
+ one. For the fetch fallback, use a temporary directory and a fixed history ceiling:
106
+
107
+ ```bash
108
+ lisa_history_dir="$(mktemp -d)"
109
+ git init "$lisa_history_dir"
110
+ git -C "$lisa_history_dir" remote add origin https://github.com/CodySwannGT/lisa.git
111
+ git -C "$lisa_history_dir" fetch --no-tags --filter=blob:none --depth=256 origin \
112
+ refs/tags/v<installed>:refs/tags/v<installed> \
113
+ refs/tags/v<latest>:refs/tags/v<latest>
114
+ git -C "$lisa_history_dir" merge-base --is-ancestor v<installed> v<latest>
115
+ git -C "$lisa_history_dir" log --format='%H%x09%s' v<installed>..v<latest> -- <paths>
116
+ git -C "$lisa_history_dir" show --format=fuller --stat --patch <relevant-sha> -- <paths>
117
+ ```
118
+
119
+ The explicit tag refspecs, `--no-tags`, and finite `--depth=256` make this fetch bounded. Do not
120
+ silently deepen beyond that ceiling. If `merge-base --is-ancestor` fails, the shallow window is
121
+ incomplete (or the tags are not on one ancestry line): do not make definitive attribution from
122
+ it. If none of the bounded sources are reachable, or only the unbounded path-scoped fallback is
123
+ reachable, report the gap in the evidence and return `ambiguous` — never fail the caller's flow
124
+ because history was unavailable or incomplete, and never let a degraded history produce `lisa`.
125
+ 3. **Scope the reading to what the failure touches.** Filter the commit list to the paths that
126
+ generate the failing surface: the detected stacks' template dirs (`typescript/`, `expo/`, …),
127
+ `plugins/src/base/` for skills/hooks/rules, `scripts/` for governance scripts, and the shipped
128
+ config factories (`src/configs/`). A failure about a lint rule reads the lint-config commits,
129
+ not the whole log.
130
+ 4. **Attribute.** When the upstream history shows Lisa changed the relevant contract (a tightened
131
+ lint rule, a renamed check context, a new required config key), the verdict is `lisa` — cite the
132
+ commit subject/version. When history shows no relevant upstream change and signals 1-2 are also
133
+ negative, the project drifted — the verdict is `project`, citing the absence of a relevant
134
+ upstream change as the evidence.
135
+
136
+ ## Rules
137
+
138
+ - **Read-only.** This skill reads the project and Lisa's repository; it never writes to either, never files issues, and never remediates. Filing the upstream ticket on a `lisa` verdict is the caller's flow (`lisa-persist-learning`, `handoff-upstream` disposition); doctor maps verdicts into its findings; remediation stays with whoever called.
139
+ - **Never block.** Attribution failure, missing tooling (`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.
140
+ - **Every verdict cites its evidence.** An attribution without a named signal and concrete citation is invalid — return `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.
141
+ - **Ambiguous never escalates.** `ambiguous` is a terminal local verdict: low-confidence, no upstream filing, no durable local rule derived from it.
142
+ - **Headless-safe and idempotent**: no prompts, no side effects, same event in → same verdict out.
@@ -296,88 +296,39 @@ without turning the base doctor into a second `lisa-wiki-doctor`.
296
296
  A failing or warning check has two possible causes: the project drifted, or **Lisa itself changed
297
297
  upstream** since this project last updated. Doctor must distinguish them instead of blaming the
298
298
  project by default. Whenever findings need explanation — and always before proposing repairs —
299
- pull Lisa's own git history and read what actually changed:
299
+ attribute the finding with real evidence.
300
+
301
+ The attribution procedure itself is shared: it lives in the `lisa-attribute-failure` skill
302
+ (extracted from this section so any failure event can be attributed, not only doctor findings).
303
+ Doctor invokes it per finding:
300
304
 
301
305
  1. **Resolve the version window.** Determine the project's installed Lisa version (the
302
306
  `@codyswann/lisa` entry in `package.json`/lockfile, or the plugin version stamp on the active
303
307
  runtime) and the latest published version (`npm view @codyswann/lisa version`, or the update
304
308
  check's cached result).
305
- 2. **Pull the upstream history for that window** (read-only; no clone required when `gh` is
306
- available):
307
-
308
- ```bash
309
- gh api "repos/CodySwannGT/lisa/compare/v<installed>...v<latest>" \
310
- --paginate --slurp |
311
- 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]}]}'
312
- ```
313
-
314
- `--paginate` fetches every page of commits, and `--slurp` gathers those pages into a single
315
- array before the external `jq` projection runs. GitHub CLI does not permit its built-in `--jq`
316
- flag together with `--slurp`, so keep the pipe as shown; without `--slurp`, paginated responses
317
- are not one merged input. `total_commits` and `files` only need the first page
318
- (files are capped at 300 and not repeated on later pages); `commits` flattens across all pages
319
- while retaining each commit SHA and URLs needed for accurate follow-up.
320
-
321
- After path-scoping identifies a candidate commit, fetch its targeted file-level diff context by
322
- the retained SHA rather than attributing from the subject alone:
323
-
324
- ```bash
325
- gh api "repos/CodySwannGT/lisa/commits/<sha>" \
326
- --jq '{sha, files: [.files[]? | select(.filename == "<relevant-path>" or (.filename | startswith("<relevant-prefix>/"))) | {filename, status, additions, deletions, changes, patch}]}'
327
- ```
328
-
329
- Preserve the returned filename, status, counts, and available patch with the SHA in the
330
- upstream-history projection. A missing or truncated `patch` is not proof of no relevant change;
331
- use the compare diff fallback below or downgrade attribution to `WARN` when commit-level context
332
- cannot be established.
333
-
334
- The compare endpoint paginates commits (250 without `--paginate`) and only lists changed files
335
- on the first page, capped at 300 total — a large version window can silently drop commits or
336
- files. If `total_commits` or the file count looks truncated, re-run with the
337
- `application/vnd.github.diff` accept header (`gh api ... -H "Accept: application/vnd.github.diff"`)
338
- to pull the full patch text, or fall back to the bounded-fetch `git log` below. When completeness
339
- still can't be established, say so in the finding and mark it `WARN` rather than attributing
340
- drift with unverified confidence.
341
-
342
- Fallbacks, in order: `gh api repos/CodySwannGT/lisa/commits?path=<template-path>` for a
343
- path-scoped view — note this endpoint has no way to bound results to the `v<installed>..v<latest>`
344
- window, so treat its output as best-effort context only, not authoritative attribution; a
345
- finite-depth, explicit-tag fetch, which is bounded to the two version refs and should be preferred
346
- for definitive attribution; or the local marketplace/plugin cache checkout when the runtime has
347
- one. For the fetch fallback, use a temporary directory and a fixed history ceiling:
348
-
349
- ```bash
350
- lisa_history_dir="$(mktemp -d)"
351
- git init "$lisa_history_dir"
352
- git -C "$lisa_history_dir" remote add origin https://github.com/CodySwannGT/lisa.git
353
- git -C "$lisa_history_dir" fetch --no-tags --filter=blob:none --depth=256 origin \
354
- refs/tags/v<installed>:refs/tags/v<installed> \
355
- refs/tags/v<latest>:refs/tags/v<latest>
356
- git -C "$lisa_history_dir" merge-base --is-ancestor v<installed> v<latest>
357
- git -C "$lisa_history_dir" log --format='%H%x09%s' v<installed>..v<latest> -- <paths>
358
- git -C "$lisa_history_dir" show --format=fuller --stat --patch <relevant-sha> -- <paths>
359
- ```
360
-
361
- The explicit tag refspecs, `--no-tags`, and finite `--depth=256` make this fetch bounded. Do not
362
- silently deepen beyond that ceiling. If `merge-base --is-ancestor` fails, the shallow window is
363
- incomplete (or the tags are not on one ancestry line): do not make definitive attribution from
364
- it. If none of the bounded sources are reachable, or only the unbounded path-scoped fallback is
365
- reachable, report the gap as a `WARN`-level observability note — never fail the audit because
366
- history was unavailable or incomplete.
367
- 3. **Scope the reading to what the finding touches.** Filter the commit list to the paths that
368
- generate the failing surface: the detected stacks' template dirs (`typescript/`, `expo/`, …),
369
- `plugins/src/base/` for skills/hooks/rules, `scripts/` for governance scripts, and the shipped
370
- config factories (`src/configs/`). A finding about a lint rule failure reads the lint-config
371
- commits, not the whole log.
372
- 4. **Attribute the finding.** When the upstream history shows Lisa changed the contract (a
373
- tightened lint rule, a renamed check context, a new required config key), say so in `Observed:`
374
- with the commit subject/version, and let `Remediation:` point at the sanctioned adoption path
375
- (e.g. `lisa update` + re-apply, a documented config opt-out) rather than hand-editing managed
376
- files. When history shows no relevant upstream change, the project drifted — remediate on the
377
- project side.
378
-
379
- This history pull is part of doctor's read-only contract: it reads Lisa's repository, never writes
380
- to it, and repair suggestions stay suggestions.
309
+ 2. **Invoke `lisa-attribute-failure`** with the finding as the failure event: the defect
310
+ description, the implicated template/config paths, the rule/skill/hook in play, and the
311
+ resolved version window. The skill evaluates its three ordered signals — Lisa-managed surface
312
+ ownership, shipped rule/skill/hook behavior, and the upstream change-history window (the
313
+ version-window compare procedure formerly documented inline here, preserved verbatim in that
314
+ skill: pagination/`--slurp` handling, targeted per-SHA diff context, truncation caveats, and
315
+ the bounded explicit-tag fetch fallback) and returns `lisa` | `project` | `ambiguous` plus
316
+ the cited evidence.
317
+ 3. **Map the verdict into the finding.**
318
+ - `lisa` Lisa changed the contract (a tightened lint rule, a renamed check context, a new
319
+ required config key) or shipped the defective surface. Say so in `Observed:` with the cited
320
+ evidence (commit subject/version or managed-surface proof), and let `Remediation:` point at
321
+ the sanctioned adoption path (e.g. `lisa update` + re-apply, a documented config opt-out)
322
+ rather than hand-editing managed files.
323
+ - `project` history shows no relevant upstream change and the surface is project-owned: the
324
+ project drifted, so remediate on the project side.
325
+ - `ambiguous` history was unavailable, truncated, or otherwise inconclusive: report the gap
326
+ as a `WARN`-level observability note with the evidence gap named never fail the audit
327
+ because history was unavailable or incomplete, and never attribute drift with unverified
328
+ confidence.
329
+
330
+ This diagnosis remains part of doctor's read-only contract: the attribution skill reads Lisa's
331
+ repository, never writes to it, and repair suggestions stay suggestions.
381
332
 
382
333
  ## Output contract
383
334
 
@@ -256,6 +256,8 @@ A blocker is active if it is open and has no cleared status label. Treat `status
256
256
 
257
257
  **On `rejection-reclaim`, reflect before re-implementing** (per `rejection-detection`): read the rejection evidence through the access layer — the issue comments posted after the backward transition (the QA rejection comment) and the review threads on the rejected PR via `lisa-github-read-issue` — assemble ONE candidate learning (rule, why, provenance linking the rejection comment + rejected PR, evidence links, scope hint, triggering issue, fingerprint `sll4-sha1(rule\ntriggering_issue)[:12]`), and route it to the `lisa-persist-learning` skill. If that skill is absent, record the candidate as a comment carrying a **visible prose line plus** the marker (a bare marker renders as an empty bubble) — `Recorded a candidate learning from this rejection (queued for the judgment gate): <one-line candidate rule>.` then `<!-- [lisa-rejection-candidate] key=<issue>-<transition-ts> -->` — and proceed. Dedupe on `<issue>-<backward-transition-timestamp>` — a second re-claim produces no duplicate. Unreadable/absent evidence → no candidate, still implement.
258
258
 
259
+ **Claim-time archaeology runs second — after rejection detection, still before the relabel below.** Classify this item per the vendor-neutral `claim-archaeology` rule, with the rejection classification above as its input. All shared semantics — ancestry signals, classification, learning-loop exclusion, cost budget, candidate derivation, marker dedupe, and the never-block degrade — live in that one slug; change them there, never here. GitHub wiring only: the typed relations and `closingIssuesReferences` are already in the read bundle; text-similarity searches use `gh search issues` over recently-closed issues; the fallback candidate comment is posted with `gh issue comment`.
260
+
259
261
  ```bash
260
262
  gh issue edit <number> --repo <org>/<repo> --remove-label "$READY" --add-label "$CLAIMED"
261
263
  # Assign to the authenticated user ONLY when the issue is currently unassigned (attributable claim;
@@ -325,6 +327,22 @@ If the canonical fix is merged but not yet present on the production branch, app
325
327
 
326
328
  This path is distinct from `BLOCKED`: ambiguity, open blockers, and duplicate-of-open findings remain held for human action and must not be auto-closed.
327
329
 
330
+ #### 3c.2 Confirm applied learnings (last_confirmed bump)
331
+
332
+ Run this at the end of 3c, after the lifecycle outcome is recorded and before 3d. It keeps the decay pass safe: a genuinely useful learning that keeps applying stays fresh, while dead weight ages out.
333
+
334
+ 1. **Identify which learnings demonstrably applied.** Resolve the learnings surface with `resolveProjectLearningsFile` and parse it with `parseLearningsFile` from `@codyswann/lisa/learnings` (never hardcode the path; a missing file skips this step silently). An entry counts as applied ONLY when its rule was explicitly cited or observably followed in this claim's plan, diff, or review responses — the plan quotes the rule or its id, or the diff does specifically what the rule mandates where the default behavior would have differed. **Presence in context is NOT application**: the ledger is loaded eagerly into every session, so counting "it was loaded" would confirm every entry on every claim and defeat decay entirely. A run that produced no plan or diff has nothing to confirm.
335
+ 2. **Bump each applied entry exactly once** via the surgical writer:
336
+
337
+ ```bash
338
+ node -e 'import("@codyswann/lisa/learnings").then(async m => { const r = await m.confirmLearningEntry(process.cwd(), process.argv[1], new Date().toISOString().slice(0, 10)); console.log(JSON.stringify(r)); }).catch(error => { console.log(JSON.stringify({ status: "error", id: process.argv[1], message: String(error) })); })' <entry-id> || true
339
+ ```
340
+
341
+ The invocation is failure-safe by construction: a rejected import or write resolves to a structured `error` result instead of a crash, and the trailing `|| true` absorbs any remaining non-zero exit (missing `node`, unresolvable package). Record an `error` or non-zero outcome in the cycle summary and continue — the bump must never abort the lifecycle.
342
+
343
+ `confirmLearningEntry` advances ONLY `last_confirmed` — rule text, why, provenance, `first_learned`, and confidence are untouched — and is idempotent within a claim: a repeat same-date bump returns `unchanged`, so an entry that applied repeatedly during one claim is bumped once, not once per application.
344
+ 3. **Never block on it.** A failed bump, an unwritable file, or a `not-found` result (the entry was pruned) is recorded under the cycle summary and the claim proceeds — shipping the issue always outranks confirming a learning about it.
345
+
328
346
  #### 3d. Transition to $DONE (only after the PR is merged)
329
347
 
330
348
  A `done` env state (`status:on-dev`, `status:on-stg`, or the terminal value) asserts that the code has actually reached that environment. Never set it for a PR that is merely open: auto-merge can be blocked indefinitely (a required rebase / `BEHIND` branch, failing checks, an unaddressed review), and the change may never land. Relabeling an issue `status:on-stg` on an open PR makes it *claim* a deploy that never happened. Transition only after confirming the PR merged.
@@ -200,6 +200,8 @@ This gate never blocks a legitimate flat Task/Bug: those have no open children a
200
200
 
201
201
  **On `rejection-reclaim`, reflect before re-implementing** (per `rejection-detection`): read the rejection evidence through the access layer — the ticket comments posted after the backward transition (the QA rejection comment) via `lisa-atlassian-access operation: read-ticket` / `comment` reads and the review threads on the rejected PR — assemble ONE candidate learning (rule, why, provenance linking the rejection comment + rejected PR, evidence links, scope hint, triggering issue, fingerprint `sll4-sha1(rule\ntriggering_issue)[:12]`), and route it to the `lisa-persist-learning` skill. If that skill is absent, record the candidate via `lisa-atlassian-access operation: comment` as a comment carrying a **visible prose line plus** the marker (a bare marker renders as an empty bubble) — `Recorded a candidate learning from this rejection (queued for the judgment gate): <one-line candidate rule>.` then `<!-- [lisa-rejection-candidate] key=<issue>-<transition-ts> -->` — and proceed. Dedupe on `<issue>-<backward-transition-timestamp>` — a second re-claim produces no duplicate. Unreadable/absent evidence → no candidate, still implement.
202
202
 
203
+ **Claim-time archaeology runs second — after rejection detection, still before the transition below.** Classify this ticket per the vendor-neutral `claim-archaeology` rule, with the rejection classification above as its input. All shared semantics — ancestry signals, classification, learning-loop exclusion, cost budget, candidate derivation, marker dedupe, and the never-block degrade — live in that one slug; change them there, never here. JIRA wiring only: the typed relations are already in the read bundle; text-similarity searches run through `lisa-atlassian-access operation: search-issues jql:` over recently-closed tickets; the fallback candidate comment is posted via `lisa-atlassian-access operation: comment`.
204
+
203
205
  Transition the ticket from `$READY` to `$CLAIMED` by invoking `lisa-atlassian-access` `operation: transition key: <TICKET> to: "$CLAIMED"`.
204
206
  - **Assign to the authenticated user when the ticket is unassigned.** A claim must be attributable. If the ticket has no assignee, assign it to the authenticated account — prefer acli `--assignee @me` (resolves server-side to the authenticated user, which avoids the federated-`accountId` mis-assignment), or `write-ticket` with the `accountId` from the `/rest/api/3/myself` identity probe the access skill already documents. Leave an already-assigned ticket's assignee untouched — never reassign work that already has an owner.
205
207
  - Post a `[claude-build-intake]` comment via `lisa-atlassian-access` `operation: comment key: <TICKET> body: "Claimed by Claude. Starting build."`
@@ -257,6 +259,22 @@ If the canonical fix is merged but not yet present on the production branch, app
257
259
 
258
260
  This path is distinct from `BLOCKED`: ambiguity, open blockers, and duplicate-of-open findings remain held for human action and must not be auto-closed.
259
261
 
262
+ #### 3c.2 Confirm applied learnings (last_confirmed bump)
263
+
264
+ Run this at the end of 3c, after the lifecycle outcome is recorded and before 3d. It keeps the decay pass safe: a genuinely useful learning that keeps applying stays fresh, while dead weight ages out.
265
+
266
+ 1. **Identify which learnings demonstrably applied.** Resolve the learnings surface with `resolveProjectLearningsFile` and parse it with `parseLearningsFile` from `@codyswann/lisa/learnings` (never hardcode the path; a missing file skips this step silently). An entry counts as applied ONLY when its rule was explicitly cited or observably followed in this claim's plan, diff, or review responses — the plan quotes the rule or its id, or the diff does specifically what the rule mandates where the default behavior would have differed. **Presence in context is NOT application**: the ledger is loaded eagerly into every session, so counting "it was loaded" would confirm every entry on every claim and defeat decay entirely. A run that produced no plan or diff has nothing to confirm.
267
+ 2. **Bump each applied entry exactly once** via the surgical writer:
268
+
269
+ ```bash
270
+ node -e 'import("@codyswann/lisa/learnings").then(async m => { const r = await m.confirmLearningEntry(process.cwd(), process.argv[1], new Date().toISOString().slice(0, 10)); console.log(JSON.stringify(r)); }).catch(error => { console.log(JSON.stringify({ status: "error", id: process.argv[1], message: String(error) })); })' <entry-id> || true
271
+ ```
272
+
273
+ The invocation is failure-safe by construction: a rejected import or write resolves to a structured `error` result instead of a crash, and the trailing `|| true` absorbs any remaining non-zero exit (missing `node`, unresolvable package). Record an `error` or non-zero outcome in the cycle summary and continue — the bump must never abort the lifecycle.
274
+
275
+ `confirmLearningEntry` advances ONLY `last_confirmed` — rule text, why, provenance, `first_learned`, and confidence are untouched — and is idempotent within a claim: a repeat same-date bump returns `unchanged`, so an entry that applied repeatedly during one claim is bumped once, not once per application.
276
+ 3. **Never block on it.** A failed bump, an unwritable file, or a `not-found` result (the entry was pruned) is recorded under the cycle summary and the claim proceeds — shipping the ticket always outranks confirming a learning about it.
277
+
260
278
  #### 3d. Transition to $DONE (only after the PR is merged)
261
279
 
262
280
  A `done` env status (`On Dev`, `On Stg`, or the terminal value) asserts that the code has actually reached that environment. Never set it for a PR that is merely open: auto-merge can be blocked indefinitely (a required rebase / `BEHIND` branch, failing checks, an unaddressed review), and the change may never land. Setting `On Stg` on an open PR makes a ticket *claim* a deploy that never happened. Transition only after confirming the PR merged.
@@ -190,6 +190,8 @@ This gate never blocks a legitimate flat Task/Bug: those have no open children a
190
190
 
191
191
  **On `rejection-reclaim`, reflect before re-implementing** (per `rejection-detection`): read the rejection evidence through the access layer — the Issue comments posted after the backward transition (the QA rejection comment) via `lisa-linear-access operation: list-comments` and the review threads on the rejected PR — assemble ONE candidate learning (rule, why, provenance linking the rejection comment + rejected PR, evidence links, scope hint, triggering issue, fingerprint `sll4-sha1(rule\ntriggering_issue)[:12]`), and route it to the `lisa-persist-learning` skill. If that skill is absent, record the candidate via `lisa-linear-access operation: save-comment` as a comment carrying a **visible prose line plus** the marker (a bare marker renders as an empty bubble) — `Recorded a candidate learning from this rejection (queued for the judgment gate): <one-line candidate rule>.` then `<!-- [lisa-rejection-candidate] key=<issue>-<transition-ts> -->` — and proceed. Dedupe on `<issue>-<backward-transition-timestamp>` — a second re-claim produces no duplicate. Unreadable/absent evidence → no candidate, still implement.
192
192
 
193
+ **Claim-time archaeology runs second — after rejection detection, still before the relabel below.** Classify this Issue per the vendor-neutral `claim-archaeology` rule, with the rejection classification above as its input. All shared semantics — ancestry signals, classification, learning-loop exclusion, cost budget, candidate derivation, marker dedupe, and the never-block degrade — live in that one slug; change them there, never here. Linear wiring only: the native relations are already in the read bundle; text-similarity searches run through `lisa-linear-access operation: list-issues` filtered to recently-closed Issues; the fallback candidate comment is posted via `lisa-linear-access operation: save-comment`.
194
+
193
195
  Update labels via `lisa-linear-access operation: save-issue`: remove `$READY`, add `$CLAIMED`. Resolve label IDs via `list_issue_labels` (create `$CLAIMED` if missing).
194
196
 
195
197
  **Assign to the authenticated user when the Issue is unassigned.** A claim must be attributable. If the Issue has no assignee, set its `assigneeId` to the authenticated viewer (resolve the viewer's id via the Linear MCP identity — e.g. `get_user` for the current actor) through `lisa-linear-access operation: save-issue`. Leave an already-assigned Issue's assignee untouched — never reassign work that already has an owner.
@@ -250,6 +252,22 @@ If the canonical fix is merged but not yet present on the production branch, app
250
252
 
251
253
  This path is distinct from `BLOCKED`: ambiguity, open blockers, and duplicate-of-open findings remain held for human action and must not be auto-closed.
252
254
 
255
+ #### 3c.2 Confirm applied learnings (last_confirmed bump)
256
+
257
+ Run this at the end of 3c, after the lifecycle outcome is recorded and before 3d. It keeps the decay pass safe: a genuinely useful learning that keeps applying stays fresh, while dead weight ages out.
258
+
259
+ 1. **Identify which learnings demonstrably applied.** Resolve the learnings surface with `resolveProjectLearningsFile` and parse it with `parseLearningsFile` from `@codyswann/lisa/learnings` (never hardcode the path; a missing file skips this step silently). An entry counts as applied ONLY when its rule was explicitly cited or observably followed in this claim's plan, diff, or review responses — the plan quotes the rule or its id, or the diff does specifically what the rule mandates where the default behavior would have differed. **Presence in context is NOT application**: the ledger is loaded eagerly into every session, so counting "it was loaded" would confirm every entry on every claim and defeat decay entirely. A run that produced no plan or diff has nothing to confirm.
260
+ 2. **Bump each applied entry exactly once** via the surgical writer:
261
+
262
+ ```bash
263
+ node -e 'import("@codyswann/lisa/learnings").then(async m => { const r = await m.confirmLearningEntry(process.cwd(), process.argv[1], new Date().toISOString().slice(0, 10)); console.log(JSON.stringify(r)); }).catch(error => { console.log(JSON.stringify({ status: "error", id: process.argv[1], message: String(error) })); })' <entry-id> || true
264
+ ```
265
+
266
+ The invocation is failure-safe by construction: a rejected import or write resolves to a structured `error` result instead of a crash, and the trailing `|| true` absorbs any remaining non-zero exit (missing `node`, unresolvable package). Record an `error` or non-zero outcome in the cycle summary and continue — the bump must never abort the lifecycle.
267
+
268
+ `confirmLearningEntry` advances ONLY `last_confirmed` — rule text, why, provenance, `first_learned`, and confidence are untouched — and is idempotent within a claim: a repeat same-date bump returns `unchanged`, so an entry that applied repeatedly during one claim is bumped once, not once per application.
269
+ 3. **Never block on it.** A failed bump, an unwritable file, or a `not-found` result (the entry was pruned) is recorded under the cycle summary and the claim proceeds — shipping the Issue always outranks confirming a learning about it.
270
+
253
271
  #### 3d. Relabel to $DONE (only after the PR is merged)
254
272
 
255
273
  A `done` env state (`status:on-dev`, `status:on-stg`, or the terminal value) asserts that the code has actually reached that environment. Never set it for a PR that is merely open: auto-merge can be blocked indefinitely (a required rebase / `BEHIND` branch, failing checks, an unaddressed review), and the change may never land. Relabeling an Issue `status:on-stg` on an open PR makes it *claim* a deploy that never happened. Transition only after confirming the PR merged.