@codyswann/lisa 3.15.0 → 3.16.0

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 (79) hide show
  1. package/all/copy-overwrite/scripts/check-state-classification.mjs +3 -5
  2. package/all/copy-overwrite/scripts/lib/invoked-as-script.mjs +96 -0
  3. package/all/copy-overwrite/scripts/lisa-command-envelope.mjs +3 -5
  4. package/all/copy-overwrite/scripts/lisa-work-item.mjs +3 -5
  5. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  6. package/dist/core/lisa-owned-hash-ledger.js +19 -0
  7. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  8. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  9. package/dist/core/upstream-evidence-manifest.js +32 -17
  10. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  11. package/expo/copy-overwrite/scripts/bdd/markdown-cell.mjs +128 -0
  12. package/expo/copy-overwrite/scripts/bdd/render.mjs +1 -14
  13. package/expo/copy-overwrite/scripts/bdd-matrix.mjs +3 -3
  14. package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +3 -3
  15. package/expo/copy-overwrite/scripts/check-e2e-coverage.mjs +3 -5
  16. package/expo/copy-overwrite/scripts/classify-maestro-failures.mjs +5 -15
  17. package/expo/copy-overwrite/scripts/lib/invoked-as-script.mjs +96 -0
  18. package/package.json +1 -1
  19. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-agy/plugin.json +1 -1
  22. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  25. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo-agy/plugin.json +1 -1
  32. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  42. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  47. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  52. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails-agy/plugin.json +1 -1
  57. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  62. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  67. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  69. package/scripts/build-plugins.sh +29 -0
  70. package/scripts/check-conflict-markers.mjs +4 -5
  71. package/scripts/detect-stale-workflow-inputs.mjs +4 -5
  72. package/scripts/lib/invoked-as-script.mjs +93 -0
  73. package/scripts/plugin-parity-drift.mjs +4 -5
  74. package/scripts/plugin-routing-validate.mjs +4 -5
  75. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +2 -5
  76. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +3 -5
  77. package/typescript/copy-overwrite/scripts/check-verification-coverage.mjs +3 -5
  78. package/typescript/copy-overwrite/scripts/lib/invoked-as-script.mjs +96 -0
  79. package/ui/index.html +271 -12
@@ -0,0 +1,128 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /**
5
+ * Markdown table-cell escaping, shared by the BDD burndown renderer and the
6
+ * Maestro failure classifier.
7
+ *
8
+ * Waiver reasons, scenario titles, discovered test names and Maestro failure
9
+ * messages are repo data that lands inside a generated Markdown table. A single
10
+ * `|` ends the cell early and shifts every column after it, so the columns to
11
+ * its right silently misreport — in a generated file nobody re-reads. A line
12
+ * ending is worse: it ends the ROW, so one record becomes two and every column
13
+ * after the break is read against the wrong header.
14
+ *
15
+ * This module exists because the escaper was module-private and DUPLICATED —
16
+ * `cell` inside `scripts/bdd/render.mjs` and `escapeCell` inside
17
+ * `scripts/classify-maestro-failures.mjs`. Neither copy could be reached by a
18
+ * unit test, so both could only be exercised indirectly through a whole
19
+ * rendered document, and the two had already drifted: one stripped `\r\n` and
20
+ * `\n`, the other only `\n`. Both leaked live column separators.
21
+ *
22
+ * The contract predicates below are exported alongside it deliberately, so
23
+ * "table safe" has one definition rather than one per test. That is worth
24
+ * having and it is NOT sufficient: the first fix on this defect shipped with
25
+ * `hasUnescapedPipe` already asserting the right answer for `\\|` while `cell`
26
+ * was never fed that input, so the escaper leaked at every even backslash run
27
+ * with the yardstick sitting right there, correct and unused. A shared
28
+ * predicate only helps once something SWEEPS the subject with it. Enumerate the
29
+ * property; the named instances are documentation.
30
+ *
31
+ * @module scripts/bdd/markdown-cell
32
+ */
33
+
34
+ /**
35
+ * Escape one pipe together with the WHOLE backslash run in front of it.
36
+ *
37
+ * The rule is the same one {@link hasUnescapedPipe} measures, stated once:
38
+ * a pipe is a live column separator exactly when an EVEN number of backslashes
39
+ * (zero included) precedes it. So an even run needs one more backslash; an odd
40
+ * run is already an escape and must be left alone, or the escaper would grow
41
+ * the run on every pass and stop being idempotent.
42
+ *
43
+ * Two nearby spellings are wrong and both look right:
44
+ *
45
+ * - `/\\?\|/` — consumes at most ONE preceding backslash, so `\\|` (escaped
46
+ * backslash, live pipe) is seen as an escape that is already present and is
47
+ * passed through untouched. It leaks at every even run: measured live at
48
+ * runs 2, 4, 6 and 8. That was this defect's first fix and was itself a bug —
49
+ * the report named two INSTANCES, and fixing those is not fixing the
50
+ * property.
51
+ * - `/((?:\\\\)*)\|/` with a `"$1\\|"` replacement — an even-run pattern with
52
+ * no left anchor. The engine simply slides one character right and matches
53
+ * an even run INSIDE an odd one, so `\|` becomes `\\\|`. Measured: 60 leaks
54
+ * over the same sweep. A `(?<!\\)` lookbehind fixes it, but the arithmetic
55
+ * below says the same thing without needing that argument.
56
+ *
57
+ * @param {string} _match - The whole match; unused.
58
+ * @param {string} backslashes - The backslash run preceding the pipe.
59
+ * @returns {string} The run, escaped, plus the pipe.
60
+ */
61
+ const escapePipeRun = (_match, backslashes) =>
62
+ `${backslashes}${backslashes.length % 2 === 0 ? "\\" : ""}|`;
63
+
64
+ /**
65
+ * Make author-supplied text safe inside a Markdown table cell.
66
+ *
67
+ * Both steps exist because the obvious spelling of each was wrong, and both
68
+ * failures are invisible in review:
69
+ *
70
+ * - Pipes are escaped by RUN, not one at a time — see {@link escapePipeRun}.
71
+ * - `/\r\n?|\n/` and not `/\r?\n/`. The latter only matches a CR that is
72
+ * FOLLOWED by an LF, so a lone `\r` survives — and CommonMark treats a lone
73
+ * CR as a line ending, which ends the row rather than the cell.
74
+ *
75
+ * The `?? ""` guard is load-bearing and must stay: without it a nullish waiver
76
+ * field prints the literal text "null" or "undefined" into the ledger, which
77
+ * reads as data.
78
+ *
79
+ * SCOPE — this is SAFETY, not FIDELITY, and the difference is deliberate. The
80
+ * only promise made is that no live column separator and no line ending reach
81
+ * the table; it is NOT promised that a reader can recover the input byte for
82
+ * byte. Lone backslashes are left exactly as they arrive, so Markdown consumes
83
+ * them pairwise and a cell holding N backslashes DISPLAYS `floor(N / 2)` of
84
+ * them. A round-trip-safe escaper is one step longer —
85
+ * `.replace(/\\/g, "\\\\").replace(/\|/g, "\\|")` — and was not chosen: it
86
+ * rewrites every backslash in every waiver reason and scenario title in the
87
+ * repo, which churns the generated ledger for a fidelity nobody consumes, and
88
+ * it is not what {@link hasUnescapedPipe} measures. If a caller ever needs the
89
+ * input back out of the table, it needs that escaper and a matching
90
+ * unescaper — not a patch to this one.
91
+ * @param {unknown} text - Arbitrary cell content.
92
+ * @returns {string} Text safe to interpolate into a table row.
93
+ */
94
+ export const cell = text =>
95
+ String(text ?? "")
96
+ .replace(/(\\*)\|/g, escapePipeRun)
97
+ .replace(/\r\n?|\n/g, " ");
98
+
99
+ /**
100
+ * Whether `text` still carries a line ending. A cell containing one splits the
101
+ * table row it sits in, so this is the second half of the safety contract.
102
+ * @param {string} text - Text already passed through {@link cell}.
103
+ * @returns {boolean} True when a CR or an LF survives.
104
+ */
105
+ export const hasLineEnding = text => /[\r\n]/.test(String(text));
106
+
107
+ /**
108
+ * Whether `text` still carries a pipe that Markdown will read as a column
109
+ * separator.
110
+ *
111
+ * A pipe is escaped only when an ODD number of backslashes precedes it: `\|` is
112
+ * a literal pipe, but `\\|` is an escaped backslash followed by a LIVE pipe.
113
+ * That distinction is the whole reason this predicate is not a regex.
114
+ * @param {string} text - Text already passed through {@link cell}.
115
+ * @returns {boolean} True when a column-separating pipe survives.
116
+ */
117
+ export const hasUnescapedPipe = text => {
118
+ let backslashes = 0;
119
+ for (const character of String(text)) {
120
+ if (character === "\\") {
121
+ backslashes += 1;
122
+ continue;
123
+ }
124
+ if (character === "|" && backslashes % 2 === 0) return true;
125
+ backslashes = 0;
126
+ }
127
+ return false;
128
+ };
@@ -11,6 +11,7 @@
11
11
  * @module scripts/bdd/render
12
12
  */
13
13
  import { byCodeUnit } from "./contract.mjs";
14
+ import { cell } from "./markdown-cell.mjs";
14
15
 
15
16
  /**
16
17
  * Format a coverage figure, refusing to print "100%" for an empty
@@ -25,20 +26,6 @@ const fmt = value =>
25
26
  ? "n/a (no obligations)"
26
27
  : `${value.covered}/${value.total} (${value.percentage.toFixed(1)}%)`;
27
28
 
28
- /**
29
- * Make author-supplied text safe inside a Markdown table cell.
30
- *
31
- * Waiver reasons and scenario titles are repo data. A single `|` ends the
32
- * cell early and shifts every column after it, so the Ticket and Expires
33
- * columns silently misreport — in a generated file nobody re-reads.
34
- * @param {unknown} text - Arbitrary cell content.
35
- * @returns {string} Text safe to interpolate into a table row.
36
- */
37
- const cell = text =>
38
- String(text ?? "")
39
- .replace(/\|/g, "\\|")
40
- .replace(/\r?\n/g, " ");
41
-
42
29
  /**
43
30
  * Render the per-platform traceability table.
44
31
  * @param {object} report - The coverage report.
@@ -21,12 +21,13 @@
21
21
  */
22
22
  import * as fs from "node:fs";
23
23
  import * as path from "node:path";
24
- import { fileURLToPath, pathToFileURL } from "node:url";
24
+ import { fileURLToPath } from "node:url";
25
25
 
26
26
  import { byCodeUnit, declaredPlatforms, trackerUrl } from "./bdd/contract.mjs";
27
27
  import { loadScenarios } from "./bdd/parse.mjs";
28
28
  import { indexResults } from "./bdd/report.mjs";
29
29
  import { loadExecutionResults } from "./check-bdd-coverage.mjs";
30
+ import { invokedAsScript } from "./lib/invoked-as-script.mjs";
30
31
 
31
32
  const PACKAGE_ROOT = path.resolve(
32
33
  path.dirname(fileURLToPath(import.meta.url)),
@@ -250,5 +251,4 @@ function main() {
250
251
  console.log(`[bdd-matrix] wrote ${OUT_REL}`);
251
252
  }
252
253
 
253
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
254
- main();
254
+ if (invokedAsScript(import.meta.url)) main();
@@ -43,7 +43,7 @@
43
43
  */
44
44
  import * as fs from "node:fs";
45
45
  import * as path from "node:path";
46
- import { fileURLToPath, pathToFileURL } from "node:url";
46
+ import { fileURLToPath } from "node:url";
47
47
 
48
48
  import {
49
49
  ADOPTION_STATES,
@@ -81,6 +81,7 @@ import {
81
81
  validateTrackerTags,
82
82
  } from "./bdd/validate.mjs";
83
83
  import { ISO_DATE, validateWaivers } from "./bdd/waivers.mjs";
84
+ import { invokedAsScript } from "./lib/invoked-as-script.mjs";
84
85
 
85
86
  const PACKAGE_ROOT = path.resolve(
86
87
  path.dirname(fileURLToPath(import.meta.url)),
@@ -796,5 +797,4 @@ function printHuman(gateRun, envelope) {
796
797
  console.error(`[bdd-coverage] ${envelope.summary.headline}`);
797
798
  }
798
799
 
799
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
800
- main();
800
+ if (invokedAsScript(import.meta.url)) main();
@@ -32,7 +32,8 @@
32
32
  */
33
33
  import * as fs from "node:fs";
34
34
  import * as path from "node:path";
35
- import { pathToFileURL } from "node:url";
35
+
36
+ import { invokedAsScript } from "./lib/invoked-as-script.mjs";
36
37
 
37
38
  export const defaultThresholds = {
38
39
  playwright: { routes: 80 },
@@ -389,9 +390,6 @@ function main() {
389
390
  }
390
391
 
391
392
  // Run only when invoked directly — importing for tests must have no side effects.
392
- if (
393
- process.argv[1] &&
394
- import.meta.url === pathToFileURL(process.argv[1]).href
395
- ) {
393
+ if (invokedAsScript(import.meta.url)) {
396
394
  main();
397
395
  }
@@ -104,7 +104,9 @@
104
104
  */
105
105
  import * as fs from "node:fs";
106
106
  import * as path from "node:path";
107
- import { pathToFileURL } from "node:url";
107
+
108
+ import { cell } from "./bdd/markdown-cell.mjs";
109
+ import { invokedAsScript } from "./lib/invoked-as-script.mjs";
108
110
 
109
111
  /**
110
112
  * Wait commands whose `timeout:` is a ceiling the command burns before failing.
@@ -667,7 +669,7 @@ export function renderMarkdown({ report, failures, defects }) {
667
669
  ? `${failure.intermittent.ratePercent}% (${failure.intermittent.failures}/${failure.intermittent.runs}, measured ${failure.intermittent.measuredAt})`
668
670
  : "—";
669
671
  lines.push(
670
- `| \`${failure.flow}\` | ${escapeCell(failure.message)} | ${known} |`
672
+ `| \`${failure.flow}\` | ${cell(failure.message)} | ${known} |`
671
673
  );
672
674
  }
673
675
  lines.push("");
@@ -696,15 +698,6 @@ export function renderMarkdown({ report, failures, defects }) {
696
698
  return lines.join("\n");
697
699
  }
698
700
 
699
- /**
700
- * Escape a value so it cannot break out of a markdown table cell.
701
- * @param {string} value - Raw text
702
- * @returns {string} Escaped text
703
- */
704
- function escapeCell(value) {
705
- return String(value).replace(/\|/g, "\\|").replace(/\n/g, " ");
706
- }
707
-
708
701
  /**
709
702
  * Render one report's classification as plain text.
710
703
  * @param {{report: string, failures: object[], defects: object[]}} result - One report's result
@@ -770,9 +763,6 @@ function main(argv) {
770
763
  }
771
764
  }
772
765
 
773
- if (
774
- process.argv[1] &&
775
- import.meta.url === pathToFileURL(process.argv[1]).href
776
- ) {
766
+ if (invokedAsScript(import.meta.url)) {
777
767
  main(process.argv.slice(2));
778
768
  }
@@ -0,0 +1,96 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /**
5
+ * invoked-as-script — the one implementation of "was this module run as the CLI
6
+ * entry point?", shared by every guarded `.mjs` entry point Lisa ships.
7
+ *
8
+ * @remarks
9
+ * ## Why this is shared rather than copied
10
+ *
11
+ * The obvious spelling is `import.meta.url === pathToFileURL(process.argv[1]).href`,
12
+ * and it is wrong in a way that cannot be seen in review. `import.meta.url` is
13
+ * normally the REAL path — node resolves ESM through `realpath` unless
14
+ * `--preserve-symlinks` or `--preserve-symlinks-main` is set — while `argv[1]`
15
+ * is whatever spelling the caller typed. Naming only the first of those two
16
+ * flags is what let a second instance of this very defect survive review here:
17
+ * `--preserve-symlinks-main` is a SEPARATE flag that applies to the entry
18
+ * module specifically, which is exactly the module this guard runs in, so it is
19
+ * the one that matters most and the one easiest to leave out. See the section
20
+ * on realpathing both sides below.
21
+ * Reached through a symlinked checkout, a symlinked bin shim, or a
22
+ * `/tmp` path on macOS (`/tmp` is itself a symlink to `/private/tmp`), the two
23
+ * differ, the guard is false, `main()` never runs, and the process exits 0
24
+ * having done nothing.
25
+ *
26
+ * That failure mode is not symmetric. A no-op GENERATOR usually fails closed —
27
+ * whatever consumes its output notices the missing artifact. A no-op CHECK
28
+ * fails **OPEN**: it prints nothing, exits 0, and the npm script "succeeds", so
29
+ * a gate that is meant to block a merge silently stops having an opinion. Every
30
+ * `check-*.mjs` in this tree is in the second category, and git worktrees —
31
+ * which every Lisa-driven agent uses — put a symlinked path on that code path
32
+ * as a matter of routine.
33
+ *
34
+ * ## `moduleUrl` is REQUIRED, and is the FIRST parameter
35
+ *
36
+ * A deliberate deviation from the obvious signature
37
+ * `invokedAsScript(argv1 = process.argv[1], moduleUrl = import.meta.url)`. A
38
+ * defaulted `import.meta.url` inside a SHARED module resolves to *this* file,
39
+ * which is never anybody's `process.argv[1]` — so a caller that forgot the
40
+ * second argument would get `false` forever and no-op silently. That is exactly
41
+ * the fail-open defect this module exists to remove, re-introduced by its own
42
+ * convenience default. Making it required and first means a call site cannot be
43
+ * written wrong.
44
+ *
45
+ * ## Why ANY resolution error returns false
46
+ *
47
+ * `realpathSync` throws ENOENT, EACCES, ELOOP and ENOTDIR, not just ENOENT. A
48
+ * fallback that narrows to ENOENT and then compares `resolve(argv1)` reinstates
49
+ * the un-normalized comparison the realpath exists to avoid, so the symlink
50
+ * defect survives on precisely the path meant to be the safety net.
51
+ *
52
+ * Returning `false` on an unresolvable `argv[1]` is sound rather than merely
53
+ * convenient: node resolved and LOADED the entry point from that path moments
54
+ * earlier, so a path that cannot be resolved now is not the path this module
55
+ * was loaded through. Comparing an unnormalized spelling instead would answer
56
+ * "maybe" with a confident "yes".
57
+ *
58
+ * ## Why BOTH sides are realpath'd, not just `argv[1]`
59
+ *
60
+ * "`import.meta.url` is always the real path" is true by default and false under
61
+ * `--preserve-symlinks-main`, which tells node not to resolve the main entry.
62
+ * Normalizing only `argv[1]` then compares a real path against a symlinked one
63
+ * and answers `false` for an entry point that WAS invoked directly — the same
64
+ * fail-open this module exists to remove, reappearing on the flag that most
65
+ * looks like it should not matter. Measured: a symlinked entry point reports
66
+ * `true` normally and `false` under the flag.
67
+ *
68
+ * Realpathing both sides is free in the ordinary case, where `moduleUrl` is
69
+ * already canonical and `realpathSync` is the identity.
70
+ *
71
+ * @module scripts/lib/invoked-as-script
72
+ */
73
+ import { realpathSync } from "node:fs";
74
+ import { fileURLToPath } from "node:url";
75
+
76
+ /**
77
+ * True when `moduleUrl` names the module node was asked to run.
78
+ *
79
+ * Both sides are realpath'd before comparison — see the module remarks for why a
80
+ * raw comparison silently answers "no" under a symlinked path, and why
81
+ * normalizing only `argv[1]` leaves the same hole open under
82
+ * `--preserve-symlinks-main`.
83
+ * @param {string} moduleUrl - The caller's own `import.meta.url`.
84
+ * @param {string | undefined} [argv1] - Entry path; defaults to `process.argv[1]`.
85
+ * @returns {boolean} Whether the caller should run its CLI body.
86
+ */
87
+ export function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
88
+ // `node -e`, `node --eval`, `node --print` and the REPL leave `argv[1]`
89
+ // undefined. Nothing was asked to run, so nothing should.
90
+ if (!argv1) return false;
91
+ try {
92
+ return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
package/package.json CHANGED
@@ -125,7 +125,7 @@
125
125
  }
126
126
  },
127
127
  "name": "@codyswann/lisa",
128
- "version": "3.15.0",
128
+ "version": "3.16.0",
129
129
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
130
130
  "main": "dist/index.js",
131
131
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
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": "3.15.0",
3
+ "version": "3.16.0",
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": "3.15.0",
3
+ "version": "3.16.0",
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-cdk",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
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": "3.15.0",
3
+ "version": "3.16.0",
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": "3.15.0",
3
+ "version": "3.16.0",
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": "3.15.0",
3
+ "version": "3.16.0",
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": "3.15.0",
3
+ "version": "3.16.0",
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": "3.15.0",
3
+ "version": "3.16.0",
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": "3.15.0",
3
+ "version": "3.16.0",
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-expo",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"