@codyswann/lisa 4.9.0 → 4.9.1
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.
- package/all/copy-overwrite/scripts/lisa-gates.mjs +29 -3
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +3 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +7 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/hooks-installer.d.ts.map +1 -1
- package/dist/opencode/hooks-installer.js +39 -17
- package/dist/opencode/hooks-installer.js.map +1 -1
- package/dist/opencode/plugin-catalog.d.ts.map +1 -1
- package/dist/opencode/plugin-catalog.js +5 -0
- package/dist/opencode/plugin-catalog.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +464 -0
- package/dist/opencode/plugin-templates/lisa-block-no-verify.ts +81 -0
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/scripts/copy-opencode-plugin-templates.mjs +4 -0
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +20 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lisa-managed OpenCode plugin (tool.execute.before).
|
|
3
|
+
*
|
|
4
|
+
* Blocks commands that bypass git's verification hooks — `--no-verify` and any
|
|
5
|
+
* prefix abbreviation git accepts for it, the short `git commit -n` (bare or
|
|
6
|
+
* bundled into a cluster such as `-nm "msg"`), `HUSKY=0` / `HUSKY_SKIP_HOOKS=`,
|
|
7
|
+
* and every spelling of `core.hooksPath` that points hooks somewhere that
|
|
8
|
+
* disables them.
|
|
9
|
+
*
|
|
10
|
+
* ## Why a plugin rather than more deny globs
|
|
11
|
+
*
|
|
12
|
+
* OpenCode's `permission.bash` map matches SPELLINGS. Lisa's emit shipped
|
|
13
|
+
* `"*--no-verify*"` and two `git commit -n` prefixes, and that is a filter on
|
|
14
|
+
* how the bypass was typed, not on what git will do with it: `git commit
|
|
15
|
+
* --no-veri` is an unambiguous abbreviation git resolves to `--no-verify` and
|
|
16
|
+
* matched no glob at all. Adding `*--no-veri*` would close that one spelling
|
|
17
|
+
* and leave the class open, because the set of abbreviations git accepts is a
|
|
18
|
+
* property of git's own parser. It also cannot be closed by widening: a glob
|
|
19
|
+
* broad enough to catch `-n` late on a line refuses `grep -n`.
|
|
20
|
+
*
|
|
21
|
+
* The distinction the globs cannot draw is between a token that IS an option
|
|
22
|
+
* and a token that merely CONTAINS the text of one, and drawing it requires
|
|
23
|
+
* tokenizing the command. OpenCode fires `tool.execute.before` for the `bash`
|
|
24
|
+
* tool before the command runs, and a throw from it cancels the call — so the
|
|
25
|
+
* capability was there; the emit was not using it.
|
|
26
|
+
*
|
|
27
|
+
* ## Why it shells out
|
|
28
|
+
*
|
|
29
|
+
* Policy stays single-sourced in `block-no-verify.sh`, the canonical guard
|
|
30
|
+
* Claude, Codex, Cursor and Copilot already run, copied beside this module by
|
|
31
|
+
* the installer exactly as `parity-safety-net.sh` is. A second implementation
|
|
32
|
+
* of git's option grammar in TypeScript would be a second thing to harden, and
|
|
33
|
+
* the two would diverge at the first vector closed in only one of them. This
|
|
34
|
+
* adapter converts OpenCode's `bash` tool input into Lisa's canonical Claude
|
|
35
|
+
* Bash-hook envelope, runs the script, and surfaces its refusal.
|
|
36
|
+
*
|
|
37
|
+
* The `permission.bash` deny globs stay in the emit as a FLOOR, not as the
|
|
38
|
+
* guard: if this plugin fails to load, the literal spellings are still refused.
|
|
39
|
+
* A coarse guard that is present beats a precise one that is absent.
|
|
40
|
+
*
|
|
41
|
+
* NOTE: Lisa copies this template verbatim into `.opencode/plugin/`. It runs
|
|
42
|
+
* under OpenCode's Bun runtime and is excluded from this repository's tsconfig.
|
|
43
|
+
*/
|
|
44
|
+
export const LisaBlockNoVerify = async () => ({
|
|
45
|
+
"tool.execute.before": async (
|
|
46
|
+
input: { tool: string },
|
|
47
|
+
output: { args?: { command?: string } }
|
|
48
|
+
) => {
|
|
49
|
+
if (input.tool !== "bash") return;
|
|
50
|
+
const command = String(output.args?.command ?? "");
|
|
51
|
+
if (!command) return;
|
|
52
|
+
|
|
53
|
+
const hookPath = `${import.meta.dir}/block-no-verify.sh`;
|
|
54
|
+
const processHandle = Bun.spawn(["/bin/bash", hookPath], {
|
|
55
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: process.cwd() },
|
|
56
|
+
stdin: "pipe",
|
|
57
|
+
stdout: "ignore",
|
|
58
|
+
stderr: "pipe",
|
|
59
|
+
});
|
|
60
|
+
processHandle.stdin.write(
|
|
61
|
+
JSON.stringify({ tool_name: "Bash", tool_input: { command } })
|
|
62
|
+
);
|
|
63
|
+
processHandle.stdin.end();
|
|
64
|
+
|
|
65
|
+
const [status, reason] = await Promise.all([
|
|
66
|
+
processHandle.exited,
|
|
67
|
+
new Response(processHandle.stderr).text(),
|
|
68
|
+
]);
|
|
69
|
+
// Exit 0 is ALLOW, including the canonical script's degraded path when jq
|
|
70
|
+
// or python3 is missing — it says so on stderr and permits the command,
|
|
71
|
+
// which is why stderr alone must never be read as a refusal. Any non-zero
|
|
72
|
+
// status is a block: 2 is the guard's own refusal, and anything else means
|
|
73
|
+
// the guard could not complete, which is not a state to run a bypass in.
|
|
74
|
+
if (status === 0) return;
|
|
75
|
+
throw new Error(
|
|
76
|
+
reason.trim() ||
|
|
77
|
+
"block-no-verify: this command bypasses git's verification hooks. " +
|
|
78
|
+
"Fix the underlying failure instead of skipping the check."
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
});
|
package/package.json
CHANGED
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"zod-validation-error": "^4.0.0"
|
|
149
149
|
},
|
|
150
150
|
"name": "@codyswann/lisa",
|
|
151
|
-
"version": "4.9.
|
|
151
|
+
"version": "4.9.1",
|
|
152
152
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
153
153
|
"main": "dist/index.js",
|
|
154
154
|
"exports": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.1",
|
|
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": "4.9.
|
|
3
|
+
"version": "4.9.1",
|
|
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": "4.9.
|
|
3
|
+
"version": "4.9.1",
|
|
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": "4.9.
|
|
3
|
+
"version": "4.9.1",
|
|
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": "4.9.
|
|
3
|
+
"version": "4.9.1",
|
|
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"
|
|
@@ -16,7 +16,11 @@ const repoRoot = path.resolve(import.meta.dirname, "..");
|
|
|
16
16
|
const sourceDir = path.join(repoRoot, "src", "opencode", "plugin-templates");
|
|
17
17
|
const destDir = path.join(repoRoot, "dist", "opencode", "plugin-templates");
|
|
18
18
|
const canonicalHookDir = path.join(repoRoot, "plugins", "src", "base", "hooks");
|
|
19
|
+
// Kept in step with PLUGIN_SUPPORT_FILES in src/opencode/hooks-installer.ts —
|
|
20
|
+
// that list is what the installer copies into a host, this one is what makes
|
|
21
|
+
// the files reachable from `dist/` for a packaged install.
|
|
19
22
|
const canonicalSupportFiles = [
|
|
23
|
+
"block-no-verify.sh",
|
|
20
24
|
"parity-safety-net.sh",
|
|
21
25
|
"parity-safety-net-heredoc.py",
|
|
22
26
|
];
|
|
@@ -635,11 +635,30 @@ export const MIN_DETECTED_FOR_SHARE = 50;
|
|
|
635
635
|
|
|
636
636
|
/**
|
|
637
637
|
* The share ceiling in force.
|
|
638
|
+
*
|
|
639
|
+
* A variable that is SET BUT EMPTY reads as unset, not as zero. `Number("")` is
|
|
640
|
+
* 0, and 0 passes both `Number.isFinite` and `>= 0`, so an empty string used to
|
|
641
|
+
* be the one non-numeric spelling that did NOT fall back: `"abc"` resolved to
|
|
642
|
+
* the default and `""` resolved to a ceiling of 0, which fails the run on the
|
|
643
|
+
* first timed-out mutant. That is the strictest ceiling reachable, delivered by
|
|
644
|
+
* a value nobody chose.
|
|
645
|
+
*
|
|
646
|
+
* Empty is exactly what GitHub Actions produces when an unset workflow input is
|
|
647
|
+
* mapped into `env:` — `MUTATION_TIMEOUT_SHARE_MAX: ${{ inputs.share_max }}` on
|
|
648
|
+
* a caller that passes nothing sets the variable to "". The declaration is the
|
|
649
|
+
* caller's, the emptiness is the harness's, and reading the harness's silence
|
|
650
|
+
* as a deliberate zero is how a gate goes red for a reason no human wrote.
|
|
651
|
+
*
|
|
652
|
+
* Zero stays reachable, because zero stays typeable: `MUTATION_TIMEOUT_SHARE_MAX=0`
|
|
653
|
+
* still resolves to 0. What changes is that a ceiling that severe now has to be
|
|
654
|
+
* asked for in a character, rather than arrived at through an absent one.
|
|
638
655
|
* @returns {number} The ceiling, as a percentage.
|
|
639
656
|
*/
|
|
640
657
|
export const resolveTimeoutShareCeiling = () => {
|
|
641
658
|
const raw = process.env.MUTATION_TIMEOUT_SHARE_MAX;
|
|
642
|
-
if (raw === undefined)
|
|
659
|
+
if (raw === undefined || raw.trim() === "") {
|
|
660
|
+
return DEFAULT_TIMEOUT_SHARE_CEILING_PCT;
|
|
661
|
+
}
|
|
643
662
|
const parsed = Number(raw);
|
|
644
663
|
return Number.isFinite(parsed) && parsed >= 0
|
|
645
664
|
? parsed
|