@codyswann/lisa 4.33.5 → 4.33.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.
Files changed (106) hide show
  1. package/dist/cli/apply.d.ts.map +1 -1
  2. package/dist/cli/apply.js +8 -1
  3. package/dist/cli/apply.js.map +1 -1
  4. package/dist/core/apply-receipt.d.ts +18 -0
  5. package/dist/core/apply-receipt.d.ts.map +1 -1
  6. package/dist/core/apply-receipt.js +7 -5
  7. package/dist/core/apply-receipt.js.map +1 -1
  8. package/dist/core/config.d.ts +34 -0
  9. package/dist/core/config.d.ts.map +1 -1
  10. package/dist/core/config.js.map +1 -1
  11. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  12. package/dist/core/lisa-owned-hash-ledger.js +8 -0
  13. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  14. package/dist/core/lisa.d.ts +75 -0
  15. package/dist/core/lisa.d.ts.map +1 -1
  16. package/dist/core/lisa.js +125 -6
  17. package/dist/core/lisa.js.map +1 -1
  18. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  19. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  20. package/dist/core/upstream-evidence-manifest.js +22 -7
  21. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  22. package/dist/core/workflow-deletion-ownership.d.ts +69 -0
  23. package/dist/core/workflow-deletion-ownership.d.ts.map +1 -0
  24. package/dist/core/workflow-deletion-ownership.js +156 -0
  25. package/dist/core/workflow-deletion-ownership.js.map +1 -0
  26. package/package.json +4 -4
  27. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  29. package/plugins/lisa/.codex-plugin/skills/lisa-intake-explain/SKILL.md +1 -1
  30. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +32 -20
  31. package/plugins/lisa/.codex-plugin/skills/lisa-ticket-triage/SKILL.md +1 -1
  32. package/plugins/lisa/rules/eager/blocker-containment.md +44 -0
  33. package/plugins/lisa/rules/reference/blocker-containment.md +92 -0
  34. package/plugins/lisa/skills/lisa-intake-explain/SKILL.md +1 -1
  35. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +32 -20
  36. package/plugins/lisa/skills/lisa-ticket-triage/SKILL.md +1 -1
  37. package/plugins/lisa-agy/plugin.json +1 -1
  38. package/plugins/lisa-agy/skills/lisa-intake-explain/SKILL.md +1 -1
  39. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +32 -20
  40. package/plugins/lisa-agy/skills/lisa-ticket-triage/SKILL.md +1 -1
  41. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  44. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-copilot/rules/eager/blocker-containment.md +44 -0
  48. package/plugins/lisa-copilot/rules/reference/blocker-containment.md +92 -0
  49. package/plugins/lisa-copilot/skills/lisa-intake-explain/SKILL.md +1 -1
  50. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +32 -20
  51. package/plugins/lisa-copilot/skills/lisa-ticket-triage/SKILL.md +1 -1
  52. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-cursor/rules/blocker-containment-reference.mdc +97 -0
  54. package/plugins/lisa-cursor/rules/blocker-containment.mdc +49 -0
  55. package/plugins/lisa-cursor/skills/lisa-intake-explain/SKILL.md +1 -1
  56. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +32 -20
  57. package/plugins/lisa-cursor/skills/lisa-ticket-triage/SKILL.md +1 -1
  58. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-expo-agy/plugin.json +1 -1
  61. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  65. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  66. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  71. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  75. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  76. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  81. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-rails-agy/plugin.json +1 -1
  86. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  91. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  96. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/src/base/rules/eager/blocker-containment.md +44 -0
  99. package/plugins/src/base/rules/reference/blocker-containment.md +92 -0
  100. package/plugins/src/base/skills/lisa-intake-explain/SKILL.md +1 -1
  101. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +32 -20
  102. package/plugins/src/base/skills/lisa-ticket-triage/SKILL.md +1 -1
  103. package/typescript/copy-overwrite/.github/GITHUB_ACTIONS.md +2 -2
  104. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +414 -6
  105. package/typescript/create-only/.github/workflows/review-evidence.yml +78 -3
  106. package/typescript/deletions.json +8 -1
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The sentence a `copy-overwrite` template carries, minus its comment prefix.
3
+ *
4
+ * Matched as a substring rather than a whole line because the prefix differs by
5
+ * file type (`#`, `//`, ` *`) and the wording has been revised once already —
6
+ * the retired header said "changes will be overwritten on the next `lisa` run".
7
+ * Both spellings contain this phrase, so a consumer still holding a
8
+ * years-old Lisa workflow is still correctly attributed.
9
+ */
10
+ export declare const LISA_MANAGED_MARKER = "managed by Lisa";
11
+ /**
12
+ * The word a `create-only` seed header shouts, minus its comment prefix.
13
+ *
14
+ * "YOURS" is the load-bearing token of that contract and the one the header
15
+ * test asserts. A file carrying it came out of Lisa's template tree AND was
16
+ * handed to the consumer, which is exactly the pair of facts that makes it
17
+ * undeletable.
18
+ */
19
+ export declare const LISA_SEEDED_MARKER = "YOURS";
20
+ /** What Lisa can prove about a workflow file it has been asked to delete. */
21
+ export type WorkflowDeletionVerdict =
22
+ /** Header asserts Lisa replaces this file wholesale. Lisa may retire it. */
23
+ {
24
+ readonly kind: "lisa-managed";
25
+ }
26
+ /** Header seeded it as the consumer's. Lisa promised not to touch it. */
27
+ | {
28
+ readonly kind: "host-owned-seed";
29
+ }
30
+ /** No Lisa header at all. Lisa has no record of installing this. */
31
+ | {
32
+ readonly kind: "unattributable";
33
+ };
34
+ /**
35
+ * Whether a deletion target lives under `.github/workflows/`.
36
+ *
37
+ * Only this tree is gated. The manifests name 200-odd other paths whose
38
+ * deletion is unchanged by #3656, and narrowing the guard is deliberate: a
39
+ * workflow's absence is undetectable by construction, which is not true of a
40
+ * missing config file or skill directory.
41
+ * @param relativePath - Repo-relative path a deletions manifest wants removed
42
+ * @returns Whether the ownership gate applies to it
43
+ */
44
+ export declare function isWorkflowDeletionPath(relativePath: string): boolean;
45
+ /**
46
+ * Read the ownership contract a workflow file states about itself.
47
+ *
48
+ * Precedence is seed-before-managed. A `create-only` header mentions
49
+ * `copy-overwrite` in its own second line, so a naive managed-first check would
50
+ * read every seeded workflow as Lisa-managed and delete the very files #3656
51
+ * lost. Ordering it this way means the strongest claim a file can make is the
52
+ * one that keeps it.
53
+ * @param contents - Raw text of the workflow file on disk
54
+ * @returns The verdict its header supports
55
+ */
56
+ export declare function classifyWorkflowForDeletion(contents: string): WorkflowDeletionVerdict;
57
+ /**
58
+ * The operator-readable reason a workflow deletion was refused.
59
+ *
60
+ * Written for whoever is staring at an install log wondering why a file they
61
+ * expected to disappear is still there — so it names the contract, not the
62
+ * implementation, and says what to do about it.
63
+ * @param verdict - The verdict that refused the deletion
64
+ * @returns A sentence fragment naming why the file was kept
65
+ */
66
+ export declare function describeRefusal(verdict: Exclude<WorkflowDeletionVerdict, {
67
+ kind: "lisa-managed";
68
+ }>): string;
69
+ //# sourceMappingURL=workflow-deletion-ownership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-deletion-ownership.d.ts","sourceRoot":"","sources":["../../src/core/workflow-deletion-ownership.ts"],"names":[],"mappings":"AAuEA;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAc1C,6EAA6E;AAC7E,MAAM,MAAM,uBAAuB;AACjC,4EAA4E;AAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE;AACnC,yEAAyE;GACvE;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE;AACtC,oEAAoE;GAClE;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAYxC;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,GACf,uBAAuB,CAQzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,CAAC,uBAAuB,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC,GAClE,MAAM,CAIR"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Whether Lisa may delete a workflow file it finds in a consumer repository.
3
+ *
4
+ * A deletions manifest names DESTINATION PATHS. It cannot name bytes, and until
5
+ * CodySwannGT/lisa#3656 nothing between the manifest and `fse.remove` looked at
6
+ * any. So a manifest entry retiring one of Lisa's own workflows also removed a
7
+ * completely unrelated file a consumer happened to author at the same path —
8
+ * three of them at once, on a routine version bump, with nothing in the install
9
+ * output anyone could read afterwards.
10
+ *
11
+ * The damage shape is what makes path-only deletion unacceptable here rather
12
+ * than merely untidy. **A workflow that no longer exists cannot fail.** Removing
13
+ * one does not redden a check; it removes the check. Two capabilities stopped in
14
+ * that repo — PR branches were no longer auto-updated and required-checks drift
15
+ * was no longer policed — and both losses presented as a quiet, healthy repo.
16
+ * The only reason anyone found out was an unrelated test that happened to read
17
+ * one of the files off disk and got `ENOENT`.
18
+ *
19
+ * ## Ownership is read from the file's own header, and the header already exists
20
+ *
21
+ * Every template Lisa ships carries an ownership header, and which header it
22
+ * carries is enforced repo-wide by `tests/unit/templates/template-ownership-header.test.ts`.
23
+ * The two headers are not decoration — they are opposite contracts, and this
24
+ * module simply honours the one the file on disk is carrying:
25
+ *
26
+ * - **`copy-overwrite`**: "This file is managed by Lisa and IS replaced on each
27
+ * `lisa` run." Lisa asserts ownership of the bytes and has always replaced
28
+ * them wholesale. Retiring such a file is Lisa deleting its own artifact,
29
+ * which is squarely inside the mandate.
30
+ * - **`create-only`**: "Seeded by Lisa on first setup — this file is YOURS.
31
+ * Lisa will not overwrite it." Lisa explicitly DISCLAIMED ownership at seed
32
+ * time. Whatever is in that file now may be entirely the consumer's work, and
33
+ * deleting it is the one operation the header promised would never happen.
34
+ * Overwriting it was already refused; removing it must be too.
35
+ *
36
+ * All three files lost in #3656 were `create-only` seeds. So was every workflow
37
+ * template Lisa currently ships.
38
+ *
39
+ * ## Why a header and not the hash ledger
40
+ *
41
+ * `core/lisa-owned-provenance` answers a neighbouring question — are these bytes
42
+ * some past Lisa release? — from a generated hash ledger, and it would be the
43
+ * stronger signal if it applied. It does not: the generator
44
+ * (`scripts/generate-lisa-owned-hash-ledger.mjs`) enrols only `copy-overwrite`
45
+ * sources under `scripts/`, so `LISA_OWNED_HASH_LEDGER` holds no
46
+ * `.github/workflows/` key at all and every workflow would classify as
47
+ * `unenrolled` — the verdict whose documented meaning is "behave exactly as
48
+ * before", which is the bug. Enrolling workflows would mean walking the history
49
+ * of retired template paths, and it still could not vouch for a `create-only`
50
+ * seed a consumer has since edited. The header answers the question that is
51
+ * actually being asked, for every workflow Lisa has ever shipped, with no
52
+ * generated artifact to keep in step.
53
+ *
54
+ * ## The residual risk, stated
55
+ *
56
+ * A consumer who deletes the `copy-overwrite` header out of a Lisa-managed
57
+ * workflow makes it unattributable, and Lisa will then decline to retire it.
58
+ * That is the safe direction: a stale reusable workflow nobody calls costs
59
+ * nothing, and `lisa doctor` is not made worse by its presence. The unsafe
60
+ * direction — deleting a file Lisa cannot account for — is the one this module
61
+ * closes.
62
+ * @module core/workflow-deletion-ownership
63
+ */
64
+ import * as path from "node:path";
65
+ /**
66
+ * Repo-relative directory GitHub Actions reads workflow definitions from.
67
+ * Deletion of anything inside it is what this module governs.
68
+ */
69
+ const WORKFLOWS_DIR = ".github/workflows";
70
+ /**
71
+ * The sentence a `copy-overwrite` template carries, minus its comment prefix.
72
+ *
73
+ * Matched as a substring rather than a whole line because the prefix differs by
74
+ * file type (`#`, `//`, ` *`) and the wording has been revised once already —
75
+ * the retired header said "changes will be overwritten on the next `lisa` run".
76
+ * Both spellings contain this phrase, so a consumer still holding a
77
+ * years-old Lisa workflow is still correctly attributed.
78
+ */
79
+ export const LISA_MANAGED_MARKER = "managed by Lisa";
80
+ /**
81
+ * The word a `create-only` seed header shouts, minus its comment prefix.
82
+ *
83
+ * "YOURS" is the load-bearing token of that contract and the one the header
84
+ * test asserts. A file carrying it came out of Lisa's template tree AND was
85
+ * handed to the consumer, which is exactly the pair of facts that makes it
86
+ * undeletable.
87
+ */
88
+ export const LISA_SEEDED_MARKER = "YOURS";
89
+ /**
90
+ * How many leading lines of a file count as its ownership header.
91
+ *
92
+ * Bounded on purpose. A whole-file search would let any mention of Lisa
93
+ * anywhere in a consumer's workflow — a step name, a comment about a Lisa
94
+ * command — authorise that file's deletion, which is the false-negative
95
+ * direction that reintroduces #3656. Every shipped template puts its header on
96
+ * the first two lines; four covers a leading `---` document marker or a blank
97
+ * line above it and still stops short of any workflow body.
98
+ */
99
+ const HEADER_LINES = 4;
100
+ /**
101
+ * Normalize a path for comparison: POSIX separators, no leading `./`.
102
+ * @param value - Raw path from a deletions manifest
103
+ * @returns Comparable POSIX-style repo-relative path
104
+ */
105
+ function normalizeRepoPath(value) {
106
+ const posix = value.split(path.sep).join("/");
107
+ return posix.startsWith("./") ? posix.slice(2) : posix;
108
+ }
109
+ /**
110
+ * Whether a deletion target lives under `.github/workflows/`.
111
+ *
112
+ * Only this tree is gated. The manifests name 200-odd other paths whose
113
+ * deletion is unchanged by #3656, and narrowing the guard is deliberate: a
114
+ * workflow's absence is undetectable by construction, which is not true of a
115
+ * missing config file or skill directory.
116
+ * @param relativePath - Repo-relative path a deletions manifest wants removed
117
+ * @returns Whether the ownership gate applies to it
118
+ */
119
+ export function isWorkflowDeletionPath(relativePath) {
120
+ return normalizeRepoPath(relativePath).startsWith(`${WORKFLOWS_DIR}/`);
121
+ }
122
+ /**
123
+ * Read the ownership contract a workflow file states about itself.
124
+ *
125
+ * Precedence is seed-before-managed. A `create-only` header mentions
126
+ * `copy-overwrite` in its own second line, so a naive managed-first check would
127
+ * read every seeded workflow as Lisa-managed and delete the very files #3656
128
+ * lost. Ordering it this way means the strongest claim a file can make is the
129
+ * one that keeps it.
130
+ * @param contents - Raw text of the workflow file on disk
131
+ * @returns The verdict its header supports
132
+ */
133
+ export function classifyWorkflowForDeletion(contents) {
134
+ const header = contents.split("\n").slice(0, HEADER_LINES).join("\n");
135
+ if (header.includes(LISA_SEEDED_MARKER)) {
136
+ return { kind: "host-owned-seed" };
137
+ }
138
+ return header.includes(LISA_MANAGED_MARKER)
139
+ ? { kind: "lisa-managed" }
140
+ : { kind: "unattributable" };
141
+ }
142
+ /**
143
+ * The operator-readable reason a workflow deletion was refused.
144
+ *
145
+ * Written for whoever is staring at an install log wondering why a file they
146
+ * expected to disappear is still there — so it names the contract, not the
147
+ * implementation, and says what to do about it.
148
+ * @param verdict - The verdict that refused the deletion
149
+ * @returns A sentence fragment naming why the file was kept
150
+ */
151
+ export function describeRefusal(verdict) {
152
+ return verdict.kind === "host-owned-seed"
153
+ ? "Lisa seeded this workflow as yours and will not remove it"
154
+ : "Lisa cannot prove it installed this workflow";
155
+ }
156
+ //# sourceMappingURL=workflow-deletion-ownership.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-deletion-ownership.js","sourceRoot":"","sources":["../../src/core/workflow-deletion-ownership.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;GAGG;AACH,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAErD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,YAAY,GAAG,CAAC,CAAC;AAWvB;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,KAAa;IACtC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9C,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACzD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,YAAoB;IACzD,OAAO,iBAAiB,CAAC,YAAY,CAAC,CAAC,UAAU,CAAC,GAAG,aAAa,GAAG,CAAC,CAAC;AACzE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B,CACzC,QAAgB;IAEhB,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACxC,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACzC,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE;QAC1B,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAmE;IAEnE,OAAO,OAAO,CAAC,IAAI,KAAK,iBAAiB;QACvC,CAAC,CAAC,2DAA2D;QAC7D,CAAC,CAAC,8CAA8C,CAAC;AACrD,CAAC"}
package/package.json CHANGED
@@ -165,7 +165,7 @@
165
165
  "zod-validation-error": "^4.0.0"
166
166
  },
167
167
  "name": "@codyswann/lisa",
168
- "version": "4.33.5",
168
+ "version": "4.33.8",
169
169
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
170
170
  "main": "dist/index.js",
171
171
  "exports": {
@@ -313,7 +313,7 @@
313
313
  "test": "tests"
314
314
  },
315
315
  "types": "./dist/index.d.ts",
316
- "lisaReleaseCommit": "093c20ef2f67eadda94e473ffe5ff52cd14e1107",
317
- "gitHead": "093c20ef2f67eadda94e473ffe5ff52cd14e1107",
318
- "lisaReleaseTag": "v4.33.5"
316
+ "lisaReleaseCommit": "463ea1625bf43084967f87bee3daa9ab4225be85",
317
+ "gitHead": "463ea1625bf43084967f87bee3daa9ab4225be85",
318
+ "lisaReleaseTag": "v4.33.8"
319
319
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.5",
3
+ "version": "4.33.8",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.5",
3
+ "version": "4.33.8",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -190,7 +190,7 @@ Apply gate verdicts in the same order as build intake:
190
190
 
191
191
  1. **Repo-scope gate.** A build item carrying `repo:<other>` and not `repo:<current>` is outside this repo's pickup lane. Return `MISCONFIGURED` when repo scope is absent or contradictory enough that the current repo cannot be determined confidently; otherwise explain the repo-scope mismatch and recommend running intake in the target repo or fixing the `repo:<name>` label. For an unlabeled item whose target repo is obvious from the item body, report the inferred repo signal but stay read-only: execution intake would stamp `repo:<name>`, while intake-explain only says it would do so. A multi-repo leaf is not directly buildable; explain that execution intake would split it per `repo-scope-split`, but this read-only diagnosis does not split.
192
192
  2. **Leaf-only gate.** If the item has open child work from native GitHub sub-issues or body parentage, return `NON_LEAF_CONTAINER` and explain that direct build pickup is leaf-only per `leaf-only-lifecycle`. If it has no open children but carries `type:Epic`, also return `NON_LEAF_CONTAINER` because a childless Epic is a pure rollup container that still needs decomposition or reclassification. (A childless `type:Story` or `type:Spike` is a valid leaf per `leaf-only-lifecycle` and is NOT a `NON_LEAF_CONTAINER` — it is directly buildable.) The next action for a childless Epic is decomposition, moving `status:ready` to leaf children, or correcting the issue type. Execution build intake would move such a stale ready container out of the pickup queue; this read-only diagnosis must not perform that repair.
193
- 3. **Dependency hold gate.** If a single-repo leaf for the current repo has explicit blockers, read each blocker. Closed blockers are clear. Open blockers are clear only when they carry a cleared build status such as `status:code-review`, `status:on-dev`, `status:on-stg`, `status:done`, or the configured done-equivalent labels. Open blockers with `status:ready`, `status:in-progress`, no cleared status label, or inaccessible state are active. Return `HELD_BY_BLOCKERS`, list the active blocker refs, and make the next action blocker resolution rather than `/lisa:intake`.
193
+ 3. **Dependency hold gate.** If a single-repo leaf for the current repo has explicit blockers, read each blocker and clear-check it **by containment, per the `blocker-containment` rule** — cite that slug for the full contract rather than restating it. A blocker is clear only when its merged PRs are ancestors of the branch this leaf will be built from (resolved from the leaf's `## Target Backend Environment` through `.lisa.config.json` `deploy.branches`, never from a rendered `Branch Plan`), or when the rule's narrow no-code carve-out or human override applies. **A build status label is never the test** — `status:code-review`, `status:on-dev`, `status:on-stg` and `status:done` are name matches that hold in a single-trunk workflow and come apart in a promotion workflow, where `main` is the trailing branch. Every other state, and every case where containment is not computable, is active: fail closed. Return `HELD_BY_BLOCKERS`, list the active blocker refs with the rule's reason key for each, and make the next action blocker resolution rather than `/lisa:intake`.
194
194
  4. **Ready leaf.** A build item in the configured ready role, scoped to the current repo, with no open child work and no active blockers returns `ELIGIBLE_FOR_INTAKE`. The `Why:` line should say it is a single-repo leaf for the current repo and that leaf-only, repo-scope, and dependency gates all pass.
195
195
 
196
196
  Relevant `Signals:` should include the decisive context, not every field: for example `repo:lisa; type:Sub-task; no open children`, `open children #12/#13`, `repo:api but current repo is web`, or `active blockers CodySwannGT/lisa#123`.
@@ -1005,28 +1005,40 @@ is no normalized `is blocked by` field. Read the bundle, then extract blockers p
1005
1005
  - **Linear**: inspect the native issue **relations** from Linear MCP `get_issue` and select
1006
1006
  blocker relations.
1007
1007
 
1008
- Then classify each blocker:
1009
-
1010
- - **Closed, or shipped to any environment** → **cleared**. A blocker is cleared at **any**
1011
- env-staged `done` role — not only the production terminal. The `done` role is configured
1012
- per-env as a `{dev, staging, production}` map (`config-resolution`), so `On Dev`, `On Stg`,
1013
- **and** production `Done` all mean the blocker's code is merged and deployed to ≥1 environment.
1014
- A post-build `review` role (e.g. `Code Review`) is likewise cleared — the change exists and is
1015
- in flight. An `is blocked by` link is a **development** dependency: it is satisfied once the
1016
- blocker's code is in trunk; it must NOT wait for the blocker to reach production. (This matches
1017
- the intake-path dependency-hold gate in `lisa-intake-explain`, which already treats
1018
- `code-review` / `on-dev` / `on-stg` / `done` as cleared — repair-intake must not be stricter.
1019
- In an env-staged workflow where `Done` means "in production", a strict production-terminal
1020
- check strands every dependent forever behind a blocker that is already merged and sitting at
1021
- `On Stg` / `On Dev`.)
1022
- - **Open** in a pre-merge role (`ready` / `claimed` / unknown — code not yet in trunk) → **still
1023
- blocking**.
1024
- - **Inaccessible** (deleted, cross-org, permission denied) → **still blocking**, unless the item
1025
- body or a newer human comment explicitly states the dependency is resolved.
1008
+ Then classify each blocker **by containment, per the `blocker-containment` rule** — cite that
1009
+ slug for the full contract; do not restate its resolution steps or its fail-closed table here.
1010
+
1011
+ The one question is whether the blocker's code is on the branch **this item** will be built from:
1012
+ resolve this item's base branch from its `## Target Backend Environment` through
1013
+ `.lisa.config.json` `deploy.branches` (`derived-branch-plan`, `config-resolution` — never by
1014
+ reading a rendered `Branch Plan`), resolve the blocker's merge commit through the vendor access
1015
+ layer, and test ancestry against the remote.
1016
+
1017
+ - **Contained** — every merged PR on the blocker is an ancestor of this item's base branch →
1018
+ **cleared**.
1019
+ - **Not contained**, or containment **not computable** for any reason the rule enumerates (no
1020
+ linked PR, PR not merged, blocker inaccessible, base branch underivable or absent from the
1021
+ remote, ancestry query errored) → **still blocking**, with the rule's reason key in the run
1022
+ record.
1023
+ - **Ships no code** — the blocker is closed as completed, positively declares
1024
+ `Target Backend Environment: None — no runtime behavior change`, and has no merged PR →
1025
+ **cleared** under the rule's carve-out. This is a positive determination, never an absence:
1026
+ "no PR found" is `no-pr` and stays blocking.
1027
+ - **Human override** — an explicit human statement on this item that the dependency is satisfied
1028
+ clears it and outranks a failed containment check. Name it in the run record.
1029
+
1030
+ **A lifecycle status name is never the test.** `done`, `on-stg`, `on-dev` and `code-review` are
1031
+ labels that correlate with containment in a single-trunk workflow and come apart from it in a
1032
+ promotion workflow, where `main` is the trailing branch — a blocker at `On Stg` is on `staging`
1033
+ and absent from `main`, and a blocker in `Code Review` is merged to nothing. Containment gets
1034
+ both the loose and the strict failure right at once: a blocker merged into the branch this item
1035
+ builds from clears **immediately**, with no wait for production, so a dependent is never stranded
1036
+ behind work already merged where it needs it.
1026
1037
 
1027
1038
  Only re-dispatch when **every** parsed blocker is cleared. When in doubt, stay blocked — a
1028
- false-negative (left blocked) is cheap; a false-positive (re-dispatched into a real blocker)
1029
- wastes a build cycle.
1039
+ false-negative (left blocked) costs one cycle and is visible; a false-positive is **invisible**,
1040
+ because the item moves to `ready`, which looks exactly like a repair that worked, and the failure
1041
+ surfaces days later as work begun before its dependency existed.
1030
1042
 
1031
1043
  ### Class B — validation / quality-gate self-block
1032
1044
 
@@ -46,7 +46,7 @@ If relevant code IS found, proceed to Phase 2.
46
46
 
47
47
  From the context bundle, evaluate relationships before analyzing this ticket in isolation:
48
48
 
49
- - **Open blockers (`is blocked by`)**: if any blocker is not `Done` or its linked PR is not merged, raise an ambiguity: "Blocker {KEY} is not shipped — work cannot meaningfully start." This is an automatic `BLOCKED` verdict unless the human confirms the blocker state is acceptable.
49
+ - **Open blockers (`is blocked by`)**: clear-check each blocker **by containment, per the `blocker-containment` rule** — cite that slug for the full contract rather than restating it. A blocker is satisfied when its merged PRs are ancestors of the branch this ticket will be built from (resolved from this ticket's `## Target Backend Environment` through `.lisa.config.json` `deploy.branches`, never from a rendered `Branch Plan`), or when the rule's narrow no-code carve-out or human override applies. **`Done` is not the test** — a state name holds in a single-trunk workflow and comes apart in a promotion workflow, and requiring the production terminal strands every dependent behind work already merged where this ticket needs it. This is the same predicate the `DUPLICATE_ALREADY_FIXED` bullet below already requires ("present on the relevant base branch… never from a name/label match alone"), applied here too. For a blocker that is not contained, or where containment is not computable, raise an ambiguity naming the rule's reason key: "Blocker {KEY} is not on {BRANCH} ({REASON}) — work cannot meaningfully start." That is an automatic `BLOCKED` verdict unless the human confirms the blocker state is acceptable.
50
50
  - **Epic siblings in progress**: if a sibling under the same epic is `In Progress` / `In Review` with a different assignee and overlapping scope, raise it as an edge case in Phase 4 ("Duplicate-work risk with {KEY}").
51
51
  - **`duplicates` / `is duplicated by` links**:
52
52
  - If this ticket is a duplicate of an open canonical ticket whose fix is not yet merged into the base branch, verdict is `BLOCKED` with the recommendation to close as duplicate manually rather than implement.
@@ -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.
@@ -190,7 +190,7 @@ Apply gate verdicts in the same order as build intake:
190
190
 
191
191
  1. **Repo-scope gate.** A build item carrying `repo:<other>` and not `repo:<current>` is outside this repo's pickup lane. Return `MISCONFIGURED` when repo scope is absent or contradictory enough that the current repo cannot be determined confidently; otherwise explain the repo-scope mismatch and recommend running intake in the target repo or fixing the `repo:<name>` label. For an unlabeled item whose target repo is obvious from the item body, report the inferred repo signal but stay read-only: execution intake would stamp `repo:<name>`, while intake-explain only says it would do so. A multi-repo leaf is not directly buildable; explain that execution intake would split it per `repo-scope-split`, but this read-only diagnosis does not split.
192
192
  2. **Leaf-only gate.** If the item has open child work from native GitHub sub-issues or body parentage, return `NON_LEAF_CONTAINER` and explain that direct build pickup is leaf-only per `leaf-only-lifecycle`. If it has no open children but carries `type:Epic`, also return `NON_LEAF_CONTAINER` because a childless Epic is a pure rollup container that still needs decomposition or reclassification. (A childless `type:Story` or `type:Spike` is a valid leaf per `leaf-only-lifecycle` and is NOT a `NON_LEAF_CONTAINER` — it is directly buildable.) The next action for a childless Epic is decomposition, moving `status:ready` to leaf children, or correcting the issue type. Execution build intake would move such a stale ready container out of the pickup queue; this read-only diagnosis must not perform that repair.
193
- 3. **Dependency hold gate.** If a single-repo leaf for the current repo has explicit blockers, read each blocker. Closed blockers are clear. Open blockers are clear only when they carry a cleared build status such as `status:code-review`, `status:on-dev`, `status:on-stg`, `status:done`, or the configured done-equivalent labels. Open blockers with `status:ready`, `status:in-progress`, no cleared status label, or inaccessible state are active. Return `HELD_BY_BLOCKERS`, list the active blocker refs, and make the next action blocker resolution rather than `/lisa:intake`.
193
+ 3. **Dependency hold gate.** If a single-repo leaf for the current repo has explicit blockers, read each blocker and clear-check it **by containment, per the `blocker-containment` rule** — cite that slug for the full contract rather than restating it. A blocker is clear only when its merged PRs are ancestors of the branch this leaf will be built from (resolved from the leaf's `## Target Backend Environment` through `.lisa.config.json` `deploy.branches`, never from a rendered `Branch Plan`), or when the rule's narrow no-code carve-out or human override applies. **A build status label is never the test** — `status:code-review`, `status:on-dev`, `status:on-stg` and `status:done` are name matches that hold in a single-trunk workflow and come apart in a promotion workflow, where `main` is the trailing branch. Every other state, and every case where containment is not computable, is active: fail closed. Return `HELD_BY_BLOCKERS`, list the active blocker refs with the rule's reason key for each, and make the next action blocker resolution rather than `/lisa:intake`.
194
194
  4. **Ready leaf.** A build item in the configured ready role, scoped to the current repo, with no open child work and no active blockers returns `ELIGIBLE_FOR_INTAKE`. The `Why:` line should say it is a single-repo leaf for the current repo and that leaf-only, repo-scope, and dependency gates all pass.
195
195
 
196
196
  Relevant `Signals:` should include the decisive context, not every field: for example `repo:lisa; type:Sub-task; no open children`, `open children #12/#13`, `repo:api but current repo is web`, or `active blockers CodySwannGT/lisa#123`.
@@ -1005,28 +1005,40 @@ is no normalized `is blocked by` field. Read the bundle, then extract blockers p
1005
1005
  - **Linear**: inspect the native issue **relations** from Linear MCP `get_issue` and select
1006
1006
  blocker relations.
1007
1007
 
1008
- Then classify each blocker:
1009
-
1010
- - **Closed, or shipped to any environment** → **cleared**. A blocker is cleared at **any**
1011
- env-staged `done` role — not only the production terminal. The `done` role is configured
1012
- per-env as a `{dev, staging, production}` map (`config-resolution`), so `On Dev`, `On Stg`,
1013
- **and** production `Done` all mean the blocker's code is merged and deployed to ≥1 environment.
1014
- A post-build `review` role (e.g. `Code Review`) is likewise cleared — the change exists and is
1015
- in flight. An `is blocked by` link is a **development** dependency: it is satisfied once the
1016
- blocker's code is in trunk; it must NOT wait for the blocker to reach production. (This matches
1017
- the intake-path dependency-hold gate in `lisa-intake-explain`, which already treats
1018
- `code-review` / `on-dev` / `on-stg` / `done` as cleared — repair-intake must not be stricter.
1019
- In an env-staged workflow where `Done` means "in production", a strict production-terminal
1020
- check strands every dependent forever behind a blocker that is already merged and sitting at
1021
- `On Stg` / `On Dev`.)
1022
- - **Open** in a pre-merge role (`ready` / `claimed` / unknown — code not yet in trunk) → **still
1023
- blocking**.
1024
- - **Inaccessible** (deleted, cross-org, permission denied) → **still blocking**, unless the item
1025
- body or a newer human comment explicitly states the dependency is resolved.
1008
+ Then classify each blocker **by containment, per the `blocker-containment` rule** — cite that
1009
+ slug for the full contract; do not restate its resolution steps or its fail-closed table here.
1010
+
1011
+ The one question is whether the blocker's code is on the branch **this item** will be built from:
1012
+ resolve this item's base branch from its `## Target Backend Environment` through
1013
+ `.lisa.config.json` `deploy.branches` (`derived-branch-plan`, `config-resolution` — never by
1014
+ reading a rendered `Branch Plan`), resolve the blocker's merge commit through the vendor access
1015
+ layer, and test ancestry against the remote.
1016
+
1017
+ - **Contained** — every merged PR on the blocker is an ancestor of this item's base branch →
1018
+ **cleared**.
1019
+ - **Not contained**, or containment **not computable** for any reason the rule enumerates (no
1020
+ linked PR, PR not merged, blocker inaccessible, base branch underivable or absent from the
1021
+ remote, ancestry query errored) → **still blocking**, with the rule's reason key in the run
1022
+ record.
1023
+ - **Ships no code** — the blocker is closed as completed, positively declares
1024
+ `Target Backend Environment: None — no runtime behavior change`, and has no merged PR →
1025
+ **cleared** under the rule's carve-out. This is a positive determination, never an absence:
1026
+ "no PR found" is `no-pr` and stays blocking.
1027
+ - **Human override** — an explicit human statement on this item that the dependency is satisfied
1028
+ clears it and outranks a failed containment check. Name it in the run record.
1029
+
1030
+ **A lifecycle status name is never the test.** `done`, `on-stg`, `on-dev` and `code-review` are
1031
+ labels that correlate with containment in a single-trunk workflow and come apart from it in a
1032
+ promotion workflow, where `main` is the trailing branch — a blocker at `On Stg` is on `staging`
1033
+ and absent from `main`, and a blocker in `Code Review` is merged to nothing. Containment gets
1034
+ both the loose and the strict failure right at once: a blocker merged into the branch this item
1035
+ builds from clears **immediately**, with no wait for production, so a dependent is never stranded
1036
+ behind work already merged where it needs it.
1026
1037
 
1027
1038
  Only re-dispatch when **every** parsed blocker is cleared. When in doubt, stay blocked — a
1028
- false-negative (left blocked) is cheap; a false-positive (re-dispatched into a real blocker)
1029
- wastes a build cycle.
1039
+ false-negative (left blocked) costs one cycle and is visible; a false-positive is **invisible**,
1040
+ because the item moves to `ready`, which looks exactly like a repair that worked, and the failure
1041
+ surfaces days later as work begun before its dependency existed.
1030
1042
 
1031
1043
  ### Class B — validation / quality-gate self-block
1032
1044
 
@@ -46,7 +46,7 @@ If relevant code IS found, proceed to Phase 2.
46
46
 
47
47
  From the context bundle, evaluate relationships before analyzing this ticket in isolation:
48
48
 
49
- - **Open blockers (`is blocked by`)**: if any blocker is not `Done` or its linked PR is not merged, raise an ambiguity: "Blocker {KEY} is not shipped — work cannot meaningfully start." This is an automatic `BLOCKED` verdict unless the human confirms the blocker state is acceptable.
49
+ - **Open blockers (`is blocked by`)**: clear-check each blocker **by containment, per the `blocker-containment` rule** — cite that slug for the full contract rather than restating it. A blocker is satisfied when its merged PRs are ancestors of the branch this ticket will be built from (resolved from this ticket's `## Target Backend Environment` through `.lisa.config.json` `deploy.branches`, never from a rendered `Branch Plan`), or when the rule's narrow no-code carve-out or human override applies. **`Done` is not the test** — a state name holds in a single-trunk workflow and comes apart in a promotion workflow, and requiring the production terminal strands every dependent behind work already merged where this ticket needs it. This is the same predicate the `DUPLICATE_ALREADY_FIXED` bullet below already requires ("present on the relevant base branch… never from a name/label match alone"), applied here too. For a blocker that is not contained, or where containment is not computable, raise an ambiguity naming the rule's reason key: "Blocker {KEY} is not on {BRANCH} ({REASON}) — work cannot meaningfully start." That is an automatic `BLOCKED` verdict unless the human confirms the blocker state is acceptable.
50
50
  - **Epic siblings in progress**: if a sibling under the same epic is `In Progress` / `In Review` with a different assignee and overlapping scope, raise it as an edge case in Phase 4 ("Duplicate-work risk with {KEY}").
51
51
  - **`duplicates` / `is duplicated by` links**:
52
52
  - If this ticket is a duplicate of an open canonical ticket whose fix is not yet merged into the base branch, verdict is `BLOCKED` with the recommendation to close as duplicate manually rather than implement.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.5",
3
+ "version": "4.33.8",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"