@codyswann/lisa 4.45.3 → 4.45.4

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 (88) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/block-blind-automerge.sh +523 -70
  2. package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +160 -2
  3. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  4. package/dist/core/lisa-owned-hash-ledger.js +6 -0
  5. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  6. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  7. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  8. package/dist/core/upstream-evidence-manifest.js +10 -8
  9. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  10. package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +40 -1
  11. package/package.json +4 -4
  12. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  14. package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +16 -0
  15. package/plugins/lisa/hooks/block-blind-automerge.agy.sh +5 -4
  16. package/plugins/lisa/hooks/block-blind-automerge.sh +523 -70
  17. package/plugins/lisa/hooks/block-direct-issue-create.sh +160 -2
  18. package/plugins/lisa/rules/eager/ready-role-filing.md +3 -2
  19. package/plugins/lisa/rules/reference/ready-role-filing.md +20 -2
  20. package/plugins/lisa/skills/lisa-github-write-issue/SKILL.md +16 -0
  21. package/plugins/lisa-agy/hooks/block-blind-automerge.agy.sh +5 -4
  22. package/plugins/lisa-agy/hooks/block-blind-automerge.sh +523 -70
  23. package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +160 -2
  24. package/plugins/lisa-agy/plugin.json +1 -1
  25. package/plugins/lisa-agy/skills/lisa-github-write-issue/SKILL.md +16 -0
  26. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  29. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +523 -70
  33. package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +160 -2
  34. package/plugins/lisa-copilot/rules/eager/ready-role-filing.md +3 -2
  35. package/plugins/lisa-copilot/rules/reference/ready-role-filing.md +20 -2
  36. package/plugins/lisa-copilot/skills/lisa-github-write-issue/SKILL.md +16 -0
  37. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +523 -70
  39. package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +160 -2
  40. package/plugins/lisa-cursor/rules/ready-role-filing-reference.mdc +20 -2
  41. package/plugins/lisa-cursor/rules/ready-role-filing.mdc +3 -2
  42. package/plugins/lisa-cursor/skills/lisa-github-write-issue/SKILL.md +16 -0
  43. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-expo-agy/plugin.json +1 -1
  46. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  51. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  56. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  61. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  65. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  66. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails-agy/plugin.json +1 -1
  71. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  75. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  76. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  81. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  83. package/plugins/src/base/hooks/block-blind-automerge.agy.sh +5 -4
  84. package/plugins/src/base/hooks/block-blind-automerge.sh +523 -70
  85. package/plugins/src/base/hooks/block-direct-issue-create.sh +160 -2
  86. package/plugins/src/base/rules/eager/ready-role-filing.md +3 -2
  87. package/plugins/src/base/rules/reference/ready-role-filing.md +20 -2
  88. package/plugins/src/base/skills/lisa-github-write-issue/SKILL.md +16 -0
@@ -346,7 +346,7 @@ session found 13 of 13 issues filed this way, none of them through Lisa's
346
346
  filing path. The rule saying not to do this already existed — it did not bind,
347
347
  so it is now enforced here.
348
348
 
349
- FILE IT THE SANCTIONED WAY — one of these two, always explicit:
349
+ FILE IT THE SANCTIONED WAY — one of these three, always explicit:
350
350
 
351
351
  1. The item is complete enough to build. Use the filing flow, not the CLI:
352
352
 
@@ -368,11 +368,26 @@ FILE IT THE SANCTIONED WAY — one of these two, always explicit:
368
368
  Held for a human product call: <reason>.
369
369
  <!-- [lisa-human-gate] reason=<short-slug> -->
370
370
 
371
+ 3. The item is a CONTAINER — an Epic, or any item whose state rolls up from
372
+ its children. It is neither of the two above and must not pretend to be:
373
+ \`leaf-only-lifecycle\` FORBIDS the build-ready role on a container, and a
374
+ human gate it does not have is a durable false hold. It declares the third
375
+ thing instead — the canonical container line, in place of a Target Backend
376
+ Environment:
377
+
378
+ ## Target Backend Environment
379
+
380
+ None — container: state rolls up from children
381
+
382
+ That declaration is refused alongside a by-design leaf type
383
+ (\`type:Bug\` / \`type:Task\` / \`type:Sub-task\` / \`type:Improvement\`):
384
+ an item cannot be a container and a leaf at once.
385
+
371
386
  Filed, not ready, and no \`human_gate\` is the incomplete-handoff case, and
372
387
  \`build_ready: false\` with no reason is the same omission with a value
373
388
  attached. See the \`ready-role-filing\` rule for the full contract.
374
389
 
375
- If you must run the CLI directly, the command has to carry one of the two
390
+ If you must run the CLI directly, the command has to carry one of the three
376
391
  declarations itself:
377
392
 
378
393
  $(declaration_hint)
@@ -380,6 +395,9 @@ $(declaration_hint)
380
395
  Or a \`[lisa-human-gate]\` marker in the body it submits, when a human
381
396
  product call really is pending.
382
397
 
398
+ Or, for a container, \`None — container: state rolls up from children\` in
399
+ that body, and no by-design leaf type on the command.
400
+
383
401
  WHERE THE DECLARATION IS READ FROM: argv, the request payload — inline, in a
384
402
  \`--data-binary @file\`, or piped in over stdin — and the contents of a script
385
403
  this command runs. Moving the create into a file no longer moves it out of
@@ -528,6 +546,78 @@ UNPARSEABLE_CREATION = re.compile(
528
546
 
529
547
  BODY_FILE_FLAGS = {"--body-file", "-F", "--input", "--data-binary"}
530
548
  LABEL_FLAGS = {"--label", "--labels", "--add-label", "--status", "--state"}
549
+
550
+ # ---------------------------------------------------------------------------
551
+ # THE THIRD DECLARATION: THIS ITEM IS A CONTAINER
552
+ #
553
+ # The two declarations above are both illegitimate for a container, and the
554
+ # rules say so in as many words. `leaf-only-lifecycle` FORBIDS the build-ready
555
+ # role on an Epic — its state rolls up from its children, and a hand-applied
556
+ # role on a parent is the exact input that rule's claim-time arm exists to
557
+ # reject. `lisa-github-write-issue` says a container needs no human gate.
558
+ # So a guard with only those two arms could be satisfied for a container only
559
+ # by writing something untrue, and BOTH untruths corrupt data another control
560
+ # reads: a stamped role puts a container in the build lane, and a fabricated
561
+ # hold marker is a state change with no inverse that reads to every later
562
+ # observer as a real product hold. A guard that can only be obeyed by lying is
563
+ # worse than one that simply refuses, because the lie is indistinguishable from
564
+ # a correct filing afterwards.
565
+ #
566
+ # WHY NOT `--label type:Epic`
567
+ #
568
+ # Because a declared type is a CLAIM, and one that costs a leaf nothing: the
569
+ # item it produces still looks buildable, so the exemption would be a free
570
+ # bypass of the readiness requirement for anything willing to mistype itself.
571
+ # That is the shape of this repository's measured case where an allowlist added
572
+ # to harden a guard became the way around it.
573
+ #
574
+ # WHAT IS READ INSTEAD
575
+ #
576
+ # The canonical container declaration the `derived-branch-plan` rule already
577
+ # defines and `lisa-github-write-issue` already stamps, in place of a Target
578
+ # Backend Environment:
579
+ #
580
+ # None — container: state rolls up from children
581
+ #
582
+ # It is a marker with no other meaning, so it is matched wherever the payload
583
+ # reaches — exactly the asymmetry `HUMAN_GATE_MARKER` gets, and for the same
584
+ # reason. Reading the declaration the writer already emits is also what keeps
585
+ # the guard and the skill from drifting apart a second time: there is one
586
+ # string, defined in one rule, and both read it.
587
+ #
588
+ # It is self-limiting in a way a type label is not. Writing it costs the item
589
+ # its Target Backend Environment and its Branch Plan — the two fields a leaf
590
+ # needs to be built at all — so the declaration is only useful to something
591
+ # that actually is a container. A leaf that writes it does not smuggle a
592
+ # buildable item past the gate; it files an unbuildable one, in the lane
593
+ # `leaf-only-lifecycle` and `lisa-repair-intake` already sweep.
594
+ #
595
+ # The WHOLE canonical value, `None` included, not just the distinctive tail.
596
+ # Dropping the leading half would match ordinary prose about how a container's
597
+ # state behaves — including a bug report ABOUT this guard — and a declaration
598
+ # that ordinary prose satisfies is not a declaration. Whitespace-tolerant
599
+ # because a body file may wrap the line; dash-tolerant because an author
600
+ # retyping it by hand will not always reach for the em dash.
601
+ CONTAINER_DECLARATION = re.compile(
602
+ r"None\s*[\u2014\u2013-]\s*container:\s*state\s+rolls\s+up\s+from\s+children",
603
+ re.IGNORECASE,
604
+ )
605
+ # The checkable half of "is it really a container". Provenance is unobservable
606
+ # here, as the header notes, but a CONTRADICTION is not: an item cannot be a
607
+ # container and a by-design leaf at once. Quoted from `leaf-only-lifecycle` —
608
+ # "the by-design leaf types (Bug, Task, Sub-task, Improvement)". Story and
609
+ # Spike are deliberately ABSENT: the same rule's childless-parent exception
610
+ # makes them leaf-or-container depending on child work, and a decomposition
611
+ # legitimately files a parent Story before the children that make it one.
612
+ BY_DESIGN_LEAF_TYPES = {"bug", "task", "sub-task", "subtask", "improvement"}
613
+ # Where a declared type can land on the created item. The label spellings are
614
+ # GitHub's `type:<value>` convention; `--type` and its aliases are the JIRA and
615
+ # Linear clients' spelling. Read with the same positional discipline as the
616
+ # build-ready role — a type named in a title or body is not a type applied.
617
+ TYPE_FLAGS = {
618
+ "--label", "--labels", "--add-label",
619
+ "--type", "--issue-type", "--issuetype",
620
+ }
531
621
  POST_METHOD_FLAGS = {"-X", "--request", "--method"}
532
622
  POST_PAYLOAD_FLAGS = {
533
623
  "-d", "--data", "--data-raw", "--data-binary",
@@ -1433,6 +1523,65 @@ def declares_readiness(raw_args, roles, extra="", state_role_ok=False):
1433
1523
  return False
1434
1524
 
1435
1525
 
1526
+ def declares_leaf_type(args):
1527
+ """Whether the filing declares a by-design leaf type as a flag value.
1528
+
1529
+ Read only from positions that reach the created item, and never from a
1530
+ title or body — the same discipline the build-ready role is held to, for
1531
+ the same reason: a type named in prose is not a type applied.
1532
+
1533
+ Args:
1534
+ args: The creating command's arguments, already scoped to the flags.
1535
+
1536
+ Returns:
1537
+ True when a Bug / Task / Sub-task / Improvement is declared.
1538
+ """
1539
+ for raw in flag_values(args, TYPE_FLAGS):
1540
+ for part in raw.split(","):
1541
+ value = part.strip().strip("'\"").lower()
1542
+ if value.startswith("type:"):
1543
+ value = value[len("type:") :]
1544
+ if value in BY_DESIGN_LEAF_TYPES:
1545
+ return True
1546
+ return False
1547
+
1548
+
1549
+ def declares_container(raw_args, texts=()):
1550
+ """Whether the create declares the item a container.
1551
+
1552
+ A container is neither build-ready nor human-gated by construction, so this
1553
+ is the third accepted declaration rather than a third way to satisfy the
1554
+ first two. See the CONTAINER_DECLARATION block for why it reads the
1555
+ canonical declaration and not a `type:Epic` claim.
1556
+
1557
+ Args:
1558
+ raw_args: The creating command's arguments.
1559
+ texts: Payload or script text this command submits or runs.
1560
+
1561
+ Returns:
1562
+ True when the container declaration is present and uncontradicted.
1563
+ """
1564
+ args = before_end_of_options(raw_args)
1565
+ # The contradiction check comes FIRST, so a filing that declares itself
1566
+ # both a container and a by-design leaf is refused rather than allowed on
1567
+ # the strength of the half that suits it.
1568
+ if declares_leaf_type(args):
1569
+ return False
1570
+ if CONTAINER_DECLARATION.search(" ".join(args)):
1571
+ return True
1572
+ for path in body_file_paths(args):
1573
+ try:
1574
+ with open(path, encoding="utf-8", errors="replace") as handle:
1575
+ if CONTAINER_DECLARATION.search(handle.read()):
1576
+ return True
1577
+ except OSError:
1578
+ continue
1579
+ for text in texts:
1580
+ if text and CONTAINER_DECLARATION.search(text):
1581
+ return True
1582
+ return False
1583
+
1584
+
1436
1585
  def flag_values(args, names):
1437
1586
  """Every value assigned to one of the named flags.
1438
1587
 
@@ -1880,6 +2029,15 @@ def scan(text, depth, from_file=False):
1880
2029
  state_role_ok = tracker in STATE_ROLE_TRACKERS and target is None
1881
2030
  if declares_readiness(args, roles, submitted, state_role_ok):
1882
2031
  continue
2032
+ # The container arm. Checked after readiness, so it adds a
2033
+ # compliant filing where none existed and takes none away where one
2034
+ # already did. `text` joins the scan only for a creation found
2035
+ # INSIDE a file, where the declaration lives in the same file as
2036
+ # the create it answers for.
2037
+ if declares_container(
2038
+ args, (submitted, text) if from_file else (submitted,)
2039
+ ):
2040
+ continue
1883
2041
  # `LIFECYCLE_ROLE=ready curl …` puts the declaration BEFORE the
1884
2042
  # client, which is where an inline assignment has to go, so the
1885
2043
  # role is read from the whole segment rather than from the client's
@@ -27,7 +27,7 @@ So the same vendor-neutral `lisa-tracker-write` call produced a different lifecy
27
27
 
28
28
  The safe direction is the implicit one. A ticket that reaches a build queue by accident is worse than one that waits, because the queue is what agents act on autonomously.
29
29
 
30
- ## Every filing declares one of two things
30
+ ## Every filing declares one of two things — three, counting containers
31
31
 
32
32
  A writer accepts a filing only when it carries **one** of:
33
33
 
@@ -43,6 +43,24 @@ Filed, not ready, and no `human_gate` is the **incomplete handoff** case: the wr
43
43
 
44
44
  `build_ready` remains strictly subordinate to `leaf-only-lifecycle`: a container is never build-ready regardless of what a caller passes, and a container needs no `human_gate` because its state rolls up from its children rather than being claimed.
45
45
 
46
+ ### The container declaration, on the direct-CLI path
47
+
48
+ That exemption used to live only in writer prose, which meant the guard below — the thing that actually runs — had no arm for it. A container could satisfy the guard only by declaring a build-ready role `leaf-only-lifecycle` forbids on it, or a human gate it does not have. **A guard that can only be obeyed by writing something untrue is worse than one that simply refuses**, because the lie is indistinguishable from a correct filing afterwards, and both available lies corrupt data another control reads: a stamped role puts a container in the lane build-intake claims from, and a fabricated hold marker is a state change with no inverse.
49
+
50
+ So a container filed directly through a tracker CLI declares a **third** thing — the canonical container line the `derived-branch-plan` rule already defines and every writer already stamps in place of a Target Backend Environment:
51
+
52
+ ```text
53
+ ## Target Backend Environment
54
+
55
+ None — container: state rolls up from children
56
+ ```
57
+
58
+ **Not `--label type:Epic`.** A declared type is a claim, and one that costs a leaf nothing: the item it produces still looks buildable, so keying the exemption on it would hand any item willing to mistype itself a free bypass of the readiness requirement — the shape of the measured case where an allowlist added to harden a guard became the way around it. The container line is self-limiting instead: writing it costs the item the Target Backend Environment and Branch Plan a leaf needs to be built at all, so it is only useful to something that actually is a container.
59
+
60
+ The declaration is refused alongside a **by-design leaf type** declared as a flag value (`type:Bug` / `type:Task` / `type:Sub-task` / `type:Improvement`, or the `--type` spelling on a state-based tracker) — an item cannot be a container and a leaf at once. Story and Spike are deliberately not in that set: `leaf-only-lifecycle`'s childless-parent exception makes them leaf-or-container depending on child work, and a decomposition legitimately files a parent Story before the children that make it one.
61
+
62
+ One string, defined in one rule, read by the guard and stamped by the writers — which is what keeps this exemption from drifting back into prose only.
63
+
46
64
  ## Complete defects found during other work are filed build-ready
47
65
 
48
66
  Any defect discovered while doing something else, which is complete enough to build, is filed through `lisa-track` / `lisa-tracker-write` with **explicit `build_ready: true`**. It must be claimable by build-intake on the next cycle with no human flipping status. "Complete enough to build" is the `work-item-definition-of-ready` bar — reproduction, observed-versus-expected, and Gherkin acceptance criteria — not a placeholder to be fleshed out later.
@@ -65,7 +83,7 @@ The rule shipped as prose first, and prose did not hold. A conformance audit of
65
83
 
66
84
  The control is the PreToolUse Bash guard `block-direct-issue-create.sh`, shipped to every agent variant (Claude, Codex, Cursor, Copilot, agy, OpenCode) and to the host `scripts/lisa-hooks/` fallback. It refuses a direct tracker-creation command — `gh issue create`, `gh api` POST to an issues endpoint, `gh api graphql createIssue`, `linear issue create`, `jira issue create`, `acli … create`, and equivalent `curl` posts — when the command carries **no readiness declaration**.
67
85
 
68
- What it checks is the artifact, not the caller. A Bash-level hook cannot observe call provenance: any provenance signal is settable by the very agent being governed, so a guard built on one would be theatre. So the guard asks whether the command about to run produces a correctly declared item — the configured build-ready role is applied, or a `[lisa-human-gate]` marker is present (inline, or in the `--body-file` the create is about to submit). That is precisely the machine-checkable content of this rule, and it lets the three writers through by construction, because they always stamp one.
86
+ What it checks is the artifact, not the caller. A Bash-level hook cannot observe call provenance: any provenance signal is settable by the very agent being governed, so a guard built on one would be theatre. So the guard asks whether the command about to run produces a correctly declared item — the configured build-ready role is applied, or a `[lisa-human-gate]` marker is present, or the container declaration is (each read inline, or in the `--body-file` the create is about to submit). That is precisely the machine-checkable content of this rule, and it lets the three writers through by construction, because they always stamp one.
69
87
 
70
88
  Two ways out, both deliberate:
71
89
 
@@ -11,10 +11,11 @@ Filing a work item without the ready role is an **incomplete handoff** — build
11
11
 
12
12
  **An omitted `build_ready` is NOT build-ready on JIRA, GitHub, and Linear alike.** Ready is an explicit claim, never an accident of which tracker a project configured. This is a **breaking change** for GitHub and Linear callers, which previously treated omission as ready; `lisa-github-validate-issue` F4's compensating omitted → `true` normalization is removed with it.
13
13
 
14
- ## Every filing declares one of two things
14
+ ## Every filing declares one of three things
15
15
 
16
16
  - **`build_ready: true`** — complete enough to build; enters the ready lane for auto-pickup. Required for any complete defect found during other work, so it is claimable next cycle with no human flipping status.
17
17
  - **`human_gate: "<why a human must judge this first>"`** — deliberately held outside the queue. The writer stamps a visible line plus `<!-- [lisa-human-gate] reason=<short-slug> -->` so the hold is auditable.
18
+ - **The container declaration** — `None — container: state rolls up from children`, the canonical line `derived-branch-plan` defines and every writer stamps in place of a Target Backend Environment. A container is never build-ready (`leaf-only-lifecycle`) and needs no gate, so it declares the third thing instead. Deliberately **not** keyed on `--label type:Epic`: a declared type is a claim that costs a leaf nothing and leaves it looking buildable, where the container line costs the item the environment and Branch Plan a leaf needs to be built. It is refused alongside a by-design leaf type (`type:Bug` / `type:Task` / `type:Sub-task` / `type:Improvement`) — an item cannot be a container and a leaf at once.
18
19
 
19
20
  Filed, not ready, and no `human_gate` is the **incomplete handoff** case — writers reject it and name both ways to resolve it. `build_ready: false` with no reason is the same omission with a value attached. `build_ready` stays subordinate to `leaf-only-lifecycle`: a container is never build-ready and needs no gate.
20
21
 
@@ -26,7 +27,7 @@ Filed, not ready, and no `human_gate` is the **incomplete handoff** case — wri
26
27
 
27
28
  ## This rule is enforced
28
29
 
29
- The PreToolUse guard `block-direct-issue-create.sh` refuses a direct tracker-creation command (`gh issue create`, `gh api` POST to an issues endpoint, `linear`/`jira`/`acli` creates, equivalent `curl` posts) that carries **no readiness declaration** — no configured build-ready role and no `[lisa-human-gate]` marker. It shipped because this rule as prose did not bind: 13 of 13 issues filed during the session that merged it bypassed it, several by the agent that wrote it, while the one obligation backed by a git hook was honored 50 of 50 times. It stands down where no tracker is configured, and for an operator who set `LISA_ALLOW_DIRECT_ISSUE_CREATE` **in the ambient environment** — never from an inline assignment on the refused command.
30
+ The PreToolUse guard `block-direct-issue-create.sh` refuses a direct tracker-creation command (`gh issue create`, `gh api` POST to an issues endpoint, `linear`/`jira`/`acli` creates, equivalent `curl` posts) that carries **none of the three declarations** — no configured build-ready role, no `[lisa-human-gate]` marker, and no container declaration. It shipped because this rule as prose did not bind: 13 of 13 issues filed during the session that merged it bypassed it, several by the agent that wrote it, while the one obligation backed by a git hook was honored 50 of 50 times. It stands down where no tracker is configured, and for an operator who set `LISA_ALLOW_DIRECT_ISSUE_CREATE` **in the ambient environment** — never from an inline assignment on the refused command.
30
31
 
31
32
  **The role is the TARGET repository's, not the caller's.** A creation addressed at another repository — `--repo` / `-R`, or a `repos/<org>/<repo>/issues` endpoint — is judged against that repository's build-ready role: `hardening.upstreamReadyRole` (default `status:ready`) when the target is `hardening.upstreamRepo`, and the stock `status:ready` otherwise. A creation naming no repository, or naming the project's own `github.org`/`github.repo`, is judged against the project's own role exactly as before. A declaration is required either way; only its vocabulary changes.
32
33
 
@@ -289,6 +289,22 @@ proceed — the marker still holds. Never file the label *instead of* the marker
289
289
 
290
290
  If a leaf arrives with `build_ready` omitted or `false` **and** no `human_gate`, do not create it: report the incomplete handoff and name both ways to resolve it (`build_ready: true`, or a `human_gate` reason). Containers are exempt — their state rolls up from children, so they need neither.
291
291
 
292
+ **The container exemption is executable, not prose.** It used to live only in the sentence above,
293
+ which meant the `block-direct-issue-create.sh` guard — the control that actually runs on the CLI
294
+ path — had no arm for it, and a container could satisfy that guard only by declaring a build-ready
295
+ role `leaf-only-lifecycle` forbids on it or a human gate it does not have. A container now declares
296
+ a **third** thing, and this skill already writes it: the canonical Target Backend Environment value
297
+ `None — container: state rolls up from children`, defined once by `derived-branch-plan`. The guard
298
+ reads that one string, so there is nothing for the skill and the guard to disagree about.
299
+
300
+ Two consequences for a container written from here. Render the Target Backend Environment section
301
+ with that exact value — an absent section reads as *underivable*, not exempt, and leaves the guard
302
+ nothing to read. And do not put a **by-design leaf type** (`type:Bug` / `type:Task` /
303
+ `type:Sub-task` / `type:Improvement`) on a filing carrying that declaration: an item cannot be a
304
+ container and a leaf at once, and the guard refuses the combination. `type:Story` and `type:Spike`
305
+ are not in that set, because `leaf-only-lifecycle`'s childless-parent exception makes them
306
+ leaf-or-container depending on child work.
307
+
292
308
  ## Phase 5.5 — Validate (Pre-write Gate)
293
309
 
294
310
  Before any write, invoke `lisa-github-validate-issue` with the full proposed spec assembled from Phases 2 / 3 / 4 / 5. Pass it as a YAML block per the `lisa-github-validate-issue` schema, including `runtime_behavior_change`, `authenticated_surface`, and `artifacts_attached` flags so the right gates run.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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.45.3",
3
+ "version": "4.45.4",
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"