@codyswann/lisa 2.298.1 → 2.299.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-contents/.gitattributes +6 -0
- package/dist/cli/doctor-readiness-domain.d.ts.map +1 -1
- package/dist/cli/doctor-readiness-domain.js +10 -0
- package/dist/cli/doctor-readiness-domain.js.map +1 -1
- package/dist/cli/doctor-readiness-guardrails.d.ts.map +1 -1
- package/dist/cli/doctor-readiness-guardrails.js +10 -4
- package/dist/cli/doctor-readiness-guardrails.js.map +1 -1
- 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
|
@@ -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
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learnings-document.js","sourceRoot":"","sources":["../../src/core/learnings-document.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,OAAO,EACL,kBAAkB,EAClB,sBAAsB,GAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,WAAW,GAAG;;gCAEY,kBAAkB,CAAC,OAAO;;CAEzD,CAAC;AACF,MAAM,gBAAgB,GAAG,YAAY,CAAC;AACtC,MAAM,cAAc,GAAG,SAAS,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,eAAe,GAAG,uCAAuC,CAAC;AAEhE;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GACpC,uDAAuD,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAiC;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,OAAO,GAAG,WAAW,GAAG,gBAAgB,GAAG,KAAK,GAAG,cAAc,EAAE,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,YAAY,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACrD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,mBAAmB,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,sBAAsB,CAAC,OAAO,CAAC,GAAG,kBAAkB,CAAC,SAAS,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CACb,+CAA+C,kBAAkB,CAAC,SAAS,EAAE,CAC9E,CAAC;IACJ,CAAC;IACD,IACE,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,gBAAgB,EAAE,CAAC;QACxD,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EACjC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC;IAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC;IACvD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CACnE,2BAA2B,CAC5B,CAAC;IACF,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;IAC3E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,UAAkB,EAClB,OAAe;IAEf,IAAI,UAAU,GAAG,kBAAkB,CAAC,UAAU,EAAE,CAAC;QAC/C,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"learnings-document.js","sourceRoot":"","sources":["../../src/core/learnings-document.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,36 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registration contract for the project-learnings union merge driver.
|
|
3
|
-
*
|
|
4
|
-
* A git merge driver has two halves that live in different places:
|
|
5
|
-
*
|
|
6
|
-
* 1. **The attribute** (`.gitattributes`) says WHICH driver a path uses. It is
|
|
7
|
-
* a committed file, so Lisa ships it to the repository and to every host
|
|
8
|
-
* project through the template pipeline.
|
|
9
|
-
* 2. **The driver command** (`merge.<name>.driver`) says HOW to run it. Git
|
|
10
|
-
* deliberately keeps this in machine-local config and never reads it from
|
|
11
|
-
* the repository, because a committed driver command would let a cloned
|
|
12
|
-
* repository execute arbitrary code on `git merge`.
|
|
13
|
-
*
|
|
14
|
-
* So the attribute alone is inert. Until the command is registered, git falls
|
|
15
|
-
* back to its built-in text merge — which is exactly today's behavior, so an
|
|
16
|
-
* unregistered checkout is degraded, never broken. That fallback is asserted in
|
|
17
|
-
* `tests/unit/core/learnings-merge-driver.test.ts`.
|
|
18
|
-
*
|
|
19
|
-
* Registration is AUTOMATIC, not operator-initiated. `EnsureLearningsMergeDriver`
|
|
20
|
-
* runs on every `lisa apply`, and the TypeScript stack's `package.lisa.json`
|
|
21
|
-
* wires `lisa apply` into `postinstall` — so an ordinary `npm install` in a host
|
|
22
|
-
* project writes `merge.lisa-learnings.driver` into `.git/config` with no
|
|
23
|
-
* separate human step, and against Lisa's own repository too.
|
|
24
|
-
*
|
|
25
|
-
* That is worth stating plainly because of what it implies: the registration
|
|
26
|
-
* persists an executable hook in local git config that fires on ordinary git
|
|
27
|
-
* operations, outside npm's lifecycle. It is not an escalation over `npm
|
|
28
|
-
* install` itself (which already runs arbitrary code), but it does outlive it.
|
|
29
|
-
* Hosts that would rather not carry that hook set `learnings.mergeDriver: false`
|
|
30
|
-
* in `.lisa.config.json`; `lisa install-merge-driver` remains available to
|
|
31
|
-
* register it by hand in a clone that apply has not touched.
|
|
32
|
-
* @module core/learnings-merge-driver
|
|
33
|
-
*/
|
|
34
1
|
/** Git merge-driver name shared by `.gitattributes` and the local git config. */
|
|
35
2
|
export declare const LEARNINGS_MERGE_DRIVER_NAME = "lisa-learnings";
|
|
36
3
|
/** Human-readable driver name recorded alongside the command. */
|
|
@@ -76,6 +43,14 @@ export declare function buildLearningsAttributeLine(ledgerPath: string): string;
|
|
|
76
43
|
* Wrapped in the same guardrail markers the `.gitignore` template uses, so the
|
|
77
44
|
* `copy-contents` strategy replaces exactly this block on re-apply and leaves
|
|
78
45
|
* every host-authored rule outside it untouched.
|
|
46
|
+
*
|
|
47
|
+
* Both halves of the learnings surface are bound. The overflow file
|
|
48
|
+
* (CodySwannGT/lisa#1996) is written by the same concurrent learner passes, on
|
|
49
|
+
* the same per-fingerprint branches, in the same canonical document format — so
|
|
50
|
+
* it has the ledger's exact merge problem and the exact same union-by-id
|
|
51
|
+
* resolution. Binding only the ledger would leave the overflow on git's default
|
|
52
|
+
* text merge, silently re-introducing the corruption this driver exists to
|
|
53
|
+
* remove, on the one file whose whole job is not losing content.
|
|
79
54
|
* @param ledgerPath - Project-relative learnings file path
|
|
80
55
|
* @returns Managed block, newline-terminated
|
|
81
56
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learnings-merge-driver.d.ts","sourceRoot":"","sources":["../../src/core/learnings-merge-driver.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"learnings-merge-driver.d.ts","sourceRoot":"","sources":["../../src/core/learnings-merge-driver.ts"],"names":[],"mappings":"AAmCA,iFAAiF;AACjF,eAAO,MAAM,2BAA2B,mBAAmB,CAAC;AAE5D,iEAAiE;AACjE,eAAO,MAAM,kCAAkC,wCACR,CAAC;AAExC,wEAAwE;AACxE,eAAO,MAAM,0BAA0B,2BAA2B,CAAC;AAEnE,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gCAAgC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iCAAiC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB5E"}
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* register it by hand in a clone that apply has not touched.
|
|
32
32
|
* @module core/learnings-merge-driver
|
|
33
33
|
*/
|
|
34
|
+
import { resolveLearningsOverflowFile } from "./learnings-overflow.js";
|
|
34
35
|
/** Git merge-driver name shared by `.gitattributes` and the local git config. */
|
|
35
36
|
export const LEARNINGS_MERGE_DRIVER_NAME = "lisa-learnings";
|
|
36
37
|
/** Human-readable driver name recorded alongside the command. */
|
|
@@ -80,6 +81,14 @@ export function buildLearningsAttributeLine(ledgerPath) {
|
|
|
80
81
|
* Wrapped in the same guardrail markers the `.gitignore` template uses, so the
|
|
81
82
|
* `copy-contents` strategy replaces exactly this block on re-apply and leaves
|
|
82
83
|
* every host-authored rule outside it untouched.
|
|
84
|
+
*
|
|
85
|
+
* Both halves of the learnings surface are bound. The overflow file
|
|
86
|
+
* (CodySwannGT/lisa#1996) is written by the same concurrent learner passes, on
|
|
87
|
+
* the same per-fingerprint branches, in the same canonical document format — so
|
|
88
|
+
* it has the ledger's exact merge problem and the exact same union-by-id
|
|
89
|
+
* resolution. Binding only the ledger would leave the overflow on git's default
|
|
90
|
+
* text merge, silently re-introducing the corruption this driver exists to
|
|
91
|
+
* remove, on the one file whose whole job is not losing content.
|
|
83
92
|
* @param ledgerPath - Project-relative learnings file path
|
|
84
93
|
* @returns Managed block, newline-terminated
|
|
85
94
|
*/
|
|
@@ -93,6 +102,12 @@ export function renderLearningsGitattributesBlock(ledgerPath) {
|
|
|
93
102
|
"# command is machine-local — run `lisa install-merge-driver` to register it.",
|
|
94
103
|
buildLearningsAttributeLine(ledgerPath),
|
|
95
104
|
"",
|
|
105
|
+
"# The overflow buffer holds captures the ledger had no budget for, until",
|
|
106
|
+
"# the gardener drains them. Same writers, same branches, same format — so",
|
|
107
|
+
"# it needs the same union merge, or a merge could destroy the very content",
|
|
108
|
+
"# it exists to preserve.",
|
|
109
|
+
buildLearningsAttributeLine(resolveLearningsOverflowFile(ledgerPath)),
|
|
110
|
+
"",
|
|
96
111
|
GITATTRIBUTES_END_MARKER,
|
|
97
112
|
"",
|
|
98
113
|
].join("\n");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learnings-merge-driver.js","sourceRoot":"","sources":["../../src/core/learnings-merge-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;
|
|
1
|
+
{"version":3,"file":"learnings-merge-driver.js","sourceRoot":"","sources":["../../src/core/learnings-merge-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AAEvE,iFAAiF;AACjF,MAAM,CAAC,MAAM,2BAA2B,GAAG,gBAAgB,CAAC;AAE5D,iEAAiE;AACjE,MAAM,CAAC,MAAM,kCAAkC,GAC7C,qCAAqC,CAAC;AAExC,wEAAwE;AACxE,MAAM,CAAC,MAAM,0BAA0B,GAAG,wBAAwB,CAAC;AAEnE,wEAAwE;AACxE,MAAM,CAAC,MAAM,wBAAwB,GAAG,sBAAsB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,gCAAgC,CAAC,UAAkB;IACjE,OAAO,GAAG,UAAU,4DAA4D,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,UAAkB;IAC5D,OAAO,GAAG,UAAU,UAAU,2BAA2B,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,iCAAiC,CAAC,UAAkB;IAClE,OAAO;QACL,0BAA0B;QAC1B,EAAE;QACF,6EAA6E;QAC7E,2EAA2E;QAC3E,0EAA0E;QAC1E,8EAA8E;QAC9E,2BAA2B,CAAC,UAAU,CAAC;QACvC,EAAE;QACF,0EAA0E;QAC1E,2EAA2E;QAC3E,4EAA4E;QAC5E,0BAA0B;QAC1B,2BAA2B,CAAC,4BAA4B,CAAC,UAAU,CAAC,CAAC;QACrE,EAAE;QACF,wBAAwB;QACxB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type LearningEntry } from "./learnings-contract.js";
|
|
2
|
+
import { LearningsBudgetError } from "./learnings-document.js";
|
|
3
|
+
/**
|
|
4
|
+
* Derive the overflow path from a project's configured ledger path.
|
|
5
|
+
*
|
|
6
|
+
* Derived rather than separately configurable so a project that relocates its
|
|
7
|
+
* ledger through `learnings.file` cannot end up with the two halves of one
|
|
8
|
+
* mechanism in different directories — and so there is no second config key to
|
|
9
|
+
* validate, contain, and keep in sync.
|
|
10
|
+
* @param learningsFile - Project-relative ledger path
|
|
11
|
+
* @returns Project-relative overflow path
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveLearningsOverflowFile(learningsFile: string): string;
|
|
14
|
+
/** Entries currently awaiting drain, and where they live. */
|
|
15
|
+
export interface LearningsOverflowContents {
|
|
16
|
+
/** Absolute overflow path, whether or not the file exists yet. */
|
|
17
|
+
readonly file: string;
|
|
18
|
+
/** Validated entries awaiting drain. */
|
|
19
|
+
readonly entries: readonly LearningEntry[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read the overflow without modifying it.
|
|
23
|
+
*
|
|
24
|
+
* An absent file is the common, expected case and reads as empty — a project
|
|
25
|
+
* that has never dropped a capture must not have to special-case its absence.
|
|
26
|
+
* @param projectRoot - Absolute path to the host project root
|
|
27
|
+
* @returns Overflow path and the entries awaiting drain
|
|
28
|
+
*/
|
|
29
|
+
export declare function readLearningsOverflow(projectRoot: string): Promise<LearningsOverflowContents>;
|
|
30
|
+
/** Outcome of draining entries the gardener has finished with. */
|
|
31
|
+
export interface LearningsOverflowDrain {
|
|
32
|
+
/** Absolute overflow path. */
|
|
33
|
+
readonly file: string;
|
|
34
|
+
/** Ids that were present and have now been removed. */
|
|
35
|
+
readonly drained: readonly string[];
|
|
36
|
+
/** Requested ids that were not in the overflow — reported, never fatal. */
|
|
37
|
+
readonly absent: readonly string[];
|
|
38
|
+
/** How many entries still await drain. */
|
|
39
|
+
readonly remaining: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Remove entries the gardener has durably captured elsewhere.
|
|
43
|
+
*
|
|
44
|
+
* Drain is BY ID and never "empty the file", because the gardener's real
|
|
45
|
+
* sequence is read → file a ticket per entry → drain. Clearing everything up
|
|
46
|
+
* front would lose any entry whose ticket failed to file; removing only the ids
|
|
47
|
+
* that already have a durable home makes a partial run safely resumable.
|
|
48
|
+
*
|
|
49
|
+
* An unknown id is reported rather than thrown, for the same reason an absent
|
|
50
|
+
* supersede target is tolerated: on a re-run, an id another pass already drained
|
|
51
|
+
* is indistinguishable from a bogus one, and failing the drain would strand
|
|
52
|
+
* every other entry in the batch.
|
|
53
|
+
* @param projectRoot - Absolute path to the host project root
|
|
54
|
+
* @param ids - Entry ids to remove
|
|
55
|
+
* @returns Which ids were drained, which were absent, and what remains
|
|
56
|
+
*/
|
|
57
|
+
export declare function drainLearningsOverflow(projectRoot: string, ids: readonly string[]): Promise<LearningsOverflowDrain>;
|
|
58
|
+
/**
|
|
59
|
+
* Preserve a capture the ledger had no budget for, then re-raise the breach.
|
|
60
|
+
*
|
|
61
|
+
* Never returns normally: the capture genuinely did not land in the ledger, and
|
|
62
|
+
* a caller that treated preservation as success would report a learning as
|
|
63
|
+
* persisted when the projection will never serve it. The re-raised error names
|
|
64
|
+
* the overflow file so the drop is actionable rather than merely fatal.
|
|
65
|
+
*
|
|
66
|
+
* If the overflow is itself full, the original ledger breach is re-raised with
|
|
67
|
+
* both surfaces named. Nothing is evicted to make room — silently discarding an
|
|
68
|
+
* older dropped learning to store a newer one is the exact data loss this whole
|
|
69
|
+
* mechanism exists to prevent.
|
|
70
|
+
* @param projectRoot - Absolute path to the host project root
|
|
71
|
+
* @param entry - The validated entry the ledger rejected
|
|
72
|
+
* @param breach - The budget breach the ledger write raised
|
|
73
|
+
* @returns Never — always throws
|
|
74
|
+
*/
|
|
75
|
+
export declare function preserveDroppedLearning(projectRoot: string, entry: LearningEntry, breach: LearningsBudgetError): Promise<never>;
|
|
76
|
+
//# sourceMappingURL=learnings-overflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-overflow.d.ts","sourceRoot":"","sources":["../../src/core/learnings-overflow.ts"],"names":[],"mappings":"AAkDA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,oBAAoB,EAIrB,MAAM,yBAAyB,CAAC;AAejC;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAI1E;AAmCD,6DAA6D;AAC7D,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,yBAAyB,CAAC,CAGpC;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,SAAS,MAAM,EAAE,GACrB,OAAO,CAAC,sBAAsB,CAAC,CAwBjC;AAqBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,KAAK,CAAC,CAyBhB"}
|