@codyswann/lisa 4.8.1 → 4.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-overwrite/scripts/lisa-gates.mjs +212 -3
- package/all/copy-overwrite/scripts/lisa-reconcile-policy.mjs +11 -0
- package/dist/cli/gate-report-cells.d.ts +1 -1
- package/dist/cli/gate-report-cells.d.ts.map +1 -1
- package/dist/cli/gate-report-cells.js +18 -2
- package/dist/cli/gate-report-cells.js.map +1 -1
- package/dist/cli/gate-report-joins.d.ts.map +1 -1
- package/dist/cli/gate-report-joins.js +36 -4
- package/dist/cli/gate-report-joins.js.map +1 -1
- package/dist/cli/gate-report-registry.d.ts +30 -0
- package/dist/cli/gate-report-registry.d.ts.map +1 -1
- package/dist/cli/gate-report-registry.js +25 -0
- package/dist/cli/gate-report-registry.js.map +1 -1
- package/dist/core/gate-declaration-drift.d.ts +18 -0
- package/dist/core/gate-declaration-drift.d.ts.map +1 -1
- package/dist/core/gate-declaration-drift.js +30 -1
- package/dist/core/gate-declaration-drift.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +4 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +10 -6
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-lighthouse-collect-options.d.ts +3 -1
- package/dist/migrations/ensure-lighthouse-collect-options.d.ts.map +1 -1
- package/dist/migrations/ensure-lighthouse-collect-options.js +12 -4
- package/dist/migrations/ensure-lighthouse-collect-options.js.map +1 -1
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.d.ts +51 -0
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.d.ts.map +1 -0
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.js +122 -0
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.js.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +3 -0
- package/dist/migrations/index.js.map +1 -1
- package/expo/create-only/lighthouserc.js +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +41 -0
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +41 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +41 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +41 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +41 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +41 -0
- package/scripts/lisa-github-rulesets.sh +15 -5
- package/scripts/lisa-ruleset-payload.mjs +30 -0
|
@@ -569,6 +569,37 @@ additive-only `addRequiredChecks`, which is still read but can never express "st
|
|
|
569
569
|
Both surfaces name the ruleset a repair writes to, so `--ruleset` is needed only for a
|
|
570
570
|
gate-DERIVED context on a repository whose rulesets are ambiguous.
|
|
571
571
|
|
|
572
|
+
**2b. A gate proved OUTSIDE the quality facade names its own check-run chain.** A run-mode gate's
|
|
573
|
+
required context is derived by prefixing the gate's label with the chain of jobs that reach the
|
|
574
|
+
facade. When a project proves one property from a workflow of its own instead, that chain describes
|
|
575
|
+
a route that never reaches the prover, and the derived name is one nothing posts — which does not
|
|
576
|
+
turn a pull request red, it holds it at "Expected — Waiting for status to be reported" for ever.
|
|
577
|
+
Declare the real chain on that moment:
|
|
578
|
+
|
|
579
|
+
```json
|
|
580
|
+
"gates": {
|
|
581
|
+
"e2e-browser": {
|
|
582
|
+
"run": "test:e2e:pr",
|
|
583
|
+
"pull-request": {
|
|
584
|
+
"level": "required",
|
|
585
|
+
"caller_chain": ["🎭 PR Browser Coverage"]
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Read the chain off a completed run, never off the YAML: a check run's reported name is the
|
|
592
|
+
`/`-joined chain of the JOB names that reach it, outermost first, and the top workflow's own `name:`
|
|
593
|
+
never appears in it. Take it from
|
|
594
|
+
`gh api repos/OWNER/NAME/commits/SHA/check-runs --paginate --slurp`, or let
|
|
595
|
+
`lisa-gates.mjs verify-contexts --posted=<file>` compare a derivation against what actually posted.
|
|
596
|
+
Either spelling works — `["A", "B"]` or `"A / B"` — and the override replaces the caller's chain for
|
|
597
|
+
that gate only; every other gate keeps deriving exactly what it derived before. It is declared per
|
|
598
|
+
MOMENT, because the same job posts a one-level name on the pull-request path and a two-level one on
|
|
599
|
+
the release path. Declaring it on the whole gate, on an `await`ing moment, at a moment that posts no
|
|
600
|
+
status, or with a blank level is refused by `lisa-gates.mjs validate` — at declaration time, which
|
|
601
|
+
is the last point at which any of those can still fail loudly.
|
|
602
|
+
|
|
572
603
|
**3. Keep both names during a rename.** `--previous=<old label>` requires the old and the new
|
|
573
604
|
context simultaneously for one release. Without it the reconciliation reports the still-live old
|
|
574
605
|
context as EXTRA (and `--prune` would delete it) while in-flight pull requests wait on a context
|
|
@@ -588,6 +619,16 @@ That rule construction is itself config-driven now. `all/github-rulesets/base.js
|
|
|
588
619
|
`policy.review.required_approving_review_count`, none of which config could express while the
|
|
589
620
|
template owned them.
|
|
590
621
|
|
|
622
|
+
`policy.review.require_extra_approval_for_unattributed_changes` is declarable for a reason worth
|
|
623
|
+
stating, because its default is not "leave it alone". Measured against the live rulesets API on
|
|
624
|
+
2026-08-25: a `pull_request` rule sent WITHOUT the parameter came back with it `true`; set
|
|
625
|
+
explicitly to `false` it came back `false`; sent again without it, it came back `true`. GitHub
|
|
626
|
+
re-applies its own default on every write that omits the field. So an operator who wants it OFF
|
|
627
|
+
cannot hold it off unless the payload names it, and an operator who wants it ON is recording no
|
|
628
|
+
choice at all — the value is whatever GitHub's default happens to be. Declaring it pins the choice
|
|
629
|
+
and makes it visible to the reconciler; declaring nothing keeps the payload byte-for-byte what it
|
|
630
|
+
was, parameter absent.
|
|
631
|
+
|
|
591
632
|
## Secrets configuration
|
|
592
633
|
|
|
593
634
|
Run the secrets health checks through the skill that owns the contract, rather than reimplementing
|
|
@@ -569,6 +569,37 @@ additive-only `addRequiredChecks`, which is still read but can never express "st
|
|
|
569
569
|
Both surfaces name the ruleset a repair writes to, so `--ruleset` is needed only for a
|
|
570
570
|
gate-DERIVED context on a repository whose rulesets are ambiguous.
|
|
571
571
|
|
|
572
|
+
**2b. A gate proved OUTSIDE the quality facade names its own check-run chain.** A run-mode gate's
|
|
573
|
+
required context is derived by prefixing the gate's label with the chain of jobs that reach the
|
|
574
|
+
facade. When a project proves one property from a workflow of its own instead, that chain describes
|
|
575
|
+
a route that never reaches the prover, and the derived name is one nothing posts — which does not
|
|
576
|
+
turn a pull request red, it holds it at "Expected — Waiting for status to be reported" for ever.
|
|
577
|
+
Declare the real chain on that moment:
|
|
578
|
+
|
|
579
|
+
```json
|
|
580
|
+
"gates": {
|
|
581
|
+
"e2e-browser": {
|
|
582
|
+
"run": "test:e2e:pr",
|
|
583
|
+
"pull-request": {
|
|
584
|
+
"level": "required",
|
|
585
|
+
"caller_chain": ["🎭 PR Browser Coverage"]
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Read the chain off a completed run, never off the YAML: a check run's reported name is the
|
|
592
|
+
`/`-joined chain of the JOB names that reach it, outermost first, and the top workflow's own `name:`
|
|
593
|
+
never appears in it. Take it from
|
|
594
|
+
`gh api repos/OWNER/NAME/commits/SHA/check-runs --paginate --slurp`, or let
|
|
595
|
+
`lisa-gates.mjs verify-contexts --posted=<file>` compare a derivation against what actually posted.
|
|
596
|
+
Either spelling works — `["A", "B"]` or `"A / B"` — and the override replaces the caller's chain for
|
|
597
|
+
that gate only; every other gate keeps deriving exactly what it derived before. It is declared per
|
|
598
|
+
MOMENT, because the same job posts a one-level name on the pull-request path and a two-level one on
|
|
599
|
+
the release path. Declaring it on the whole gate, on an `await`ing moment, at a moment that posts no
|
|
600
|
+
status, or with a blank level is refused by `lisa-gates.mjs validate` — at declaration time, which
|
|
601
|
+
is the last point at which any of those can still fail loudly.
|
|
602
|
+
|
|
572
603
|
**3. Keep both names during a rename.** `--previous=<old label>` requires the old and the new
|
|
573
604
|
context simultaneously for one release. Without it the reconciliation reports the still-live old
|
|
574
605
|
context as EXTRA (and `--prune` would delete it) while in-flight pull requests wait on a context
|
|
@@ -588,6 +619,16 @@ That rule construction is itself config-driven now. `all/github-rulesets/base.js
|
|
|
588
619
|
`policy.review.required_approving_review_count`, none of which config could express while the
|
|
589
620
|
template owned them.
|
|
590
621
|
|
|
622
|
+
`policy.review.require_extra_approval_for_unattributed_changes` is declarable for a reason worth
|
|
623
|
+
stating, because its default is not "leave it alone". Measured against the live rulesets API on
|
|
624
|
+
2026-08-25: a `pull_request` rule sent WITHOUT the parameter came back with it `true`; set
|
|
625
|
+
explicitly to `false` it came back `false`; sent again without it, it came back `true`. GitHub
|
|
626
|
+
re-applies its own default on every write that omits the field. So an operator who wants it OFF
|
|
627
|
+
cannot hold it off unless the payload names it, and an operator who wants it ON is recording no
|
|
628
|
+
choice at all — the value is whatever GitHub's default happens to be. Declaring it pins the choice
|
|
629
|
+
and makes it visible to the reconciler; declaring nothing keeps the payload byte-for-byte what it
|
|
630
|
+
was, parameter absent.
|
|
631
|
+
|
|
591
632
|
## Secrets configuration
|
|
592
633
|
|
|
593
634
|
Run the secrets health checks through the skill that owns the contract, rather than reimplementing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.3",
|
|
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.8.
|
|
3
|
+
"version": "4.8.3",
|
|
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.8.
|
|
3
|
+
"version": "4.8.3",
|
|
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.8.
|
|
3
|
+
"version": "4.8.3",
|
|
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.8.
|
|
3
|
+
"version": "4.8.3",
|
|
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"
|
|
@@ -569,6 +569,37 @@ additive-only `addRequiredChecks`, which is still read but can never express "st
|
|
|
569
569
|
Both surfaces name the ruleset a repair writes to, so `--ruleset` is needed only for a
|
|
570
570
|
gate-DERIVED context on a repository whose rulesets are ambiguous.
|
|
571
571
|
|
|
572
|
+
**2b. A gate proved OUTSIDE the quality facade names its own check-run chain.** A run-mode gate's
|
|
573
|
+
required context is derived by prefixing the gate's label with the chain of jobs that reach the
|
|
574
|
+
facade. When a project proves one property from a workflow of its own instead, that chain describes
|
|
575
|
+
a route that never reaches the prover, and the derived name is one nothing posts — which does not
|
|
576
|
+
turn a pull request red, it holds it at "Expected — Waiting for status to be reported" for ever.
|
|
577
|
+
Declare the real chain on that moment:
|
|
578
|
+
|
|
579
|
+
```json
|
|
580
|
+
"gates": {
|
|
581
|
+
"e2e-browser": {
|
|
582
|
+
"run": "test:e2e:pr",
|
|
583
|
+
"pull-request": {
|
|
584
|
+
"level": "required",
|
|
585
|
+
"caller_chain": ["🎭 PR Browser Coverage"]
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Read the chain off a completed run, never off the YAML: a check run's reported name is the
|
|
592
|
+
`/`-joined chain of the JOB names that reach it, outermost first, and the top workflow's own `name:`
|
|
593
|
+
never appears in it. Take it from
|
|
594
|
+
`gh api repos/OWNER/NAME/commits/SHA/check-runs --paginate --slurp`, or let
|
|
595
|
+
`lisa-gates.mjs verify-contexts --posted=<file>` compare a derivation against what actually posted.
|
|
596
|
+
Either spelling works — `["A", "B"]` or `"A / B"` — and the override replaces the caller's chain for
|
|
597
|
+
that gate only; every other gate keeps deriving exactly what it derived before. It is declared per
|
|
598
|
+
MOMENT, because the same job posts a one-level name on the pull-request path and a two-level one on
|
|
599
|
+
the release path. Declaring it on the whole gate, on an `await`ing moment, at a moment that posts no
|
|
600
|
+
status, or with a blank level is refused by `lisa-gates.mjs validate` — at declaration time, which
|
|
601
|
+
is the last point at which any of those can still fail loudly.
|
|
602
|
+
|
|
572
603
|
**3. Keep both names during a rename.** `--previous=<old label>` requires the old and the new
|
|
573
604
|
context simultaneously for one release. Without it the reconciliation reports the still-live old
|
|
574
605
|
context as EXTRA (and `--prune` would delete it) while in-flight pull requests wait on a context
|
|
@@ -588,6 +619,16 @@ That rule construction is itself config-driven now. `all/github-rulesets/base.js
|
|
|
588
619
|
`policy.review.required_approving_review_count`, none of which config could express while the
|
|
589
620
|
template owned them.
|
|
590
621
|
|
|
622
|
+
`policy.review.require_extra_approval_for_unattributed_changes` is declarable for a reason worth
|
|
623
|
+
stating, because its default is not "leave it alone". Measured against the live rulesets API on
|
|
624
|
+
2026-08-25: a `pull_request` rule sent WITHOUT the parameter came back with it `true`; set
|
|
625
|
+
explicitly to `false` it came back `false`; sent again without it, it came back `true`. GitHub
|
|
626
|
+
re-applies its own default on every write that omits the field. So an operator who wants it OFF
|
|
627
|
+
cannot hold it off unless the payload names it, and an operator who wants it ON is recording no
|
|
628
|
+
choice at all — the value is whatever GitHub's default happens to be. Declaring it pins the choice
|
|
629
|
+
and makes it visible to the reconciler; declaring nothing keeps the payload byte-for-byte what it
|
|
630
|
+
was, parameter absent.
|
|
631
|
+
|
|
591
632
|
## Secrets configuration
|
|
592
633
|
|
|
593
634
|
Run the secrets health checks through the skill that owns the contract, rather than reimplementing
|
|
@@ -570,11 +570,21 @@ ruleset_added_contexts() {
|
|
|
570
570
|
# True when everything the payload asks for is ALREADY true of the live ruleset.
|
|
571
571
|
#
|
|
572
572
|
# Deliberately a subset test rather than an equality test: GitHub echoes back
|
|
573
|
-
# parameters it filled in with its own defaults (`required_reviewers`,
|
|
574
|
-
#
|
|
575
|
-
#
|
|
576
|
-
#
|
|
577
|
-
#
|
|
573
|
+
# parameters it filled in with its own defaults (`required_reviewers`, ...)
|
|
574
|
+
# which no template names. Comparing whole documents would report drift on
|
|
575
|
+
# every run and make "nothing to do" unreachable — the AC's second scenario
|
|
576
|
+
# would then never hold even on a repository that needs no change.
|
|
577
|
+
#
|
|
578
|
+
# `require_extra_approval_for_unattributed_changes` used to be listed here as
|
|
579
|
+
# another such default, and that reading was measured wrong on 2026-08-25. It
|
|
580
|
+
# IS filled in when omitted, but the fill is `true` and it is re-applied on
|
|
581
|
+
# every write: a rule PUT without the field came back `true` even after being
|
|
582
|
+
# explicitly set to `false`. Being invisible to a subset test is therefore not
|
|
583
|
+
# harmless for it — an operator who wants it off cannot keep it off, and the
|
|
584
|
+
# reconciliation reports no drift while GitHub turns it back on. It is now
|
|
585
|
+
# declarable as `policy.review.require_extra_approval_for_unattributed_changes`
|
|
586
|
+
# (CodySwannGT/lisa#3096), and once declared the payload names it, so this
|
|
587
|
+
# subset test compares it like any other parameter.
|
|
578
588
|
ruleset_is_current() {
|
|
579
589
|
local live="$1"
|
|
580
590
|
local payload="$2"
|
|
@@ -107,6 +107,31 @@ export const REVIEW_DEFAULTS = Object.freeze({
|
|
|
107
107
|
require_code_owner_review: false,
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* The review parameter with no default here, on purpose.
|
|
112
|
+
*
|
|
113
|
+
* `require_extra_approval_for_unattributed_changes` is emitted only when the
|
|
114
|
+
* project declares it, and is left out of the payload entirely otherwise. A
|
|
115
|
+
* default of either polarity would be wrong:
|
|
116
|
+
*
|
|
117
|
+
* - `false` would send every repository a loosening it never asked for, and
|
|
118
|
+
* would flip a live `true` off on the next apply;
|
|
119
|
+
* - `true` would send every repository a tightening it never asked for, and
|
|
120
|
+
* would make the negative control below impossible — a project that declares
|
|
121
|
+
* nothing must generate byte-for-byte the payload it generated before this
|
|
122
|
+
* field was declarable.
|
|
123
|
+
*
|
|
124
|
+
* Omission is not neutral either, and that is the finding rather than a
|
|
125
|
+
* shortcoming of this choice. Measured against the live rulesets API on
|
|
126
|
+
* 2026-08-25 (a `pull_request` rule POSTed to a disabled probe ruleset, then
|
|
127
|
+
* PUT three times): omitted, it came back `true`; sent `false`, it came back
|
|
128
|
+
* `false`; sent omitted again, it came back `true` — GitHub resets it to its
|
|
129
|
+
* own default on every write that does not name it. Declaring it is therefore
|
|
130
|
+
* the ONLY way to hold it at `false`, and the only way to record `true` as a
|
|
131
|
+
* choice rather than as whatever GitHub's default happens to be that week.
|
|
132
|
+
*/
|
|
133
|
+
const EXTRA_APPROVAL = "require_extra_approval_for_unattributed_changes";
|
|
134
|
+
|
|
110
135
|
/** Branch-protection defaults, matching the retired template. */
|
|
111
136
|
export const PROTECT_DEFAULTS = Object.freeze({
|
|
112
137
|
deletion: true,
|
|
@@ -245,6 +270,11 @@ function pullRequestRule(policy) {
|
|
|
245
270
|
"conversation_resolution",
|
|
246
271
|
PROTECT_DEFAULTS
|
|
247
272
|
),
|
|
273
|
+
// Spread, not `resolved`, because there is no default to resolve to.
|
|
274
|
+
// See EXTRA_APPROVAL: an undeclared project sends no such key at all.
|
|
275
|
+
...(policy?.review?.[EXTRA_APPROVAL] === undefined
|
|
276
|
+
? {}
|
|
277
|
+
: { [EXTRA_APPROVAL]: policy.review[EXTRA_APPROVAL] }),
|
|
248
278
|
allowed_merge_methods: allowed,
|
|
249
279
|
},
|
|
250
280
|
};
|