@codyswann/lisa 4.26.2 → 4.26.3
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/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.js +2 -2
- package/package.json +3 -3
- 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-jira-journey/SKILL.md +24 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-journey/scripts/parse-plan.py +144 -30
- package/plugins/lisa/skills/lisa-jira-journey/SKILL.md +24 -2
- package/plugins/lisa/skills/lisa-jira-journey/scripts/parse-plan.py +144 -30
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-jira-journey/SKILL.md +24 -2
- package/plugins/lisa-agy/skills/lisa-jira-journey/scripts/parse-plan.py +144 -30
- 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/skills/lisa-jira-journey/SKILL.md +24 -2
- package/plugins/lisa-copilot/skills/lisa-jira-journey/scripts/parse-plan.py +144 -30
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-jira-journey/SKILL.md +24 -2
- package/plugins/lisa-cursor/skills/lisa-jira-journey/scripts/parse-plan.py +144 -30
- 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/skills/lisa-jira-journey/SKILL.md +24 -2
- package/plugins/src/base/skills/lisa-jira-journey/scripts/parse-plan.py +144 -30
|
@@ -25,6 +25,7 @@ Output (JSON):
|
|
|
25
25
|
}
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
+
import ipaddress
|
|
28
29
|
import json
|
|
29
30
|
import os
|
|
30
31
|
import re
|
|
@@ -41,6 +42,24 @@ from urllib.parse import urlsplit
|
|
|
41
42
|
# a capture obligation.
|
|
42
43
|
LOCAL_EVIDENCE_PATTERN = re.compile(r'\[(SCREENSHOT|EVIDENCE):\s*([^\]]+)\]')
|
|
43
44
|
|
|
45
|
+
# One ASCII DNS name: dot-separated labels of letters, digits and inner hyphens.
|
|
46
|
+
# IPv4 literals are a subset of this form. Anything else -- percent-encoding, a
|
|
47
|
+
# trailing dot, an empty label, a leading or trailing hyphen -- is refused,
|
|
48
|
+
# because a hostname this pattern cannot describe is one whose meaning depends
|
|
49
|
+
# on who resolves it.
|
|
50
|
+
DNS_NAME_PATTERN = re.compile(
|
|
51
|
+
r'^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# The only two path spellings that mean "no path".
|
|
55
|
+
ROOT_PATHS = ("", "/")
|
|
56
|
+
|
|
57
|
+
DEFAULT_HTTPS_PORT = 443
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class JiraConfigError(Exception):
|
|
61
|
+
"""A configured Jira server could not be established as trusted."""
|
|
62
|
+
|
|
44
63
|
|
|
45
64
|
def jira_config_candidates():
|
|
46
65
|
"""Return project-first jira-cli config candidates without duplicates."""
|
|
@@ -80,63 +99,158 @@ def read_jira_config(config_path):
|
|
|
80
99
|
return server, login
|
|
81
100
|
|
|
82
101
|
|
|
102
|
+
def canonical_authority(netloc, hostname):
|
|
103
|
+
"""Return the canonical authority for one host, or "" when it is not one."""
|
|
104
|
+
if netloc.startswith("["):
|
|
105
|
+
try:
|
|
106
|
+
return f"[{ipaddress.IPv6Address(hostname).compressed}]"
|
|
107
|
+
except ValueError:
|
|
108
|
+
return ""
|
|
109
|
+
return hostname if DNS_NAME_PATTERN.match(hostname) else ""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def is_bare_origin(server, parsed):
|
|
113
|
+
"""Report whether the raw value names an origin and nothing besides.
|
|
114
|
+
|
|
115
|
+
The query and fragment are checked against the raw string rather than the
|
|
116
|
+
parse, because ``https://host?`` and ``https://host`` parse to the same
|
|
117
|
+
empty query. A delimiter carrying nothing is still a component the operator
|
|
118
|
+
wrote and the trust key would discard.
|
|
119
|
+
"""
|
|
120
|
+
if "?" in server or "#" in server:
|
|
121
|
+
return False
|
|
122
|
+
if parsed.scheme != "https" or "@" in parsed.netloc:
|
|
123
|
+
return False
|
|
124
|
+
return parsed.path in ROOT_PATHS
|
|
125
|
+
|
|
126
|
+
|
|
83
127
|
def server_origin(server):
|
|
84
|
-
"""Return the
|
|
128
|
+
"""Return the canonical HTTPS origin, or "" when the value is not one.
|
|
129
|
+
|
|
130
|
+
This value is both the credential trust key and the base every request is
|
|
131
|
+
built from, so the two have to be the same string. A normalizer that reduced
|
|
132
|
+
an arbitrary URL to its origin would approve one value and send another:
|
|
133
|
+
userinfo, a query, a fragment, or a path would ride along into the request
|
|
134
|
+
while the trust check saw only the host, which is how an API token reaches a
|
|
135
|
+
URL that validation never approved. So the accepted grammar is a bare
|
|
136
|
+
origin -- ``https://host`` or ``https://host/`` with an optional non-default
|
|
137
|
+
port -- and every richer form is refused rather than trimmed down to fit.
|
|
138
|
+
|
|
139
|
+
Self-hosted context paths and internationalized hostnames are deliberately
|
|
140
|
+
out of scope: either needs its own base-path contract, and inventing one
|
|
141
|
+
here would re-open the gap between what is checked and what is sent.
|
|
142
|
+
"""
|
|
143
|
+
if not server or any(char <= " " or char >= "\x7f" for char in server):
|
|
144
|
+
return ""
|
|
85
145
|
try:
|
|
86
146
|
parsed = urlsplit(server)
|
|
87
147
|
port = parsed.port
|
|
88
148
|
except ValueError:
|
|
89
149
|
return ""
|
|
90
|
-
if parsed.
|
|
150
|
+
if not parsed.hostname or port == 0:
|
|
151
|
+
return ""
|
|
152
|
+
if not is_bare_origin(server, parsed):
|
|
91
153
|
return ""
|
|
92
|
-
|
|
154
|
+
authority = canonical_authority(parsed.netloc, parsed.hostname)
|
|
155
|
+
if not authority:
|
|
93
156
|
return ""
|
|
94
|
-
|
|
95
|
-
authority = f"[{hostname}]" if ":" in hostname else hostname
|
|
96
|
-
if port is not None and port != 443:
|
|
157
|
+
if port is not None and port != DEFAULT_HTTPS_PORT:
|
|
97
158
|
authority = f"{authority}:{port}"
|
|
98
159
|
return f"https://{authority}"
|
|
99
160
|
|
|
100
161
|
|
|
101
|
-
def
|
|
102
|
-
"""
|
|
162
|
+
def resolve_trusted_origin():
|
|
163
|
+
"""Return the operator-owned origin a checkout config must match, or "".
|
|
164
|
+
|
|
165
|
+
An explicit JIRA_SERVER wins over the home config. A JIRA_SERVER that is not
|
|
166
|
+
a canonical origin raises rather than resolving to "": degrading a malformed
|
|
167
|
+
trust root into "no trust root" would silently widen what a checkout config
|
|
168
|
+
is allowed to claim, which is the opposite of what setting it asked for.
|
|
169
|
+
"""
|
|
170
|
+
configured = os.environ.get("JIRA_SERVER", "")
|
|
171
|
+
if configured.strip():
|
|
172
|
+
origin = server_origin(configured)
|
|
173
|
+
if not origin:
|
|
174
|
+
raise JiraConfigError(
|
|
175
|
+
"JIRA_SERVER must be a valid HTTPS URL naming one origin, with "
|
|
176
|
+
"no userinfo, path, query or fragment"
|
|
177
|
+
)
|
|
178
|
+
return origin
|
|
179
|
+
|
|
180
|
+
home_config = (Path.home() / ".config" / ".jira" / ".config.yml").resolve()
|
|
181
|
+
if not home_config.exists():
|
|
182
|
+
return ""
|
|
183
|
+
return server_origin(read_jira_config(home_config)[0])
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def resolve_jira_config_path(trusted_origin=None):
|
|
187
|
+
"""Select the jira-cli config that governs this checkout.
|
|
188
|
+
|
|
189
|
+
The nearest checkout-owned config is decisive. When one exists it must match
|
|
190
|
+
the operator's trust root, and a mismatch fails naming that file. Falling
|
|
191
|
+
through to the home config instead would report a missing or unusable home
|
|
192
|
+
config for a project config that was found and refused -- an error about the
|
|
193
|
+
wrong file, pointing whoever has to fix it away from the one that is wrong.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
trusted_origin: The operator's trust root, resolved when not supplied.
|
|
197
|
+
"""
|
|
103
198
|
candidates = jira_config_candidates()
|
|
104
199
|
home_config = candidates[-1]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
trusted_server, _ = read_jira_config(home_config)
|
|
108
|
-
trusted_origin = server_origin(trusted_server)
|
|
200
|
+
if trusted_origin is None:
|
|
201
|
+
trusted_origin = resolve_trusted_origin()
|
|
109
202
|
|
|
110
203
|
for candidate in candidates[:-1]:
|
|
111
204
|
if not candidate.exists():
|
|
112
205
|
continue
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
206
|
+
if not trusted_origin:
|
|
207
|
+
raise JiraConfigError(
|
|
208
|
+
f"jira-cli config {candidate} cannot be trusted: neither "
|
|
209
|
+
"JIRA_SERVER nor a home config establishes a trust root"
|
|
210
|
+
)
|
|
211
|
+
if server_origin(read_jira_config(candidate)[0]) != trusted_origin:
|
|
212
|
+
raise JiraConfigError(
|
|
213
|
+
f"jira-cli config {candidate} server does not match the "
|
|
214
|
+
"trusted JIRA server origin"
|
|
215
|
+
)
|
|
216
|
+
return candidate
|
|
116
217
|
return home_config
|
|
117
218
|
|
|
118
219
|
|
|
119
220
|
def get_jira_config():
|
|
120
|
-
"""Read JIRA server and login from the project-first jira-cli config.
|
|
121
|
-
|
|
221
|
+
"""Read JIRA server and login from the project-first jira-cli config.
|
|
222
|
+
|
|
223
|
+
Every exit below happens before the token is read, so a configured URL that
|
|
224
|
+
fails validation cannot reach a request. The returned server is the
|
|
225
|
+
canonical origin, never the configured string, which is what keeps the value
|
|
226
|
+
that was checked and the value that is sent identical.
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
trusted_origin = resolve_trusted_origin()
|
|
230
|
+
config_path = resolve_jira_config_path(trusted_origin)
|
|
231
|
+
except JiraConfigError as error:
|
|
232
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
233
|
+
sys.exit(1)
|
|
234
|
+
|
|
122
235
|
if not config_path.exists():
|
|
123
236
|
print(f"ERROR: jira-cli config not found at {config_path}", file=sys.stderr)
|
|
124
237
|
sys.exit(1)
|
|
125
238
|
|
|
126
239
|
server, login = read_jira_config(config_path)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
240
|
+
origin = server_origin(server)
|
|
241
|
+
if not origin:
|
|
242
|
+
print(
|
|
243
|
+
f"ERROR: JIRA server in {config_path} must be an HTTPS URL naming "
|
|
244
|
+
"one origin, with no userinfo, path, query or fragment",
|
|
245
|
+
file=sys.stderr,
|
|
246
|
+
)
|
|
132
247
|
sys.exit(1)
|
|
133
|
-
if trusted_origin:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
print("ERROR: JIRA server must be an HTTPS URL from a trusted config", file=sys.stderr)
|
|
248
|
+
if trusted_origin and origin != trusted_origin:
|
|
249
|
+
print(
|
|
250
|
+
f"ERROR: jira-cli config {config_path} server does not match the "
|
|
251
|
+
"trusted JIRA server origin",
|
|
252
|
+
file=sys.stderr,
|
|
253
|
+
)
|
|
140
254
|
sys.exit(1)
|
|
141
255
|
|
|
142
256
|
token = os.environ.get("JIRA_API_TOKEN", "")
|
|
@@ -144,7 +258,7 @@ def get_jira_config():
|
|
|
144
258
|
print("ERROR: JIRA_API_TOKEN env var not set", file=sys.stderr)
|
|
145
259
|
sys.exit(1)
|
|
146
260
|
|
|
147
|
-
return
|
|
261
|
+
return origin, login, token
|
|
148
262
|
|
|
149
263
|
|
|
150
264
|
def fetch_ticket(server, login, token, ticket_id):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.26.
|
|
3
|
+
"version": "4.26.3",
|
|
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.26.
|
|
3
|
+
"version": "4.26.3",
|
|
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.26.
|
|
3
|
+
"version": "4.26.3",
|
|
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.26.
|
|
3
|
+
"version": "4.26.3",
|
|
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.26.
|
|
3
|
+
"version": "4.26.3",
|
|
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"
|
|
@@ -37,8 +37,30 @@ python3 .claude/skills/jira-journey/scripts/parse-plan.py <TICKET_ID>
|
|
|
37
37
|
|
|
38
38
|
The parser resolves Jira configuration from the checkout first: it searches
|
|
39
39
|
`$CLAUDE_PROJECT_DIR/.lisa/jira-cli/.config.yml`, then the current directory and
|
|
40
|
-
its parents, and
|
|
41
|
-
copy project settings into the home-level file just to make this
|
|
40
|
+
its parents, and uses `~/.config/.jira/.config.yml` when no checkout config
|
|
41
|
+
exists. Do not copy project settings into the home-level file just to make this
|
|
42
|
+
step work.
|
|
43
|
+
|
|
44
|
+
A checkout config is not automatically trusted. The first checkout candidate
|
|
45
|
+
that exists is decisive — that is `$CLAUDE_PROJECT_DIR` when it is set, which
|
|
46
|
+
outranks a config nearer the current directory, and only otherwise the nearest
|
|
47
|
+
one walking up from the current directory. Its server must match the operator's
|
|
48
|
+
trust root — `JIRA_SERVER` when set, otherwise the home config — and a mismatch
|
|
49
|
+
fails naming that file rather than quietly using the home config instead, so the
|
|
50
|
+
error points at the file that is actually wrong.
|
|
51
|
+
|
|
52
|
+
`server` must be a bare HTTPS origin: `https://host`, optionally with a port and
|
|
53
|
+
a single trailing slash. An explicit `:443` is accepted and canonicalized away,
|
|
54
|
+
so `https://host:443` and `https://host` are the same origin and either may be
|
|
55
|
+
configured. Userinfo (`https://user:token@…`), a query, a fragment, and any
|
|
56
|
+
other path are refused rather than trimmed away. So are a hostname this parser
|
|
57
|
+
cannot describe unambiguously — surrounding whitespace, control characters,
|
|
58
|
+
percent-encoded or non-ASCII hostnames, a trailing dot, and port 0.
|
|
59
|
+
The reason is that this value is both the trust key and the base every request
|
|
60
|
+
is built from; normalizing a richer URL down to its origin would approve one
|
|
61
|
+
value and then send the API token to a different one. Self-hosted context paths
|
|
62
|
+
and internationalized hostnames are out of scope and would need their own
|
|
63
|
+
base-path contract.
|
|
42
64
|
|
|
43
65
|
The script outputs JSON with: `ticket`, `prerequisites`, `steps`, `viewports`, `assertions`.
|
|
44
66
|
|