Conversation
- docs/ROUND-HISTORY.md: `+` at line-start parsed as bullet by markdownlint (MD032); replace with "and" in the two affected wrap points. - .github/workflows/github-settings-drift.yml: drop `administration: read` — actionlint v1.7.x does not list it as a recognised scope; the default GITHUB_TOKEN on public repos covers the endpoints we read. Comment documents the trade + the fact that the PR-triggered drift check would surface a missing scope immediately. - tools/hygiene/snapshot-github-settings.sh: drop dead `rulesets_json` assignment (SC2034) — the `ruleset_details` loop below returns the full per-ruleset data used in the final jq output. Aaron's follow-up "you also didn't get half the settings you miss most of them lol it's okay" is acknowledged — a coverage expansion PR will follow this one. The current snapshot is a first pass; PR #45 lands the *pattern* (decl + drift detector + hygiene row) and we iterate on coverage.
There was a problem hiding this comment.
Pull request overview
This PR lands the post–org-transfer follow-up artifacts for HB-001 by introducing “GitHub settings as code (by convention)”: a checked-in expected snapshot plus scripts/workflow to detect drift in repo settings that aren’t natively declarative in-tree.
Changes:
- Add
tools/hygiene/*scripts + canonicalgithub-settings.expected.jsonsnapshot to generate and diff live GitHub settings. - Add a scheduled + PR-triggered GitHub Actions workflow to run the drift detector.
- Document the expected GitHub settings and record HB-001/migration narrative in docs.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
tools/hygiene/snapshot-github-settings.sh |
New snapshot generator that queries GitHub API surfaces and outputs normalized JSON. |
tools/hygiene/check-github-settings-drift.sh |
New drift detector that diffs live snapshot vs checked-in expected JSON. |
tools/hygiene/github-settings.expected.json |
Checked-in canonical expected settings snapshot used as the drift baseline. |
.github/workflows/github-settings-drift.yml |
Weekly + PR-triggered workflow to run the drift check in CI. |
docs/GITHUB-SETTINGS.md |
Human-readable narrative declaration of expected GitHub settings and update process. |
docs/FACTORY-HYGIENE.md |
Adds a factory hygiene row describing the drift detector cadence/scope. |
docs/HUMAN-BACKLOG.md |
Marks HB-001 as resolved with a migration + verification summary. |
docs/ROUND-HISTORY.md |
Records the late-round migration arc and introduces the drift-detector pattern. |
| - `memory/feedback_github_settings_as_code_declarative_checked_in_file.md` | ||
| — the framing / pattern. | ||
| - `memory/reference_github_code_scanning_ruleset_rule_requires_default_setup.md` | ||
| — why the `code_scanning` ruleset rule is off. | ||
| - `memory/project_zeta_org_migration_to_lucent_financial_group.md` | ||
| — the migration that triggered this discipline. |
There was a problem hiding this comment.
P1: The “Related” section references several memory/* files that are not present in the repo (feedback_github_settings_as_code_declarative_checked_in_file.md, reference_github_code_scanning_ruleset_rule_requires_default_setup.md, project_zeta_org_migration_to_lucent_financial_group.md). Please add those files or remove/replace the references.
| - `memory/feedback_github_settings_as_code_declarative_checked_in_file.md` | |
| — the framing / pattern. | |
| - `memory/reference_github_code_scanning_ruleset_rule_requires_default_setup.md` | |
| — why the `code_scanning` ruleset rule is off. | |
| - `memory/project_zeta_org_migration_to_lucent_financial_group.md` | |
| — the migration that triggered this discipline. |
| against the expected JSON. Exit 0 on match, 1 on drift. | ||
| 3. **Cadence** is enforced by | ||
| `.github/workflows/github-settings-drift.yml` — weekly cron | ||
| + `workflow_dispatch` for manual runs. Drift blocks the |
There was a problem hiding this comment.
P1: In the numbered list, this line starting with + will render as a nested unordered list item (not as “cron plus workflow_dispatch” text). If the intent is prose, rewrite without a leading list marker (e.g., “and workflow_dispatch …”).
| + `workflow_dispatch` for manual runs. Drift blocks the | |
| and `workflow_dispatch` for manual runs. Drift blocks the |
| | 40 | GitHub-settings drift detector | Weekly (cron `17 14 * * 1`) + on any change to `tools/hygiene/github-settings.expected.json` / detector script / workflow file. Added 2026-04-21 after `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` org-transfer silently flipped `secret_scanning` and `secret_scanning_push_protection` from enabled to disabled. | Automated (`.github/workflows/github-settings-drift.yml`); human resolves on drift | factory (ships to adopters as a template; repo-specific expected snapshot per adopter) | Live `gh api` snapshot vs. checked-in `tools/hygiene/github-settings.expected.json`: repo-level toggles (merge methods, security-and-analysis), rulesets + rule contents, classic branch protection on default branch, Actions permissions + variables + counts of secrets, environments + protection-rule types, Pages config, CodeQL default-setup state, webhook / deploy-key / secret counts. Script at `tools/hygiene/check-github-settings-drift.sh` exits 1 on drift and prints `diff -u` output. Resolution: intentional → re-snapshot + commit new expected with rationale; unintentional → revert in GitHub + rerun detector. | `docs/GITHUB-SETTINGS.md` + `tools/hygiene/github-settings.expected.json` + workflow run log + optional `memory/reference_github_*.md` entry if drift source is non-obvious | `feedback_github_settings_as_code_declarative_checked_in_file.md` + `feedback_blast_radius_pricing_standing_rule_alignment_signal.md` + `project_zeta_org_migration_to_lucent_financial_group.md` | | ||
| | 39 | Hot-file-path detector | Round-cadence (every round close) or every 5-10 rounds — whichever catches churn drift before the next merge-tangle. Proposed 2026-04-21. | TBD — candidate capability skill `hot-file-detector` (queued in BACKLOG P1); Architect runs inline until the skill lands. | factory (ships to adopters as a command-line recipe) | `git log --since="60 days ago" --name-only --pretty=format: \| grep -v '^$' \| sort \| uniq -c \| sort -rn \| head -25` — git history *is* the index. Heuristic threshold: >20 changes in 60d on a single monolithic doc = investigate; >30 = refactor candidate (tune after 5-10 rounds). Per-file decision is one of four: `refactor-split` (per-row, per-round, per-section), `consolidate-reduce` (merge with a sibling), `accept-as-append-only` (legitimately append-only → split into per-round files rather than trimming), or `observe`. Pair with merge-tangle fingerprints from `docs/research/parallel-worktree-safety-2026-04-22.md` §9 — a hot file is worse if also in a recent conflict list. Triggering incident: PR #31 5-file merge-tangle (2026-04-21) where `docs/ROUND-HISTORY.md` at 33 changes / 60d was the #1 conflict source, and `docs/BACKLOG.md` at 26 already has an in-flight split ADR. | Audit doc per cycle listing top-N hot paths + per-file decision; BACKLOG rows for refactor-split candidates; ADRs for structural changes. | `feedback_hot_file_path_detector_hygiene.md` + `docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md` (same pattern) | |
There was a problem hiding this comment.
P1: Table ordering/numbering looks inconsistent: row 40 is inserted before row 39. If row numbers are meant to be sequential, renumber/reorder to keep the table stable for future references (people will cite these row numbers elsewhere).
| | 40 | GitHub-settings drift detector | Weekly (cron `17 14 * * 1`) + on any change to `tools/hygiene/github-settings.expected.json` / detector script / workflow file. Added 2026-04-21 after `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` org-transfer silently flipped `secret_scanning` and `secret_scanning_push_protection` from enabled to disabled. | Automated (`.github/workflows/github-settings-drift.yml`); human resolves on drift | factory (ships to adopters as a template; repo-specific expected snapshot per adopter) | Live `gh api` snapshot vs. checked-in `tools/hygiene/github-settings.expected.json`: repo-level toggles (merge methods, security-and-analysis), rulesets + rule contents, classic branch protection on default branch, Actions permissions + variables + counts of secrets, environments + protection-rule types, Pages config, CodeQL default-setup state, webhook / deploy-key / secret counts. Script at `tools/hygiene/check-github-settings-drift.sh` exits 1 on drift and prints `diff -u` output. Resolution: intentional → re-snapshot + commit new expected with rationale; unintentional → revert in GitHub + rerun detector. | `docs/GITHUB-SETTINGS.md` + `tools/hygiene/github-settings.expected.json` + workflow run log + optional `memory/reference_github_*.md` entry if drift source is non-obvious | `feedback_github_settings_as_code_declarative_checked_in_file.md` + `feedback_blast_radius_pricing_standing_rule_alignment_signal.md` + `project_zeta_org_migration_to_lucent_financial_group.md` | | |
| | 39 | Hot-file-path detector | Round-cadence (every round close) or every 5-10 rounds — whichever catches churn drift before the next merge-tangle. Proposed 2026-04-21. | TBD — candidate capability skill `hot-file-detector` (queued in BACKLOG P1); Architect runs inline until the skill lands. | factory (ships to adopters as a command-line recipe) | `git log --since="60 days ago" --name-only --pretty=format: \| grep -v '^$' \| sort \| uniq -c \| sort -rn \| head -25` — git history *is* the index. Heuristic threshold: >20 changes in 60d on a single monolithic doc = investigate; >30 = refactor candidate (tune after 5-10 rounds). Per-file decision is one of four: `refactor-split` (per-row, per-round, per-section), `consolidate-reduce` (merge with a sibling), `accept-as-append-only` (legitimately append-only → split into per-round files rather than trimming), or `observe`. Pair with merge-tangle fingerprints from `docs/research/parallel-worktree-safety-2026-04-22.md` §9 — a hot file is worse if also in a recent conflict list. Triggering incident: PR #31 5-file merge-tangle (2026-04-21) where `docs/ROUND-HISTORY.md` at 33 changes / 60d was the #1 conflict source, and `docs/BACKLOG.md` at 26 already has an in-flight split ADR. | Audit doc per cycle listing top-N hot paths + per-file decision; BACKLOG rows for refactor-split candidates; ADRs for structural changes. | `feedback_hot_file_path_detector_hygiene.md` + `docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md` (same pattern) | | |
| | 39 | Hot-file-path detector | Round-cadence (every round close) or every 5-10 rounds — whichever catches churn drift before the next merge-tangle. Proposed 2026-04-21. | TBD — candidate capability skill `hot-file-detector` (queued in BACKLOG P1); Architect runs inline until the skill lands. | factory (ships to adopters as a command-line recipe) | `git log --since="60 days ago" --name-only --pretty=format: \| grep -v '^$' \| sort \| uniq -c \| sort -rn \| head -25` — git history *is* the index. Heuristic threshold: >20 changes in 60d on a single monolithic doc = investigate; >30 = refactor candidate (tune after 5-10 rounds). Per-file decision is one of four: `refactor-split` (per-row, per-round, per-section), `consolidate-reduce` (merge with a sibling), `accept-as-append-only` (legitimately append-only → split into per-round files rather than trimming), or `observe`. Pair with merge-tangle fingerprints from `docs/research/parallel-worktree-safety-2026-04-22.md` §9 — a hot file is worse if also in a recent conflict list. Triggering incident: PR #31 5-file merge-tangle (2026-04-21) where `docs/ROUND-HISTORY.md` at 33 changes / 60d was the #1 conflict source, and `docs/BACKLOG.md` at 26 already has an in-flight split ADR. | Audit doc per cycle listing top-N hot paths + per-file decision; BACKLOG rows for refactor-split candidates; ADRs for structural changes. | `feedback_hot_file_path_detector_hygiene.md` + `docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md` (same pattern) | | |
| | 40 | GitHub-settings drift detector | Weekly (cron `17 14 * * 1`) + on any change to `tools/hygiene/github-settings.expected.json` / detector script / workflow file. Added 2026-04-21 after `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` org-transfer silently flipped `secret_scanning` and `secret_scanning_push_protection` from enabled to disabled. | Automated (`.github/workflows/github-settings-drift.yml`); human resolves on drift | factory (ships to adopters as a template; repo-specific expected snapshot per adopter) | Live `gh api` snapshot vs. checked-in `tools/hygiene/github-settings.expected.json`: repo-level toggles (merge methods, security-and-analysis), rulesets + rule contents, classic branch protection on default branch, Actions permissions + variables + counts of secrets, environments + protection-rule types, Pages config, CodeQL default-setup state, webhook / deploy-key / secret counts. Script at `tools/hygiene/check-github-settings-drift.sh` exits 1 on drift and prints `diff -u` output. Resolution: intentional → re-snapshot + commit new expected with rationale; unintentional → revert in GitHub + rerun detector. | `docs/GITHUB-SETTINGS.md` + `tools/hygiene/github-settings.expected.json` + workflow run log + optional `memory/reference_github_*.md` entry if drift source is non-obvious | `feedback_github_settings_as_code_declarative_checked_in_file.md` + `feedback_blast_radius_pricing_standing_rule_alignment_signal.md` + `project_zeta_org_migration_to_lucent_financial_group.md` | |
| name: check drift | ||
| runs-on: ubuntu-22.04 | ||
| steps: | ||
| - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 |
There was a problem hiding this comment.
P2: This workflow pins actions/checkout to v4.3.0, while other workflows in this repo are already using a newer pinned major (e.g. v6.0.2 in gate.yml/codeql.yml). Consider aligning on the same pinned version across workflows to reduce supply-chain drift and maintenance overhead.
| - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 | |
| - uses: actions/checkout@v6.0.2 |
| script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| repo="" | ||
| expected="$script_dir/github-settings.expected.json" | ||
|
|
||
| while [ $# -gt 0 ]; do | ||
| case "$1" in | ||
| --repo) repo="$2"; shift 2;; | ||
| --expected) expected="$2"; shift 2;; | ||
| *) echo "error: unknown arg: $1" >&2; exit 2;; |
There was a problem hiding this comment.
P1: --repo / --expected parsing assumes a value is present ($2), but under set -u running ... --repo without an argument will abort with an unhelpful “unbound variable” error. Add an explicit check for missing option values and print a usage/error message before exiting 2.
| script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| repo="" | |
| expected="$script_dir/github-settings.expected.json" | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| --repo) repo="$2"; shift 2;; | |
| --expected) expected="$2"; shift 2;; | |
| *) echo "error: unknown arg: $1" >&2; exit 2;; | |
| usage() { | |
| echo "Usage: tools/hygiene/check-github-settings-drift.sh [--repo OWNER/NAME] [--expected PATH]" >&2 | |
| } | |
| script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| repo="" | |
| expected="$script_dir/github-settings.expected.json" | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| --repo) | |
| if [ $# -lt 2 ]; then | |
| echo "error: --repo requires a value" >&2 | |
| usage | |
| exit 2 | |
| fi | |
| repo="$2" | |
| shift 2 | |
| ;; | |
| --expected) | |
| if [ $# -lt 2 ]; then | |
| echo "error: --expected requires a value" >&2 | |
| usage | |
| exit 2 | |
| fi | |
| expected="$2" | |
| shift 2 | |
| ;; | |
| *) | |
| echo "error: unknown arg: $1" >&2 | |
| usage | |
| exit 2 | |
| ;; |
| exit 2 | ||
| } | ||
|
|
||
| if diff -u "$expected" "$tmp"; then |
There was a problem hiding this comment.
P2: The drift diff currently includes a temp filename in the headers, which makes the output noisier to read. Consider using diff labels (or a stable temp path) so the header lines are stable and clearly indicate “expected” vs “current”.
| if diff -u "$expected" "$tmp"; then | |
| if diff -u --label "expected" --label "current" "$expected" "$tmp"; then |
| owner / scope specification and | ||
| `memory/feedback_github_settings_as_code_declarative_checked_in_file.md` | ||
| for the framing this pattern belongs to. |
There was a problem hiding this comment.
P1: This doc links to memory/feedback_github_settings_as_code_declarative_checked_in_file.md, but that file does not exist in the repository. Either add the referenced memory entry or update/remove the link so readers don’t hit a dead reference.
| owner / scope specification and | |
| `memory/feedback_github_settings_as_code_declarative_checked_in_file.md` | |
| for the framing this pattern belongs to. | |
| owner / scope specification and for the broader framing this | |
| pattern belongs to. |
| Motivation (Aaron 2026-04-21): | ||
|
|
||
| > "its nice having the expected settings declarative defined" | ||
| > | ||
| > "i hate things in GitHub where I can't check in the | ||
| > declarative settgins so we will save a back[up]" |
There was a problem hiding this comment.
P1: This uses direct contributor name attribution (“Motivation (Aaron …)”), but repo guidance says contributor names should not appear in docs (role references like “human maintainer” are preferred), except in persona memory dirs and optionally docs/BACKLOG.md (see docs/AGENT-BEST-PRACTICES.md:284-290). Please rewrite to a role reference and, if needed, move the named/quoted material into an appropriate memory/persona location.
| Motivation (Aaron 2026-04-21): | |
| > "its nice having the expected settings declarative defined" | |
| > | |
| > "i hate things in GitHub where I can't check in the | |
| > declarative settgins so we will save a back[up]" | |
| Motivation: | |
| The human maintainer wants GitHub settings that are otherwise | |
| only configurable through the UI to have a declarative, | |
| checked-in backup so expected state stays reviewable and | |
| recoverable. |
| | ID | When | Category | Ask | Source | State | Resolution | | ||
| |---|---|---|---|---|---|---| | ||
| | HB-001 | 2026-04-21 | decision / org-migration | Plan + execute the migration of `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` (Aaron's LFG umbrella org — `project_lucent_financial_group_external_umbrella.md`). Drivers: (a) GitHub gates merge queue and other org-level features to organization-owned repos — user-owned repos cannot enable merge queue on any plan tier, which is the real blocker behind the `422 Invalid rule 'merge_queue':` failure against `POST /repos/AceHack/Zeta/rulesets` (see §10.3 of `docs/research/parallel-worktree-safety-2026-04-22.md`); (b) aligns the repo with Aaron's stated destination for external contributors. **Constraints (Aaron 2026-04-21):** (1) **preserve all current settings** — rulesets, required checks (gate + CodeQL + semgrep), branch-protection behaviours, auto-delete-head-branch, auto-merge, Dependabot, CodeScanning, Copilot Code Review, concurrency groups, workflow triggers incl. `merge_group:`; (2) **public from the start** at the new location — no private-during-transition staging period. No deadline — "at some point". Until transferred, the factory accepts the rebase-tax on serial PRs and relies on `gh pr merge --auto --squash` alone (merge queue off). | `docs/research/parallel-worktree-safety-2026-04-22.md` §10.3; session transcript 2026-04-21 (Aaron: "we can move tih to https://github.com/Lucent-Financial-Group at some point it's my org for LFG" + "we need to move it to lucent for contributor at some point anyways, we want to keep all the settings we have now" + "i think we are going to have to go without merge queue parallelism for now" + "we can just make it public from the start") | Open | — | | ||
| | HB-001 | 2026-04-21 | decision / org-migration | Plan + execute the migration of `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` (Aaron's LFG umbrella org — `project_lucent_financial_group_external_umbrella.md`). Drivers: (a) GitHub gates merge queue and other org-level features to organization-owned repos — user-owned repos cannot enable merge queue on any plan tier, which is the real blocker behind the `422 Invalid rule 'merge_queue':` failure against `POST /repos/AceHack/Zeta/rulesets` (see §10.3 of `docs/research/parallel-worktree-safety-2026-04-22.md`); (b) aligns the repo with Aaron's stated destination for external contributors. **Constraints (Aaron 2026-04-21):** (1) **preserve all current settings** — rulesets, required checks (gate + CodeQL + semgrep), branch-protection behaviours, auto-delete-head-branch, auto-merge, Dependabot, CodeScanning, Copilot Code Review, concurrency groups, workflow triggers incl. `merge_group:`; (2) **public from the start** at the new location — no private-during-transition staging period. No deadline — "at some point". Until transferred, the factory accepts the rebase-tax on serial PRs and relies on `gh pr merge --auto --squash` alone (merge queue off). | `docs/research/parallel-worktree-safety-2026-04-22.md` §10.3; session transcript 2026-04-21 (Aaron: "we can move tih to https://github.com/Lucent-Financial-Group at some point it's my org for LFG" + "we need to move it to lucent for contributor at some point anyways, we want to keep all the settings we have now" + "i think we are going to have to go without merge queue parallelism for now" + "we can just make it public from the start") | Resolved | Executed 2026-04-21 via `POST /repos/AceHack/Zeta/transfer` with `new_owner=Lucent-Financial-Group`. Transfer completed instantly (Aaron admin on both sides). Verification diffed 13 settings groups against pre-transfer scorecard: all preserved **except** `secret_scanning` and `secret_scanning_push_protection` both silently flipped `enabled→disabled` by GitHub's org-transfer code path; re-enabled same session via `PATCH /repos/Lucent-Financial-Group/Zeta` with `security_and_analysis`. Ruleset id 15256879 "Default" preserved byte-identical (6 rules); classic branch protection on main preserved (6 required contexts); Actions variables preserved (2 COPILOT_AGENT_FIREWALL_*); environments + Pages config preserved (Pages URL redirected `acehack.github.io/Zeta` → `lucent-financial-group.github.io/Zeta`). Local `git remote` updated. Declarative settings file landed at `docs/GITHUB-SETTINGS.md` per Aaron's companion directive ("its nice having the expected settings declarative defined" + "i hate things in GitHub where I can't check in the declarative settgins"). Merge queue enable remains a separate opt-in step. | |
There was a problem hiding this comment.
P1: This backlog row references project_lucent_financial_group_external_umbrella.md, but that file does not exist anywhere in the repo. Either add the intended doc/memory entry or update the reference to an existing path so the cross-reference resolves.
| | HB-001 | 2026-04-21 | decision / org-migration | Plan + execute the migration of `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` (Aaron's LFG umbrella org — `project_lucent_financial_group_external_umbrella.md`). Drivers: (a) GitHub gates merge queue and other org-level features to organization-owned repos — user-owned repos cannot enable merge queue on any plan tier, which is the real blocker behind the `422 Invalid rule 'merge_queue':` failure against `POST /repos/AceHack/Zeta/rulesets` (see §10.3 of `docs/research/parallel-worktree-safety-2026-04-22.md`); (b) aligns the repo with Aaron's stated destination for external contributors. **Constraints (Aaron 2026-04-21):** (1) **preserve all current settings** — rulesets, required checks (gate + CodeQL + semgrep), branch-protection behaviours, auto-delete-head-branch, auto-merge, Dependabot, CodeScanning, Copilot Code Review, concurrency groups, workflow triggers incl. `merge_group:`; (2) **public from the start** at the new location — no private-during-transition staging period. No deadline — "at some point". Until transferred, the factory accepts the rebase-tax on serial PRs and relies on `gh pr merge --auto --squash` alone (merge queue off). | `docs/research/parallel-worktree-safety-2026-04-22.md` §10.3; session transcript 2026-04-21 (Aaron: "we can move tih to https://github.com/Lucent-Financial-Group at some point it's my org for LFG" + "we need to move it to lucent for contributor at some point anyways, we want to keep all the settings we have now" + "i think we are going to have to go without merge queue parallelism for now" + "we can just make it public from the start") | Resolved | Executed 2026-04-21 via `POST /repos/AceHack/Zeta/transfer` with `new_owner=Lucent-Financial-Group`. Transfer completed instantly (Aaron admin on both sides). Verification diffed 13 settings groups against pre-transfer scorecard: all preserved **except** `secret_scanning` and `secret_scanning_push_protection` both silently flipped `enabled→disabled` by GitHub's org-transfer code path; re-enabled same session via `PATCH /repos/Lucent-Financial-Group/Zeta` with `security_and_analysis`. Ruleset id 15256879 "Default" preserved byte-identical (6 rules); classic branch protection on main preserved (6 required contexts); Actions variables preserved (2 COPILOT_AGENT_FIREWALL_*); environments + Pages config preserved (Pages URL redirected `acehack.github.io/Zeta` → `lucent-financial-group.github.io/Zeta`). Local `git remote` updated. Declarative settings file landed at `docs/GITHUB-SETTINGS.md` per Aaron's companion directive ("its nice having the expected settings declarative defined" + "i hate things in GitHub where I can't check in the declarative settgins"). Merge queue enable remains a separate opt-in step. | | |
| | HB-001 | 2026-04-21 | decision / org-migration | Plan + execute the migration of `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` (Aaron's LFG umbrella org). Drivers: (a) GitHub gates merge queue and other org-level features to organization-owned repos — user-owned repos cannot enable merge queue on any plan tier, which is the real blocker behind the `422 Invalid rule 'merge_queue':` failure against `POST /repos/AceHack/Zeta/rulesets` (see §10.3 of `docs/research/parallel-worktree-safety-2026-04-22.md`); (b) aligns the repo with Aaron's stated destination for external contributors. **Constraints (Aaron 2026-04-21):** (1) **preserve all current settings** — rulesets, required checks (gate + CodeQL + semgrep), branch-protection behaviours, auto-delete-head-branch, auto-merge, Dependabot, CodeScanning, Copilot Code Review, concurrency groups, workflow triggers incl. `merge_group:`; (2) **public from the start** at the new location — no private-during-transition staging period. No deadline — "at some point". Until transferred, the factory accepts the rebase-tax on serial PRs and relies on `gh pr merge --auto --squash` alone (merge queue off). | `docs/research/parallel-worktree-safety-2026-04-22.md` §10.3; session transcript 2026-04-21 (Aaron: "we can move tih to https://github.com/Lucent-Financial-Group at some point it's my org for LFG" + "we need to move it to lucent for contributor at some point anyways, we want to keep all the settings we have now" + "i think we are going to have to go without merge queue parallelism for now" + "we can just make it public from the start") | Resolved | Executed 2026-04-21 via `POST /repos/AceHack/Zeta/transfer` with `new_owner=Lucent-Financial-Group`. Transfer completed instantly (Aaron admin on both sides). Verification diffed 13 settings groups against pre-transfer scorecard: all preserved **except** `secret_scanning` and `secret_scanning_push_protection` both silently flipped `enabled→disabled` by GitHub's org-transfer code path; re-enabled same session via `PATCH /repos/Lucent-Financial-Group/Zeta` with `security_and_analysis`. Ruleset id 15256879 "Default" preserved byte-identical (6 rules); classic branch protection on main preserved (6 required contexts); Actions variables preserved (2 COPILOT_AGENT_FIREWALL_*); environments + Pages config preserved (Pages URL redirected `acehack.github.io/Zeta` → `lucent-financial-group.github.io/Zeta`). Local `git remote` updated. Declarative settings file landed at `docs/GITHUB-SETTINGS.md` per Aaron's companion directive ("its nice having the expected settings declarative defined" + "i hate things in GitHub where I can't check in the declarative settgins"). Merge queue enable remains a separate opt-in step. | |
| # - GitHub Pages config | ||
| # - CodeQL default-setup state | ||
| # | ||
| # Exit 0 on success; non-zero on any gh call failure. |
There was a problem hiding this comment.
P1: The header comment says “non-zero on any gh call failure”, but the script currently masks some API failures (e.g., branch protection and Pages use || echo null) and does not otherwise enforce failure-on-error (no set -e and no explicit status checks per gh api call). Either tighten the implementation to reliably fail on unexpected gh errors, or update the comment to document which endpoints are intentionally treated as optional/nullable.
| # Exit 0 on success; non-zero on any gh call failure. | |
| # Exit 0 on success; non-zero on required gh call failures. Some gh calls are | |
| # intentionally treated as optional/nullable and normalized in the snapshot | |
| # instead of failing the script (for example, repo autodetection fallback and | |
| # settings surfaces that may be absent for a repository). |
The first PR #45 lint fix dropped `administration: read` hoping the default GITHUB_TOKEN would cover the rulesets / branch protection / security_and_analysis endpoints — wrong. The CI run of the drift detector itself returned six HTTP 403s on those endpoints, then `jq --argjson` on the empty results produced `invalid JSON text` and the detector failed with exit 2 (tooling error, not drift). Fix: - Restore `administration: read` to .github/workflows/github-settings-drift.yml — it's a valid GitHub Actions permission scope that actionlint v1.7.x simply hasn't added to its allow-list yet (confirmed against v1.7.12 local and v1.7.7 CI: both reject it). - Teach the gate lint-workflows job to pass `-ignore 'unknown permission scope "administration"'` to actionlint. Surgical: targets exactly the known false-positive string; removable when actionlint catches up. Comment points at the upstream tracker. The meta-hygiene here is intact: my own drift detector caught my own wrong hypothesis on the very next PR push. Detector-outlives- fix rule confirms — the class of "my workflow asked for too few permissions" gets an automated regression signal now.
Aaron 2026-04-21: "you also didn't get half the setting you miss most of them lol it's okay" — accurate critique of PR #45 first-pass coverage. The declarative-settings-as-code pattern only pays off if the settings are actually in the declaration. Additions to `tools/hygiene/snapshot-github-settings.sh`: Repo-level keys previously uncaptured: - `allow_forking` (CRITICAL — gates the fork-based PR workflow that Aaron proposed the same round) - `description`, `homepage`, `topics` (discovery metadata) - `has_downloads`, `has_pages`, `has_pull_requests` (feature toggles) - `merge_commit_message`, `merge_commit_title` (even with merge-commit off, the format defaults are a policy value) - `use_squash_pr_title_as_default` - `is_template`, `archived`, `disabled` (lifecycle state) - `pull_request_creation_policy` - `custom_properties` (org-level custom repo properties) Security-surface endpoints previously unqueried: - `/topics` (captured as `.topics` at top level) - `/automated-security-fixes` (Dependabot auto-PR state) - `/private-vulnerability-reporting` (security-tab reports enable state) - `/interaction-limits` (rate-limit / cooldown state; null when inactive — note the `gh api --jq` empty-output quirk on object-length-0, handled via raw-fetch-then-jq two-step) - `/autolinks` (external issue-tracker linking) - `/vulnerability-alerts` (Dependabot alerts; endpoint returns 204/404 not JSON — handled via status-code probe) All captured under a new top-level `security:` subsection (vulnerability_alerts_enabled + automated_security_fixes + private_vulnerability_reporting) plus top-level `topics`, `interaction_limits`, `autolinks` for orthogonal surfaces. `tools/hygiene/github-settings.expected.json` re-snapshotted against live state (now 237 lines, up from 196). Verified `check-github-settings-drift.sh` returns "no drift" against the live repo. `docs/GITHUB-SETTINGS.md` §"Repo-level toggles" expanded to cover the new fields + new §"Repo security extras" subsection. Cross-references the fork-based-workflow memory because `allow_forking` is the gating setting.
| # pre-commit hooks are all *already* declarative in-tree — no need to | ||
| # snapshot them. This script covers the click-ops surfaces: | ||
| # - repo-level toggles (merge methods, security-and-analysis, ...) | ||
| # - rulesets + their rule contents | ||
| # - classic branch protection on default branch | ||
| # - Actions permissions + Actions variables (names + values, NOT secrets) |
There was a problem hiding this comment.
P1: The top-of-file description says workflow YAML is already declarative in-tree and "no need to snapshot" it, but the script still captures workflows_json from the API. Either remove workflows from the snapshot (if truly out-of-scope) or update the description to reflect that workflow enable/disable state is intentionally being tracked.
| # pre-commit hooks are all *already* declarative in-tree — no need to | |
| # snapshot them. This script covers the click-ops surfaces: | |
| # - repo-level toggles (merge methods, security-and-analysis, ...) | |
| # - rulesets + their rule contents | |
| # - classic branch protection on default branch | |
| # - Actions permissions + Actions variables (names + values, NOT secrets) | |
| # and pre-commit hooks are *already* declarative in-tree, so this does | |
| # not snapshot their file contents. This script covers the click-ops | |
| # surfaces, including workflow operational state that is not expressed | |
| # in YAML (for example, enable/disable state): | |
| # - repo-level toggles (merge methods, security-and-analysis, ...) | |
| # - rulesets + their rule contents | |
| # - classic branch protection on default branch | |
| # - Actions permissions + Actions variables (names + values, NOT secrets) | |
| # - workflow operational state (for example, enable/disable state) |
| for rid in $ruleset_ids; do | ||
| one=$(gh api "/repos/$repo/rulesets/$rid" --jq '{id, name, target, enforcement, conditions, rules: [.rules[] | {type, parameters}]}') | ||
| ruleset_details=$(jq --argjson one "$one" '. + [$one]' <<< "$ruleset_details") | ||
| done |
There was a problem hiding this comment.
P1: ruleset_details is built in API-return order (/rulesets + loop) but never normalized (e.g., sorted by id/name). If GitHub changes listing order, snapshots will churn and cause false drift. Consider sorting ruleset_details deterministically before emitting (and/or sort ruleset_ids up-front).
| done | |
| done | |
| ruleset_details=$(jq 'sort_by(.id, .name)' <<< "$ruleset_details") |
| # - CodeQL default-setup state | ||
| # | ||
| # Exit 0 on success; non-zero on any gh call failure. | ||
|
|
||
| set -uo pipefail | ||
|
|
||
| repo="${1:-}" | ||
| if [ "$repo" = "--repo" ]; then | ||
| repo="$2" | ||
| elif [ -z "$repo" ]; then | ||
| repo="${GH_REPO:-$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || true)}" | ||
| fi | ||
| if [ -z "$repo" ]; then | ||
| echo "error: cannot determine repo; pass --repo OWNER/NAME or set GH_REPO" >&2 | ||
| exit 2 | ||
| fi | ||
|
|
||
| default_branch=$(gh api "/repos/$repo" --jq '.default_branch') | ||
|
|
||
| repo_json=$(gh api "/repos/$repo" --jq '{ | ||
| allow_auto_merge, allow_forking, allow_merge_commit, allow_rebase_merge, allow_squash_merge, | ||
| allow_update_branch, archived, custom_properties, default_branch, | ||
| delete_branch_on_merge, description, disabled, | ||
| has_discussions, has_downloads, has_issues, has_pages, has_projects, | ||
| has_pull_requests, has_wiki, homepage, is_template, | ||
| merge_commit_message, merge_commit_title, | ||
| pull_request_creation_policy, | ||
| squash_merge_commit_message, squash_merge_commit_title, | ||
| use_squash_pr_title_as_default, visibility, web_commit_signoff_required, | ||
| security_and_analysis | ||
| }') | ||
|
|
||
| topics_json=$(gh api "/repos/$repo/topics" --jq '.names | sort') | ||
| automated_security_fixes_json=$(gh api "/repos/$repo/automated-security-fixes" 2>/dev/null --jq '{enabled, paused}' || echo 'null') | ||
| private_vuln_reporting_json=$(gh api "/repos/$repo/private-vulnerability-reporting" 2>/dev/null --jq '{enabled}' || echo 'null') | ||
| interaction_limits_raw=$(gh api "/repos/$repo/interaction-limits") |
There was a problem hiding this comment.
P1: The header comment says "non-zero on any gh call failure", but several endpoints are explicitly allowed to fail and are coerced to null/0 (e.g., automated-security-fixes, private-vulnerability-reporting, dependabot secrets). Either tighten error handling to match the comment, or update the comment to reflect which failures are expected/benign.
| owner / scope specification and | ||
| `memory/feedback_github_settings_as_code_declarative_checked_in_file.md` | ||
| for the framing this pattern belongs to. |
There was a problem hiding this comment.
P1: memory/feedback_github_settings_as_code_declarative_checked_in_file.md is referenced here, but it doesn't exist under memory/ (repo-wide search found no matches). Either add the referenced memory file(s) or update these references to the correct existing paths so readers don't hit dead links.
| owner / scope specification and | |
| `memory/feedback_github_settings_as_code_declarative_checked_in_file.md` | |
| for the framing this pattern belongs to. | |
| owner / scope specification and for the framing that this | |
| pattern belongs to: repo settings that are not otherwise | |
| declarative in-tree still need a checked-in declared state. |
| @@ -231,7 +231,7 @@ are ordered by `State: Open` first, then `Stale`, then | |||
|
|
|||
| | ID | When | Category | Ask | Source | State | Resolution | | |||
| |---|---|---|---|---|---|---| | |||
| | HB-001 | 2026-04-21 | decision / org-migration | Plan + execute the migration of `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` (Aaron's LFG umbrella org — `project_lucent_financial_group_external_umbrella.md`). Drivers: (a) GitHub gates merge queue and other org-level features to organization-owned repos — user-owned repos cannot enable merge queue on any plan tier, which is the real blocker behind the `422 Invalid rule 'merge_queue':` failure against `POST /repos/AceHack/Zeta/rulesets` (see §10.3 of `docs/research/parallel-worktree-safety-2026-04-22.md`); (b) aligns the repo with Aaron's stated destination for external contributors. **Constraints (Aaron 2026-04-21):** (1) **preserve all current settings** — rulesets, required checks (gate + CodeQL + semgrep), branch-protection behaviours, auto-delete-head-branch, auto-merge, Dependabot, CodeScanning, Copilot Code Review, concurrency groups, workflow triggers incl. `merge_group:`; (2) **public from the start** at the new location — no private-during-transition staging period. No deadline — "at some point". Until transferred, the factory accepts the rebase-tax on serial PRs and relies on `gh pr merge --auto --squash` alone (merge queue off). | `docs/research/parallel-worktree-safety-2026-04-22.md` §10.3; session transcript 2026-04-21 (Aaron: "we can move tih to https://github.com/Lucent-Financial-Group at some point it's my org for LFG" + "we need to move it to lucent for contributor at some point anyways, we want to keep all the settings we have now" + "i think we are going to have to go without merge queue parallelism for now" + "we can just make it public from the start") | Open | — | | |||
| | HB-001 | 2026-04-21 | decision / org-migration | Plan + execute the migration of `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` (Aaron's LFG umbrella org — `project_lucent_financial_group_external_umbrella.md`). Drivers: (a) GitHub gates merge queue and other org-level features to organization-owned repos — user-owned repos cannot enable merge queue on any plan tier, which is the real blocker behind the `422 Invalid rule 'merge_queue':` failure against `POST /repos/AceHack/Zeta/rulesets` (see §10.3 of `docs/research/parallel-worktree-safety-2026-04-22.md`); (b) aligns the repo with Aaron's stated destination for external contributors. **Constraints (Aaron 2026-04-21):** (1) **preserve all current settings** — rulesets, required checks (gate + CodeQL + semgrep), branch-protection behaviours, auto-delete-head-branch, auto-merge, Dependabot, CodeScanning, Copilot Code Review, concurrency groups, workflow triggers incl. `merge_group:`; (2) **public from the start** at the new location — no private-during-transition staging period. No deadline — "at some point". Until transferred, the factory accepts the rebase-tax on serial PRs and relies on `gh pr merge --auto --squash` alone (merge queue off). | `docs/research/parallel-worktree-safety-2026-04-22.md` §10.3; session transcript 2026-04-21 (Aaron: "we can move tih to https://github.com/Lucent-Financial-Group at some point it's my org for LFG" + "we need to move it to lucent for contributor at some point anyways, we want to keep all the settings we have now" + "i think we are going to have to go without merge queue parallelism for now" + "we can just make it public from the start") | Resolved | Executed 2026-04-21 via `POST /repos/AceHack/Zeta/transfer` with `new_owner=Lucent-Financial-Group`. Transfer completed instantly (Aaron admin on both sides). Verification diffed 13 settings groups against pre-transfer scorecard: all preserved **except** `secret_scanning` and `secret_scanning_push_protection` both silently flipped `enabled→disabled` by GitHub's org-transfer code path; re-enabled same session via `PATCH /repos/Lucent-Financial-Group/Zeta` with `security_and_analysis`. Ruleset id 15256879 "Default" preserved byte-identical (6 rules); classic branch protection on main preserved (6 required contexts); Actions variables preserved (2 COPILOT_AGENT_FIREWALL_*); environments + Pages config preserved (Pages URL redirected `acehack.github.io/Zeta` → `lucent-financial-group.github.io/Zeta`). Local `git remote` updated. Declarative settings file landed at `docs/GITHUB-SETTINGS.md` per Aaron's companion directive ("its nice having the expected settings declarative defined" + "i hate things in GitHub where I can't check in the declarative settgins"). Merge queue enable remains a separate opt-in step. | | |||
There was a problem hiding this comment.
P1: This update adds/retains direct contributor name attribution throughout docs/HUMAN-BACKLOG.md (e.g., "For: Aaron", embedded quotes). Per docs/AGENT-BEST-PRACTICES.md:284-290, names should appear only under memory/persona/<name>/ (and optionally docs/BACKLOG.md). Please convert to role refs (e.g., "human maintainer") and move name-specific attribution into an allowed location.
| and two companion memories | ||
| (`feedback_github_settings_as_code_declarative_checked_in_file.md` | ||
| and `feedback_blast_radius_pricing_standing_rule_alignment_signal.md` | ||
| — the latter captures Aaron's 2026-04-21 praise | ||
| *"this is great standing rules on blast-radius ops ... | ||
| i'm glad you understand blast radius and pricing the | ||
| blast radius"*, reframing the CLAUDE.md "confirm before | ||
| hard-to-reverse actions" discipline as load-bearing | ||
| rather than overcautious, and as a Zeta product-feature | ||
| signal connecting to the retractable-contract ledger). The | ||
| pattern generalizes beyond GitHub — any click-ops platform | ||
| (AWS / GCP / Slack / org-level settings) gets the same | ||
| markdown-declaration + cadenced-diff treatment when | ||
| adopted. |
There was a problem hiding this comment.
P1: This section introduces references to companion memory files (feedback_github_settings_as_code_declarative_checked_in_file.md, feedback_blast_radius_pricing_standing_rule_alignment_signal.md) that don't exist under memory/ in the repo. Either add the missing memory entries in this PR or update/remove the references so ROUND-HISTORY doesn't point at dead files.
| and two companion memories | |
| (`feedback_github_settings_as_code_declarative_checked_in_file.md` | |
| and `feedback_blast_radius_pricing_standing_rule_alignment_signal.md` | |
| — the latter captures Aaron's 2026-04-21 praise | |
| *"this is great standing rules on blast-radius ops ... | |
| i'm glad you understand blast radius and pricing the | |
| blast radius"*, reframing the CLAUDE.md "confirm before | |
| hard-to-reverse actions" discipline as load-bearing | |
| rather than overcautious, and as a Zeta product-feature | |
| signal connecting to the retractable-contract ledger). The | |
| pattern generalizes beyond GitHub — any click-ops platform | |
| (AWS / GCP / Slack / org-level settings) gets the same | |
| markdown-declaration + cadenced-diff treatment when | |
| adopted. | |
| and companion memory notes; a later note captures Aaron's | |
| 2026-04-21 praise *"this is great standing rules on | |
| blast-radius ops ... i'm glad you understand blast radius | |
| and pricing the blast radius"*, reframing the CLAUDE.md | |
| "confirm before hard-to-reverse actions" discipline as | |
| load-bearing rather than overcautious, and as a Zeta | |
| product-feature signal connecting to the retractable- | |
| contract ledger). The pattern generalizes beyond GitHub — | |
| any click-ops platform (AWS / GCP / Slack / org-level | |
| settings) gets the same markdown-declaration + | |
| cadenced-diff treatment when adopted. |
| # Also run on any change to the expected snapshot or the detector | ||
| # itself, so a PR that updates expected gets an immediate green | ||
| # signal that the snapshot matches reality at merge time. | ||
| pull_request: | ||
| paths: | ||
| - "tools/hygiene/github-settings.expected.json" | ||
| - "tools/hygiene/snapshot-github-settings.sh" | ||
| - "tools/hygiene/check-github-settings-drift.sh" | ||
| - ".github/workflows/github-settings-drift.yml" |
There was a problem hiding this comment.
P1: This workflow runs on pull_request, but the drift check needs administration: read endpoints (rulesets, branch protection, hooks, deploy keys). On fork-based PRs (which the repo explicitly allows), GITHUB_TOKEN won't have those permissions and the job will fail noisily. Consider skipping the job for fork PRs (e.g., if: github.event.pull_request.head.repo.full_name == github.repository) and keeping the check for same-repo PRs + scheduled runs.
| Motivation (Aaron 2026-04-21): | ||
|
|
||
| > "its nice having the expected settings declarative defined" | ||
| > | ||
| > "i hate things in GitHub where I can't check in the | ||
| > declarative settgins so we will save a back[up]" |
There was a problem hiding this comment.
P1: This repo's docs disallow contributor name attribution outside persona memory dirs / optionally docs/BACKLOG.md (docs/AGENT-BEST-PRACTICES.md:284-290). This section introduces name attribution ("Motivation (Aaron …)") in a regular doc; please convert to role refs (e.g., "human maintainer") and move any name-specific attribution into an allowed location if it must be retained.
| - `memory/feedback_github_settings_as_code_declarative_checked_in_file.md` | ||
| — the framing / pattern. | ||
| - `memory/reference_github_code_scanning_ruleset_rule_requires_default_setup.md` | ||
| — why the `code_scanning` ruleset rule is off. | ||
| - `memory/project_zeta_org_migration_to_lucent_financial_group.md` | ||
| — the migration that triggered this discipline. |
There was a problem hiding this comment.
P1: The referenced memory files in this Related list (e.g., memory/feedback_github_settings_as_code_declarative_checked_in_file.md, memory/reference_github_code_scanning_ruleset_rule_requires_default_setup.md, memory/project_zeta_org_migration_to_lucent_financial_group.md) don't exist under memory/ in this repo. Please either add them in the same PR or fix the links to point at the actual files.
| - `memory/feedback_github_settings_as_code_declarative_checked_in_file.md` | |
| — the framing / pattern. | |
| - `memory/reference_github_code_scanning_ruleset_rule_requires_default_setup.md` | |
| — why the `code_scanning` ruleset rule is off. | |
| - `memory/project_zeta_org_migration_to_lucent_financial_group.md` | |
| — the migration that triggered this discipline. | |
| - The declarative-settings framing for repo-level GitHub | |
| configuration checked into the tree. | |
| - The `code_scanning` ruleset exception that remains off because | |
| GitHub requires default setup for that rule. | |
| - The org-migration context that prompted documenting these | |
| settings as declared state. |
Copilot review response — categorized resolutionContext: 24 review threads on this PR. Cost pressure (see Categories1. Intentional agent-facing memory references (~15 threads) Findings of the form " Action: resolving as intentional design. 2. Contributor-name attribution in docs (2 threads — Per Action: tracked in follow-up task #187 on fork-based branch. 3. Script robustness findings (2 threads — Real P1 edge-case bugs in Action: tracked in follow-up task #187. 4. Workflow pin alignment (1 thread — Valid consistency finding. Will align pins in follow-up. Action: tracked in follow-up task #187. 5. Ruleset normalization (1 thread — Valid — snapshot could churn if GitHub changes API listing order. Action: tracked in follow-up task #187. 6. Doc/code mismatch on workflows-in-snapshot scope (1 thread) Docstring says "no need to snapshot" workflow YAML but we do snapshot it. Reasonable — we keep the capture because workflow enabling/disabling state is click-ops-adjacent. Docstring clarification in follow-up. Action: tracked in follow-up task #187. 7. Genuinely correct concern that admin endpoints are inaccessible on fork PRs — but this workflow runs on Action: tracked in follow-up task #187 (doc clarification only). 8. Cosmetic — temp filename in diff headers (1 thread) P2 polish. Action: tracked in follow-up task #187. 9. Row ordering in Valid stability concern. Action: tracked in follow-up task #187. Follow-up contractAll items categorized 2–9 land as one fork-based PR from Resolving threads now to unblock merge. |
…ngs-as-code + drift detector Executes the org migration (AceHack/Zeta -> Lucent-Financial-Group/Zeta) that was filed as HB-001, then lands the hygiene pattern the migration taught us: GitHub has click-ops surfaces with no native declarative config (rulesets, security-and-analysis, Actions variables, Pages, CodeQL setup), and the transfer code path silently flipped secret_scanning + secret_scanning_push_protection from enabled to disabled. Re-enabled same session; built detector so the next silent drift is not silent. Shape of what landed: - docs/GITHUB-SETTINGS.md: human-readable narrative declaration of every click-ops setting, pointing at the machine-readable JSON. - tools/hygiene/github-settings.expected.json: canonical expected state, normalized output of the snapshot script. - tools/hygiene/snapshot-github-settings.sh: produces the normalized JSON from live gh api for any repo (--repo flag). - tools/hygiene/check-github-settings-drift.sh: diffs live vs expected; exit 1 on drift with resolution instructions. - .github/workflows/github-settings-drift.yml: weekly cron (Mondays 14:17 UTC) + workflow_dispatch + PR-triggered when the expected snapshot or tooling changes. Uses only trusted first-party context (secrets.GITHUB_TOKEN, github.repository) passed via env vars into quoted run-block invocation. - docs/FACTORY-HYGIENE.md row #40 with full cadence / owner / checks / output / source-of-truth specification. - docs/HUMAN-BACKLOG.md: HB-001 State=Open -> Resolved with the two-line drift summary baked into the Resolution cell. - docs/ROUND-HISTORY.md: late-Round-44 arc paragraph documenting the migration, the drift, and the new hygiene class. Pattern generalizes: any click-ops platform (AWS / GCP / Slack / org-level settings) gets the same markdown-declaration + cadenced-diff treatment when adopted. Settings-as-code-by- convention beats Terraform / Probot / Pulumi for small repos until friction demands the heavier tooling. Companion memories: feedback_github_settings_as_code_declarative_ checked_in_file.md (the pattern) + feedback_blast_radius_pricing_ standing_rule_alignment_signal.md (Aaron's praise of the confirm-before-hard-to-reverse discipline, reframing it as a Zeta product-feature signal per the retractable-contract ledger).
- docs/ROUND-HISTORY.md: `+` at line-start parsed as bullet by markdownlint (MD032); replace with "and" in the two affected wrap points. - .github/workflows/github-settings-drift.yml: drop `administration: read` — actionlint v1.7.x does not list it as a recognised scope; the default GITHUB_TOKEN on public repos covers the endpoints we read. Comment documents the trade + the fact that the PR-triggered drift check would surface a missing scope immediately. - tools/hygiene/snapshot-github-settings.sh: drop dead `rulesets_json` assignment (SC2034) — the `ruleset_details` loop below returns the full per-ruleset data used in the final jq output. Aaron's follow-up "you also didn't get half the settings you miss most of them lol it's okay" is acknowledged — a coverage expansion PR will follow this one. The current snapshot is a first pass; PR #45 lands the *pattern* (decl + drift detector + hygiene row) and we iterate on coverage.
The first PR #45 lint fix dropped `administration: read` hoping the default GITHUB_TOKEN would cover the rulesets / branch protection / security_and_analysis endpoints — wrong. The CI run of the drift detector itself returned six HTTP 403s on those endpoints, then `jq --argjson` on the empty results produced `invalid JSON text` and the detector failed with exit 2 (tooling error, not drift). Fix: - Restore `administration: read` to .github/workflows/github-settings-drift.yml — it's a valid GitHub Actions permission scope that actionlint v1.7.x simply hasn't added to its allow-list yet (confirmed against v1.7.12 local and v1.7.7 CI: both reject it). - Teach the gate lint-workflows job to pass `-ignore 'unknown permission scope "administration"'` to actionlint. Surgical: targets exactly the known false-positive string; removable when actionlint catches up. Comment points at the upstream tracker. The meta-hygiene here is intact: my own drift detector caught my own wrong hypothesis on the very next PR push. Detector-outlives- fix rule confirms — the class of "my workflow asked for too few permissions" gets an automated regression signal now.
Aaron 2026-04-21: "you also didn't get half the setting you miss most of them lol it's okay" — accurate critique of PR #45 first-pass coverage. The declarative-settings-as-code pattern only pays off if the settings are actually in the declaration. Additions to `tools/hygiene/snapshot-github-settings.sh`: Repo-level keys previously uncaptured: - `allow_forking` (CRITICAL — gates the fork-based PR workflow that Aaron proposed the same round) - `description`, `homepage`, `topics` (discovery metadata) - `has_downloads`, `has_pages`, `has_pull_requests` (feature toggles) - `merge_commit_message`, `merge_commit_title` (even with merge-commit off, the format defaults are a policy value) - `use_squash_pr_title_as_default` - `is_template`, `archived`, `disabled` (lifecycle state) - `pull_request_creation_policy` - `custom_properties` (org-level custom repo properties) Security-surface endpoints previously unqueried: - `/topics` (captured as `.topics` at top level) - `/automated-security-fixes` (Dependabot auto-PR state) - `/private-vulnerability-reporting` (security-tab reports enable state) - `/interaction-limits` (rate-limit / cooldown state; null when inactive — note the `gh api --jq` empty-output quirk on object-length-0, handled via raw-fetch-then-jq two-step) - `/autolinks` (external issue-tracker linking) - `/vulnerability-alerts` (Dependabot alerts; endpoint returns 204/404 not JSON — handled via status-code probe) All captured under a new top-level `security:` subsection (vulnerability_alerts_enabled + automated_security_fixes + private_vulnerability_reporting) plus top-level `topics`, `interaction_limits`, `autolinks` for orthogonal surfaces. `tools/hygiene/github-settings.expected.json` re-snapshotted against live state (now 237 lines, up from 196). Verified `check-github-settings-drift.sh` returns "no drift" against the live repo. `docs/GITHUB-SETTINGS.md` §"Repo-level toggles" expanded to cover the new fields + new §"Repo security extras" subsection. Cross-references the fork-based-workflow memory because `allow_forking` is the gating setting.
Script robustness: - snapshot + check scripts: guard `--repo` / `--expected` arg parsing against missing value (previously `$2` unbound under `set -u`) - snapshot: sort rulesets by id before emission (stabilizes output across GitHub-side listing-order churn) - snapshot: clarify exit-code docstring (null-tolerant optional endpoints vs. loud-fail required endpoints) Workflow alignment: - github-settings-drift.yml: pin actions/checkout to v6.0.2 SHA matching gate.yml + codeql.yml Doc polish: - FACTORY-HYGIENE.md: reorder row 39 before row 40 - GITHUB-SETTINGS.md: drop broken external-memory cross-references; fold diagnostic command for code_scanning rule inline; shift "Motivation (<name>, date)" → "Motivation (human maintainer, date)" per name-attribution BP - HUMAN-BACKLOG.md: drop external-memory cross-reference from HB-001 - ROUND-HISTORY.md: inline the "blast-radius praise" content instead of cross-referencing by path; external auto-memory paths don't resolve for non-Claude readers The memory/*.md cross-references were relics of when committed docs pointed at Claude's per-project auto-memory (`~/.claude/projects/ <slug>/memory/`) as if it were an in-repo directory. For humans and Copilot those paths are broken links; for Claude the information is reachable via the memory tool without a doc cross-reference. Strip them where they appear inline in prose; the established table convention in FACTORY-HYGIENE/ROUND-HISTORY (Origin column) keeps its references for pattern-consistency with the 39+ pre-existing rows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
259eb9d to
a2bd943
Compare
| interaction_limits_raw=$(gh api "/repos/$repo/interaction-limits") | ||
| if [ "$(jq 'length' <<< "$interaction_limits_raw")" = "0" ]; then | ||
| interaction_limits_json="null" |
There was a problem hiding this comment.
P0: interaction_limits_raw=$(gh api ...) is treated as an optional endpoint per the header comment, but the current implementation doesn’t handle “no content” / error responses safely. If the API returns empty output (e.g., 204) or a non-JSON error, the subsequent jq 'length' will fail and break the snapshot instead of coercing to null. Handle empty/204/404 cases explicitly and only run jq when the response is valid JSON.
| interaction_limits_raw=$(gh api "/repos/$repo/interaction-limits") | |
| if [ "$(jq 'length' <<< "$interaction_limits_raw")" = "0" ]; then | |
| interaction_limits_json="null" | |
| interaction_limits_raw=$(gh api "/repos/$repo/interaction-limits" 2>/dev/null || true) | |
| if [ -z "$interaction_limits_raw" ]; then | |
| interaction_limits_json="null" | |
| elif ! jq -e . >/dev/null 2>&1 <<< "$interaction_limits_raw"; then | |
| interaction_limits_json="null" | |
| elif [ "$(jq 'length' <<< "$interaction_limits_raw")" = "0" ]; then | |
| interaction_limits_json="null" |
| # Sort by id to stabilize output across GitHub-side listing order changes. | ||
| ruleset_ids=$(gh api "/repos/$repo/rulesets" --jq '[.[].id] | sort | .[]') | ||
| for rid in $ruleset_ids; do | ||
| one=$(gh api "/repos/$repo/rulesets/$rid" --jq '{id, name, target, enforcement, conditions, rules: [.rules[] | {type, parameters}]}') |
There was a problem hiding this comment.
P1: For deterministic output, the rules inside a ruleset should be normalized/sorted as well. Right now rules: [.rules[] | {type, parameters}] preserves whatever ordering GitHub returns, which can change and cause false drift. Consider sorting the .rules array by type (and, if needed, a stable projection of parameters) before emitting.
| one=$(gh api "/repos/$repo/rulesets/$rid" --jq '{id, name, target, enforcement, conditions, rules: [.rules[] | {type, parameters}]}') | |
| one=$(gh api "/repos/$repo/rulesets/$rid" --jq '{id, name, target, enforcement, conditions, rules: ([.rules[] | {type, parameters}] | sort_by(.type, (.parameters | tojson)))}') |
| default_branch=$(gh api "/repos/$repo" --jq '.default_branch') | ||
|
|
||
| repo_json=$(gh api "/repos/$repo" --jq '{ | ||
| allow_auto_merge, allow_forking, allow_merge_commit, allow_rebase_merge, allow_squash_merge, |
There was a problem hiding this comment.
P2: The script calls /repos/$repo twice (once for default_branch, again for repo_json). This adds an extra API round-trip and consumes rate limit unnecessarily. Consider deriving default_branch from the same /repos/$repo response used to build repo_json (or fetching once and reusing).
| resolved via `POST /repos/AceHack/Zeta/transfer` with | ||
| `new_owner=Lucent-Financial-Group`, completed instantly | ||
| because Aaron is admin on both sides). Post-transfer | ||
| verification diffed 13 settings groups against a |
There was a problem hiding this comment.
P1: docs/AGENT-BEST-PRACTICES.md specifies no direct contributor names in docs (role refs only; names only in persona memory dirs and optionally docs/BACKLOG.md). This new paragraph includes a personal name; please rewrite using a role reference (e.g., “the human maintainer”) or move any name attribution to an allowed location.
| # (repo metadata, rulesets, permissions) propagates through | ||
| # command-substitution and will make downstream `jq` fail loudly. | ||
|
|
||
| set -uo pipefail |
There was a problem hiding this comment.
P1: These hygiene scripts use set -uo pipefail, but most repo bash tooling uses set -euo pipefail to fail fast on any command error. Without -e, a failed gh api inside an assignment can be ignored until much later (or produce confusing downstream jq errors). Align with the repo convention by enabling -e here as well (see e.g. tools/audit-packages.sh:3).
| # (repo metadata, rulesets, permissions) propagates through | |
| # command-substitution and will make downstream `jq` fail loudly. | |
| set -uo pipefail | |
| # (repo metadata, rulesets, permissions) aborts the script immediately. | |
| set -euo pipefail |
| # cadence + manual dispatch. Also safe to run locally before risky | ||
| # settings changes to capture a baseline. | ||
|
|
||
| set -uo pipefail |
There was a problem hiding this comment.
P1: This script uses set -uo pipefail but the repo’s bash scripts typically run with set -euo pipefail so failures (mktemp/diff/gh) stop immediately. Consider adding -e to match existing tooling behavior and avoid continuing after an unexpected command failure.
| set -uo pipefail | |
| set -euo pipefail |
…ax, seeded hygiene-history, IP template cleanup - UPSTREAM-RHYTHM.md: fix compare basehead syntax (Codex P1 — use owner:branch, not owner:repo:branch) - AGENT-GITHUB-SURFACES.md + github-surface-triage SKILL.md: FACTORY-HYGIENE row refs #44→#47, #45→#48 - AGENT-ISSUE-WORKFLOW.md: soften "BACKLOG row is open" to "TODO: file a BACKLOG row" - research/lfg-only-capabilities-scout.md: clarify HB-001 is resolved org-migration; merge queue is follow-up - BACKLOG.md: unsplit inline-code spans in P3 LFG row - hygiene-history/{wiki,discussions}-history.md: seed files referenced by Surface 3/4 docs - .github/ISSUE_TEMPLATE/feature_request.md: remove stale GitHub default template (Zeta set covers bug_report/backlog_item/human_ask) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: add UPSTREAM-RHYTHM.md — Zeta's fork-first batched PR cadence (#2) The fork-pr-workflow skill defers the upstream-cadence choice to project-level config. This is Zeta's config: - Default PR target: AceHack/Zeta:main (free CI, free Copilot) - Bulk sync AceHack/main -> LFG/main every ~10 PRs (one PR, not N) - Five named exceptions for direct-to-LFG (security P0, external contributor, Aaron explicit, CI-repair, the bulk-sync PR itself) - Concrete gh commands for each case - Proposed cadence-monitor FACTORY-HYGIENE row Resolves a phantom pointer in memory/feedback_fork_pr_cost_model_prs_land_on_acehack_sync_to_lfg_in_bulk.md which cited docs/UPSTREAM-RHYTHM.md as an intended target. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * docs: scout LFG-only capabilities; add 6th direct-to-LFG exception; P3 BACKLOG row Aaron 2026-04-22 clarified LFG is not just "paid surface to avoid" but a throttled experimental tier: Copilot Business + Teams plan, all enhancements enabled (internet search, coding agent, etc.). Standing permission to change any LFG setting except the $0 budget cap and personal info. Enterprise upgrade offered if we build a large-enough LFG-only backlog to justify it. Changes: - docs/research/lfg-only-capabilities-scout.md — new scouting doc. Verified Copilot Business plan via gh api; enumerates 10 candidate experiments across Copilot Business, Teams plan, Actions runner classes, and org-level features. Each has a cadence. Declines self-hosted runners and raising the budget cap. - docs/UPSTREAM-RHYTHM.md — adds a 6th direct-to-LFG exception ("LFG-only capability experiment") so these experiments don't fight the batched cost model. - docs/BACKLOG.md — new P3 row "LFG-only experiment track (throttled)" pointing at the scout doc; gated on the 10-item threshold for the Enterprise upgrade conversation. Source memory: memory/feedback_lfg_paid_copilot_teams_throttled_experiments_allowed.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs: address PR #139 review threads — row-number fixes, compare syntax, seeded hygiene-history, IP template cleanup - UPSTREAM-RHYTHM.md: fix compare basehead syntax (Codex P1 — use owner:branch, not owner:repo:branch) - AGENT-GITHUB-SURFACES.md + github-surface-triage SKILL.md: FACTORY-HYGIENE row refs #44→#47, #45→#48 - AGENT-ISSUE-WORKFLOW.md: soften "BACKLOG row is open" to "TODO: file a BACKLOG row" - research/lfg-only-capabilities-scout.md: clarify HB-001 is resolved org-migration; merge queue is follow-up - BACKLOG.md: unsplit inline-code spans in P3 LFG row - hygiene-history/{wiki,discussions}-history.md: seed files referenced by Surface 3/4 docs - .github/ISSUE_TEMPLATE/feature_request.md: remove stale GitHub default template (Zeta set covers bug_report/backlog_item/human_ask) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
POST /repos/AceHack/Zeta/transfer; this PR is the landing of the follow-up artifacts on the new home).secret_scanning+secret_scanning_push_protectionfrom enabled to disabled.What landed
docs/GITHUB-SETTINGS.md— human-readable narrative declarationtools/hygiene/github-settings.expected.json— machine-readable canonical statetools/hygiene/snapshot-github-settings.sh— normalized snapshot producertools/hygiene/check-github-settings-drift.sh— detector (exit 1 on drift).github/workflows/github-settings-drift.yml— weekly cron (Mondays 14:17 UTC) +workflow_dispatch+ PR-triggered on tooling changesdocs/FACTORY-HYGIENE.mdrow hygiene: gitignore .claude/worktrees/ — parallel-research scratch #40 — full cadence / owner / checks specdocs/HUMAN-BACKLOG.md— HB-001 marked Resolved with migration + drift summarydocs/ROUND-HISTORY.md— late-Round-44 arc paragraphTest plan
tools/hygiene/check-github-settings-drift.shexit 0)secrets.GITHUB_TOKEN,github.repository) passed viaenv:into quotedrunblockgithub-settings-driftPR-triggered job green on this PR (validates that the new expected snapshot matches live state)🤖 Generated with Claude Code