@codyswann/lisa 2.121.0 → 2.121.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/dist/agy/mcp-installer.d.ts +18 -0
- package/dist/agy/mcp-installer.d.ts.map +1 -1
- package/dist/agy/mcp-installer.js +21 -0
- package/dist/agy/mcp-installer.js.map +1 -1
- package/dist/agy/plugin-installer.d.ts.map +1 -1
- package/dist/agy/plugin-installer.js +6 -2
- package/dist/agy/plugin-installer.js.map +1 -1
- package/dist/codex/lisa-plugin-detection.d.ts +18 -19
- package/dist/codex/lisa-plugin-detection.d.ts.map +1 -1
- package/dist/codex/lisa-plugin-detection.js +26 -22
- package/dist/codex/lisa-plugin-detection.js.map +1 -1
- package/dist/copilot/plugin-installer.d.ts.map +1 -1
- package/dist/copilot/plugin-installer.js +7 -3
- package/dist/copilot/plugin-installer.js.map +1 -1
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +20 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +10 -6
- package/dist/core/lisa.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa/{.codex-plugin → hooks}/hooks.json +7 -7
- 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-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-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa-harper-fabric/{.codex-plugin → hooks}/hooks.json +2 -2
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa-nestjs/{.codex-plugin → hooks}/hooks.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-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa-rails/{.codex-plugin → hooks}/hooks.json +4 -4
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +2 -2
- package/plugins/lisa-typescript/{.codex-plugin → hooks}/hooks.json +4 -4
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/scripts/generate-codex-plugin-artifacts.mjs +28 -48
- package/scripts/generate-copilot-plugin-artifacts.mjs +5 -0
- package/scripts/generate-cursor-plugin-artifacts.mjs +4 -0
- package/scripts/internal-copilot-runtime-probe.json +10 -4
- package/plugins/lisa/.codex-plugin/hooks/block-no-verify.sh +0 -37
- package/plugins/lisa/.codex-plugin/hooks/inject-flow-context.sh +0 -12
- package/plugins/lisa/.codex-plugin/hooks/inject-rules.sh +0 -33
- package/plugins/lisa/.codex-plugin/hooks/install-pkgs.sh +0 -69
- package/plugins/lisa/.codex-plugin/hooks/notify-ntfy.sh +0 -183
- package/plugins/lisa/.codex-plugin/hooks/setup-jira-cli.sh +0 -51
- package/plugins/lisa-harper-fabric/.codex-plugin/hooks/inject-rules.sh +0 -16
- package/plugins/lisa-nestjs/.codex-plugin/hooks/block-migration-edits.sh +0 -60
- package/plugins/lisa-rails/.codex-plugin/hooks/inject-rules.sh +0 -22
- package/plugins/lisa-rails/.codex-plugin/hooks/rubocop-on-edit.sh +0 -78
- package/plugins/lisa-rails/.codex-plugin/hooks/sg-scan-on-edit.sh +0 -74
- package/plugins/lisa-typescript/.codex-plugin/hooks/block-suppress-directives.sh +0 -73
- package/plugins/lisa-typescript/.codex-plugin/hooks/format-on-edit.sh +0 -79
- package/plugins/lisa-typescript/.codex-plugin/hooks/lint-on-edit.sh +0 -134
- package/plugins/lisa-typescript/.codex-plugin/hooks/sg-scan-on-edit.sh +0 -71
|
@@ -468,24 +468,33 @@ function main() {
|
|
|
468
468
|
* @param {object} claudeManifest Parsed contents of .claude-plugin/plugin.json.
|
|
469
469
|
*/
|
|
470
470
|
function emitCodexHooks(pluginDir, claudeManifest) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
471
|
+
// Codex auto-discovers a plugin's hooks at <plugin-root>/hooks/hooks.json and
|
|
472
|
+
// resolves the manifest hooks pointer + ${PLUGIN_ROOT} relative to the plugin
|
|
473
|
+
// root (developers.openai.com/codex/plugins/build). The hook scripts already
|
|
474
|
+
// ship at <plugin-root>/hooks/ (copied from plugins/src by the Claude build),
|
|
475
|
+
// so the Codex hooks.json lives alongside them and no script copy is needed.
|
|
476
|
+
const hooksDir = path.join(pluginDir, "hooks");
|
|
477
|
+
const hooksJsonPath = path.join(hooksDir, "hooks.json");
|
|
478
|
+
// Clean up the pre-2.121 layout (hooks.json + copied scripts under
|
|
479
|
+
// .codex-plugin/) so a rebuilt plugin never ships both.
|
|
480
|
+
const legacyCodexPluginDir = path.join(pluginDir, ".codex-plugin");
|
|
481
|
+
fs.rmSync(path.join(legacyCodexPluginDir, "hooks.json"), { force: true });
|
|
482
|
+
fs.rmSync(path.join(legacyCodexPluginDir, "hooks"), {
|
|
483
|
+
force: true,
|
|
484
|
+
recursive: true,
|
|
485
|
+
});
|
|
474
486
|
const filtered = filterCodexHooks(claudeManifest.hooks);
|
|
475
487
|
if (filtered === null) {
|
|
476
|
-
// Nothing survived the filter. Remove any stale hooks
|
|
477
|
-
//
|
|
478
|
-
// hooks via the ./hooks.json pointer.
|
|
488
|
+
// Nothing survived the filter. Remove any stale hooks.json so
|
|
489
|
+
// componentPointers() doesn't keep advertising removed hooks.
|
|
479
490
|
fs.rmSync(hooksJsonPath, { force: true });
|
|
480
|
-
fs.rmSync(hooksScriptsDir, { force: true, recursive: true });
|
|
481
491
|
return;
|
|
482
492
|
}
|
|
483
|
-
fs.mkdirSync(
|
|
493
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
484
494
|
fs.writeFileSync(
|
|
485
495
|
hooksJsonPath,
|
|
486
496
|
`${JSON.stringify(buildCodexHooksDocument(filtered), null, 2)}\n`
|
|
487
497
|
);
|
|
488
|
-
copyCodexHookScripts(pluginDir, filtered);
|
|
489
498
|
}
|
|
490
499
|
|
|
491
500
|
/**
|
|
@@ -540,8 +549,8 @@ function componentPointers(pluginDir) {
|
|
|
540
549
|
...(fs.existsSync(path.join(pluginDir, ".mcp.json"))
|
|
541
550
|
? { mcpServers: "./.mcp.json" }
|
|
542
551
|
: {}),
|
|
543
|
-
...(fs.existsSync(path.join(pluginDir, "
|
|
544
|
-
? { hooks: "./hooks.json" }
|
|
552
|
+
...(fs.existsSync(path.join(pluginDir, "hooks", "hooks.json"))
|
|
553
|
+
? { hooks: "./hooks/hooks.json" }
|
|
545
554
|
: {}),
|
|
546
555
|
};
|
|
547
556
|
}
|
|
@@ -551,8 +560,9 @@ function componentPointers(pluginDir) {
|
|
|
551
560
|
* Claude plugin.json hooks block:
|
|
552
561
|
* - Drop every `entire hooks claude-code *` command (Claude-only).
|
|
553
562
|
* - Drop every reference to `enforce-team-first.sh` (Claude-team-specific).
|
|
554
|
-
* - Rewrite ${CLAUDE_PLUGIN_ROOT}/hooks/<script>.sh to
|
|
555
|
-
* (Codex
|
|
563
|
+
* - Rewrite ${CLAUDE_PLUGIN_ROOT}/hooks/<script>.sh to
|
|
564
|
+
* ${PLUGIN_ROOT}/hooks/<script>.sh (Codex exposes the installed plugin
|
|
565
|
+
* root to hook commands as ${PLUGIN_ROOT}).
|
|
556
566
|
* - Drop matchers that produce no surviving handlers.
|
|
557
567
|
*
|
|
558
568
|
* When the resulting block is empty, no hooks.json is written and the
|
|
@@ -590,11 +600,13 @@ export function filterCodexHooks(hooksBlock) {
|
|
|
590
600
|
return [
|
|
591
601
|
{
|
|
592
602
|
...h,
|
|
593
|
-
// Codex
|
|
594
|
-
//
|
|
603
|
+
// Codex exposes the installed plugin root as ${PLUGIN_ROOT} to hook
|
|
604
|
+
// commands (developers.openai.com/codex/plugins/build). A bare
|
|
605
|
+
// ./hooks/ path would resolve against the session cwd, not the
|
|
606
|
+
// plugin, so rewrite to the plugin-root env var.
|
|
595
607
|
command: h.command.replaceAll(
|
|
596
608
|
"${CLAUDE_PLUGIN_ROOT}/hooks/",
|
|
597
|
-
"
|
|
609
|
+
"${PLUGIN_ROOT}/hooks/"
|
|
598
610
|
),
|
|
599
611
|
},
|
|
600
612
|
];
|
|
@@ -613,38 +625,6 @@ export function filterCodexHooks(hooksBlock) {
|
|
|
613
625
|
return Object.keys(out).length > 0 ? out : null;
|
|
614
626
|
}
|
|
615
627
|
|
|
616
|
-
/**
|
|
617
|
-
* Copy hook scripts that survived filterCodexHooks into the Codex artifact.
|
|
618
|
-
*
|
|
619
|
-
* Scripts land at <pluginDir>/.codex-plugin/hooks/ so the hooks.json pointer
|
|
620
|
-
* "./hooks/<n>.sh" resolves correctly when Codex loads the plugin.
|
|
621
|
-
*
|
|
622
|
-
* @param {string} pluginDir Built Claude plugin directory.
|
|
623
|
-
* @param {object} hooks Codex-shaped hooks block from filterCodexHooks.
|
|
624
|
-
*/
|
|
625
|
-
function copyCodexHookScripts(pluginDir, hooks) {
|
|
626
|
-
const srcHooksDir = path.join(pluginDir, "hooks");
|
|
627
|
-
if (!fs.existsSync(srcHooksDir)) return;
|
|
628
|
-
const referenced = new Set();
|
|
629
|
-
for (const entries of Object.values(hooks)) {
|
|
630
|
-
for (const entry of entries) {
|
|
631
|
-
for (const h of entry.hooks ?? []) {
|
|
632
|
-
if (typeof h.command !== "string") continue;
|
|
633
|
-
const m = /^\.\/hooks\/([^/\s]+\.sh)/.exec(h.command);
|
|
634
|
-
if (m) referenced.add(m[1]);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
if (referenced.size === 0) return;
|
|
639
|
-
const dstHooksDir = path.join(pluginDir, ".codex-plugin", "hooks");
|
|
640
|
-
fs.mkdirSync(dstHooksDir, { recursive: true });
|
|
641
|
-
for (const name of referenced) {
|
|
642
|
-
const src = path.join(srcHooksDir, name);
|
|
643
|
-
if (!fs.existsSync(src)) continue;
|
|
644
|
-
fs.copyFileSync(src, path.join(dstHooksDir, name));
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
628
|
function metadataFor(pluginName, claudeManifest) {
|
|
649
629
|
const map = {
|
|
650
630
|
lisa: {
|
|
@@ -139,6 +139,11 @@ export function generateCopilotVariant(srcDir, outDir, version) {
|
|
|
139
139
|
copyDir(srcDir, outDir, relPath => {
|
|
140
140
|
if (relPath.startsWith(".codex-plugin/") || relPath === ".codex-plugin")
|
|
141
141
|
return false;
|
|
142
|
+
// Drop the Codex hooks manifest the base build emits at hooks/hooks.json —
|
|
143
|
+
// Copilot reads its (camelCase) hooks from .claude-plugin/plugin.json, not
|
|
144
|
+
// this Codex-shaped (PascalCase, ${PLUGIN_ROOT}) file. Keeping it would ship
|
|
145
|
+
// a spurious, wrong-shaped hooks manifest in the Copilot variant.
|
|
146
|
+
if (relPath === path.join("hooks", "hooks.json")) return false;
|
|
142
147
|
// Drop Codex-specific per-skill openai.yaml artifacts — Copilot does not use them.
|
|
143
148
|
if (/^skills\/[^/]+\/agents\/openai\.ya?ml$/.test(relPath)) return false;
|
|
144
149
|
const skillsPrefix = path.join("skills") + path.sep;
|
|
@@ -105,6 +105,10 @@ export function generateCursorVariant(srcDir, outDir, version) {
|
|
|
105
105
|
// Drop the `.codex-plugin/` directory — Cursor does not consume it.
|
|
106
106
|
if (relPath.startsWith(".codex-plugin/") || relPath === ".codex-plugin")
|
|
107
107
|
return false;
|
|
108
|
+
// Drop the Codex hooks manifest the base build emits at hooks/hooks.json —
|
|
109
|
+
// Cursor reads its (filtered) hooks from .claude-plugin/plugin.json, not this
|
|
110
|
+
// Codex-shaped file. The surviving .sh scripts in hooks/ are kept below.
|
|
111
|
+
if (relPath === path.join("hooks", "hooks.json")) return false;
|
|
108
112
|
// Drop Codex-specific per-skill openai.yaml artifacts — Cursor does not use them.
|
|
109
113
|
// These live at skills/<n>/agents/openai.yaml and are generated by the Codex
|
|
110
114
|
// artifact builder; the per-agent variants ship only the SKILL.md.
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Cached Copilot runtime probe results
|
|
3
|
-
"cliVersion":
|
|
2
|
+
"_comment": "Cached Copilot runtime probe results consumed by scripts/generate-copilot-plugin-artifacts.mjs. Populated 2026-05-28 by empirical probes against GitHub Copilot CLI 1.0.55 (see _evidence). Missing/null values fall through to conservative generator defaults.",
|
|
3
|
+
"cliVersion": "1.0.55",
|
|
4
4
|
"pluginRootEnvVar": null,
|
|
5
|
-
"rulesAutoLoads":
|
|
6
|
-
"agentPathOverrideAccepted":
|
|
5
|
+
"rulesAutoLoads": false,
|
|
6
|
+
"agentPathOverrideAccepted": false,
|
|
7
|
+
"_evidence": {
|
|
8
|
+
"cliVersion": "[VERIFIED-BY-RUN] `copilot --version` => 'GitHub Copilot CLI 1.0.55.'",
|
|
9
|
+
"rulesAutoLoads": "[VERIFIED-BY-RUN] A plugin's bundled rules/ directory is NOT auto-loaded. Probe: `copilot --plugin-dir <pkg> -p 'plugin codeword?'` with a sentinel rule in <pkg>/rules/sentinel.md and no .github/copilot-instructions.md returned 'UNKNOWN'. (Copilot DOES auto-load .github/copilot-instructions.md — a separate project file — but that template only points at the plugin, so inject-rules.sh must still ship. Hence false: do NOT strip inject-rules.sh.)",
|
|
10
|
+
"agentPathOverrideAccepted": "[PARTIAL] With an explicit manifest 'agents: ./agents/' pointer, `copilot --plugin-dir <pkg> --agent <ns>:<name>` loaded a non-.agent.md file (agents/probeagent.md) and it responded. However that is session-only --plugin-dir loading; the marketplace-install auto-discovery path is unverified and .agent.md loads in every observed context. Kept false so the generator retains the universally-safe rename to <name>.agent.md.",
|
|
11
|
+
"pluginRootEnvVar": "[VERIFIED-DOC] GitHub Copilot CLI plugin reference documents ${COPILOT_PLUGIN_DATA} is also exposed as ${CLAUDE_PLUGIN_DATA}; Copilot aliases the CLAUDE_* plugin env vars, so the ${CLAUDE_PLUGIN_ROOT} form the generator emits in hook commands is supported. null => generator keeps ${CLAUDE_PLUGIN_ROOT}. Direct hook-fire env capture was not achievable via --plugin-dir in -p mode (hook did not fire; firing likely requires a real `copilot plugin install` and/or interactive trust — unverified)."
|
|
12
|
+
}
|
|
7
13
|
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# PreToolUse hook for Bash: blocks any command containing --no-verify.
|
|
3
|
-
# --no-verify on git commit/push (and equivalents) bypasses pre-commit/pre-push
|
|
4
|
-
# hooks that exist for a reason. The fix is to address the underlying issue,
|
|
5
|
-
# not silence the check. See feedback_never_no_verify in user memory.
|
|
6
|
-
#
|
|
7
|
-
# Word-boundary match avoids false positives on flags like --no-verify-ssl,
|
|
8
|
-
# --no-verify-host, etc.
|
|
9
|
-
set -euo pipefail
|
|
10
|
-
|
|
11
|
-
input="$(cat)"
|
|
12
|
-
|
|
13
|
-
tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
|
|
14
|
-
if [ "$tool_name" != "Bash" ]; then
|
|
15
|
-
exit 0
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
command_str="$(printf '%s' "$input" | jq -r '.tool_input.command // empty')"
|
|
19
|
-
if [ -z "$command_str" ]; then
|
|
20
|
-
exit 0
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
# Match --no-verify bounded by non-token characters (not alphanumeric, _, or -).
|
|
24
|
-
# This catches all syntactic positions including subshells (e.g. `(git commit --no-verify)`)
|
|
25
|
-
# while excluding longer flags like --no-verify-ssl, --no-verify-host, etc.
|
|
26
|
-
if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])'; then
|
|
27
|
-
cat >&2 <<'EOF'
|
|
28
|
-
Blocked: --no-verify bypasses pre-commit/pre-push hooks. Fix the underlying
|
|
29
|
-
issue (lint error, failing test, formatting) or ask the user before bypassing.
|
|
30
|
-
|
|
31
|
-
If the user has explicitly authorized the bypass for this specific command,
|
|
32
|
-
re-run after they confirm.
|
|
33
|
-
EOF
|
|
34
|
-
exit 2
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
exit 0
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Tells subagents not to ask users for flow selection.
|
|
3
|
-
# The parent agent has already determined the flow and work type.
|
|
4
|
-
# Used by SubagentStart hook.
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
jq -n '{
|
|
8
|
-
"hookSpecificOutput": {
|
|
9
|
-
"hookEventName": "SubagentStart",
|
|
10
|
-
"additionalContext": "You are a subagent operating within an established flow. Your parent agent has already determined the flow and work type. Do NOT ask the user to choose a flow or classify the request. Execute your assigned work within the context provided by your parent agent."
|
|
11
|
-
}
|
|
12
|
-
}'
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Reads all .md files from the plugin's rules/eager/ directory and injects them
|
|
3
|
-
# into the session context via additionalContext.
|
|
4
|
-
# Used by SessionStart and SubagentStart hooks.
|
|
5
|
-
#
|
|
6
|
-
# The split between eager and reference rules is documented in
|
|
7
|
-
# rules/eager/00-bootstrap.md (or the equivalent README). Reference bodies
|
|
8
|
-
# under rules/reference/ are installed alongside but loaded only when the
|
|
9
|
-
# eager breadcrumb points to them.
|
|
10
|
-
set -euo pipefail
|
|
11
|
-
|
|
12
|
-
RULES_DIR="${CLAUDE_PLUGIN_ROOT}/rules/eager"
|
|
13
|
-
|
|
14
|
-
# Backward compatibility: if the eager subdir is absent (older Lisa install),
|
|
15
|
-
# fall back to the flat rules/ directory so a partial upgrade still ships rules.
|
|
16
|
-
if [ ! -d "$RULES_DIR" ]; then
|
|
17
|
-
RULES_DIR="${CLAUDE_PLUGIN_ROOT}/rules"
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
# Bail silently if no rules directory at all
|
|
21
|
-
[ -d "$RULES_DIR" ] || exit 0
|
|
22
|
-
|
|
23
|
-
CONTEXT=""
|
|
24
|
-
for file in "$RULES_DIR"/*.md; do
|
|
25
|
-
[ -f "$file" ] || continue
|
|
26
|
-
CONTEXT+="$(cat "$file")"$'\n\n'
|
|
27
|
-
done
|
|
28
|
-
|
|
29
|
-
# Bail if no rules found
|
|
30
|
-
[ -n "$CONTEXT" ] || exit 0
|
|
31
|
-
|
|
32
|
-
# Output as JSON — jq handles escaping
|
|
33
|
-
jq -n --arg ctx "$CONTEXT" '{"additionalContext": $ctx}'
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# This file is managed by Lisa.
|
|
3
|
-
# Do not edit directly — changes will be overwritten on the next `lisa` run.
|
|
4
|
-
|
|
5
|
-
# Only run package installation when node_modules are missing.
|
|
6
|
-
# This covers remote environments, new worktrees, fresh clones, and CI.
|
|
7
|
-
if [ -d "node_modules" ]; then
|
|
8
|
-
exit 0
|
|
9
|
-
fi
|
|
10
|
-
|
|
11
|
-
# Detect package manager based on lock file presence
|
|
12
|
-
if [ -f "bun.lockb" ] || [ -f "bun.lock" ]; then
|
|
13
|
-
bun install
|
|
14
|
-
elif [ -f "pnpm-lock.yaml" ]; then
|
|
15
|
-
pnpm install
|
|
16
|
-
elif [ -f "yarn.lock" ]; then
|
|
17
|
-
yarn install
|
|
18
|
-
elif [ -f "package-lock.json" ]; then
|
|
19
|
-
npm install
|
|
20
|
-
else
|
|
21
|
-
npm install
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
# The tools below use Linux-specific binaries and paths — skip on other platforms.
|
|
25
|
-
if [ "$(uname -s)" != "Linux" ]; then
|
|
26
|
-
exit 0
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
# Install Gitleaks for secret detection (pre-commit hook)
|
|
30
|
-
echo "Installing Gitleaks for secret detection..."
|
|
31
|
-
GITLEAKS_VERSION="8.18.4"
|
|
32
|
-
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | tar -xz -C /usr/local/bin gitleaks
|
|
33
|
-
echo "Gitleaks installed: $(gitleaks version)"
|
|
34
|
-
|
|
35
|
-
# Install jira-cli for JIRA integration
|
|
36
|
-
# The tarball nests the binary at jira_VERSION_linux_x86_64/bin/jira,
|
|
37
|
-
# so we extract to a temp dir and copy the binary out.
|
|
38
|
-
echo "Installing jira-cli for JIRA integration..."
|
|
39
|
-
JIRA_CLI_VERSION="1.7.0"
|
|
40
|
-
JIRA_TMPDIR=$(mktemp -d)
|
|
41
|
-
curl -sSfL "https://github.com/ankitpokhrel/jira-cli/releases/download/v${JIRA_CLI_VERSION}/jira_${JIRA_CLI_VERSION}_linux_x86_64.tar.gz" \
|
|
42
|
-
| tar -xz -C "${JIRA_TMPDIR}"
|
|
43
|
-
cp "${JIRA_TMPDIR}/jira_${JIRA_CLI_VERSION}_linux_x86_64/bin/jira" /usr/local/bin/jira
|
|
44
|
-
chmod +x /usr/local/bin/jira
|
|
45
|
-
rm -rf "${JIRA_TMPDIR}"
|
|
46
|
-
echo "jira-cli installed: $(jira version)"
|
|
47
|
-
|
|
48
|
-
# Install Chromium for Lighthouse CI (pre-push hook)
|
|
49
|
-
# Playwright's bundled Chromium works with @lhci/cli
|
|
50
|
-
echo "Installing Chromium for Lighthouse CI..."
|
|
51
|
-
npx playwright install chromium
|
|
52
|
-
|
|
53
|
-
# Find and export CHROME_PATH for Lighthouse CI
|
|
54
|
-
# Use sort to ensure deterministic selection of the latest version
|
|
55
|
-
CHROME_PATH=$(find ~/.cache/ms-playwright -name "chrome" -type f 2>/dev/null | grep "chrome-linux" | sort | tail -n 1)
|
|
56
|
-
if [ -n "$CHROME_PATH" ]; then
|
|
57
|
-
# Append to ~/.bashrc for shell sessions (idempotent)
|
|
58
|
-
if ! grep -q "export CHROME_PATH=" ~/.bashrc 2>/dev/null; then
|
|
59
|
-
echo "export CHROME_PATH=\"$CHROME_PATH\"" >> ~/.bashrc
|
|
60
|
-
else
|
|
61
|
-
# Update existing CHROME_PATH in bashrc
|
|
62
|
-
sed -i "s|^export CHROME_PATH=.*|export CHROME_PATH=\"$CHROME_PATH\"|" ~/.bashrc
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
export CHROME_PATH="$CHROME_PATH"
|
|
66
|
-
echo "Chromium installed at: $CHROME_PATH"
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
exit 0
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# This file is managed by Lisa.
|
|
3
|
-
# Do not edit directly — changes will be overwritten on the next `lisa` run.
|
|
4
|
-
# =============================================================================
|
|
5
|
-
# ntfy.sh Notification Hook for Claude Code
|
|
6
|
-
# =============================================================================
|
|
7
|
-
# Sends desktop and mobile notifications via ntfy.sh when Claude needs
|
|
8
|
-
# attention or finishes a task.
|
|
9
|
-
#
|
|
10
|
-
# Setup:
|
|
11
|
-
# 1. Install ntfy app on mobile (iOS App Store / Android Play Store)
|
|
12
|
-
# 2. Subscribe to your unique topic in the app
|
|
13
|
-
# 3. Set NTFY_TOPIC environment variable (e.g., in ~/.bashrc or ~/.zshrc):
|
|
14
|
-
# export NTFY_TOPIC="my-claude-alerts-xyz123"
|
|
15
|
-
#
|
|
16
|
-
# @see https://ntfy.sh
|
|
17
|
-
# =============================================================================
|
|
18
|
-
|
|
19
|
-
# Read JSON input from stdin
|
|
20
|
-
INPUT=$(cat)
|
|
21
|
-
|
|
22
|
-
# Extract hook event name
|
|
23
|
-
HOOK_EVENT=$(echo "$INPUT" | grep -o '"hook_event_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
|
24
|
-
|
|
25
|
-
# Extract notification type (for Notification hooks)
|
|
26
|
-
NOTIFICATION_TYPE=$(echo "$INPUT" | grep -o '"notification_type"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
|
27
|
-
|
|
28
|
-
# Extract message if available
|
|
29
|
-
MESSAGE=$(echo "$INPUT" | grep -o '"message"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
|
30
|
-
|
|
31
|
-
# Extract session ID (first 8 chars for brevity)
|
|
32
|
-
FULL_SESSION_ID=$(echo "$INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
|
33
|
-
SESSION_ID="${FULL_SESSION_ID:0:8}"
|
|
34
|
-
|
|
35
|
-
# Extract transcript path for task summary
|
|
36
|
-
TRANSCRIPT_PATH=$(echo "$INPUT" | grep -o '"transcript_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
|
37
|
-
|
|
38
|
-
# Determine source (Web vs Local)
|
|
39
|
-
if [ "$CLAUDE_CODE_REMOTE" = "true" ]; then
|
|
40
|
-
SOURCE="Web"
|
|
41
|
-
else
|
|
42
|
-
SOURCE="Local"
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# Get project name from current directory
|
|
46
|
-
PROJECT_NAME=$(basename "$CLAUDE_PROJECT_DIR" 2>/dev/null || basename "$(pwd)")
|
|
47
|
-
|
|
48
|
-
# Load NTFY_TOPIC from local config if not already set
|
|
49
|
-
if [ -z "$NTFY_TOPIC" ]; then
|
|
50
|
-
# Check for project-local config (gitignored)
|
|
51
|
-
if [ -f "$CLAUDE_PROJECT_DIR/.claude/env.local" ]; then
|
|
52
|
-
# shellcheck source=/dev/null
|
|
53
|
-
source "$CLAUDE_PROJECT_DIR/.claude/env.local"
|
|
54
|
-
fi
|
|
55
|
-
# Check for user-global config
|
|
56
|
-
if [ -z "$NTFY_TOPIC" ] && [ -f "$HOME/.claude/env.local" ]; then
|
|
57
|
-
# shellcheck source=/dev/null
|
|
58
|
-
source "$HOME/.claude/env.local"
|
|
59
|
-
fi
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
# Exit silently if still not configured
|
|
63
|
-
if [ -z "$NTFY_TOPIC" ]; then
|
|
64
|
-
exit 0
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
# Extract task summary from transcript (last assistant message, truncated)
|
|
68
|
-
TASK_SUMMARY=""
|
|
69
|
-
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
70
|
-
# Get the last assistant message from the JSONL transcript
|
|
71
|
-
# The transcript contains lines with "type":"assistant" and "message" content
|
|
72
|
-
# Use awk for cross-platform compatibility (tac is not available on macOS)
|
|
73
|
-
LAST_ASSISTANT=$(awk '/"type"[[:space:]]*:[[:space:]]*"assistant"/{line=$0} END{if(line) print line}' "$TRANSCRIPT_PATH" 2>/dev/null)
|
|
74
|
-
if [ -n "$LAST_ASSISTANT" ]; then
|
|
75
|
-
# Extract the message content - look for text content in the message
|
|
76
|
-
# Format: {"message":{"content":[{"type":"text","text":"..."}]}}
|
|
77
|
-
# Use jq for robust JSON parsing when available, fallback to grep/sed
|
|
78
|
-
if command -v jq >/dev/null 2>&1; then
|
|
79
|
-
RAW_SUMMARY=$(echo "$LAST_ASSISTANT" | jq -r '.message.content[] | select(.type == "text") | .text' 2>/dev/null | head -1)
|
|
80
|
-
else
|
|
81
|
-
# Fallback: simple regex extraction (may fail on escaped quotes)
|
|
82
|
-
RAW_SUMMARY=$(echo "$LAST_ASSISTANT" | grep -o '"text"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*: *"//' | sed 's/"$//')
|
|
83
|
-
fi
|
|
84
|
-
if [ -n "$RAW_SUMMARY" ]; then
|
|
85
|
-
# Truncate to 100 chars and clean up newlines
|
|
86
|
-
TASK_SUMMARY=$(echo "$RAW_SUMMARY" | tr '\n' ' ' | cut -c1-100)
|
|
87
|
-
# Add ellipsis if truncated
|
|
88
|
-
if [ ${#RAW_SUMMARY} -gt 100 ]; then
|
|
89
|
-
TASK_SUMMARY="${TASK_SUMMARY}..."
|
|
90
|
-
fi
|
|
91
|
-
fi
|
|
92
|
-
fi
|
|
93
|
-
fi
|
|
94
|
-
|
|
95
|
-
# Build session info string (shown in body)
|
|
96
|
-
SESSION_INFO=""
|
|
97
|
-
if [ -n "$SESSION_ID" ]; then
|
|
98
|
-
SESSION_INFO="Session: $SESSION_ID"
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
# Determine notification title and body based on hook type
|
|
102
|
-
case "$HOOK_EVENT" in
|
|
103
|
-
"Notification")
|
|
104
|
-
case "$NOTIFICATION_TYPE" in
|
|
105
|
-
"permission_prompt")
|
|
106
|
-
TITLE="Claude [$SOURCE] - Permission Required"
|
|
107
|
-
BODY="${MESSAGE:-Claude needs your permission to continue}"
|
|
108
|
-
if [ -n "$SESSION_INFO" ]; then
|
|
109
|
-
BODY="$SESSION_INFO
|
|
110
|
-
$BODY"
|
|
111
|
-
fi
|
|
112
|
-
PRIORITY="high"
|
|
113
|
-
TAGS="warning"
|
|
114
|
-
;;
|
|
115
|
-
"idle_prompt")
|
|
116
|
-
TITLE="Claude [$SOURCE] - Waiting"
|
|
117
|
-
BODY="${MESSAGE:-Claude is waiting for your input}"
|
|
118
|
-
if [ -n "$SESSION_INFO" ]; then
|
|
119
|
-
BODY="$SESSION_INFO
|
|
120
|
-
$BODY"
|
|
121
|
-
fi
|
|
122
|
-
PRIORITY="default"
|
|
123
|
-
TAGS="hourglass"
|
|
124
|
-
;;
|
|
125
|
-
*)
|
|
126
|
-
TITLE="Claude [$SOURCE] - Attention"
|
|
127
|
-
BODY="${MESSAGE:-Claude needs your attention}"
|
|
128
|
-
if [ -n "$SESSION_INFO" ]; then
|
|
129
|
-
BODY="$SESSION_INFO
|
|
130
|
-
$BODY"
|
|
131
|
-
fi
|
|
132
|
-
PRIORITY="default"
|
|
133
|
-
TAGS="bell"
|
|
134
|
-
;;
|
|
135
|
-
esac
|
|
136
|
-
;;
|
|
137
|
-
"Stop")
|
|
138
|
-
TITLE="Claude [$SOURCE] - Finished"
|
|
139
|
-
BODY="$PROJECT_NAME"
|
|
140
|
-
if [ -n "$SESSION_INFO" ]; then
|
|
141
|
-
BODY="$SESSION_INFO | $BODY"
|
|
142
|
-
fi
|
|
143
|
-
if [ -n "$TASK_SUMMARY" ]; then
|
|
144
|
-
BODY="$BODY
|
|
145
|
-
$TASK_SUMMARY"
|
|
146
|
-
fi
|
|
147
|
-
PRIORITY="default"
|
|
148
|
-
TAGS="white_check_mark"
|
|
149
|
-
;;
|
|
150
|
-
"SubagentStop")
|
|
151
|
-
TITLE="Claude [$SOURCE] - Subagent Done"
|
|
152
|
-
BODY="$PROJECT_NAME"
|
|
153
|
-
if [ -n "$SESSION_INFO" ]; then
|
|
154
|
-
BODY="$SESSION_INFO | $BODY"
|
|
155
|
-
fi
|
|
156
|
-
if [ -n "$TASK_SUMMARY" ]; then
|
|
157
|
-
BODY="$BODY
|
|
158
|
-
$TASK_SUMMARY"
|
|
159
|
-
fi
|
|
160
|
-
PRIORITY="low"
|
|
161
|
-
TAGS="checkered_flag"
|
|
162
|
-
;;
|
|
163
|
-
*)
|
|
164
|
-
TITLE="Claude [$SOURCE]"
|
|
165
|
-
BODY="${MESSAGE:-Event: $HOOK_EVENT}"
|
|
166
|
-
if [ -n "$SESSION_INFO" ]; then
|
|
167
|
-
BODY="$SESSION_INFO
|
|
168
|
-
$BODY"
|
|
169
|
-
fi
|
|
170
|
-
PRIORITY="default"
|
|
171
|
-
TAGS="robot"
|
|
172
|
-
;;
|
|
173
|
-
esac
|
|
174
|
-
|
|
175
|
-
# Send notification via ntfy.sh
|
|
176
|
-
curl -s \
|
|
177
|
-
-H "Title: $TITLE" \
|
|
178
|
-
-H "Priority: $PRIORITY" \
|
|
179
|
-
-H "Tags: $TAGS" \
|
|
180
|
-
-d "$BODY" \
|
|
181
|
-
"https://ntfy.sh/$NTFY_TOPIC" > /dev/null 2>&1
|
|
182
|
-
|
|
183
|
-
exit 0
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
##
|
|
3
|
-
# Writes the JIRA CLI config file from environment variables.
|
|
4
|
-
# Runs on SessionStart so the config is available for every session.
|
|
5
|
-
#
|
|
6
|
-
# Required env vars (must be created in your Claude Code Web environment):
|
|
7
|
-
# JIRA_INSTALLATION - cloud or local
|
|
8
|
-
# JIRA_SERVER - Atlassian instance URL
|
|
9
|
-
# JIRA_LOGIN - login email
|
|
10
|
-
# JIRA_PROJECT - default project key
|
|
11
|
-
# JIRA_API_TOKEN - already expected by jira-cli natively
|
|
12
|
-
#
|
|
13
|
-
# Optional env vars:
|
|
14
|
-
# JIRA_BOARD - default board name
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
set -euo pipefail
|
|
18
|
-
|
|
19
|
-
# Fix jira-cli installation if install-pkgs.sh failed to extract correctly.
|
|
20
|
-
# Only attempt on Linux — the binary download is Linux-only.
|
|
21
|
-
if [[ "$(uname -s)" == "Linux" ]] && ! command -v jira &>/dev/null; then
|
|
22
|
-
JIRA_CLI_VERSION="1.7.0"
|
|
23
|
-
TMPDIR=$(mktemp -d)
|
|
24
|
-
curl -sSfL "https://github.com/ankitpokhrel/jira-cli/releases/download/v${JIRA_CLI_VERSION}/jira_${JIRA_CLI_VERSION}_linux_x86_64.tar.gz" \
|
|
25
|
-
| tar -xz -C "${TMPDIR}"
|
|
26
|
-
cp "${TMPDIR}/jira_${JIRA_CLI_VERSION}_linux_x86_64/bin/jira" /usr/local/bin/jira
|
|
27
|
-
chmod +x /usr/local/bin/jira
|
|
28
|
-
rm -rf "${TMPDIR}"
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
CONFIG_DIR="${HOME}/.config/.jira"
|
|
32
|
-
CONFIG_FILE="${CONFIG_DIR}/.config.yml"
|
|
33
|
-
|
|
34
|
-
# Skip config write if required vars are missing
|
|
35
|
-
if [[ -z "${JIRA_SERVER:-}" || -z "${JIRA_LOGIN:-}" ]]; then
|
|
36
|
-
exit 0
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
mkdir -p "${CONFIG_DIR}"
|
|
40
|
-
|
|
41
|
-
cat > "${CONFIG_FILE}" << EOF
|
|
42
|
-
installation: ${JIRA_INSTALLATION:-cloud}
|
|
43
|
-
server: ${JIRA_SERVER}
|
|
44
|
-
login: ${JIRA_LOGIN}
|
|
45
|
-
project: ${JIRA_PROJECT:-}
|
|
46
|
-
board: "${JIRA_BOARD:-}"
|
|
47
|
-
auth_type: basic
|
|
48
|
-
epic:
|
|
49
|
-
name: Epic Name
|
|
50
|
-
link: Epic Link
|
|
51
|
-
EOF
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Reads all .md files from the plugin's rules/ directory and injects them
|
|
3
|
-
# into Claude context at session/subagent start.
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
ROOT="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}}"
|
|
7
|
-
RULES_DIR="$ROOT/rules"
|
|
8
|
-
|
|
9
|
-
[ -d "$RULES_DIR" ] || exit 0
|
|
10
|
-
|
|
11
|
-
for rule in "$RULES_DIR"/*.md; do
|
|
12
|
-
[ -f "$rule" ] || continue
|
|
13
|
-
printf '\n<lisa-harper-fabric-rule path="%s">\n' "$rule"
|
|
14
|
-
cat "$rule"
|
|
15
|
-
printf '\n</lisa-harper-fabric-rule>\n'
|
|
16
|
-
done
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# This file is managed by Lisa.
|
|
3
|
-
# Do not edit directly — changes will be overwritten on the next `lisa` run.
|
|
4
|
-
|
|
5
|
-
# PreToolUse hook: block Write/Edit on TypeORM migration files.
|
|
6
|
-
# NestJS projects must use `bun run migration:generate` to create migrations
|
|
7
|
-
# from entity diffs. Hand-written migrations drift from entity metadata and
|
|
8
|
-
# break the schema/migration contract.
|
|
9
|
-
# Reference: https://docs.claude.com/en/docs/claude-code/hooks
|
|
10
|
-
# Exit code 2 blocks the tool call and surfaces stderr to Claude.
|
|
11
|
-
|
|
12
|
-
JSON_INPUT=$(cat)
|
|
13
|
-
|
|
14
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
15
|
-
echo "⚠ block-migration-edits: jq not available, allowing edit" >&2
|
|
16
|
-
exit 0
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
FILE_PATH=$(echo "$JSON_INPUT" | jq -r '.tool_input.file_path // empty')
|
|
20
|
-
|
|
21
|
-
if [ -z "$FILE_PATH" ]; then
|
|
22
|
-
exit 0
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
case "$FILE_PATH" in
|
|
26
|
-
*/migrations/*.ts|*/migrations/*.js)
|
|
27
|
-
cat >&2 <<EOF
|
|
28
|
-
❌ Blocked: Direct edits to TypeORM migration files are not allowed.
|
|
29
|
-
|
|
30
|
-
File: $FILE_PATH
|
|
31
|
-
|
|
32
|
-
Entity files (src/database/entities/*.ts) are the single source of
|
|
33
|
-
truth for the database schema in this project. Migrations are a derived
|
|
34
|
-
artifact — generate them from entity diffs:
|
|
35
|
-
|
|
36
|
-
1. Edit the entity to express the desired schema.
|
|
37
|
-
2. Run: bun run migration:generate --name=<DescriptiveName>
|
|
38
|
-
3. Review the generated migration; commit entity + migration together.
|
|
39
|
-
|
|
40
|
-
If a schema change cannot be expressed via the entity model, the entity
|
|
41
|
-
model is wrong — fix the entity, do not hand-write the migration.
|
|
42
|
-
|
|
43
|
-
OUT-OF-BAND MIGRATIONS (seed data, backfills, data transformations,
|
|
44
|
-
one-off cleanup): these genuinely cannot come from entity diffs. They
|
|
45
|
-
are legitimate but they bypass the entity-as-source-of-truth contract.
|
|
46
|
-
|
|
47
|
-
If you believe this edit is an out-of-band migration:
|
|
48
|
-
1. STOP and tell the user what change is needed and why it cannot
|
|
49
|
-
be expressed via the entity model.
|
|
50
|
-
2. Get explicit approval before proceeding.
|
|
51
|
-
3. Document the rationale in the migration's class comment.
|
|
52
|
-
|
|
53
|
-
Do NOT silently hand-write a migration. See the nestjs-rules skill
|
|
54
|
-
for the full rationale.
|
|
55
|
-
EOF
|
|
56
|
-
exit 2
|
|
57
|
-
;;
|
|
58
|
-
esac
|
|
59
|
-
|
|
60
|
-
exit 0
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Reads all .md files from the plugin's rules/ directory and injects them
|
|
3
|
-
# into the session context via additionalContext.
|
|
4
|
-
# Used by SessionStart and SubagentStart hooks.
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
RULES_DIR="${CLAUDE_PLUGIN_ROOT}/rules"
|
|
8
|
-
|
|
9
|
-
# Bail silently if no rules directory
|
|
10
|
-
[ -d "$RULES_DIR" ] || exit 0
|
|
11
|
-
|
|
12
|
-
CONTEXT=""
|
|
13
|
-
for file in "$RULES_DIR"/*.md; do
|
|
14
|
-
[ -f "$file" ] || continue
|
|
15
|
-
CONTEXT+="$(cat "$file")"$'\n\n'
|
|
16
|
-
done
|
|
17
|
-
|
|
18
|
-
# Bail if no rules found
|
|
19
|
-
[ -n "$CONTEXT" ] || exit 0
|
|
20
|
-
|
|
21
|
-
# Output as JSON — jq handles escaping
|
|
22
|
-
jq -n --arg ctx "$CONTEXT" '{"additionalContext": $ctx}'
|