@codyswann/lisa 4.10.14 → 4.11.0

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 (121) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +4 -1
  2. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +132 -43
  3. package/dist/cli/doctor-worktree-hygiene.d.ts +5 -2
  4. package/dist/cli/doctor-worktree-hygiene.d.ts.map +1 -1
  5. package/dist/cli/doctor-worktree-hygiene.js +12 -6
  6. package/dist/cli/doctor-worktree-hygiene.js.map +1 -1
  7. package/dist/cli/index.d.ts.map +1 -1
  8. package/dist/cli/index.js +2 -0
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/prune-commands.d.ts +52 -0
  11. package/dist/cli/prune-commands.d.ts.map +1 -0
  12. package/dist/cli/prune-commands.js +158 -0
  13. package/dist/cli/prune-commands.js.map +1 -0
  14. package/dist/cli/prune-report.d.ts +40 -0
  15. package/dist/cli/prune-report.d.ts.map +1 -0
  16. package/dist/cli/prune-report.js +81 -0
  17. package/dist/cli/prune-report.js.map +1 -0
  18. package/dist/cli/stash-prune-policy.d.ts +72 -0
  19. package/dist/cli/stash-prune-policy.d.ts.map +1 -0
  20. package/dist/cli/stash-prune-policy.js +90 -0
  21. package/dist/cli/stash-prune-policy.js.map +1 -0
  22. package/dist/cli/stash-prune.d.ts +104 -0
  23. package/dist/cli/stash-prune.d.ts.map +1 -0
  24. package/dist/cli/stash-prune.js +206 -0
  25. package/dist/cli/stash-prune.js.map +1 -0
  26. package/dist/cli/worktree-inventory.d.ts +82 -0
  27. package/dist/cli/worktree-inventory.d.ts.map +1 -0
  28. package/dist/cli/worktree-inventory.js +195 -0
  29. package/dist/cli/worktree-inventory.js.map +1 -0
  30. package/dist/cli/worktree-liveness.d.ts +52 -0
  31. package/dist/cli/worktree-liveness.d.ts.map +1 -0
  32. package/dist/cli/worktree-liveness.js +124 -0
  33. package/dist/cli/worktree-liveness.js.map +1 -0
  34. package/dist/cli/worktree-ownership.d.ts +65 -0
  35. package/dist/cli/worktree-ownership.d.ts.map +1 -0
  36. package/dist/cli/worktree-ownership.js +142 -0
  37. package/dist/cli/worktree-ownership.js.map +1 -0
  38. package/dist/cli/worktree-prune-policy.d.ts +76 -0
  39. package/dist/cli/worktree-prune-policy.d.ts.map +1 -0
  40. package/dist/cli/worktree-prune-policy.js +114 -0
  41. package/dist/cli/worktree-prune-policy.js.map +1 -0
  42. package/dist/cli/worktree-prune.d.ts +96 -0
  43. package/dist/cli/worktree-prune.d.ts.map +1 -0
  44. package/dist/cli/worktree-prune.js +153 -0
  45. package/dist/cli/worktree-prune.js.map +1 -0
  46. package/dist/codex/scripts/block-no-verify.sh +4 -1
  47. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  48. package/dist/core/lisa-owned-hash-ledger.js +4 -0
  49. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  50. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  51. package/dist/core/upstream-evidence-manifest.js +23 -6
  52. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  53. package/dist/opencode/plugin-templates/block-no-verify.sh +4 -1
  54. package/dist/opencode/plugin-templates/parity-safety-net.sh +132 -43
  55. package/package.json +1 -1
  56. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa/hooks/block-no-verify.agy.sh +4 -1
  59. package/plugins/lisa/hooks/block-no-verify.sh +4 -1
  60. package/plugins/lisa/hooks/parity-safety-net.sh +132 -43
  61. package/plugins/lisa/rules/reference/base-rules.md +1 -1
  62. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +4 -1
  63. package/plugins/lisa-agy/hooks/parity-safety-net.sh +132 -43
  64. package/plugins/lisa-agy/plugin.json +1 -1
  65. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  68. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-copilot/hooks/block-no-verify.sh +4 -1
  72. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +132 -43
  73. package/plugins/lisa-copilot/rules/reference/base-rules.md +1 -1
  74. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-cursor/hooks/block-no-verify.sh +4 -1
  76. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +132 -43
  77. package/plugins/lisa-cursor/rules/base-rules-reference.mdc +1 -1
  78. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa-expo-agy/plugin.json +1 -1
  81. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  86. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  91. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  96. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  100. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  101. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-rails-agy/plugin.json +1 -1
  106. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  111. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  116. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/src/base/hooks/block-no-verify.agy.sh +4 -1
  119. package/plugins/src/base/hooks/block-no-verify.sh +4 -1
  120. package/plugins/src/base/hooks/parity-safety-net.sh +132 -43
  121. package/plugins/src/base/rules/reference/base-rules.md +1 -1
@@ -444,7 +444,8 @@ rm_scan_scope() {
444
444
  printf '%s' "$stmt"
445
445
  return
446
446
  fi
447
- local i=0 ch q="" esc=0 outside="" cur_run="" first_rm_run="" found=0
447
+ local i=0 ch q="" esc=0 outside="" cur_run="" rm_runs="" found=0
448
+ local run_leading=0
448
449
  while [ "$i" -lt "$n" ]; do
449
450
  ch="${stmt:i:1}"
450
451
  i=$((i + 1))
@@ -459,16 +460,19 @@ rm_scan_scope() {
459
460
  fi
460
461
  if [ -n "$q" ]; then
461
462
  if [ "$ch" = "$q" ]; then
462
- if [ "$found" -eq 0 ]; then
463
- case "$cur_run" in
464
- *[Rr][Mm]*)
465
- if scan -Ei "$cur_run" "$RM_CMD"'([[:space:]]|$)'; then
466
- found=1
467
- first_rm_run="$cur_run"
463
+ case "$cur_run" in
464
+ *[Rr][Mm]*)
465
+ if scan -Ei "$cur_run" "$RM_CMD"'([[:space:]]|$)'; then
466
+ found=$((found + 1))
467
+ if [ -n "$rm_runs" ]; then rm_runs="$rm_runs"$'\n'; fi
468
+ rm_runs="$rm_runs$cur_run"
469
+ if scan -Ei "$cur_run" \
470
+ '^[[:space:]]*(\\|\$\(|`|\(|<\(|>\()*[[:space:]]*([[:alnum:]_./-]*/)?rm([[:space:]]|$)'; then
471
+ run_leading=1
468
472
  fi
469
- ;;
470
- esac
471
- fi
473
+ fi
474
+ ;;
475
+ esac
472
476
  q=""
473
477
  cur_run=""
474
478
  else
@@ -495,36 +499,118 @@ rm_scan_scope() {
495
499
  printf '%s' "$stmt"
496
500
  return
497
501
  fi
498
- # `rm` first in its run (modulo whitespace and substitution/alias openers) is
499
- # an invocation, not prose.
500
- if scan -Ei "$first_rm_run" \
501
- '^[[:space:]]*(\\|\$\(|`|\(|<\(|>\()*[[:space:]]*([[:alnum:]_./-]*/)?rm([[:space:]]|$)'; then
502
+ # `rm` first in ANY run (modulo whitespace and substitution/alias openers) is
503
+ # an invocation, not prose. Inspect every run before deciding: returning on
504
+ # the first prose run used to hide a real delete in a later quoted run.
505
+ if [ "$run_leading" -eq 1 ]; then
502
506
  printf '%s' "$stmt"
503
507
  return
504
508
  fi
505
- printf '%s' "$first_rm_run"
509
+ printf '%s' "$rm_runs"
506
510
  }
507
511
  # Look up the ONE unambiguous assignment of NAME inside the command being
508
512
  # classified — issue #3106 arm B, the variable half.
509
513
  #
510
- # Prints the recorded right-hand side, or fails when the name is never assigned
511
- # here or is assigned more than one distinct value. Ambiguity must fail: the
512
- # last write wins at run time and a text scan cannot order writes against the
513
- # delete, so `V=/etc; rm -rf "$V"; V=/tmp/x` must not resolve to the harmless
514
- # one. Only assignments literally present in this command are ever trusted;
515
- # nothing is expanded from the hook's own environment.
514
+ # Prints the recorded right-hand side, or fails when the name is not assigned
515
+ # before the first rm token or is assigned more than one distinct value there.
516
+ # A later write cannot affect an earlier delete, and a quoted assignment-like
517
+ # argument can be prose, so neither is considered. Only assignments literally
518
+ # present in this command are ever trusted; nothing is expanded from the hook's
519
+ # own environment.
520
+ rm_assignment_stmt=""
516
521
  rm_assignment_value() {
517
- local name="$1" hits="" count=""
522
+ local name="$1"
518
523
  case "$name" in
519
524
  "" | *[!A-Za-z0-9_]*) return 1 ;;
520
525
  esac
521
- hits="$(printf '%s\n' "$normalized_command_str" | tr ';&|' '\n' \
522
- | grep -oE "(^|[[:space:]])${name}=[^[:space:]]*" \
523
- | sed -E "s/^.*${name}=//")"
524
- [ -n "$hits" ] || return 1
525
- count="$(printf '%s\n' "$hits" | sort -u | grep -c '' | tr -d '[:space:]')"
526
- [ "$count" = "1" ] || return 1
527
- printf '%s' "$(printf '%s\n' "$hits" | head -n 1)"
526
+ # Only assignments that occur before this statement's rm token can influence
527
+ # its delete safely. A later assignment is too late at runtime, and an
528
+ # assignment whose NAME begins inside a quoted argument can be prose. The
529
+ # small lexer below preserves quoted right-hand sides such as V="/tmp/x"
530
+ # while excluding an argument such as echo "V=/tmp/x". Any ambiguity fails
531
+ # closed.
532
+ RM_ASSIGNMENT_NAME="$name" RM_ASSIGNMENT_COMMAND="$normalized_command_str" \
533
+ RM_ASSIGNMENT_STATEMENT="$rm_assignment_stmt" \
534
+ python3 - <<'PY'
535
+ import os
536
+ import re
537
+ import sys
538
+
539
+ name = os.environ.get("RM_ASSIGNMENT_NAME", "")
540
+ command = os.environ.get("RM_ASSIGNMENT_COMMAND", "")
541
+ statement = os.environ.get("RM_ASSIGNMENT_STATEMENT", "")
542
+ if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name):
543
+ sys.exit(1)
544
+
545
+ rm_pattern = re.compile(
546
+ r"(?i)(?<![A-Za-z0-9_./-])(?:[A-Za-z0-9_./-]*/)?rm(?=$|[\s;&|()'\"`])",
547
+ )
548
+ rm_token = rm_pattern.search(statement)
549
+ if rm_token is None or not statement or command.count(statement) != 1:
550
+ sys.exit(1)
551
+ statement_start = command.find(statement)
552
+ if statement_start < 0:
553
+ sys.exit(1)
554
+ prefix = command[: statement_start + rm_token.start()]
555
+
556
+ tokens = []
557
+ buffer = []
558
+ quote = None
559
+ escaped = False
560
+ started = False
561
+ started_unquoted = False
562
+
563
+ def flush():
564
+ global buffer, started, started_unquoted
565
+ if started:
566
+ tokens.append(("".join(buffer), started_unquoted))
567
+ buffer = []
568
+ started = False
569
+ started_unquoted = False
570
+
571
+ for char in prefix:
572
+ if escaped:
573
+ buffer.append(char)
574
+ escaped = False
575
+ continue
576
+ if quote is not None:
577
+ if char == quote:
578
+ quote = None
579
+ elif char == "\\" and quote == '"':
580
+ escaped = True
581
+ else:
582
+ buffer.append(char)
583
+ continue
584
+ if char in "'\"":
585
+ if not started:
586
+ started = True
587
+ started_unquoted = False
588
+ quote = char
589
+ elif char == "\\":
590
+ if not started:
591
+ started = True
592
+ started_unquoted = True
593
+ escaped = True
594
+ elif char.isspace() or char in ";&|()":
595
+ flush()
596
+ else:
597
+ if not started:
598
+ started = True
599
+ started_unquoted = True
600
+ buffer.append(char)
601
+ flush()
602
+
603
+ needle = f"{name}="
604
+ hits = [token[len(needle):] for token, outside in tokens
605
+ if outside and token.startswith(needle)]
606
+ distinct = set(hits)
607
+ if len(distinct) != 1:
608
+ sys.exit(1)
609
+ value = hits[0]
610
+ if not value:
611
+ sys.exit(1)
612
+ sys.stdout.write(value)
613
+ PY
528
614
  }
529
615
  # Rewrite a leading `$NAME` / `${NAME}` in a deletion target to the value the
530
616
  # command itself assigns it, so the target is classified as if the agent had
@@ -664,21 +750,24 @@ while IFS= read -r rm_stmt; do
664
750
  # Scope the walk to the quoting region that actually contains the rm (#3106
665
751
  # arm A). Unquoted and run-leading invocations get the whole statement back,
666
752
  # so this is a no-op for every real delete.
753
+ rm_assignment_stmt="$rm_stmt"
667
754
  rm_walk_text="$(rm_scan_scope "$rm_stmt")"
668
- set -f
669
- seen_rm=0
670
- for raw_token in $rm_walk_text; do
671
- token="$(strip_subst_wrappers "$raw_token")"
672
- if [ "$seen_rm" -eq 0 ]; then
673
- # Path-prefixed spellings (`/bin/rm`, `./rm`) are still rm (F2).
674
- case "$token" in
675
- rm | */rm) seen_rm=1 ;;
676
- esac
677
- continue
678
- fi
679
- classify_rm_target "$token"
680
- done
681
- set +f
755
+ while IFS= read -r rm_scope; do
756
+ set -f
757
+ seen_rm=0
758
+ for raw_token in $rm_scope; do
759
+ token="$(strip_subst_wrappers "$raw_token")"
760
+ if [ "$seen_rm" -eq 0 ]; then
761
+ # Path-prefixed spellings (`/bin/rm`, `./rm`) are still rm (F2).
762
+ case "$token" in
763
+ rm | */rm) seen_rm=1 ;;
764
+ esac
765
+ continue
766
+ fi
767
+ classify_rm_target "$token"
768
+ done
769
+ set +f
770
+ done <<< "$rm_walk_text"
682
771
  done <<<"$rm_segments"
683
772
 
684
773
  # 2. Force-pushing a protected branch. `--force-with-lease` is the safe,
@@ -75,7 +75,7 @@ Testing Discipline:
75
75
  JIRA Discipline:
76
76
  - If working on a JIRA issue, make sure the branch you're working on references and is added to the JIRA issue.
77
77
  - If working on a JIRA issue, update the issue as you work through it. For example, if working on a Bug Triage, update the issue with your questions/feedback/suggestions.
78
- - When reading a JIRA issue, always read ALL comments on the ticket — not just the description. Comments contain critical context: stakeholder decisions, scope changes, blockers, triage findings from other repos, and implementation notes. Use the Atlassian MCP or `jira --config .lisa/jira-cli/.config.yml issue view <TICKET_ID> --comments 100` to fetch them (the `--config` pins jira-cli to the config the `setup-jira-cli` hook wrote; drop it to use the machine's own `~/.config/.jira/.config.yml`).
78
+ - When reading a JIRA issue, always read ALL comments on the ticket — not just the description. Comments contain critical context: stakeholder decisions, scope changes, blockers, triage findings from other repos, and implementation notes. Use the Atlassian MCP or first resolve the project root from `$CLAUDE_PROJECT_DIR` or `git rev-parse --show-toplevel`, then run `jira --config "$PROJECT_ROOT/.lisa/jira-cli/.config.yml" issue view <TICKET_ID> --comments 100`. If that project config is absent, omit `--config` to use the machine's own `~/.config/.jira/.config.yml`.
79
79
  - When requesting clarification on a JIRA issue, post the question as a comment using ADF (Atlassian Document Format) and @mention the Reporter so they receive a notification.
80
80
  - When creating JIRA tickets, establish issue link relationships (e.g. "is blocked by", "blocks", "relates to", "is duplicated by") between tickets that have dependencies or logical connections. Do not leave related tickets unlinked. Relationship discovery is mandatory on every create and update — search BOTH the local git history (`git log --all --grep=<keyword>`, `git log -- <path>`) AND Jira (JQL by component, keyword, label, epic siblings) before declaring "no related work." Record the searches you ran and their outcomes in the description (or a comment) so a reviewer can see the search was real.
81
81
  - Ticket scope by type: Bug, Task, and Sub-task tickets MUST be single-repo (one work unit, one repo). Epic, Spike, and Story tickets MAY span multiple repos. If a Bug/Task/Sub-task crosses repos, split it per the `repo-scope-split` rule — at PRD-decomposition time via `task-decomposition` step 1.5 (parent Story + per-repo children); at work-time (an existing ticket an agent is about to implement) via the work-time split procedure (narrow the original to one repo, spin off a sibling per additional repo cloning its metadata, link the producer→consumer dependency, then proceed). A cross-repo work unit is a decomposition error the agent fixes by splitting, not a reason to bounce the ticket to the reporter.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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.14",
3
+ "version": "4.11.0",
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-nestjs",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
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.10.14",
3
+ "version": "4.11.0",
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.10.14",
3
+ "version": "4.11.0",
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.10.14",
3
+ "version": "4.11.0",
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.10.14",
3
+ "version": "4.11.0",
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-phaser",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.10.14",
3
+ "version": "4.11.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -68,7 +68,10 @@ def strip_heredocs(text: str) -> str:
68
68
  words = shlex.split(raw, posix=True)
69
69
  except ValueError:
70
70
  return None
71
- return words[0] if len(words) == 1 and words[0] else None
71
+ # An explicitly quoted empty word is a valid heredoc delimiter. Bash
72
+ # terminates that heredoc on the next empty line, so preserve "" as a
73
+ # marker instead of treating it as a parse failure.
74
+ return words[0] if len(words) == 1 else None
72
75
 
73
76
  index = 0
74
77
  while index < len(lines):
@@ -109,7 +109,10 @@ def strip_heredocs(text: str) -> str:
109
109
  words = shlex.split(raw, posix=True)
110
110
  except ValueError:
111
111
  return None
112
- return words[0] if len(words) == 1 and words[0] else None
112
+ # An explicitly quoted empty word is a valid heredoc delimiter. Bash
113
+ # terminates that heredoc on the next empty line, so preserve "" as a
114
+ # marker instead of treating it as a parse failure.
115
+ return words[0] if len(words) == 1 else None
113
116
 
114
117
  index = 0
115
118
  while index < len(lines):