@codyswann/lisa 4.74.5 → 4.74.6

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 (60) hide show
  1. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  2. package/dist/core/upstream-evidence-manifest.js +2 -2
  3. package/package.json +4 -4
  4. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  5. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  6. package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/SKILL.md +1 -0
  7. package/plugins/lisa/skills/lisa-setup-github-repo/SKILL.md +1 -0
  8. package/plugins/lisa-agy/plugin.json +1 -1
  9. package/plugins/lisa-agy/skills/lisa-setup-github-repo/SKILL.md +1 -0
  10. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  12. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  13. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-copilot/skills/lisa-setup-github-repo/SKILL.md +1 -0
  17. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cursor/skills/lisa-setup-github-repo/SKILL.md +1 -0
  19. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-expo-agy/plugin.json +1 -1
  22. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  27. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  32. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  37. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  42. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-rails-agy/plugin.json +1 -1
  47. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  52. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  57. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/src/base/skills/lisa-setup-github-repo/SKILL.md +1 -0
  60. package/scripts/lisa-github-rulesets.sh +219 -0
@@ -41,9 +41,9 @@ export const NIGHTLY_E2E_GUARD_BEHAVIOR_CERTIFICATES = Object.freeze({
41
41
  }),
42
42
  d1b26caeaf65950e3b60ff41964d234c032c50470072e7643310b1f6722e34e6: Object.freeze({
43
43
  contractVersion: "1.9.0",
44
- packageVersions: Object.freeze(["4.74.5"]),
44
+ packageVersions: Object.freeze(["4.74.6"]),
45
45
  provenances: Object.freeze([
46
- "workspace package @codyswann/lisa@4.74.5 (typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs)",
46
+ "workspace package @codyswann/lisa@4.74.6 (typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs)",
47
47
  ]),
48
48
  }),
49
49
  dfd2fef680de5ef03e63075e8a61c62ca4d95cdfdff6201939b79c748d3397f6: Object.freeze({
@@ -815,7 +815,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
815
815
  "plugins/src/base/skills/lisa-setup-automations/SKILL.md": "520dbf5c1735c038ef6607eafefb6914e4e24a434c6abb2b057c6065efbf6bb6",
816
816
  "plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs": "c13e287c8beb6f847b03c93298b8a3fb211e65ce897e63692933377f8c4303f1",
817
817
  "plugins/src/base/skills/lisa-setup-confluence/SKILL.md": "e92d762dbdaeae671c3e52dfe8e10d40a10b606b5e050589181a3898649faedc",
818
- "plugins/src/base/skills/lisa-setup-github-repo/SKILL.md": "91302b0cd254e6402b1a70b37794960575f7113d27376f30e267eb8ac7799149",
818
+ "plugins/src/base/skills/lisa-setup-github-repo/SKILL.md": "bc9232d0609a27c66a1267221f6da33b50850145532da760f5501d37addd2cb0",
819
819
  "plugins/src/base/skills/lisa-setup-github/SKILL.md": "75435e81130abd21c678d302b7a220da95a19868d122ca93168278628b200666",
820
820
  "plugins/src/base/skills/lisa-setup-jira/SKILL.md": "261acaeb2923cc9238f468dcf986a0e1323a67e1e51d175b2c1b700dacab2fb8",
821
821
  "plugins/src/base/skills/lisa-setup-kane/SKILL.md": "fe50024a4f896af01716f2435f57227b1f8de61803e80bf3e3c3247ec179b103",
@@ -1327,7 +1327,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
1327
1327
  "scripts/lisa-github-environments.sh": "0a76e92f108519abaf3e29991299ec3b0db20ea533e69e6d2a53d802dba9c370",
1328
1328
  "scripts/lisa-github-repo-settings.sh": "4afbccdfb08c62ba13afee42ec8561a28facf87c61f6f4b80000115328364d54",
1329
1329
  "scripts/lisa-github-repo-setup.sh": "4717873e339b3e615d0bf22b4cb290b3575df5aba8c3639dc0d3bebc23abf651",
1330
- "scripts/lisa-github-rulesets.sh": "1818492ed1d5489030c959438bd1ab34379c261ba398ebeae1b851c1ab357a09",
1330
+ "scripts/lisa-github-rulesets.sh": "183e3f80b09c3189c131e5b31b21d9349c17d1eded5d42742f49e91f058c75de",
1331
1331
  "scripts/lisa-mutation.mjs": "74603e61f493d700318feb862f61ee1653430700b5edf232e70241ccdedac587",
1332
1332
  "scripts/lisa-remote-env/materialized-env-authority.mjs": "2844475d7b407c4dccce4e0f500a3aaf5412f9f315c9173dd0ca22dd24497049",
1333
1333
  "scripts/lisa-remote-env/session-start.sh": "2b40873f39b38e00b50285ca2c4c705ea018285fa457c3e1a985adbeaad6c26b",
package/package.json CHANGED
@@ -189,7 +189,7 @@
189
189
  "qs": "^6.16.0"
190
190
  },
191
191
  "name": "@codyswann/lisa",
192
- "version": "4.74.5",
192
+ "version": "4.74.6",
193
193
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
194
194
  "main": "dist/index.js",
195
195
  "exports": {
@@ -345,7 +345,7 @@
345
345
  "test": "tests"
346
346
  },
347
347
  "types": "./dist/index.d.ts",
348
- "lisaReleaseCommit": "ef8f7f2dc99790e757388c1836738c538a5aacef",
349
- "gitHead": "ef8f7f2dc99790e757388c1836738c538a5aacef",
350
- "lisaReleaseTag": "v4.74.5"
348
+ "lisaReleaseCommit": "00b41544bab8ef052550f94becac99518ccecb32",
349
+ "gitHead": "00b41544bab8ef052550f94becac99518ccecb32",
350
+ "lisaReleaseTag": "v4.74.6"
351
351
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -32,6 +32,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
32
32
  - Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
33
33
  - **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
34
34
  - **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
35
+ - **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
35
36
  3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
36
37
  4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
37
38
  ```json
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
30
30
  - Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
31
31
  - **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
32
32
  - **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
33
+ - **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
33
34
  3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
34
35
  4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
35
36
  ```json
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
30
30
  - Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
31
31
  - **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
32
32
  - **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
33
+ - **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
33
34
  3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
34
35
  4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
35
36
  ```json
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
30
30
  - Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
31
31
  - **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
32
32
  - **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
33
+ - **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
33
34
  3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
34
35
  4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
35
36
  ```json
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
30
30
  - Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
31
31
  - **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
32
32
  - **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
33
+ - **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
33
34
  3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
34
35
  4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
35
36
  ```json
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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.74.5",
3
+ "version": "4.74.6",
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-wiki",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.74.5",
3
+ "version": "4.74.6",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
30
30
  - Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
31
31
  - **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
32
32
  - **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
33
+ - **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
33
34
  3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
34
35
  4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
35
36
  ```json
@@ -724,6 +724,209 @@ report_removed_contexts() {
724
724
  done < <(ruleset_removed_contexts "$live" "$payload")
725
725
  }
726
726
 
727
+ ##############################################################################
728
+ # Bypass Actors
729
+ ##############################################################################
730
+
731
+ # Bypass actors follow the same contract as required checks (#4297).
732
+ #
733
+ # The applier used to send each template's `bypass_actors` as the whole list,
734
+ # and GitHub REPLACES the list on update. So a bypass a consumer added on
735
+ # purpose — measured: an admin RepositoryRole on `nightly e2e health` — was
736
+ # deleted by every apply, reported as a routine "Applied", and no config key
737
+ # could keep it. Losing a bypass is not a tightening anyone asked for either:
738
+ # it can strand the only actor allowed to land an emergency fix.
739
+ #
740
+ # So, exactly like the required list:
741
+ # - by default the LIVE bypass list is preserved and the template's entries
742
+ # are added to it;
743
+ # - a ruleset whose bypass list is DECLARED gets precisely that list, which
744
+ # is the only way to remove an actor;
745
+ # - every actor added or removed, and every bypass_mode changed, is printed
746
+ # by name, on a dry run too; an undeclared live entry keeps its own mode.
747
+ #
748
+ # Declared per ruleset under `github.rulesets.bypassActors`:
749
+ # { "github": { "rulesets": { "bypassActors": {
750
+ # "nightly e2e health": [
751
+ # { "actor_type": "DeployKey", "actor_id": null, "bypass_mode": "always" }
752
+ # ] } } } }
753
+ #
754
+ # The generated `base` ruleset already has a declaration surface —
755
+ # `policy.ruleset.bypass_actors`, which lisa-ruleset-payload.mjs builds it
756
+ # from — so that is what makes base declarative. A second key for the same
757
+ # field would recreate the two-writers problem the generator exists to end.
758
+
759
+ # The declared bypass list for one ruleset, or "null" when none is declared.
760
+ config_bypass_actors() {
761
+ local project_path="$1"
762
+ local ruleset_name="$2"
763
+ local config="$project_path/.lisa.config.json"
764
+
765
+ if [[ ! -f "$config" ]]; then
766
+ echo "null"
767
+ return 0
768
+ fi
769
+
770
+ local filter='.github.rulesets.bypassActors[$name] // null'
771
+ if [[ "$ruleset_name" == "base" ]]; then
772
+ if jq -e '.github.rulesets.bypassActors.base != null' "$config" &> /dev/null; then
773
+ log_warning "github.rulesets.bypassActors.base is ignored — declare base's bypass list as policy.ruleset.bypass_actors" >&2
774
+ fi
775
+ filter='.policy.ruleset.bypass_actors // null'
776
+ fi
777
+
778
+ # An omitted bypass_mode is GitHub's default, `always`, and an omitted
779
+ # actor_id is null (DeployKey). Filled in here so a declaration that leaves
780
+ # them out compares equal to what GitHub stores, rather than reporting a
781
+ # change that never happens.
782
+ local declared
783
+ if ! declared=$(jq -c --arg name "$ruleset_name" \
784
+ "($filter) | if type == \"array\" then map(. + {actor_id: (.actor_id // null), bypass_mode: (.bypass_mode // \"always\")}) else null end" \
785
+ "$config" 2>/dev/null); then
786
+ log_warning ".lisa.config.json could not be parsed — ignoring github.rulesets overrides" >&2
787
+ echo "null"
788
+ return 0
789
+ fi
790
+ echo "$declared"
791
+ }
792
+
793
+ # Print why this ruleset's bypass declaration is unusable, or nothing when it
794
+ # is absent or well-formed. A malformed declaration must stop the ruleset: read
795
+ # as "nothing declared" it would quietly PRESERVE the actors the operator tried
796
+ # to remove, and the run would still report success.
797
+ bypass_declaration_error() {
798
+ local project_path="$1"
799
+ local ruleset_name="$2"
800
+ local config="$project_path/.lisa.config.json"
801
+
802
+ [[ -f "$config" ]] || return 0
803
+
804
+ local key="github.rulesets.bypassActors[\"$ruleset_name\"]"
805
+ local filter='.github.rulesets.bypassActors[$name]?'
806
+ if [[ "$ruleset_name" == "base" ]]; then
807
+ key="policy.ruleset.bypass_actors"
808
+ filter='.policy.ruleset.bypass_actors?'
809
+ fi
810
+
811
+ jq -r --arg name "$ruleset_name" --arg key "$key" "
812
+ ($filter) as \$declared
813
+ | if \$declared == null then empty
814
+ elif (\$declared | type) != \"array\" then
815
+ \"\(\$key) must be a list of bypass actors, got \(\$declared | type)\"
816
+ elif any(\$declared[]; type != \"object\" or (.actor_type | type) != \"string\") then
817
+ \"\(\$key) has an entry without a string actor_type\"
818
+ elif any(\$declared[]; .actor_id != null and ((.actor_id | type) != \"number\" or .actor_id != (.actor_id | floor))) then
819
+ \"\(\$key) has an actor_id that is neither an integer nor null\"
820
+ elif any(\$declared[]; .bypass_mode as \$mode | \$mode != null and ([\"always\", \"pull_request\", \"exempt\"] | index(\$mode)) == null) then
821
+ \"\(\$key) has a bypass_mode other than always, pull_request or exempt\"
822
+ else empty end" "$config" 2>/dev/null \
823
+ || echo "$key could not be validated — refusing to apply a declaration nobody checked"
824
+ }
825
+
826
+ # Replace a template's bypass list with the declared one, when there is one.
827
+ # `base` is skipped: its payload is already built from its declaration.
828
+ apply_config_bypass_actors() {
829
+ local json="$1"
830
+ local project_path="$2"
831
+ local ruleset_name="$3"
832
+
833
+ local declared
834
+ declared=$(config_bypass_actors "$project_path" "$ruleset_name")
835
+ if [[ "$declared" == "null" || "$ruleset_name" == "base" ]]; then
836
+ echo "$json"
837
+ return 0
838
+ fi
839
+ echo "$json" | jq --argjson declared "$declared" '.bypass_actors = $declared'
840
+ }
841
+
842
+ # Union the live bypass list back into the payload. Live entries are kept
843
+ # verbatim and in live order, so a ruleset that needs no change compares equal
844
+ # and nothing is sent; template-only actors are appended. A live entry the
845
+ # template also names keeps its LIVE bypass_mode: an operator who narrowed the
846
+ # admin bypass from `always` to `pull_request` made the same deliberate choice
847
+ # as one who added it, and taking the template's mode would widen it back
848
+ # without a word. Changing a mode, like removing an actor, needs a declaration.
849
+ preserve_live_bypass_actors() {
850
+ local live="$1"
851
+ local json="$2"
852
+
853
+ if [[ -z "$live" ]] || ! echo "$live" | jq -e 'has("bypass_actors")' &> /dev/null; then
854
+ echo "$json"
855
+ return 0
856
+ fi
857
+
858
+ echo "$json" | jq --argjson live "$live" '
859
+ def actor_key: [.actor_type, .actor_id];
860
+ (.bypass_actors // []) as $want
861
+ | ($live.bypass_actors // []) as $have
862
+ | .bypass_actors = (
863
+ $have
864
+ + [ $want[]
865
+ | . as $entry
866
+ | select([ $have[] | actor_key ] | index([$entry | actor_key]) | not) ]
867
+ )'
868
+ }
869
+
870
+ # One line per bypass actor in $1's list that $2's list lacks, keyed by
871
+ # actor type and id. Formatted `<type> [<id>] (<bypass_mode>)`.
872
+ bypass_actors_missing_from() {
873
+ local from="$1"
874
+ local against="$2"
875
+
876
+ jq -r -n --argjson from "${from:-null}" --argjson against "${against:-null}" '
877
+ def actor_key: [.actor_type, .actor_id];
878
+ def actors: if . == null then [] else (.bypass_actors // []) end;
879
+ ($against | actors | map(actor_key)) as $keys
880
+ | ($from | actors)[]
881
+ | select(actor_key as $k | ($keys | index([$k])) | not)
882
+ | "\(.actor_type)\(if .actor_id == null then "" else " \(.actor_id)" end) (\(.bypass_mode))"'
883
+ }
884
+
885
+ # One line per actor present in both lists whose bypass_mode differs,
886
+ # formatted `<type> [<id>] (<live mode> -> <payload mode>)`.
887
+ bypass_actor_mode_changes() {
888
+ local live="$1"
889
+ local payload="$2"
890
+
891
+ jq -r -n --argjson live "${live:-null}" --argjson want "${payload:-null}" '
892
+ def actor_key: [.actor_type, .actor_id];
893
+ def actors: if . == null then [] else (.bypass_actors // []) end;
894
+ ($live | actors) as $have
895
+ | ($want | actors)[]
896
+ | . as $entry
897
+ | ([ $have[] | select(actor_key == ($entry | actor_key)) ] | first) as $old
898
+ | select($old != null and $old.bypass_mode != $entry.bypass_mode)
899
+ | "\(.actor_type)\(if .actor_id == null then "" else " \(.actor_id)" end) (\($old.bypass_mode) -> \(.bypass_mode))"'
900
+ }
901
+
902
+ # Name every bypass actor this run adds and removes. A removal is a warning:
903
+ # it can only happen when config declared the list, and it should still never
904
+ # read in the audit log as a routine reconciliation.
905
+ report_bypass_actor_changes() {
906
+ local live="$1"
907
+ local payload="$2"
908
+
909
+ # A live ruleset that does not disclose its bypass list cannot be preserved,
910
+ # and the payload's list will replace whatever is there.
911
+ if [[ -n "$live" ]] \
912
+ && ! echo "$live" | jq -e 'has("bypass_actors")' &> /dev/null \
913
+ && echo "$payload" | jq -e 'has("bypass_actors")' &> /dev/null; then
914
+ log_warning " GitHub did not disclose this ruleset's bypass actors — any it has will be replaced by the list sent (the token needs repository admin to read them)"
915
+ return 0
916
+ fi
917
+
918
+ local actor
919
+ while IFS= read -r actor; do
920
+ [[ -n "$actor" ]] && log_info " + bypass actor added: $actor"
921
+ done < <(bypass_actors_missing_from "$payload" "$live")
922
+ while IFS= read -r actor; do
923
+ [[ -n "$actor" ]] && log_warning " - bypass actor removed: $actor (the declared bypass list does not name it)"
924
+ done < <(bypass_actors_missing_from "$live" "$payload")
925
+ while IFS= read -r actor; do
926
+ [[ -n "$actor" ]] && log_warning " ~ bypass actor mode changed: $actor (the declared bypass list sets it)"
927
+ done < <(bypass_actor_mode_changes "$live" "$payload")
928
+ }
929
+
727
930
  apply_ruleset() {
728
931
  local repo="$1"
729
932
  local template_file="$2"
@@ -736,12 +939,20 @@ apply_ruleset() {
736
939
  local ruleset_name
737
940
  ruleset_name=$(echo "$template_content" | jq -r '.name')
738
941
 
942
+ local bypass_error
943
+ bypass_error=$(bypass_declaration_error "$project_path" "$ruleset_name")
944
+ if [[ -n "$bypass_error" ]]; then
945
+ log_error "Not applying ruleset '$ruleset_name': $bypass_error"
946
+ return 1
947
+ fi
948
+
739
949
  local clean_template
740
950
  clean_template=$(strip_readonly_fields "$template_content")
741
951
  clean_template=$(add_derived_run_gate_checks "$clean_template" "$project_path" "$ruleset_name")
742
952
  clean_template=$(add_config_required_checks "$clean_template" "$project_path" "$ruleset_name")
743
953
  clean_template=$(strip_actions_checks_if_no_workflows "$clean_template" "$project_path")
744
954
  clean_template=$(strip_config_dropped_checks "$clean_template" "$project_path")
955
+ clean_template=$(apply_config_bypass_actors "$clean_template" "$project_path" "$ruleset_name")
745
956
 
746
957
  # A template whose rules were entirely stripped has nothing to enforce here.
747
958
  if [[ $(echo "$clean_template" | jq '(.rules // [1]) | length') -eq 0 ]]; then
@@ -779,6 +990,12 @@ apply_ruleset() {
779
990
  else
780
991
  clean_template=$(preserve_live_required_checks "$live" "$clean_template")
781
992
  fi
993
+ # The same opt-in, for the bypass list: preserved unless declared.
994
+ if [[ "$(config_bypass_actors "$project_path" "$ruleset_name")" == "null" ]]; then
995
+ clean_template=$(preserve_live_bypass_actors "$live" "$clean_template")
996
+ else
997
+ log_verbose "Bypass actors for '$ruleset_name' are declared in .lisa.config.json — the live list is not preserved"
998
+ fi
782
999
  fi
783
1000
 
784
1001
  # Idempotence: a run that would change nothing must say so and send nothing,
@@ -797,6 +1014,7 @@ apply_ruleset() {
797
1014
  fi
798
1015
  report_added_contexts "$live" "$clean_template"
799
1016
  report_removed_contexts "$live" "$clean_template"
1017
+ report_bypass_actor_changes "$live" "$clean_template"
800
1018
  log_verbose "Template: $template_file"
801
1019
  return 0
802
1020
  fi
@@ -811,6 +1029,7 @@ apply_ruleset() {
811
1029
  if apply_with_integration_fallback "$repo" "$ruleset_name" "$clean_template" "$existing_id"; then
812
1030
  report_added_contexts "$live" "${APPLIED_PAYLOAD:-$clean_template}"
813
1031
  report_removed_contexts "$live" "${APPLIED_PAYLOAD:-$clean_template}"
1032
+ report_bypass_actor_changes "$live" "${APPLIED_PAYLOAD:-$clean_template}"
814
1033
  return 0
815
1034
  fi
816
1035
  log_error "Failed to apply ruleset '$ruleset_name'"