@codyswann/lisa 4.10.1 → 4.10.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.
Files changed (64) hide show
  1. package/dist/codex/scripts/setup-jira-cli.sh +87 -18
  2. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  3. package/dist/core/lisa-owned-hash-ledger.js +3 -0
  4. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  5. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  6. package/dist/core/upstream-evidence-manifest.js +4 -2
  7. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  8. package/dist/opencode/plugin-templates/lisa-session-bootstrap.ts +11 -2
  9. package/package.json +1 -1
  10. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  12. package/plugins/lisa/hooks/setup-jira-cli.sh +88 -22
  13. package/plugins/lisa-agy/plugin.json +1 -1
  14. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  17. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-copilot/hooks/setup-jira-cli.sh +88 -22
  21. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cursor/hooks/setup-jira-cli.sh +88 -22
  23. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo-agy/plugin.json +1 -1
  26. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  36. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  41. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  46. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails-agy/plugin.json +1 -1
  51. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  56. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  61. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  63. package/plugins/src/base/hooks/setup-jira-cli.sh +88 -22
  64. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +321 -10
@@ -81,8 +81,17 @@ export const LisaSessionBootstrap = async ({
81
81
  value = (value as Record<string, unknown>)[key];
82
82
  }
83
83
  if (typeof value === "string" && value) return value;
84
- } catch {
85
- // ignore malformed local config and keep fail-open behavior
84
+ } catch (error) {
85
+ // VISIBLE, not silent. A config that exists but cannot be parsed is
86
+ // a failure, not an answer: treating it as absent reports the
87
+ // project as unconfigured when nobody can see that anything went
88
+ // wrong. The two shell implementations exit non-zero here. This one
89
+ // cannot — throwing out of the plugin factory would take the whole
90
+ // session down — so the compensating rung is a warning naming the
91
+ // file, per the AGENTS.md rule on gaps a harness cannot represent.
92
+ console.error(
93
+ `lisa-session-bootstrap: ${configPath} is not valid JSON; treating it as absent: ${String(error)}`
94
+ );
86
95
  }
87
96
  }
88
97
  return undefined;
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.1",
152
+ "version": "4.10.3",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -6,9 +6,9 @@
6
6
  # Gated on the project's configured tracker: this only writes anything when
7
7
  # `.lisa.config*.json` declares `tracker: "jira"`. A project on Linear or GitHub
8
8
  # Issues has no use for a jira-cli config, and writing one made a false implicit
9
- # claim about which tracker the project runs on. The gate fails closed — an
10
- # unreadable or absent tracker writes nothing — because Lisa treats a missing
11
- # `tracker` as unconfigured rather than as a jira default (see lisa-tracker-read).
9
+ # claim about which tracker the project runs on. The gate fails closed — a
10
+ # tracker that cannot be READ now says so on stderr and exits non-zero, rather
11
+ # than exiting 0 as though the project had declared nothing.
12
12
  #
13
13
  # Required env vars (must be created in your Claude Code Web environment):
14
14
  # JIRA_INSTALLATION - cloud or local
@@ -26,36 +26,95 @@ set -euo pipefail
26
26
  # Drain the hook envelope before any early return.
27
27
  cat >/dev/null 2>&1 || true
28
28
 
29
- PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
30
- CONFIG_DIR="${PROJECT_DIR}/.lisa/jira-cli"
31
- CONFIG_FILE="${CONFIG_DIR}/.config.yml"
29
+ # ---------------------------------------------------------------------------
30
+ # WHERE THE CONFIG IS READ FROM.
31
+ #
32
+ # The write target has always been absolute (`${PROJECT_DIR}/.lisa/jira-cli`)
33
+ # while the reads were relative to the process working directory. A session
34
+ # launched anywhere but the project root therefore read a different directory
35
+ # than it wrote, and `jq` on a missing file yields empty, which every consumer
36
+ # below treats as "not configured" — so the hook reported success having
37
+ # configured nothing. Both ends now resolve against PROJECT_DIR, matching the
38
+ # OpenCode implementation, which has always resolved against the worktree root
39
+ # it is handed.
40
+ #
41
+ # CLAUDE_PROJECT_DIR first (the harness's own declaration of the root, and
42
+ # inert on harnesses that never set it), then the git toplevel, then pwd. This
43
+ # block is deliberately identical in both shell implementations: two copies of
44
+ # one resolution rule, allowed to drift, is what produced the Codex half of
45
+ # this defect in the first place.
46
+ # ---------------------------------------------------------------------------
47
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-}"
48
+ if [[ -z "${PROJECT_DIR}" || ! -d "${PROJECT_DIR}" ]]; then
49
+ PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
50
+ fi
51
+ LISA_CONFIG_LOCAL="${PROJECT_DIR}/.lisa.config.local.json"
52
+ LISA_CONFIG_MAIN="${PROJECT_DIR}/.lisa.config.json"
32
53
 
33
- read_lisa_config() {
34
- local query="$1"
35
- local value=""
54
+ # A project with no Lisa config at all is genuinely unconfigured: nothing to
55
+ # read, nothing to write. That case exits silently, exactly as it always has —
56
+ # it is the control that keeps this change from being a behavior change for
57
+ # every non-Lisa directory a harness might launch in.
58
+ if [[ ! -f "${LISA_CONFIG_LOCAL}" && ! -f "${LISA_CONFIG_MAIN}" ]]; then
59
+ exit 0
60
+ fi
36
61
 
37
- if ! command -v jq &>/dev/null; then
38
- return 0
39
- fi
62
+ # FAIL CLOSED FROM HERE. A config file exists, so anything that stops us
63
+ # reading it is a failure, not an answer. Reporting "no tracker configured"
64
+ # because jq is absent or the JSON is malformed is the silent-inert-control
65
+ # failure this hook family keeps producing; it is now visible on stderr and
66
+ # non-zero rather than an exit 0 that claims the project configured nothing.
67
+ if ! command -v jq &>/dev/null; then
68
+ printf '%s\n' \
69
+ "setup-jira-cli: cannot read ${LISA_CONFIG_MAIN}: jq is not installed." \
70
+ "setup-jira-cli: refusing to report this project as unconfigured. Install jq." >&2
71
+ exit 1
72
+ fi
40
73
 
41
- if [[ -f ".lisa.config.local.json" ]]; then
42
- value=$(jq -r "${query} // empty" .lisa.config.local.json 2>/dev/null || true)
74
+ for lisa_config_candidate in "${LISA_CONFIG_LOCAL}" "${LISA_CONFIG_MAIN}"; do
75
+ if [[ -f "${lisa_config_candidate}" ]] &&
76
+ ! jq empty "${lisa_config_candidate}" >/dev/null 2>&1; then
77
+ printf '%s\n' \
78
+ "setup-jira-cli: ${lisa_config_candidate} is not valid JSON." \
79
+ "setup-jira-cli: refusing to report this project as unconfigured." >&2
80
+ exit 1
43
81
  fi
82
+ done
44
83
 
45
- if [[ -z "${value}" && -f ".lisa.config.json" ]]; then
46
- value=$(jq -r "${query} // empty" .lisa.config.json 2>/dev/null || true)
47
- fi
84
+ # Reads the first non-empty value for `query` across the local override and the
85
+ # committed config, both resolved from PROJECT_DIR. A jq failure returns
86
+ # non-zero so the caller can fail closed; it never degrades to an empty string.
87
+ read_lisa_config() {
88
+ local query="$1"
89
+ local value=""
90
+ local candidate
91
+
92
+ for candidate in "${LISA_CONFIG_LOCAL}" "${LISA_CONFIG_MAIN}"; do
93
+ [[ -f "${candidate}" ]] || continue
94
+ if ! value="$(jq -r "${query} // empty" "${candidate}")"; then
95
+ printf '%s\n' \
96
+ "setup-jira-cli: failed to read ${query} from ${candidate}." >&2
97
+ return 1
98
+ fi
99
+ [[ -n "${value}" ]] && break
100
+ done
48
101
 
49
102
  printf '%s' "${value}"
50
103
  }
51
104
 
52
- # Tracker gate: do nothing unless this project actually runs on JIRA.
53
- if [[ "$(read_lisa_config '.tracker')" != "jira" ]]; then
105
+ # Tracker gate: do nothing unless this project actually runs on JIRA. An
106
+ # unreadable tracker is a hard stop, not a "no".
107
+ if ! LISA_TRACKER="$(read_lisa_config '.tracker')"; then
108
+ exit 1
109
+ fi
110
+ if [[ "${LISA_TRACKER}" != "jira" ]]; then
54
111
  exit 0
55
112
  fi
56
113
 
57
114
  if [[ -z "${JIRA_SERVER:-}" ]]; then
58
- ATLASSIAN_SITE="$(read_lisa_config '.atlassian.site')"
115
+ if ! ATLASSIAN_SITE="$(read_lisa_config '.atlassian.site')"; then
116
+ exit 1
117
+ fi
59
118
  if [[ -n "${ATLASSIAN_SITE}" ]]; then
60
119
  if [[ "${ATLASSIAN_SITE}" == http://* || "${ATLASSIAN_SITE}" == https://* ]]; then
61
120
  JIRA_SERVER="${ATLASSIAN_SITE}"
@@ -66,10 +125,17 @@ if [[ -z "${JIRA_SERVER:-}" ]]; then
66
125
  fi
67
126
 
68
127
  if [[ -z "${JIRA_PROJECT:-}" ]]; then
69
- JIRA_PROJECT="$(read_lisa_config '.jira.project')"
128
+ if ! JIRA_PROJECT="$(read_lisa_config '.jira.project')"; then
129
+ exit 1
130
+ fi
70
131
  fi
71
132
 
72
- # Skip config write if required vars are missing
133
+ CONFIG_DIR="${PROJECT_DIR}/.lisa/jira-cli"
134
+ CONFIG_FILE="${CONFIG_DIR}/.config.yml"
135
+
136
+ # Skip config write if required vars are missing. These are identity/secret
137
+ # values supplied by the environment, never by .lisa.config*.json, so their
138
+ # absence is an unconfigured environment rather than an unreadable one.
73
139
  if [[ -z "${JIRA_SERVER:-}" || -z "${JIRA_LOGIN:-}" ]]; then
74
140
  exit 0
75
141
  fi
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -6,9 +6,9 @@
6
6
  # Gated on the project's configured tracker: this only writes anything when
7
7
  # `.lisa.config*.json` declares `tracker: "jira"`. A project on Linear or GitHub
8
8
  # Issues has no use for a jira-cli config, and writing one made a false implicit
9
- # claim about which tracker the project runs on. The gate fails closed — an
10
- # unreadable or absent tracker writes nothing — because Lisa treats a missing
11
- # `tracker` as unconfigured rather than as a jira default (see lisa-tracker-read).
9
+ # claim about which tracker the project runs on. The gate fails closed — a
10
+ # tracker that cannot be READ now says so on stderr and exits non-zero, rather
11
+ # than exiting 0 as though the project had declared nothing.
12
12
  #
13
13
  # Required env vars (must be created in your Claude Code Web environment):
14
14
  # JIRA_INSTALLATION - cloud or local
@@ -26,36 +26,95 @@ set -euo pipefail
26
26
  # Drain the hook envelope before any early return.
27
27
  cat >/dev/null 2>&1 || true
28
28
 
29
- PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
30
- CONFIG_DIR="${PROJECT_DIR}/.lisa/jira-cli"
31
- CONFIG_FILE="${CONFIG_DIR}/.config.yml"
29
+ # ---------------------------------------------------------------------------
30
+ # WHERE THE CONFIG IS READ FROM.
31
+ #
32
+ # The write target has always been absolute (`${PROJECT_DIR}/.lisa/jira-cli`)
33
+ # while the reads were relative to the process working directory. A session
34
+ # launched anywhere but the project root therefore read a different directory
35
+ # than it wrote, and `jq` on a missing file yields empty, which every consumer
36
+ # below treats as "not configured" — so the hook reported success having
37
+ # configured nothing. Both ends now resolve against PROJECT_DIR, matching the
38
+ # OpenCode implementation, which has always resolved against the worktree root
39
+ # it is handed.
40
+ #
41
+ # CLAUDE_PROJECT_DIR first (the harness's own declaration of the root, and
42
+ # inert on harnesses that never set it), then the git toplevel, then pwd. This
43
+ # block is deliberately identical in both shell implementations: two copies of
44
+ # one resolution rule, allowed to drift, is what produced the Codex half of
45
+ # this defect in the first place.
46
+ # ---------------------------------------------------------------------------
47
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-}"
48
+ if [[ -z "${PROJECT_DIR}" || ! -d "${PROJECT_DIR}" ]]; then
49
+ PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
50
+ fi
51
+ LISA_CONFIG_LOCAL="${PROJECT_DIR}/.lisa.config.local.json"
52
+ LISA_CONFIG_MAIN="${PROJECT_DIR}/.lisa.config.json"
32
53
 
33
- read_lisa_config() {
34
- local query="$1"
35
- local value=""
54
+ # A project with no Lisa config at all is genuinely unconfigured: nothing to
55
+ # read, nothing to write. That case exits silently, exactly as it always has —
56
+ # it is the control that keeps this change from being a behavior change for
57
+ # every non-Lisa directory a harness might launch in.
58
+ if [[ ! -f "${LISA_CONFIG_LOCAL}" && ! -f "${LISA_CONFIG_MAIN}" ]]; then
59
+ exit 0
60
+ fi
36
61
 
37
- if ! command -v jq &>/dev/null; then
38
- return 0
39
- fi
62
+ # FAIL CLOSED FROM HERE. A config file exists, so anything that stops us
63
+ # reading it is a failure, not an answer. Reporting "no tracker configured"
64
+ # because jq is absent or the JSON is malformed is the silent-inert-control
65
+ # failure this hook family keeps producing; it is now visible on stderr and
66
+ # non-zero rather than an exit 0 that claims the project configured nothing.
67
+ if ! command -v jq &>/dev/null; then
68
+ printf '%s\n' \
69
+ "setup-jira-cli: cannot read ${LISA_CONFIG_MAIN}: jq is not installed." \
70
+ "setup-jira-cli: refusing to report this project as unconfigured. Install jq." >&2
71
+ exit 1
72
+ fi
40
73
 
41
- if [[ -f ".lisa.config.local.json" ]]; then
42
- value=$(jq -r "${query} // empty" .lisa.config.local.json 2>/dev/null || true)
74
+ for lisa_config_candidate in "${LISA_CONFIG_LOCAL}" "${LISA_CONFIG_MAIN}"; do
75
+ if [[ -f "${lisa_config_candidate}" ]] &&
76
+ ! jq empty "${lisa_config_candidate}" >/dev/null 2>&1; then
77
+ printf '%s\n' \
78
+ "setup-jira-cli: ${lisa_config_candidate} is not valid JSON." \
79
+ "setup-jira-cli: refusing to report this project as unconfigured." >&2
80
+ exit 1
43
81
  fi
82
+ done
44
83
 
45
- if [[ -z "${value}" && -f ".lisa.config.json" ]]; then
46
- value=$(jq -r "${query} // empty" .lisa.config.json 2>/dev/null || true)
47
- fi
84
+ # Reads the first non-empty value for `query` across the local override and the
85
+ # committed config, both resolved from PROJECT_DIR. A jq failure returns
86
+ # non-zero so the caller can fail closed; it never degrades to an empty string.
87
+ read_lisa_config() {
88
+ local query="$1"
89
+ local value=""
90
+ local candidate
91
+
92
+ for candidate in "${LISA_CONFIG_LOCAL}" "${LISA_CONFIG_MAIN}"; do
93
+ [[ -f "${candidate}" ]] || continue
94
+ if ! value="$(jq -r "${query} // empty" "${candidate}")"; then
95
+ printf '%s\n' \
96
+ "setup-jira-cli: failed to read ${query} from ${candidate}." >&2
97
+ return 1
98
+ fi
99
+ [[ -n "${value}" ]] && break
100
+ done
48
101
 
49
102
  printf '%s' "${value}"
50
103
  }
51
104
 
52
- # Tracker gate: do nothing unless this project actually runs on JIRA.
53
- if [[ "$(read_lisa_config '.tracker')" != "jira" ]]; then
105
+ # Tracker gate: do nothing unless this project actually runs on JIRA. An
106
+ # unreadable tracker is a hard stop, not a "no".
107
+ if ! LISA_TRACKER="$(read_lisa_config '.tracker')"; then
108
+ exit 1
109
+ fi
110
+ if [[ "${LISA_TRACKER}" != "jira" ]]; then
54
111
  exit 0
55
112
  fi
56
113
 
57
114
  if [[ -z "${JIRA_SERVER:-}" ]]; then
58
- ATLASSIAN_SITE="$(read_lisa_config '.atlassian.site')"
115
+ if ! ATLASSIAN_SITE="$(read_lisa_config '.atlassian.site')"; then
116
+ exit 1
117
+ fi
59
118
  if [[ -n "${ATLASSIAN_SITE}" ]]; then
60
119
  if [[ "${ATLASSIAN_SITE}" == http://* || "${ATLASSIAN_SITE}" == https://* ]]; then
61
120
  JIRA_SERVER="${ATLASSIAN_SITE}"
@@ -66,10 +125,17 @@ if [[ -z "${JIRA_SERVER:-}" ]]; then
66
125
  fi
67
126
 
68
127
  if [[ -z "${JIRA_PROJECT:-}" ]]; then
69
- JIRA_PROJECT="$(read_lisa_config '.jira.project')"
128
+ if ! JIRA_PROJECT="$(read_lisa_config '.jira.project')"; then
129
+ exit 1
130
+ fi
70
131
  fi
71
132
 
72
- # Skip config write if required vars are missing
133
+ CONFIG_DIR="${PROJECT_DIR}/.lisa/jira-cli"
134
+ CONFIG_FILE="${CONFIG_DIR}/.config.yml"
135
+
136
+ # Skip config write if required vars are missing. These are identity/secret
137
+ # values supplied by the environment, never by .lisa.config*.json, so their
138
+ # absence is an unconfigured environment rather than an unreadable one.
73
139
  if [[ -z "${JIRA_SERVER:-}" || -z "${JIRA_LOGIN:-}" ]]; then
74
140
  exit 0
75
141
  fi
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -6,9 +6,9 @@
6
6
  # Gated on the project's configured tracker: this only writes anything when
7
7
  # `.lisa.config*.json` declares `tracker: "jira"`. A project on Linear or GitHub
8
8
  # Issues has no use for a jira-cli config, and writing one made a false implicit
9
- # claim about which tracker the project runs on. The gate fails closed — an
10
- # unreadable or absent tracker writes nothing — because Lisa treats a missing
11
- # `tracker` as unconfigured rather than as a jira default (see lisa-tracker-read).
9
+ # claim about which tracker the project runs on. The gate fails closed — a
10
+ # tracker that cannot be READ now says so on stderr and exits non-zero, rather
11
+ # than exiting 0 as though the project had declared nothing.
12
12
  #
13
13
  # Required env vars (must be created in your Claude Code Web environment):
14
14
  # JIRA_INSTALLATION - cloud or local
@@ -26,36 +26,95 @@ set -euo pipefail
26
26
  # Drain the hook envelope before any early return.
27
27
  cat >/dev/null 2>&1 || true
28
28
 
29
- PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
30
- CONFIG_DIR="${PROJECT_DIR}/.lisa/jira-cli"
31
- CONFIG_FILE="${CONFIG_DIR}/.config.yml"
29
+ # ---------------------------------------------------------------------------
30
+ # WHERE THE CONFIG IS READ FROM.
31
+ #
32
+ # The write target has always been absolute (`${PROJECT_DIR}/.lisa/jira-cli`)
33
+ # while the reads were relative to the process working directory. A session
34
+ # launched anywhere but the project root therefore read a different directory
35
+ # than it wrote, and `jq` on a missing file yields empty, which every consumer
36
+ # below treats as "not configured" — so the hook reported success having
37
+ # configured nothing. Both ends now resolve against PROJECT_DIR, matching the
38
+ # OpenCode implementation, which has always resolved against the worktree root
39
+ # it is handed.
40
+ #
41
+ # CLAUDE_PROJECT_DIR first (the harness's own declaration of the root, and
42
+ # inert on harnesses that never set it), then the git toplevel, then pwd. This
43
+ # block is deliberately identical in both shell implementations: two copies of
44
+ # one resolution rule, allowed to drift, is what produced the Codex half of
45
+ # this defect in the first place.
46
+ # ---------------------------------------------------------------------------
47
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-}"
48
+ if [[ -z "${PROJECT_DIR}" || ! -d "${PROJECT_DIR}" ]]; then
49
+ PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
50
+ fi
51
+ LISA_CONFIG_LOCAL="${PROJECT_DIR}/.lisa.config.local.json"
52
+ LISA_CONFIG_MAIN="${PROJECT_DIR}/.lisa.config.json"
32
53
 
33
- read_lisa_config() {
34
- local query="$1"
35
- local value=""
54
+ # A project with no Lisa config at all is genuinely unconfigured: nothing to
55
+ # read, nothing to write. That case exits silently, exactly as it always has —
56
+ # it is the control that keeps this change from being a behavior change for
57
+ # every non-Lisa directory a harness might launch in.
58
+ if [[ ! -f "${LISA_CONFIG_LOCAL}" && ! -f "${LISA_CONFIG_MAIN}" ]]; then
59
+ exit 0
60
+ fi
36
61
 
37
- if ! command -v jq &>/dev/null; then
38
- return 0
39
- fi
62
+ # FAIL CLOSED FROM HERE. A config file exists, so anything that stops us
63
+ # reading it is a failure, not an answer. Reporting "no tracker configured"
64
+ # because jq is absent or the JSON is malformed is the silent-inert-control
65
+ # failure this hook family keeps producing; it is now visible on stderr and
66
+ # non-zero rather than an exit 0 that claims the project configured nothing.
67
+ if ! command -v jq &>/dev/null; then
68
+ printf '%s\n' \
69
+ "setup-jira-cli: cannot read ${LISA_CONFIG_MAIN}: jq is not installed." \
70
+ "setup-jira-cli: refusing to report this project as unconfigured. Install jq." >&2
71
+ exit 1
72
+ fi
40
73
 
41
- if [[ -f ".lisa.config.local.json" ]]; then
42
- value=$(jq -r "${query} // empty" .lisa.config.local.json 2>/dev/null || true)
74
+ for lisa_config_candidate in "${LISA_CONFIG_LOCAL}" "${LISA_CONFIG_MAIN}"; do
75
+ if [[ -f "${lisa_config_candidate}" ]] &&
76
+ ! jq empty "${lisa_config_candidate}" >/dev/null 2>&1; then
77
+ printf '%s\n' \
78
+ "setup-jira-cli: ${lisa_config_candidate} is not valid JSON." \
79
+ "setup-jira-cli: refusing to report this project as unconfigured." >&2
80
+ exit 1
43
81
  fi
82
+ done
44
83
 
45
- if [[ -z "${value}" && -f ".lisa.config.json" ]]; then
46
- value=$(jq -r "${query} // empty" .lisa.config.json 2>/dev/null || true)
47
- fi
84
+ # Reads the first non-empty value for `query` across the local override and the
85
+ # committed config, both resolved from PROJECT_DIR. A jq failure returns
86
+ # non-zero so the caller can fail closed; it never degrades to an empty string.
87
+ read_lisa_config() {
88
+ local query="$1"
89
+ local value=""
90
+ local candidate
91
+
92
+ for candidate in "${LISA_CONFIG_LOCAL}" "${LISA_CONFIG_MAIN}"; do
93
+ [[ -f "${candidate}" ]] || continue
94
+ if ! value="$(jq -r "${query} // empty" "${candidate}")"; then
95
+ printf '%s\n' \
96
+ "setup-jira-cli: failed to read ${query} from ${candidate}." >&2
97
+ return 1
98
+ fi
99
+ [[ -n "${value}" ]] && break
100
+ done
48
101
 
49
102
  printf '%s' "${value}"
50
103
  }
51
104
 
52
- # Tracker gate: do nothing unless this project actually runs on JIRA.
53
- if [[ "$(read_lisa_config '.tracker')" != "jira" ]]; then
105
+ # Tracker gate: do nothing unless this project actually runs on JIRA. An
106
+ # unreadable tracker is a hard stop, not a "no".
107
+ if ! LISA_TRACKER="$(read_lisa_config '.tracker')"; then
108
+ exit 1
109
+ fi
110
+ if [[ "${LISA_TRACKER}" != "jira" ]]; then
54
111
  exit 0
55
112
  fi
56
113
 
57
114
  if [[ -z "${JIRA_SERVER:-}" ]]; then
58
- ATLASSIAN_SITE="$(read_lisa_config '.atlassian.site')"
115
+ if ! ATLASSIAN_SITE="$(read_lisa_config '.atlassian.site')"; then
116
+ exit 1
117
+ fi
59
118
  if [[ -n "${ATLASSIAN_SITE}" ]]; then
60
119
  if [[ "${ATLASSIAN_SITE}" == http://* || "${ATLASSIAN_SITE}" == https://* ]]; then
61
120
  JIRA_SERVER="${ATLASSIAN_SITE}"
@@ -66,10 +125,17 @@ if [[ -z "${JIRA_SERVER:-}" ]]; then
66
125
  fi
67
126
 
68
127
  if [[ -z "${JIRA_PROJECT:-}" ]]; then
69
- JIRA_PROJECT="$(read_lisa_config '.jira.project')"
128
+ if ! JIRA_PROJECT="$(read_lisa_config '.jira.project')"; then
129
+ exit 1
130
+ fi
70
131
  fi
71
132
 
72
- # Skip config write if required vars are missing
133
+ CONFIG_DIR="${PROJECT_DIR}/.lisa/jira-cli"
134
+ CONFIG_FILE="${CONFIG_DIR}/.config.yml"
135
+
136
+ # Skip config write if required vars are missing. These are identity/secret
137
+ # values supplied by the environment, never by .lisa.config*.json, so their
138
+ # absence is an unconfigured environment rather than an unreadable one.
73
139
  if [[ -z "${JIRA_SERVER:-}" || -z "${JIRA_LOGIN:-}" ]]; then
74
140
  exit 0
75
141
  fi
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.10.1",
3
+ "version": "4.10.3",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"