@codyswann/lisa 4.10.5 → 4.10.7
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-hooks/parity-safety-net.sh +269 -44
- package/dist/codex/scripts/setup-jira-cli.sh +50 -0
- 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 +19 -17
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/lisa-session-bootstrap.ts +13 -0
- package/dist/opencode/plugin-templates/parity-safety-net.sh +269 -44
- 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-jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-evidence/SKILL.md +6 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-journey/SKILL.md +2 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +53 -4
- package/plugins/lisa/hooks/parity-safety-net.sh +269 -44
- package/plugins/lisa/hooks/setup-jira-cli.sh +50 -0
- package/plugins/lisa/rules/reference/base-rules.md +1 -1
- package/plugins/lisa/skills/lisa-jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa/skills/lisa-jira-evidence/SKILL.md +6 -3
- package/plugins/lisa/skills/lisa-jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa/skills/lisa-jira-journey/SKILL.md +2 -1
- package/plugins/lisa/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +53 -4
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +269 -44
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-agy/skills/lisa-jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-agy/skills/lisa-jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-agy/skills/lisa-jira-journey/SKILL.md +2 -1
- package/plugins/lisa-agy/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +53 -4
- 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/parity-safety-net.sh +269 -44
- package/plugins/lisa-copilot/hooks/setup-jira-cli.sh +50 -0
- package/plugins/lisa-copilot/rules/reference/base-rules.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-copilot/skills/lisa-jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-copilot/skills/lisa-jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-copilot/skills/lisa-jira-journey/SKILL.md +2 -1
- package/plugins/lisa-copilot/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +53 -4
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +269 -44
- package/plugins/lisa-cursor/hooks/setup-jira-cli.sh +50 -0
- package/plugins/lisa-cursor/rules/base-rules-reference.mdc +1 -1
- package/plugins/lisa-cursor/skills/lisa-jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-cursor/skills/lisa-jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-cursor/skills/lisa-jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-cursor/skills/lisa-jira-journey/SKILL.md +2 -1
- package/plugins/lisa-cursor/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +53 -4
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-expo/.codex-plugin/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-expo/.codex-plugin/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-expo/.codex-plugin/skills/jira-journey/SKILL.md +2 -1
- package/plugins/lisa-expo/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-expo/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-expo/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-expo/skills/jira-journey/SKILL.md +2 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-agy/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-expo-agy/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-expo-agy/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-expo-agy/skills/jira-journey/SKILL.md +2 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-expo-copilot/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-expo-copilot/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-expo-copilot/skills/jira-journey/SKILL.md +2 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-expo-cursor/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-expo-cursor/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-expo-cursor/skills/jira-journey/SKILL.md +2 -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/.codex-plugin/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-rails/.codex-plugin/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-rails/.codex-plugin/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-rails/.codex-plugin/skills/jira-journey/SKILL.md +2 -1
- package/plugins/lisa-rails/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-rails/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-rails/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-rails/skills/jira-journey/SKILL.md +2 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-agy/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-rails-agy/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-rails-agy/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-rails-agy/skills/jira-journey/SKILL.md +2 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-rails-copilot/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-rails-copilot/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-rails-copilot/skills/jira-journey/SKILL.md +2 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/lisa-rails-cursor/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/lisa-rails-cursor/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/lisa-rails-cursor/skills/jira-journey/SKILL.md +2 -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/parity-safety-net.sh +269 -44
- package/plugins/src/base/hooks/setup-jira-cli.sh +50 -0
- package/plugins/src/base/rules/reference/base-rules.md +1 -1
- package/plugins/src/base/skills/lisa-jira-add-journey/SKILL.md +16 -2
- package/plugins/src/base/skills/lisa-jira-evidence/SKILL.md +6 -3
- package/plugins/src/base/skills/lisa-jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/src/base/skills/lisa-jira-journey/SKILL.md +2 -1
- package/plugins/src/base/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +53 -4
- package/plugins/src/expo/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/src/expo/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/src/expo/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/src/expo/skills/jira-journey/SKILL.md +2 -1
- package/plugins/src/rails/skills/jira-add-journey/SKILL.md +16 -2
- package/plugins/src/rails/skills/jira-evidence/SKILL.md +6 -3
- package/plugins/src/rails/skills/jira-evidence/scripts/post-evidence.sh +56 -5
- package/plugins/src/rails/skills/jira-journey/SKILL.md +2 -1
|
@@ -109,6 +109,19 @@ export const LisaSessionBootstrap = async ({
|
|
|
109
109
|
process.env.JIRA_PROJECT ?? readLisaConfig(["jira", "project"]) ?? "";
|
|
110
110
|
const login = process.env.JIRA_LOGIN;
|
|
111
111
|
if (server && login) {
|
|
112
|
+
// Consumers of this file (CodySwannGT/lisa#2767 — until then it had
|
|
113
|
+
// none, making this hook an inert control):
|
|
114
|
+
// * lisa-jira-evidence/scripts/post-evidence.sh — greps server/login,
|
|
115
|
+
// then passes `--config <path>` to `jira issue move`.
|
|
116
|
+
// * lisa-jira-read-ticket/scripts/download-attachment.sh — greps the
|
|
117
|
+
// same keys, but only when JIRA_SERVER/JIRA_LOGIN are unset.
|
|
118
|
+
// * SKILL prose — types `jira --config .lisa/jira-cli/.config.yml`.
|
|
119
|
+
//
|
|
120
|
+
// No per-harness environment export is involved anywhere: two consumers
|
|
121
|
+
// parse this YAML in Lisa-owned code, and the one real jira-cli call
|
|
122
|
+
// passes an argument. jira-cli resolves --config > JIRA_CONFIG_FILE >
|
|
123
|
+
// ~/.config/.jira/.config.yml, and a --config path that does not exist
|
|
124
|
+
// fails closed rather than falling back (measured, jira-cli v1.7.0).
|
|
112
125
|
const dir = `${root}/.lisa/jira-cli`;
|
|
113
126
|
mkdirSync(dir, { recursive: true });
|
|
114
127
|
const config = [
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
# guards also match path-prefixed invocations like `/bin/rm` and `./rm`)
|
|
13
13
|
# - `rm -rf` of the cwd (`.`), a `..`-traversal path, a `~/`-anchored path,
|
|
14
14
|
# an absolute path outside the project (with a /tmp, /var/tmp, $TMPDIR
|
|
15
|
-
# allowance
|
|
16
|
-
#
|
|
15
|
+
# allowance, each also matched by its symlink-resolved spelling so /tmp and
|
|
16
|
+
# /private/tmp are ONE location), a `$VAR` target that neither is $TMPDIR
|
|
17
|
+
# nor resolves through an unambiguous in-command assignment, or ANY
|
|
18
|
+
# recursive forced delete while cwd is $HOME
|
|
17
19
|
# - force-pushing a protected branch (main/master/production/prod/release) —
|
|
18
20
|
# feature-branch force-push stays allowed (sanctioned rebase workflow;
|
|
19
21
|
# deliberate divergence from upstream's any-branch block). Every git guard
|
|
@@ -45,9 +47,19 @@
|
|
|
45
47
|
# CLOSED: any parse error exits 2 (a non-2 exit would be a non-blocking hook
|
|
46
48
|
# error in Claude Code, silently failing open).
|
|
47
49
|
#
|
|
50
|
+
# Narrowed false-positive class (issue #3106): the rm TARGET WALK is now scoped
|
|
51
|
+
# to the quoting region that contains the rm, so a recursive delete quoted as
|
|
52
|
+
# PROSE no longer turns every later token on the line into a deletion target. A
|
|
53
|
+
# variable in an unrelated argument — a `gh issue create --body-file "$G/x"`, a
|
|
54
|
+
# `git commit -F "$MSG"` — is no longer read as the thing being deleted. An rm
|
|
55
|
+
# outside quotes, or first inside its quoted run (`bash -c "rm -rf /"`,
|
|
56
|
+
# `echo "$(rm -rf /)"`), still hands the whole statement to the walk. See
|
|
57
|
+
# rm_scan_scope below for the rule and its accepted residual.
|
|
58
|
+
#
|
|
48
59
|
# Known accepted false-positive class: this is a text scan, not a shell engine,
|
|
49
60
|
# so display commands quoting a destructive string (`echo "docs about rm -rf /"`)
|
|
50
|
-
# can match
|
|
61
|
+
# can match — including one whose target sits in the SAME quoted run as the
|
|
62
|
+
# prose, which the #3106 scoping deliberately does not reach. Upstream exempts those via an engine-only DISPLAY_COMMANDS list a
|
|
51
63
|
# grep hook cannot replicate. The same text-scan limit means the rm guard treats
|
|
52
64
|
# a substitution-wrapped catastrophic delete as verdict-neutral (issue #1982): an
|
|
53
65
|
# executable `echo "$(rm -rf /)"` is blocked, but so are inert twins the shell
|
|
@@ -271,6 +283,36 @@ project_dir="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
|
271
283
|
tmp_dir_allow="${TMPDIR:-/tmp}"
|
|
272
284
|
tmp_dir_allow="${tmp_dir_allow%/}"
|
|
273
285
|
[ -n "$tmp_dir_allow" ] || tmp_dir_allow="/tmp"
|
|
286
|
+
# Physical (symlink-resolved) spelling of an allowed root — issue #3106 arm B.
|
|
287
|
+
# The allowance below is a set of LITERAL string prefixes, so on macOS, where
|
|
288
|
+
# /tmp is a symlink to /private/tmp, one directory had two spellings and only
|
|
289
|
+
# one of them was allowed: `rm -rf /tmp/x` passed while `rm -rf /private/tmp/x`
|
|
290
|
+
# — the SAME directory, and the spelling an agent's own scratchpad path is
|
|
291
|
+
# handed to it in — was refused as "outside the project". Two opposite verdicts
|
|
292
|
+
# for one location is a defect regardless of which verdict is right.
|
|
293
|
+
#
|
|
294
|
+
# This widens no LOCATION. It adds the second name of a location the guard has
|
|
295
|
+
# always allowed, computed from the filesystem rather than hardcoded, so a
|
|
296
|
+
# platform where /tmp is not a symlink gains nothing. Falls back to the logical
|
|
297
|
+
# path when the directory does not exist or resolves to `/`, so the allowance
|
|
298
|
+
# can never become an empty or root-anchored prefix (an empty prefix would make
|
|
299
|
+
# `""/*` match every absolute path).
|
|
300
|
+
phys_dir() {
|
|
301
|
+
local logical="$1" real=""
|
|
302
|
+
real="$(cd -- "$logical" 2>/dev/null && pwd -P)" || real=""
|
|
303
|
+
case "$real" in
|
|
304
|
+
"" | /) printf '%s' "$logical" ;;
|
|
305
|
+
*) printf '%s' "$real" ;;
|
|
306
|
+
esac
|
|
307
|
+
}
|
|
308
|
+
project_dir_phys="$(phys_dir "$project_dir")"
|
|
309
|
+
tmp_phys="$(phys_dir /tmp)"
|
|
310
|
+
var_tmp_phys="$(phys_dir /var/tmp)"
|
|
311
|
+
tmp_dir_allow_phys="$(phys_dir "$tmp_dir_allow")"
|
|
312
|
+
[ -n "$project_dir_phys" ] || project_dir_phys="$project_dir"
|
|
313
|
+
[ -n "$tmp_phys" ] || tmp_phys="/tmp"
|
|
314
|
+
[ -n "$var_tmp_phys" ] || var_tmp_phys="/var/tmp"
|
|
315
|
+
[ -n "$tmp_dir_allow_phys" ] || tmp_dir_allow_phys="$tmp_dir_allow"
|
|
274
316
|
# Peel command-substitution wrappers off a token so an rm nested in `$(…)`,
|
|
275
317
|
# backticks, `<(…)`/`>(…)`, a `"…"`/`'…'` quote, or a leading `\` alias-bypass
|
|
276
318
|
# (`\rm`) is recognized and its target classified as if the token were unwrapped
|
|
@@ -310,6 +352,224 @@ strip_subst_wrappers() {
|
|
|
310
352
|
done
|
|
311
353
|
printf '%s' "$t"
|
|
312
354
|
}
|
|
355
|
+
# Decide which text of an rm statement the TARGET WALK may read — issue #3106
|
|
356
|
+
# arm A. The statement selector matches the RAW command text, so a recursive
|
|
357
|
+
# delete quoted as PROSE inside a string argument selects the line, and every
|
|
358
|
+
# remaining token on it is then classified as a deletion target. Three
|
|
359
|
+
# independent sightings in one day, all of them commands that delete nothing:
|
|
360
|
+
# a `printf` reduction, a `gh issue create --title …` filing the report about
|
|
361
|
+
# this guard, and a commit message describing it. Each was refused for a
|
|
362
|
+
# variable in a LATER, unrelated argument.
|
|
363
|
+
#
|
|
364
|
+
# The rule is structural, with no allowlist of "display commands":
|
|
365
|
+
# - an rm invocation OUTSIDE any quote is real; the walk reads the whole
|
|
366
|
+
# statement, exactly as before (`rm -rf "$HOME"` keeps its target),
|
|
367
|
+
# - an rm at the START of its quoted run is an invocation the quote merely
|
|
368
|
+
# wraps (`bash -c "rm -rf /"`, `echo "$(rm -rf /)"`); the walk reads the
|
|
369
|
+
# whole statement, exactly as before,
|
|
370
|
+
# - an rm preceded by other words INSIDE its quoted run is prose; the walk is
|
|
371
|
+
# scoped to that run, so the rm's own quoted arguments are still classified
|
|
372
|
+
# in full while a variable in a later argument is no longer read as a
|
|
373
|
+
# deletion target.
|
|
374
|
+
#
|
|
375
|
+
# Failure directions all point at blocking: an unterminated quote, an
|
|
376
|
+
# over-long statement, or any run the scan cannot place falls back to the whole
|
|
377
|
+
# statement. Residual (accepted, documented): `eval "prose rm -rf" "$X"` would
|
|
378
|
+
# concatenate its words at run time and is scoped to the first run here.
|
|
379
|
+
rm_scan_scope() {
|
|
380
|
+
local stmt="$1"
|
|
381
|
+
local n=${#stmt}
|
|
382
|
+
# Long statements fall back to the full walk rather than pay a char scan.
|
|
383
|
+
if [ "$n" -gt 4000 ]; then
|
|
384
|
+
printf '%s' "$stmt"
|
|
385
|
+
return
|
|
386
|
+
fi
|
|
387
|
+
local i=0 ch q="" esc=0 outside="" cur_run="" first_rm_run="" found=0
|
|
388
|
+
while [ "$i" -lt "$n" ]; do
|
|
389
|
+
ch="${stmt:i:1}"
|
|
390
|
+
i=$((i + 1))
|
|
391
|
+
if [ "$esc" -eq 1 ]; then
|
|
392
|
+
esc=0
|
|
393
|
+
if [ -n "$q" ]; then cur_run="$cur_run$ch"; else outside="$outside$ch"; fi
|
|
394
|
+
continue
|
|
395
|
+
fi
|
|
396
|
+
if [ "$ch" = '\' ] && [ "$q" != "'" ]; then
|
|
397
|
+
esc=1
|
|
398
|
+
continue
|
|
399
|
+
fi
|
|
400
|
+
if [ -n "$q" ]; then
|
|
401
|
+
if [ "$ch" = "$q" ]; then
|
|
402
|
+
if [ "$found" -eq 0 ]; then
|
|
403
|
+
case "$cur_run" in
|
|
404
|
+
*[Rr][Mm]*)
|
|
405
|
+
if printf '%s' "$cur_run" | grep -Eiq -- "$RM_CMD"'([[:space:]]|$)'; then
|
|
406
|
+
found=1
|
|
407
|
+
first_rm_run="$cur_run"
|
|
408
|
+
fi
|
|
409
|
+
;;
|
|
410
|
+
esac
|
|
411
|
+
fi
|
|
412
|
+
q=""
|
|
413
|
+
cur_run=""
|
|
414
|
+
else
|
|
415
|
+
cur_run="$cur_run$ch"
|
|
416
|
+
fi
|
|
417
|
+
continue
|
|
418
|
+
fi
|
|
419
|
+
case "$ch" in
|
|
420
|
+
'"' | "'")
|
|
421
|
+
q="$ch"
|
|
422
|
+
cur_run=""
|
|
423
|
+
;;
|
|
424
|
+
*) outside="$outside$ch" ;;
|
|
425
|
+
esac
|
|
426
|
+
done
|
|
427
|
+
# An unterminated quote is text the scan cannot place: treat it as unquoted so
|
|
428
|
+
# the fallback below hands back the whole statement.
|
|
429
|
+
[ -z "$q" ] || outside="$outside$cur_run"
|
|
430
|
+
if printf '%s' "$outside" | grep -Eiq -- "$RM_CMD"'([[:space:]]|$)'; then
|
|
431
|
+
printf '%s' "$stmt"
|
|
432
|
+
return
|
|
433
|
+
fi
|
|
434
|
+
if [ "$found" -eq 0 ]; then
|
|
435
|
+
printf '%s' "$stmt"
|
|
436
|
+
return
|
|
437
|
+
fi
|
|
438
|
+
# `rm` first in its run (modulo whitespace and substitution/alias openers) is
|
|
439
|
+
# an invocation, not prose.
|
|
440
|
+
if printf '%s' "$first_rm_run" \
|
|
441
|
+
| grep -Eiq -- '^[[:space:]]*(\\|\$\(|`|\(|<\(|>\()*[[:space:]]*([[:alnum:]_./-]*/)?rm([[:space:]]|$)'; then
|
|
442
|
+
printf '%s' "$stmt"
|
|
443
|
+
return
|
|
444
|
+
fi
|
|
445
|
+
printf '%s' "$first_rm_run"
|
|
446
|
+
}
|
|
447
|
+
# Look up the ONE unambiguous assignment of NAME inside the command being
|
|
448
|
+
# classified — issue #3106 arm B, the variable half.
|
|
449
|
+
#
|
|
450
|
+
# Prints the recorded right-hand side, or fails when the name is never assigned
|
|
451
|
+
# here or is assigned more than one distinct value. Ambiguity must fail: the
|
|
452
|
+
# last write wins at run time and a text scan cannot order writes against the
|
|
453
|
+
# delete, so `V=/etc; rm -rf "$V"; V=/tmp/x` must not resolve to the harmless
|
|
454
|
+
# one. Only assignments literally present in this command are ever trusted;
|
|
455
|
+
# nothing is expanded from the hook's own environment.
|
|
456
|
+
rm_assignment_value() {
|
|
457
|
+
local name="$1" hits="" count=""
|
|
458
|
+
case "$name" in
|
|
459
|
+
"" | *[!A-Za-z0-9_]*) return 1 ;;
|
|
460
|
+
esac
|
|
461
|
+
hits="$(printf '%s\n' "$normalized_command_str" | tr ';&|' '\n' \
|
|
462
|
+
| grep -oE "(^|[[:space:]])${name}=[^[:space:]]*" \
|
|
463
|
+
| sed -E "s/^.*${name}=//")"
|
|
464
|
+
[ -n "$hits" ] || return 1
|
|
465
|
+
count="$(printf '%s\n' "$hits" | sort -u | grep -c '' | tr -d '[:space:]')"
|
|
466
|
+
[ "$count" = "1" ] || return 1
|
|
467
|
+
printf '%s' "$(printf '%s\n' "$hits" | head -n 1)"
|
|
468
|
+
}
|
|
469
|
+
# Rewrite a leading `$NAME` / `${NAME}` in a deletion target to the value the
|
|
470
|
+
# command itself assigns it, so the target is classified as if the agent had
|
|
471
|
+
# written that value literally — issue #3106 arm B.
|
|
472
|
+
#
|
|
473
|
+
# The guard's standing reason for refusing variable targets is that "unset or
|
|
474
|
+
# mistyped variables can point anywhere", and that reason survives intact: a
|
|
475
|
+
# name with no in-command assignment, an ambiguous one, or a value that is
|
|
476
|
+
# itself a command substitution never resolves and stays refused. What changes
|
|
477
|
+
# is only that a target the agent could have spelled literally is no longer
|
|
478
|
+
# refused for being spelled through a variable.
|
|
479
|
+
resolve_var_prefix() {
|
|
480
|
+
local token="$1" name="" rest="" value=""
|
|
481
|
+
case "$token" in
|
|
482
|
+
'${'*'}'*)
|
|
483
|
+
name="${token#'${'}"
|
|
484
|
+
name="${name%%\}*}"
|
|
485
|
+
rest="${token#'${'"$name"'}'}"
|
|
486
|
+
;;
|
|
487
|
+
'$'*)
|
|
488
|
+
rest="${token#'$'}"
|
|
489
|
+
name="${rest%%[!A-Za-z0-9_]*}"
|
|
490
|
+
rest="${rest#"$name"}"
|
|
491
|
+
;;
|
|
492
|
+
*) return 1 ;;
|
|
493
|
+
esac
|
|
494
|
+
[ -n "$name" ] || return 1
|
|
495
|
+
value="$(rm_assignment_value "$name")" || return 1
|
|
496
|
+
# A value that is itself a command substitution names no location the guard
|
|
497
|
+
# can read, so it never resolves — the refusal stands. `$(…)` already fails
|
|
498
|
+
# below (its leading `$` yields no variable name); backticks are named here so
|
|
499
|
+
# both spellings of substitution behave the same way.
|
|
500
|
+
case "$value" in
|
|
501
|
+
*'`'*) return 1 ;;
|
|
502
|
+
esac
|
|
503
|
+
case "$value" in
|
|
504
|
+
'"'*'"')
|
|
505
|
+
value="${value#\"}"
|
|
506
|
+
value="${value%\"}"
|
|
507
|
+
;;
|
|
508
|
+
"'"*"'")
|
|
509
|
+
value="${value#\'}"
|
|
510
|
+
value="${value%\'}"
|
|
511
|
+
;;
|
|
512
|
+
esac
|
|
513
|
+
[ -n "$value" ] || return 1
|
|
514
|
+
printf '%s%s' "$value" "$rest"
|
|
515
|
+
}
|
|
516
|
+
# Classify ONE deletion target. Extracted from the token walk so a variable
|
|
517
|
+
# target can be re-classified through every arm after resolution (#3106),
|
|
518
|
+
# instead of the variable arm being a terminal verdict of its own.
|
|
519
|
+
classify_rm_target() {
|
|
520
|
+
local token="$1" depth="${2:-0}" resolved=""
|
|
521
|
+
case "$token" in
|
|
522
|
+
-*) return 0 ;;
|
|
523
|
+
. | ./)
|
|
524
|
+
set +f
|
|
525
|
+
block "recursive forced delete of the current directory (rm -rf .)"
|
|
526
|
+
;;
|
|
527
|
+
.. | ../* | */.. | */../*)
|
|
528
|
+
set +f
|
|
529
|
+
block "recursive forced delete of a path outside the project (.. traversal)"
|
|
530
|
+
;;
|
|
531
|
+
'~' | '~'/*)
|
|
532
|
+
set +f
|
|
533
|
+
block "recursive forced delete of a home-anchored path (~/…) outside the project"
|
|
534
|
+
;;
|
|
535
|
+
/*)
|
|
536
|
+
case "$token" in
|
|
537
|
+
"$project_dir" | "$project_dir"/* | "$project_dir_phys" | "$project_dir_phys"/* \
|
|
538
|
+
| /tmp | /tmp/* | "$tmp_phys" | "$tmp_phys"/* \
|
|
539
|
+
| /var/tmp | /var/tmp/* | "$var_tmp_phys" | "$var_tmp_phys"/* \
|
|
540
|
+
| "$tmp_dir_allow" | "$tmp_dir_allow"/* | "$tmp_dir_allow_phys" | "$tmp_dir_allow_phys"/*) : ;;
|
|
541
|
+
*)
|
|
542
|
+
set +f
|
|
543
|
+
block "recursive forced delete of an absolute path outside the project (only the project, /tmp, /var/tmp, and \$TMPDIR are allowed)"
|
|
544
|
+
;;
|
|
545
|
+
esac
|
|
546
|
+
;;
|
|
547
|
+
'*')
|
|
548
|
+
# Bare top-level wildcard expands to every entry in the cwd. Guard 1a no
|
|
549
|
+
# longer bounds `*)` (T4b F1), so a substitution-wrapped `rm -rf *` is
|
|
550
|
+
# caught here after the trailing-closer strip peels `*)"` down to `*`.
|
|
551
|
+
set +f
|
|
552
|
+
block "recursive forced delete of a top-level wildcard (rm -rf *)"
|
|
553
|
+
;;
|
|
554
|
+
*'$'*)
|
|
555
|
+
case "$token" in
|
|
556
|
+
'$TMPDIR' | '$TMPDIR'/* | '${TMPDIR}' | '${TMPDIR}'/*) : ;;
|
|
557
|
+
*)
|
|
558
|
+
if [ "$depth" -lt 4 ] \
|
|
559
|
+
&& resolved="$(resolve_var_prefix "$token")" \
|
|
560
|
+
&& [ -n "$resolved" ] \
|
|
561
|
+
&& [ "$resolved" != "$token" ]; then
|
|
562
|
+
classify_rm_target "$resolved" $((depth + 1))
|
|
563
|
+
else
|
|
564
|
+
set +f
|
|
565
|
+
block "recursive forced delete of a variable-expanded target (unset or mistyped variables can point anywhere; \$TMPDIR is the only sanctioned dynamic target)"
|
|
566
|
+
fi
|
|
567
|
+
;;
|
|
568
|
+
esac
|
|
569
|
+
;;
|
|
570
|
+
esac
|
|
571
|
+
return 0
|
|
572
|
+
}
|
|
313
573
|
while IFS= read -r rm_stmt; do
|
|
314
574
|
if ! printf '%s' "$rm_stmt" | grep -Eiq -- "$RM_RF_CLUSTER" \
|
|
315
575
|
&& ! printf '%s' "$rm_stmt" | grep -Eiq -- "$RM_RF_SPLIT"; then
|
|
@@ -325,9 +585,13 @@ while IFS= read -r rm_stmt; do
|
|
|
325
585
|
&& [ "$(pwd -P)" = "$(cd -- "$HOME" 2>/dev/null && pwd -P)" ]; then
|
|
326
586
|
block "recursive forced delete while the working directory is \$HOME (cd into a project first)"
|
|
327
587
|
fi
|
|
588
|
+
# Scope the walk to the quoting region that actually contains the rm (#3106
|
|
589
|
+
# arm A). Unquoted and run-leading invocations get the whole statement back,
|
|
590
|
+
# so this is a no-op for every real delete.
|
|
591
|
+
rm_walk_text="$(rm_scan_scope "$rm_stmt")"
|
|
328
592
|
set -f
|
|
329
593
|
seen_rm=0
|
|
330
|
-
for raw_token in $
|
|
594
|
+
for raw_token in $rm_walk_text; do
|
|
331
595
|
token="$(strip_subst_wrappers "$raw_token")"
|
|
332
596
|
if [ "$seen_rm" -eq 0 ]; then
|
|
333
597
|
# Path-prefixed spellings (`/bin/rm`, `./rm`) are still rm (F2).
|
|
@@ -336,46 +600,7 @@ while IFS= read -r rm_stmt; do
|
|
|
336
600
|
esac
|
|
337
601
|
continue
|
|
338
602
|
fi
|
|
339
|
-
|
|
340
|
-
-*) continue ;;
|
|
341
|
-
. | ./)
|
|
342
|
-
set +f
|
|
343
|
-
block "recursive forced delete of the current directory (rm -rf .)"
|
|
344
|
-
;;
|
|
345
|
-
.. | ../* | */.. | */../*)
|
|
346
|
-
set +f
|
|
347
|
-
block "recursive forced delete of a path outside the project (.. traversal)"
|
|
348
|
-
;;
|
|
349
|
-
'~' | '~'/*)
|
|
350
|
-
set +f
|
|
351
|
-
block "recursive forced delete of a home-anchored path (~/…) outside the project"
|
|
352
|
-
;;
|
|
353
|
-
/*)
|
|
354
|
-
case "$token" in
|
|
355
|
-
"$project_dir" | "$project_dir"/* | /tmp | /tmp/* | /var/tmp | /var/tmp/* | "$tmp_dir_allow" | "$tmp_dir_allow"/*) : ;;
|
|
356
|
-
*)
|
|
357
|
-
set +f
|
|
358
|
-
block "recursive forced delete of an absolute path outside the project (only the project, /tmp, /var/tmp, and \$TMPDIR are allowed)"
|
|
359
|
-
;;
|
|
360
|
-
esac
|
|
361
|
-
;;
|
|
362
|
-
'*')
|
|
363
|
-
# Bare top-level wildcard expands to every entry in the cwd. Guard 1a no
|
|
364
|
-
# longer bounds `*)` (T4b F1), so a substitution-wrapped `rm -rf *` is
|
|
365
|
-
# caught here after the trailing-closer strip peels `*)"` down to `*`.
|
|
366
|
-
set +f
|
|
367
|
-
block "recursive forced delete of a top-level wildcard (rm -rf *)"
|
|
368
|
-
;;
|
|
369
|
-
*'$'*)
|
|
370
|
-
case "$token" in
|
|
371
|
-
'$TMPDIR' | '$TMPDIR'/* | '${TMPDIR}' | '${TMPDIR}'/*) : ;;
|
|
372
|
-
*)
|
|
373
|
-
set +f
|
|
374
|
-
block "recursive forced delete of a variable-expanded target (unset or mistyped variables can point anywhere; \$TMPDIR is the only sanctioned dynamic target)"
|
|
375
|
-
;;
|
|
376
|
-
esac
|
|
377
|
-
;;
|
|
378
|
-
esac
|
|
603
|
+
classify_rm_target "$token"
|
|
379
604
|
done
|
|
380
605
|
set +f
|
|
381
606
|
done < <(printf '%s' "$normalized_command_str" | tr '&|;' '\n' \
|
package/package.json
CHANGED
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"zod-validation-error": "^4.0.0"
|
|
150
150
|
},
|
|
151
151
|
"name": "@codyswann/lisa",
|
|
152
|
-
"version": "4.10.
|
|
152
|
+
"version": "4.10.7",
|
|
153
153
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
154
154
|
"main": "dist/index.js",
|
|
155
155
|
"exports": {
|
|
@@ -16,7 +16,12 @@ Read an existing JIRA ticket, analyze the change type, and generate a Validation
|
|
|
16
16
|
## Prerequisites
|
|
17
17
|
|
|
18
18
|
- `JIRA_API_TOKEN` environment variable set
|
|
19
|
-
- `jira-cli` configured
|
|
19
|
+
- `jira-cli` configured. Prefer the config Lisa writes at
|
|
20
|
+
`.lisa/jira-cli/.config.yml` (the `setup-jira-cli` SessionStart hook writes it
|
|
21
|
+
from `JIRA_SERVER` / `JIRA_LOGIN` / `JIRA_PROJECT` on a project whose
|
|
22
|
+
`tracker` is `jira`), and pass it explicitly with `--config`. A developer's
|
|
23
|
+
own `~/.config/.jira/.config.yml` still works as jira-cli's default when no
|
|
24
|
+
`--config` is given.
|
|
20
25
|
|
|
21
26
|
## Workflow
|
|
22
27
|
|
|
@@ -25,9 +30,18 @@ Read an existing JIRA ticket, analyze the change type, and generate a Validation
|
|
|
25
30
|
Use the Atlassian MCP or jira-cli to read the full ticket details:
|
|
26
31
|
|
|
27
32
|
```bash
|
|
28
|
-
jira
|
|
33
|
+
# Run from the project root. --config pins jira-cli to the file Lisa's
|
|
34
|
+
# setup-jira-cli hook wrote, instead of the machine's own ~/.config/.jira.
|
|
35
|
+
# jira-cli resolves --config > JIRA_CONFIG_FILE > ~/.config/.jira/.config.yml,
|
|
36
|
+
# and a --config path that does not exist fails closed with "Missing
|
|
37
|
+
# configuration file." rather than silently using the default.
|
|
38
|
+
jira --config .lisa/jira-cli/.config.yml issue view <TICKET_ID>
|
|
29
39
|
```
|
|
30
40
|
|
|
41
|
+
If `.lisa/jira-cli/.config.yml` does not exist, drop `--config` to fall back to
|
|
42
|
+
the machine's own jira-cli config — but say so in your report rather than
|
|
43
|
+
letting the Lisa-written config go quietly unused.
|
|
44
|
+
|
|
31
45
|
Extract: title, description, acceptance criteria, components, labels, linked tickets.
|
|
32
46
|
|
|
33
47
|
### Step 2: Check for Existing Journey
|
|
@@ -35,7 +35,10 @@ Upload captured evidence and generated templates to GitHub PR description and JI
|
|
|
35
35
|
## Prerequisites
|
|
36
36
|
|
|
37
37
|
- `JIRA_API_TOKEN` environment variable set
|
|
38
|
-
-
|
|
38
|
+
- a jira-cli config — `.lisa/jira-cli/.config.yml` if the `setup-jira-cli`
|
|
39
|
+
SessionStart hook wrote one, otherwise `~/.config/.jira/.config.yml`. `server`
|
|
40
|
+
and `login` are read from whichever is used, and the fallback is announced on
|
|
41
|
+
stderr rather than taken silently.
|
|
39
42
|
- `gh` CLI authenticated
|
|
40
43
|
- Evidence directory containing:
|
|
41
44
|
- `NN-name.txt` or `NN-name.json` text evidence files (e.g., `01-health-check.json`)
|
|
@@ -66,7 +69,7 @@ bash .claude/skills/jira-evidence/scripts/post-evidence.sh PROJ-123 ./evidence 4
|
|
|
66
69
|
|
|
67
70
|
## What It Does
|
|
68
71
|
|
|
69
|
-
1. **Read JIRA config** —
|
|
72
|
+
1. **Read JIRA config** — Resolves the jira-cli config (`$PROJECT_DIR/.lisa/jira-cli/.config.yml` first, then `~/.config/.jira/.config.yml`, announcing the fallback) and reads `server` and `login` from it dynamically — no hardcoded values. Exits 1 naming both paths when neither exists. The same resolved path is then passed to `jira issue move` as `--config`, so the CLI cannot end up on a different config than the one this script parsed.
|
|
70
73
|
2. **Upload to GitHub `pr-assets` release** — Uploads evidence files via `gh release upload --clobber`
|
|
71
74
|
3. **Update PR description** — Replaces or appends the `## Evidence` section in the PR body
|
|
72
75
|
4. **Post JIRA comment** — Posts `comment.txt` as a new comment (wiki markup with code blocks)
|
|
@@ -97,4 +100,4 @@ gh release create pr-assets --title "PR Assets" --notes "CDN for PR evidence"
|
|
|
97
100
|
|
|
98
101
|
### JIRA API returns 403
|
|
99
102
|
|
|
100
|
-
Ensure `JIRA_API_TOKEN` is set and `login` in `~/.config/.jira/.config.yml` matches your Atlassian account email.
|
|
103
|
+
Ensure `JIRA_API_TOKEN` is set and `login` in the resolved jira-cli config (`.lisa/jira-cli/.config.yml`, else `~/.config/.jira/.config.yml`) matches your Atlassian account email.
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
#
|
|
7
7
|
# Prerequisites:
|
|
8
8
|
# - JIRA_API_TOKEN env var set
|
|
9
|
-
# - jira-cli
|
|
9
|
+
# - a jira-cli config, resolved in this order (see "CONFIG RESOLUTION" below):
|
|
10
|
+
# 1. ${PROJECT_DIR}/.lisa/jira-cli/.config.yml (written by the
|
|
11
|
+
# setup-jira-cli SessionStart hook from the JIRA_* environment)
|
|
12
|
+
# 2. ~/.config/.jira/.config.yml (a developer's own
|
|
13
|
+
# `jira init` config; announced on stderr when it is used)
|
|
10
14
|
# - gh CLI authenticated
|
|
11
15
|
#
|
|
12
16
|
# What it does:
|
|
@@ -23,9 +27,50 @@ TICKET_ID="${1:?Usage: post-evidence.sh <TICKET_ID> <EVIDENCE_DIR> <PR_NUMBER>}"
|
|
|
23
27
|
EVIDENCE_DIR="${2:?Usage: post-evidence.sh <TICKET_ID> <EVIDENCE_DIR> <PR_NUMBER>}"
|
|
24
28
|
PR_NUMBER="${3:?Usage: post-evidence.sh <TICKET_ID> <EVIDENCE_DIR> <PR_NUMBER>}"
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
# CONFIG RESOLUTION
|
|
32
|
+
#
|
|
33
|
+
# The setup-jira-cli SessionStart hook writes ${PROJECT_DIR}/.lisa/jira-cli/
|
|
34
|
+
# .config.yml. Nothing read it — this script looked only at the developer's own
|
|
35
|
+
# ~/.config/.jira/.config.yml and exited 1 telling the operator to run
|
|
36
|
+
# `jira init`, so on a headless JIRA project the hook was an inert control.
|
|
37
|
+
# See CodySwannGT/lisa#2767.
|
|
38
|
+
#
|
|
39
|
+
# PROJECT_DIR is resolved with the same rule as the hook that writes the file,
|
|
40
|
+
# deliberately character-for-character: CLAUDE_PROJECT_DIR (the harness's own
|
|
41
|
+
# declaration of the root, inert on harnesses that never set it), then the git
|
|
42
|
+
# toplevel, then pwd. A session launched from a subdirectory otherwise reads a
|
|
43
|
+
# directory it never wrote — the #2768 defect, on the read side.
|
|
44
|
+
#
|
|
45
|
+
# Nothing here writes to ~/.config/.jira. That file is a developer's personal
|
|
46
|
+
# jira-cli state; it is read as a fallback and never created or modified.
|
|
47
|
+
#
|
|
48
|
+
# NOTE: the `.lisa.config.json` reads in Step 5 below are still resolved from
|
|
49
|
+
# the process working directory. That is pre-existing and out of scope here.
|
|
50
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-}"
|
|
52
|
+
if [[ -z "${PROJECT_DIR}" || ! -d "${PROJECT_DIR}" ]]; then
|
|
53
|
+
PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
54
|
+
fi
|
|
55
|
+
PROJECT_JIRA_CONFIG="${PROJECT_DIR}/.lisa/jira-cli/.config.yml"
|
|
56
|
+
HOME_JIRA_CONFIG="${HOME}/.config/.jira/.config.yml"
|
|
57
|
+
|
|
58
|
+
if [[ -f "$PROJECT_JIRA_CONFIG" ]]; then
|
|
59
|
+
JIRA_CONFIG="$PROJECT_JIRA_CONFIG"
|
|
60
|
+
elif [[ -f "$HOME_JIRA_CONFIG" ]]; then
|
|
61
|
+
# Announced, never silent. A silent fall-through to the developer's own file
|
|
62
|
+
# is precisely how the Lisa-written config stayed unconsumed without anyone
|
|
63
|
+
# noticing, and on a headless runner there is no such file to fall back to.
|
|
64
|
+
echo "NOTE: no Lisa-written jira-cli config at $PROJECT_JIRA_CONFIG" >&2
|
|
65
|
+
echo " falling back to the developer config at $HOME_JIRA_CONFIG" >&2
|
|
66
|
+
JIRA_CONFIG="$HOME_JIRA_CONFIG"
|
|
67
|
+
else
|
|
68
|
+
echo "ERROR: jira-cli config not found." >&2
|
|
69
|
+
echo " Lisa-written config (expected): $PROJECT_JIRA_CONFIG" >&2
|
|
70
|
+
echo " developer config (fallback): $HOME_JIRA_CONFIG" >&2
|
|
71
|
+
echo " Fix: export JIRA_SERVER, JIRA_LOGIN (and JIRA_INSTALLATION," >&2
|
|
72
|
+
echo " JIRA_PROJECT) and start a new session so the setup-jira-cli hook" >&2
|
|
73
|
+
echo " writes the project config — or run 'jira init' locally." >&2
|
|
29
74
|
exit 1
|
|
30
75
|
fi
|
|
31
76
|
JIRA_SERVER=$(grep '^server:' "$JIRA_CONFIG" | awk '{print $2}')
|
|
@@ -169,7 +214,13 @@ fi
|
|
|
169
214
|
echo ""
|
|
170
215
|
if [ -n "$REVIEW" ]; then
|
|
171
216
|
echo "==> Moving $TICKET_ID to $REVIEW..."
|
|
172
|
-
|
|
217
|
+
# --config pins jira-cli to the same file this script parsed above.
|
|
218
|
+
# jira-cli resolves --config > JIRA_CONFIG_FILE > ~/.config/.jira/
|
|
219
|
+
# .config.yml, and a --config path that does not exist fails closed
|
|
220
|
+
# rather than falling back (measured against jira-cli v1.7.0). The flag
|
|
221
|
+
# needs no environment export, so this works identically on every
|
|
222
|
+
# harness — see the harness note in the setup-jira-cli hook.
|
|
223
|
+
jira --config "$JIRA_CONFIG" issue move "$TICKET_ID" "$REVIEW" 2>&1 && echo " ✓ Ticket moved to $REVIEW" || echo " WARNING: Could not move ticket to $REVIEW (not a valid transition?); leaving in current status" >&2
|
|
173
224
|
else
|
|
174
225
|
echo "==> No jira.workflow.review configured; leaving $TICKET_ID in its current (claimed) status per config-resolution."
|
|
175
226
|
fi
|
|
@@ -20,7 +20,8 @@ Parse a JIRA ticket's Validation Journey, execute the verification steps using t
|
|
|
20
20
|
## Prerequisites
|
|
21
21
|
|
|
22
22
|
- `JIRA_API_TOKEN` environment variable set
|
|
23
|
-
- `jira-cli` configured
|
|
23
|
+
- `jira-cli` configured — `.lisa/jira-cli/.config.yml` (written by the
|
|
24
|
+
`setup-jira-cli` SessionStart hook) or `~/.config/.jira/.config.yml`
|
|
24
25
|
- `gh` CLI authenticated
|
|
25
26
|
- Appropriate services running for the verification type (dev server, database, etc.)
|
|
26
27
|
|
package/plugins/lisa/.codex-plugin/skills/lisa-jira-read-ticket/scripts/download-attachment.sh
CHANGED
|
@@ -19,6 +19,13 @@
|
|
|
19
19
|
# JIRA_LOGIN - login email
|
|
20
20
|
# JIRA_API_TOKEN - API token (https://id.atlassian.com/manage-profile/security/api-tokens)
|
|
21
21
|
#
|
|
22
|
+
# JIRA_SERVER and JIRA_LOGIN each fall back to a jira-cli config when unset:
|
|
23
|
+
# ${PROJECT_DIR}/.lisa/jira-cli/.config.yml (written by the setup-jira-cli
|
|
24
|
+
# SessionStart hook) first, then ${HOME}/.config/.jira/.config.yml, with the
|
|
25
|
+
# fallback announced on stderr. When BOTH env vars are set, no config file is
|
|
26
|
+
# opened at all. JIRA_API_TOKEN has no config fallback — it is a secret and
|
|
27
|
+
# is never written to the jira-cli config.
|
|
28
|
+
#
|
|
22
29
|
# Exit codes:
|
|
23
30
|
# 0 success
|
|
24
31
|
# 1 download failed (HTTP error)
|
|
@@ -34,12 +41,54 @@ fi
|
|
|
34
41
|
ID_OR_URL="$1"
|
|
35
42
|
OUTPUT_PATH="$2"
|
|
36
43
|
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
# Resolve credentials: prefer env, fall back to a jira-cli config for
|
|
46
|
+
# server/login.
|
|
47
|
+
#
|
|
48
|
+
# The env-first arm is load-bearing and unchanged: a headless environment that
|
|
49
|
+
# exports JIRA_SERVER and JIRA_LOGIN never opens a config file at all. The
|
|
50
|
+
# resolution below is therefore gated on actually needing it.
|
|
51
|
+
#
|
|
52
|
+
# What changed (CodySwannGT/lisa#2767): the fallback looked only at the
|
|
53
|
+
# developer's own ~/.config/.jira/.config.yml. The setup-jira-cli SessionStart
|
|
54
|
+
# hook writes ${PROJECT_DIR}/.lisa/jira-cli/.config.yml, which nothing read —
|
|
55
|
+
# so on a headless JIRA project with the JIRA_* vars absent, the file Lisa had
|
|
56
|
+
# just written was ignored and this script exited 2. The Lisa-written config is
|
|
57
|
+
# now preferred, with the developer config kept as an announced fallback.
|
|
58
|
+
#
|
|
59
|
+
# PROJECT_DIR uses the same rule as the hook that writes the file:
|
|
60
|
+
# CLAUDE_PROJECT_DIR, then the git toplevel, then pwd. Resolving from the
|
|
61
|
+
# process working directory instead would reintroduce #2768 on the read side.
|
|
62
|
+
#
|
|
63
|
+
# Nothing here writes to ~/.config/.jira — it is read, never created.
|
|
64
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
JIRA_CONFIG=""
|
|
66
|
+
if [[ -z "${JIRA_SERVER:-}" || -z "${JIRA_LOGIN:-}" ]]; then
|
|
67
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-}"
|
|
68
|
+
if [[ -z "${PROJECT_DIR}" || ! -d "${PROJECT_DIR}" ]]; then
|
|
69
|
+
PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
70
|
+
fi
|
|
71
|
+
PROJECT_JIRA_CONFIG="${PROJECT_DIR}/.lisa/jira-cli/.config.yml"
|
|
72
|
+
HOME_JIRA_CONFIG="${HOME}/.config/.jira/.config.yml"
|
|
73
|
+
|
|
74
|
+
if [[ -f "$PROJECT_JIRA_CONFIG" ]]; then
|
|
75
|
+
JIRA_CONFIG="$PROJECT_JIRA_CONFIG"
|
|
76
|
+
elif [[ -f "$HOME_JIRA_CONFIG" ]]; then
|
|
77
|
+
# Announced, never silent — a quiet fall-through to the developer's own
|
|
78
|
+
# file is how the Lisa-written config stayed unconsumed unnoticed.
|
|
79
|
+
echo "NOTE: no Lisa-written jira-cli config at $PROJECT_JIRA_CONFIG" >&2
|
|
80
|
+
echo " falling back to the developer config at $HOME_JIRA_CONFIG" >&2
|
|
81
|
+
JIRA_CONFIG="$HOME_JIRA_CONFIG"
|
|
82
|
+
else
|
|
83
|
+
echo "NOTE: no jira-cli config at $PROJECT_JIRA_CONFIG" >&2
|
|
84
|
+
echo " or $HOME_JIRA_CONFIG; relying on the environment alone." >&2
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [[ -z "${JIRA_SERVER:-}" && -n "$JIRA_CONFIG" ]]; then
|
|
40
89
|
JIRA_SERVER=$(grep '^server:' "$JIRA_CONFIG" | awk '{print $2}')
|
|
41
90
|
fi
|
|
42
|
-
if [[ -z "${JIRA_LOGIN:-}" && -
|
|
91
|
+
if [[ -z "${JIRA_LOGIN:-}" && -n "$JIRA_CONFIG" ]]; then
|
|
43
92
|
JIRA_LOGIN=$(grep '^login:' "$JIRA_CONFIG" | awk '{print $2}')
|
|
44
93
|
fi
|
|
45
94
|
|