@codyswann/lisa 4.73.1 → 4.73.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +20 -0
  2. package/all/copy-overwrite/scripts/lib/history-secret-git.mjs +158 -5
  3. package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +25 -1
  4. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +137 -163
  5. package/all/copy-overwrite/scripts/lisa-enforcement-freshness.mjs +180 -0
  6. package/all/copy-overwrite/scripts/lisa-history-secrets.mjs +17 -3
  7. package/all/copy-overwrite/scripts/lisa-hooks/block-blind-automerge.sh +86 -7
  8. package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +7 -1
  9. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +62 -7
  10. package/all/copy-overwrite/scripts/lisa-rails-prepush.mjs +4 -1
  11. package/all/copy-overwrite/scripts/lisa-work-item.mjs +191 -5
  12. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +5 -5
  13. package/all/deletions.json +19 -2
  14. package/dist/codex/scripts/block-no-verify.sh +62 -7
  15. package/dist/core/config.d.ts +16 -0
  16. package/dist/core/config.d.ts.map +1 -1
  17. package/dist/core/config.js.map +1 -1
  18. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  19. package/dist/core/lisa-owned-hash-ledger.js +88 -0
  20. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  21. package/dist/core/lisa.d.ts +31 -0
  22. package/dist/core/lisa.d.ts.map +1 -1
  23. package/dist/core/lisa.js +69 -4
  24. package/dist/core/lisa.js.map +1 -1
  25. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  26. package/dist/core/retire-unmodified.d.ts +66 -0
  27. package/dist/core/retire-unmodified.d.ts.map +1 -0
  28. package/dist/core/retire-unmodified.js +244 -0
  29. package/dist/core/retire-unmodified.js.map +1 -0
  30. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  31. package/dist/core/upstream-evidence-manifest.js +39 -21
  32. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  33. package/dist/opencode/plugin-templates/block-managed-file-edits.sh +7 -1
  34. package/dist/opencode/plugin-templates/block-no-verify.sh +62 -7
  35. package/package.json +4 -4
  36. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa/hooks/auto-update.mjs +69 -1
  39. package/plugins/lisa/hooks/block-blind-automerge.sh +86 -7
  40. package/plugins/lisa/hooks/block-managed-file-edits.sh +7 -1
  41. package/plugins/lisa/hooks/block-no-verify.agy.sh +62 -7
  42. package/plugins/lisa/hooks/block-no-verify.sh +62 -7
  43. package/plugins/lisa-agy/hooks/block-blind-automerge.sh +86 -7
  44. package/plugins/lisa-agy/hooks/block-managed-file-edits.sh +7 -1
  45. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +62 -7
  46. package/plugins/lisa-agy/plugin.json +1 -1
  47. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  50. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-copilot/hooks/auto-update.mjs +69 -1
  54. package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +86 -7
  55. package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +7 -1
  56. package/plugins/lisa-copilot/hooks/block-no-verify.sh +62 -7
  57. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-cursor/hooks/auto-update.mjs +69 -1
  59. package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +86 -7
  60. package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +7 -1
  61. package/plugins/lisa-cursor/hooks/block-no-verify.sh +62 -7
  62. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-expo-agy/plugin.json +1 -1
  65. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  70. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  75. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  79. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  80. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  84. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  85. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  89. package/plugins/lisa-rails-agy/plugin.json +1 -1
  90. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  95. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  99. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  100. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  102. package/plugins/materialized-artifacts.json +1 -0
  103. package/plugins/src/base/hooks/auto-update.mjs +69 -1
  104. package/plugins/src/base/hooks/block-blind-automerge.sh +86 -7
  105. package/plugins/src/base/hooks/block-managed-file-edits.sh +7 -1
  106. package/plugins/src/base/hooks/block-no-verify.agy.sh +62 -7
  107. package/plugins/src/base/hooks/block-no-verify.sh +62 -7
  108. package/scripts/build-plugins.sh +7 -0
  109. package/scripts/check-shipped-surface-removals.mjs +22 -2
  110. package/scripts/lisa-enforcement-fallback.sh +137 -163
  111. package/scripts/lisa-enforcement-freshness.mjs +177 -0
  112. package/scripts/two-channel-couplings.json +1 -1
  113. package/typescript/copy-overwrite/.prettierignore +1 -0
package/README.md CHANGED
@@ -333,6 +333,26 @@ agent configuration changes. This retirement
333
333
  boundary belongs to the Codex project overlay. The other supported agents do
334
334
  not delete this shell-command layout during apply.
335
335
 
336
+ Fallback diagnostics compare each selected host guard entry file with the
337
+ installed package's template, separately from its historical apply receipt.
338
+ Matching bytes need no apply repair; differing bytes and unknown evidence are
339
+ reported separately. Installed plugin channel versions remain independent
340
+ evidence and do not prove those hooks are live. The optional Node helper ships
341
+ beside the fallback; without it, enforcement still runs and freshness is unknown.
342
+ Diagnostics are offline and lazy: at most eight guard/template pairs (1 MiB per
343
+ file), four 64 KiB manifests/receipts and one 4 MiB plugin registry, with one
344
+ additional byte per read to detect overflow. After the session notice, permitted
345
+ calls invoke no freshness helper. Later refusals re-evaluate their own evidence.
346
+
347
+ | Agent | Fallback diagnostic surface |
348
+ | --- | --- |
349
+ | Claude Code | Repository PreToolUse fallback; independent plugin guards remain registered. |
350
+ | Codex | Tagged project hook dispatches the same delivered fallback after project trust. |
351
+ | Cursor | Native individual guard registrations; the common host fallback is delivered, but its aggregate diagnostic is not registered natively. |
352
+ | OpenCode | Native TypeScript guard adapters; the common host fallback is delivered, but its aggregate diagnostic is not represented by those adapters. |
353
+ | Antigravity | Native adapted guard commands; the common host fallback is delivered, but its aggregate diagnostic is not registered natively. |
354
+ | Copilot | Native individual guard registrations; the common host fallback is delivered, but its aggregate diagnostic is not registered natively. |
355
+
336
356
  Remote coding environments use one vendor-neutral AWS bootstrap rather than
337
357
  repository-specific or agent-specific IAM users. Run `/lisa:setup-remote-aws`
338
358
  to install the common setup script and native Cursor/Copilot adapters; Claude,
@@ -44,12 +44,15 @@ export const gitEnvironment = () => {
44
44
  };
45
45
  };
46
46
 
47
+ /** Replacement refs must never redirect which objects a proof reads. */
48
+ const NO_REPLACE_OBJECTS = "--no-replace-objects";
49
+
47
50
  /** Git errors can contain arbitrary payloads; only authored guidance leaves here. */
48
- export const gitRead = (args, cwd) => {
51
+ export const gitRead = (args, cwd, input) => {
49
52
  const result = spawnSync(
50
53
  "git",
51
54
  [
52
- "--no-replace-objects",
55
+ NO_REPLACE_OBJECTS,
53
56
  "-c",
54
57
  "diff.external=",
55
58
  "-c",
@@ -60,6 +63,7 @@ export const gitRead = (args, cwd) => {
60
63
  cwd,
61
64
  env: gitEnvironment(),
62
65
  encoding: "utf8",
66
+ input,
63
67
  timeout: 120000,
64
68
  maxBuffer: 64 * 1024 * 1024,
65
69
  }
@@ -151,8 +155,143 @@ export const eventPairs = (event, name, width) => {
151
155
  );
152
156
  };
153
157
 
158
+ /**
159
+ * Git configuration exactly as the push itself sees it.
160
+ *
161
+ * gitEnvironment pins configuration so history reads cannot be steered, which
162
+ * is right for walking objects and wrong for naming a destination: the push
163
+ * resolved its URL, `pushurl`, `pushInsteadOf` and credentials through user,
164
+ * system and environment configuration, and a destination resolved under any
165
+ * other configuration may be a different repository.
166
+ */
167
+ const remoteEnvironment = () => {
168
+ const environment = gitEnvironment();
169
+ for (const key of Object.keys(environment))
170
+ if (key.startsWith("GIT_CONFIG")) delete environment[key];
171
+ for (const [key, value] of Object.entries(process.env))
172
+ if (key.startsWith("GIT_CONFIG") && value !== undefined)
173
+ environment[key] = value;
174
+ // Never wait on a person: no terminal prompt and no askpass program, so a
175
+ // destination that needs an unavailable credential fails at once.
176
+ return { ...environment, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
177
+ };
178
+
179
+ /**
180
+ * Run one destination-resolution read under the push's own configuration.
181
+ * @param {string[]} args Git arguments.
182
+ * @param {string} cwd Repository directory.
183
+ * @returns {string | null} Trimmed stdout, or null when Git refused or failed.
184
+ */
185
+ const remoteRead = (args, cwd) => {
186
+ const result = spawnSync("git", [NO_REPLACE_OBJECTS, ...args], {
187
+ cwd,
188
+ env: remoteEnvironment(),
189
+ encoding: "utf8",
190
+ timeout: 60000,
191
+ maxBuffer: 64 * 1024 * 1024,
192
+ stdio: ["ignore", "pipe", "ignore"],
193
+ });
194
+ // probe-direction: fail-closed — every caller treats null as "the
195
+ // destination is unproved", which subtracts nothing from the scan.
196
+ if (result.error || result.signal || result.status !== 0) return null;
197
+ return result.stdout.trim();
198
+ };
199
+
200
+ /**
201
+ * The repository a push is actually going to, from Git's pre-push arguments.
202
+ *
203
+ * Git calls the hook with the remote's name (or the URL, when the push named
204
+ * no remote) and the URL it pushes to, already rewritten by `pushInsteadOf`.
205
+ * That URL is the destination, so it wins when present. A wrapper that
206
+ * forwards only a remote NAME gets that remote's push URL, resolved under the
207
+ * push's own configuration; a remote with several push URLs, or a bare URL
208
+ * with no `$2`, cannot be pinned to one destination and returns null. A value
209
+ * that looks like an option is never used.
210
+ * @param {string[]} remoteArgs Git's pre-push `$1` and `$2`, as forwarded.
211
+ * @param {string} cwd Repository directory.
212
+ * @returns {string | null} The destination, or null when it cannot be proved.
213
+ */
214
+ export const pushDestination = (remoteArgs, cwd) => {
215
+ const [remote, url] = remoteArgs ?? [];
216
+ const usable = value =>
217
+ typeof value === "string" && value !== "" && !value.startsWith("-");
218
+ if (usable(url)) return url;
219
+ if (!usable(remote)) return null;
220
+ const names = (remoteRead(["remote"], cwd) ?? "").split("\n");
221
+ if (!names.includes(remote)) return null;
222
+ const urls = (
223
+ remoteRead(["remote", "get-url", "--push", "--all", "--", remote], cwd) ??
224
+ ""
225
+ )
226
+ .split("\n")
227
+ .filter(Boolean);
228
+ return urls.length === 1 ? urls[0] : null;
229
+ };
230
+
231
+ /**
232
+ * Commit IDs the push destination advertises right now that exist locally.
233
+ *
234
+ * Asked of the destination itself (`git ls-remote`), not read from local
235
+ * tracking refs: a tracking ref can outlive a branch deleted or reset on the
236
+ * remote, and with a separate pushurl it describes a different repository, so
237
+ * either would exclude commits the destination lacks. An advertised ID this
238
+ * repository does not have cannot bound a local walk and is dropped, which
239
+ * only ever widens the scan.
240
+ * @param {string | null} destination The push destination from pushDestination.
241
+ * @param {string} cwd Repository directory.
242
+ * @returns {string[]} IDs to exclude; empty when nothing could be proved.
243
+ */
244
+ export const advertisedTips = (destination, cwd) => {
245
+ if (destination === null) return [];
246
+ // An `insteadOf` rule can rewrite even a URL Git already rewrote for the
247
+ // push, and then the probe would describe another repository. Trust the
248
+ // advertisement only when ls-remote would contact the destination itself.
249
+ // probe-direction: fail-closed — a URL that cannot be confirmed subtracts
250
+ // nothing.
251
+ if (
252
+ remoteRead(["ls-remote", "--get-url", "--", destination], cwd) !==
253
+ destination
254
+ )
255
+ return [];
256
+ const listed = spawnSync(
257
+ "git",
258
+ [NO_REPLACE_OBJECTS, "ls-remote", "--", destination],
259
+ {
260
+ cwd,
261
+ env: remoteEnvironment(),
262
+ encoding: "utf8",
263
+ timeout: 60000,
264
+ maxBuffer: 64 * 1024 * 1024,
265
+ stdio: ["ignore", "pipe", "ignore"],
266
+ }
267
+ );
268
+ // probe-direction: fail-closed — a destination that cannot be asked
269
+ // advertises nothing, so nothing is subtracted and the complete history is
270
+ // scanned.
271
+ if (listed.error || listed.signal || listed.status !== 0) return [];
272
+ const advertised = [
273
+ ...new Set(
274
+ listed.stdout
275
+ .split("\n")
276
+ .map(line => line.split("\t")[0])
277
+ .filter(id => /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(id))
278
+ ),
279
+ ];
280
+ if (advertised.length === 0) return [];
281
+ const present = gitRead(
282
+ ["cat-file", "--batch-check=%(objectname) %(objecttype)"],
283
+ cwd,
284
+ advertised.map(id => `${id}\n`).join("")
285
+ );
286
+ return present
287
+ .split("\n")
288
+ .map(line => line.split(" "))
289
+ .filter(([, type]) => type === "commit" || type === "tag")
290
+ .map(([id]) => id);
291
+ };
292
+
154
293
  /** Pairwise subtraction prevents one ref's old tip from hiding another update. */
155
- export const introducedCommits = (pairs, cwd, width) => {
294
+ export const introducedCommits = (pairs, cwd, width, remoteArgs = []) => {
156
295
  const oid = new RegExp(`^[a-f0-9]{${width}}$`, "u");
157
296
  for (const pair of pairs) {
158
297
  if (!oid.test(pair.before ?? "") || !oid.test(pair.after ?? ""))
@@ -175,6 +314,16 @@ export const introducedCommits = (pairs, cwd, width) => {
175
314
  "Legacy Git grafts prevent complete history proof. Use an unmodified complete repository checkout."
176
315
  );
177
316
  const commits = new Set();
317
+ // A new remote ref (zero `before`) has no old tip to subtract, and scanning
318
+ // everything reachable from it counts the remote's own published history as
319
+ // introduced: every new branch of an old repository then re-litigates its
320
+ // root commit. What a push introduces is what the destination does not
321
+ // already hold, so a new ref subtracts what the push destination advertises.
322
+ // Without a destination, or one that cannot be asked, nothing is subtracted
323
+ // and the complete reachable history is scanned as before.
324
+ const published = updates.some(pair => /^0+$/u.test(pair.before))
325
+ ? advertisedTips(pushDestination(remoteArgs, cwd), cwd)
326
+ : [];
178
327
  for (const { before, after } of updates) {
179
328
  const next = gitRead(["rev-parse", "--verify", `${after}^{commit}`], cwd);
180
329
  const previous = /^0+$/u.test(before)
@@ -190,9 +339,13 @@ export const introducedCommits = (pairs, cwd, width) => {
190
339
  "Required Git trees or blobs are missing. Fetch complete objects, repair the checkout and retry."
191
340
  );
192
341
  }
342
+ // Exclusions go through stdin: a remote can hold more refs than one
343
+ // command line carries.
344
+ const exclusions = previous ? [previous] : published;
193
345
  const list = gitRead(
194
- ["rev-list", next, ...(previous ? [`^${previous}`] : [])],
195
- cwd
346
+ ["rev-list", "--stdin", next],
347
+ cwd,
348
+ exclusions.map(oid => `^${oid}\n`).join("")
196
349
  );
197
350
  for (const commit of list.split("\n").filter(Boolean)) commits.add(commit);
198
351
  }
@@ -109,7 +109,31 @@ export function validateLock(lock, manifest, updates) {
109
109
  !item.link,
110
110
  "unsupported lock node"
111
111
  );
112
- const url = new URL(item.resolved);
112
+ // A bundled dependency ships inside its parent's tarball, so npm records
113
+ // no source or integrity of its own: the parent's registry integrity is
114
+ // what covers its bytes. Accept it only nested under a parent this loop
115
+ // also validates, and never when it claims a source of its own.
116
+ if (item.inBundle === true) {
117
+ required(
118
+ item.resolved === undefined && item.integrity === undefined,
119
+ "bundled lock node declares its own source"
120
+ );
121
+ const boundary = name.lastIndexOf("/node_modules/");
122
+ const parent = boundary < 0 ? "" : name.slice(0, boundary);
123
+ required(
124
+ parent.startsWith("node_modules/") &&
125
+ Object.hasOwn(lock.packages, parent),
126
+ "bundled lock node has no registry parent"
127
+ );
128
+ continue;
129
+ }
130
+ required(typeof item.resolved === "string", "registry source missing");
131
+ let url;
132
+ try {
133
+ url = new URL(item.resolved);
134
+ } catch {
135
+ required(false, "registry source unparseable");
136
+ }
113
137
  required(
114
138
  url.protocol === "https:" &&
115
139
  url.hostname === "registry.npmjs.org" &&