@codyswann/lisa 4.20.0 → 4.21.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 (127) hide show
  1. package/all/copy-contents/.gitattributes +2 -1
  2. package/all/create-only/.lisa/PROJECT_LEARNINGS.md +1 -1
  3. package/dist/core/learnings-alias.d.ts +26 -45
  4. package/dist/core/learnings-alias.d.ts.map +1 -1
  5. package/dist/core/learnings-alias.js +29 -48
  6. package/dist/core/learnings-alias.js.map +1 -1
  7. package/dist/core/learnings-budget-check.d.ts +2 -1
  8. package/dist/core/learnings-budget-check.d.ts.map +1 -1
  9. package/dist/core/learnings-budget-check.js +13 -11
  10. package/dist/core/learnings-budget-check.js.map +1 -1
  11. package/dist/core/learnings-contract.d.ts +27 -10
  12. package/dist/core/learnings-contract.d.ts.map +1 -1
  13. package/dist/core/learnings-contract.js +37 -9
  14. package/dist/core/learnings-contract.js.map +1 -1
  15. package/dist/core/learnings-document.d.ts +20 -1
  16. package/dist/core/learnings-document.d.ts.map +1 -1
  17. package/dist/core/learnings-document.js +81 -14
  18. package/dist/core/learnings-document.js.map +1 -1
  19. package/dist/core/learnings-entry.d.ts +11 -1
  20. package/dist/core/learnings-entry.d.ts.map +1 -1
  21. package/dist/core/learnings-entry.js +98 -24
  22. package/dist/core/learnings-entry.js.map +1 -1
  23. package/dist/core/learnings-merge-driver.d.ts +4 -2
  24. package/dist/core/learnings-merge-driver.d.ts.map +1 -1
  25. package/dist/core/learnings-merge-driver.js +6 -3
  26. package/dist/core/learnings-merge-driver.js.map +1 -1
  27. package/dist/core/learnings-merge.d.ts.map +1 -1
  28. package/dist/core/learnings-merge.js +96 -10
  29. package/dist/core/learnings-merge.js.map +1 -1
  30. package/dist/core/learnings-overflow.d.ts.map +1 -1
  31. package/dist/core/learnings-overflow.js +72 -3
  32. package/dist/core/learnings-overflow.js.map +1 -1
  33. package/dist/core/learnings-supersede.d.ts +56 -0
  34. package/dist/core/learnings-supersede.d.ts.map +1 -0
  35. package/dist/core/learnings-supersede.js +199 -0
  36. package/dist/core/learnings-supersede.js.map +1 -0
  37. package/dist/core/learnings-writer.d.ts +9 -7
  38. package/dist/core/learnings-writer.d.ts.map +1 -1
  39. package/dist/core/learnings-writer.js +31 -58
  40. package/dist/core/learnings-writer.js.map +1 -1
  41. package/dist/core/learnings.d.ts +3 -0
  42. package/dist/core/learnings.d.ts.map +1 -1
  43. package/dist/core/learnings.js +3 -0
  44. package/dist/core/learnings.js.map +1 -1
  45. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  46. package/dist/core/upstream-evidence-manifest.js +17 -7
  47. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  48. package/package.json +3 -2
  49. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa/.codex-plugin/skills/lisa-debrief-apply/SKILL.md +5 -4
  52. package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +2 -2
  53. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1 -0
  54. package/plugins/lisa/agents/learner.md +7 -6
  55. package/plugins/lisa/rules/reference/project-learnings.md +34 -1
  56. package/plugins/lisa/skills/lisa-debrief-apply/SKILL.md +5 -4
  57. package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +2 -2
  58. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1 -0
  59. package/plugins/lisa-agy/agents/learner.md +7 -6
  60. package/plugins/lisa-agy/plugin.json +1 -1
  61. package/plugins/lisa-agy/skills/lisa-debrief-apply/SKILL.md +5 -4
  62. package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +2 -2
  63. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1 -0
  64. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  67. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-copilot/agents/learner.agent.md +7 -6
  71. package/plugins/lisa-copilot/rules/reference/project-learnings.md +34 -1
  72. package/plugins/lisa-copilot/skills/lisa-debrief-apply/SKILL.md +5 -4
  73. package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +2 -2
  74. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1 -0
  75. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-cursor/agents/learner.md +7 -6
  77. package/plugins/lisa-cursor/rules/project-learnings-reference.mdc +34 -1
  78. package/plugins/lisa-cursor/skills/lisa-debrief-apply/SKILL.md +5 -4
  79. package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +2 -2
  80. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1 -0
  81. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-expo-agy/plugin.json +1 -1
  84. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  88. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  89. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  93. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  94. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  98. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  99. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  103. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  104. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  108. package/plugins/lisa-rails-agy/plugin.json +1 -1
  109. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  110. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  113. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  114. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  115. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  116. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  118. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  119. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  120. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  121. package/plugins/src/base/agents/learner.md +7 -6
  122. package/plugins/src/base/rules/reference/project-learnings.md +34 -1
  123. package/plugins/src/base/skills/lisa-debrief-apply/SKILL.md +5 -4
  124. package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +2 -2
  125. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1 -0
  126. package/scripts/verify-learner-frontmatter-built.mjs +1 -1
  127. package/scripts/verify-packed-learnings-contract.mjs +268 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: learner
3
- description: "Post-implementation learning agent. Capture-only — collects task learnings, builds seven-field entries, and persists them to the machine-managed ledger through the executable contract with provenance. Never promotes: it creates no skills, appends no rules, files no upstream issues; promotion is exclusively the gardener's ticket-gated job."
3
+ description: "Post-implementation learning agent. Capture-only — collects task learnings, builds eight-field entries, and persists them to the machine-managed ledger through the executable contract with provenance. Never promotes: it creates no skills, appends no rules, files no upstream issues; promotion is exclusively the gardener's ticket-gated job."
4
4
  ---
5
5
 
6
6
  # Learner Agent
@@ -22,11 +22,12 @@ Why this shape: promotion without a human gate lets agents unilaterally rewrite
22
22
 
23
23
  If no learnings exist, report "No learnings to process" and complete.
24
24
 
25
- ### Step 2: Build the Seven-Field Entry
25
+ ### Step 2: Build the Eight-Field Entry
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
- - `id` — a stable dedupe key. Use `learner-` + the first 12 hex chars of `sha1(normalized_rule)` so the same rule always yields the same id (this is what makes re-runs idempotent — the writer throws on a duplicate id).
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.
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.
30
31
  - `rule` — the actionable learning, **≤ 240 characters and ≤ 2 lines** per `LEARNINGS_CONTRACT`.
31
32
  - `why` — the causal claim (why the rule holds).
32
33
  - `provenance` — stable refs behind the candidate: the originating task id(s), plus any PR/issue/commit refs the task recorded. At most 20 entries. This is also where scope markers live (below).
@@ -47,9 +48,9 @@ LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => {
47
48
  For each candidate entry:
48
49
 
49
50
  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.
50
- - **Related entry found** → consolidate, do not sibling. Write via `persistConsolidatedLearning(projectRoot, entry, { supersede: [<related ids>] })`, merging the still-true content of the superseded entry into the new rule and keeping the earliest `first_learned`. A near-duplicate sibling is a bug, not an entry.
51
+ - **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.
51
52
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
52
- - **Already present (same id)** → the writer throws on a duplicate id; treat that as a dropped-duplicate no-op and record it as such. Re-running over the same tasks leaves the ledger unchanged.
53
+ - **Already present (same fingerprint)** → the writer throws on a duplicate fingerprint before mutation; treat that as a dropped-duplicate no-op and record it as such. Re-running over the same tasks leaves the ledger unchanged.
53
54
  2. The writer re-asserts the entry and document budgets. An over-budget failure means consolidate harder or drop — never truncate by hand.
54
55
 
55
56
  Persistence rides the implement flow's normal branch/PR: the code change and its learnings land in the same PR (satisfying "every persistence is a PR" with no new machinery). Never commit the ledger straight to the default branch and never hand-edit it.
@@ -75,7 +76,7 @@ One row per collected learning, mapping it to its terminal disposition:
75
76
  - **Capture-only.** Create no skills, append to no human-authored rules file, file no issue anywhere — take no promotion decision of any kind. Every promotion (skill, eager rule, executable control, upstream ticket) is the gardener's ticket-gated job, and the ledger is your only output surface.
76
77
  - **One write path.** The ledger changes only through `persistLearningEntry` / `persistConsolidatedLearning` from `@codyswann/lisa/learnings`, inside a PR. Never hand-edit the file.
77
78
  - **Consolidate, never sibling.** A related existing entry is merged or superseded at write time (SLL-6 discipline), never duplicated.
78
- - **Idempotent.** Deduplicate before persisting; stable ids and consolidation guarantee re-runs over the same tasks leave the ledger unchanged.
79
+ - **Idempotent.** Deduplicate before persisting; the deterministic fingerprint and stamped consolidation guarantee re-runs over the same tasks leave the ledger unchanged.
79
80
  - **Headless-safe.** No interactive prompts; runs identically under an intake cron.
80
81
  - **Never block the build.** If persistence fails, report the failure and let the primary flow continue — shipping the work outranks recording a learning about it.
81
82
  - **No learning loops about learning.** Gardener tickets and learning PRs are not themselves learnings; never capture them.
@@ -22,9 +22,10 @@ then recency — that fit the token/entry budget, plus how many were omitted).
22
22
  the bounded projection, not the full document. Do not duplicate numeric caps in
23
23
  rules or prompts; the exported contract is the source of truth.
24
24
 
25
- Each persisted entry has seven fields:
25
+ Each persisted v2 entry has eight fields:
26
26
 
27
27
  - `id`
28
+ - `fingerprint`
28
29
  - `rule`
29
30
  - `why`
30
31
  - `provenance`
@@ -32,6 +33,36 @@ Each persisted entry has seven fields:
32
33
  - `last_confirmed`
33
34
  - `confidence`
34
35
 
36
+ `fingerprint` is the content-version token; `id` is the stable public identity.
37
+ A new entry starts with `id = fingerprint`. Consolidation passes exact
38
+ `{ id, fingerprint }` stamps copied from parsed entries. The writer checks the
39
+ whole target set inside its lock: an exact set is replaced together while the
40
+ deterministic primary id carries forward; any stale stamp removes nothing and
41
+ the new fingerprint is appended safely. Fingerprints are unique, so an
42
+ identical re-capture fails before mutation rather than gaining a sibling.
43
+
44
+ ## v1 compatibility and mutation-only migration
45
+
46
+ `parseLearningsDocument` accepts both contract versions and reports
47
+ `sourceVersion`, normalized `entries`, `canonicalSource`, and
48
+ `sourceMaxTokens`. A v1 entry is normalized in memory with `fingerprint = id`,
49
+ which records the version-token job its old id already performed. A read never
50
+ rewrites the ledger. The next real writer mutation — append, exact stamped
51
+ consolidation, confirmation, overflow drain, or merge — publishes one atomic v2
52
+ document. Each source version is validated against its own derived budget; do
53
+ not copy either numeric ceiling into instructions. Stable ids and fingerprints
54
+ are bounded machine tokens, and the v2 budget is derived to absorb the maximum
55
+ per-entry fingerprint expansion for every v1 document the compatibility parser
56
+ accepts. That migration-closure invariant prevents an ordinary later mutation
57
+ from discovering that a previously accepted legacy document cannot be
58
+ published.
59
+
60
+ The three-way merge understands mixed v1/v2 sides. When two branches both
61
+ legitimately replace the same stable id from the same observed fingerprint, it
62
+ keeps both successors deterministically: one retains the stable id and the
63
+ other is keyed by its unique fingerprint. It never guesses when fingerprints
64
+ collide.
65
+
35
66
  ## Who writes the ledger
36
67
 
37
68
  **Contract-mediated writers** — all going through the executable contract
@@ -39,6 +70,8 @@ Each persisted entry has seven fields:
39
70
 
40
71
  - The **learner agent** at capture time appends or consolidates new entries
41
72
  (`persistLearningEntry` / `persistConsolidatedLearning`) from task learnings.
73
+ Consolidations pass exact parsed `{ id, fingerprint }` stamps and report a
74
+ stale snapshot through `onStaleSupersede` instead of overwriting its winner.
42
75
  It is capture-only: it never appends to host rules, creates skills, or
43
76
  files upstream issues — promotion is the gardener's ticket-gated job.
44
77
  - **`lisa-debrief-apply`** routes accepted debrief findings in the three
@@ -51,15 +51,16 @@ For each such Accepted row:
51
51
  ```
52
52
 
53
53
  2. **Consolidation check (mandatory before writing).** Parse the existing entries with `parseLearningsFile` and look for one related to the row (same failure class, overlapping topic, or near-duplicate wording). Then write through the contract:
54
- - **Related entry found** → consolidate via `persistConsolidatedLearning(projectRoot, entry, { supersede: [<related ids>] })`, merging the old entry's still-true content into the new rule. Never append a near-duplicate sibling — a sibling is a bug.
54
+ - **Related entry found** → consolidate with the exact parsed versions: `persistConsolidatedLearning(projectRoot, entry, { supersede: [{ id: <related id>, fingerprint: <related fingerprint> }], onStaleSupersede: targets => report(targets) })`. Every stamp is checked together inside the writer lock. If any is stale, the writer removes none, safely appends the new fingerprint, and reports the mismatch. Never append a near-duplicate sibling by choice — a sibling is a bug; the stale append is the deliberate concurrent-writer preservation path.
55
55
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
56
56
 
57
- 3. **Entry mapping (seven fields).**
58
- - `id` — the entry id returned by the contract writer (report it in the run summary).
57
+ 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.
59
+ - `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.
59
60
  - `rule` / `why` — from the row's `Summary` and the category-specific guidance in the routing table above (≤240 chars, ≤2 lines).
60
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.
61
62
  - `first_learned` = `last_confirmed` = today (ISO date; on consolidation keep the superseded entry's earliest `first_learned`).
62
- - `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`). The writer re-asserts the entry and token budgets; an over-budget failure means consolidate harder or drop, never truncate by hand.
63
+ - `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.
63
64
 
64
65
  **Machine-local memory is no longer a knowledge destination.** Auto-memory (`project_*.md`, `MEMORY.md`) remains available only for the assistant's *personal* collaboration notes — it is invisible to cloud runs and to teammates, so it can never hold shared project knowledge. **`AGENTS.md` is human-authored** agent operating instruction — it is the source of truth, and `CLAUDE.md` is only a one-line `@AGENTS.md` pointer — and the host-rules directory `.agents/rules/` is durable human-authored guidance; `apply` never writes to any of the three for these categories.
65
66
 
@@ -171,9 +171,9 @@ No learning content is ever committed without a PR — there is no other write p
171
171
  ```
172
172
 
173
173
  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**:
174
- - **Related entry found** → consolidate via `persistConsolidatedLearning(projectRoot, entry, { supersede: [<related ids>] })`, merging the old entry's still-true content into the new rule. Never append a near-duplicate sibling — a sibling is a bug that fails review.
174
+ - **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.
175
175
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)` and state in the PR body why appending was correct.
176
- - Entry mapping: `id` = the 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`. The writer re-asserts the entry and token budgets — an over-budget failure means consolidate harder or drop, never truncate by hand.
176
+ - 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.
177
177
  - **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.
178
178
 
179
179
  The saturation fingerprint keys on the **ledger, not the candidate**, so the signal fires once per saturation episode — never once per capture:
@@ -34,6 +34,7 @@ import {
34
34
  existsSync,
35
35
  mkdirSync,
36
36
  readFileSync,
37
+ realpathSync,
37
38
  rmSync,
38
39
  writeFileSync,
39
40
  } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.20.0",
3
+ "version": "4.21.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: learner
3
- description: "Post-implementation learning agent. Capture-only — collects task learnings, builds seven-field entries, and persists them to the machine-managed ledger through the executable contract with provenance. Never promotes: it creates no skills, appends no rules, files no upstream issues; promotion is exclusively the gardener's ticket-gated job."
3
+ description: "Post-implementation learning agent. Capture-only — collects task learnings, builds eight-field entries, and persists them to the machine-managed ledger through the executable contract with provenance. Never promotes: it creates no skills, appends no rules, files no upstream issues; promotion is exclusively the gardener's ticket-gated job."
4
4
  ---
5
5
 
6
6
  # Learner Agent
@@ -22,11 +22,12 @@ Why this shape: promotion without a human gate lets agents unilaterally rewrite
22
22
 
23
23
  If no learnings exist, report "No learnings to process" and complete.
24
24
 
25
- ### Step 2: Build the Seven-Field Entry
25
+ ### Step 2: Build the Eight-Field Entry
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
- - `id` — a stable dedupe key. Use `learner-` + the first 12 hex chars of `sha1(normalized_rule)` so the same rule always yields the same id (this is what makes re-runs idempotent — the writer throws on a duplicate id).
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.
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.
30
31
  - `rule` — the actionable learning, **≤ 240 characters and ≤ 2 lines** per `LEARNINGS_CONTRACT`.
31
32
  - `why` — the causal claim (why the rule holds).
32
33
  - `provenance` — stable refs behind the candidate: the originating task id(s), plus any PR/issue/commit refs the task recorded. At most 20 entries. This is also where scope markers live (below).
@@ -47,9 +48,9 @@ LEARNINGS_FILE=$(node -e 'import("@codyswann/lisa/learnings").then(async m => {
47
48
  For each candidate entry:
48
49
 
49
50
  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.
50
- - **Related entry found** → consolidate, do not sibling. Write via `persistConsolidatedLearning(projectRoot, entry, { supersede: [<related ids>] })`, merging the still-true content of the superseded entry into the new rule and keeping the earliest `first_learned`. A near-duplicate sibling is a bug, not an entry.
51
+ - **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.
51
52
  - **No related entry** → append via `persistLearningEntry(projectRoot, entry)`.
52
- - **Already present (same id)** → the writer throws on a duplicate id; treat that as a dropped-duplicate no-op and record it as such. Re-running over the same tasks leaves the ledger unchanged.
53
+ - **Already present (same fingerprint)** → the writer throws on a duplicate fingerprint before mutation; treat that as a dropped-duplicate no-op and record it as such. Re-running over the same tasks leaves the ledger unchanged.
53
54
  2. The writer re-asserts the entry and document budgets. An over-budget failure means consolidate harder or drop — never truncate by hand.
54
55
 
55
56
  Persistence rides the implement flow's normal branch/PR: the code change and its learnings land in the same PR (satisfying "every persistence is a PR" with no new machinery). Never commit the ledger straight to the default branch and never hand-edit it.
@@ -75,7 +76,7 @@ One row per collected learning, mapping it to its terminal disposition:
75
76
  - **Capture-only.** Create no skills, append to no human-authored rules file, file no issue anywhere — take no promotion decision of any kind. Every promotion (skill, eager rule, executable control, upstream ticket) is the gardener's ticket-gated job, and the ledger is your only output surface.
76
77
  - **One write path.** The ledger changes only through `persistLearningEntry` / `persistConsolidatedLearning` from `@codyswann/lisa/learnings`, inside a PR. Never hand-edit the file.
77
78
  - **Consolidate, never sibling.** A related existing entry is merged or superseded at write time (SLL-6 discipline), never duplicated.
78
- - **Idempotent.** Deduplicate before persisting; stable ids and consolidation guarantee re-runs over the same tasks leave the ledger unchanged.
79
+ - **Idempotent.** Deduplicate before persisting; the deterministic fingerprint and stamped consolidation guarantee re-runs over the same tasks leave the ledger unchanged.
79
80
  - **Headless-safe.** No interactive prompts; runs identically under an intake cron.
80
81
  - **Never block the build.** If persistence fails, report the failure and let the primary flow continue — shipping the work outranks recording a learning about it.
81
82
  - **No learning loops about learning.** Gardener tickets and learning PRs are not themselves learnings; never capture them.