@codyswann/lisa 4.33.0 → 4.33.2

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 (72) hide show
  1. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +74 -0
  2. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +16 -1
  3. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  4. package/dist/core/lisa-owned-hash-ledger.js +4 -0
  5. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  6. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  7. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  8. package/dist/core/upstream-evidence-manifest.js +7 -4
  9. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  10. package/package.json +4 -4
  11. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  13. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +58 -5
  14. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +189 -5
  15. package/plugins/lisa/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +58 -5
  16. package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +189 -5
  17. package/plugins/lisa-agy/plugin.json +1 -1
  18. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +58 -5
  19. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +189 -5
  20. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  23. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +58 -5
  27. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +189 -5
  28. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +58 -5
  30. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +189 -5
  31. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo-agy/plugin.json +1 -1
  34. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  54. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails-agy/plugin.json +1 -1
  59. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  64. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  69. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  71. package/plugins/src/base/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +58 -5
  72. package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +189 -5
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.33.0",
3
+ "version": "4.33.2",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.33.0",
3
+ "version": "4.33.2",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -22,6 +22,7 @@ import {
22
22
  chmodSync,
23
23
  existsSync,
24
24
  mkdirSync,
25
+ readdirSync,
25
26
  readFileSync,
26
27
  renameSync,
27
28
  rmSync,
@@ -30,6 +31,17 @@ import {
30
31
  import { homedir } from "node:os";
31
32
  import { dirname, join } from "node:path";
32
33
 
34
+ /**
35
+ * The one grammar a bootstrap key may take: a single safe path segment.
36
+ *
37
+ * Named once and shared by the guard that enforces it on the way in
38
+ * ({@link assertKey}) and the filter that applies it on the way out
39
+ * ({@link listBootstrapFiles}). Two copies of this expression would eventually
40
+ * disagree, and the direction that matters is the lax one: a name accepted by
41
+ * the reader but rejected by the writer is a name this module never wrote.
42
+ */
43
+ const KEY_GRAMMAR = /^[A-Za-z0-9._-]+$/;
44
+
33
45
  /**
34
46
  * Where a file-backed bootstrap lives, for platforms with no keychain.
35
47
  * @param {string} key Bootstrap variable name.
@@ -57,11 +69,7 @@ export function bootstrapFile(key, env = process.env) {
57
69
  * @returns {string} The key, unchanged, when valid.
58
70
  */
59
71
  export function assertKey(key) {
60
- if (
61
- typeof key !== "string" ||
62
- !/^[A-Za-z0-9._-]+$/.test(key) ||
63
- key === ".."
64
- ) {
72
+ if (typeof key !== "string" || !KEY_GRAMMAR.test(key) || key === "..") {
65
73
  throw new Error(
66
74
  `bootstrap key must be one safe path segment, got ${JSON.stringify(key)}`
67
75
  );
@@ -230,3 +238,48 @@ export function readBootstrapFile(key, env = process.env) {
230
238
  if (!existsSync(path)) return "";
231
239
  return readFileSync(path, "utf8").trim();
232
240
  }
241
+
242
+ /**
243
+ * Every file-backed bootstrap NAME this machine holds. Never a value.
244
+ *
245
+ * Exists so a failed lookup can say what is actually here instead of only what
246
+ * was missing. Those are different sentences: "no bootstrap credential is
247
+ * provisioned" and "one is provisioned under a different name" have different
248
+ * remedies, and a message that cannot tell them apart gets read as the first
249
+ * when it means the second (CodySwannGT/lisa#3555).
250
+ *
251
+ * Absence is empty, never an error. This runs on a path that is already
252
+ * failing, and a diagnostic that throws replaces the real message with its own.
253
+ *
254
+ * Two filters, and both are load-bearing because these names are rendered into
255
+ * an operator-facing message and into a `.lisa.config.json` snippet the
256
+ * operator is invited to paste:
257
+ *
258
+ * - **Grammar.** `readdirSync` returns whatever is in the directory. A name
259
+ * outside {@link KEY_GRAMMAR} is not a key this module would ever have
260
+ * written, and one carrying a quote or a newline would corrupt the suggested
261
+ * JSON rather than merely look odd. The same grammar `assertKey` enforces on
262
+ * the way in is applied on the way out.
263
+ * - **Dotfiles.** The reader is deliberately STRICTER than the writer here.
264
+ * `KEY_GRAMMAR` permits a leading dot, so `assertKey(".DS_Store")` passes —
265
+ * and macOS writes exactly that file into any directory it browses. Listing
266
+ * it would offer the operator a credential named `.DS_Store`. Being stricter
267
+ * on read is the safe direction: it can only omit a name, never invent one.
268
+ * - **Content.** An empty file is not a credential. Offering one sends the
269
+ * operator to a store that will fail the same way a second time — and the
270
+ * environment scan already refuses empty values, so listing empty FILES would
271
+ * have made the two stores disagree about what counts as provisioned.
272
+ * @param {Record<string, string|undefined>} [env] Environment to read.
273
+ * @returns {string[]} Bootstrap names that hold a credential, sorted.
274
+ */
275
+ export function listBootstrapFiles(env = process.env) {
276
+ try {
277
+ const root = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
278
+ return readdirSync(join(root, "lisa", "bootstrap"))
279
+ .filter(name => KEY_GRAMMAR.test(name) && !name.startsWith("."))
280
+ .filter(name => readBootstrapFile(name, env) !== "")
281
+ .sort();
282
+ } catch {
283
+ return [];
284
+ }
285
+ }
@@ -9,7 +9,7 @@
9
9
  * @module providers
10
10
  */
11
11
 
12
- import { readBootstrapFile } from "./bootstrap-store.mjs";
12
+ import { listBootstrapFiles, readBootstrapFile } from "./bootstrap-store.mjs";
13
13
 
14
14
  import {
15
15
  boundedChildOutput,
@@ -138,10 +138,194 @@ export function bootstrapToken(bootstrap) {
138
138
  : "";
139
139
  if (found) return found;
140
140
  }
141
- throw new Error(
142
- `${bootstrap.key} not found in: ${bootstrap.sources.join(", ")}.\n` +
143
- `It is the bootstrap credential — without it no other secret can be read.`
144
- );
141
+ throw new Error(describeMissingBootstrap(bootstrap));
142
+ }
143
+
144
+ /**
145
+ * The name Lisa looked up, reduced to the provider prefix it was built from.
146
+ *
147
+ * `BWS_ACCESS_TOKEN_lisa` → `BWS_ACCESS_TOKEN`. Used to find the SIBLINGS of a
148
+ * missing key: names that differ only in their tenant suffix are the ones an
149
+ * operator can actually point this project at.
150
+ * @param {string} key The bootstrap variable name that was looked up.
151
+ * @returns {string} The provider prefix, or the key when it carries no suffix.
152
+ */
153
+ function bootstrapPrefix(key) {
154
+ for (const canonical of Object.values(PROVIDER_BOOTSTRAP_ENV)) {
155
+ if (key === canonical || key.startsWith(`${canonical}_`)) return canonical;
156
+ }
157
+ return key;
158
+ }
159
+
160
+ /**
161
+ * Keychain SERVICE NAMES on this machine. Never a password.
162
+ *
163
+ * `dump-keychain` without `-d` prints metadata only, so this cannot carry a
164
+ * credential into an error message. Bounded and wrapped: it runs on a path that
165
+ * is already failing, where a throw would replace the real message with its own
166
+ * and a hang would replace it with nothing.
167
+ * @returns {string[]} Service names, or empty when unavailable.
168
+ */
169
+ function keychainServiceNames() {
170
+ if (process.platform !== "darwin") return [];
171
+ try {
172
+ const dump = boundedChildOutput("security", ["dump-keychain"], {
173
+ encoding: "utf8",
174
+ stdio: ["ignore", "pipe", "ignore"],
175
+ timeout: 5000,
176
+ });
177
+ return [...String(dump).matchAll(/"svce"<blob>="([^"]*)"/g)].map(
178
+ ([, name]) => name
179
+ );
180
+ } catch {
181
+ // A locked, absent, or oversized keychain leaves the list empty.
182
+ return [];
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Bootstrap NAMES this machine holds under the same provider prefix.
188
+ *
189
+ * Names only — no value is read, and none is ever reported.
190
+ *
191
+ * Every source is injectable because the alternative is a test that asserts
192
+ * against whatever credentials the developer's own workstation happens to hold.
193
+ * Such a test passes or fails by accident, and it fails on the one machine that
194
+ * reproduces the bug (CodySwannGT/lisa#3555).
195
+ * @param {string} prefix Provider bootstrap prefix, e.g. `BWS_ACCESS_TOKEN`.
196
+ * @param {object} [deps] Injected sources for testing.
197
+ * @returns {Array<{name: string, where: string}>} What exists, sorted by name.
198
+ */
199
+ export function discoverBootstrapNames(prefix, deps = {}) {
200
+ const env = deps.env ?? process.env;
201
+ const matches = name => name === prefix || name.startsWith(`${prefix}_`);
202
+ const found = new Map();
203
+ const note = (name, where) => {
204
+ if (matches(name) && !found.has(name)) found.set(name, where);
205
+ };
206
+
207
+ for (const name of Object.keys(env)) {
208
+ if ((env[name] ?? "").trim()) note(name, "env");
209
+ }
210
+ for (const name of (deps.listFiles ?? listBootstrapFiles)()) {
211
+ note(name, "bootstrap file");
212
+ }
213
+ for (const name of (deps.keychainNames ?? keychainServiceNames)()) {
214
+ note(name, "keychain");
215
+ }
216
+
217
+ return [...found.entries()]
218
+ .map(([name, where]) => ({ name, where }))
219
+ .sort((a, b) => a.name.localeCompare(b.name));
220
+ }
221
+
222
+ /**
223
+ * Whether the provider's own CLI is resolvable on PATH.
224
+ *
225
+ * Asked because the failure this message replaces was read as a MISSING BINARY
226
+ * by two separate sessions, one of which turned that reading into a standing
227
+ * instruction telling other agents not to bother installing it. The binary was
228
+ * on PATH the whole time. Stating which of the two is true costs one
229
+ * `command -v` and removes the wrong reading entirely.
230
+ *
231
+ * **Presence only, and the message says exactly that.** `command -v` proves the
232
+ * name resolves, never that the binary runs, is the right architecture, or is
233
+ * authorised. Reporting it as "installed and working" would be a second
234
+ * confidently-worded overclaim in the same message whose first overclaim is the
235
+ * defect being fixed. Actually proving "working" means executing the provider
236
+ * CLI on a path that is already failing, which is a cost and a side effect this
237
+ * diagnostic has no business incurring.
238
+ * @param {string} prefix Provider bootstrap prefix.
239
+ * @returns {boolean|null} PATH presence, or null when the provider has no CLI.
240
+ */
241
+ export function providerCliPresent(prefix) {
242
+ const cli = { BWS_ACCESS_TOKEN: "bws", DOPPLER_TOKEN: "doppler" }[prefix];
243
+ if (!cli) return null;
244
+ try {
245
+ return Boolean(
246
+ String(
247
+ boundedChildOutput("command", ["-v", cli], {
248
+ encoding: "utf8",
249
+ shell: true,
250
+ stdio: ["ignore", "pipe", "ignore"],
251
+ timeout: 5000,
252
+ })
253
+ ).trim()
254
+ );
255
+ } catch {
256
+ return false;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Explain a bootstrap that was not found, distinguishing the two causes.
262
+ *
263
+ * The message this replaces was accurate and still produced a wrong conclusion
264
+ * twice, because it answered only "what was missing" and never "what is here".
265
+ * Lisa looks up `<PREFIX>_<namespace>` where the namespace defaults to the
266
+ * project's own name, so a workstation holding one credential provisioned for
267
+ * one project fails closed in every OTHER project — and reads, from inside
268
+ * those projects, as though the provider were unavailable on the machine.
269
+ * @param {{sources: string[], key: string}} bootstrap Bootstrap config.
270
+ * @param {object} [deps] Injected sources for testing.
271
+ * @returns {string} The operator-facing explanation.
272
+ */
273
+ export function describeMissingBootstrap(bootstrap, deps = {}) {
274
+ const prefix = bootstrapPrefix(bootstrap.key);
275
+ const siblings = (deps.discover ?? discoverBootstrapNames)(
276
+ prefix,
277
+ deps
278
+ ).filter(entry => entry.name !== bootstrap.key);
279
+ const cli = (deps.cliPresent ?? providerCliPresent)(prefix);
280
+
281
+ const lines = [
282
+ `${bootstrap.key} not found in: ${bootstrap.sources.join(", ")}.`,
283
+ `It is the bootstrap credential — without it no other secret can be read.`,
284
+ ``,
285
+ ];
286
+
287
+ if (siblings.length === 0) {
288
+ lines.push(
289
+ `No bootstrap credential of any name was found on this machine, so none`,
290
+ `is provisioned here yet. Store one with:`,
291
+ ``,
292
+ ` lisa secrets bootstrap --key ${bootstrap.key}`
293
+ );
294
+ } else {
295
+ lines.push(
296
+ `This is a NAME MISMATCH, not a missing credential. ${siblings.length === 1 ? "A bootstrap credential exists" : "Bootstrap credentials exist"} here`,
297
+ `under ${siblings.length === 1 ? "a different name" : "different names"}:`,
298
+ ``,
299
+ ...siblings.map(entry => ` ${entry.name} (${entry.where})`),
300
+ ``,
301
+ `Lisa looks up ${prefix}_<namespace>, where <namespace> defaults to this`,
302
+ `project's own name — so a credential provisioned for one project is not`,
303
+ `found from any other. Point this project at an existing one by setting`,
304
+ `its name in .lisa.config.json:`,
305
+ ``,
306
+ ` { "secrets": { "bootstrap": { "key": "${siblings[0].name}" } } }`,
307
+ ``,
308
+ `or by exporting ${bootstrap.key} in the environment.`
309
+ );
310
+ }
311
+
312
+ if (cli !== null) {
313
+ lines.push(``);
314
+ lines.push(
315
+ ...(cli
316
+ ? [
317
+ `The provider CLI was found on PATH, so this is not a missing-binary`,
318
+ `problem and reinstalling it will not help. Only the credential NAME`,
319
+ `is wrong.`,
320
+ ]
321
+ : [
322
+ `Separately, the provider CLI was NOT found on PATH. Both that and the`,
323
+ `credential name above need resolving.`,
324
+ ])
325
+ );
326
+ }
327
+
328
+ return lines.join("\n");
145
329
  }
146
330
 
147
331
  /**