@codyswann/lisa 3.54.2 → 3.54.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.
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +1 -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 +12 -7
- package/dist/core/upstream-evidence-manifest.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 +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +37 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +53 -7
- package/plugins/lisa/hooks/threshold-ratchet.mjs +17 -0
- package/plugins/lisa/scripts/plugin-sync-explain.mjs +27 -1
- package/plugins/lisa/skills/lisa-linear-access/SKILL.md +37 -2
- package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +53 -7
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/plugin-sync-explain.mjs +27 -1
- package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +37 -2
- package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +53 -7
- 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-copilot/hooks/threshold-ratchet.mjs +17 -0
- package/plugins/lisa-copilot/scripts/plugin-sync-explain.mjs +27 -1
- package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +37 -2
- package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +53 -7
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/threshold-ratchet.mjs +17 -0
- package/plugins/lisa-cursor/scripts/plugin-sync-explain.mjs +27 -1
- package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +37 -2
- package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +53 -7
- 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/plugins/src/base/hooks/threshold-ratchet.mjs +17 -0
- package/plugins/src/base/scripts/plugin-sync-explain.mjs +27 -1
- package/plugins/src/base/skills/lisa-linear-access/SKILL.md +37 -2
- package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +53 -7
- package/rails/copy-overwrite/scripts/check-threshold-ratchet.mjs +17 -0
- package/scripts/check-security-floors.mjs +36 -10
- package/typescript/copy-overwrite/scripts/check-threshold-ratchet.mjs +17 -0
package/package.json
CHANGED
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"zod-validation-error": "^4.0.0"
|
|
142
142
|
},
|
|
143
143
|
"name": "@codyswann/lisa",
|
|
144
|
-
"version": "3.54.
|
|
144
|
+
"version": "3.54.4",
|
|
145
145
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
146
146
|
"main": "dist/index.js",
|
|
147
147
|
"exports": {
|
|
@@ -84,9 +84,37 @@ All GraphQL calls use:
|
|
|
84
84
|
# nowhere to migrate to. Mirrors `atlassian-access` and `notion-access`.
|
|
85
85
|
read_linear_key() {
|
|
86
86
|
[ -n "${LINEAR_API_KEY:-}" ] && { echo "$LINEAR_API_KEY"; return; }
|
|
87
|
+
|
|
88
|
+
# The ladder is ordered repo-first and ends at the plugin's own copy.
|
|
89
|
+
#
|
|
90
|
+
# Repo-relative rungs lead because a project that vendors the resolver has
|
|
91
|
+
# declared which copy it wants used, and that decision must survive this
|
|
92
|
+
# change untouched. The plugin rungs are the floor: `resolve-secret.mjs`
|
|
93
|
+
# ships beside this skill, so a rung pointing at it is reachable from
|
|
94
|
+
# anywhere the plugin itself is installed. Without one, a repo that vendors
|
|
95
|
+
# none of the leading paths has no route to the key at all — which is what
|
|
96
|
+
# every consumer of this skill in a `.opencode`-layout repository actually
|
|
97
|
+
# hit, and why agents started improvising their own key lookups.
|
|
98
|
+
local candidates=(
|
|
99
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
100
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
101
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
102
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
103
|
+
)
|
|
104
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
105
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
106
|
+
fi
|
|
107
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
108
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
109
|
+
fi
|
|
110
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
111
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
112
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
113
|
+
|
|
87
114
|
local resolver
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
local tried=()
|
|
116
|
+
for resolver in "${candidates[@]}"; do
|
|
117
|
+
tried+=("$resolver")
|
|
90
118
|
if [ -f "$resolver" ]; then
|
|
91
119
|
local via_lisa
|
|
92
120
|
via_lisa=$(node "$resolver" get LINEAR_API_KEY 2>/dev/null) \
|
|
@@ -94,6 +122,13 @@ read_linear_key() {
|
|
|
94
122
|
break
|
|
95
123
|
fi
|
|
96
124
|
done
|
|
125
|
+
|
|
126
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
127
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
128
|
+
# seconds-long diagnosis. Paths only — never any resolved value.
|
|
129
|
+
echo "Error: could not resolve LINEAR_API_KEY through lisa-secrets-access." >&2
|
|
130
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
131
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
97
132
|
return 1
|
|
98
133
|
}
|
|
99
134
|
|
|
@@ -92,10 +92,43 @@ read_linear_key() { # $1=workspace slug
|
|
|
92
92
|
# Preferred path: the single secrets chokepoint. It owns the one-store rule
|
|
93
93
|
# and the surface ladder, so anything it can answer must not be read out of an
|
|
94
94
|
# OS keychain here — a second reader is how the same credential ends up living
|
|
95
|
-
# in two places and drifting.
|
|
95
|
+
# in two places and drifting.
|
|
96
|
+
#
|
|
97
|
+
# The CANDIDATE LADDER below must stay identical to `linear-access`. What may
|
|
98
|
+
# differ is only what happens after it: `linear-access` has nowhere else to
|
|
99
|
+
# go and fails loudly, whereas this skill falls through to the legacy keychain
|
|
100
|
+
# rung. Stating the invariant as "the ladder" rather than "this function" is
|
|
101
|
+
# deliberate — the previous wording said to keep the whole thing identical,
|
|
102
|
+
# which is not achievable, and a rule that cannot be followed is a rule that
|
|
103
|
+
# gets ignored. That is exactly how this copy kept the two-rung ladder while
|
|
104
|
+
# `linear-access` grew to seven, leaving `/lisa:setup:linear` unable to reach
|
|
105
|
+
# a key that `lisa-linear-access` could read from the same repository.
|
|
106
|
+
#
|
|
107
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
108
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
109
|
+
# wants used. The plugin rungs are the floor: `resolve-secret.mjs` ships
|
|
110
|
+
# beside this skill, so a rung pointing at it is reachable from anywhere the
|
|
111
|
+
# plugin itself is installed.
|
|
112
|
+
local candidates=(
|
|
113
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
114
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
115
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
116
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
117
|
+
)
|
|
118
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
119
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
120
|
+
fi
|
|
121
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
122
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
123
|
+
fi
|
|
124
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
125
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
126
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
127
|
+
|
|
96
128
|
local resolver
|
|
97
|
-
|
|
98
|
-
|
|
129
|
+
local tried=()
|
|
130
|
+
for resolver in "${candidates[@]}"; do
|
|
131
|
+
tried+=("$resolver")
|
|
99
132
|
if [ -f "$resolver" ]; then
|
|
100
133
|
local via_lisa
|
|
101
134
|
via_lisa=$(node "$resolver" get LINEAR_API_KEY 2>/dev/null) \
|
|
@@ -106,15 +139,16 @@ read_linear_key() { # $1=workspace slug
|
|
|
106
139
|
# Legacy fallback: the OS keychain written by the guided flow below, for
|
|
107
140
|
# projects that have not adopted a credentials provider. Reached only when the
|
|
108
141
|
# chokepoint is absent or has no entry.
|
|
142
|
+
local from_keychain=""
|
|
109
143
|
case "$(uname -s)" in
|
|
110
|
-
Darwin) security find-generic-password -s lisa-linear -a "$ws" -w 2>/dev/null ;;
|
|
111
|
-
Linux) command -v secret-tool >/dev/null && secret-tool lookup service lisa-linear account "$ws" 2>/dev/null ;;
|
|
144
|
+
Darwin) from_keychain=$(security find-generic-password -s lisa-linear -a "$ws" -w 2>/dev/null) ;;
|
|
145
|
+
Linux) command -v secret-tool >/dev/null && from_keychain=$(secret-tool lookup service lisa-linear account "$ws" 2>/dev/null) ;;
|
|
112
146
|
MINGW*|MSYS*|CYGWIN*)
|
|
113
147
|
# `cmdkey /generic ... /pass:` stores the secret in Windows Credential Manager, but
|
|
114
148
|
# `cmdkey /list` never prints stored passwords (by design). Read the CredentialBlob
|
|
115
149
|
# back via the Win32 CredRead API through PowerShell; pass the target name via an env
|
|
116
150
|
# var to dodge nested quoting, and strip the CRLF powershell.exe appends.
|
|
117
|
-
LISA_CRED_TARGET="lisa-linear-${ws}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
151
|
+
from_keychain=$(LISA_CRED_TARGET="lisa-linear-${ws}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
118
152
|
Add-Type -TypeDefinition @"
|
|
119
153
|
using System;
|
|
120
154
|
using System.Runtime.InteropServices;
|
|
@@ -140,8 +174,20 @@ public static class LisaCred {
|
|
|
140
174
|
}
|
|
141
175
|
}
|
|
142
176
|
"@
|
|
143
|
-
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r' ;;
|
|
177
|
+
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r') ;;
|
|
144
178
|
esac
|
|
179
|
+
[ -n "$from_keychain" ] && { echo "$from_keychain"; return; }
|
|
180
|
+
|
|
181
|
+
# Name every path, the same way `linear-access` does — the diagnostics are
|
|
182
|
+
# part of the parity, not decoration. A silent empty return sends the next
|
|
183
|
+
# reader hunting for a resolver they cannot see the absence of; the
|
|
184
|
+
# enumeration turns that into a seconds-long diagnosis. Paths and store
|
|
185
|
+
# coordinates only — never any resolved value, on any path.
|
|
186
|
+
echo "Error: could not resolve LINEAR_API_KEY through lisa-secrets-access or the legacy keychain." >&2
|
|
187
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
188
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
189
|
+
echo " <OS keychain> service=lisa-linear account=$ws" >&2
|
|
190
|
+
return 1
|
|
145
191
|
}
|
|
146
192
|
|
|
147
193
|
KEY=$(read_linear_key "$WORKSPACE")
|
|
@@ -53,8 +53,25 @@ import {
|
|
|
53
53
|
* Standard git locations, checked in order so the executable comes from a
|
|
54
54
|
* fixed, unwriteable directory rather than a PATH lookup. The bare "git"
|
|
55
55
|
* fallback keeps unusual layouts (e.g. Windows git-bash) working.
|
|
56
|
+
*
|
|
57
|
+
* Order within that constraint is set by measurement, not by convention. On
|
|
58
|
+
* macOS `/usr/bin/git` is not git: it is Apple's `xcrun` shim, which locates a
|
|
59
|
+
* developer directory and re-executes the real binary there. Dispatching
|
|
60
|
+
* through it costs a **median 33 ms against 15 ms** for either
|
|
61
|
+
* developer-directory git, and **100 ms against 21 ms at p90** — randomized
|
|
62
|
+
* call order, fixed inter-call gaps, `git rev-parse --show-toplevel`, n=30 each
|
|
63
|
+
* (lisa#2898). The call does no work at all; the difference is the dispatch.
|
|
64
|
+
*
|
|
65
|
+
* The two entries promoted ahead of it are the developer-directory gits the
|
|
66
|
+
* shim itself re-executes. Both are `root:wheel` files in system locations, so
|
|
67
|
+
* this is the same trust class as `/usr/bin/git` and not a relaxation: the
|
|
68
|
+
* user-writable `/usr/local` and Homebrew entries stay behind it, exactly where
|
|
69
|
+
* they already were. Neither promoted path exists on Linux, so every CI runner
|
|
70
|
+
* resolves precisely what it resolved before.
|
|
56
71
|
*/
|
|
57
72
|
const GIT_LOCATIONS = [
|
|
73
|
+
"/Library/Developer/CommandLineTools/usr/bin/git",
|
|
74
|
+
"/Applications/Xcode.app/Contents/Developer/usr/bin/git",
|
|
58
75
|
"/usr/bin/git",
|
|
59
76
|
"/usr/local/bin/git",
|
|
60
77
|
"/opt/homebrew/bin/git",
|
|
@@ -33,7 +33,33 @@ export const PLUGIN_SYNC_CLASSIFICATIONS = [
|
|
|
33
33
|
const PLUGINS_DIR = "plugins";
|
|
34
34
|
const SOURCE_ROOT = "plugins/src";
|
|
35
35
|
const MARKETPLACE = ".claude-plugin/marketplace.json";
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Fixed absolute git locations, tried in order rather than a bare command name
|
|
38
|
+
* so a writeable directory early on `PATH` cannot decide which binary runs.
|
|
39
|
+
*
|
|
40
|
+
* Order within that constraint is set by measurement, not by convention. On
|
|
41
|
+
* macOS `/usr/bin/git` is not git: it is Apple's `xcrun` shim, which locates a
|
|
42
|
+
* developer directory and re-executes the real binary there. Dispatching
|
|
43
|
+
* through it costs a **median 33 ms against 15 ms** for either
|
|
44
|
+
* developer-directory git, and **100 ms against 21 ms at p90** — randomized
|
|
45
|
+
* call order, fixed inter-call gaps, `git rev-parse --show-toplevel`, n=30 each
|
|
46
|
+
* (lisa#2898). The call does no work at all; the difference is the dispatch.
|
|
47
|
+
*
|
|
48
|
+
* The two entries promoted ahead of it are the developer-directory gits the
|
|
49
|
+
* shim itself re-executes. Both are `root:wheel` files in system locations, so
|
|
50
|
+
* this is the same trust class as `/usr/bin/git` and not a relaxation: the
|
|
51
|
+
* user-writable `/usr/local` and Homebrew entries stay behind it, exactly where
|
|
52
|
+
* they already were. Neither promoted path exists on Linux, so every CI runner
|
|
53
|
+
* resolves precisely what it resolved before.
|
|
54
|
+
*/
|
|
55
|
+
const GIT_LOCATIONS = [
|
|
56
|
+
"/Library/Developer/CommandLineTools/usr/bin/git",
|
|
57
|
+
"/Applications/Xcode.app/Contents/Developer/usr/bin/git",
|
|
58
|
+
"/usr/bin/git",
|
|
59
|
+
"/usr/local/bin/git",
|
|
60
|
+
"/opt/homebrew/bin/git",
|
|
61
|
+
];
|
|
62
|
+
const GIT_BIN = GIT_LOCATIONS.find(candidate => existsSync(candidate)) ?? "git";
|
|
37
63
|
|
|
38
64
|
/**
|
|
39
65
|
* @typedef {{
|
|
@@ -84,9 +84,37 @@ All GraphQL calls use:
|
|
|
84
84
|
# nowhere to migrate to. Mirrors `atlassian-access` and `notion-access`.
|
|
85
85
|
read_linear_key() {
|
|
86
86
|
[ -n "${LINEAR_API_KEY:-}" ] && { echo "$LINEAR_API_KEY"; return; }
|
|
87
|
+
|
|
88
|
+
# The ladder is ordered repo-first and ends at the plugin's own copy.
|
|
89
|
+
#
|
|
90
|
+
# Repo-relative rungs lead because a project that vendors the resolver has
|
|
91
|
+
# declared which copy it wants used, and that decision must survive this
|
|
92
|
+
# change untouched. The plugin rungs are the floor: `resolve-secret.mjs`
|
|
93
|
+
# ships beside this skill, so a rung pointing at it is reachable from
|
|
94
|
+
# anywhere the plugin itself is installed. Without one, a repo that vendors
|
|
95
|
+
# none of the leading paths has no route to the key at all — which is what
|
|
96
|
+
# every consumer of this skill in a `.opencode`-layout repository actually
|
|
97
|
+
# hit, and why agents started improvising their own key lookups.
|
|
98
|
+
local candidates=(
|
|
99
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
100
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
101
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
102
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
103
|
+
)
|
|
104
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
105
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
106
|
+
fi
|
|
107
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
108
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
109
|
+
fi
|
|
110
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
111
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
112
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
113
|
+
|
|
87
114
|
local resolver
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
local tried=()
|
|
116
|
+
for resolver in "${candidates[@]}"; do
|
|
117
|
+
tried+=("$resolver")
|
|
90
118
|
if [ -f "$resolver" ]; then
|
|
91
119
|
local via_lisa
|
|
92
120
|
via_lisa=$(node "$resolver" get LINEAR_API_KEY 2>/dev/null) \
|
|
@@ -94,6 +122,13 @@ read_linear_key() {
|
|
|
94
122
|
break
|
|
95
123
|
fi
|
|
96
124
|
done
|
|
125
|
+
|
|
126
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
127
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
128
|
+
# seconds-long diagnosis. Paths only — never any resolved value.
|
|
129
|
+
echo "Error: could not resolve LINEAR_API_KEY through lisa-secrets-access." >&2
|
|
130
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
131
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
97
132
|
return 1
|
|
98
133
|
}
|
|
99
134
|
|
|
@@ -92,10 +92,43 @@ read_linear_key() { # $1=workspace slug
|
|
|
92
92
|
# Preferred path: the single secrets chokepoint. It owns the one-store rule
|
|
93
93
|
# and the surface ladder, so anything it can answer must not be read out of an
|
|
94
94
|
# OS keychain here — a second reader is how the same credential ends up living
|
|
95
|
-
# in two places and drifting.
|
|
95
|
+
# in two places and drifting.
|
|
96
|
+
#
|
|
97
|
+
# The CANDIDATE LADDER below must stay identical to `linear-access`. What may
|
|
98
|
+
# differ is only what happens after it: `linear-access` has nowhere else to
|
|
99
|
+
# go and fails loudly, whereas this skill falls through to the legacy keychain
|
|
100
|
+
# rung. Stating the invariant as "the ladder" rather than "this function" is
|
|
101
|
+
# deliberate — the previous wording said to keep the whole thing identical,
|
|
102
|
+
# which is not achievable, and a rule that cannot be followed is a rule that
|
|
103
|
+
# gets ignored. That is exactly how this copy kept the two-rung ladder while
|
|
104
|
+
# `linear-access` grew to seven, leaving `/lisa:setup:linear` unable to reach
|
|
105
|
+
# a key that `lisa-linear-access` could read from the same repository.
|
|
106
|
+
#
|
|
107
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
108
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
109
|
+
# wants used. The plugin rungs are the floor: `resolve-secret.mjs` ships
|
|
110
|
+
# beside this skill, so a rung pointing at it is reachable from anywhere the
|
|
111
|
+
# plugin itself is installed.
|
|
112
|
+
local candidates=(
|
|
113
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
114
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
115
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
116
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
117
|
+
)
|
|
118
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
119
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
120
|
+
fi
|
|
121
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
122
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
123
|
+
fi
|
|
124
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
125
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
126
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
127
|
+
|
|
96
128
|
local resolver
|
|
97
|
-
|
|
98
|
-
|
|
129
|
+
local tried=()
|
|
130
|
+
for resolver in "${candidates[@]}"; do
|
|
131
|
+
tried+=("$resolver")
|
|
99
132
|
if [ -f "$resolver" ]; then
|
|
100
133
|
local via_lisa
|
|
101
134
|
via_lisa=$(node "$resolver" get LINEAR_API_KEY 2>/dev/null) \
|
|
@@ -106,15 +139,16 @@ read_linear_key() { # $1=workspace slug
|
|
|
106
139
|
# Legacy fallback: the OS keychain written by the guided flow below, for
|
|
107
140
|
# projects that have not adopted a credentials provider. Reached only when the
|
|
108
141
|
# chokepoint is absent or has no entry.
|
|
142
|
+
local from_keychain=""
|
|
109
143
|
case "$(uname -s)" in
|
|
110
|
-
Darwin) security find-generic-password -s lisa-linear -a "$ws" -w 2>/dev/null ;;
|
|
111
|
-
Linux) command -v secret-tool >/dev/null && secret-tool lookup service lisa-linear account "$ws" 2>/dev/null ;;
|
|
144
|
+
Darwin) from_keychain=$(security find-generic-password -s lisa-linear -a "$ws" -w 2>/dev/null) ;;
|
|
145
|
+
Linux) command -v secret-tool >/dev/null && from_keychain=$(secret-tool lookup service lisa-linear account "$ws" 2>/dev/null) ;;
|
|
112
146
|
MINGW*|MSYS*|CYGWIN*)
|
|
113
147
|
# `cmdkey /generic ... /pass:` stores the secret in Windows Credential Manager, but
|
|
114
148
|
# `cmdkey /list` never prints stored passwords (by design). Read the CredentialBlob
|
|
115
149
|
# back via the Win32 CredRead API through PowerShell; pass the target name via an env
|
|
116
150
|
# var to dodge nested quoting, and strip the CRLF powershell.exe appends.
|
|
117
|
-
LISA_CRED_TARGET="lisa-linear-${ws}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
151
|
+
from_keychain=$(LISA_CRED_TARGET="lisa-linear-${ws}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
118
152
|
Add-Type -TypeDefinition @"
|
|
119
153
|
using System;
|
|
120
154
|
using System.Runtime.InteropServices;
|
|
@@ -140,8 +174,20 @@ public static class LisaCred {
|
|
|
140
174
|
}
|
|
141
175
|
}
|
|
142
176
|
"@
|
|
143
|
-
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r' ;;
|
|
177
|
+
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r') ;;
|
|
144
178
|
esac
|
|
179
|
+
[ -n "$from_keychain" ] && { echo "$from_keychain"; return; }
|
|
180
|
+
|
|
181
|
+
# Name every path, the same way `linear-access` does — the diagnostics are
|
|
182
|
+
# part of the parity, not decoration. A silent empty return sends the next
|
|
183
|
+
# reader hunting for a resolver they cannot see the absence of; the
|
|
184
|
+
# enumeration turns that into a seconds-long diagnosis. Paths and store
|
|
185
|
+
# coordinates only — never any resolved value, on any path.
|
|
186
|
+
echo "Error: could not resolve LINEAR_API_KEY through lisa-secrets-access or the legacy keychain." >&2
|
|
187
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
188
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
189
|
+
echo " <OS keychain> service=lisa-linear account=$ws" >&2
|
|
190
|
+
return 1
|
|
145
191
|
}
|
|
146
192
|
|
|
147
193
|
KEY=$(read_linear_key "$WORKSPACE")
|
|
@@ -33,7 +33,33 @@ export const PLUGIN_SYNC_CLASSIFICATIONS = [
|
|
|
33
33
|
const PLUGINS_DIR = "plugins";
|
|
34
34
|
const SOURCE_ROOT = "plugins/src";
|
|
35
35
|
const MARKETPLACE = ".claude-plugin/marketplace.json";
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Fixed absolute git locations, tried in order rather than a bare command name
|
|
38
|
+
* so a writeable directory early on `PATH` cannot decide which binary runs.
|
|
39
|
+
*
|
|
40
|
+
* Order within that constraint is set by measurement, not by convention. On
|
|
41
|
+
* macOS `/usr/bin/git` is not git: it is Apple's `xcrun` shim, which locates a
|
|
42
|
+
* developer directory and re-executes the real binary there. Dispatching
|
|
43
|
+
* through it costs a **median 33 ms against 15 ms** for either
|
|
44
|
+
* developer-directory git, and **100 ms against 21 ms at p90** — randomized
|
|
45
|
+
* call order, fixed inter-call gaps, `git rev-parse --show-toplevel`, n=30 each
|
|
46
|
+
* (lisa#2898). The call does no work at all; the difference is the dispatch.
|
|
47
|
+
*
|
|
48
|
+
* The two entries promoted ahead of it are the developer-directory gits the
|
|
49
|
+
* shim itself re-executes. Both are `root:wheel` files in system locations, so
|
|
50
|
+
* this is the same trust class as `/usr/bin/git` and not a relaxation: the
|
|
51
|
+
* user-writable `/usr/local` and Homebrew entries stay behind it, exactly where
|
|
52
|
+
* they already were. Neither promoted path exists on Linux, so every CI runner
|
|
53
|
+
* resolves precisely what it resolved before.
|
|
54
|
+
*/
|
|
55
|
+
const GIT_LOCATIONS = [
|
|
56
|
+
"/Library/Developer/CommandLineTools/usr/bin/git",
|
|
57
|
+
"/Applications/Xcode.app/Contents/Developer/usr/bin/git",
|
|
58
|
+
"/usr/bin/git",
|
|
59
|
+
"/usr/local/bin/git",
|
|
60
|
+
"/opt/homebrew/bin/git",
|
|
61
|
+
];
|
|
62
|
+
const GIT_BIN = GIT_LOCATIONS.find(candidate => existsSync(candidate)) ?? "git";
|
|
37
63
|
|
|
38
64
|
/**
|
|
39
65
|
* @typedef {{
|
|
@@ -84,9 +84,37 @@ All GraphQL calls use:
|
|
|
84
84
|
# nowhere to migrate to. Mirrors `atlassian-access` and `notion-access`.
|
|
85
85
|
read_linear_key() {
|
|
86
86
|
[ -n "${LINEAR_API_KEY:-}" ] && { echo "$LINEAR_API_KEY"; return; }
|
|
87
|
+
|
|
88
|
+
# The ladder is ordered repo-first and ends at the plugin's own copy.
|
|
89
|
+
#
|
|
90
|
+
# Repo-relative rungs lead because a project that vendors the resolver has
|
|
91
|
+
# declared which copy it wants used, and that decision must survive this
|
|
92
|
+
# change untouched. The plugin rungs are the floor: `resolve-secret.mjs`
|
|
93
|
+
# ships beside this skill, so a rung pointing at it is reachable from
|
|
94
|
+
# anywhere the plugin itself is installed. Without one, a repo that vendors
|
|
95
|
+
# none of the leading paths has no route to the key at all — which is what
|
|
96
|
+
# every consumer of this skill in a `.opencode`-layout repository actually
|
|
97
|
+
# hit, and why agents started improvising their own key lookups.
|
|
98
|
+
local candidates=(
|
|
99
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
100
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
101
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
102
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
103
|
+
)
|
|
104
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
105
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
106
|
+
fi
|
|
107
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
108
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
109
|
+
fi
|
|
110
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
111
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
112
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
113
|
+
|
|
87
114
|
local resolver
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
local tried=()
|
|
116
|
+
for resolver in "${candidates[@]}"; do
|
|
117
|
+
tried+=("$resolver")
|
|
90
118
|
if [ -f "$resolver" ]; then
|
|
91
119
|
local via_lisa
|
|
92
120
|
via_lisa=$(node "$resolver" get LINEAR_API_KEY 2>/dev/null) \
|
|
@@ -94,6 +122,13 @@ read_linear_key() {
|
|
|
94
122
|
break
|
|
95
123
|
fi
|
|
96
124
|
done
|
|
125
|
+
|
|
126
|
+
# Name every path. A bare `return 1` sends the next reader hunting for a
|
|
127
|
+
# resolver they cannot see the absence of; the enumeration turns that into a
|
|
128
|
+
# seconds-long diagnosis. Paths only — never any resolved value.
|
|
129
|
+
echo "Error: could not resolve LINEAR_API_KEY through lisa-secrets-access." >&2
|
|
130
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
131
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
97
132
|
return 1
|
|
98
133
|
}
|
|
99
134
|
|
|
@@ -92,10 +92,43 @@ read_linear_key() { # $1=workspace slug
|
|
|
92
92
|
# Preferred path: the single secrets chokepoint. It owns the one-store rule
|
|
93
93
|
# and the surface ladder, so anything it can answer must not be read out of an
|
|
94
94
|
# OS keychain here — a second reader is how the same credential ends up living
|
|
95
|
-
# in two places and drifting.
|
|
95
|
+
# in two places and drifting.
|
|
96
|
+
#
|
|
97
|
+
# The CANDIDATE LADDER below must stay identical to `linear-access`. What may
|
|
98
|
+
# differ is only what happens after it: `linear-access` has nowhere else to
|
|
99
|
+
# go and fails loudly, whereas this skill falls through to the legacy keychain
|
|
100
|
+
# rung. Stating the invariant as "the ladder" rather than "this function" is
|
|
101
|
+
# deliberate — the previous wording said to keep the whole thing identical,
|
|
102
|
+
# which is not achievable, and a rule that cannot be followed is a rule that
|
|
103
|
+
# gets ignored. That is exactly how this copy kept the two-rung ladder while
|
|
104
|
+
# `linear-access` grew to seven, leaving `/lisa:setup:linear` unable to reach
|
|
105
|
+
# a key that `lisa-linear-access` could read from the same repository.
|
|
106
|
+
#
|
|
107
|
+
# Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
|
|
108
|
+
# lead because a project that vendors the resolver has declared which copy it
|
|
109
|
+
# wants used. The plugin rungs are the floor: `resolve-secret.mjs` ships
|
|
110
|
+
# beside this skill, so a rung pointing at it is reachable from anywhere the
|
|
111
|
+
# plugin itself is installed.
|
|
112
|
+
local candidates=(
|
|
113
|
+
.claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
114
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
115
|
+
.opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
116
|
+
.codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
|
|
117
|
+
)
|
|
118
|
+
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
119
|
+
candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
120
|
+
fi
|
|
121
|
+
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
122
|
+
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
123
|
+
fi
|
|
124
|
+
# Last rung deliberately needs no environment variable: an agent that was
|
|
125
|
+
# never handed a plugin root still has the installed package to fall back on.
|
|
126
|
+
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
127
|
+
|
|
96
128
|
local resolver
|
|
97
|
-
|
|
98
|
-
|
|
129
|
+
local tried=()
|
|
130
|
+
for resolver in "${candidates[@]}"; do
|
|
131
|
+
tried+=("$resolver")
|
|
99
132
|
if [ -f "$resolver" ]; then
|
|
100
133
|
local via_lisa
|
|
101
134
|
via_lisa=$(node "$resolver" get LINEAR_API_KEY 2>/dev/null) \
|
|
@@ -106,15 +139,16 @@ read_linear_key() { # $1=workspace slug
|
|
|
106
139
|
# Legacy fallback: the OS keychain written by the guided flow below, for
|
|
107
140
|
# projects that have not adopted a credentials provider. Reached only when the
|
|
108
141
|
# chokepoint is absent or has no entry.
|
|
142
|
+
local from_keychain=""
|
|
109
143
|
case "$(uname -s)" in
|
|
110
|
-
Darwin) security find-generic-password -s lisa-linear -a "$ws" -w 2>/dev/null ;;
|
|
111
|
-
Linux) command -v secret-tool >/dev/null && secret-tool lookup service lisa-linear account "$ws" 2>/dev/null ;;
|
|
144
|
+
Darwin) from_keychain=$(security find-generic-password -s lisa-linear -a "$ws" -w 2>/dev/null) ;;
|
|
145
|
+
Linux) command -v secret-tool >/dev/null && from_keychain=$(secret-tool lookup service lisa-linear account "$ws" 2>/dev/null) ;;
|
|
112
146
|
MINGW*|MSYS*|CYGWIN*)
|
|
113
147
|
# `cmdkey /generic ... /pass:` stores the secret in Windows Credential Manager, but
|
|
114
148
|
# `cmdkey /list` never prints stored passwords (by design). Read the CredentialBlob
|
|
115
149
|
# back via the Win32 CredRead API through PowerShell; pass the target name via an env
|
|
116
150
|
# var to dodge nested quoting, and strip the CRLF powershell.exe appends.
|
|
117
|
-
LISA_CRED_TARGET="lisa-linear-${ws}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
151
|
+
from_keychain=$(LISA_CRED_TARGET="lisa-linear-${ws}" powershell.exe -NoProfile -NonInteractive -Command '
|
|
118
152
|
Add-Type -TypeDefinition @"
|
|
119
153
|
using System;
|
|
120
154
|
using System.Runtime.InteropServices;
|
|
@@ -140,8 +174,20 @@ public static class LisaCred {
|
|
|
140
174
|
}
|
|
141
175
|
}
|
|
142
176
|
"@
|
|
143
|
-
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r' ;;
|
|
177
|
+
[LisaCred]::Read($env:LISA_CRED_TARGET)' 2>/dev/null | tr -d '\r') ;;
|
|
144
178
|
esac
|
|
179
|
+
[ -n "$from_keychain" ] && { echo "$from_keychain"; return; }
|
|
180
|
+
|
|
181
|
+
# Name every path, the same way `linear-access` does — the diagnostics are
|
|
182
|
+
# part of the parity, not decoration. A silent empty return sends the next
|
|
183
|
+
# reader hunting for a resolver they cannot see the absence of; the
|
|
184
|
+
# enumeration turns that into a seconds-long diagnosis. Paths and store
|
|
185
|
+
# coordinates only — never any resolved value, on any path.
|
|
186
|
+
echo "Error: could not resolve LINEAR_API_KEY through lisa-secrets-access or the legacy keychain." >&2
|
|
187
|
+
echo "Tried, in order (relative paths are from $PWD):" >&2
|
|
188
|
+
printf ' %s\n' "${tried[@]}" >&2
|
|
189
|
+
echo " <OS keychain> service=lisa-linear account=$ws" >&2
|
|
190
|
+
return 1
|
|
145
191
|
}
|
|
146
192
|
|
|
147
193
|
KEY=$(read_linear_key "$WORKSPACE")
|