@codyswann/lisa 4.34.6 → 4.34.8
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-direct-issue-create.sh +184 -5
- package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +11 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +6 -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 +19 -13
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa/.codex-plugin/skills/lisa-git-submit-pr/SKILL.md +13 -5
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +64 -7
- package/plugins/lisa/hooks/block-direct-issue-create.sh +184 -5
- package/plugins/lisa/hooks/block-managed-file-edits.sh +11 -5
- package/plugins/lisa/rules/eager/rejection-detection.md +14 -1
- package/plugins/lisa/rules/eager/settled-decisions.md +20 -0
- package/plugins/lisa/rules/reference/rejection-detection.md +69 -0
- package/plugins/lisa/rules/reference/settled-decisions.md +10 -0
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +64 -7
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +184 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa-agy/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +64 -7
- 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-direct-issue-create.sh +184 -5
- package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +11 -5
- package/plugins/lisa-copilot/rules/eager/rejection-detection.md +14 -1
- package/plugins/lisa-copilot/rules/eager/settled-decisions.md +20 -0
- package/plugins/lisa-copilot/rules/reference/rejection-detection.md +69 -0
- package/plugins/lisa-copilot/rules/reference/settled-decisions.md +10 -0
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa-copilot/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +64 -7
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +184 -5
- package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +11 -5
- package/plugins/lisa-cursor/rules/rejection-detection-reference.mdc +69 -0
- package/plugins/lisa-cursor/rules/rejection-detection.mdc +14 -1
- package/plugins/lisa-cursor/rules/settled-decisions-reference.mdc +10 -0
- package/plugins/lisa-cursor/rules/settled-decisions.mdc +20 -0
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa-cursor/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +64 -7
- 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-direct-issue-create.sh +184 -5
- package/plugins/src/base/hooks/block-managed-file-edits.sh +11 -5
- package/plugins/src/base/rules/eager/rejection-detection.md +14 -1
- package/plugins/src/base/rules/eager/settled-decisions.md +20 -0
- package/plugins/src/base/rules/reference/rejection-detection.md +69 -0
- package/plugins/src/base/rules/reference/settled-decisions.md +10 -0
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/src/base/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +64 -7
- package/scripts/two-channel-couplings.json +1 -1
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +69 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.34.
|
|
3
|
+
"version": "4.34.8",
|
|
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.34.
|
|
3
|
+
"version": "4.34.8",
|
|
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.34.
|
|
3
|
+
"version": "4.34.8",
|
|
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.34.
|
|
3
|
+
"version": "4.34.8",
|
|
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.34.
|
|
3
|
+
"version": "4.34.8",
|
|
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"
|
|
@@ -1399,12 +1399,190 @@ def nested_operands(argv):
|
|
|
1399
1399
|
inspected_files = set()
|
|
1400
1400
|
|
|
1401
1401
|
|
|
1402
|
+
# Interpreters whose OPERAND is a program they run. Broader than a shell list
|
|
1403
|
+
# because `file_creation` recognises a creation in any language — the
|
|
1404
|
+
# `node wrapper.mjs` that speaks HTTP directly is exactly the shape #3484
|
|
1405
|
+
# measured as a fail-open, so narrowing this to shells would reopen it.
|
|
1406
|
+
EXECUTING_INTERPRETERS = {
|
|
1407
|
+
"bash", "dash", "ksh", "sh", "zsh",
|
|
1408
|
+
"bun", "deno", "node", "nodejs", "perl", "php",
|
|
1409
|
+
"python", "python3", "ruby", "ts-node", "tsx",
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
# `.` and its `source` alias run the named file in the CURRENT shell.
|
|
1413
|
+
SOURCE_BUILTINS = {"source", "."}
|
|
1414
|
+
|
|
1415
|
+
# Wrappers that run what FOLLOWS them without changing what it is, mapped to
|
|
1416
|
+
# (options whose value is a SEPARATE token, positional operands consumed).
|
|
1417
|
+
# The positional count is why this is a table: `timeout 5 bash create.sh` puts
|
|
1418
|
+
# an operand between the wrapper and the interpreter, so a walk that steps over
|
|
1419
|
+
# `-flags` only stops at `5` and never reaches the interpreter.
|
|
1420
|
+
EXEC_WRAPPERS = {
|
|
1421
|
+
"builtin": (frozenset(), 0),
|
|
1422
|
+
"command": (frozenset(), 0),
|
|
1423
|
+
"exec": (frozenset({"-a"}), 0),
|
|
1424
|
+
"env": (frozenset({"-u", "--unset", "-C", "--chdir", "--argv0"}), 0),
|
|
1425
|
+
"nice": (frozenset({"-n", "--adjustment"}), 0),
|
|
1426
|
+
"nohup": (frozenset(), 0),
|
|
1427
|
+
"setsid": (frozenset(), 0),
|
|
1428
|
+
"stdbuf": (frozenset({"-i", "-o", "-e"}), 0),
|
|
1429
|
+
"sudo": (frozenset({"-u", "--user", "-g", "--group", "-C", "--close-from"}), 0),
|
|
1430
|
+
"time": (frozenset(), 0),
|
|
1431
|
+
"timeout": (frozenset({"-k", "--kill-after", "-s", "--signal"}), 1),
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
# Interpreter options that mean "no script file follows": a command string or a
|
|
1435
|
+
# module name, both of which the guard reads by other means or not at all.
|
|
1436
|
+
NO_SCRIPT_OPTIONS = {"-c", "-e", "-m", "--eval", "--command", "--module"}
|
|
1437
|
+
|
|
1438
|
+
# Programs that READ their operands and never execute them as programs.
|
|
1439
|
+
#
|
|
1440
|
+
# This is an allowlist of readers rather than the inverse, and the asymmetry is
|
|
1441
|
+
# deliberate. An UNKNOWN program with a file operand is still followed, because
|
|
1442
|
+
# whether it executes that operand is genuinely unknowable from the command —
|
|
1443
|
+
# and a probe in this guard's own suite drives it with a runner nobody
|
|
1444
|
+
# enumerated precisely so a fix keyed on a list of interpreters fails. Failing
|
|
1445
|
+
# closed on the unknown keeps that coverage.
|
|
1446
|
+
#
|
|
1447
|
+
# What the list buys is the measured population of #3705, every member of which
|
|
1448
|
+
# is a well-known inspector: `git diff`, `grep -n`, `wc -l`, a `git grep`
|
|
1449
|
+
# PATHSPEC, `sed -n '1,50p'`, and a test run. `git` is here wholesale because no
|
|
1450
|
+
# git subcommand executes a named file as a program — `git commit -F <file>` and
|
|
1451
|
+
# `git grep -- <pathspec>` both take the path as data.
|
|
1452
|
+
#
|
|
1453
|
+
# Test runners are here on purpose and it is the one entry worth arguing about.
|
|
1454
|
+
# `vitest <file>` does run that file, so the executes-rule would follow it — but
|
|
1455
|
+
# running a test is not filing an issue, and refusing a test run is the sharpest
|
|
1456
|
+
# harm #3705 records, because it can block the verification of a fix while
|
|
1457
|
+
# saying nothing about why.
|
|
1458
|
+
#
|
|
1459
|
+
# RESIDUAL, stated rather than hidden: a reader NOT on this list is still
|
|
1460
|
+
# followed and can still over-refuse. The set of read-only tools is unbounded,
|
|
1461
|
+
# so this closes the measured population and not the class. Add names here as
|
|
1462
|
+
# they are measured; do not invert the default to close it by fiat, or the
|
|
1463
|
+
# executed-script reach that #3484 bought is lost.
|
|
1464
|
+
READ_ONLY_PROGRAMS = {
|
|
1465
|
+
"awk", "bat", "cat", "cksum", "cmp", "column", "comm", "cut", "diff",
|
|
1466
|
+
"du", "file", "fold", "git", "grep", "head", "hexdump", "jest", "jq",
|
|
1467
|
+
"less", "ls", "md5", "md5sum", "more", "nl", "od", "pytest", "rg",
|
|
1468
|
+
"sed", "sha1sum", "sha256sum", "shellcheck", "shfmt", "sort", "stat",
|
|
1469
|
+
"strings", "tail", "tee", "uniq", "vitest", "wc", "xxd", "yamllint",
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
|
|
1473
|
+
def executing_command(argv):
|
|
1474
|
+
"""The program this command runs, and the tokens after it.
|
|
1475
|
+
|
|
1476
|
+
Steps over leading variable assignments and over wrappers that do not
|
|
1477
|
+
change what runs. Reading a wrapper as the command is how a payload hides
|
|
1478
|
+
from a classifier that inspects only a segment's first word.
|
|
1479
|
+
|
|
1480
|
+
Args:
|
|
1481
|
+
argv: One command's tokens.
|
|
1482
|
+
|
|
1483
|
+
Returns:
|
|
1484
|
+
A triple (token, program, args); (None, None, []) when none is named.
|
|
1485
|
+
The raw token is carried alongside the basename because a script run by
|
|
1486
|
+
bare path — `./create.sh`, relying on its shebang — has no interpreter
|
|
1487
|
+
to recognise, and the command word IS the thing being executed.
|
|
1488
|
+
"""
|
|
1489
|
+
index = 0
|
|
1490
|
+
while index < len(argv):
|
|
1491
|
+
token = argv[index]
|
|
1492
|
+
if "=" in token and not token.startswith(("=", "-")):
|
|
1493
|
+
index += 1
|
|
1494
|
+
continue
|
|
1495
|
+
program = basename(token)
|
|
1496
|
+
if program not in EXEC_WRAPPERS:
|
|
1497
|
+
return (token, program, argv[index + 1 :])
|
|
1498
|
+
separate, positional = EXEC_WRAPPERS[program]
|
|
1499
|
+
index += 1
|
|
1500
|
+
while index < len(argv) and argv[index].startswith("-"):
|
|
1501
|
+
option = argv[index]
|
|
1502
|
+
if option == "--":
|
|
1503
|
+
index += 1
|
|
1504
|
+
break
|
|
1505
|
+
index += 2 if option in separate else 1
|
|
1506
|
+
index += positional
|
|
1507
|
+
return (None, None, [])
|
|
1508
|
+
|
|
1509
|
+
|
|
1510
|
+
def executed_operand(argv):
|
|
1511
|
+
"""The path this command EXECUTES, or None.
|
|
1512
|
+
|
|
1513
|
+
THIS IS THE COMMAND-POSITION QUESTION, and declining to ask it is what
|
|
1514
|
+
#3705 records: the guard opened any readable file any token named, then
|
|
1515
|
+
justified a refusal from that FILE's contents rather than from the
|
|
1516
|
+
COMMAND's behaviour. Measured refusals included `git diff`, `grep -n`,
|
|
1517
|
+
`wc -l`, a `git grep` PATHSPEC and a test run — every one of them read-only,
|
|
1518
|
+
and one of them blocked running the tests that would have proved a fix.
|
|
1519
|
+
|
|
1520
|
+
`parity-safety-net.sh` already states the rule this restores, and names the
|
|
1521
|
+
opposite as the known-wrong fix (`origin/main`, lines 337-346 and 704-705):
|
|
1522
|
+
"Only a COMMAND POSITION can execute something. A path anywhere else is an
|
|
1523
|
+
argument, and an argument is data." `block-no-verify.sh:495-513`
|
|
1524
|
+
(`strip_command_prefix`) implements the same split with a fail-closed third
|
|
1525
|
+
state. Both live in this directory; this guard was the one that declined.
|
|
1526
|
+
|
|
1527
|
+
NOT narrowed to shells, and NOT narrowed to an enumerated interpreter list.
|
|
1528
|
+
`file_creation` recognises a creation in any language, so `node wrapper.mjs`
|
|
1529
|
+
must still be followed — a measured fail-open in #3484 — and this guard's
|
|
1530
|
+
own suite drives it with a runner nobody enumerated, precisely so a fix
|
|
1531
|
+
keyed on such a list fails. So the default for an unrecognised program is to
|
|
1532
|
+
FOLLOW, and only a known reader is exempt. A script run by bare path with no
|
|
1533
|
+
interpreter at all (`./create.sh`, via its shebang) is followed too: there
|
|
1534
|
+
the command word is itself the thing being executed.
|
|
1535
|
+
|
|
1536
|
+
Reading a REQUEST PAYLOAD is a separate question with a separate answer:
|
|
1537
|
+
`payload_text` resolves `--data-binary @file` and friends by flag name, so
|
|
1538
|
+
narrowing this walk does not stop a creation whose body lives in a file
|
|
1539
|
+
from being seen.
|
|
1540
|
+
|
|
1541
|
+
Args:
|
|
1542
|
+
argv: One command's tokens.
|
|
1543
|
+
|
|
1544
|
+
Returns:
|
|
1545
|
+
The operand token naming a program that will run, or None.
|
|
1546
|
+
"""
|
|
1547
|
+
token, program, args = executing_command(argv)
|
|
1548
|
+
if program is None:
|
|
1549
|
+
return None
|
|
1550
|
+
# A reader takes its operands as data. This is the #3705 population.
|
|
1551
|
+
if program in READ_ONLY_PROGRAMS:
|
|
1552
|
+
return None
|
|
1553
|
+
if program in SOURCE_BUILTINS:
|
|
1554
|
+
return args[0] if args else None
|
|
1555
|
+
# A command word that is ITSELF a readable file is being executed by its
|
|
1556
|
+
# shebang — `./create.sh` names no interpreter and runs all the same.
|
|
1557
|
+
if program not in EXECUTING_INTERPRETERS and resolve_operand(token) is not None:
|
|
1558
|
+
return token
|
|
1559
|
+
# `bash < create.sh` runs that file as surely as `bash create.sh` does, and
|
|
1560
|
+
# `<` is not a segment boundary, so the pair survives in one argv.
|
|
1561
|
+
for index, argument in enumerate(args):
|
|
1562
|
+
if argument == "<" and index + 1 < len(args):
|
|
1563
|
+
return args[index + 1]
|
|
1564
|
+
# Reached by a known interpreter AND by a program this parser cannot name.
|
|
1565
|
+
# The unknown case is followed on purpose — see READ_ONLY_PROGRAMS.
|
|
1566
|
+
for argument in args:
|
|
1567
|
+
if argument == "--":
|
|
1568
|
+
continue
|
|
1569
|
+
if argument.startswith("-"):
|
|
1570
|
+
# A command string or a module name; no script file follows.
|
|
1571
|
+
head = argument.split("=", 1)[0]
|
|
1572
|
+
if argument in NO_SCRIPT_OPTIONS or head in NO_SCRIPT_OPTIONS:
|
|
1573
|
+
return None
|
|
1574
|
+
continue
|
|
1575
|
+
return argument
|
|
1576
|
+
return None
|
|
1577
|
+
|
|
1578
|
+
|
|
1402
1579
|
def file_operands(argv):
|
|
1403
|
-
"""
|
|
1580
|
+
"""The file this command EXECUTES, if it executes one.
|
|
1404
1581
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1582
|
+
Deciding WHICH programs execute their operands is the question this file
|
|
1583
|
+
used to refuse to ask, and refusing it is what made the guard read a path
|
|
1584
|
+
quoted as prose inside a `--body-file` markdown and attribute its contents
|
|
1585
|
+
to the command. It is answered in `executed_operand` now.
|
|
1408
1586
|
|
|
1409
1587
|
Args:
|
|
1410
1588
|
argv: One command's tokens.
|
|
@@ -1413,7 +1591,8 @@ def file_operands(argv):
|
|
|
1413
1591
|
Existing paths, deduplicated across the whole scan and capped.
|
|
1414
1592
|
"""
|
|
1415
1593
|
paths = []
|
|
1416
|
-
|
|
1594
|
+
operand = executed_operand(argv)
|
|
1595
|
+
for token in [operand] if operand is not None else []:
|
|
1417
1596
|
if len(paths) >= FILE_OPERANDS_PER_SEGMENT:
|
|
1418
1597
|
break
|
|
1419
1598
|
path = resolve_operand(token)
|
|
@@ -415,11 +415,17 @@ EOF
|
|
|
415
415
|
# what a tokenizer is for.
|
|
416
416
|
#
|
|
417
417
|
# The analyzer is read into a variable by `read -d ''` rather than run
|
|
418
|
-
# inside a
|
|
419
|
-
# a command substitution by re-scanning its text, and a
|
|
420
|
-
# there is scanned too: the lone backtick in
|
|
421
|
-
# reads as an unterminated backtick
|
|
422
|
-
#
|
|
418
|
+
# inside a command substitution wrapping a here-document. Bash finds the
|
|
419
|
+
# closing paren of a command substitution by re-scanning its text, and a
|
|
420
|
+
# here-document body nested there is scanned too: the lone backtick in
|
|
421
|
+
# `re.compile(r"[$`]")` below then reads as an unterminated backtick
|
|
422
|
+
# substitution and the whole script fails to parse. Keeping the
|
|
423
|
+
# here-document out of the substitution is the fix. This is also the shape
|
|
424
|
+
# `block-direct-issue-create.sh` already uses for its own classifier.
|
|
425
|
+
#
|
|
426
|
+
# The delimiter is deliberately not spelled out in this comment: a tool that
|
|
427
|
+
# greps for here-document starts would otherwise match the prose and read
|
|
428
|
+
# the wrong span.
|
|
423
429
|
analyzer=''
|
|
424
430
|
IFS= read -r -d '' analyzer <<'PY' || true
|
|
425
431
|
import os
|
|
@@ -48,4 +48,17 @@ A **loop-proposed** item that a human **closed as _not planned_** is a durable d
|
|
|
48
48
|
- **Outcomes:** an all-suppressed cycle ends `nothing-needed` naming the **suppression count**; a memory check that cannot read the tracker ends `recovery-required`, never a silent `nothing-needed` (e.g. `Tracker unreachable during the decline check — restore credentials; nothing was filed this run.`).
|
|
49
49
|
- **Concurrency:** search-then-write = convergence, not mutual exclusion — a transient duplicate is closed by the next run; no cross-run lock.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
## Automation-reversal memory (repair-side — orthogonal to both halves above)
|
|
52
|
+
|
|
53
|
+
A human who **undoes a lifecycle transition this flow made** is correcting the flow, not supplying new state. Every loop that writes a lifecycle role as a **repair** (`lisa-repair-intake`) consults this before re-making a transition. It is a third classification, never merged into `rejection-reclaim`: that one is anchored on the ready lane and answers a rejection with re-implementation; this one lands in `$BLOCKED` and answers a correction by withdrawing a decision.
|
|
54
|
+
|
|
55
|
+
- **Seam:** top of the repair walk for each item, **before any lifecycle write** — after the write the current-lane signal is gone.
|
|
56
|
+
- **Classify:** `automation-reversal` — a role change **authored by** this flow, then a later **backward** move of that role **authored by** a human, with no automation move after it. Otherwise `no-reversal`. Authorship unavailable or history unreadable → `unknown`.
|
|
57
|
+
- **Authorship is the signal, not the backward move.** An item can return to `$BLOCKED` because a real new blocker appeared — that is the world moving on. Read the `actor` (GitHub timeline, Linear history) or `author` (JIRA changelog) through the access layers; an unexposed actor is `unknown`, never an assumed human. **A marker and a transition on the same item say nothing until you know their order** — compare timestamps, and treat an unorderable pair as `unknown`.
|
|
58
|
+
- **Lanes from config** (`github.labels.build.blocked` + the JIRA/Linear equivalents, `config-resolution`) — never hardcode.
|
|
59
|
+
- **Degrade:** `unknown` is **neither a reversal nor a clearance** — the item is judged by the loop's normal rules and detection **never blocks the sweep**. The learning-loop exclusion above applies unchanged.
|
|
60
|
+
- **Rule:** on `automation-reversal`, do not re-apply the reversed classification; leave the item where the human put it, ensure `human_needed`, and **name the reversed transition in the run record**. Re-attempt **only** on evidence that **postdates the reversal**, cited in the run record — mirroring the re-proposal rule the proposal half enforces.
|
|
61
|
+
- **A changed lifecycle role is no longer a sufficient warrant on its own.** A consuming loop's recurrence brake must carry **authorship and an overturn bit**, not state alone.
|
|
62
|
+
- **Signed per path.** Apply the inversion only where the loop decides whether to re-make a transition. A staleness/keep-alive path where a human comment already *suppresses* action is protective — leave it. One global sign reads as tightening and removes that protection.
|
|
63
|
+
|
|
64
|
+
Full contract (classification table, per-vendor bindings, reflection & evidence handoff, Proposal rejection memory, Automation-reversal memory): [reference/rejection-detection.md](../reference/rejection-detection.md).
|
|
@@ -53,3 +53,23 @@ honored.
|
|
|
53
53
|
When you take a settled decision, make it auditable in one clause — "auto-merge on, per your standing
|
|
54
54
|
preference", "scoped app-wide, because the Android finding decides it". That gives the operator the
|
|
55
55
|
chance to correct it without requiring them to answer first.
|
|
56
|
+
|
|
57
|
+
## Before you escalate, re-read the instruction file from disk
|
|
58
|
+
|
|
59
|
+
**This rule is delivered at session start; the re-read it demands happens at the escalation
|
|
60
|
+
boundary.** Re-reading at session start would re-read the same moment your context was built from
|
|
61
|
+
and buys nothing.
|
|
62
|
+
|
|
63
|
+
So before you form an escalation, hand a decision back, or re-open a question that may already be
|
|
64
|
+
settled: **open `AGENTS.md` (or `CLAUDE.md`) from disk and read its `## Standing rulings` section.**
|
|
65
|
+
If the file has no such heading, re-read the file.
|
|
66
|
+
|
|
67
|
+
The instruction is to open the file — not to "check whether your context looks complete", because
|
|
68
|
+
**context completeness is not observable from inside.** An instruction file is injected at session
|
|
69
|
+
start and can arrive shortened, and the cut can land on a clean section boundary, where a truncated
|
|
70
|
+
document is indistinguishable from one that never had that section: your copy ends tidily, so you
|
|
71
|
+
conclude you read all of it. Rulings are also written *after* sessions begin — your copy is a
|
|
72
|
+
snapshot of the moment you started, and nothing tells you when it stops being current.
|
|
73
|
+
|
|
74
|
+
This is a **pointer, not a copy**. It stays correct as rulings accumulate, and it survives a
|
|
75
|
+
shortened copy of the file it points at precisely because it does not travel inside that file.
|