@codyswann/lisa 2.298.0 → 2.299.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/all/copy-contents/.gitattributes +6 -0
  2. package/dist/cli/gate-commands.d.ts +8 -6
  3. package/dist/cli/gate-commands.d.ts.map +1 -1
  4. package/dist/cli/gate-commands.js +21 -0
  5. package/dist/cli/gate-commands.js.map +1 -1
  6. package/dist/cli/learnings-overflow-cmd.d.ts +27 -0
  7. package/dist/cli/learnings-overflow-cmd.d.ts.map +1 -0
  8. package/dist/cli/learnings-overflow-cmd.js +57 -0
  9. package/dist/cli/learnings-overflow-cmd.js.map +1 -0
  10. package/dist/core/learnings-alias.d.ts +156 -0
  11. package/dist/core/learnings-alias.d.ts.map +1 -0
  12. package/dist/core/learnings-alias.js +199 -0
  13. package/dist/core/learnings-alias.js.map +1 -0
  14. package/dist/core/learnings-document.d.ts +18 -0
  15. package/dist/core/learnings-document.d.ts.map +1 -1
  16. package/dist/core/learnings-document.js +23 -2
  17. package/dist/core/learnings-document.js.map +1 -1
  18. package/dist/core/learnings-merge-driver.d.ts +8 -33
  19. package/dist/core/learnings-merge-driver.d.ts.map +1 -1
  20. package/dist/core/learnings-merge-driver.js +15 -0
  21. package/dist/core/learnings-merge-driver.js.map +1 -1
  22. package/dist/core/learnings-overflow.d.ts +76 -0
  23. package/dist/core/learnings-overflow.d.ts.map +1 -0
  24. package/dist/core/learnings-overflow.js +212 -0
  25. package/dist/core/learnings-overflow.js.map +1 -0
  26. package/dist/core/learnings-writer.d.ts +6 -0
  27. package/dist/core/learnings-writer.d.ts.map +1 -1
  28. package/dist/core/learnings-writer.js +57 -8
  29. package/dist/core/learnings-writer.js.map +1 -1
  30. package/dist/core/learnings.d.ts +2 -0
  31. package/dist/core/learnings.d.ts.map +1 -1
  32. package/dist/core/learnings.js +2 -0
  33. package/dist/core/learnings.js.map +1 -1
  34. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  35. package/dist/core/upstream-evidence-manifest.js +15 -6
  36. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  37. package/dist/opencode/plugin-templates/parity-safety-net-heredoc.py +232 -40
  38. package/dist/opencode/plugin-templates/parity-safety-net.sh +21 -1
  39. package/package.json +3 -1
  40. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa/.codex-plugin/skills/lisa-learnings-audit/SKILL.md +42 -6
  43. package/plugins/lisa/.codex-plugin/skills/lisa-parity-safety-net-rules/SKILL.md +38 -1
  44. package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +7 -2
  45. package/plugins/lisa/hooks/parity-safety-net-heredoc.py +232 -40
  46. package/plugins/lisa/hooks/parity-safety-net.sh +21 -1
  47. package/plugins/lisa/skills/lisa-learnings-audit/SKILL.md +42 -6
  48. package/plugins/lisa/skills/lisa-parity-safety-net-rules/SKILL.md +38 -1
  49. package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +7 -2
  50. package/plugins/lisa-agy/hooks/parity-safety-net-heredoc.py +232 -40
  51. package/plugins/lisa-agy/hooks/parity-safety-net.sh +21 -1
  52. package/plugins/lisa-agy/plugin.json +1 -1
  53. package/plugins/lisa-agy/skills/lisa-learnings-audit/SKILL.md +42 -6
  54. package/plugins/lisa-agy/skills/lisa-parity-safety-net-rules/SKILL.md +38 -1
  55. package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +7 -2
  56. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  59. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-copilot/hooks/parity-safety-net-heredoc.py +232 -40
  63. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +21 -1
  64. package/plugins/lisa-copilot/skills/lisa-learnings-audit/SKILL.md +42 -6
  65. package/plugins/lisa-copilot/skills/lisa-parity-safety-net-rules/SKILL.md +38 -1
  66. package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +7 -2
  67. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-cursor/hooks/parity-safety-net-heredoc.py +232 -40
  69. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +21 -1
  70. package/plugins/lisa-cursor/skills/lisa-learnings-audit/SKILL.md +42 -6
  71. package/plugins/lisa-cursor/skills/lisa-parity-safety-net-rules/SKILL.md +38 -1
  72. package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +7 -2
  73. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  75. package/plugins/lisa-expo-agy/plugin.json +1 -1
  76. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  81. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  86. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  91. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  96. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  100. package/plugins/lisa-rails-agy/plugin.json +1 -1
  101. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  106. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  111. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/src/base/hooks/parity-safety-net-heredoc.py +232 -40
  114. package/plugins/src/base/hooks/parity-safety-net.sh +21 -1
  115. package/plugins/src/base/skills/lisa-learnings-audit/SKILL.md +42 -6
  116. package/plugins/src/base/skills/lisa-parity-safety-net-rules/SKILL.md +38 -1
  117. package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +7 -2
@@ -6,4 +6,10 @@
6
6
  # command is machine-local — run `lisa install-merge-driver` to register it.
7
7
  .lisa/PROJECT_LEARNINGS.md merge=lisa-learnings
8
8
 
9
+ # The overflow buffer holds captures the ledger had no budget for, until
10
+ # the gardener drains them. Same writers, same branches, same format — so
11
+ # it needs the same union merge, or a merge could destroy the very content
12
+ # it exists to preserve.
13
+ .lisa/PROJECT_LEARNINGS.overflow.md merge=lisa-learnings
14
+
9
15
  # END: AI GUARDRAILS
@@ -5,11 +5,13 @@ import type { FileUpstreamOptions } from "./file-upstream-cmd.js";
5
5
  * Gate runners kept structural to avoid importing the whole CLI dependency
6
6
  * record.
7
7
  *
8
- * `merge-learnings` and `install-merge-driver` are deliberately NOT members.
9
- * They are invoked by git and by an operator repairing a repository, never
10
- * substituted by the root CLI, and both mutate real state the merge driver
11
- * overwrites the file git hands it, and the installer writes local git config.
12
- * An optional, defaulted field would be the worst shape here: a caller that
8
+ * `merge-learnings`, `install-merge-driver`, and `learnings-overflow` are
9
+ * deliberately NOT members. They are invoked by git, by an operator repairing a
10
+ * repository, or by the gardener draining its buffer never substituted by the
11
+ * root CLI and all three mutate real state: the merge driver overwrites the
12
+ * file git hands it, the installer writes local git config, and the drain
13
+ * removes entries from the only durable copy of a dropped capture. An optional,
14
+ * defaulted field would be the worst shape here: a caller that
13
15
  * believed it had stubbed every gate command would silently run the real driver
14
16
  * against real files. Leaving them off the record makes non-injectability
15
17
  * explicit; they are covered directly by unit tests and by end-to-end tests
@@ -26,7 +28,7 @@ export interface GateCommandDependencies {
26
28
  * ledger, so a network update check would stall the merge and pollute the
27
29
  * driver's output.
28
30
  */
29
- export declare const GATE_COMMAND_NAMES: readonly ["check-learnings-budget", "file-upstream", "merge-learnings", "install-merge-driver"];
31
+ export declare const GATE_COMMAND_NAMES: readonly ["check-learnings-budget", "file-upstream", "learnings-overflow", "merge-learnings", "install-merge-driver"];
30
32
  /**
31
33
  * Register all non-project gate commands.
32
34
  * @param program - Commander program
@@ -1 +1 @@
1
- {"version":3,"file":"gate-commands.d.ts","sourceRoot":"","sources":["../../src/cli/gate-commands.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAOlE;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAuB;IACtC,uBAAuB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvE,eAAe,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACpE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,iGAKrB,CAAC;AAuFX;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,uBAAuB,GACpC,IAAI,CAKN"}
1
+ {"version":3,"file":"gate-commands.d.ts","sourceRoot":"","sources":["../../src/cli/gate-commands.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAWlE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,uBAAuB;IACtC,uBAAuB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvE,eAAe,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACpE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,uHAMrB,CAAC;AA8GX;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,uBAAuB,GACpC,IAAI,CAMN"}
@@ -1,4 +1,5 @@
1
1
  import { runInstallMergeDriver } from "./install-merge-driver-cmd.js";
2
+ import { runLearningsOverflow, } from "./learnings-overflow-cmd.js";
2
3
  import { runMergeLearnings, } from "./merge-learnings-cmd.js";
3
4
  /**
4
5
  * Gate commands skip the root npm update check.
@@ -10,6 +11,7 @@ import { runMergeLearnings, } from "./merge-learnings-cmd.js";
10
11
  export const GATE_COMMAND_NAMES = [
11
12
  "check-learnings-budget",
12
13
  "file-upstream",
14
+ "learnings-overflow",
13
15
  "merge-learnings",
14
16
  "install-merge-driver",
15
17
  ];
@@ -45,6 +47,24 @@ function addFileUpstreamCommand(program, dependencies) {
45
47
  process.exitCode = code;
46
48
  });
47
49
  }
50
+ /**
51
+ * Register the overflow inspect/drain handle the gardener uses.
52
+ *
53
+ * Not injectable: the drain removes entries from the only durable copy of a
54
+ * budget-dropped capture, so a caller must never be able to stub it away.
55
+ * @param program - Commander program
56
+ */
57
+ function addLearningsOverflowCommand(program) {
58
+ program
59
+ .command("learnings-overflow")
60
+ .description("List or drain learnings the ledger had no budget to accept (JSON output)")
61
+ .option("--drain <ids...>", "Entry ids to remove once they have a durable home elsewhere")
62
+ .action(async (options) => {
63
+ const code = await runLearningsOverflow(options);
64
+ if (code !== 0)
65
+ process.exitCode = code;
66
+ });
67
+ }
48
68
  /**
49
69
  * Register the project-learnings union merge driver.
50
70
  *
@@ -89,6 +109,7 @@ function addInstallMergeDriverCommand(program) {
89
109
  export function addGateCommands(program, dependencies) {
90
110
  addCheckLearningsBudgetCommand(program, dependencies);
91
111
  addFileUpstreamCommand(program, dependencies);
112
+ addLearningsOverflowCommand(program);
92
113
  addMergeLearningsCommand(program);
93
114
  addInstallMergeDriverCommand(program);
94
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"gate-commands.js","sourceRoot":"","sources":["../../src/cli/gate-commands.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EACL,iBAAiB,GAElB,MAAM,0BAA0B,CAAC;AAqBlC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,wBAAwB;IACxB,eAAe;IACf,iBAAiB;IACjB,sBAAsB;CACd,CAAC;AAEX;;;;GAIG;AACH,SAAS,8BAA8B,CACrC,OAAgB,EAChB,YAAqC;IAErC,OAAO;SACJ,OAAO,CAAC,wBAAwB,CAAC;SACjC,WAAW,CACV,kFAAkF,CACnF;SACA,QAAQ,CACP,QAAQ,EACR,oEAAoE,CACrE;SACA,MAAM,CAAC,KAAK,EAAE,UAA8B,EAAE,EAAE;QAC/C,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;QACpE,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC1C,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAC7B,OAAgB,EAChB,YAAqC;IAErC,OAAO;SACJ,OAAO,CAAC,eAAe,CAAC;SACxB,WAAW,CACV,wEAAwE,CACzE;SACA,MAAM,CAAC,gBAAgB,EAAE,yCAAyC,CAAC;SACnE,MAAM,CAAC,KAAK,EAAE,OAA4B,EAAE,EAAE;QAC7C,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC1C,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;;GAMG;AACH,SAAS,wBAAwB,CAAC,OAAgB;IAChD,OAAO;SACJ,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CACV,kEAAkE,CACnE;SACA,MAAM,CAAC,eAAe,EAAE,6BAA6B,CAAC;SACtD,MAAM,CAAC,eAAe,EAAE,2CAA2C,CAAC;SACpE,MAAM,CAAC,iBAAiB,EAAE,wBAAwB,CAAC;SACnD,MAAM,CAAC,eAAe,EAAE,wCAAwC,CAAC;SACjE,MAAM,CAAC,KAAK,EAAE,OAA8B,EAAE,EAAE;QAC/C,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC1C,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;GAGG;AACH,SAAS,4BAA4B,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,sBAAsB,CAAC;SAC/B,WAAW,CACV,uEAAuE,CACxE;SACA,QAAQ,CAAC,QAAQ,EAAE,gDAAgD,CAAC;SACpE,MAAM,CAAC,KAAK,EAAE,UAA8B,EAAE,EAAE;QAC/C,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC,UAAU,CAAC,CAAC;QACrD,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC1C,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAgB,EAChB,YAAqC;IAErC,8BAA8B,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACtD,sBAAsB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC9C,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAClC,4BAA4B,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"gate-commands.js","sourceRoot":"","sources":["../../src/cli/gate-commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /** Command-line options for {@link runLearningsOverflow}. */
2
+ export interface LearningsOverflowOptions {
3
+ /** Entry ids to remove once they have a durable home elsewhere. */
4
+ readonly drain?: readonly string[];
5
+ }
6
+ /** Injectable collaborators for {@link runLearningsOverflow}. */
7
+ export interface LearningsOverflowDependencies {
8
+ /** Working directory used to resolve the project config (defaults to cwd). */
9
+ readonly cwd?: string;
10
+ /** Sink for the JSON payload (defaults to stdout). */
11
+ readonly log?: (message: string) => void;
12
+ /** Sink for the failure diagnostic (defaults to stderr). */
13
+ readonly error?: (message: string) => void;
14
+ }
15
+ /**
16
+ * Read or drain the overflow and print the outcome as JSON.
17
+ *
18
+ * A malformed or unsafe overflow is a real failure and exits non-zero: unlike a
19
+ * missing file (which is the ordinary case and reads as empty), a document the
20
+ * contract refuses to parse must not be reported as "nothing to drain" — that
21
+ * would present a corrupted buffer as a healthy one.
22
+ * @param options - Parsed command-line options
23
+ * @param dependencies - Injectable collaborators for tests
24
+ * @returns Process exit code
25
+ */
26
+ export declare function runLearningsOverflow(options: LearningsOverflowOptions, dependencies?: LearningsOverflowDependencies): Promise<number>;
27
+ //# sourceMappingURL=learnings-overflow-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-overflow-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/learnings-overflow-cmd.ts"],"names":[],"mappings":"AAsBA,6DAA6D;AAC7D,MAAM,WAAW,wBAAwB;IACvC,mEAAmE;IACnE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,iEAAiE;AACjE,MAAM,WAAW,6BAA6B;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,wBAAwB,EACjC,YAAY,GAAE,6BAAkC,GAC/C,OAAO,CAAC,MAAM,CAAC,CAajB"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `lisa learnings-overflow` — inspect and drain captures the ledger had no
3
+ * budget to accept (CodySwannGT/lisa#1996).
4
+ *
5
+ * This is the gardener's drain handle. The audit skill is forbidden from
6
+ * hand-parsing or hand-editing any learnings surface, so the only way to give it
7
+ * the contract's containment checks, cross-process lock, and atomic write is to
8
+ * ship them behind a command — exactly as `check-learnings-budget` ships the
9
+ * budget gate and `merge-learnings` ships the union driver.
10
+ *
11
+ * Output is JSON because the consumer is an agent, not a human reading a
12
+ * terminal: the gardener reads the pending entries, files one ticket per entry,
13
+ * and only then drains the ids it durably re-homed. Drain is therefore BY ID and
14
+ * never "empty it" — a run whose ticket filing fails part-way leaves every
15
+ * unfiled capture exactly where it was, and is safely resumable.
16
+ * @module cli/learnings-overflow-cmd
17
+ */
18
+ import { drainLearningsOverflow, readLearningsOverflow, } from "../core/learnings-overflow.js";
19
+ /**
20
+ * Read or drain the overflow and print the outcome as JSON.
21
+ *
22
+ * A malformed or unsafe overflow is a real failure and exits non-zero: unlike a
23
+ * missing file (which is the ordinary case and reads as empty), a document the
24
+ * contract refuses to parse must not be reported as "nothing to drain" — that
25
+ * would present a corrupted buffer as a healthy one.
26
+ * @param options - Parsed command-line options
27
+ * @param dependencies - Injectable collaborators for tests
28
+ * @returns Process exit code
29
+ */
30
+ export async function runLearningsOverflow(options, dependencies = {}) {
31
+ const cwd = dependencies.cwd ?? process.cwd();
32
+ const log = dependencies.log ?? ((message) => console.log(message));
33
+ const error = dependencies.error ?? ((message) => console.error(message));
34
+ try {
35
+ log(JSON.stringify(await resolvePayload(cwd, options.drain), null, 2));
36
+ return 0;
37
+ }
38
+ catch (caught) {
39
+ const detail = caught instanceof Error ? caught.message : String(caught);
40
+ error(`learnings-overflow: ${detail}`);
41
+ return 1;
42
+ }
43
+ }
44
+ /**
45
+ * Produce the read payload or the drain payload, depending on the request.
46
+ * @param cwd - Project directory
47
+ * @param drain - Entry ids to remove, when draining
48
+ * @returns JSON-serializable outcome
49
+ */
50
+ async function resolvePayload(cwd, drain) {
51
+ if (drain === undefined || drain.length === 0) {
52
+ const { file, entries } = await readLearningsOverflow(cwd);
53
+ return { file, pending: entries.length, entries };
54
+ }
55
+ return drainLearningsOverflow(cwd, drain);
56
+ }
57
+ //# sourceMappingURL=learnings-overflow-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-overflow-cmd.js","sourceRoot":"","sources":["../../src/cli/learnings-overflow-cmd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AAkBvC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAiC,EACjC,eAA8C,EAAE;IAEhD,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9C,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5E,MAAM,KAAK,GACT,YAAY,CAAC,KAAK,IAAI,CAAC,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACvE,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACzE,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;QACvC,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,cAAc,CAC3B,GAAW,EACX,KAAoC;IAEpC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC3D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,sBAAsB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Reference survival across supersede-in-place consolidation.
3
+ *
4
+ * ## The problem
5
+ *
6
+ * `persistConsolidatedLearning(..., { supersede: [ids] })` removes the
7
+ * superseded entries and adds a new one under the caller's own id — the
8
+ * learner's content fingerprint. So every consolidation churns the id, and
9
+ * anything that had cited the old id (a tracker comment, a gardener ticket, a
10
+ * cross-link from another learning) silently points at an entry that no longer
11
+ * exists (CodySwannGT/lisa#1997).
12
+ *
13
+ * ## Why an alias map and NOT "carry the earliest superseded id forward"
14
+ *
15
+ * The issue offered both. Carry-forward was implemented first and had to be
16
+ * abandoned, because the churning fingerprint id is doing a second job nobody
17
+ * wrote down: it is an accidental **compare-and-swap token**.
18
+ *
19
+ * Two learner passes racing to consolidate the same entry `base` each hold a
20
+ * stale snapshot. Today the first writer removes `base` and lands under `a`; the
21
+ * second finds `base` already gone, is tolerated rather than fatal (#1995), and
22
+ * lands under `b`. Both learnings survive — "at worst two entries where one
23
+ * consolidation was intended," exactly the cost the writer documents.
24
+ *
25
+ * Carry the id forward and that protection evaporates: the first writer lands
26
+ * under `base`, so the second writer's stale "supersede base" now MATCHES,
27
+ * removes the first writer's entry, and overwrites it. `learnings-supersede-race`
28
+ * proves the damage — nine writers consolidating one target went from nine
29
+ * preserved learnings to **one**, destroying eight. That is the #1995 data-loss
30
+ * symptom re-opened, which the brief explicitly forbids.
31
+ *
32
+ * The fix is undecidable from the seven fields alone: a stale "supersede base"
33
+ * and a legitimate chained "supersede base" are the same bytes. Telling them
34
+ * apart needs a real version token — the fingerprint retained as an eighth,
35
+ * disambiguating field, per the issue's parenthetical. That is a persisted-schema
36
+ * change: a contract version bump propagated through every reader, the merge
37
+ * driver, the CI budget gate, and all six plugin skill projections. It is the
38
+ * principled long-term answer and it deserves its own change.
39
+ *
40
+ * So this takes the issue's second option. Ids keep churning — the CAS token,
41
+ * the #1995 guarantee, and the fingerprint-is-the-id dedupe model are all left
42
+ * exactly as they are — and references survive because the consolidated entry
43
+ * *records what it replaced*.
44
+ *
45
+ * ## Where the map lives
46
+ *
47
+ * In the entry's own `provenance`, as `supersedes:<old id>` references. No new
48
+ * field, no new file, no new format: provenance is already a validated,
49
+ * rendered, merged, budget-counted list of stable references, and "this entry
50
+ * replaced that one" is precisely a provenance claim.
51
+ *
52
+ * Resolution is ONE HOP, never transitive. When an entry is consolidated, the
53
+ * writer copies the removed entries' own alias references forward, so a lineage
54
+ * `base → a → b` leaves `b` carrying both `supersedes:a` and `supersedes:base`.
55
+ * There is no chain for a reader to walk and therefore no cycle or depth limit
56
+ * to get wrong.
57
+ *
58
+ * An alias is recorded ONLY for a target that was actually present and removed.
59
+ * A supersede naming an already-consolidated id removed nothing, so claiming its
60
+ * reference would hijack a pointer that the earlier writer legitimately owns —
61
+ * in the nine-writer race, only the one writer that truly removed `base` claims
62
+ * `base`, and the reference stays unambiguous.
63
+ * @module core/learnings-alias
64
+ */
65
+ import { type LearningEntry } from "./learnings-contract.js";
66
+ /** Prefix marking a provenance reference as a superseded-id alias. */
67
+ export declare const SUPERSEDES_PREFIX = "supersedes:";
68
+ /**
69
+ * Build the provenance reference recording that an entry replaced another.
70
+ * @param id - Id of the entry that was removed
71
+ * @returns Canonical alias reference
72
+ */
73
+ export declare function buildSupersedesReference(id: string): string;
74
+ /**
75
+ * Reject a caller trying to mint its own `supersedes:` reference.
76
+ *
77
+ * The prefix is WRITER-OWNED. An alias is a factual claim that this write
78
+ * removed that entry, and the writer is the only party that knows whether it
79
+ * did — so a hand-written `supersedes:<id>` would let any caller capture a
80
+ * reference to an entry it never touched, silently redirecting an old id at
81
+ * content of its choosing.
82
+ *
83
+ * This lives at the writer's entry point rather than inside
84
+ * `validateLearningEntry` on purpose: that validator also runs on every entry
85
+ * parsed back off disk and on every side of a merge, where writer-added
86
+ * `supersedes:` references are legitimate and must be accepted. Rejecting there
87
+ * would make the contract unable to read its own output.
88
+ * @param entry - Validated entry exactly as the caller composed it
89
+ * @returns The rejection to throw, or undefined when the provenance is clean
90
+ */
91
+ export declare function findCallerMintedAliasError(entry: LearningEntry): Error | undefined;
92
+ /**
93
+ * Read the ids one entry declares it superseded.
94
+ * @param entry - Validated learning entry
95
+ * @returns Superseded ids, in the order they were recorded
96
+ */
97
+ export declare function readSupersededIds(entry: LearningEntry): readonly string[];
98
+ /** Result of folding alias references into a consolidated entry. */
99
+ export interface AliasedLearningEntry {
100
+ /** Entry provenance with as many alias references as the contract allows. */
101
+ readonly provenance: readonly string[];
102
+ /**
103
+ * Alias references that did not fit and were dropped. Never silent: the
104
+ * writer reports these so a reference that is about to stop resolving is
105
+ * visible instead of quietly disappearing.
106
+ */
107
+ readonly dropped: readonly string[];
108
+ }
109
+ /**
110
+ * Fold alias references into a caller's provenance within the contract cap.
111
+ *
112
+ * Caller-supplied provenance is NEVER sacrificed for an alias. It is the
113
+ * evidence the learning rests on — the tracker links and commits that justify
114
+ * the rule existing at all — while an alias is a convenience for finding the
115
+ * entry by a name it used to have. Evicting evidence to store a convenience
116
+ * would quietly delete the reason a learning is believed.
117
+ *
118
+ * When the two together exceed `maxProvenanceReferences`, the NEWEST aliases
119
+ * drop first, so the oldest surviving reference is kept longest. An alias gets
120
+ * MORE valuable as it ages, not less: an id that churned in this very pull
121
+ * request is still discoverable from the branch, the commit, and the capture
122
+ * report, whereas a months-old tracker comment citing an old id has no other
123
+ * way home — and silently breaking exactly those references is what
124
+ * CodySwannGT/lisa#1997 exists to fix. (An earlier revision dropped oldest-first
125
+ * on the theory that ancient references were probably already closed out; that
126
+ * has it backwards. A closed ticket citing an id is precisely where someone
127
+ * searching history lands.)
128
+ * @param entry - New entry as the caller composed it
129
+ * @param removed - Entries this write actually removed from the document
130
+ * @returns Merged provenance plus any alias references that did not fit
131
+ */
132
+ export declare function applySupersedeAliases(entry: LearningEntry, removed: readonly LearningEntry[]): AliasedLearningEntry;
133
+ /**
134
+ * Resolve every entry an id could refer to, live id first.
135
+ *
136
+ * A live id always wins: if an entry still carries the id, that entry IS the
137
+ * reference and no alias can shadow it.
138
+ *
139
+ * More than one entry can claim the same alias in exactly one situation — the
140
+ * union merge driver joined two branches that had each removed the same target
141
+ * — so this returns all claimants in deterministic id order rather than
142
+ * pretending the ambiguity away. {@link resolveLearningReference} takes the
143
+ * first for callers that just need a pointer.
144
+ * @param entries - Validated entries from the document
145
+ * @param id - Possibly-superseded id to resolve
146
+ * @returns Matching entries, deterministically ordered
147
+ */
148
+ export declare function resolveLearningReferences(entries: readonly LearningEntry[], id: string): readonly LearningEntry[];
149
+ /**
150
+ * Resolve one id to the entry that now carries its content.
151
+ * @param entries - Validated entries from the document
152
+ * @param id - Possibly-superseded id to resolve
153
+ * @returns The entry the reference now points at, when one exists
154
+ */
155
+ export declare function resolveLearningReference(entries: readonly LearningEntry[], id: string): LearningEntry | undefined;
156
+ //# sourceMappingURL=learnings-alias.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-alias.d.ts","sourceRoot":"","sources":["../../src/core/learnings-alias.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAEjC,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,aAAa,GACnB,KAAK,GAAG,SAAS,CASnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,MAAM,EAAE,CAKzE;AA4BD,oEAAoE;AACpE,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,SAAS,aAAa,EAAE,GAChC,oBAAoB,CActB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,EAAE,EAAE,MAAM,GACT,SAAS,aAAa,EAAE,CAS1B;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,EAAE,EAAE,MAAM,GACT,aAAa,GAAG,SAAS,CAE3B"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Reference survival across supersede-in-place consolidation.
3
+ *
4
+ * ## The problem
5
+ *
6
+ * `persistConsolidatedLearning(..., { supersede: [ids] })` removes the
7
+ * superseded entries and adds a new one under the caller's own id — the
8
+ * learner's content fingerprint. So every consolidation churns the id, and
9
+ * anything that had cited the old id (a tracker comment, a gardener ticket, a
10
+ * cross-link from another learning) silently points at an entry that no longer
11
+ * exists (CodySwannGT/lisa#1997).
12
+ *
13
+ * ## Why an alias map and NOT "carry the earliest superseded id forward"
14
+ *
15
+ * The issue offered both. Carry-forward was implemented first and had to be
16
+ * abandoned, because the churning fingerprint id is doing a second job nobody
17
+ * wrote down: it is an accidental **compare-and-swap token**.
18
+ *
19
+ * Two learner passes racing to consolidate the same entry `base` each hold a
20
+ * stale snapshot. Today the first writer removes `base` and lands under `a`; the
21
+ * second finds `base` already gone, is tolerated rather than fatal (#1995), and
22
+ * lands under `b`. Both learnings survive — "at worst two entries where one
23
+ * consolidation was intended," exactly the cost the writer documents.
24
+ *
25
+ * Carry the id forward and that protection evaporates: the first writer lands
26
+ * under `base`, so the second writer's stale "supersede base" now MATCHES,
27
+ * removes the first writer's entry, and overwrites it. `learnings-supersede-race`
28
+ * proves the damage — nine writers consolidating one target went from nine
29
+ * preserved learnings to **one**, destroying eight. That is the #1995 data-loss
30
+ * symptom re-opened, which the brief explicitly forbids.
31
+ *
32
+ * The fix is undecidable from the seven fields alone: a stale "supersede base"
33
+ * and a legitimate chained "supersede base" are the same bytes. Telling them
34
+ * apart needs a real version token — the fingerprint retained as an eighth,
35
+ * disambiguating field, per the issue's parenthetical. That is a persisted-schema
36
+ * change: a contract version bump propagated through every reader, the merge
37
+ * driver, the CI budget gate, and all six plugin skill projections. It is the
38
+ * principled long-term answer and it deserves its own change.
39
+ *
40
+ * So this takes the issue's second option. Ids keep churning — the CAS token,
41
+ * the #1995 guarantee, and the fingerprint-is-the-id dedupe model are all left
42
+ * exactly as they are — and references survive because the consolidated entry
43
+ * *records what it replaced*.
44
+ *
45
+ * ## Where the map lives
46
+ *
47
+ * In the entry's own `provenance`, as `supersedes:<old id>` references. No new
48
+ * field, no new file, no new format: provenance is already a validated,
49
+ * rendered, merged, budget-counted list of stable references, and "this entry
50
+ * replaced that one" is precisely a provenance claim.
51
+ *
52
+ * Resolution is ONE HOP, never transitive. When an entry is consolidated, the
53
+ * writer copies the removed entries' own alias references forward, so a lineage
54
+ * `base → a → b` leaves `b` carrying both `supersedes:a` and `supersedes:base`.
55
+ * There is no chain for a reader to walk and therefore no cycle or depth limit
56
+ * to get wrong.
57
+ *
58
+ * An alias is recorded ONLY for a target that was actually present and removed.
59
+ * A supersede naming an already-consolidated id removed nothing, so claiming its
60
+ * reference would hijack a pointer that the earlier writer legitimately owns —
61
+ * in the nine-writer race, only the one writer that truly removed `base` claims
62
+ * `base`, and the reference stays unambiguous.
63
+ * @module core/learnings-alias
64
+ */
65
+ import { LEARNINGS_CONTRACT, } from "./learnings-contract.js";
66
+ /** Prefix marking a provenance reference as a superseded-id alias. */
67
+ export const SUPERSEDES_PREFIX = "supersedes:";
68
+ /**
69
+ * Build the provenance reference recording that an entry replaced another.
70
+ * @param id - Id of the entry that was removed
71
+ * @returns Canonical alias reference
72
+ */
73
+ export function buildSupersedesReference(id) {
74
+ return `${SUPERSEDES_PREFIX}${id}`;
75
+ }
76
+ /**
77
+ * Reject a caller trying to mint its own `supersedes:` reference.
78
+ *
79
+ * The prefix is WRITER-OWNED. An alias is a factual claim that this write
80
+ * removed that entry, and the writer is the only party that knows whether it
81
+ * did — so a hand-written `supersedes:<id>` would let any caller capture a
82
+ * reference to an entry it never touched, silently redirecting an old id at
83
+ * content of its choosing.
84
+ *
85
+ * This lives at the writer's entry point rather than inside
86
+ * `validateLearningEntry` on purpose: that validator also runs on every entry
87
+ * parsed back off disk and on every side of a merge, where writer-added
88
+ * `supersedes:` references are legitimate and must be accepted. Rejecting there
89
+ * would make the contract unable to read its own output.
90
+ * @param entry - Validated entry exactly as the caller composed it
91
+ * @returns The rejection to throw, or undefined when the provenance is clean
92
+ */
93
+ export function findCallerMintedAliasError(entry) {
94
+ const minted = entry.provenance.filter(reference => reference.startsWith(SUPERSEDES_PREFIX));
95
+ return minted.length === 0
96
+ ? undefined
97
+ : new Error(`Invalid provenance: '${SUPERSEDES_PREFIX}' references are added by the writer, not the caller (found ${minted.join(", ")})`);
98
+ }
99
+ /**
100
+ * Read the ids one entry declares it superseded.
101
+ * @param entry - Validated learning entry
102
+ * @returns Superseded ids, in the order they were recorded
103
+ */
104
+ export function readSupersededIds(entry) {
105
+ return entry.provenance
106
+ .filter(reference => reference.startsWith(SUPERSEDES_PREFIX))
107
+ .map(reference => reference.slice(SUPERSEDES_PREFIX.length))
108
+ .filter(id => id !== "");
109
+ }
110
+ /**
111
+ * Compose the alias references a consolidated entry must carry.
112
+ *
113
+ * Ordering is oldest-lineage-first: the ids inherited from the removed entries
114
+ * come before the removed entries' own ids, so the reference that has existed
115
+ * longest sits earliest and survives longest under the cap below.
116
+ *
117
+ * An entry that supersedes its OWN id is editing itself in place, not renaming
118
+ * itself — the reference never broke, so it earns no alias. Its inherited
119
+ * lineage is still carried forward, because an in-place edit must not drop the
120
+ * ancestors that already resolve through it.
121
+ * @param removed - Entries this write actually removed from the document
122
+ * @param selfId - Id of the entry being written
123
+ * @returns Deduplicated alias references in lineage order
124
+ */
125
+ function composeAliasReferences(removed, selfId) {
126
+ const inherited = removed.flatMap(entry => readSupersededIds(entry));
127
+ const direct = removed.map(entry => entry.id);
128
+ return [...new Set([...inherited, ...direct])]
129
+ .filter(id => id !== selfId)
130
+ .map(buildSupersedesReference);
131
+ }
132
+ /**
133
+ * Fold alias references into a caller's provenance within the contract cap.
134
+ *
135
+ * Caller-supplied provenance is NEVER sacrificed for an alias. It is the
136
+ * evidence the learning rests on — the tracker links and commits that justify
137
+ * the rule existing at all — while an alias is a convenience for finding the
138
+ * entry by a name it used to have. Evicting evidence to store a convenience
139
+ * would quietly delete the reason a learning is believed.
140
+ *
141
+ * When the two together exceed `maxProvenanceReferences`, the NEWEST aliases
142
+ * drop first, so the oldest surviving reference is kept longest. An alias gets
143
+ * MORE valuable as it ages, not less: an id that churned in this very pull
144
+ * request is still discoverable from the branch, the commit, and the capture
145
+ * report, whereas a months-old tracker comment citing an old id has no other
146
+ * way home — and silently breaking exactly those references is what
147
+ * CodySwannGT/lisa#1997 exists to fix. (An earlier revision dropped oldest-first
148
+ * on the theory that ancient references were probably already closed out; that
149
+ * has it backwards. A closed ticket citing an id is precisely where someone
150
+ * searching history lands.)
151
+ * @param entry - New entry as the caller composed it
152
+ * @param removed - Entries this write actually removed from the document
153
+ * @returns Merged provenance plus any alias references that did not fit
154
+ */
155
+ export function applySupersedeAliases(entry, removed) {
156
+ const provenance = entry.provenance;
157
+ const existing = new Set(provenance);
158
+ const aliases = composeAliasReferences(removed, entry.id).filter(reference => !existing.has(reference));
159
+ const room = Math.max(0, LEARNINGS_CONTRACT.maxProvenanceReferences - provenance.length);
160
+ return {
161
+ provenance: [...provenance, ...aliases.slice(0, room)],
162
+ dropped: aliases.slice(room),
163
+ };
164
+ }
165
+ /**
166
+ * Resolve every entry an id could refer to, live id first.
167
+ *
168
+ * A live id always wins: if an entry still carries the id, that entry IS the
169
+ * reference and no alias can shadow it.
170
+ *
171
+ * More than one entry can claim the same alias in exactly one situation — the
172
+ * union merge driver joined two branches that had each removed the same target
173
+ * — so this returns all claimants in deterministic id order rather than
174
+ * pretending the ambiguity away. {@link resolveLearningReference} takes the
175
+ * first for callers that just need a pointer.
176
+ * @param entries - Validated entries from the document
177
+ * @param id - Possibly-superseded id to resolve
178
+ * @returns Matching entries, deterministically ordered
179
+ */
180
+ export function resolveLearningReferences(entries, id) {
181
+ const live = entries.find(entry => entry.id === id);
182
+ if (live !== undefined) {
183
+ return [live];
184
+ }
185
+ const alias = buildSupersedesReference(id);
186
+ return entries
187
+ .filter(entry => entry.provenance.includes(alias))
188
+ .sort((left, right) => (left.id < right.id ? -1 : 1));
189
+ }
190
+ /**
191
+ * Resolve one id to the entry that now carries its content.
192
+ * @param entries - Validated entries from the document
193
+ * @param id - Possibly-superseded id to resolve
194
+ * @returns The entry the reference now points at, when one exists
195
+ */
196
+ export function resolveLearningReference(entries, id) {
197
+ return resolveLearningReferences(entries, id)[0];
198
+ }
199
+ //# sourceMappingURL=learnings-alias.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-alias.js","sourceRoot":"","sources":["../../src/core/learnings-alias.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,24 @@ export declare function renderLearningsFile(entries: readonly LearningEntry[]):
17
17
  * @returns Revalidated entries from the document
18
18
  */
19
19
  export declare function parseLearningsFile(content: string): LearningEntry[];
20
+ /**
21
+ * A document that does not fit the hard entry-count or token budget.
22
+ *
23
+ * Typed so the writer can tell "this capture has nowhere to go" apart from every
24
+ * other write failure. That distinction is load-bearing: a budget breach is the
25
+ * one failure whose dropped CONTENT is still worth preserving in the overflow
26
+ * file (CodySwannGT/lisa#1996), whereas an unsafe path or a corrupted document
27
+ * must fail outright rather than have content salvaged out of it. The messages
28
+ * are unchanged from the plain-`Error` era so existing diagnostics and their
29
+ * assertions still read the same.
30
+ */
31
+ export declare class LearningsBudgetError extends Error {
32
+ /**
33
+ * Build a budget breach carrying the caller-facing diagnosis.
34
+ * @param message - Single-line budget diagnosis
35
+ */
36
+ constructor(message: string);
37
+ }
20
38
  /**
21
39
  * Enforce the shared entry-count and model-agnostic token upper bounds.
22
40
  * @param content - Canonical document
@@ -1 +1 @@
1
- {"version":3,"file":"learnings-document.d.ts","sourceRoot":"","sources":["../../src/core/learnings-document.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AA+BjC;;;GAGG;AACH,eAAO,MAAM,yBAAyB,0DACmB,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CA8BnE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,IAAI,CAYN;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAIvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,UAAU,GACjB,MAAM,GAAG,SAAS,CAEpB"}
1
+ {"version":3,"file":"learnings-document.d.ts","sourceRoot":"","sources":["../../src/core/learnings-document.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AA+BjC;;;GAGG;AACH,eAAO,MAAM,yBAAyB,0DACmB,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CA8BnE;AAED;;;;;;;;;;GAUG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C;;;OAGG;gBACS,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,IAAI,CAYN;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAIvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,UAAU,GACjB,MAAM,GAAG,SAAS,CAEpB"}
@@ -71,6 +71,27 @@ export function parseLearningsFile(content) {
71
71
  assertDocumentBudget(content, entries.length, "Project learnings payload");
72
72
  return entries;
73
73
  }
74
+ /**
75
+ * A document that does not fit the hard entry-count or token budget.
76
+ *
77
+ * Typed so the writer can tell "this capture has nowhere to go" apart from every
78
+ * other write failure. That distinction is load-bearing: a budget breach is the
79
+ * one failure whose dropped CONTENT is still worth preserving in the overflow
80
+ * file (CodySwannGT/lisa#1996), whereas an unsafe path or a corrupted document
81
+ * must fail outright rather than have content salvaged out of it. The messages
82
+ * are unchanged from the plain-`Error` era so existing diagnostics and their
83
+ * assertions still read the same.
84
+ */
85
+ export class LearningsBudgetError extends Error {
86
+ /**
87
+ * Build a budget breach carrying the caller-facing diagnosis.
88
+ * @param message - Single-line budget diagnosis
89
+ */
90
+ constructor(message) {
91
+ super(message);
92
+ this.name = "LearningsBudgetError";
93
+ }
94
+ }
74
95
  /**
75
96
  * Enforce the shared entry-count and model-agnostic token upper bounds.
76
97
  * @param content - Canonical document
@@ -79,11 +100,11 @@ export function parseLearningsFile(content) {
79
100
  */
80
101
  export function assertDocumentBudget(content, entryCount, context) {
81
102
  if (entryCount > LEARNINGS_CONTRACT.maxEntries) {
82
- throw new Error(`${context} exceeds maxEntries: measured ${entryCount}, allowed ${LEARNINGS_CONTRACT.maxEntries}`);
103
+ throw new LearningsBudgetError(`${context} exceeds maxEntries: measured ${entryCount}, allowed ${LEARNINGS_CONTRACT.maxEntries}`);
83
104
  }
84
105
  const estimatedTokens = estimateLearningTokens(content);
85
106
  if (estimatedTokens > LEARNINGS_CONTRACT.maxTokens) {
86
- throw new Error(`${context} exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens} (measured ${estimatedTokens})`);
107
+ throw new LearningsBudgetError(`${context} exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens} (measured ${estimatedTokens})`);
87
108
  }
88
109
  }
89
110
  /**