@codyswann/lisa 4.59.5 → 4.60.1

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 (102) hide show
  1. package/README.md +13 -0
  2. package/dist/cli/index.d.ts.map +1 -1
  3. package/dist/cli/index.js +2 -0
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/cli/setup-project.d.ts +2 -0
  6. package/dist/cli/setup-project.d.ts.map +1 -1
  7. package/dist/cli/setup-project.js +29 -8
  8. package/dist/cli/setup-project.js.map +1 -1
  9. package/dist/cli/starter-cmd.d.ts +21 -0
  10. package/dist/cli/starter-cmd.d.ts.map +1 -0
  11. package/dist/cli/starter-cmd.js +40 -0
  12. package/dist/cli/starter-cmd.js.map +1 -0
  13. package/dist/cli/starter-provenance.d.ts +47 -0
  14. package/dist/cli/starter-provenance.d.ts.map +1 -0
  15. package/dist/cli/starter-provenance.js +112 -0
  16. package/dist/cli/starter-provenance.js.map +1 -0
  17. package/dist/core/learning-fingerprint.d.ts +9 -0
  18. package/dist/core/learning-fingerprint.d.ts.map +1 -0
  19. package/dist/core/learning-fingerprint.js +15 -0
  20. package/dist/core/learning-fingerprint.js.map +1 -0
  21. package/dist/core/learnings-merge-driver.d.ts.map +1 -1
  22. package/dist/core/learnings-merge-driver.js +7 -1
  23. package/dist/core/learnings-merge-driver.js.map +1 -1
  24. package/dist/core/learnings.d.ts +1 -0
  25. package/dist/core/learnings.d.ts.map +1 -1
  26. package/dist/core/learnings.js +1 -0
  27. package/dist/core/learnings.js.map +1 -1
  28. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  29. package/dist/core/project-config-starter.d.ts.map +1 -1
  30. package/dist/core/project-config-starter.js +1 -0
  31. package/dist/core/project-config-starter.js.map +1 -1
  32. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  33. package/dist/core/upstream-evidence-manifest.js +9 -3
  34. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  35. package/package.json +4 -4
  36. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa/.codex-plugin/skills/lisa-debrief-apply/SKILL.md +8 -6
  39. package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +3 -3
  40. package/plugins/lisa/agents/learner.md +3 -1
  41. package/plugins/lisa/skills/lisa-debrief-apply/SKILL.md +8 -6
  42. package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +3 -3
  43. package/plugins/lisa-agy/agents/learner.md +3 -1
  44. package/plugins/lisa-agy/plugin.json +1 -1
  45. package/plugins/lisa-agy/skills/lisa-debrief-apply/SKILL.md +8 -6
  46. package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +3 -3
  47. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  50. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-copilot/agents/learner.agent.md +3 -1
  54. package/plugins/lisa-copilot/skills/lisa-debrief-apply/SKILL.md +8 -6
  55. package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +3 -3
  56. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-cursor/agents/learner.md +3 -1
  58. package/plugins/lisa-cursor/skills/lisa-debrief-apply/SKILL.md +8 -6
  59. package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +3 -3
  60. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-expo-agy/plugin.json +1 -1
  63. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  68. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  72. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  73. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  77. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  78. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  82. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  83. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  87. package/plugins/lisa-rails-agy/plugin.json +1 -1
  88. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  92. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  93. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  97. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  98. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  100. package/plugins/src/base/agents/learner.md +3 -1
  101. package/plugins/src/base/skills/lisa-debrief-apply/SKILL.md +8 -6
  102. package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +3 -3
package/package.json CHANGED
@@ -187,7 +187,7 @@
187
187
  "zod-validation-error": "^4.0.0"
188
188
  },
189
189
  "name": "@codyswann/lisa",
190
- "version": "4.59.5",
190
+ "version": "4.60.1",
191
191
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
192
192
  "main": "dist/index.js",
193
193
  "exports": {
@@ -336,7 +336,7 @@
336
336
  "test": "tests"
337
337
  },
338
338
  "types": "./dist/index.d.ts",
339
- "lisaReleaseCommit": "69c6b4d5bfc6f031457a2ccc3034422fc2c5e734",
340
- "gitHead": "69c6b4d5bfc6f031457a2ccc3034422fc2c5e734",
341
- "lisaReleaseTag": "v4.59.5"
339
+ "lisaReleaseCommit": "ea756778a00e6b989be6623c5dd3a9dd6127f8cd",
340
+ "gitHead": "ea756778a00e6b989be6623c5dd3a9dd6127f8cd",
341
+ "lisaReleaseTag": "v4.60.1"
342
342
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -44,6 +44,8 @@ The three knowledge categories — **recurring gotcha**, **process friction**, a
44
44
 
45
45
  For each such Accepted row:
46
46
 
47
+ Use the branch, PR deduplication, commit, submission and confidence-routing sequence in `lisa-persist-learning` Phase 3. The human's Accepted disposition supplies the high-confidence durable-learning decision here. Before writing, create or reuse the learning branch in an isolated worktree; never write the ledger in the default-branch checkout. Submit a pull request carrying only the resolved ledger or overflow surface. Keep the triage-document update separate. A local write alone is not an applied learning: report the PR and whether it is pending or merged.
48
+
47
49
  1. **Resolve the ledger path (never hardcode).** Use `resolveProjectLearningsFile` from `@codyswann/lisa/learnings` — the `learnings.file` override, else the default `.lisa/PROJECT_LEARNINGS.md` (a cold path, never an auto-loaded rules tree):
48
50
 
49
51
  ```bash
@@ -55,10 +57,10 @@ For each such Accepted row:
55
57
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
56
58
 
57
59
  3. **Entry mapping (eight fields).**
58
- - `fingerprint` — `debrief-` + the first 12 hex characters of `sha1(normalized_rule + "\n" + sorted_provenance.join("\n"))`, where the rule is lowercased with whitespace collapsed and provenance is sorted by codepoint. Compute it mechanically, never estimate it.
60
+ - `fingerprint` — `learningFingerprint(rule)` from `@codyswann/lisa/learnings`, computed from the final consolidated rule. Keep workflow/PR markers separate: use the `lisa-persist-learning` Phase 0 occurrence key with the stable triage-document reference as `triggering_issue`; the rule distinguishes rows sharing that reference.
59
61
  - `id` — initially `id = fingerprint`. On an exact stamped consolidation the writer carries forward the deterministic primary target id; report the returned id in the run summary.
60
62
  - `rule` / `why` — from the row's `Summary` and the category-specific guidance in the routing table above (≤240 chars, ≤2 lines).
61
- - `provenance` — **the triage-doc row's evidence links**. This is the same evidence link that doubles as the idempotency fingerprint below, so the row's provenance is what a later re-apply scans for.
63
+ - `provenance` — **the triage-doc row's evidence links**. Evidence supports a rule; it is not the rule's identity, and one event may support several distinct lessons.
62
64
  - `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`).
63
65
  - `confidence` = **`high`**. A human marking the row **Accept** is corroboration — an independent human judgement that the learning is real — so a debrief-accepted entry starts higher than the learner's single-occurrence auto-capture (which defaults to `low`). A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets; an over-budget failure means consolidate harder or drop, never truncate by hand.
64
66
 
@@ -66,16 +68,16 @@ For each such Accepted row:
66
68
 
67
69
  ## Idempotency
68
70
 
69
- `apply` is safe to re-run. Each Accepted row carries an evidence link that doubles as a fingerprint. Before writing, check whether the destination already cites that fingerprint:
71
+ `apply` is safe to re-run. Compute the final rule's fingerprint before checking its destination:
70
72
 
71
- - **Knowledge categories (gotcha, friction, drift) → ledger.** Parse the ledger once with `parseLearningsFile` from `@codyswann/lisa/learnings` and scan the entries' `provenance` for the row's evidence link. If any entry's provenance already contains it, the row is already persisted — skip the write. This replaces the old scattered-file greps (memory files, host rules, `AGENTS.md`): provenance in the single governed ledger is now the one fingerprint surface.
73
+ - **Knowledge categories (gotcha, friction, drift) → ledger.** Read the default-branch ledger through `parseLearningsFile` from `@codyswann/lisa/learnings` and compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)`. An equal rule there is already captured: preserve its legacy id and fingerprint, report that id, and skip the write. A rule present only on an open PR branch remains pending, not applied; reuse that PR. For changed consolidations use exact parsed legacy stamps in `supersede`; never rewrite them merely to adopt the helper. Distinct rules sharing provenance must both be considered. Also reuse the existing PR when its workflow marker is present, including a PR not yet merged.
72
74
  - **Other categories** keep their existing destination check (the tracker for the ticket marker, the PRD for the defect comment, `intent-routing.md` for the edge-case citation).
73
75
 
74
76
  If the fingerprint is already present, skip the write and note the row as `already-applied` in the run summary. This lets the human triage a doc incrementally (mark a few, run apply, mark more, run apply again) without producing duplicates.
75
77
 
76
78
  ## Updating the triage doc
77
79
 
78
- After each Accepted row is persisted, replace its `[ ] Accept` checkbox with `[x] Applied — <one-line summary of what was written>`. This makes the triage doc itself the audit log of what was acted on. If a write fails (e.g., tracker is unreachable), mark the row `[!] Apply failed — <reason>` and continue with the rest. Never abort the whole run because one row failed.
80
+ After a learning PR merges (or the rule is already in the default-branch ledger), replace the row's `[ ] Accept` checkbox with `[x] Applied — <entry id and PR>`. While the PR is open, leave the row Accepted and record `Pending PR: <url>` so a rerun reuses it. Other destinations retain their successful-write marking. If a write or submission fails, mark the row `[!] Apply failed — <reason>` and continue with the rest.
79
81
 
80
82
  ## Output
81
83
 
@@ -101,4 +103,4 @@ Failed:
101
103
  Triage doc updated in place: <path>
102
104
  ```
103
105
 
104
- If anything is written to a tracker, suggest the human commit the local file changes (the learnings ledger, intent-routing) when ready — `apply` does not commit.
106
+ Report ledger PRs and their pending/merged state in the summary. Other local changes, such as the triage document or intent-routing checklist, remain separate from those PRs and may be committed through the project's normal flow.
@@ -25,7 +25,7 @@ Accept the candidate as JSON or `key=value` fields:
25
25
 
26
26
  ## Phase 0 — Fingerprint (stable dedupe key)
27
27
 
28
- Every marker and branch below keys off one deterministic fingerprint:
28
+ Every workflow marker and branch below keys off this occurrence fingerprint. It is separate from the ledger entry fingerprint, which all writers compute with `learningFingerprint(rule)` from `@codyswann/lisa/learnings`:
29
29
 
30
30
  ```text
31
31
  fingerprint = "sll4-" + first 12 hex chars of sha1(normalized_rule + "\n" + triggering_issue)
@@ -174,10 +174,10 @@ No learning content is ever committed without a PR — there is no other write p
174
174
  LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => { const c = await m.readProjectConfig(process.cwd()); console.log(m.resolveProjectLearningsFile(c)); })')
175
175
  ```
176
176
 
177
- 3. **Consolidation check (mandatory before writing).** Parse the existing entries (`parseLearningsFile` from `@codyswann/lisa/learnings`) and look for entries related to the new rule (same failure class, overlapping topic, or near-duplicate wording). Then write through the executable contract — **never hand-edit the markdown**:
177
+ 3. **Consolidation check (mandatory before writing).** Parse the existing entries (`parseLearningsFile` from `@codyswann/lisa/learnings`). First compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)`: an equal rule is already captured, even if its legacy id or fingerprint has another prefix. Preserve the stored values, report the existing id, and stop without writing that candidate. Shared evidence alone never makes two rules duplicates. Only otherwise look for related entries (same failure class, overlapping topic, or near-duplicate wording) and write through the executable contract — **never hand-edit the markdown**:
178
178
  - **Related entry found** → consolidate with the exact versions returned by the parser: `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`. The writer checks every stamp together inside the lock. If any target is absent or its fingerprint changed, the whole supersede is stale: it removes nothing, safely appends the new fingerprint, and reports the mismatch. Never append a near-duplicate sibling by choice — a sibling is a bug that fails review; the writer's stale append is the deliberate race-safe preservation path.
179
179
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)` and state in the PR body why appending was correct.
180
- - Entry mapping (eight fields): `fingerprint` = the deterministic Phase 0 fingerprint and initial `id = fingerprint`; `rule`/`why`/`provenance` from the candidate; `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`); `confidence` = the judge's `high`/`low`. On an exact stamped consolidation the writer carries forward the deterministic primary target id while persisting the new fingerprint as the version token. A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
180
+ - Entry mapping (eight fields): `fingerprint` = `learningFingerprint(rule)` and initial `id = fingerprint`; `rule`/`why`/`provenance` from the candidate; `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`); `confidence` = the judge's `high`/`low`. On an exact stamped consolidation the writer carries forward the deterministic primary target id while persisting the new fingerprint as the version token. Use exact parsed legacy stamps in `supersede`; never rewrite old ids or fingerprints just to adopt the helper. A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
181
181
  - **On a budget-forced drop, signal saturation once (never silent).** When the writer's budget re-assertion cannot fit the entry even after consolidating harder — a durable capture has to be DROPPED for budget — the ledger is saturated, and that pressure must be visible to an operator instead of swallowed. Emit **exactly one** tracker signal via `lisa-tracker-write` (`issue_type: Task`; GitHub trackers carry the `type:Task` label), then continue — dropping the capture never blocks the build. Follow the same marker-dedupe discipline as every other comment here (match on the **marker, never the title**; **exactly one marker per body**; the eventual-consistency guard when the search index is stale), with **one deliberate difference: dedupe against OPEN signals only.** A *closed* saturation ticket means room was already reclaimed, so a fresh saturation is a new actionable event — searching closed tickets too (as the drop/upstream markers do) would permanently suppress every later saturation.
182
182
 
183
183
  The saturation fingerprint keys on the **ledger, not the candidate**, so the signal fires once per saturation episode — never once per capture:
@@ -26,7 +26,7 @@ If no learnings exist, report "No learnings to process" and complete.
26
26
 
27
27
  For each `mistake`/`learning` candidate, build the ledger entry the executable contract validates. The `LEARNINGS_CONTRACT` caps apply — an over-cap entry cannot persist; tighten it or drop it, never truncate by hand:
28
28
 
29
- - `fingerprint` — the deterministic content-version token: `learner-` + the first 12 hex chars of `sha1(normalized_rule)`. Recompute it from the fully consolidated rule; never estimate it.
29
+ - `fingerprint` — `learningFingerprint(rule)` from `@codyswann/lisa/learnings`, computed from the final consolidated rule. Every ledger writer uses this rule-only identity; evidence links and workflow markers are separate.
30
30
  - `id` — set `id = fingerprint` for a new capture. On an exact stamped consolidation the writer carries forward the deterministic primary target id, while `fingerprint` changes with the new content.
31
31
  - `rule` — the actionable learning, **≤ 240 characters and ≤ 2 lines** per `LEARNINGS_CONTRACT`.
32
32
  - `why` — the causal claim (why the rule holds).
@@ -47,6 +47,8 @@ LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => {
47
47
 
48
48
  For each candidate entry:
49
49
 
50
+ Compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)` before writing. An equal rule is already captured, even if its legacy id or fingerprint uses another prefix; preserve those stored values and report the existing id. Distinct rules may cite the same evidence. For a changed consolidation, keep the exact parsed legacy stamps in `supersede`; never migrate ids or fingerprints merely to adopt the helper.
51
+
50
52
  1. **Consolidation check (mandatory before writing).** Parse existing entries with `parseLearningsFile` from `@codyswann/lisa/learnings` and look for a related entry — same failure class, overlapping topic, or near-duplicate wording.
51
53
  - **Related entry found** → consolidate, do not sibling. Copy each target's exact parsed version stamp and write via `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`, merging the still-true content of the superseded entry into the new rule and keeping the earliest `first_learned`. Stamps are checked together inside the lock: if any is stale, the writer removes none, safely appends the new fingerprint, and reports the mismatch. A near-duplicate sibling is a bug, not an entry.
52
54
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
@@ -44,6 +44,8 @@ The three knowledge categories — **recurring gotcha**, **process friction**, a
44
44
 
45
45
  For each such Accepted row:
46
46
 
47
+ Use the branch, PR deduplication, commit, submission and confidence-routing sequence in `lisa-persist-learning` Phase 3. The human's Accepted disposition supplies the high-confidence durable-learning decision here. Before writing, create or reuse the learning branch in an isolated worktree; never write the ledger in the default-branch checkout. Submit a pull request carrying only the resolved ledger or overflow surface. Keep the triage-document update separate. A local write alone is not an applied learning: report the PR and whether it is pending or merged.
48
+
47
49
  1. **Resolve the ledger path (never hardcode).** Use `resolveProjectLearningsFile` from `@codyswann/lisa/learnings` — the `learnings.file` override, else the default `.lisa/PROJECT_LEARNINGS.md` (a cold path, never an auto-loaded rules tree):
48
50
 
49
51
  ```bash
@@ -55,10 +57,10 @@ For each such Accepted row:
55
57
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
56
58
 
57
59
  3. **Entry mapping (eight fields).**
58
- - `fingerprint` — `debrief-` + the first 12 hex characters of `sha1(normalized_rule + "\n" + sorted_provenance.join("\n"))`, where the rule is lowercased with whitespace collapsed and provenance is sorted by codepoint. Compute it mechanically, never estimate it.
60
+ - `fingerprint` — `learningFingerprint(rule)` from `@codyswann/lisa/learnings`, computed from the final consolidated rule. Keep workflow/PR markers separate: use the `lisa-persist-learning` Phase 0 occurrence key with the stable triage-document reference as `triggering_issue`; the rule distinguishes rows sharing that reference.
59
61
  - `id` — initially `id = fingerprint`. On an exact stamped consolidation the writer carries forward the deterministic primary target id; report the returned id in the run summary.
60
62
  - `rule` / `why` — from the row's `Summary` and the category-specific guidance in the routing table above (≤240 chars, ≤2 lines).
61
- - `provenance` — **the triage-doc row's evidence links**. This is the same evidence link that doubles as the idempotency fingerprint below, so the row's provenance is what a later re-apply scans for.
63
+ - `provenance` — **the triage-doc row's evidence links**. Evidence supports a rule; it is not the rule's identity, and one event may support several distinct lessons.
62
64
  - `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`).
63
65
  - `confidence` = **`high`**. A human marking the row **Accept** is corroboration — an independent human judgement that the learning is real — so a debrief-accepted entry starts higher than the learner's single-occurrence auto-capture (which defaults to `low`). A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets; an over-budget failure means consolidate harder or drop, never truncate by hand.
64
66
 
@@ -66,16 +68,16 @@ For each such Accepted row:
66
68
 
67
69
  ## Idempotency
68
70
 
69
- `apply` is safe to re-run. Each Accepted row carries an evidence link that doubles as a fingerprint. Before writing, check whether the destination already cites that fingerprint:
71
+ `apply` is safe to re-run. Compute the final rule's fingerprint before checking its destination:
70
72
 
71
- - **Knowledge categories (gotcha, friction, drift) → ledger.** Parse the ledger once with `parseLearningsFile` from `@codyswann/lisa/learnings` and scan the entries' `provenance` for the row's evidence link. If any entry's provenance already contains it, the row is already persisted — skip the write. This replaces the old scattered-file greps (memory files, host rules, `AGENTS.md`): provenance in the single governed ledger is now the one fingerprint surface.
73
+ - **Knowledge categories (gotcha, friction, drift) → ledger.** Read the default-branch ledger through `parseLearningsFile` from `@codyswann/lisa/learnings` and compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)`. An equal rule there is already captured: preserve its legacy id and fingerprint, report that id, and skip the write. A rule present only on an open PR branch remains pending, not applied; reuse that PR. For changed consolidations use exact parsed legacy stamps in `supersede`; never rewrite them merely to adopt the helper. Distinct rules sharing provenance must both be considered. Also reuse the existing PR when its workflow marker is present, including a PR not yet merged.
72
74
  - **Other categories** keep their existing destination check (the tracker for the ticket marker, the PRD for the defect comment, `intent-routing.md` for the edge-case citation).
73
75
 
74
76
  If the fingerprint is already present, skip the write and note the row as `already-applied` in the run summary. This lets the human triage a doc incrementally (mark a few, run apply, mark more, run apply again) without producing duplicates.
75
77
 
76
78
  ## Updating the triage doc
77
79
 
78
- After each Accepted row is persisted, replace its `[ ] Accept` checkbox with `[x] Applied — <one-line summary of what was written>`. This makes the triage doc itself the audit log of what was acted on. If a write fails (e.g., tracker is unreachable), mark the row `[!] Apply failed — <reason>` and continue with the rest. Never abort the whole run because one row failed.
80
+ After a learning PR merges (or the rule is already in the default-branch ledger), replace the row's `[ ] Accept` checkbox with `[x] Applied — <entry id and PR>`. While the PR is open, leave the row Accepted and record `Pending PR: <url>` so a rerun reuses it. Other destinations retain their successful-write marking. If a write or submission fails, mark the row `[!] Apply failed — <reason>` and continue with the rest.
79
81
 
80
82
  ## Output
81
83
 
@@ -101,4 +103,4 @@ Failed:
101
103
  Triage doc updated in place: <path>
102
104
  ```
103
105
 
104
- If anything is written to a tracker, suggest the human commit the local file changes (the learnings ledger, intent-routing) when ready — `apply` does not commit.
106
+ Report ledger PRs and their pending/merged state in the summary. Other local changes, such as the triage document or intent-routing checklist, remain separate from those PRs and may be committed through the project's normal flow.
@@ -25,7 +25,7 @@ Accept the candidate as JSON or `key=value` fields:
25
25
 
26
26
  ## Phase 0 — Fingerprint (stable dedupe key)
27
27
 
28
- Every marker and branch below keys off one deterministic fingerprint:
28
+ Every workflow marker and branch below keys off this occurrence fingerprint. It is separate from the ledger entry fingerprint, which all writers compute with `learningFingerprint(rule)` from `@codyswann/lisa/learnings`:
29
29
 
30
30
  ```text
31
31
  fingerprint = "sll4-" + first 12 hex chars of sha1(normalized_rule + "\n" + triggering_issue)
@@ -174,10 +174,10 @@ No learning content is ever committed without a PR — there is no other write p
174
174
  LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => { const c = await m.readProjectConfig(process.cwd()); console.log(m.resolveProjectLearningsFile(c)); })')
175
175
  ```
176
176
 
177
- 3. **Consolidation check (mandatory before writing).** Parse the existing entries (`parseLearningsFile` from `@codyswann/lisa/learnings`) and look for entries related to the new rule (same failure class, overlapping topic, or near-duplicate wording). Then write through the executable contract — **never hand-edit the markdown**:
177
+ 3. **Consolidation check (mandatory before writing).** Parse the existing entries (`parseLearningsFile` from `@codyswann/lisa/learnings`). First compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)`: an equal rule is already captured, even if its legacy id or fingerprint has another prefix. Preserve the stored values, report the existing id, and stop without writing that candidate. Shared evidence alone never makes two rules duplicates. Only otherwise look for related entries (same failure class, overlapping topic, or near-duplicate wording) and write through the executable contract — **never hand-edit the markdown**:
178
178
  - **Related entry found** → consolidate with the exact versions returned by the parser: `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`. The writer checks every stamp together inside the lock. If any target is absent or its fingerprint changed, the whole supersede is stale: it removes nothing, safely appends the new fingerprint, and reports the mismatch. Never append a near-duplicate sibling by choice — a sibling is a bug that fails review; the writer's stale append is the deliberate race-safe preservation path.
179
179
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)` and state in the PR body why appending was correct.
180
- - Entry mapping (eight fields): `fingerprint` = the deterministic Phase 0 fingerprint and initial `id = fingerprint`; `rule`/`why`/`provenance` from the candidate; `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`); `confidence` = the judge's `high`/`low`. On an exact stamped consolidation the writer carries forward the deterministic primary target id while persisting the new fingerprint as the version token. A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
180
+ - Entry mapping (eight fields): `fingerprint` = `learningFingerprint(rule)` and initial `id = fingerprint`; `rule`/`why`/`provenance` from the candidate; `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`); `confidence` = the judge's `high`/`low`. On an exact stamped consolidation the writer carries forward the deterministic primary target id while persisting the new fingerprint as the version token. Use exact parsed legacy stamps in `supersede`; never rewrite old ids or fingerprints just to adopt the helper. A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
181
181
  - **On a budget-forced drop, signal saturation once (never silent).** When the writer's budget re-assertion cannot fit the entry even after consolidating harder — a durable capture has to be DROPPED for budget — the ledger is saturated, and that pressure must be visible to an operator instead of swallowed. Emit **exactly one** tracker signal via `lisa-tracker-write` (`issue_type: Task`; GitHub trackers carry the `type:Task` label), then continue — dropping the capture never blocks the build. Follow the same marker-dedupe discipline as every other comment here (match on the **marker, never the title**; **exactly one marker per body**; the eventual-consistency guard when the search index is stale), with **one deliberate difference: dedupe against OPEN signals only.** A *closed* saturation ticket means room was already reclaimed, so a fresh saturation is a new actionable event — searching closed tickets too (as the drop/upstream markers do) would permanently suppress every later saturation.
182
182
 
183
183
  The saturation fingerprint keys on the **ledger, not the candidate**, so the signal fires once per saturation episode — never once per capture:
@@ -26,7 +26,7 @@ If no learnings exist, report "No learnings to process" and complete.
26
26
 
27
27
  For each `mistake`/`learning` candidate, build the ledger entry the executable contract validates. The `LEARNINGS_CONTRACT` caps apply — an over-cap entry cannot persist; tighten it or drop it, never truncate by hand:
28
28
 
29
- - `fingerprint` — the deterministic content-version token: `learner-` + the first 12 hex chars of `sha1(normalized_rule)`. Recompute it from the fully consolidated rule; never estimate it.
29
+ - `fingerprint` — `learningFingerprint(rule)` from `@codyswann/lisa/learnings`, computed from the final consolidated rule. Every ledger writer uses this rule-only identity; evidence links and workflow markers are separate.
30
30
  - `id` — set `id = fingerprint` for a new capture. On an exact stamped consolidation the writer carries forward the deterministic primary target id, while `fingerprint` changes with the new content.
31
31
  - `rule` — the actionable learning, **≤ 240 characters and ≤ 2 lines** per `LEARNINGS_CONTRACT`.
32
32
  - `why` — the causal claim (why the rule holds).
@@ -47,6 +47,8 @@ LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => {
47
47
 
48
48
  For each candidate entry:
49
49
 
50
+ Compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)` before writing. An equal rule is already captured, even if its legacy id or fingerprint uses another prefix; preserve those stored values and report the existing id. Distinct rules may cite the same evidence. For a changed consolidation, keep the exact parsed legacy stamps in `supersede`; never migrate ids or fingerprints merely to adopt the helper.
51
+
50
52
  1. **Consolidation check (mandatory before writing).** Parse existing entries with `parseLearningsFile` from `@codyswann/lisa/learnings` and look for a related entry — same failure class, overlapping topic, or near-duplicate wording.
51
53
  - **Related entry found** → consolidate, do not sibling. Copy each target's exact parsed version stamp and write via `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`, merging the still-true content of the superseded entry into the new rule and keeping the earliest `first_learned`. Stamps are checked together inside the lock: if any is stale, the writer removes none, safely appends the new fingerprint, and reports the mismatch. A near-duplicate sibling is a bug, not an entry.
52
54
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -44,6 +44,8 @@ The three knowledge categories — **recurring gotcha**, **process friction**, a
44
44
 
45
45
  For each such Accepted row:
46
46
 
47
+ Use the branch, PR deduplication, commit, submission and confidence-routing sequence in `lisa-persist-learning` Phase 3. The human's Accepted disposition supplies the high-confidence durable-learning decision here. Before writing, create or reuse the learning branch in an isolated worktree; never write the ledger in the default-branch checkout. Submit a pull request carrying only the resolved ledger or overflow surface. Keep the triage-document update separate. A local write alone is not an applied learning: report the PR and whether it is pending or merged.
48
+
47
49
  1. **Resolve the ledger path (never hardcode).** Use `resolveProjectLearningsFile` from `@codyswann/lisa/learnings` — the `learnings.file` override, else the default `.lisa/PROJECT_LEARNINGS.md` (a cold path, never an auto-loaded rules tree):
48
50
 
49
51
  ```bash
@@ -55,10 +57,10 @@ For each such Accepted row:
55
57
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
56
58
 
57
59
  3. **Entry mapping (eight fields).**
58
- - `fingerprint` — `debrief-` + the first 12 hex characters of `sha1(normalized_rule + "\n" + sorted_provenance.join("\n"))`, where the rule is lowercased with whitespace collapsed and provenance is sorted by codepoint. Compute it mechanically, never estimate it.
60
+ - `fingerprint` — `learningFingerprint(rule)` from `@codyswann/lisa/learnings`, computed from the final consolidated rule. Keep workflow/PR markers separate: use the `lisa-persist-learning` Phase 0 occurrence key with the stable triage-document reference as `triggering_issue`; the rule distinguishes rows sharing that reference.
59
61
  - `id` — initially `id = fingerprint`. On an exact stamped consolidation the writer carries forward the deterministic primary target id; report the returned id in the run summary.
60
62
  - `rule` / `why` — from the row's `Summary` and the category-specific guidance in the routing table above (≤240 chars, ≤2 lines).
61
- - `provenance` — **the triage-doc row's evidence links**. This is the same evidence link that doubles as the idempotency fingerprint below, so the row's provenance is what a later re-apply scans for.
63
+ - `provenance` — **the triage-doc row's evidence links**. Evidence supports a rule; it is not the rule's identity, and one event may support several distinct lessons.
62
64
  - `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`).
63
65
  - `confidence` = **`high`**. A human marking the row **Accept** is corroboration — an independent human judgement that the learning is real — so a debrief-accepted entry starts higher than the learner's single-occurrence auto-capture (which defaults to `low`). A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets; an over-budget failure means consolidate harder or drop, never truncate by hand.
64
66
 
@@ -66,16 +68,16 @@ For each such Accepted row:
66
68
 
67
69
  ## Idempotency
68
70
 
69
- `apply` is safe to re-run. Each Accepted row carries an evidence link that doubles as a fingerprint. Before writing, check whether the destination already cites that fingerprint:
71
+ `apply` is safe to re-run. Compute the final rule's fingerprint before checking its destination:
70
72
 
71
- - **Knowledge categories (gotcha, friction, drift) → ledger.** Parse the ledger once with `parseLearningsFile` from `@codyswann/lisa/learnings` and scan the entries' `provenance` for the row's evidence link. If any entry's provenance already contains it, the row is already persisted — skip the write. This replaces the old scattered-file greps (memory files, host rules, `AGENTS.md`): provenance in the single governed ledger is now the one fingerprint surface.
73
+ - **Knowledge categories (gotcha, friction, drift) → ledger.** Read the default-branch ledger through `parseLearningsFile` from `@codyswann/lisa/learnings` and compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)`. An equal rule there is already captured: preserve its legacy id and fingerprint, report that id, and skip the write. A rule present only on an open PR branch remains pending, not applied; reuse that PR. For changed consolidations use exact parsed legacy stamps in `supersede`; never rewrite them merely to adopt the helper. Distinct rules sharing provenance must both be considered. Also reuse the existing PR when its workflow marker is present, including a PR not yet merged.
72
74
  - **Other categories** keep their existing destination check (the tracker for the ticket marker, the PRD for the defect comment, `intent-routing.md` for the edge-case citation).
73
75
 
74
76
  If the fingerprint is already present, skip the write and note the row as `already-applied` in the run summary. This lets the human triage a doc incrementally (mark a few, run apply, mark more, run apply again) without producing duplicates.
75
77
 
76
78
  ## Updating the triage doc
77
79
 
78
- After each Accepted row is persisted, replace its `[ ] Accept` checkbox with `[x] Applied — <one-line summary of what was written>`. This makes the triage doc itself the audit log of what was acted on. If a write fails (e.g., tracker is unreachable), mark the row `[!] Apply failed — <reason>` and continue with the rest. Never abort the whole run because one row failed.
80
+ After a learning PR merges (or the rule is already in the default-branch ledger), replace the row's `[ ] Accept` checkbox with `[x] Applied — <entry id and PR>`. While the PR is open, leave the row Accepted and record `Pending PR: <url>` so a rerun reuses it. Other destinations retain their successful-write marking. If a write or submission fails, mark the row `[!] Apply failed — <reason>` and continue with the rest.
79
81
 
80
82
  ## Output
81
83
 
@@ -101,4 +103,4 @@ Failed:
101
103
  Triage doc updated in place: <path>
102
104
  ```
103
105
 
104
- If anything is written to a tracker, suggest the human commit the local file changes (the learnings ledger, intent-routing) when ready — `apply` does not commit.
106
+ Report ledger PRs and their pending/merged state in the summary. Other local changes, such as the triage document or intent-routing checklist, remain separate from those PRs and may be committed through the project's normal flow.
@@ -25,7 +25,7 @@ Accept the candidate as JSON or `key=value` fields:
25
25
 
26
26
  ## Phase 0 — Fingerprint (stable dedupe key)
27
27
 
28
- Every marker and branch below keys off one deterministic fingerprint:
28
+ Every workflow marker and branch below keys off this occurrence fingerprint. It is separate from the ledger entry fingerprint, which all writers compute with `learningFingerprint(rule)` from `@codyswann/lisa/learnings`:
29
29
 
30
30
  ```text
31
31
  fingerprint = "sll4-" + first 12 hex chars of sha1(normalized_rule + "\n" + triggering_issue)
@@ -174,10 +174,10 @@ No learning content is ever committed without a PR — there is no other write p
174
174
  LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => { const c = await m.readProjectConfig(process.cwd()); console.log(m.resolveProjectLearningsFile(c)); })')
175
175
  ```
176
176
 
177
- 3. **Consolidation check (mandatory before writing).** Parse the existing entries (`parseLearningsFile` from `@codyswann/lisa/learnings`) and look for entries related to the new rule (same failure class, overlapping topic, or near-duplicate wording). Then write through the executable contract — **never hand-edit the markdown**:
177
+ 3. **Consolidation check (mandatory before writing).** Parse the existing entries (`parseLearningsFile` from `@codyswann/lisa/learnings`). First compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)`: an equal rule is already captured, even if its legacy id or fingerprint has another prefix. Preserve the stored values, report the existing id, and stop without writing that candidate. Shared evidence alone never makes two rules duplicates. Only otherwise look for related entries (same failure class, overlapping topic, or near-duplicate wording) and write through the executable contract — **never hand-edit the markdown**:
178
178
  - **Related entry found** → consolidate with the exact versions returned by the parser: `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`. The writer checks every stamp together inside the lock. If any target is absent or its fingerprint changed, the whole supersede is stale: it removes nothing, safely appends the new fingerprint, and reports the mismatch. Never append a near-duplicate sibling by choice — a sibling is a bug that fails review; the writer's stale append is the deliberate race-safe preservation path.
179
179
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)` and state in the PR body why appending was correct.
180
- - Entry mapping (eight fields): `fingerprint` = the deterministic Phase 0 fingerprint and initial `id = fingerprint`; `rule`/`why`/`provenance` from the candidate; `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`); `confidence` = the judge's `high`/`low`. On an exact stamped consolidation the writer carries forward the deterministic primary target id while persisting the new fingerprint as the version token. A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
180
+ - Entry mapping (eight fields): `fingerprint` = `learningFingerprint(rule)` and initial `id = fingerprint`; `rule`/`why`/`provenance` from the candidate; `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`); `confidence` = the judge's `high`/`low`. On an exact stamped consolidation the writer carries forward the deterministic primary target id while persisting the new fingerprint as the version token. Use exact parsed legacy stamps in `supersede`; never rewrite old ids or fingerprints just to adopt the helper. A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
181
181
  - **On a budget-forced drop, signal saturation once (never silent).** When the writer's budget re-assertion cannot fit the entry even after consolidating harder — a durable capture has to be DROPPED for budget — the ledger is saturated, and that pressure must be visible to an operator instead of swallowed. Emit **exactly one** tracker signal via `lisa-tracker-write` (`issue_type: Task`; GitHub trackers carry the `type:Task` label), then continue — dropping the capture never blocks the build. Follow the same marker-dedupe discipline as every other comment here (match on the **marker, never the title**; **exactly one marker per body**; the eventual-consistency guard when the search index is stale), with **one deliberate difference: dedupe against OPEN signals only.** A *closed* saturation ticket means room was already reclaimed, so a fresh saturation is a new actionable event — searching closed tickets too (as the drop/upstream markers do) would permanently suppress every later saturation.
182
182
 
183
183
  The saturation fingerprint keys on the **ledger, not the candidate**, so the signal fires once per saturation episode — never once per capture:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -26,7 +26,7 @@ If no learnings exist, report "No learnings to process" and complete.
26
26
 
27
27
  For each `mistake`/`learning` candidate, build the ledger entry the executable contract validates. The `LEARNINGS_CONTRACT` caps apply — an over-cap entry cannot persist; tighten it or drop it, never truncate by hand:
28
28
 
29
- - `fingerprint` — the deterministic content-version token: `learner-` + the first 12 hex chars of `sha1(normalized_rule)`. Recompute it from the fully consolidated rule; never estimate it.
29
+ - `fingerprint` — `learningFingerprint(rule)` from `@codyswann/lisa/learnings`, computed from the final consolidated rule. Every ledger writer uses this rule-only identity; evidence links and workflow markers are separate.
30
30
  - `id` — set `id = fingerprint` for a new capture. On an exact stamped consolidation the writer carries forward the deterministic primary target id, while `fingerprint` changes with the new content.
31
31
  - `rule` — the actionable learning, **≤ 240 characters and ≤ 2 lines** per `LEARNINGS_CONTRACT`.
32
32
  - `why` — the causal claim (why the rule holds).
@@ -47,6 +47,8 @@ LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => {
47
47
 
48
48
  For each candidate entry:
49
49
 
50
+ Compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)` before writing. An equal rule is already captured, even if its legacy id or fingerprint uses another prefix; preserve those stored values and report the existing id. Distinct rules may cite the same evidence. For a changed consolidation, keep the exact parsed legacy stamps in `supersede`; never migrate ids or fingerprints merely to adopt the helper.
51
+
50
52
  1. **Consolidation check (mandatory before writing).** Parse existing entries with `parseLearningsFile` from `@codyswann/lisa/learnings` and look for a related entry — same failure class, overlapping topic, or near-duplicate wording.
51
53
  - **Related entry found** → consolidate, do not sibling. Copy each target's exact parsed version stamp and write via `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`, merging the still-true content of the superseded entry into the new rule and keeping the earliest `first_learned`. Stamps are checked together inside the lock: if any is stale, the writer removes none, safely appends the new fingerprint, and reports the mismatch. A near-duplicate sibling is a bug, not an entry.
52
54
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
@@ -44,6 +44,8 @@ The three knowledge categories — **recurring gotcha**, **process friction**, a
44
44
 
45
45
  For each such Accepted row:
46
46
 
47
+ Use the branch, PR deduplication, commit, submission and confidence-routing sequence in `lisa-persist-learning` Phase 3. The human's Accepted disposition supplies the high-confidence durable-learning decision here. Before writing, create or reuse the learning branch in an isolated worktree; never write the ledger in the default-branch checkout. Submit a pull request carrying only the resolved ledger or overflow surface. Keep the triage-document update separate. A local write alone is not an applied learning: report the PR and whether it is pending or merged.
48
+
47
49
  1. **Resolve the ledger path (never hardcode).** Use `resolveProjectLearningsFile` from `@codyswann/lisa/learnings` — the `learnings.file` override, else the default `.lisa/PROJECT_LEARNINGS.md` (a cold path, never an auto-loaded rules tree):
48
50
 
49
51
  ```bash
@@ -55,10 +57,10 @@ For each such Accepted row:
55
57
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
56
58
 
57
59
  3. **Entry mapping (eight fields).**
58
- - `fingerprint` — `debrief-` + the first 12 hex characters of `sha1(normalized_rule + "\n" + sorted_provenance.join("\n"))`, where the rule is lowercased with whitespace collapsed and provenance is sorted by codepoint. Compute it mechanically, never estimate it.
60
+ - `fingerprint` — `learningFingerprint(rule)` from `@codyswann/lisa/learnings`, computed from the final consolidated rule. Keep workflow/PR markers separate: use the `lisa-persist-learning` Phase 0 occurrence key with the stable triage-document reference as `triggering_issue`; the rule distinguishes rows sharing that reference.
59
61
  - `id` — initially `id = fingerprint`. On an exact stamped consolidation the writer carries forward the deterministic primary target id; report the returned id in the run summary.
60
62
  - `rule` / `why` — from the row's `Summary` and the category-specific guidance in the routing table above (≤240 chars, ≤2 lines).
61
- - `provenance` — **the triage-doc row's evidence links**. This is the same evidence link that doubles as the idempotency fingerprint below, so the row's provenance is what a later re-apply scans for.
63
+ - `provenance` — **the triage-doc row's evidence links**. Evidence supports a rule; it is not the rule's identity, and one event may support several distinct lessons.
62
64
  - `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`).
63
65
  - `confidence` = **`high`**. A human marking the row **Accept** is corroboration — an independent human judgement that the learning is real — so a debrief-accepted entry starts higher than the learner's single-occurrence auto-capture (which defaults to `low`). A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets; an over-budget failure means consolidate harder or drop, never truncate by hand.
64
66
 
@@ -66,16 +68,16 @@ For each such Accepted row:
66
68
 
67
69
  ## Idempotency
68
70
 
69
- `apply` is safe to re-run. Each Accepted row carries an evidence link that doubles as a fingerprint. Before writing, check whether the destination already cites that fingerprint:
71
+ `apply` is safe to re-run. Compute the final rule's fingerprint before checking its destination:
70
72
 
71
- - **Knowledge categories (gotcha, friction, drift) → ledger.** Parse the ledger once with `parseLearningsFile` from `@codyswann/lisa/learnings` and scan the entries' `provenance` for the row's evidence link. If any entry's provenance already contains it, the row is already persisted — skip the write. This replaces the old scattered-file greps (memory files, host rules, `AGENTS.md`): provenance in the single governed ledger is now the one fingerprint surface.
73
+ - **Knowledge categories (gotcha, friction, drift) → ledger.** Read the default-branch ledger through `parseLearningsFile` from `@codyswann/lisa/learnings` and compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)`. An equal rule there is already captured: preserve its legacy id and fingerprint, report that id, and skip the write. A rule present only on an open PR branch remains pending, not applied; reuse that PR. For changed consolidations use exact parsed legacy stamps in `supersede`; never rewrite them merely to adopt the helper. Distinct rules sharing provenance must both be considered. Also reuse the existing PR when its workflow marker is present, including a PR not yet merged.
72
74
  - **Other categories** keep their existing destination check (the tracker for the ticket marker, the PRD for the defect comment, `intent-routing.md` for the edge-case citation).
73
75
 
74
76
  If the fingerprint is already present, skip the write and note the row as `already-applied` in the run summary. This lets the human triage a doc incrementally (mark a few, run apply, mark more, run apply again) without producing duplicates.
75
77
 
76
78
  ## Updating the triage doc
77
79
 
78
- After each Accepted row is persisted, replace its `[ ] Accept` checkbox with `[x] Applied — <one-line summary of what was written>`. This makes the triage doc itself the audit log of what was acted on. If a write fails (e.g., tracker is unreachable), mark the row `[!] Apply failed — <reason>` and continue with the rest. Never abort the whole run because one row failed.
80
+ After a learning PR merges (or the rule is already in the default-branch ledger), replace the row's `[ ] Accept` checkbox with `[x] Applied — <entry id and PR>`. While the PR is open, leave the row Accepted and record `Pending PR: <url>` so a rerun reuses it. Other destinations retain their successful-write marking. If a write or submission fails, mark the row `[!] Apply failed — <reason>` and continue with the rest.
79
81
 
80
82
  ## Output
81
83
 
@@ -101,4 +103,4 @@ Failed:
101
103
  Triage doc updated in place: <path>
102
104
  ```
103
105
 
104
- If anything is written to a tracker, suggest the human commit the local file changes (the learnings ledger, intent-routing) when ready — `apply` does not commit.
106
+ Report ledger PRs and their pending/merged state in the summary. Other local changes, such as the triage document or intent-routing checklist, remain separate from those PRs and may be committed through the project's normal flow.
@@ -25,7 +25,7 @@ Accept the candidate as JSON or `key=value` fields:
25
25
 
26
26
  ## Phase 0 — Fingerprint (stable dedupe key)
27
27
 
28
- Every marker and branch below keys off one deterministic fingerprint:
28
+ Every workflow marker and branch below keys off this occurrence fingerprint. It is separate from the ledger entry fingerprint, which all writers compute with `learningFingerprint(rule)` from `@codyswann/lisa/learnings`:
29
29
 
30
30
  ```text
31
31
  fingerprint = "sll4-" + first 12 hex chars of sha1(normalized_rule + "\n" + triggering_issue)
@@ -174,10 +174,10 @@ No learning content is ever committed without a PR — there is no other write p
174
174
  LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => { const c = await m.readProjectConfig(process.cwd()); console.log(m.resolveProjectLearningsFile(c)); })')
175
175
  ```
176
176
 
177
- 3. **Consolidation check (mandatory before writing).** Parse the existing entries (`parseLearningsFile` from `@codyswann/lisa/learnings`) and look for entries related to the new rule (same failure class, overlapping topic, or near-duplicate wording). Then write through the executable contract — **never hand-edit the markdown**:
177
+ 3. **Consolidation check (mandatory before writing).** Parse the existing entries (`parseLearningsFile` from `@codyswann/lisa/learnings`). First compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)`: an equal rule is already captured, even if its legacy id or fingerprint has another prefix. Preserve the stored values, report the existing id, and stop without writing that candidate. Shared evidence alone never makes two rules duplicates. Only otherwise look for related entries (same failure class, overlapping topic, or near-duplicate wording) and write through the executable contract — **never hand-edit the markdown**:
178
178
  - **Related entry found** → consolidate with the exact versions returned by the parser: `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`. The writer checks every stamp together inside the lock. If any target is absent or its fingerprint changed, the whole supersede is stale: it removes nothing, safely appends the new fingerprint, and reports the mismatch. Never append a near-duplicate sibling by choice — a sibling is a bug that fails review; the writer's stale append is the deliberate race-safe preservation path.
179
179
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)` and state in the PR body why appending was correct.
180
- - Entry mapping (eight fields): `fingerprint` = the deterministic Phase 0 fingerprint and initial `id = fingerprint`; `rule`/`why`/`provenance` from the candidate; `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`); `confidence` = the judge's `high`/`low`. On an exact stamped consolidation the writer carries forward the deterministic primary target id while persisting the new fingerprint as the version token. A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
180
+ - Entry mapping (eight fields): `fingerprint` = `learningFingerprint(rule)` and initial `id = fingerprint`; `rule`/`why`/`provenance` from the candidate; `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`); `confidence` = the judge's `high`/`low`. On an exact stamped consolidation the writer carries forward the deterministic primary target id while persisting the new fingerprint as the version token. Use exact parsed legacy stamps in `supersede`; never rewrite old ids or fingerprints just to adopt the helper. A duplicate fingerprint fails before mutation. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
181
181
  - **On a budget-forced drop, signal saturation once (never silent).** When the writer's budget re-assertion cannot fit the entry even after consolidating harder — a durable capture has to be DROPPED for budget — the ledger is saturated, and that pressure must be visible to an operator instead of swallowed. Emit **exactly one** tracker signal via `lisa-tracker-write` (`issue_type: Task`; GitHub trackers carry the `type:Task` label), then continue — dropping the capture never blocks the build. Follow the same marker-dedupe discipline as every other comment here (match on the **marker, never the title**; **exactly one marker per body**; the eventual-consistency guard when the search index is stale), with **one deliberate difference: dedupe against OPEN signals only.** A *closed* saturation ticket means room was already reclaimed, so a fresh saturation is a new actionable event — searching closed tickets too (as the drop/upstream markers do) would permanently suppress every later saturation.
182
182
 
183
183
  The saturation fingerprint keys on the **ledger, not the candidate**, so the signal fires once per saturation episode — never once per capture:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.59.5",
3
+ "version": "4.60.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -26,7 +26,7 @@ If no learnings exist, report "No learnings to process" and complete.
26
26
 
27
27
  For each `mistake`/`learning` candidate, build the ledger entry the executable contract validates. The `LEARNINGS_CONTRACT` caps apply — an over-cap entry cannot persist; tighten it or drop it, never truncate by hand:
28
28
 
29
- - `fingerprint` — the deterministic content-version token: `learner-` + the first 12 hex chars of `sha1(normalized_rule)`. Recompute it from the fully consolidated rule; never estimate it.
29
+ - `fingerprint` — `learningFingerprint(rule)` from `@codyswann/lisa/learnings`, computed from the final consolidated rule. Every ledger writer uses this rule-only identity; evidence links and workflow markers are separate.
30
30
  - `id` — set `id = fingerprint` for a new capture. On an exact stamped consolidation the writer carries forward the deterministic primary target id, while `fingerprint` changes with the new content.
31
31
  - `rule` — the actionable learning, **≤ 240 characters and ≤ 2 lines** per `LEARNINGS_CONTRACT`.
32
32
  - `why` — the causal claim (why the rule holds).
@@ -47,6 +47,8 @@ LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => {
47
47
 
48
48
  For each candidate entry:
49
49
 
50
+ Compare `learningFingerprint(existing.rule)` with `learningFingerprint(candidate.rule)` before writing. An equal rule is already captured, even if its legacy id or fingerprint uses another prefix; preserve those stored values and report the existing id. Distinct rules may cite the same evidence. For a changed consolidation, keep the exact parsed legacy stamps in `supersede`; never migrate ids or fingerprints merely to adopt the helper.
51
+
50
52
  1. **Consolidation check (mandatory before writing).** Parse existing entries with `parseLearningsFile` from `@codyswann/lisa/learnings` and look for a related entry — same failure class, overlapping topic, or near-duplicate wording.
51
53
  - **Related entry found** → consolidate, do not sibling. Copy each target's exact parsed version stamp and write via `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`, merging the still-true content of the superseded entry into the new rule and keeping the earliest `first_learned`. Stamps are checked together inside the lock: if any is stale, the writer removes none, safely appends the new fingerprint, and reports the mismatch. A near-duplicate sibling is a bug, not an entry.
52
54
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.