@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.
- package/dist/core/learnings-entry.d.ts +9 -0
- package/dist/core/learnings-entry.d.ts.map +1 -1
- package/dist/core/learnings-entry.js +4 -2
- package/dist/core/learnings-entry.js.map +1 -1
- package/dist/core/learnings-writer.d.ts +33 -0
- package/dist/core/learnings-writer.d.ts.map +1 -1
- package/dist/core/learnings-writer.js +75 -1
- package/dist/core/learnings-writer.js.map +1 -1
- package/dist/sync/registry.d.ts.map +1 -1
- package/dist/sync/registry.js +7 -0
- package/dist/sync/registry.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-attribute-failure/agents/openai.yaml +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +28 -77
- package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +18 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +18 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +18 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +61 -8
- package/plugins/lisa/commands/attribute-failure.md +6 -0
- package/plugins/lisa/commands/persist-learning.md +1 -1
- package/plugins/lisa/rules/eager/claim-archaeology.md +37 -0
- package/plugins/lisa/rules/reference/claim-archaeology.md +142 -0
- package/plugins/lisa/rules/reference/project-learnings.md +18 -0
- package/plugins/lisa/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/lisa/skills/lisa-attribute-failure/agents/openai.yaml +4 -0
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +28 -77
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +18 -0
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +18 -0
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +18 -0
- package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +62 -9
- package/plugins/lisa-agy/commands/lisa/attribute-failure.md +6 -0
- package/plugins/lisa-agy/commands/lisa/persist-learning.md +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +28 -77
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +18 -0
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +18 -0
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +18 -0
- package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +62 -9
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/commands/lisa/attribute-failure.md +6 -0
- package/plugins/lisa-copilot/commands/lisa/persist-learning.md +1 -1
- package/plugins/lisa-copilot/rules/eager/claim-archaeology.md +37 -0
- package/plugins/lisa-copilot/rules/reference/claim-archaeology.md +142 -0
- package/plugins/lisa-copilot/rules/reference/project-learnings.md +18 -0
- package/plugins/lisa-copilot/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +28 -77
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +18 -0
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +18 -0
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +18 -0
- package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +62 -9
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/commands/lisa/attribute-failure.md +6 -0
- package/plugins/lisa-cursor/commands/lisa/persist-learning.md +1 -1
- package/plugins/lisa-cursor/rules/claim-archaeology-reference.mdc +147 -0
- package/plugins/lisa-cursor/rules/claim-archaeology.mdc +42 -0
- package/plugins/lisa-cursor/rules/project-learnings-reference.mdc +18 -0
- package/plugins/lisa-cursor/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +28 -77
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +18 -0
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +18 -0
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +18 -0
- package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +62 -9
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/commands/attribute-failure.md +6 -0
- package/plugins/src/base/commands/persist-learning.md +1 -1
- package/plugins/src/base/rules/eager/claim-archaeology.md +37 -0
- package/plugins/src/base/rules/reference/claim-archaeology.md +142 -0
- package/plugins/src/base/rules/reference/project-learnings.md +18 -0
- package/plugins/src/base/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +28 -77
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +18 -0
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +18 -0
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +18 -0
- package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +62 -9
|
@@ -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.
|
|
@@ -62,20 +62,73 @@ The note is one line naming the classification (with its fixed plain-language gl
|
|
|
62
62
|
|----------------|-------|
|
|
63
63
|
| `one-off` | a one-time fluke, not a recurring pattern |
|
|
64
64
|
| `misunderstanding/spec-gap` | traced to an unclear requirement, not a durable lesson |
|
|
65
|
-
| `lisa-upstream` | root cause
|
|
65
|
+
| `lisa-upstream` | root cause suspected in Lisa; routed for upstream attribution |
|
|
66
66
|
|
|
67
|
-
(
|
|
67
|
+
(A `lisa-upstream` classification never produces a drop note — it routes through the `handoff-upstream` flow below, whose step-1 note uses this pre-attribution wording because filing only happens after attribution confirms the Lisa surface.) Dedupe before posting: if any comment on the triggering issue already carries `[lisa-learning-drop] key=<fingerprint>`, do not post again — report the existing note.
|
|
68
68
|
|
|
69
69
|
### `handoff-upstream` (classification `lisa-upstream`)
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
This disposition completes the SLL-5 loop (#1583): on a Lisa-attributed failure the upstream Lisa ticket is filed **automatically**. Filing lives here — not in `lisa-attribute-failure` — because that skill is deliberately read-only (doctor delegates to it inside its own read-only contract), while this skill already owns exactly the verdict's side effects and the marker-dedupe discipline. Never persist a local rule for a Lisa-attributed failure; the host project's only durable trace is the brief linking note in step 6.
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
1. **Post the handoff marker** on the triggering issue (same one-comment marker dedupe; the marker key is unchanged). The visible line must not claim a filing that has not happened yet — attribution and filing come after this step:
|
|
74
|
+
|
|
75
|
+
```markdown
|
|
76
|
+
<!-- [lisa-learning-upstream-handoff] key=<fingerprint> -->
|
|
77
|
+
Candidate routed for upstream attribution (root cause suspected in Lisa): <reason>.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. **Require a confirmed `lisa` verdict from `lisa-attribute-failure` — always.** Run the `lisa-attribute-failure` skill on the failure event before any filing. The judge's `cited_evidence` seeds the event (implicated files, surface in play, failure class) but never substitutes for the verdict — a path or commit reference alone is not attribution. File **only** when the skill returns a conclusive `lisa` verdict that names the Lisa surface with cited evidence. Any other outcome — `ambiguous`, `project`, or a verdict that cannot name a concrete Lisa surface — files **NOTHING** upstream: the candidate stays local and low-confidence, the run summary says attribution was inconclusive, and the step-1 note is resolved with one corrective follow-up comment on the triggering issue (marker-deduped so re-runs never repeat it; the suffix is distinct from the filing-failure marker in step 8 so one outcome never suppresses the other):
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
<!-- [lisa-learning-upstream-handoff] key=<fingerprint>-inconclusive -->
|
|
84
|
+
Attribution was inconclusive — nothing was filed upstream and nothing was persisted locally.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
3. **Derive the root-cause key from the LISA SURFACE, never the host project or the local issue.** Two projects hitting the same Lisa bug MUST collide on the same key — that collision is the design (update, not duplicate):
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
root-cause-key = <lisa-surface>#<failure-class>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- `<lisa-surface>` — the Lisa-relative path of the surface at fault (e.g. `plugins/src/base/skills/lisa-doctor/SKILL.md`, `typescript/copy-overwrite/.github/workflows/quality.yml`), or the canonical rule/skill/hook name when no single file applies (e.g. `lisa-doctor`, `block-no-verify`).
|
|
94
|
+
- `<failure-class>` — a short lowercase hyphen-slug for the class of failure (e.g. `pagination-truncation`, `stale-artifact-overwrite`).
|
|
95
|
+
- Normalize: lowercase, trim, collapse every whitespace run to a single `-`. The key must contain no host-project name, no local issue number, and no fingerprint — those vary per project and would defeat fleet-wide dedupe.
|
|
96
|
+
|
|
97
|
+
4. **Enforce the per-run cap.** Resolve `hardening.maxUpstreamFilingsPerRun` from `.lisa.config.json` (default `5` — a conservative bound modeled on `lisa-repair-intake`'s `max_candidates` precedent). Count every upstream create **and** update this run performs; once the cap is reached, drop the remaining candidates and **note each dropped candidate visibly** (in the run summary, naming its root-cause key) — never queue a spam burst and never drop silently. A later run picks the dropped candidates up idempotently.
|
|
98
|
+
|
|
99
|
+
5. **Evidence redaction (binding).** The upstream repo is PUBLIC by default (`hardening.upstreamRepo` → `CodySwannGT/lisa`) and this filing runs headless on crons — treat every drafted upstream body and comment as world-readable:
|
|
77
100
|
|
|
78
|
-
|
|
101
|
+
- Quote ONLY Lisa-owned surface text: template/rule/skill/hook excerpts and upstream commit references. The reproduction must be REDACTED — generic placeholders, never the host project's real values.
|
|
102
|
+
- Never paste host environment values, tokens/credentials, connection strings, API keys, PII (names, emails, customer data), or proprietary host code/payloads.
|
|
103
|
+
- The evidence chain names the Lisa surface and the failure class — never project payloads. When host context is essential to understand the failure, LINK the host-project issue instead of quoting it.
|
|
104
|
+
- Before filing or commenting, scan the drafted body for common secret shapes — `key=value` pairs with high-entropy values, token prefixes (`AKIA`, `ghp_`, `xox`), email addresses — and strip on match. When in doubt, leave it out: a thinner upstream ticket is recoverable; a leaked secret is not.
|
|
105
|
+
|
|
106
|
+
6. **Dedupe by marker, then file or update.** The upstream marker is:
|
|
107
|
+
|
|
108
|
+
```markdown
|
|
109
|
+
<!-- [lisa-upstream-attribution] key=<root-cause-key> -->
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Resolve the upstream repo from `.lisa.config.json` `hardening.upstreamRepo` (default `CodySwannGT/lisa`). Search **all issue states** for an existing issue carrying the marker — a closed marker-bearing ticket still owns this root cause, and searching only open issues would mint a duplicate the moment the original closes. Match on the **MARKER, never the title** — with the same eventual-consistency guard as above (`gh issue list -R <upstream> --state all --search '"<marker>" in:body' --json number,state,url`, and when the search index returns nothing, also `gh issue list -R <upstream> --state all --json number,state,body` and grep the bodies for the marker before concluding no ticket exists).
|
|
113
|
+
|
|
114
|
+
- **No existing ticket** → file via `lisa-github-write-issue` targeting the upstream repo, following the `lisa-rework-triage` "Filing upstream" discipline: a three-audience description (what failed for the operator, what the harness did wrong, what to change), the redacted evidence chain (Lisa-owned text only, per step 5: defect → Lisa surface → attribution evidence → redacted reproduction), the affected project named, and the `self-hardening` label. The body carries **exactly one** dedupe marker; **never write a markerless body** — it permanently breaks all future dedupe.
|
|
115
|
+
- **Existing ticket (open or closed)** → this is a repeat encounter: comment the new occurrence on the existing issue with this project's evidence, marker-deduped per occurrence via `<!-- [lisa-upstream-attribution-occurrence] key=<fingerprint> -->` so re-runs never duplicate the occurrence comment. Never open a second issue, and never match on the title — evidence compounds on one ticket. When the match is **CLOSED**, still comment the occurrence there and reference it in the local trace instead of filing a duplicate; do not reopen it yourself — recurrence evidence on a closed ticket signals the shipped fix may not cover this case, and reopening is the upstream maintainer's call.
|
|
116
|
+
|
|
117
|
+
7. **Leave the local trace — a note, never a rule.** Post one follow-up comment on the triggering issue linking the upstream ticket:
|
|
118
|
+
|
|
119
|
+
```markdown
|
|
120
|
+
<!-- [lisa-upstream-filed] key=<fingerprint> -->
|
|
121
|
+
Upstream ticket: <url> (root-cause key `<root-cause-key>`). No local rule persisted — the fix ships fleet-wide through Lisa.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The learnings surface gains **no durable local rule** for a Lisa-attributed failure. Agents avoid the trap via the upstream ticket link until the fix ships.
|
|
125
|
+
|
|
126
|
+
8. **Degrade gracefully.** If filing fails (auth, rate limit, network), report the failure in the run summary and continue shipping the host issue — a later run retries idempotently. Never block the primary build flow. So the step-1 note is not left dangling, post a marker-deduped corrective follow-up on the triggering issue — with its own suffix, distinct from step 2's `-inconclusive`, so an earlier inconclusive note can never suppress a filing-failure note (or vice versa):
|
|
127
|
+
|
|
128
|
+
```markdown
|
|
129
|
+
<!-- [lisa-learning-upstream-handoff] key=<fingerprint>-filing-failed -->
|
|
130
|
+
Upstream filing did not complete — nothing was filed upstream and nothing was persisted locally; a later run retries.
|
|
131
|
+
```
|
|
79
132
|
|
|
80
133
|
### `persist` (classification `durable-learning`)
|
|
81
134
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Attribute an arbitrary failure event to Lisa or the project with cited evidence. Evaluates three ordered signals — Lisa-managed surface ownership, shipped rule/skill/hook behavior, and the upstream Lisa change-history window — and returns lisa | project | ambiguous. Read-only; inconclusive cases stay local as ambiguous."
|
|
3
|
+
argument-hint: "<failure event: defect description, implicated files, rule/skill/hook in play>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use the /lisa-attribute-failure skill to attribute the given failure event to Lisa or the project and return the verdict with its cited evidence. $ARGUMENTS
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Route a candidate learning through the hostile-default learning-judge gate and act on the verdict: leave a dropped-with-reason note on the triggering issue (drop),
|
|
2
|
+
description: "Route a candidate learning through the hostile-default learning-judge gate and act on the verdict: leave a dropped-with-reason note on the triggering issue (drop), file the upstream Lisa ticket automatically with marker dedupe, evidence, and a per-run cap (lisa-upstream), or persist a durable learning via a confidence-routed PR that touches only the learnings surface — auto-merge on for high confidence, auto-merge off plus the learning:needs-triage label for low confidence. Idempotent via marker dedupe."
|
|
3
3
|
argument-hint: "<candidate-json-or-fields>"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Claim-Time Archaeology (load-bearing)
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
**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).
|
|
6
|
+
|
|
7
|
+
## When it runs
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
## Classify the claimed item
|
|
12
|
+
|
|
13
|
+
Return exactly one of:
|
|
14
|
+
|
|
15
|
+
- **`rejection-reclaim`** — taken directly from the `rejection-detection` result. Reuse it; do not re-derive.
|
|
16
|
+
- **`retry-of-done-issue`** — an ancestry signal names a closed done issue whose shipped work this issue exists to fix.
|
|
17
|
+
- **`fresh`** — no ancestor found, signals weak/inconclusive, budget exhausted, or the pass errored. The default and the safe degrade.
|
|
18
|
+
|
|
19
|
+
## Ancestry signals (summary — full bindings in the reference body)
|
|
20
|
+
|
|
21
|
+
1. **Tracker metadata** — the typed relations the read skills already parse (Blocks / Blocked by / Relates to / Duplicates / Cloned from, `closingIssuesReferences`, cross-references).
|
|
22
|
+
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.
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
## Learning-loop exclusion (scan-side)
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
## Cost budget — never block the claim
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
## On `retry-of-done-issue`
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
Full contract (signal bindings, classification table, candidate derivation, budget mechanics): [reference/claim-archaeology.md](../reference/claim-archaeology.md).
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Claim-Time Archaeology
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
## Seam and sequencing — after rejection detection, before the claim transition
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
Within `3b`, the pre-transition window runs two passes in a fixed order:
|
|
12
|
+
|
|
13
|
+
1. **`rejection-detection` runs first** (top of `3b`, before the relabel — it needs the current-lane signal that the relabel destroys).
|
|
14
|
+
2. **Archaeology runs second** — after the rejection classification exists, still **before the relabel/transition** `$READY → $CLAIMED`.
|
|
15
|
+
|
|
16
|
+
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.
|
|
17
|
+
|
|
18
|
+
**`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.
|
|
19
|
+
|
|
20
|
+
## Ancestry signals
|
|
21
|
+
|
|
22
|
+
Three signal sources, tried in order of cheapness. Every query counts against the cost budget below.
|
|
23
|
+
|
|
24
|
+
### 1. Tracker metadata (typed relations)
|
|
25
|
+
|
|
26
|
+
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:
|
|
27
|
+
|
|
28
|
+
- 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.
|
|
29
|
+
- 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.
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
### 2. Text similarity (bounded, lexical)
|
|
34
|
+
|
|
35
|
+
**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.
|
|
36
|
+
|
|
37
|
+
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:
|
|
38
|
+
|
|
39
|
+
- **GitHub** — `gh search issues "<key terms>" --repo <org>/<repo> --state closed --sort updated` (and `--label` narrowing where labels overlap).
|
|
40
|
+
- **JIRA** — `lisa-atlassian-access operation: search-issues jql: "project = <P> AND statusCategory = Done AND resolved >= -30d AND text ~ \"<key terms>\" ORDER BY resolved DESC"`.
|
|
41
|
+
- **Linear** — `lisa-linear-access operation: list-issues` filtered to completed state types, matched client-side on title/label overlap.
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
### 3. Git ancestry (deterministic, machine-readable)
|
|
46
|
+
|
|
47
|
+
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**:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git log --follow --format='%H %aI %s' -n 5 -- <file> # last commits touching the file
|
|
51
|
+
git blame -L <range> --line-porcelain <file> # who last shipped the implicated lines
|
|
52
|
+
# The PR that shipped the file. --full-history is required: path-limited git log
|
|
53
|
+
# simplifies away merge commits by default, silently dropping the merge-PR answer.
|
|
54
|
+
# Two --grep patterns (OR'd) cover both merge conventions: classic merge commits
|
|
55
|
+
# ("Merge pull request #<n>") and squash/rebase merges (subject ending "(#<n>)").
|
|
56
|
+
# POSIX BRE only — GNU-only \+ silently matches nothing on BSD/macOS git.
|
|
57
|
+
git log --full-history --grep "Merge pull request #" --grep "(#[0-9][0-9]*)" --format='%H %aI %s' -n 5 -- <file>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
**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.
|
|
63
|
+
|
|
64
|
+
## Learning-loop exclusion (scan-side — a learning artifact is never an ancestor)
|
|
65
|
+
|
|
66
|
+
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.
|
|
67
|
+
|
|
68
|
+
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:
|
|
69
|
+
|
|
70
|
+
- `[lisa-learning-drop]`
|
|
71
|
+
- `[lisa-learning-pr]`
|
|
72
|
+
- `[lisa-learning-upstream-handoff]`
|
|
73
|
+
- `[lisa-rejection-candidate]`
|
|
74
|
+
- `[lisa-archaeology-candidate]` (this rule's own producer tag — archaeology's output must not seed the next claim's input)
|
|
75
|
+
- the `learning:needs-triage` label
|
|
76
|
+
|
|
77
|
+
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").
|
|
78
|
+
|
|
79
|
+
## Classification
|
|
80
|
+
|
|
81
|
+
Exactly one of three states:
|
|
82
|
+
|
|
83
|
+
| Classification | Condition |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `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. |
|
|
86
|
+
| `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. |
|
|
87
|
+
| `fresh` | Everything else: no ancestor, weak/inconclusive signals, budget exhausted, or the pass errored. |
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
## Candidate derivation (`retry-of-done-issue` only)
|
|
92
|
+
|
|
93
|
+
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.
|
|
94
|
+
|
|
95
|
+
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.
|
|
96
|
+
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).
|
|
97
|
+
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]`.
|
|
98
|
+
|
|
99
|
+
### Graceful degrade — `lisa-persist-learning` unavailable
|
|
100
|
+
|
|
101
|
+
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):
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
Recorded a candidate learning from this retry's ancestry (queued for the judgment gate): <one-line candidate rule>.
|
|
105
|
+
<!-- [lisa-archaeology-candidate] key=<issue>::<ancestor> -->
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The marker line is verbatim — the dedupe contract keys on it, not on the prose.
|
|
109
|
+
|
|
110
|
+
### Idempotency — marker dedupe
|
|
111
|
+
|
|
112
|
+
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.
|
|
113
|
+
|
|
114
|
+
### `fresh` produces silence
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
|
|
118
|
+
## Cost budget — enforced here, configured in one place
|
|
119
|
+
|
|
120
|
+
Archaeology is speculative digging on the critical path of every claim. The budget is what makes that safe.
|
|
121
|
+
|
|
122
|
+
- **`archaeology.maxSteps`** — the maximum number of tracker/git queries one archaeology pass may spend, read from `.lisa.config.json`:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
MAX_STEPS=$(jq -r '.archaeology.maxSteps // 8' .lisa.config.json 2>/dev/null || echo 8)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
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.
|
|
129
|
+
- **`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.
|
|
130
|
+
|
|
131
|
+
**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.
|
|
132
|
+
|
|
133
|
+
## Never block the claim
|
|
134
|
+
|
|
135
|
+
The invariant everything above hangs on: **archaeology never blocks the claim**. By construction:
|
|
136
|
+
|
|
137
|
+
- Weak or inconclusive signals → degrade to `fresh`, claim proceeds.
|
|
138
|
+
- Budget exhausted → degrade to `fresh`, claim proceeds.
|
|
139
|
+
- 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.
|
|
140
|
+
- Unreadable ancestor evidence on a genuine retry → no candidate produced, the item is still implemented — degraded, not stopped.
|
|
141
|
+
|
|
142
|
+
Headless-safe throughout: no interactive prompts, safe under intake crons.
|
|
@@ -20,6 +20,24 @@ Each persisted entry has seven fields:
|
|
|
20
20
|
- `last_confirmed`
|
|
21
21
|
- `confidence`
|
|
22
22
|
|
|
23
|
+
## Claim-time confirmation (`last_confirmed`)
|
|
24
|
+
|
|
25
|
+
`last_confirmed` is advanced at claim time by the build-intake flows (step
|
|
26
|
+
3c.2 of `lisa-{jira,github,linear}-build-intake`) when an entry's rule
|
|
27
|
+
**demonstrably applied** during a claim — the rule was explicitly cited or
|
|
28
|
+
observably followed in the claim's plan or diff. Presence in the eagerly
|
|
29
|
+
loaded context is NOT application: every entry is present in every session,
|
|
30
|
+
so counting mere presence would confirm everything on every claim and defeat
|
|
31
|
+
decay entirely.
|
|
32
|
+
|
|
33
|
+
The bump goes only through `confirmLearningEntry` from
|
|
34
|
+
`@codyswann/lisa/learnings`: a surgical, lock-protected, atomic write that
|
|
35
|
+
advances only `last_confirmed` (re-validated against the
|
|
36
|
+
`>= first_learned` invariant), returns a structured no-op for a missing
|
|
37
|
+
entry or file instead of throwing, and is idempotent within a claim (a
|
|
38
|
+
same-date repeat returns `unchanged`). A failed bump is reported and never
|
|
39
|
+
blocks the build.
|
|
40
|
+
|
|
23
41
|
Only entries accepted by the executable contract may influence the session. A
|
|
24
42
|
missing file is expected and silent. Malformed Markdown, invalid JSONL, unsafe
|
|
25
43
|
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.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
display_name: "Attribute Failure"
|
|
2
|
+
short_description: "Event-triggered root-cause attribution for an arbitrary failure: decide, with cited evidence, whether the defect is Lisa's fault or the…"
|
|
3
|
+
default_prompt:
|
|
4
|
+
- "Use $lisa-attribute-failure: Event-triggered root-cause attribution for an arbitrary failure: decide, with cited evidence, whether the defect is Lisa's fault or the…."
|