@codyswann/lisa 2.322.2 → 2.322.4

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 (76) hide show
  1. package/dist/core/upstream-evidence-manifest.js +4 -4
  2. package/package.json +1 -1
  3. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  4. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  5. package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +23 -1
  6. package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +50 -4
  7. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +16 -8
  8. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +16 -7
  9. package/plugins/lisa/skills/lisa-remote-dispatch/scripts/dispatch.mjs +23 -1
  10. package/plugins/lisa/skills/lisa-setup-automations/scripts/generate-workflow.mjs +50 -4
  11. package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +16 -8
  12. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +16 -7
  13. package/plugins/lisa-agy/plugin.json +1 -1
  14. package/plugins/lisa-agy/skills/lisa-remote-dispatch/scripts/dispatch.mjs +23 -1
  15. package/plugins/lisa-agy/skills/lisa-setup-automations/scripts/generate-workflow.mjs +50 -4
  16. package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +16 -8
  17. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +16 -7
  18. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  21. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-copilot/skills/lisa-remote-dispatch/scripts/dispatch.mjs +23 -1
  25. package/plugins/lisa-copilot/skills/lisa-setup-automations/scripts/generate-workflow.mjs +50 -4
  26. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +16 -8
  27. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +16 -7
  28. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-cursor/skills/lisa-remote-dispatch/scripts/dispatch.mjs +23 -1
  30. package/plugins/lisa-cursor/skills/lisa-setup-automations/scripts/generate-workflow.mjs +50 -4
  31. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +16 -8
  32. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +16 -7
  33. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-expo-agy/plugin.json +1 -1
  36. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  46. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  51. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  56. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-rails-agy/plugin.json +1 -1
  61. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  65. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  66. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  71. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  73. package/plugins/src/base/skills/lisa-remote-dispatch/scripts/dispatch.mjs +23 -1
  74. package/plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs +50 -4
  75. package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +16 -8
  76. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +16 -7
@@ -527,7 +527,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
527
527
  "plugins/src/base/skills/lisa-quality-review/SKILL.md": "17ba45f4c4b7877c0b0c9a3c1cc1cb9e18c642ead74ff7263698d8034d3241da",
528
528
  "plugins/src/base/skills/lisa-queue-status/SKILL.md": "87c6d34d0511d4afd812d1af3fee80fa3b7aa47db8449760b12f2699fedc1d78",
529
529
  "plugins/src/base/skills/lisa-remote-dispatch/SKILL.md": "2cc999d2c3b61b10aabb849e0833fd58edb444c5013c05108dd58e5d7d60c4ba",
530
- "plugins/src/base/skills/lisa-remote-dispatch/scripts/dispatch.mjs": "6ce559964964c65154d2105a017c751bba086dcb8b4ea209ea42ce7b4fdb7d63",
530
+ "plugins/src/base/skills/lisa-remote-dispatch/scripts/dispatch.mjs": "c473e8eddf0fd5f220a5ea7ff9eb92f5482cfb2893e77edce59acc36671418a3",
531
531
  "plugins/src/base/skills/lisa-repair-intake/SKILL.md": "4f85ac1381631e9f250d9cf3239baba633ae0df9ba1959f8835ed662592e2d77",
532
532
  "plugins/src/base/skills/lisa-reproduce-bug/SKILL.md": "4d460993fac6021219ca23eee29b1b9afa7c37479dddc662b2fcfaca510edd68",
533
533
  "plugins/src/base/skills/lisa-research/SKILL.md": "19de7c5910b117c4b3b1bb7ae42cf9c8b6ff2a376b2e21961526bc7ebde18e85",
@@ -550,7 +550,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
550
550
  "plugins/src/base/skills/lisa-sentry-access/SKILL.md": "ecaf49b42bbf399781a3ebd0b1595e5a8ac44422537832e0442b2f184da0c83c",
551
551
  "plugins/src/base/skills/lisa-setup-atlassian/SKILL.md": "3820aecb57d184fde1cfa988e7e1402d8f2a766a0b3ffbb576de5a6a72b3d5c5",
552
552
  "plugins/src/base/skills/lisa-setup-automations/SKILL.md": "3ad004148a54571a50df90f23abec71e8510ef3ea3562ac812f8e2e11beb157a",
553
- "plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs": "bfb3867cc62dd37d84367859e4d796edb7a0c046595b3e0194ddb0b6d3b63d61",
553
+ "plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs": "f639c3f174378f0068b9c9d56e59b356c6da988730a1390c61e4348b4a4073ac",
554
554
  "plugins/src/base/skills/lisa-setup-confluence/SKILL.md": "e92d762dbdaeae671c3e52dfe8e10d40a10b606b5e050589181a3898649faedc",
555
555
  "plugins/src/base/skills/lisa-setup-github-repo/SKILL.md": "b9b223a5ffad2f88e3fde5831b60fea9bdc2c981750c49d03bb046d43772a0d3",
556
556
  "plugins/src/base/skills/lisa-setup-github/SKILL.md": "75435e81130abd21c678d302b7a220da95a19868d122ca93168278628b200666",
@@ -559,10 +559,10 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
559
559
  "plugins/src/base/skills/lisa-setup-linear/SKILL.md": "ec76f832a6df6f58b163f7f5ba6826ed2fcbddc91e44a8372e874858e4d0c518",
560
560
  "plugins/src/base/skills/lisa-setup-notion/SKILL.md": "f5a1e9290789fd1c33675168d30461fb24a11c98a433ef777c1f536bc2f905ef",
561
561
  "plugins/src/base/skills/lisa-setup-remote-aws/SKILL.md": "80fbf157f9c562c033886c25a99b37356602edd9e61cd2d492f339769ddcf97e",
562
- "plugins/src/base/skills/lisa-setup-remote-env/SKILL.md": "e6530485e2564f35592a28bc5e547d185a38bf2a061feb210d533304acdc5cc2",
562
+ "plugins/src/base/skills/lisa-setup-remote-env/SKILL.md": "4fe124678cfc2b37188370af3b4e16895fcbad4d4b6f13b0fc7ac02bd99b0d26",
563
563
  "plugins/src/base/skills/lisa-setup-remote-env/assets/session-start.sh": "6e3871ec2f8d56b8ebb85376ebdd3956f3ed8fa4f7b278c2ed90ca9b8845897c",
564
564
  "plugins/src/base/skills/lisa-setup-remote-env/assets/setup.sh": "7f7cf8a2248dbaa31f2f039fdaf147d083427a21633f7cf06ad612f38344d6c1",
565
- "plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs": "c3fedb3f9cc50e706c197129cfe69baf705a96ab93194152c6d2f952f2d6d3b7",
565
+ "plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs": "e8f1e74dd29104dc880eb2540024549d18d2a9c958b2b6fce0610ab0afd55cbb",
566
566
  "plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs": "ff66d33ba41a068d09be18f81ac68988238c2afa1d4e3733ae906b73eea74324",
567
567
  "plugins/src/base/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs": "82bc2a27a0a5afb2ff413a88365c619fd7f0eaada5de3ccabf2000938f0607a4",
568
568
  "plugins/src/base/skills/lisa-setup-sonar/SKILL.md": "53fbd8acce4b5e47e88195a7b90d5be424fa6ef7ad872f52c50467c690664c3b",
package/package.json CHANGED
@@ -115,7 +115,7 @@
115
115
  "brace-expansion": ">=5.0.9"
116
116
  },
117
117
  "name": "@codyswann/lisa",
118
- "version": "2.322.2",
118
+ "version": "2.322.4",
119
119
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
120
120
  "main": "dist/index.js",
121
121
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.322.2",
3
+ "version": "2.322.4",
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": "2.322.2",
3
+ "version": "2.322.4",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -416,6 +416,28 @@ function resolveBearerToken(block) {
416
416
  }
417
417
  }
418
418
 
419
+ /**
420
+ * How each agent spells "run this skill".
421
+ *
422
+ * Codex invokes a skill with `$name`, Claude with `/name`. The prefix is the
423
+ * whole invocation: get it wrong and the agent reads a sentence that merely
424
+ * mentions a skill rather than a command that runs one — and being a capable
425
+ * model it will often do *something*, which is worse than failing, because the
426
+ * run looks successful while executing none of the skill's contract.
427
+ *
428
+ * Nothing downstream catches it either. The routine accepts any text, so the
429
+ * dispatch succeeds, a session identifier is recorded, and the ledger says the
430
+ * work was handed off. Only reading the session shows otherwise.
431
+ * @param {string} surface Execution surface.
432
+ * @param {string} skill Skill slug, without a prefix.
433
+ * @param {string} rest The payload the skill is invoked with.
434
+ * @returns {string} The thin invocation to send.
435
+ */
436
+ export function buildInvocation(surface, skill, rest) {
437
+ const prefix = surface === "claude-web" ? "/" : "$";
438
+ return `${prefix}${skill} ${rest}`.trim();
439
+ }
440
+
419
441
  async function main() {
420
442
  const { skill, raw } = splitSkillFlag(process.argv.slice(2));
421
443
  const { params, rest } = parseInvocation(raw);
@@ -432,7 +454,7 @@ async function main() {
432
454
  // The invocation stays thin on purpose. Every durable instruction lives in
433
455
  // the repository-local skill, so an interactive run, a scheduled run, and a
434
456
  // recovery run all execute one contract.
435
- const prompt = `$${skill} ${rest}`.trim();
457
+ const prompt = buildInvocation(surface, skill, rest);
436
458
 
437
459
  if (surface === "claude-web") {
438
460
  const { sessionId, url } = await dispatchClaudeWeb(block, prompt, rest);
@@ -62,6 +62,51 @@ export function readAutomation(name, cwd = process.cwd()) {
62
62
  };
63
63
  }
64
64
 
65
+ /**
66
+ * Emit shell that locates a Lisa skill script before running it.
67
+ *
68
+ * A hardcoded `.claude/skills/...` path is wrong in exactly the place this
69
+ * workflow runs. Claude and Codex receive Lisa skills as an installed plugin
70
+ * living in the user's home directory, which is emphatically not part of a
71
+ * clone — and a scheduled run is always a fresh clone with no plugin install.
72
+ * So the one path that reliably exists on a runner is the npm package, at the
73
+ * version the project pins, which is the version its automation should run.
74
+ *
75
+ * The same candidate list the remote-env entrypoint uses, for the same reason:
76
+ * the in-checkout copies are cheapest and need nothing installed, and
77
+ * `node_modules` is the fallback that makes the plugin-delivered harnesses work
78
+ * at all.
79
+ * @param {string} skill Skill slug that owns the script.
80
+ * @param {string} script Script filename.
81
+ * @param {string[]} args Arguments appended to the invocation, one per line.
82
+ * @returns {string} Shell, indented for a workflow `run:` block.
83
+ */
84
+ function runSkillScript(skill, script, args) {
85
+ const candidates = [
86
+ `.claude/skills/${skill}/scripts/${script}`,
87
+ `.agents/skills/${skill}/scripts/${script}`,
88
+ `.codex/skills/${skill}/scripts/${script}`,
89
+ `node_modules/@codyswann/lisa/plugins/lisa/skills/${skill}/scripts/${script}`,
90
+ ]
91
+ .map(candidate => ` "${candidate}"`)
92
+ .join(" \\\n");
93
+ const tail = args.map(arg => ` ${arg}`).join(" \\\n");
94
+ return ` runner=""
95
+ for candidate in \\
96
+ ${candidates}; do
97
+ if [ -f "$candidate" ]; then runner="$candidate"; break; fi
98
+ done
99
+ if [ -z "$runner" ]; then
100
+ echo "cannot find ${skill}/scripts/${script}" >&2
101
+ echo "Checked the agent skill directories and node_modules." >&2
102
+ echo "Lisa skills reach Claude and Codex as an installed plugin," >&2
103
+ echo "so node_modules is the only copy a runner ever has." >&2
104
+ exit 1
105
+ fi
106
+ node "$runner" \\
107
+ ${tail}`;
108
+ }
109
+
65
110
  /**
66
111
  * Render the workflow YAML for one loop.
67
112
  *
@@ -112,7 +157,7 @@ export function renderWorkflow(name, loop) {
112
157
  ${bootstrap}: \${{ secrets.${bootstrap} }}
113
158
  run: |
114
159
  set -euo pipefail
115
- node .claude/skills/lisa-secrets-access/scripts/rotate-secret.mjs leases`;
160
+ ${runSkillScript("lisa-secrets-access", "rotate-secret.mjs", ["leases"])}`;
116
161
 
117
162
  const trigger = loop.enabled
118
163
  ? ` schedule:\n - cron: '${loop.schedule}'\n workflow_dispatch:`
@@ -172,9 +217,10 @@ jobs:
172
217
  ${bootstrap}: \${{ secrets.${bootstrap} }}
173
218
  run: |
174
219
  set -euo pipefail
175
- node .claude/skills/lisa-remote-dispatch/scripts/dispatch.mjs \\
176
- 'executionEnv=${loop.executionEnv} ${loop.payload ?? ""}' \\
177
- --skill ${loop.skill ?? `lisa-${name}`}
220
+ ${runSkillScript("lisa-remote-dispatch", "dispatch.mjs", [
221
+ `'executionEnv=${loop.executionEnv} ${loop.payload ?? ""}'`,
222
+ `--skill ${loop.skill ?? `lisa-${name}`}`,
223
+ ])}
178
224
 
179
225
  ${rotation}
180
226
  `;
@@ -13,8 +13,8 @@ Prepare a remote surface so a host project can execute there. Today that means *
13
13
  The remote environment's own configuration fields stay **one line into the repository**. Nothing else is pasted into a vendor UI.
14
14
 
15
15
  ```text
16
- setup: for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] && exec bash "$f"; done; echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1
17
- maintenance: for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] && exec bash "$f"; done; echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1
16
+ setup: n=0; rc=0; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; exit "$rc"
17
+ maintenance: n=0; rc=0; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; exit "$rc"
18
18
  ```
19
19
 
20
20
  **That line is identical for every project AND every surface.** Nothing in it names the
@@ -34,10 +34,18 @@ and neither mentions `$HOME`. An earlier version of this field used
34
34
  the checkout is not under `$HOME` at all: the glob matches nothing and bash reports
35
35
  `No such file or directory` for a path still containing a literal `*`.
36
36
 
37
- `exec` on the first hit means no subshell and no `ls` output to parse, and the explicit
38
- `exit 1` means a missing entrypoint says so rather than the field silently succeeding. The
39
- script then anchors itself on the repository root, so it behaves the same however it was
40
- reached.
37
+ **Every** match is prepared, not the first. A Claude Code web environment can hold more than
38
+ one checkout, and stopping at the first glob hit would prepare whichever repository sorts
39
+ first and silently ignore the rest arbitrary rather than merely limited. Each script
40
+ anchors itself on its own repository root, and each project's secrets land under its own
41
+ `secrets.namespace`, so preparing several is well defined rather than a collision.
42
+
43
+ The exit status is the **first failure**, and every checkout is still attempted: one broken
44
+ repository must not hide the state of the others, and it must not report success either.
45
+
46
+ The explicit `exit 1` on `n=0` means a missing entrypoint says so rather than the field
47
+ silently succeeding — a `for` loop over a glob that matches nothing otherwise exits `0`,
48
+ which is the quiet failure this whole section exists to avoid.
41
49
 
42
50
  They are the same command. A container may be built fresh or resumed from cache; every step is idempotent and version-aware, so running it twice is correct, and running it on resume is what picks up a rotated value, an edited note, or a changed version pin.
43
51
 
@@ -156,8 +164,8 @@ When emitting, produce exactly:
156
164
  ```text
157
165
  Environment name: <project> remote executor
158
166
  Repository: <org>/<repo> (must be the default checkout)
159
- Setup script: for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] && exec bash "$f"; done; echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1
160
- Maintenance: for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] && exec bash "$f"; done; echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1
167
+ Setup script: n=0; rc=0; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; exit "$rc"
168
+ Maintenance: n=0; rc=0; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; exit "$rc"
161
169
  (identical for every project — the script finds the
162
170
  checkout and installs from the committed lockfile)
163
171
  Environment vars: LISA_SECRETS_SURFACE=codex-cloud
@@ -308,20 +308,29 @@ export function installAssets(cwd = process.cwd()) {
308
308
  * at all — so a `$HOME` glob matched nothing there and bash was handed a path
309
309
  * still containing a literal asterisk.
310
310
  *
311
- * Both candidates are therefore tried relative to cwd. `exec` on the first hit
312
- * avoids a subshell and avoids parsing `ls`; the explicit `exit 1` means a
313
- * missing entrypoint says so rather than the field quietly succeeding. The
314
- * script then anchors itself on the repository root and installs dependencies
315
- * from whichever lockfile the project commits.
311
+ * Both candidates are therefore tried relative to cwd, and EVERY match is
312
+ * prepared rather than the first. A Claude Code web environment can hold more
313
+ * than one checkout, so stopping at the first hit would prepare whichever
314
+ * repository sorts first and silently ignore the rest arbitrary rather than
315
+ * merely limited. Each script anchors itself on its own repository root and
316
+ * each project materializes under its own `secrets.namespace`, so preparing
317
+ * several is well defined rather than a collision.
318
+ *
319
+ * The status is the first failure and every checkout is still attempted: one
320
+ * broken repository must neither hide the others nor report success. The
321
+ * explicit `exit 1` on no matches matters because a `for` loop over a glob that
322
+ * matches nothing otherwise exits 0 — the quiet success this guards against.
316
323
  *
317
324
  * A field that named the repository and package manager was a string a human
318
325
  * had to get right, in a settings box with no review, no version history and no
319
326
  * test — and the logic it encoded belongs in a file that has all three.
320
327
  */
321
328
  export const SETUP_FIELD =
329
+ "n=0; rc=0; " +
322
330
  "for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; " +
323
- 'do [ -f "$f" ] && exec bash "$f"; done; ' +
324
- 'echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1';
331
+ 'do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; ' +
332
+ '[ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; ' +
333
+ 'exit "$rc"';
325
334
 
326
335
  /**
327
336
  * The settings block that wires the session-start hook into a repository.
@@ -416,6 +416,28 @@ function resolveBearerToken(block) {
416
416
  }
417
417
  }
418
418
 
419
+ /**
420
+ * How each agent spells "run this skill".
421
+ *
422
+ * Codex invokes a skill with `$name`, Claude with `/name`. The prefix is the
423
+ * whole invocation: get it wrong and the agent reads a sentence that merely
424
+ * mentions a skill rather than a command that runs one — and being a capable
425
+ * model it will often do *something*, which is worse than failing, because the
426
+ * run looks successful while executing none of the skill's contract.
427
+ *
428
+ * Nothing downstream catches it either. The routine accepts any text, so the
429
+ * dispatch succeeds, a session identifier is recorded, and the ledger says the
430
+ * work was handed off. Only reading the session shows otherwise.
431
+ * @param {string} surface Execution surface.
432
+ * @param {string} skill Skill slug, without a prefix.
433
+ * @param {string} rest The payload the skill is invoked with.
434
+ * @returns {string} The thin invocation to send.
435
+ */
436
+ export function buildInvocation(surface, skill, rest) {
437
+ const prefix = surface === "claude-web" ? "/" : "$";
438
+ return `${prefix}${skill} ${rest}`.trim();
439
+ }
440
+
419
441
  async function main() {
420
442
  const { skill, raw } = splitSkillFlag(process.argv.slice(2));
421
443
  const { params, rest } = parseInvocation(raw);
@@ -432,7 +454,7 @@ async function main() {
432
454
  // The invocation stays thin on purpose. Every durable instruction lives in
433
455
  // the repository-local skill, so an interactive run, a scheduled run, and a
434
456
  // recovery run all execute one contract.
435
- const prompt = `$${skill} ${rest}`.trim();
457
+ const prompt = buildInvocation(surface, skill, rest);
436
458
 
437
459
  if (surface === "claude-web") {
438
460
  const { sessionId, url } = await dispatchClaudeWeb(block, prompt, rest);
@@ -62,6 +62,51 @@ export function readAutomation(name, cwd = process.cwd()) {
62
62
  };
63
63
  }
64
64
 
65
+ /**
66
+ * Emit shell that locates a Lisa skill script before running it.
67
+ *
68
+ * A hardcoded `.claude/skills/...` path is wrong in exactly the place this
69
+ * workflow runs. Claude and Codex receive Lisa skills as an installed plugin
70
+ * living in the user's home directory, which is emphatically not part of a
71
+ * clone — and a scheduled run is always a fresh clone with no plugin install.
72
+ * So the one path that reliably exists on a runner is the npm package, at the
73
+ * version the project pins, which is the version its automation should run.
74
+ *
75
+ * The same candidate list the remote-env entrypoint uses, for the same reason:
76
+ * the in-checkout copies are cheapest and need nothing installed, and
77
+ * `node_modules` is the fallback that makes the plugin-delivered harnesses work
78
+ * at all.
79
+ * @param {string} skill Skill slug that owns the script.
80
+ * @param {string} script Script filename.
81
+ * @param {string[]} args Arguments appended to the invocation, one per line.
82
+ * @returns {string} Shell, indented for a workflow `run:` block.
83
+ */
84
+ function runSkillScript(skill, script, args) {
85
+ const candidates = [
86
+ `.claude/skills/${skill}/scripts/${script}`,
87
+ `.agents/skills/${skill}/scripts/${script}`,
88
+ `.codex/skills/${skill}/scripts/${script}`,
89
+ `node_modules/@codyswann/lisa/plugins/lisa/skills/${skill}/scripts/${script}`,
90
+ ]
91
+ .map(candidate => ` "${candidate}"`)
92
+ .join(" \\\n");
93
+ const tail = args.map(arg => ` ${arg}`).join(" \\\n");
94
+ return ` runner=""
95
+ for candidate in \\
96
+ ${candidates}; do
97
+ if [ -f "$candidate" ]; then runner="$candidate"; break; fi
98
+ done
99
+ if [ -z "$runner" ]; then
100
+ echo "cannot find ${skill}/scripts/${script}" >&2
101
+ echo "Checked the agent skill directories and node_modules." >&2
102
+ echo "Lisa skills reach Claude and Codex as an installed plugin," >&2
103
+ echo "so node_modules is the only copy a runner ever has." >&2
104
+ exit 1
105
+ fi
106
+ node "$runner" \\
107
+ ${tail}`;
108
+ }
109
+
65
110
  /**
66
111
  * Render the workflow YAML for one loop.
67
112
  *
@@ -112,7 +157,7 @@ export function renderWorkflow(name, loop) {
112
157
  ${bootstrap}: \${{ secrets.${bootstrap} }}
113
158
  run: |
114
159
  set -euo pipefail
115
- node .claude/skills/lisa-secrets-access/scripts/rotate-secret.mjs leases`;
160
+ ${runSkillScript("lisa-secrets-access", "rotate-secret.mjs", ["leases"])}`;
116
161
 
117
162
  const trigger = loop.enabled
118
163
  ? ` schedule:\n - cron: '${loop.schedule}'\n workflow_dispatch:`
@@ -172,9 +217,10 @@ jobs:
172
217
  ${bootstrap}: \${{ secrets.${bootstrap} }}
173
218
  run: |
174
219
  set -euo pipefail
175
- node .claude/skills/lisa-remote-dispatch/scripts/dispatch.mjs \\
176
- 'executionEnv=${loop.executionEnv} ${loop.payload ?? ""}' \\
177
- --skill ${loop.skill ?? `lisa-${name}`}
220
+ ${runSkillScript("lisa-remote-dispatch", "dispatch.mjs", [
221
+ `'executionEnv=${loop.executionEnv} ${loop.payload ?? ""}'`,
222
+ `--skill ${loop.skill ?? `lisa-${name}`}`,
223
+ ])}
178
224
 
179
225
  ${rotation}
180
226
  `;
@@ -13,8 +13,8 @@ Prepare a remote surface so a host project can execute there. Today that means *
13
13
  The remote environment's own configuration fields stay **one line into the repository**. Nothing else is pasted into a vendor UI.
14
14
 
15
15
  ```text
16
- setup: for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] && exec bash "$f"; done; echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1
17
- maintenance: for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] && exec bash "$f"; done; echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1
16
+ setup: n=0; rc=0; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; exit "$rc"
17
+ maintenance: n=0; rc=0; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; exit "$rc"
18
18
  ```
19
19
 
20
20
  **That line is identical for every project AND every surface.** Nothing in it names the
@@ -34,10 +34,18 @@ and neither mentions `$HOME`. An earlier version of this field used
34
34
  the checkout is not under `$HOME` at all: the glob matches nothing and bash reports
35
35
  `No such file or directory` for a path still containing a literal `*`.
36
36
 
37
- `exec` on the first hit means no subshell and no `ls` output to parse, and the explicit
38
- `exit 1` means a missing entrypoint says so rather than the field silently succeeding. The
39
- script then anchors itself on the repository root, so it behaves the same however it was
40
- reached.
37
+ **Every** match is prepared, not the first. A Claude Code web environment can hold more than
38
+ one checkout, and stopping at the first glob hit would prepare whichever repository sorts
39
+ first and silently ignore the rest arbitrary rather than merely limited. Each script
40
+ anchors itself on its own repository root, and each project's secrets land under its own
41
+ `secrets.namespace`, so preparing several is well defined rather than a collision.
42
+
43
+ The exit status is the **first failure**, and every checkout is still attempted: one broken
44
+ repository must not hide the state of the others, and it must not report success either.
45
+
46
+ The explicit `exit 1` on `n=0` means a missing entrypoint says so rather than the field
47
+ silently succeeding — a `for` loop over a glob that matches nothing otherwise exits `0`,
48
+ which is the quiet failure this whole section exists to avoid.
41
49
 
42
50
  They are the same command. A container may be built fresh or resumed from cache; every step is idempotent and version-aware, so running it twice is correct, and running it on resume is what picks up a rotated value, an edited note, or a changed version pin.
43
51
 
@@ -156,8 +164,8 @@ When emitting, produce exactly:
156
164
  ```text
157
165
  Environment name: <project> remote executor
158
166
  Repository: <org>/<repo> (must be the default checkout)
159
- Setup script: for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] && exec bash "$f"; done; echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1
160
- Maintenance: for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] && exec bash "$f"; done; echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1
167
+ Setup script: n=0; rc=0; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; exit "$rc"
168
+ Maintenance: n=0; rc=0; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; exit "$rc"
161
169
  (identical for every project — the script finds the
162
170
  checkout and installs from the committed lockfile)
163
171
  Environment vars: LISA_SECRETS_SURFACE=codex-cloud
@@ -308,20 +308,29 @@ export function installAssets(cwd = process.cwd()) {
308
308
  * at all — so a `$HOME` glob matched nothing there and bash was handed a path
309
309
  * still containing a literal asterisk.
310
310
  *
311
- * Both candidates are therefore tried relative to cwd. `exec` on the first hit
312
- * avoids a subshell and avoids parsing `ls`; the explicit `exit 1` means a
313
- * missing entrypoint says so rather than the field quietly succeeding. The
314
- * script then anchors itself on the repository root and installs dependencies
315
- * from whichever lockfile the project commits.
311
+ * Both candidates are therefore tried relative to cwd, and EVERY match is
312
+ * prepared rather than the first. A Claude Code web environment can hold more
313
+ * than one checkout, so stopping at the first hit would prepare whichever
314
+ * repository sorts first and silently ignore the rest arbitrary rather than
315
+ * merely limited. Each script anchors itself on its own repository root and
316
+ * each project materializes under its own `secrets.namespace`, so preparing
317
+ * several is well defined rather than a collision.
318
+ *
319
+ * The status is the first failure and every checkout is still attempted: one
320
+ * broken repository must neither hide the others nor report success. The
321
+ * explicit `exit 1` on no matches matters because a `for` loop over a glob that
322
+ * matches nothing otherwise exits 0 — the quiet success this guards against.
316
323
  *
317
324
  * A field that named the repository and package manager was a string a human
318
325
  * had to get right, in a settings box with no review, no version history and no
319
326
  * test — and the logic it encoded belongs in a file that has all three.
320
327
  */
321
328
  export const SETUP_FIELD =
329
+ "n=0; rc=0; " +
322
330
  "for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh; " +
323
- 'do [ -f "$f" ] && exec bash "$f"; done; ' +
324
- 'echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1';
331
+ 'do [ -f "$f" ] || continue; n=$((n+1)); bash "$f" || rc=$?; done; ' +
332
+ '[ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found under $PWD" >&2; exit 1; }; ' +
333
+ 'exit "$rc"';
325
334
 
326
335
  /**
327
336
  * The settings block that wires the session-start hook into a repository.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.322.2",
3
+ "version": "2.322.4",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -416,6 +416,28 @@ function resolveBearerToken(block) {
416
416
  }
417
417
  }
418
418
 
419
+ /**
420
+ * How each agent spells "run this skill".
421
+ *
422
+ * Codex invokes a skill with `$name`, Claude with `/name`. The prefix is the
423
+ * whole invocation: get it wrong and the agent reads a sentence that merely
424
+ * mentions a skill rather than a command that runs one — and being a capable
425
+ * model it will often do *something*, which is worse than failing, because the
426
+ * run looks successful while executing none of the skill's contract.
427
+ *
428
+ * Nothing downstream catches it either. The routine accepts any text, so the
429
+ * dispatch succeeds, a session identifier is recorded, and the ledger says the
430
+ * work was handed off. Only reading the session shows otherwise.
431
+ * @param {string} surface Execution surface.
432
+ * @param {string} skill Skill slug, without a prefix.
433
+ * @param {string} rest The payload the skill is invoked with.
434
+ * @returns {string} The thin invocation to send.
435
+ */
436
+ export function buildInvocation(surface, skill, rest) {
437
+ const prefix = surface === "claude-web" ? "/" : "$";
438
+ return `${prefix}${skill} ${rest}`.trim();
439
+ }
440
+
419
441
  async function main() {
420
442
  const { skill, raw } = splitSkillFlag(process.argv.slice(2));
421
443
  const { params, rest } = parseInvocation(raw);
@@ -432,7 +454,7 @@ async function main() {
432
454
  // The invocation stays thin on purpose. Every durable instruction lives in
433
455
  // the repository-local skill, so an interactive run, a scheduled run, and a
434
456
  // recovery run all execute one contract.
435
- const prompt = `$${skill} ${rest}`.trim();
457
+ const prompt = buildInvocation(surface, skill, rest);
436
458
 
437
459
  if (surface === "claude-web") {
438
460
  const { sessionId, url } = await dispatchClaudeWeb(block, prompt, rest);
@@ -62,6 +62,51 @@ export function readAutomation(name, cwd = process.cwd()) {
62
62
  };
63
63
  }
64
64
 
65
+ /**
66
+ * Emit shell that locates a Lisa skill script before running it.
67
+ *
68
+ * A hardcoded `.claude/skills/...` path is wrong in exactly the place this
69
+ * workflow runs. Claude and Codex receive Lisa skills as an installed plugin
70
+ * living in the user's home directory, which is emphatically not part of a
71
+ * clone — and a scheduled run is always a fresh clone with no plugin install.
72
+ * So the one path that reliably exists on a runner is the npm package, at the
73
+ * version the project pins, which is the version its automation should run.
74
+ *
75
+ * The same candidate list the remote-env entrypoint uses, for the same reason:
76
+ * the in-checkout copies are cheapest and need nothing installed, and
77
+ * `node_modules` is the fallback that makes the plugin-delivered harnesses work
78
+ * at all.
79
+ * @param {string} skill Skill slug that owns the script.
80
+ * @param {string} script Script filename.
81
+ * @param {string[]} args Arguments appended to the invocation, one per line.
82
+ * @returns {string} Shell, indented for a workflow `run:` block.
83
+ */
84
+ function runSkillScript(skill, script, args) {
85
+ const candidates = [
86
+ `.claude/skills/${skill}/scripts/${script}`,
87
+ `.agents/skills/${skill}/scripts/${script}`,
88
+ `.codex/skills/${skill}/scripts/${script}`,
89
+ `node_modules/@codyswann/lisa/plugins/lisa/skills/${skill}/scripts/${script}`,
90
+ ]
91
+ .map(candidate => ` "${candidate}"`)
92
+ .join(" \\\n");
93
+ const tail = args.map(arg => ` ${arg}`).join(" \\\n");
94
+ return ` runner=""
95
+ for candidate in \\
96
+ ${candidates}; do
97
+ if [ -f "$candidate" ]; then runner="$candidate"; break; fi
98
+ done
99
+ if [ -z "$runner" ]; then
100
+ echo "cannot find ${skill}/scripts/${script}" >&2
101
+ echo "Checked the agent skill directories and node_modules." >&2
102
+ echo "Lisa skills reach Claude and Codex as an installed plugin," >&2
103
+ echo "so node_modules is the only copy a runner ever has." >&2
104
+ exit 1
105
+ fi
106
+ node "$runner" \\
107
+ ${tail}`;
108
+ }
109
+
65
110
  /**
66
111
  * Render the workflow YAML for one loop.
67
112
  *
@@ -112,7 +157,7 @@ export function renderWorkflow(name, loop) {
112
157
  ${bootstrap}: \${{ secrets.${bootstrap} }}
113
158
  run: |
114
159
  set -euo pipefail
115
- node .claude/skills/lisa-secrets-access/scripts/rotate-secret.mjs leases`;
160
+ ${runSkillScript("lisa-secrets-access", "rotate-secret.mjs", ["leases"])}`;
116
161
 
117
162
  const trigger = loop.enabled
118
163
  ? ` schedule:\n - cron: '${loop.schedule}'\n workflow_dispatch:`
@@ -172,9 +217,10 @@ jobs:
172
217
  ${bootstrap}: \${{ secrets.${bootstrap} }}
173
218
  run: |
174
219
  set -euo pipefail
175
- node .claude/skills/lisa-remote-dispatch/scripts/dispatch.mjs \\
176
- 'executionEnv=${loop.executionEnv} ${loop.payload ?? ""}' \\
177
- --skill ${loop.skill ?? `lisa-${name}`}
220
+ ${runSkillScript("lisa-remote-dispatch", "dispatch.mjs", [
221
+ `'executionEnv=${loop.executionEnv} ${loop.payload ?? ""}'`,
222
+ `--skill ${loop.skill ?? `lisa-${name}`}`,
223
+ ])}
178
224
 
179
225
  ${rotation}
180
226
  `;