@codyswann/lisa 4.24.2 → 4.25.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 +218 -82
- package/all/copy-overwrite/scripts/lisa-reconcile-policy.mjs +85 -12
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +74 -15
- package/dist/cli/ui-config-write-persistence.d.ts +12 -2
- package/dist/cli/ui-config-write-persistence.d.ts.map +1 -1
- package/dist/cli/ui-config-write-persistence.js +7 -3
- package/dist/cli/ui-config-write-persistence.js.map +1 -1
- package/dist/cli/ui-config-write.d.ts +1 -1
- package/dist/cli/ui-config-write.d.ts.map +1 -1
- package/dist/cli/ui-config-write.js +62 -6
- package/dist/cli/ui-config-write.js.map +1 -1
- package/dist/codex/scripts/block-no-verify.sh +134 -37
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +8 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +7 -6
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +218 -82
- package/package.json +3 -3
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-no-verify.agy.sh +134 -37
- package/plugins/lisa/hooks/block-no-verify.sh +218 -82
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +134 -37
- 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 +218 -82
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +218 -82
- 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 +134 -37
- package/plugins/src/base/hooks/block-no-verify.sh +218 -82
- package/ui/README.md +30 -2
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
# 5. GIT_CONFIG_KEY_<n>=core.hooksPath env-var-style command-scope config;
|
|
13
13
|
# 6. GIT_CONFIG_PARAMETERS carrying core.hooksPath command-scope config;
|
|
14
14
|
# 7. the short `-n`, as a real argv token of a `git commit` — bare, or bundled
|
|
15
|
-
# into a short-option cluster such as `-nm "msg"
|
|
15
|
+
# into a short-option cluster such as `-nm "msg"`;
|
|
16
|
+
# 8. a nested shell carrying any of the above as its `-c` command string,
|
|
17
|
+
# including behind long shell options such as `bash --norc -c`;
|
|
18
|
+
# 9. `env --split-string` in EVERY abbreviation GNU accepts (`--s` upward,
|
|
19
|
+
# with or without an attached `=value`);
|
|
20
|
+
# 10. those forms behind a wrapper that does not change what runs —
|
|
21
|
+
# `command`, `exec`, `builtin`, `nohup`, `setsid`, and nested `env`.
|
|
16
22
|
#
|
|
17
23
|
# The line below is what lets `lisa apply` tell a downstream copy of this guard
|
|
18
24
|
# that is BEHIND from one that is AHEAD. Byte comparison cannot: both look like
|
|
@@ -23,7 +29,7 @@
|
|
|
23
29
|
# Add a name here in the same commit that closes a vector. A hardening that
|
|
24
30
|
# forgets to is invisible to refresh, and shows up as an unexplained diff at
|
|
25
31
|
# review time instead of a named capability.
|
|
26
|
-
# lisa-guard-capabilities: no-verify-abbrev, husky-env, hookspath-allowlist, config-env, env-split-string, git-config-key, git-config-parameters, git-config-parameters-append, git-config-parameters-expansion, heredoc-shell-word, herestring-aware, no-verify-short, nested-shell-no-verify
|
|
32
|
+
# lisa-guard-capabilities: no-verify-abbrev, husky-env, hookspath-allowlist, config-env, env-split-string, git-config-key, git-config-parameters, git-config-parameters-append, git-config-parameters-expansion, heredoc-shell-word, herestring-aware, no-verify-short, nested-shell-no-verify, nested-shell-long-options, env-split-string-abbrev, command-wrapper-normalization
|
|
27
33
|
#
|
|
28
34
|
# Shell-token matching avoids false positives from issue bodies, heredocs, and
|
|
29
35
|
# commit-message prose while still catching quoted real argv values such as
|
|
@@ -219,26 +225,61 @@ COMMAND_SEPARATORS = {
|
|
|
219
225
|
SHELL_PROGRAMS = {"bash", "dash", "ksh", "sh", "zsh"}
|
|
220
226
|
MAX_SHELL_NESTING = 8
|
|
221
227
|
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
|
|
226
|
-
|
|
228
|
+
# GNU env's long options, listed in FULL so an abbreviation can be resolved
|
|
229
|
+
# against them. getopt_long accepts any prefix that names exactly one option,
|
|
230
|
+
# and no other env option begins with `s` — so `env --s`, `env --sp`, and
|
|
231
|
+
# `env --split` are `env --split-string` just as completely as the full
|
|
232
|
+
# spelling is. Matching only the full spelling is bypassed by deleting
|
|
233
|
+
# characters, which is why the set below exists instead of a literal list of
|
|
234
|
+
# the two spellings the guard used to know.
|
|
235
|
+
ENV_LONG_OPTIONS = {
|
|
236
|
+
"--argv0", "--block-signals", "--chdir", "--debug", "--default-signal",
|
|
237
|
+
"--help", "--ignore-environment", "--ignore-signal",
|
|
238
|
+
"--list-signal-handling", "--null", "--split-string", "--unset",
|
|
239
|
+
"--version",
|
|
227
240
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
# Of those, the ones whose value is a SEPARATE token, so the operand is never
|
|
242
|
+
# mistaken for the command (`env -u bash -c ...` consumes `bash` as the
|
|
243
|
+
# variable name). The signal options take an OPTIONAL value, which GNU reads
|
|
244
|
+
# only when it is attached with `=`, so they never consume a following token.
|
|
245
|
+
ENV_LONG_SEPARATE_VALUE = {"--argv0", "--chdir", "--split-string", "--unset"}
|
|
246
|
+
|
|
247
|
+
# env's single-letter options, by whether the rest of a cluster is more
|
|
248
|
+
# options or the option's value.
|
|
249
|
+
ENV_SHORT_NO_VALUE = frozenset("i0v")
|
|
250
|
+
ENV_SHORT_TAKES_VALUE = frozenset("uCaS")
|
|
251
|
+
|
|
252
|
+
# Builtins and wrappers that run whatever FOLLOWS them without changing what
|
|
253
|
+
# it is: `command env -S ...` runs exactly the `env` that `env -S ...` runs,
|
|
254
|
+
# and `env -- env -S ...` runs exactly the inner `env`. Reading the wrapper as
|
|
255
|
+
# the command is how a payload hides from a guard that inspects only the first
|
|
256
|
+
# word of an invocation.
|
|
257
|
+
COMMAND_PREFIX_WRAPPERS = {"command", "exec", "builtin", "nohup", "setsid"}
|
|
258
|
+
# The only option of those that consumes a separate following token
|
|
259
|
+
# (`exec -a NAME env -S ...`).
|
|
260
|
+
COMMAND_PREFIX_SEPARATE_VALUE = {"-a"}
|
|
235
261
|
|
|
236
262
|
# Shell options whose following token is an operand, not another option. The
|
|
237
263
|
# generic recursive scanner must step over these before looking for `-c`.
|
|
238
264
|
SHELL_OPTIONS_SEPARATE_VALUE = {
|
|
239
|
-
"-o", "+o", "-O", "+O", "--rcfile", "--init-file",
|
|
265
|
+
"-o", "+o", "-O", "+O", "--rcfile", "--init-file", "--emulate",
|
|
266
|
+
}
|
|
267
|
+
SHELL_OPTIONS_INLINE_VALUE = ("--rcfile=", "--init-file=", "--emulate=")
|
|
268
|
+
|
|
269
|
+
# Boolean long options of the recognized shells. Listed only for PRECISION:
|
|
270
|
+
# a long option is never the command-string flag either way, but naming the
|
|
271
|
+
# ordinary ones keeps `bash --norc script.sh` from being treated as a possible
|
|
272
|
+
# value-taking option whose operand must be scanned past. zsh and ksh accept a
|
|
273
|
+
# long option per shell setting, so this set is deliberately not exhaustive —
|
|
274
|
+
# the scanner stays correct on the ones it does not name.
|
|
275
|
+
SHELL_LONG_OPTIONS_NO_VALUE = {
|
|
276
|
+
"--debugger", "--dump-po-strings", "--dump-strings", "--emacs",
|
|
277
|
+
"--globalrcs", "--help", "--interactive", "--login", "--monitor",
|
|
278
|
+
"--no-globalrcs", "--no-rcs", "--noediting", "--noprofile", "--norc",
|
|
279
|
+
"--norcs", "--posix", "--pretty-print", "--privileged", "--protected",
|
|
280
|
+
"--rcs", "--restricted", "--verbose", "--version", "--vi", "--wordexp",
|
|
281
|
+
"--xtrace",
|
|
240
282
|
}
|
|
241
|
-
SHELL_OPTIONS_INLINE_VALUE = ("--rcfile=", "--init-file=")
|
|
242
283
|
|
|
243
284
|
# git's own options that take a SEPARATE value token, which therefore must be
|
|
244
285
|
# skipped when looking for the subcommand: `git -c core.hooksPath=x commit` and
|
|
@@ -392,56 +433,143 @@ def subcommand_after_git(tokens, start):
|
|
|
392
433
|
return None
|
|
393
434
|
|
|
394
435
|
|
|
395
|
-
def
|
|
396
|
-
"""
|
|
397
|
-
start = index - 1
|
|
398
|
-
while start >= 0 and tokens[start] not in COMMAND_SEPARATORS:
|
|
399
|
-
start -= 1
|
|
400
|
-
prefix = tokens[start + 1:index]
|
|
401
|
-
if not prefix:
|
|
402
|
-
return True
|
|
403
|
-
if prefix[0].rsplit("/", 1)[-1] != "env":
|
|
404
|
-
return all("=" in token and not token.startswith("=") for token in prefix)
|
|
436
|
+
def resolve_env_long_option(name):
|
|
437
|
+
"""Resolve one GNU env long option, abbreviations included.
|
|
405
438
|
|
|
406
|
-
|
|
439
|
+
Args:
|
|
440
|
+
name: A token beginning with `--`, with any `=value` already removed.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
The full option spelling; "" when the abbreviation names more than one
|
|
444
|
+
option, which real env refuses; None when nothing matches at all.
|
|
445
|
+
"""
|
|
446
|
+
if not name.startswith("--") or name == "--":
|
|
447
|
+
return None
|
|
448
|
+
if name in ENV_LONG_OPTIONS:
|
|
449
|
+
return name
|
|
450
|
+
matches = [option for option in ENV_LONG_OPTIONS if option.startswith(name)]
|
|
451
|
+
if len(matches) == 1:
|
|
452
|
+
return matches[0]
|
|
453
|
+
return "" if matches else None
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def env_option_kind(token):
|
|
457
|
+
"""Classify one token sitting in an `env` option position.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
"split-string" when env will reparse an argv value as shell words,
|
|
461
|
+
"separate-value" when the NEXT token is this option's operand,
|
|
462
|
+
"no-value" when the token stands alone, "ambiguous" when env accepts
|
|
463
|
+
the token but this parser cannot say which option it is, or None when
|
|
464
|
+
the token is not an option.
|
|
465
|
+
"""
|
|
466
|
+
if not token.startswith("-") or token in {"-", "--"}:
|
|
467
|
+
return None
|
|
468
|
+
if token.startswith("--"):
|
|
469
|
+
name = token.split("=", 1)[0]
|
|
470
|
+
resolved = resolve_env_long_option(name)
|
|
471
|
+
if not resolved:
|
|
472
|
+
# Either unknown to this parser or an abbreviation naming several
|
|
473
|
+
# options. Neither can be reasoned about, so say so and let the
|
|
474
|
+
# caller fail closed rather than read past it.
|
|
475
|
+
return "ambiguous"
|
|
476
|
+
if resolved == "--split-string":
|
|
477
|
+
return "split-string"
|
|
478
|
+
if "=" in token:
|
|
479
|
+
return "no-value"
|
|
480
|
+
return (
|
|
481
|
+
"separate-value" if resolved in ENV_LONG_SEPARATE_VALUE else "no-value"
|
|
482
|
+
)
|
|
483
|
+
for position, letter in enumerate(token[1:], start=2):
|
|
484
|
+
if letter == "S":
|
|
485
|
+
return "split-string"
|
|
486
|
+
if letter in ENV_SHORT_TAKES_VALUE:
|
|
487
|
+
# The rest of this cluster is the option's value when there is
|
|
488
|
+
# one; otherwise the value is the following token.
|
|
489
|
+
return "no-value" if position <= len(token) - 1 else "separate-value"
|
|
490
|
+
if letter not in ENV_SHORT_NO_VALUE:
|
|
491
|
+
return "ambiguous"
|
|
492
|
+
return "no-value"
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def strip_command_prefix(prefix):
|
|
496
|
+
"""Drop the tokens that precede a command without changing what it is.
|
|
497
|
+
|
|
498
|
+
Variable assignments, the `command`/`exec`/`builtin` builtins, and nested
|
|
499
|
+
`env` wrappers all leave the following word in command position. Reading
|
|
500
|
+
any of them as the command itself is what let `command env -S ...` and
|
|
501
|
+
`env -- env -S ...` slip past the split-string check: the prefix failed
|
|
502
|
+
the "assignments only" test, so the wrapped `env` was never classified.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
prefix: The tokens between the last command separator and the
|
|
506
|
+
candidate program.
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
A pair (status, remainder). "ok" carries the tokens still standing
|
|
510
|
+
between the separator and the candidate — empty means the candidate is
|
|
511
|
+
in command position. "operand" means the candidate is a wrapper
|
|
512
|
+
option's value rather than a command. "ambiguous" means a wrapper
|
|
513
|
+
option could not be classified and the caller must fail closed.
|
|
514
|
+
"""
|
|
515
|
+
cursor = 0
|
|
407
516
|
while cursor < len(prefix):
|
|
408
517
|
token = prefix[cursor]
|
|
409
|
-
if token
|
|
410
|
-
return cursor == len(prefix) - 1
|
|
411
|
-
if "=" in token and not token.startswith("=") and not token.startswith("-"):
|
|
518
|
+
if "=" in token and not token.startswith(("=", "-")):
|
|
412
519
|
cursor += 1
|
|
413
520
|
continue
|
|
414
|
-
|
|
521
|
+
program = token.rsplit("/", 1)[-1]
|
|
522
|
+
if program in COMMAND_PREFIX_WRAPPERS:
|
|
415
523
|
cursor += 1
|
|
524
|
+
while cursor < len(prefix) and prefix[cursor].startswith("-"):
|
|
525
|
+
if prefix[cursor] == "--":
|
|
526
|
+
cursor += 1
|
|
527
|
+
break
|
|
528
|
+
if prefix[cursor] in COMMAND_PREFIX_SEPARATE_VALUE:
|
|
529
|
+
if cursor + 1 >= len(prefix):
|
|
530
|
+
return ("operand", [])
|
|
531
|
+
cursor += 2
|
|
532
|
+
continue
|
|
533
|
+
cursor += 1
|
|
416
534
|
continue
|
|
417
|
-
if
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
535
|
+
if program == "env":
|
|
536
|
+
cursor += 1
|
|
537
|
+
while cursor < len(prefix):
|
|
538
|
+
option = prefix[cursor]
|
|
539
|
+
if option == "--":
|
|
540
|
+
cursor += 1
|
|
541
|
+
break
|
|
542
|
+
kind = env_option_kind(option)
|
|
543
|
+
if kind is None:
|
|
544
|
+
break
|
|
545
|
+
if kind == "ambiguous":
|
|
546
|
+
return ("ambiguous", [])
|
|
547
|
+
if kind == "split-string":
|
|
548
|
+
# Everything after `-S` is env's own opaque payload, so the
|
|
549
|
+
# candidate is not a command at all.
|
|
550
|
+
return ("operand", [])
|
|
551
|
+
if kind == "separate-value":
|
|
552
|
+
if cursor + 1 >= len(prefix):
|
|
553
|
+
return ("operand", [])
|
|
554
|
+
cursor += 2
|
|
555
|
+
continue
|
|
556
|
+
cursor += 1
|
|
423
557
|
continue
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
# Fail closed for a recognized shell rather than trusting it.
|
|
427
|
-
return True
|
|
428
|
-
return False
|
|
429
|
-
return True
|
|
558
|
+
break
|
|
559
|
+
return ("ok", prefix[cursor:])
|
|
430
560
|
|
|
431
561
|
|
|
432
|
-
def
|
|
433
|
-
"""Whether
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return False
|
|
444
|
-
return False
|
|
562
|
+
def shell_starts_command(tokens, index):
|
|
563
|
+
"""Whether a recognized shell token occupies command position."""
|
|
564
|
+
start = index - 1
|
|
565
|
+
while start >= 0 and tokens[start] not in COMMAND_SEPARATORS:
|
|
566
|
+
start -= 1
|
|
567
|
+
status, remainder = strip_command_prefix(tokens[start + 1:index])
|
|
568
|
+
if status == "ambiguous":
|
|
569
|
+
# A prefix option this parser cannot classify may still be launching
|
|
570
|
+
# the shell. Fail closed rather than trusting the wrapper.
|
|
571
|
+
return True
|
|
572
|
+
return status == "ok" and not remainder
|
|
445
573
|
|
|
446
574
|
|
|
447
575
|
def env_uses_split_string(tokens, index):
|
|
@@ -461,8 +589,8 @@ def env_uses_split_string(tokens, index):
|
|
|
461
589
|
start = index - 1
|
|
462
590
|
while start >= 0 and tokens[start] not in COMMAND_SEPARATORS:
|
|
463
591
|
start -= 1
|
|
464
|
-
|
|
465
|
-
if
|
|
592
|
+
status, remainder = strip_command_prefix(tokens[start + 1:index])
|
|
593
|
+
if status != "ok" or remainder:
|
|
466
594
|
return False
|
|
467
595
|
|
|
468
596
|
cursor = index + 1
|
|
@@ -470,40 +598,39 @@ def env_uses_split_string(tokens, index):
|
|
|
470
598
|
token = tokens[cursor]
|
|
471
599
|
if token in COMMAND_SEPARATORS or token == "--":
|
|
472
600
|
return False
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
or token.startswith("--split-string=")
|
|
476
|
-
or env_short_option_uses_split_string(token)
|
|
477
|
-
):
|
|
601
|
+
kind = env_option_kind(token)
|
|
602
|
+
if kind == "split-string":
|
|
478
603
|
return True
|
|
479
|
-
if
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
cursor += 1
|
|
490
|
-
continue
|
|
491
|
-
if token in ENV_OPTIONS_SEPARATE_VALUE:
|
|
492
|
-
cursor += 2
|
|
493
|
-
continue
|
|
494
|
-
return False
|
|
604
|
+
if kind == "ambiguous":
|
|
605
|
+
# An option env accepts but this parser cannot name makes the rest
|
|
606
|
+
# of the invocation unreadable; refuse instead of reading past it.
|
|
607
|
+
return True
|
|
608
|
+
if kind is None:
|
|
609
|
+
if "=" in token and not token.startswith(("=", "-")):
|
|
610
|
+
cursor += 1
|
|
611
|
+
continue
|
|
612
|
+
return False
|
|
613
|
+
cursor += 2 if kind == "separate-value" else 1
|
|
495
614
|
return False
|
|
496
615
|
|
|
497
616
|
|
|
498
617
|
def nested_shell_payload(tokens, index):
|
|
499
618
|
"""Return a shell `-c` payload, or True when option parsing is ambiguous."""
|
|
500
619
|
cursor = index + 1
|
|
620
|
+
opaque_option_seen = False
|
|
501
621
|
while cursor < len(tokens):
|
|
502
622
|
option = tokens[cursor]
|
|
503
623
|
if option in COMMAND_SEPARATORS or option == "--":
|
|
504
624
|
return None
|
|
505
625
|
if not option.startswith(("-", "+")):
|
|
506
|
-
|
|
626
|
+
# Ordinarily the script operand, which ends option parsing. After
|
|
627
|
+
# a long option this parser cannot name it may instead be that
|
|
628
|
+
# option's value (`zsh --emulate ksh -c ...`), so keep scanning
|
|
629
|
+
# rather than concluding there is no command string.
|
|
630
|
+
if not opaque_option_seen:
|
|
631
|
+
return None
|
|
632
|
+
cursor += 1
|
|
633
|
+
continue
|
|
507
634
|
if option in SHELL_OPTIONS_SEPARATE_VALUE:
|
|
508
635
|
if cursor + 1 >= len(tokens) or tokens[cursor + 1] in COMMAND_SEPARATORS:
|
|
509
636
|
return True
|
|
@@ -512,8 +639,17 @@ def nested_shell_payload(tokens, index):
|
|
|
512
639
|
if option.startswith(SHELL_OPTIONS_INLINE_VALUE):
|
|
513
640
|
cursor += 1
|
|
514
641
|
continue
|
|
515
|
-
|
|
516
|
-
|
|
642
|
+
# A LONG option is never the command-string flag, and testing it for a
|
|
643
|
+
# bare `c` was the bug: `--norc` and `--restricted` merely CONTAIN one,
|
|
644
|
+
# so the real `-c` that followed was consumed as their payload and the
|
|
645
|
+
# nested `git commit --no-verify` was never classified at all. The
|
|
646
|
+
# command string lives only in a single-dash cluster (`-c`, `-lc`).
|
|
647
|
+
if option.startswith("--"):
|
|
648
|
+
if option not in SHELL_LONG_OPTIONS_NO_VALUE:
|
|
649
|
+
opaque_option_seen = True
|
|
650
|
+
cursor += 1
|
|
651
|
+
continue
|
|
652
|
+
if "c" in option[1:]:
|
|
517
653
|
if cursor + 1 >= len(tokens) or tokens[cursor + 1] in COMMAND_SEPARATORS:
|
|
518
654
|
return True
|
|
519
655
|
return tokens[cursor + 1]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.25.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.25.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.25.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.25.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.25.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"
|