@codyswann/lisa 4.33.6 → 4.33.9
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/README.md +22 -0
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +44 -1
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +361 -10
- package/dist/cli/apply.d.ts.map +1 -1
- package/dist/cli/apply.js +8 -1
- package/dist/cli/apply.js.map +1 -1
- package/dist/configs/vitest/base.d.ts +76 -11
- package/dist/configs/vitest/base.d.ts.map +1 -1
- package/dist/configs/vitest/base.js +121 -14
- package/dist/configs/vitest/base.js.map +1 -1
- package/dist/core/apply-receipt.d.ts +18 -0
- package/dist/core/apply-receipt.d.ts.map +1 -1
- package/dist/core/apply-receipt.js +7 -5
- package/dist/core/apply-receipt.js.map +1 -1
- package/dist/core/config.d.ts +34 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +12 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa.d.ts +75 -0
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +125 -6
- package/dist/core/lisa.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 +31 -13
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/core/workflow-deletion-ownership.d.ts +69 -0
- package/dist/core/workflow-deletion-ownership.d.ts.map +1 -0
- package/dist/core/workflow-deletion-ownership.js +156 -0
- package/dist/core/workflow-deletion-ownership.js.map +1 -0
- package/dist/opencode/plugin-templates/parity-safety-net.sh +44 -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 +17 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-intake-explain/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +32 -20
- package/plugins/lisa/.codex-plugin/skills/lisa-ticket-triage/SKILL.md +1 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +44 -1
- package/plugins/lisa/rules/eager/blocker-containment.md +44 -0
- package/plugins/lisa/rules/reference/blocker-containment.md +92 -0
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-intake-explain/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +32 -20
- package/plugins/lisa/skills/lisa-ticket-triage/SKILL.md +1 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +44 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-intake-explain/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +32 -20
- package/plugins/lisa-agy/skills/lisa-ticket-triage/SKILL.md +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/parity-safety-net.sh +44 -1
- package/plugins/lisa-copilot/rules/eager/blocker-containment.md +44 -0
- package/plugins/lisa-copilot/rules/reference/blocker-containment.md +92 -0
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-intake-explain/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +32 -20
- package/plugins/lisa-copilot/skills/lisa-ticket-triage/SKILL.md +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +44 -1
- package/plugins/lisa-cursor/rules/blocker-containment-reference.mdc +97 -0
- package/plugins/lisa-cursor/rules/blocker-containment.mdc +49 -0
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-intake-explain/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +32 -20
- package/plugins/lisa-cursor/skills/lisa-ticket-triage/SKILL.md +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/parity-safety-net.sh +44 -1
- package/plugins/src/base/rules/eager/blocker-containment.md +44 -0
- package/plugins/src/base/rules/reference/blocker-containment.md +92 -0
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-intake-explain/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +32 -20
- package/plugins/src/base/skills/lisa-ticket-triage/SKILL.md +1 -1
- package/scripts/two-channel-couplings.json +1 -1
- package/typescript/copy-overwrite/.github/GITHUB_ACTIONS.md +2 -2
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +96 -12
- package/typescript/copy-overwrite/scripts/reconcile-nightly-e2e-tracking.mjs +3 -2
- package/typescript/deletions.json +8 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.33.
|
|
3
|
+
"version": "4.33.9",
|
|
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.33.
|
|
3
|
+
"version": "4.33.9",
|
|
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.33.
|
|
3
|
+
"version": "4.33.9",
|
|
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.33.
|
|
3
|
+
"version": "4.33.9",
|
|
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.33.
|
|
3
|
+
"version": "4.33.9",
|
|
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"
|
|
@@ -1687,7 +1687,50 @@ fi
|
|
|
1687
1687
|
|
|
1688
1688
|
# 13. Dropping or truncating a database / schema / table (Lisa-only guard —
|
|
1689
1689
|
# upstream has no SQL protection; keep).
|
|
1690
|
-
|
|
1690
|
+
#
|
|
1691
|
+
# The two arms used to be asymmetric, and only the lopsided one produced false
|
|
1692
|
+
# positives (issue #3530). The `drop` arm requires a following keyword —
|
|
1693
|
+
# `drop database|schema|table` — so `drop the ball` never matched it. The
|
|
1694
|
+
# `truncate` arm made that keyword OPTIONAL:
|
|
1695
|
+
#
|
|
1696
|
+
# truncate[[:space:]]+(table[[:space:]]+)?[[:alnum:]_."`]+
|
|
1697
|
+
#
|
|
1698
|
+
# which reduces to "`truncate`, whitespace, any identifier-ish word", so
|
|
1699
|
+
# `truncate the board`, `truncate results` and `truncate output` all matched.
|
|
1700
|
+
# A session was refused for a bug-report TITLE describing a connection that
|
|
1701
|
+
# truncates results — and a title has no escape hatch, because the remedy this
|
|
1702
|
+
# hook prints at the wall (write the text to a file, pass it BY PATH) covers a
|
|
1703
|
+
# body, while the title flag of an issue-filing CLI is necessarily inline. The
|
|
1704
|
+
# only exit left was to reword, which is what trains people to route around a
|
|
1705
|
+
# guard.
|
|
1706
|
+
#
|
|
1707
|
+
# The fix keeps the coverage and drops the English, in three arms.
|
|
1708
|
+
#
|
|
1709
|
+
# ARM 1 — `drop`, byte-identical to before. It was already correct, and a
|
|
1710
|
+
# rewrite that unified the arms could only make it permissive.
|
|
1711
|
+
readonly SQL_DROP='\bdrop[[:space:]]+(database|schema|table)\b'
|
|
1712
|
+
# ARM 2 — `truncate` carrying its own evidence that it is a statement rather
|
|
1713
|
+
# than a verb: the TABLE keyword (what the drop arm demands), a
|
|
1714
|
+
# schema-qualified name, a quoted identifier, a trailing `;`, or one of the
|
|
1715
|
+
# tail keywords that only ever follow a TRUNCATE. English has none of these.
|
|
1716
|
+
readonly SQL_TRUNCATE_TAIL='(cascade|restrict|restart[[:space:]]+identity|continue[[:space:]]+identity)([^[:alnum:]_]|$)'
|
|
1717
|
+
readonly SQL_TRUNCATE_EVIDENCE='\btruncate[[:space:]]+(table[[:space:]]+[[:alnum:]_."`]+|(only[[:space:]]+)?([[:alnum:]_]+\.[[:alnum:]_."`]+|["`][[:alnum:]_. ]+["`]|[[:alnum:]_]+[[:space:]]*(;|'"$SQL_TRUNCATE_TAIL"')))'
|
|
1718
|
+
# ARM 3 — the permissive-dialect `TRUNCATE <name>` with none of that evidence.
|
|
1719
|
+
# Deleting this shape is what a lazy fix does: it passes every prose scenario
|
|
1720
|
+
# while silently removing the coverage a regression fixture already asserts
|
|
1721
|
+
# (`mysql -e 'TRUNCATE sessions'`). So it is recognized by CONTEXT instead —
|
|
1722
|
+
# the same command must also invoke a command-line database client. Prose in a
|
|
1723
|
+
# tracker filing or a commit message invokes none, which is exactly the
|
|
1724
|
+
# difference the text scan could not see on its own.
|
|
1725
|
+
readonly SQL_CLIENT='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(psql|pgcli|mysql|mysqladmin|mysqlsh|mariadb|mycli|sqlite3|sqlcmd|cqlsh|clickhouse-client|cockroach|mongosh|duckdb|usql)([[:space:]]|$)'
|
|
1726
|
+
readonly SQL_TRUNCATE_BARE='\btruncate[[:space:]]+(only[[:space:]]+)?[[:alnum:]_."`]'
|
|
1727
|
+
# Residual, accepted and stated: a bare `TRUNCATE <name>` with no terminator,
|
|
1728
|
+
# no qualification and no recognized client in the same command goes unmatched
|
|
1729
|
+
# — the same trade the `drop` arm has always made for `drop the ball`. No DROP
|
|
1730
|
+
# shape is weakened, and the guard still fails in the safe direction.
|
|
1731
|
+
if matches "$SQL_DROP" \
|
|
1732
|
+
|| matches "$SQL_TRUNCATE_EVIDENCE" \
|
|
1733
|
+
|| { matches "$SQL_CLIENT" && matches "$SQL_TRUNCATE_BARE"; }; then
|
|
1691
1734
|
block "destructive SQL (DROP/TRUNCATE) detected"
|
|
1692
1735
|
fi
|
|
1693
1736
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Blocker Containment (load-bearing)
|
|
2
|
+
|
|
3
|
+
**An `is blocked by` dependency is cleared by one fact only: the blocker's code is present on the branch the dependent will be built from.** A lifecycle status name — `done`, `on-stg`, `on-dev`, `code-review`, or any configured equivalent — is never that fact. It is a label that correlates with the fact in a single-trunk workflow and comes apart from it in a promotion workflow.
|
|
4
|
+
|
|
5
|
+
**One vendor-neutral contract, cited by** `lisa-repair-intake` (Class A dependency clearing), `lisa-intake-explain` (dependency hold gate), and `lisa-ticket-triage` (Phase 1.5 open-blocker gate) — the `leaf-only-lifecycle` / `repo-scope-split` precedent: one shared slug, never divergent per-consumer prose.
|
|
6
|
+
|
|
7
|
+
## Why a status name is the wrong test
|
|
8
|
+
|
|
9
|
+
Where promotion runs `dev → staging → main`, `main` is the **trailing** branch. A blocker at `On Stg` is merged to `staging` and absent from `main`, so a dependent that builds from `main` does not have it. A blocker in `Code Review` is merged to nothing at all. The two failures are symmetric and both are live in the tree:
|
|
10
|
+
|
|
11
|
+
- Test too **loosely** (any env-staged `done` clears) and a dependent is dispatched into work whose prerequisite is not on its branch. Nothing goes red; the item moves to `ready`, which is indistinguishable from a repair that worked.
|
|
12
|
+
- Test too **strictly** (only the production terminal clears) and every dependent strands behind a blocker that is already merged where it matters.
|
|
13
|
+
|
|
14
|
+
Containment is the one predicate that gets both right, because it asks the question the status name was standing in for.
|
|
15
|
+
|
|
16
|
+
## The predicate
|
|
17
|
+
|
|
18
|
+
1. **Resolve the dependent's base branch.** Take the dependent's `## Target Backend Environment` and map that exact key forward through `.lisa.config.json` `deploy.branches`, per `derived-branch-plan` and `config-resolution`. **Never read the rendered `Branch Plan` section as input** — `derived-branch-plan` forbids it; a surface needing the base branch derives it again.
|
|
19
|
+
2. **Resolve the blocker's merge commit** — the commit that landed the blocker's PR.
|
|
20
|
+
3. **Test ancestry on the remote:** is that commit an ancestor of the resolved base branch (`git merge-base --is-ancestor <sha> origin/<base>`)?
|
|
21
|
+
4. **Cleared** iff the answer is yes. Otherwise **still blocking**.
|
|
22
|
+
|
|
23
|
+
## Fail closed
|
|
24
|
+
|
|
25
|
+
**Never fail open on an unresolvable ancestry check.** Any step that cannot be positively resolved leaves the dependency **blocking**, and the run record says which step failed:
|
|
26
|
+
|
|
27
|
+
- no linked PR, or a PR that is not merged;
|
|
28
|
+
- the blocker is inaccessible (deleted, cross-org, permission denied) — containment across repositories is not defined;
|
|
29
|
+
- the dependent's environment is not derivable, maps non-uniquely, or maps to a branch absent from the remote;
|
|
30
|
+
- the ancestry query itself fails.
|
|
31
|
+
|
|
32
|
+
A false negative (left blocked) costs a cycle and is visible. A false positive (dispatched into a missing prerequisite) is invisible and surfaces days later as work begun before its dependency existed. Prefer the visible failure.
|
|
33
|
+
|
|
34
|
+
## The one carve-out: a blocker with no code
|
|
35
|
+
|
|
36
|
+
A dependency need not deliver code. A blocker **closed as completed** that positively declares no code deliverable — `Target Backend Environment: None — no runtime behavior change` (doc-only / config-only / type-only), no linked PR, no merge commit — is **cleared**, because there is no artifact whose location could matter.
|
|
37
|
+
|
|
38
|
+
This requires a **positive determination**, never an absence. "I could not find a PR" is fail-closed under the rule above; "the item declares it ships no code" is this carve-out. Collapsing the two turns the carve-out into a universal bypass.
|
|
39
|
+
|
|
40
|
+
## Human override
|
|
41
|
+
|
|
42
|
+
A human statement on the item that the dependency is satisfied clears it, and outranks a failed containment check. Record it as the reason in the run record. This is the escape hatch for cross-repo and untracked dependencies, and it is the only one. An automation may never synthesize one on its own behalf.
|
|
43
|
+
|
|
44
|
+
Full contract (resolution steps, per-vendor merge-commit bindings, the fail-closed reason-key table, the no-code carve-out's three conditions, and what this rule deliberately does not do): [reference/blocker-containment.md](../reference/blocker-containment.md).
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Blocker Containment
|
|
2
|
+
|
|
3
|
+
An `is blocked by` link is a **development** dependency. It asks one question: *does the dependent have the blocker's code on the branch it will be built from?* This rule makes that the test, replacing the lifecycle-status-name checks that three Lisa surfaces had each grown independently.
|
|
4
|
+
|
|
5
|
+
It is a **single vendor-neutral contract** consumed by `lisa-repair-intake` (Class A dependency clearing), `lisa-intake-explain` (dependency hold gate), and `lisa-ticket-triage` (Phase 1.5 open-blocker gate). Each cites this slug rather than re-implementing the test, exactly as they cite `leaf-only-lifecycle` and `repo-scope-split`. That is what keeps the three from drifting apart again.
|
|
6
|
+
|
|
7
|
+
## The failure this replaces
|
|
8
|
+
|
|
9
|
+
Before this rule, all three surfaces tested a **status name** and each picked a different threshold, so the same dependency could be simultaneously cleared by one surface and held by another.
|
|
10
|
+
|
|
11
|
+
`lisa-repair-intake` tested too loosely — any env-staged `done` role cleared the dependency, on the stated reasoning that `On Dev`, `On Stg` and production `Done` all mean *"the blocker's code is merged and deployed to ≥1 environment"*, and that an `is blocked by` link *"is satisfied once the blocker's code is in trunk."* The first clause names the right idea. The second substitutes a weaker one for it: in a `dev → staging → main` promotion workflow, "deployed to ≥1 environment" and "in trunk" are different facts. `main` is the trailing branch, so a blocker at `On Stg` is on `staging` and absent from `main`. A dependent building from `main` was dispatched without it. That surface also cleared on a post-build `review` role, where the code is merged to no branch at all.
|
|
12
|
+
|
|
13
|
+
`lisa-ticket-triage` tested too strictly — a blocker not at the production terminal raised an automatic `BLOCKED` verdict, stranding dependents behind work already merged where they needed it.
|
|
14
|
+
|
|
15
|
+
Both are the same defect. **A lifecycle status name is never the test** — not `status:done`, not `status:on-stg`, not `status:on-dev`, and least of all `status:code-review`, where the code is merged to no branch at all. The status name is a **proxy** for branch containment that holds in a single-trunk workflow and breaks in a promotion workflow, and the two surfaces broke in opposite directions because they picked opposite ends of the same bad proxy. Correcting either one alone by moving its threshold re-opens the other's bug — which is why this is one predicate rather than three tuned lists.
|
|
16
|
+
|
|
17
|
+
Notably, the correct predicate was already in the tree. `lisa-ticket-triage`'s own `DUPLICATE_ALREADY_FIXED` bullet, four lines below its defective gate, already required *"empirical evidence that the canonical fix is present on the relevant base branch. Never emit this verdict from a name/label match alone."* This rule generalizes that sentence to the gate above it.
|
|
18
|
+
|
|
19
|
+
## The predicate
|
|
20
|
+
|
|
21
|
+
### 1. Resolve the dependent's base branch
|
|
22
|
+
|
|
23
|
+
Take the **dependent's** `## Target Backend Environment`, resolve it under the existing grammar (`config-resolution`, `pre-flight-autofill` — human-confirmed wins, then validated `Inferred:`, then `Assumption:`), and map that exact key forward through `.lisa.config.json` `deploy.branches`.
|
|
24
|
+
|
|
25
|
+
**Never read the rendered `Branch Plan` section as input.** `derived-branch-plan` is explicit that the rendered branches exist for humans and are recomputed for machines: *"The rendered branches are never read as input by any flow, at any phase — a surface that needs the base branch derives it again from the environment."* Reading the rendered section would make a hand-edited ticket able to redirect the containment test.
|
|
26
|
+
|
|
27
|
+
The key must map uniquely, and the mapped branch must exist on the remote. Neither condition is assumed.
|
|
28
|
+
|
|
29
|
+
### 2. Resolve the blocker's merge commit
|
|
30
|
+
|
|
31
|
+
The commit that landed the blocker's PR, obtained through the vendor access layer (`integration-access-layer`), never a direct vendor API call:
|
|
32
|
+
|
|
33
|
+
- **GitHub** — the merge commit of the PR linked from the blocker issue.
|
|
34
|
+
- **JIRA** — the merge commit of the PR on the blocker's development panel / linked PR records.
|
|
35
|
+
- **Linear** — the merge commit of the PR attached to the blocker Issue.
|
|
36
|
+
|
|
37
|
+
An unmerged PR has no merge commit and is not a candidate. A blocker with several merged PRs requires **all** of them contained.
|
|
38
|
+
|
|
39
|
+
### 3. Test ancestry against the remote
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git fetch origin <base> --quiet
|
|
43
|
+
git merge-base --is-ancestor <blocker-merge-sha> origin/<base>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Exit `0` → contained. Exit `1` → not contained. Any other exit → not computable, which is **not** the same as not contained; both block, but the run record distinguishes them so an operator can tell a genuine dependency from a broken lookup.
|
|
47
|
+
|
|
48
|
+
### 4. Verdict
|
|
49
|
+
|
|
50
|
+
**Cleared** iff every resolved merge commit is an ancestor of the resolved base branch. Otherwise **still blocking**.
|
|
51
|
+
|
|
52
|
+
## Fail closed
|
|
53
|
+
|
|
54
|
+
**Never fail open on an unresolvable ancestry check.** Each of these leaves the dependency blocking, and each is named distinctly in the run record:
|
|
55
|
+
|
|
56
|
+
| Condition | Verdict | Run-record reason |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Blocker has no linked PR | still blocking | `no-pr` |
|
|
59
|
+
| Linked PR is open or closed unmerged | still blocking | `pr-not-merged` |
|
|
60
|
+
| Blocker inaccessible (deleted, cross-org, permission denied) | still blocking | `blocker-inaccessible` |
|
|
61
|
+
| Dependent's environment not derivable or non-unique | still blocking | `base-branch-underivable` |
|
|
62
|
+
| Mapped branch absent from the remote | still blocking | `base-branch-missing` |
|
|
63
|
+
| Ancestry query errored | still blocking | `containment-not-computable` |
|
|
64
|
+
| Merge commit resolved, not an ancestor | still blocking | `not-contained` |
|
|
65
|
+
|
|
66
|
+
The asymmetry is deliberate and is the same one the consuming surfaces already state: a false negative (left blocked) costs one cycle and an operator can see it sitting there. A false positive (re-dispatched into a prerequisite that is not on the branch) is **invisible** — the item moves to `ready`, which looks exactly like a repair that worked, and the failure surfaces days later as work that began before its dependency existed. Prefer the visible failure. When in doubt, stay blocked.
|
|
67
|
+
|
|
68
|
+
## The one carve-out: a blocker that ships no code
|
|
69
|
+
|
|
70
|
+
Not every dependency delivers code. A decision, a doc change, or a config-only item can legitimately block work and will never have a merge commit on any branch. Requiring containment of a non-existent artifact would strand those dependents permanently — the same bug, in a new place.
|
|
71
|
+
|
|
72
|
+
A blocker is cleared under this carve-out when **all** of the following hold:
|
|
73
|
+
|
|
74
|
+
1. it is **closed as completed** (not `not planned` — see `rejection-detection`, where *not planned* is a durable decline);
|
|
75
|
+
2. it **positively declares no code deliverable** — `## Target Backend Environment` reading `None — no runtime behavior change: doc-only` (or `config-only` / `type-only`), per the grammar `derived-branch-plan` already defines;
|
|
76
|
+
3. it has **no linked merged PR**. If it has one, the normal containment test applies regardless of what the section declares — the code is the authority over the prose.
|
|
77
|
+
|
|
78
|
+
This is a **positive determination, never an absence.** "I could not find a PR" is `no-pr` in the fail-closed table above. "The item declares that it ships no code" is this carve-out. Collapsing the two converts a narrow carve-out into a universal bypass, and it is the single most likely way for this rule to be implemented wrongly.
|
|
79
|
+
|
|
80
|
+
An Epic or container blocker declaring `None — container: state rolls up from children` is **not** covered: resolve containment against its children, or treat it as `blocker-inaccessible` if the children cannot be enumerated.
|
|
81
|
+
|
|
82
|
+
## Human override
|
|
83
|
+
|
|
84
|
+
An explicit human statement on the dependent — in the body or in a comment — that the dependency is satisfied clears it, and outranks a failed containment check. The run record names the override and the comment it came from.
|
|
85
|
+
|
|
86
|
+
This is the sanctioned escape hatch for the cases the predicate genuinely cannot reach: a cross-repository blocker (two repositories have no shared ancestry, so containment is undefined), a dependency on work tracked outside any connected system, or a vendor whose PR linkage is unreadable. It is the **only** escape hatch. An automation may never synthesize one on its own behalf.
|
|
87
|
+
|
|
88
|
+
## What this rule does not do
|
|
89
|
+
|
|
90
|
+
It does not decide **when** a dependency edge should exist, dissolve an edge once satisfied, or govern what a surface does after clearing — re-dispatch, re-validation, and lifecycle transitions remain the consuming skill's business. It governs exactly one question: given an edge, is it satisfied.
|
|
91
|
+
|
|
92
|
+
It also carries no memory of its own prior verdicts. A surface that re-runs this predicate after a human reversed a transition it produced will reach the same answer from the same facts; making that reversal durable is a separate concern belonging to the consuming skill's loop-prevention contract, not to this predicate.
|
|
@@ -652,7 +652,7 @@ result: **27 of 27** open items carrying the claimed role in this repository had
|
|
|
652
652
|
a merged pull request — the claimed lane reported 27 things in flight when the
|
|
653
653
|
real number was one.
|
|
654
654
|
|
|
655
|
-
|
|
655
|
+
Four properties make the command safe to run unconditionally:
|
|
656
656
|
|
|
657
657
|
1. **The terminal role is RESOLVED, never assumed.** `lifecycleContract` reads
|
|
658
658
|
`github.labels.build.done` (or the Jira/Linear equivalent), which is
|
|
@@ -666,7 +666,22 @@ Three properties make the command safe to run unconditionally:
|
|
|
666
666
|
a real one afterwards. Cross-repository references do not count: a downstream
|
|
667
667
|
consumer's PR mentioning an upstream issue is not evidence the upstream issue
|
|
668
668
|
shipped.
|
|
669
|
-
3. **It
|
|
669
|
+
3. **It weighs the merged pull request's BASE BRANCH**, the same way step (c)
|
|
670
|
+
above does, and for the same reason. A merged pull request is not evidence
|
|
671
|
+
on its own: the writer resolves `<baseRefName>` through `.lisa.config.json`
|
|
672
|
+
`deploy.branches` and applies only the role that base earned. A merge into
|
|
673
|
+
the production deploy branch is terminal and closes the item; a merge into a
|
|
674
|
+
branch mapping to a lower env records that env's role and leaves the item
|
|
675
|
+
open; a merge into a branch the project does not deploy at all — an
|
|
676
|
+
integration/stack branch — records nothing, closes nothing, and reports the
|
|
677
|
+
base it observed. **Measured**: on a stacked-PR queue the previous writer
|
|
678
|
+
stamped the production terminal role and closed every ticket in the batch at
|
|
679
|
+
the moment its PR landed on the stacking branch, and one of those false
|
|
680
|
+
completions then made push gate 1 refuse a follow-up commit on the same
|
|
681
|
+
ticket, because there was no longer an open work item to bind. When the
|
|
682
|
+
command reports `work-item NOT completed`, that is the correct answer for a
|
|
683
|
+
stacked merge — re-run it after the stack reaches the deploy branch.
|
|
684
|
+
4. **It is idempotent**, so re-running after a retry converges rather than
|
|
670
685
|
accumulating.
|
|
671
686
|
|
|
672
687
|
It also removes the claimed role rather than only adding the terminal one.
|
|
@@ -539,7 +539,7 @@ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Buil
|
|
|
539
539
|
|
|
540
540
|
The ordering is the point. Announcing before verifying leaves a public "Build complete" on an item whose lifecycle state was never confirmed, and a reader has no way to tell that claim apart from a verified one — the same indistinguishability the readback exists to prevent. On a failed readback, comment the Error instead.
|
|
541
541
|
|
|
542
|
-
`node scripts/lisa-work-item.mjs complete --ref <org>/<repo>#<number>` performs this whole terminal sequence — merged-PR evidence check, full role reconciliation, close, independent readback — in one step, and is the preferred path whenever `$DONE` is the terminal value.
|
|
542
|
+
`node scripts/lisa-work-item.mjs complete --ref <org>/<repo>#<number>` performs this whole terminal sequence — merged-PR evidence check, full role reconciliation, close, independent readback — in one step, and is the preferred path whenever `$DONE` is the terminal value. It also decides for itself whether `$DONE` is terminal here: it resolves each merged PR's base branch through `.lisa.config.json` `deploy.branches`, so a merge into an intermediate env branch records that env's role WITHOUT closing the issue, and a merge into a branch the project does not deploy records nothing and says which base it saw. The manual commands above remain available for an intermediate env transition performed without merge evidence.
|
|
543
543
|
|
|
544
544
|
For any non-Success outcome, do NOT transition. The issue sits in `$CLAIMED` (or wherever the lifecycle left it) — humans take it from there.
|
|
545
545
|
|