@codyswann/lisa 2.298.1 → 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.
- package/all/copy-contents/.gitattributes +6 -0
- package/dist/cli/gate-commands.d.ts +8 -6
- package/dist/cli/gate-commands.d.ts.map +1 -1
- package/dist/cli/gate-commands.js +21 -0
- package/dist/cli/gate-commands.js.map +1 -1
- package/dist/cli/learnings-overflow-cmd.d.ts +27 -0
- package/dist/cli/learnings-overflow-cmd.d.ts.map +1 -0
- package/dist/cli/learnings-overflow-cmd.js +57 -0
- package/dist/cli/learnings-overflow-cmd.js.map +1 -0
- package/dist/core/learnings-alias.d.ts +156 -0
- package/dist/core/learnings-alias.d.ts.map +1 -0
- package/dist/core/learnings-alias.js +199 -0
- package/dist/core/learnings-alias.js.map +1 -0
- package/dist/core/learnings-document.d.ts +18 -0
- package/dist/core/learnings-document.d.ts.map +1 -1
- package/dist/core/learnings-document.js +23 -2
- package/dist/core/learnings-document.js.map +1 -1
- package/dist/core/learnings-merge-driver.d.ts +8 -33
- package/dist/core/learnings-merge-driver.d.ts.map +1 -1
- package/dist/core/learnings-merge-driver.js +15 -0
- package/dist/core/learnings-merge-driver.js.map +1 -1
- package/dist/core/learnings-overflow.d.ts +76 -0
- package/dist/core/learnings-overflow.d.ts.map +1 -0
- package/dist/core/learnings-overflow.js +212 -0
- package/dist/core/learnings-overflow.js.map +1 -0
- package/dist/core/learnings-writer.d.ts +6 -0
- package/dist/core/learnings-writer.d.ts.map +1 -1
- package/dist/core/learnings-writer.js +57 -8
- package/dist/core/learnings-writer.js.map +1 -1
- package/dist/core/learnings.d.ts +2 -0
- package/dist/core/learnings.d.ts.map +1 -1
- package/dist/core/learnings.js +2 -0
- package/dist/core/learnings.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +11 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-learnings-audit/SKILL.md +42 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +7 -2
- package/plugins/lisa/skills/lisa-learnings-audit/SKILL.md +42 -6
- package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +7 -2
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-learnings-audit/SKILL.md +42 -6
- package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +7 -2
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-learnings-audit/SKILL.md +42 -6
- package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +7 -2
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-learnings-audit/SKILL.md +42 -6
- package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +7 -2
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-learnings-audit/SKILL.md +42 -6
- 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
|
|
9
|
-
* They are invoked by git
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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;
|
|
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;
|
|
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,oBAAoB,GAErB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,iBAAiB,GAElB,MAAM,0BAA0B,CAAC;AAuBlC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,wBAAwB;IACxB,eAAe;IACf,oBAAoB;IACpB,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,2BAA2B,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,oBAAoB,CAAC;SAC7B,WAAW,CACV,0EAA0E,CAC3E;SACA,MAAM,CACL,kBAAkB,EAClB,6DAA6D,CAC9D;SACA,MAAM,CAAC,KAAK,EAAE,OAAiC,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACjD,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,2BAA2B,CAAC,OAAO,CAAC,CAAC;IACrC,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAClC,4BAA4B,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -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
|
|
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
|
|
107
|
+
throw new LearningsBudgetError(`${context} exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens} (measured ${estimatedTokens})`);
|
|
87
108
|
}
|
|
88
109
|
}
|
|
89
110
|
/**
|