@codyswann/lisa 3.48.6 → 3.48.7
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/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +6 -2
- 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-agy/plugin.json +1 -1
- 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-cursor/.claude-plugin/plugin.json +1 -1
- 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/scripts/check-derived-artifacts.mjs +22 -4
- package/scripts/generate-upstream-evidence-manifest.mjs +10 -1
- package/scripts/lib/upstream-manifest-staleness.mjs +235 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* upstream-manifest-staleness — turn "the manifest is out of date" into "here is
|
|
3
|
+
* what moved underneath it".
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* `src/core/upstream-evidence-manifest.ts` is derived from two things: the set
|
|
7
|
+
* of TRACKED files (`git ls-files`, so the git index), and the working-tree
|
|
8
|
+
* bytes of every tracked file under a packaged-evidence prefix. Its `--check`
|
|
9
|
+
* regenerates and compares byte-for-byte, so it can say *that* the file is out
|
|
10
|
+
* of date, and until CodySwannGT/lisa#2852 that is all it said:
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* src/core/upstream-evidence-manifest.ts is stale; run bun run build:upstream-evidence-manifest
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The refusal is accurate and points away from the cause. It names the file
|
|
17
|
+
* that is out of date, so the obvious response is to regenerate that file — and
|
|
18
|
+
* when the cause is an input that moves again afterwards, regenerating
|
|
19
|
+
* reproduces the identical refusal. That is a loop with no exit signposted from
|
|
20
|
+
* inside it, and it cost a full cycle.
|
|
21
|
+
*
|
|
22
|
+
* There are exactly two ways an input can move, and they need opposite fixes:
|
|
23
|
+
*
|
|
24
|
+
* - **The bytes of a hash-pinned file changed.** Overwhelmingly this is
|
|
25
|
+
* `lint-staged`, which runs `oxlint --fix`, `eslint --fix` and
|
|
26
|
+
* `prettier --write` over every staged file at commit time — *after* the
|
|
27
|
+
* author regenerated, and *before* the artifact gate runs. The fix is to
|
|
28
|
+
* regenerate again now, after the reformat.
|
|
29
|
+
* - **The tracked set changed.** A file staged (or `git rm`'d) after the
|
|
30
|
+
* manifest was generated is a membership change, and regenerating before the
|
|
31
|
+
* next `git add` will produce the same wrong answer. The fix is to stage
|
|
32
|
+
* first and regenerate second.
|
|
33
|
+
*
|
|
34
|
+
* A third outcome is possible and worth naming: no input moved at all, which
|
|
35
|
+
* means the generated file itself was rewritten after generation — a hand edit,
|
|
36
|
+
* or a formatter reaching the artifact.
|
|
37
|
+
*
|
|
38
|
+
* ## What this module deliberately does not claim
|
|
39
|
+
*
|
|
40
|
+
* The obvious story — "the manifest hashes the ledger, so a stale manifest means
|
|
41
|
+
* a stale ledger" — is false, and was measured false while fixing #2852:
|
|
42
|
+
* `src/core/lisa-owned-hash-ledger.ts` lives outside every packaged-evidence
|
|
43
|
+
* prefix, so the manifest records its PATH and never its bytes. Mutating the
|
|
44
|
+
* ledger's contents leaves the manifest check passing. The two artifacts are
|
|
45
|
+
* independent in content; what couples them is a shared INPUT. Editing a
|
|
46
|
+
* `copy-overwrite` template moves bytes both of them record, so both go stale
|
|
47
|
+
* together — which is why the message below sends a template change to
|
|
48
|
+
* regenerate the ledger too, and says nothing of the sort for any other path.
|
|
49
|
+
*
|
|
50
|
+
* @module scripts/lib/upstream-manifest-staleness
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/** Marker identifying a template whose bytes the Lisa-owned hash ledger records. */
|
|
54
|
+
const COPY_OVERWRITE = "/copy-overwrite/";
|
|
55
|
+
|
|
56
|
+
/** Longest list of paths a refusal prints before summarizing the remainder. */
|
|
57
|
+
const MAX_LISTED_PATHS = 10;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {object} ManifestInputs
|
|
61
|
+
* @property {Map<string, string>} evidence - Hash-pinned path to sha256 digest.
|
|
62
|
+
* @property {Set<string>} surface - Every tracked path recorded at generation.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {object} ManifestDiagnosis
|
|
67
|
+
* @property {string[]} changed - Pinned paths whose recorded digest no longer matches.
|
|
68
|
+
* @property {string[]} added - Paths present now and absent when the file was generated.
|
|
69
|
+
* @property {string[]} removed - Paths recorded then and absent now.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The body of one `Object.freeze({ … })` block in the generated module.
|
|
74
|
+
*
|
|
75
|
+
* Sliced by export name rather than matched with one regex over the whole file,
|
|
76
|
+
* because the public-commit block renders `<sha>: true,` — the same shape as a
|
|
77
|
+
* tracked-surface entry. A single sweep reads 40-character SHAs as file paths
|
|
78
|
+
* and then reports thousands of phantom removals.
|
|
79
|
+
* @param {string} source - Generated module source.
|
|
80
|
+
* @param {string} exportName - Name of the exported constant to slice.
|
|
81
|
+
* @returns {string} The block body, or an empty string when it is absent.
|
|
82
|
+
*/
|
|
83
|
+
function blockFor(source, exportName) {
|
|
84
|
+
const start = source.indexOf(`export const ${exportName}`);
|
|
85
|
+
if (start === -1) return "";
|
|
86
|
+
const open = source.indexOf("Object.freeze({", start);
|
|
87
|
+
if (open === -1) return "";
|
|
88
|
+
const close = source.indexOf("\n });", open);
|
|
89
|
+
if (close === -1) return "";
|
|
90
|
+
return source.slice(open, close);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The inputs a generated manifest says it was built from.
|
|
95
|
+
* @param {string} source - Contents of `src/core/upstream-evidence-manifest.ts`.
|
|
96
|
+
* @returns {ManifestInputs} Recorded evidence digests and tracked-surface paths.
|
|
97
|
+
*/
|
|
98
|
+
export function parseManifestInputs(source) {
|
|
99
|
+
const evidence = new Map();
|
|
100
|
+
for (const match of blockFor(source, "UPSTREAM_EVIDENCE_MANIFEST").matchAll(
|
|
101
|
+
/^ {4}"([^"]+)":\n {6}"([a-f0-9]{64})",$/gmu
|
|
102
|
+
)) {
|
|
103
|
+
evidence.set(match[1], match[2]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const surface = new Set();
|
|
107
|
+
for (const match of blockFor(source, "UPSTREAM_SURFACE_MANIFEST").matchAll(
|
|
108
|
+
/^ {4}(?:"([^"]+)"|([A-Za-z_$][A-Za-z0-9_$]*)): true,$/gmu
|
|
109
|
+
)) {
|
|
110
|
+
surface.add(match[1] ?? match[2]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { evidence, surface };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* What moved between the checked-in manifest and a fresh regeneration.
|
|
118
|
+
*
|
|
119
|
+
* Direction matters: `current` is what is on disk and `fresh` is the truth, so
|
|
120
|
+
* a path in `fresh` alone was staged after generation and a path in `current`
|
|
121
|
+
* alone was removed after it.
|
|
122
|
+
* @param {string} current - The manifest as it exists on disk.
|
|
123
|
+
* @param {string} fresh - The manifest as it would be generated right now.
|
|
124
|
+
* @returns {ManifestDiagnosis} Inputs that changed, were added, or were removed.
|
|
125
|
+
*/
|
|
126
|
+
export function diagnoseStaleManifest(current, fresh) {
|
|
127
|
+
const before = parseManifestInputs(current);
|
|
128
|
+
const after = parseManifestInputs(fresh);
|
|
129
|
+
|
|
130
|
+
const changed = [...after.evidence.entries()]
|
|
131
|
+
.filter(
|
|
132
|
+
([file, digest]) =>
|
|
133
|
+
before.evidence.has(file) && before.evidence.get(file) !== digest
|
|
134
|
+
)
|
|
135
|
+
.map(([file]) => file)
|
|
136
|
+
.sort();
|
|
137
|
+
const added = [...after.surface]
|
|
138
|
+
.filter(file => !before.surface.has(file))
|
|
139
|
+
.sort();
|
|
140
|
+
const removed = [...before.surface]
|
|
141
|
+
.filter(file => !after.surface.has(file))
|
|
142
|
+
.sort();
|
|
143
|
+
|
|
144
|
+
return { changed, added, removed };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Render at most `MAX_LISTED_PATHS` paths, then say how many were withheld.
|
|
149
|
+
* @param {readonly string[]} paths - Paths to render, already sorted.
|
|
150
|
+
* @returns {string} Indented lines, one path each.
|
|
151
|
+
*/
|
|
152
|
+
function listPaths(paths) {
|
|
153
|
+
const shown = paths
|
|
154
|
+
.slice(0, MAX_LISTED_PATHS)
|
|
155
|
+
.map(file => ` ${file}`)
|
|
156
|
+
.join("\n");
|
|
157
|
+
const withheld = paths.length - MAX_LISTED_PATHS;
|
|
158
|
+
return withheld > 0 ? `${shown}\n …and ${withheld} more` : shown;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The operator-facing refusal for a stale manifest.
|
|
163
|
+
*
|
|
164
|
+
* Written for whoever is standing at the gate, which is not necessarily whoever
|
|
165
|
+
* wrote the generator: it names the input that moved, why it usually moves, and
|
|
166
|
+
* the command that clears it.
|
|
167
|
+
* @param {ManifestDiagnosis} diagnosis - Output of {@link diagnoseStaleManifest}.
|
|
168
|
+
* @returns {string} Multi-line refusal text.
|
|
169
|
+
*/
|
|
170
|
+
export function describeStaleManifest(diagnosis) {
|
|
171
|
+
const sections = ["src/core/upstream-evidence-manifest.ts is stale."];
|
|
172
|
+
|
|
173
|
+
if (diagnosis.changed.length > 0) {
|
|
174
|
+
sections.push(
|
|
175
|
+
" Its inputs moved after it was generated. These files no longer have\n" +
|
|
176
|
+
" the bytes it recorded:\n" +
|
|
177
|
+
`${listPaths(diagnosis.changed)}\n` +
|
|
178
|
+
" At commit time lint-staged reformats every staged file BEFORE this\n" +
|
|
179
|
+
" check runs, so a manifest generated first is stale by the time it is\n" +
|
|
180
|
+
" checked. Regenerate now, after the reformat."
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (diagnosis.added.length > 0 || diagnosis.removed.length > 0) {
|
|
185
|
+
const movements = [];
|
|
186
|
+
if (diagnosis.added.length > 0) {
|
|
187
|
+
movements.push(
|
|
188
|
+
` Staged since it was generated:\n${listPaths(diagnosis.added)}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (diagnosis.removed.length > 0) {
|
|
192
|
+
movements.push(
|
|
193
|
+
` Removed since it was generated:\n${listPaths(diagnosis.removed)}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
sections.push(
|
|
197
|
+
" The tracked file set moved after it was generated.\n" +
|
|
198
|
+
`${movements.join("\n")}\n` +
|
|
199
|
+
" The manifest reads TRACKED files, so `git add` (or `git rm`) first and\n" +
|
|
200
|
+
" regenerate second — the other order records the pre-change file set."
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
diagnosis.changed.length === 0 &&
|
|
206
|
+
diagnosis.added.length === 0 &&
|
|
207
|
+
diagnosis.removed.length === 0
|
|
208
|
+
) {
|
|
209
|
+
sections.push(
|
|
210
|
+
" None of its inputs moved — only the generated file's own bytes differ,\n" +
|
|
211
|
+
" so something rewrote it after generation: a hand edit, or a formatter\n" +
|
|
212
|
+
" reaching the artifact. It is generated; do not edit it by hand."
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
sections.push(
|
|
217
|
+
" Fix: bun run build:upstream-evidence-manifest\n" +
|
|
218
|
+
" Then: git add src/core/upstream-evidence-manifest.ts"
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (
|
|
222
|
+
[...diagnosis.changed, ...diagnosis.added, ...diagnosis.removed].some(
|
|
223
|
+
file => file.includes(COPY_OVERWRITE)
|
|
224
|
+
)
|
|
225
|
+
) {
|
|
226
|
+
sections.push(
|
|
227
|
+
" A copy-overwrite template is among the files above, so the Lisa-owned\n" +
|
|
228
|
+
" hash ledger records those same bytes and is stale for the same reason.\n" +
|
|
229
|
+
" Regenerate it in this commit too, or its own check fails next:\n" +
|
|
230
|
+
" bun run build:lisa-owned-hash-ledger"
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return sections.join("\n\n");
|
|
235
|
+
}
|