@codyswann/lisa 4.54.8 → 4.54.9

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 (61) hide show
  1. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  2. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  3. package/dist/core/upstream-evidence-manifest.js +2 -1
  4. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  5. package/package.json +4 -4
  6. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  8. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +99 -10
  9. package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +99 -10
  10. package/plugins/lisa-agy/plugin.json +1 -1
  11. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +99 -10
  12. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  14. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  15. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +99 -10
  19. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +99 -10
  21. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  23. package/plugins/lisa-expo-agy/plugin.json +1 -1
  24. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  34. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  39. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  44. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-rails-agy/plugin.json +1 -1
  49. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  54. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  59. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +99 -10
package/package.json CHANGED
@@ -181,7 +181,7 @@
181
181
  "zod-validation-error": "^4.0.0"
182
182
  },
183
183
  "name": "@codyswann/lisa",
184
- "version": "4.54.8",
184
+ "version": "4.54.9",
185
185
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
186
186
  "main": "dist/index.js",
187
187
  "exports": {
@@ -329,7 +329,7 @@
329
329
  "test": "tests"
330
330
  },
331
331
  "types": "./dist/index.d.ts",
332
- "lisaReleaseCommit": "bf59b54272d633d94e8e1d1ffb1bb0e072fbde01",
333
- "gitHead": "bf59b54272d633d94e8e1d1ffb1bb0e072fbde01",
334
- "lisaReleaseTag": "v4.54.8"
332
+ "lisaReleaseCommit": "a2487db25e690de0b6220e88c01d999899fe52e8",
333
+ "gitHead": "a2487db25e690de0b6220e88c01d999899fe52e8",
334
+ "lisaReleaseTag": "v4.54.9"
335
335
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -13,6 +13,8 @@ import { listBootstrapFiles, readBootstrapFile } from "./bootstrap-store.mjs";
13
13
 
14
14
  import {
15
15
  boundedChildOutput,
16
+ isChildTimeout,
17
+ SETUP_OPERATION_BUDGET_MS,
16
18
  rethrowIfChildTimeout,
17
19
  } from "../../lisa-setup-workstation/scripts/bounded-child.mjs";
18
20
 
@@ -393,7 +395,12 @@ export function fetchRaw(cfg) {
393
395
  }
394
396
 
395
397
  if (cfg.provider === "bitwarden") {
396
- const raw = run("bws", ["secret", "list", "--output", "json"], env);
398
+ const raw = run(
399
+ "bws",
400
+ ["secret", "list", "--output", "json"],
401
+ env,
402
+ "reading secrets from Bitwarden"
403
+ );
397
404
  return JSON.parse(raw || "[]").map(s => ({
398
405
  key: s.key,
399
406
  value: s.value,
@@ -407,7 +414,7 @@ export function fetchRaw(cfg) {
407
414
 
408
415
  if (cfg.provider === "doppler") {
409
416
  const args = ["secrets", "download", "--no-file", "--format", "json"];
410
- const raw = run("doppler", args, env);
417
+ const raw = run("doppler", args, env, "reading secrets from Doppler");
411
418
  return Object.entries(JSON.parse(raw || "{}")).map(([key, value]) => ({
412
419
  key,
413
420
  value: String(value),
@@ -429,12 +436,84 @@ export function fetchRaw(cfg) {
429
436
  * @param {NodeJS.ProcessEnv} env Environment for the child.
430
437
  * @returns {string} Captured stdout.
431
438
  */
432
- function run(bin, args, env) {
433
- return boundedChildOutput(bin, args, {
434
- encoding: "utf8",
435
- env,
436
- stdio: ["ignore", "pipe", "pipe"],
437
- });
439
+ function run(bin, args, env, operation = "talking to the secrets provider") {
440
+ try {
441
+ return boundedChildOutput(bin, args, {
442
+ encoding: "utf8",
443
+ env,
444
+ stdio: ["ignore", "pipe", "pipe"],
445
+ // THE OPERATION BUDGET, not the probe budget (#4045). Passing none fell
446
+ // back to `CHILD_BUDGET_MS`, which its own docstring calls "a hang
447
+ // detector, not a performance budget" — while the sibling budget's
448
+ // docstring names "secret-provider calls" explicitly. A round trip to a
449
+ // hosted vault over someone's network is neither a hang nor a local
450
+ // probe, and this is the one function every Bitwarden and Doppler call
451
+ // in the tree passes through.
452
+ timeout: PROVIDER_BUDGET_MS,
453
+ });
454
+ } catch (error) {
455
+ const timedOut = describeProviderTimeout(bin, operation, error);
456
+ // Rethrow UNCHANGED when it is not a timeout. Relabelling every failure as
457
+ // a deadline would be this defect with the sign flipped: a genuinely
458
+ // missing binary would then read as a slow one.
459
+ if (timedOut === null) throw error;
460
+ throw new Error(timedOut);
461
+ }
462
+ }
463
+
464
+ /**
465
+ * The deadline a provider CLI call is given.
466
+ *
467
+ * `SETUP_OPERATION_BUDGET_MS` rather than `CHILD_BUDGET_MS`, because
468
+ * `bounded-child.mjs` documents that budget as covering "downloads, package
469
+ * installation, secret-provider calls, or a project-declared hook", and the
470
+ * probe budget as a hang detector. Re-exported under a local name so the value
471
+ * a message quotes and the value the call enforces cannot drift apart.
472
+ */
473
+ export const PROVIDER_BUDGET_MS = SETUP_OPERATION_BUDGET_MS;
474
+
475
+ /**
476
+ * An operator-readable account of a provider CLI killed at its deadline.
477
+ *
478
+ * ## What the old message did
479
+ *
480
+ * The entire operator-facing output was `spawnSync bws ETIMEDOUT` — a binary
481
+ * name and an errno, which is the exact shape Node produces for `ENOENT`.
482
+ * MEASURED: every reader concluded the binary was missing or broken. In the
483
+ * reported session `bws 2.1.0` was installed and answering in 365–725 ms, a
484
+ * direct `bws secret list` succeeded moments later, and the operator was one
485
+ * step from routing live work to `blocked` over a working credential path.
486
+ *
487
+ * ## What this says instead, and why each part is there
488
+ *
489
+ * The operation, so the reader knows what was in flight. That the CLI RAN, in
490
+ * as many words, because that is the belief being corrected. The deadline and
491
+ * whose it is — an operator needs both: the number tells them whether a retry
492
+ * is plausible, and "Lisa's" tells them the vault reported nothing at all. A
493
+ * next step, because a message at a gate that a non-technical operator cannot
494
+ * act on is not finished.
495
+ *
496
+ * Returns null for anything that is not a killed child, so the caller rethrows
497
+ * the original untouched.
498
+ * @param {string} bin The provider executable that was run.
499
+ * @param {string} operation What the call was doing, in operator words.
500
+ * @param {unknown} error The caught value.
501
+ * @returns {string|null} The message, or null when this is not a timeout.
502
+ */
503
+ export function describeProviderTimeout(bin, operation, error) {
504
+ if (!isChildTimeout(error)) return null;
505
+ const seconds = Math.round(PROVIDER_BUDGET_MS / 1000);
506
+ return (
507
+ `${operation}: the \`${bin}\` CLI ran for ${seconds}s and Lisa stopped it ` +
508
+ `at its own deadline. This is NOT a missing or broken \`${bin}\` — the ` +
509
+ `binary was found and started, which is what separates this from the ` +
510
+ `similar-looking "spawnSync ${bin} ENOENT" you get when it is absent. The ` +
511
+ `deadline is Lisa's, so the provider reported nothing either way: no ` +
512
+ `secret was read, and nothing says your vault is unreachable. Most often ` +
513
+ `this is transient network latency — retry the command, and if it keeps ` +
514
+ `hitting the deadline check that \`${bin}\` answers on its own before ` +
515
+ `concluding the credential path is unavailable.`
516
+ );
438
517
  }
439
518
 
440
519
  /**
@@ -621,7 +700,12 @@ export function writeSecret(cfg, id, value) {
621
700
  const env = providerEnv(cfg);
622
701
 
623
702
  if (cfg.provider === "bitwarden") {
624
- run("bws", ["secret", "edit", id, "--value", value], env);
703
+ run(
704
+ "bws",
705
+ ["secret", "edit", id, "--value", value],
706
+ env,
707
+ "updating a secret in Bitwarden"
708
+ );
625
709
  return;
626
710
  }
627
711
  throw new Error(
@@ -687,7 +771,12 @@ export function removeCoordinationRecord(cfg, id) {
687
771
  const env = providerEnv(cfg);
688
772
 
689
773
  if (cfg.provider === "bitwarden") {
690
- run("bws", ["secret", "delete", id, "--output", "none"], env);
774
+ run(
775
+ "bws",
776
+ ["secret", "delete", id, "--output", "none"],
777
+ env,
778
+ "deleting a secret in Bitwarden"
779
+ );
691
780
  return;
692
781
  }
693
782
  throw new Error(
@@ -13,6 +13,8 @@ import { listBootstrapFiles, readBootstrapFile } from "./bootstrap-store.mjs";
13
13
 
14
14
  import {
15
15
  boundedChildOutput,
16
+ isChildTimeout,
17
+ SETUP_OPERATION_BUDGET_MS,
16
18
  rethrowIfChildTimeout,
17
19
  } from "../../lisa-setup-workstation/scripts/bounded-child.mjs";
18
20
 
@@ -393,7 +395,12 @@ export function fetchRaw(cfg) {
393
395
  }
394
396
 
395
397
  if (cfg.provider === "bitwarden") {
396
- const raw = run("bws", ["secret", "list", "--output", "json"], env);
398
+ const raw = run(
399
+ "bws",
400
+ ["secret", "list", "--output", "json"],
401
+ env,
402
+ "reading secrets from Bitwarden"
403
+ );
397
404
  return JSON.parse(raw || "[]").map(s => ({
398
405
  key: s.key,
399
406
  value: s.value,
@@ -407,7 +414,7 @@ export function fetchRaw(cfg) {
407
414
 
408
415
  if (cfg.provider === "doppler") {
409
416
  const args = ["secrets", "download", "--no-file", "--format", "json"];
410
- const raw = run("doppler", args, env);
417
+ const raw = run("doppler", args, env, "reading secrets from Doppler");
411
418
  return Object.entries(JSON.parse(raw || "{}")).map(([key, value]) => ({
412
419
  key,
413
420
  value: String(value),
@@ -429,12 +436,84 @@ export function fetchRaw(cfg) {
429
436
  * @param {NodeJS.ProcessEnv} env Environment for the child.
430
437
  * @returns {string} Captured stdout.
431
438
  */
432
- function run(bin, args, env) {
433
- return boundedChildOutput(bin, args, {
434
- encoding: "utf8",
435
- env,
436
- stdio: ["ignore", "pipe", "pipe"],
437
- });
439
+ function run(bin, args, env, operation = "talking to the secrets provider") {
440
+ try {
441
+ return boundedChildOutput(bin, args, {
442
+ encoding: "utf8",
443
+ env,
444
+ stdio: ["ignore", "pipe", "pipe"],
445
+ // THE OPERATION BUDGET, not the probe budget (#4045). Passing none fell
446
+ // back to `CHILD_BUDGET_MS`, which its own docstring calls "a hang
447
+ // detector, not a performance budget" — while the sibling budget's
448
+ // docstring names "secret-provider calls" explicitly. A round trip to a
449
+ // hosted vault over someone's network is neither a hang nor a local
450
+ // probe, and this is the one function every Bitwarden and Doppler call
451
+ // in the tree passes through.
452
+ timeout: PROVIDER_BUDGET_MS,
453
+ });
454
+ } catch (error) {
455
+ const timedOut = describeProviderTimeout(bin, operation, error);
456
+ // Rethrow UNCHANGED when it is not a timeout. Relabelling every failure as
457
+ // a deadline would be this defect with the sign flipped: a genuinely
458
+ // missing binary would then read as a slow one.
459
+ if (timedOut === null) throw error;
460
+ throw new Error(timedOut);
461
+ }
462
+ }
463
+
464
+ /**
465
+ * The deadline a provider CLI call is given.
466
+ *
467
+ * `SETUP_OPERATION_BUDGET_MS` rather than `CHILD_BUDGET_MS`, because
468
+ * `bounded-child.mjs` documents that budget as covering "downloads, package
469
+ * installation, secret-provider calls, or a project-declared hook", and the
470
+ * probe budget as a hang detector. Re-exported under a local name so the value
471
+ * a message quotes and the value the call enforces cannot drift apart.
472
+ */
473
+ export const PROVIDER_BUDGET_MS = SETUP_OPERATION_BUDGET_MS;
474
+
475
+ /**
476
+ * An operator-readable account of a provider CLI killed at its deadline.
477
+ *
478
+ * ## What the old message did
479
+ *
480
+ * The entire operator-facing output was `spawnSync bws ETIMEDOUT` — a binary
481
+ * name and an errno, which is the exact shape Node produces for `ENOENT`.
482
+ * MEASURED: every reader concluded the binary was missing or broken. In the
483
+ * reported session `bws 2.1.0` was installed and answering in 365–725 ms, a
484
+ * direct `bws secret list` succeeded moments later, and the operator was one
485
+ * step from routing live work to `blocked` over a working credential path.
486
+ *
487
+ * ## What this says instead, and why each part is there
488
+ *
489
+ * The operation, so the reader knows what was in flight. That the CLI RAN, in
490
+ * as many words, because that is the belief being corrected. The deadline and
491
+ * whose it is — an operator needs both: the number tells them whether a retry
492
+ * is plausible, and "Lisa's" tells them the vault reported nothing at all. A
493
+ * next step, because a message at a gate that a non-technical operator cannot
494
+ * act on is not finished.
495
+ *
496
+ * Returns null for anything that is not a killed child, so the caller rethrows
497
+ * the original untouched.
498
+ * @param {string} bin The provider executable that was run.
499
+ * @param {string} operation What the call was doing, in operator words.
500
+ * @param {unknown} error The caught value.
501
+ * @returns {string|null} The message, or null when this is not a timeout.
502
+ */
503
+ export function describeProviderTimeout(bin, operation, error) {
504
+ if (!isChildTimeout(error)) return null;
505
+ const seconds = Math.round(PROVIDER_BUDGET_MS / 1000);
506
+ return (
507
+ `${operation}: the \`${bin}\` CLI ran for ${seconds}s and Lisa stopped it ` +
508
+ `at its own deadline. This is NOT a missing or broken \`${bin}\` — the ` +
509
+ `binary was found and started, which is what separates this from the ` +
510
+ `similar-looking "spawnSync ${bin} ENOENT" you get when it is absent. The ` +
511
+ `deadline is Lisa's, so the provider reported nothing either way: no ` +
512
+ `secret was read, and nothing says your vault is unreachable. Most often ` +
513
+ `this is transient network latency — retry the command, and if it keeps ` +
514
+ `hitting the deadline check that \`${bin}\` answers on its own before ` +
515
+ `concluding the credential path is unavailable.`
516
+ );
438
517
  }
439
518
 
440
519
  /**
@@ -621,7 +700,12 @@ export function writeSecret(cfg, id, value) {
621
700
  const env = providerEnv(cfg);
622
701
 
623
702
  if (cfg.provider === "bitwarden") {
624
- run("bws", ["secret", "edit", id, "--value", value], env);
703
+ run(
704
+ "bws",
705
+ ["secret", "edit", id, "--value", value],
706
+ env,
707
+ "updating a secret in Bitwarden"
708
+ );
625
709
  return;
626
710
  }
627
711
  throw new Error(
@@ -687,7 +771,12 @@ export function removeCoordinationRecord(cfg, id) {
687
771
  const env = providerEnv(cfg);
688
772
 
689
773
  if (cfg.provider === "bitwarden") {
690
- run("bws", ["secret", "delete", id, "--output", "none"], env);
774
+ run(
775
+ "bws",
776
+ ["secret", "delete", id, "--output", "none"],
777
+ env,
778
+ "deleting a secret in Bitwarden"
779
+ );
691
780
  return;
692
781
  }
693
782
  throw new Error(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -13,6 +13,8 @@ import { listBootstrapFiles, readBootstrapFile } from "./bootstrap-store.mjs";
13
13
 
14
14
  import {
15
15
  boundedChildOutput,
16
+ isChildTimeout,
17
+ SETUP_OPERATION_BUDGET_MS,
16
18
  rethrowIfChildTimeout,
17
19
  } from "../../lisa-setup-workstation/scripts/bounded-child.mjs";
18
20
 
@@ -393,7 +395,12 @@ export function fetchRaw(cfg) {
393
395
  }
394
396
 
395
397
  if (cfg.provider === "bitwarden") {
396
- const raw = run("bws", ["secret", "list", "--output", "json"], env);
398
+ const raw = run(
399
+ "bws",
400
+ ["secret", "list", "--output", "json"],
401
+ env,
402
+ "reading secrets from Bitwarden"
403
+ );
397
404
  return JSON.parse(raw || "[]").map(s => ({
398
405
  key: s.key,
399
406
  value: s.value,
@@ -407,7 +414,7 @@ export function fetchRaw(cfg) {
407
414
 
408
415
  if (cfg.provider === "doppler") {
409
416
  const args = ["secrets", "download", "--no-file", "--format", "json"];
410
- const raw = run("doppler", args, env);
417
+ const raw = run("doppler", args, env, "reading secrets from Doppler");
411
418
  return Object.entries(JSON.parse(raw || "{}")).map(([key, value]) => ({
412
419
  key,
413
420
  value: String(value),
@@ -429,12 +436,84 @@ export function fetchRaw(cfg) {
429
436
  * @param {NodeJS.ProcessEnv} env Environment for the child.
430
437
  * @returns {string} Captured stdout.
431
438
  */
432
- function run(bin, args, env) {
433
- return boundedChildOutput(bin, args, {
434
- encoding: "utf8",
435
- env,
436
- stdio: ["ignore", "pipe", "pipe"],
437
- });
439
+ function run(bin, args, env, operation = "talking to the secrets provider") {
440
+ try {
441
+ return boundedChildOutput(bin, args, {
442
+ encoding: "utf8",
443
+ env,
444
+ stdio: ["ignore", "pipe", "pipe"],
445
+ // THE OPERATION BUDGET, not the probe budget (#4045). Passing none fell
446
+ // back to `CHILD_BUDGET_MS`, which its own docstring calls "a hang
447
+ // detector, not a performance budget" — while the sibling budget's
448
+ // docstring names "secret-provider calls" explicitly. A round trip to a
449
+ // hosted vault over someone's network is neither a hang nor a local
450
+ // probe, and this is the one function every Bitwarden and Doppler call
451
+ // in the tree passes through.
452
+ timeout: PROVIDER_BUDGET_MS,
453
+ });
454
+ } catch (error) {
455
+ const timedOut = describeProviderTimeout(bin, operation, error);
456
+ // Rethrow UNCHANGED when it is not a timeout. Relabelling every failure as
457
+ // a deadline would be this defect with the sign flipped: a genuinely
458
+ // missing binary would then read as a slow one.
459
+ if (timedOut === null) throw error;
460
+ throw new Error(timedOut);
461
+ }
462
+ }
463
+
464
+ /**
465
+ * The deadline a provider CLI call is given.
466
+ *
467
+ * `SETUP_OPERATION_BUDGET_MS` rather than `CHILD_BUDGET_MS`, because
468
+ * `bounded-child.mjs` documents that budget as covering "downloads, package
469
+ * installation, secret-provider calls, or a project-declared hook", and the
470
+ * probe budget as a hang detector. Re-exported under a local name so the value
471
+ * a message quotes and the value the call enforces cannot drift apart.
472
+ */
473
+ export const PROVIDER_BUDGET_MS = SETUP_OPERATION_BUDGET_MS;
474
+
475
+ /**
476
+ * An operator-readable account of a provider CLI killed at its deadline.
477
+ *
478
+ * ## What the old message did
479
+ *
480
+ * The entire operator-facing output was `spawnSync bws ETIMEDOUT` — a binary
481
+ * name and an errno, which is the exact shape Node produces for `ENOENT`.
482
+ * MEASURED: every reader concluded the binary was missing or broken. In the
483
+ * reported session `bws 2.1.0` was installed and answering in 365–725 ms, a
484
+ * direct `bws secret list` succeeded moments later, and the operator was one
485
+ * step from routing live work to `blocked` over a working credential path.
486
+ *
487
+ * ## What this says instead, and why each part is there
488
+ *
489
+ * The operation, so the reader knows what was in flight. That the CLI RAN, in
490
+ * as many words, because that is the belief being corrected. The deadline and
491
+ * whose it is — an operator needs both: the number tells them whether a retry
492
+ * is plausible, and "Lisa's" tells them the vault reported nothing at all. A
493
+ * next step, because a message at a gate that a non-technical operator cannot
494
+ * act on is not finished.
495
+ *
496
+ * Returns null for anything that is not a killed child, so the caller rethrows
497
+ * the original untouched.
498
+ * @param {string} bin The provider executable that was run.
499
+ * @param {string} operation What the call was doing, in operator words.
500
+ * @param {unknown} error The caught value.
501
+ * @returns {string|null} The message, or null when this is not a timeout.
502
+ */
503
+ export function describeProviderTimeout(bin, operation, error) {
504
+ if (!isChildTimeout(error)) return null;
505
+ const seconds = Math.round(PROVIDER_BUDGET_MS / 1000);
506
+ return (
507
+ `${operation}: the \`${bin}\` CLI ran for ${seconds}s and Lisa stopped it ` +
508
+ `at its own deadline. This is NOT a missing or broken \`${bin}\` — the ` +
509
+ `binary was found and started, which is what separates this from the ` +
510
+ `similar-looking "spawnSync ${bin} ENOENT" you get when it is absent. The ` +
511
+ `deadline is Lisa's, so the provider reported nothing either way: no ` +
512
+ `secret was read, and nothing says your vault is unreachable. Most often ` +
513
+ `this is transient network latency — retry the command, and if it keeps ` +
514
+ `hitting the deadline check that \`${bin}\` answers on its own before ` +
515
+ `concluding the credential path is unavailable.`
516
+ );
438
517
  }
439
518
 
440
519
  /**
@@ -621,7 +700,12 @@ export function writeSecret(cfg, id, value) {
621
700
  const env = providerEnv(cfg);
622
701
 
623
702
  if (cfg.provider === "bitwarden") {
624
- run("bws", ["secret", "edit", id, "--value", value], env);
703
+ run(
704
+ "bws",
705
+ ["secret", "edit", id, "--value", value],
706
+ env,
707
+ "updating a secret in Bitwarden"
708
+ );
625
709
  return;
626
710
  }
627
711
  throw new Error(
@@ -687,7 +771,12 @@ export function removeCoordinationRecord(cfg, id) {
687
771
  const env = providerEnv(cfg);
688
772
 
689
773
  if (cfg.provider === "bitwarden") {
690
- run("bws", ["secret", "delete", id, "--output", "none"], env);
774
+ run(
775
+ "bws",
776
+ ["secret", "delete", id, "--output", "none"],
777
+ env,
778
+ "deleting a secret in Bitwarden"
779
+ );
691
780
  return;
692
781
  }
693
782
  throw new Error(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.54.8",
3
+ "version": "4.54.9",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"