@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.
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +4 -1
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +132 -43
- package/dist/cli/doctor-worktree-hygiene.d.ts +5 -2
- package/dist/cli/doctor-worktree-hygiene.d.ts.map +1 -1
- package/dist/cli/doctor-worktree-hygiene.js +12 -6
- package/dist/cli/doctor-worktree-hygiene.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/prune-commands.d.ts +52 -0
- package/dist/cli/prune-commands.d.ts.map +1 -0
- package/dist/cli/prune-commands.js +158 -0
- package/dist/cli/prune-commands.js.map +1 -0
- package/dist/cli/prune-report.d.ts +40 -0
- package/dist/cli/prune-report.d.ts.map +1 -0
- package/dist/cli/prune-report.js +81 -0
- package/dist/cli/prune-report.js.map +1 -0
- package/dist/cli/stash-prune-policy.d.ts +72 -0
- package/dist/cli/stash-prune-policy.d.ts.map +1 -0
- package/dist/cli/stash-prune-policy.js +90 -0
- package/dist/cli/stash-prune-policy.js.map +1 -0
- package/dist/cli/stash-prune.d.ts +104 -0
- package/dist/cli/stash-prune.d.ts.map +1 -0
- package/dist/cli/stash-prune.js +206 -0
- package/dist/cli/stash-prune.js.map +1 -0
- package/dist/cli/worktree-inventory.d.ts +82 -0
- package/dist/cli/worktree-inventory.d.ts.map +1 -0
- package/dist/cli/worktree-inventory.js +195 -0
- package/dist/cli/worktree-inventory.js.map +1 -0
- package/dist/cli/worktree-liveness.d.ts +52 -0
- package/dist/cli/worktree-liveness.d.ts.map +1 -0
- package/dist/cli/worktree-liveness.js +124 -0
- package/dist/cli/worktree-liveness.js.map +1 -0
- package/dist/cli/worktree-ownership.d.ts +65 -0
- package/dist/cli/worktree-ownership.d.ts.map +1 -0
- package/dist/cli/worktree-ownership.js +142 -0
- package/dist/cli/worktree-ownership.js.map +1 -0
- package/dist/cli/worktree-prune-policy.d.ts +76 -0
- package/dist/cli/worktree-prune-policy.d.ts.map +1 -0
- package/dist/cli/worktree-prune-policy.js +114 -0
- package/dist/cli/worktree-prune-policy.js.map +1 -0
- package/dist/cli/worktree-prune.d.ts +96 -0
- package/dist/cli/worktree-prune.d.ts.map +1 -0
- package/dist/cli/worktree-prune.js +153 -0
- package/dist/cli/worktree-prune.js.map +1 -0
- package/dist/codex/scripts/block-no-verify.sh +4 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +4 -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 +23 -6
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +4 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +132 -43
- 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/hooks/block-no-verify.agy.sh +4 -1
- package/plugins/lisa/hooks/block-no-verify.sh +4 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +132 -43
- package/plugins/lisa/rules/reference/base-rules.md +1 -1
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +4 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +132 -43
- package/plugins/lisa-agy/plugin.json +1 -1
- 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/block-no-verify.sh +4 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +132 -43
- package/plugins/lisa-copilot/rules/reference/base-rules.md +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +4 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +132 -43
- package/plugins/lisa-cursor/rules/base-rules-reference.mdc +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-no-verify.agy.sh +4 -1
- package/plugins/src/base/hooks/block-no-verify.sh +4 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +132 -43
- 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=""
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
471
|
-
|
|
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
|
|
499
|
-
# an invocation, not prose.
|
|
500
|
-
|
|
501
|
-
|
|
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' "$
|
|
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
|
|
511
|
-
#
|
|
512
|
-
#
|
|
513
|
-
#
|
|
514
|
-
#
|
|
515
|
-
#
|
|
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"
|
|
522
|
+
local name="$1"
|
|
518
523
|
case "$name" in
|
|
519
524
|
"" | *[!A-Za-z0-9_]*) return 1 ;;
|
|
520
525
|
esac
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
|
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-openclaw",
|
|
3
|
-
"version": "4.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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):
|