Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/rulesets/ci-rule.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"bypass_actors": [],
"conditions": {
"ref_name": {
"exclude": [],
"include": [
"~DEFAULT_BRANCH"
]
}
},
"enforcement": "active",
"name": "ci-rule",
"rules": [
{
"parameters": {
"do_not_enforce_on_create": false,
"required_status_checks": [
{
"context": "ci-ok",
"integration_id": 15368
}
],
"strict_required_status_checks_policy": false
},
"type": "required_status_checks"
}
],
"target": "branch"
}
2 changes: 1 addition & 1 deletion .github/rulesets/main-rule.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"require_code_owner_review": true,
"require_extra_approval_for_unattributed_changes": true,
"require_last_push_approval": true,
"required_approving_review_count": 0,
"required_approving_review_count": 1,
"required_review_thread_resolution": true,
"required_reviewers": []
},
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ Each release moves `plugin.json`'s `version`, the flake package version and ever
together. A consumer adopts one by bumping its flake input, its workflow `@v…`/`devkit-ref` and its
marketplace `ref` at once.

## v0.10.0 — 2026-10-14

- `ruleset-sync` manages every manifest in `.github/rulesets/`, matched by name, and adds
`ci-rule`: `ci-ok` from GitHub Actions (integration 15368) required, non-strict, no bypass actors;
`main-rule` now needs 1 approval, and `bypass-extras.json` applies to it alone (#112). `just
rulesets-apply` after this release creates `ci-rule` with no bypass and raises `main-rule` to 1
approval in each repo, and moves marola-devkit's own admins from `always` to `pull_request`.
Apply `ci-rule` only to a repo whose CI already posts a `ci-ok` check from GitHub Actions on every
PR (no path filter on the workflow that posts it): it has no bypass actors, so it would block
every merge elsewhere. `check` and `apply` now fail on a live response without `bypass_actors`.

## v0.9.0 — 2026-10-11

- The `Cost:` commit trailer is out of scope for marola, and the tooling around it is gone (#103):
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ claude --plugin-dir plugins/marola-devkit # the plugin, from this checkout
| `scripts/` | The tools the flake puts on `PATH`, each with its `lib/` and `fixtures/` beside it |
| `plugins/marola-devkit/` | The Claude Code plugin: skills, agents, hooks; listed by `.claude-plugin/marketplace.json` |
| `agents/invariants.md` | The org invariants block every repo's AGENTS.md carries |
| `.github/` | `labels.yml`, `ISSUE_TEMPLATE/`, the PR template, `rulesets/main-rule.json`, and the reusable workflows |
| `.github/` | `labels.yml`, `ISSUE_TEMPLATE/`, the PR template, `rulesets/` (`main-rule.json`, `ci-rule.json`), and the reusable workflows |
| `.githooks/` | The opt-in pre-commit and pre-push hooks |
| `devkit.just` | The just module consumers import |
| `flake.nix` | Packages, apps, the base dev shell, and `lib.<system>` for consumers |
Expand All @@ -44,7 +44,7 @@ claude --plugin-dir plugins/marola-devkit # the plugin, from this checkout
| `docs-lint` | MIP-0074's stale-content check over `README.md` and `docs/**/*.md` |
| `skills-vendor` | Pin vendored skills to an upstream commit in `skills.lock` (`check`, `outdated`, `update`, `init`) |
| `wiring` | MIP-0076's cross-repo wiring tables (artifacts, dispatches, pin bumps, deploys), parsed from an umbrella checkout and its submodules |
| `ruleset-sync` | Check or apply `.github/rulesets/main-rule.json`'s branch ruleset to a repo, or every repo of an org |
| `ruleset-sync` | Check or apply the branch rulesets in `.github/rulesets/` (`main-rule`, `ci-rule`) to a repo, or every repo of an org |
| `api-docs-push` | Force-push a directory as one orphan commit to a branch; the `api-docs` workflow's push step |
| `graph` | A pinned graphify, offline and keyless, its graph in the user cache: `build`, `query`, `path`, `explain` |
| `mip-resolve` | Find a MIP or `.tasks.md` in the repo, the umbrella checkout, or via `gh api` |
Expand Down Expand Up @@ -73,7 +73,7 @@ The vendored skills are pinned in `plugins/marola-devkit/skills/skills.lock`
that read MIPs fall back to the umbrella's `docs/MIPs` through `gh api`; nothing reads another
repo's tree.
- **Publishes**, at each `v*` tag: the flake (packages, apps, `lib.<system>`), the plugin
marketplace, the reusable workflows, the invariants block, the labels, issue forms and ruleset.
marketplace, the reusable workflows, the invariants block, the labels, issue forms and rulesets.
- **Pinned by:** every marola repo, in three places bumped together: the flake input, every
workflow `@v…` and `devkit-ref`, and the marketplace `ref` in `.claude/settings.json`.

Expand Down Expand Up @@ -177,11 +177,13 @@ devkit's copy (`MAROLA_INVARIANTS_BLOCK` is set by the wrapper).
**Issue forms and labels** — `.github/labels.yml`, `.github/ISSUE_TEMPLATE/` and the PR template
are the same in every repo, so `agent-ready` means the same thing everywhere (MIP-0070 §5.7).

**Ruleset sync** — `.github/rulesets/main-rule.json` is the one branch ruleset every marola repo
carries (the org is on GitHub Free, so an org-level ruleset isn't available). `ruleset-sync check
[owner/repo ...]` compares a repo's live ruleset against it (default: the caller's repo) and exits
non-zero on a miss or a diff; `ruleset-sync apply <owner/repo ...>` creates or updates it, `--dry-run`
previews the `gh api` call instead of making it. `--all-org ORG` runs either over every non-archived,
**Ruleset sync** — every marola repo carries the two branch rulesets in `.github/rulesets/` (the org
is on GitHub Free, so an org-level ruleset isn't available): `main-rule` (a squash PR with 1
approval; admins, and on a consumer `marola-merge-bot`, may bypass it on a PR) and `ci-rule`
(`ci-ok` from GitHub Actions; no bypass actors). `ruleset-sync check [owner/repo ...]` compares a
repo's live rulesets against them by name (default: the caller's repo) and exits non-zero on a miss
or a diff; `ruleset-sync apply <owner/repo ...>` creates or updates each, `--dry-run` previews the
`gh api` calls instead of making them. `--all-org ORG` runs either over every non-archived,
non-fork repo of an org (`--include-forks` adds forks). `just rulesets-check`/`just rulesets-apply` wrap both.

**Docs lint** — `docs-lint [repo-root]` runs MIP-0074 §7's rules over `README.md` and
Expand Down
4 changes: 2 additions & 2 deletions devkit.just
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ pr-labels-backfill *args:
labels-sync *args:
issues labels sync {{ args }}

# Check the repo's branch ruleset against the devkit's manifest.
# Check the repo's branch rulesets (main-rule, ci-rule) against the devkit's manifests.
rulesets-check *args:
ruleset-sync check {{ args }}

# Apply the devkit's ruleset manifest to the repo.
# Apply the devkit's ruleset manifests to the repo.
rulesets-apply *args:
ruleset-sync apply {{ args }}

Expand Down
2 changes: 1 addition & 1 deletion docs/1-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ product code and reads no other repo's tree.
| `.githooks/` | `pre-commit` and `pre-push`, which run the consuming repo's own recipes | `core.hooksPath`, opt-in ([hooks](4-reference_hooks.md)) |
| `plugins/marola-devkit/` | The Claude Code plugin: skills, agents, hooks ([plugin](4-reference_plugin.md)) | The marketplace in `.claude-plugin/marketplace.json`, pinned by `ref` |
| `agents/invariants.md` | The org invariants block | `agents-check` compares a repo's AGENTS.md with it |
| `.github/` | `labels.yml`, `ISSUE_TEMPLATE/`, the PR template, `rulesets/main-rule.json`, the reusable workflows ([workflows](4-reference_workflows.md)) | Labels by `issues labels sync`, the ruleset by `ruleset-sync`, the forms and template copied as they are; workflows by `uses:` |
| `.github/` | `labels.yml`, `ISSUE_TEMPLATE/`, the PR template, `rulesets/` (`main-rule.json`, `ci-rule.json`), the reusable workflows ([workflows](4-reference_workflows.md)) | Labels by `issues labels sync`, the rulesets by `ruleset-sync`, the forms and template copied as they are; workflows by `uses:` |
| `.claude/statusline.sh` | A generic status line | `.devkit/.claude/statusline.sh` |
| `flake.nix` | The package, one app per tool, `lib.<system>`, this repo's dev shell and checks | A flake input |
| `tests/self-tests.sh` | Every `--self-test` in one run | This repo only |
Expand Down
2 changes: 1 addition & 1 deletion docs/4-reference_tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extension rather than a devkit tool.
| `pr-label` | `pr-label.sh` | `just pr-label` | Apply the deterministic `area/*`, `layer/*`, `kind/*` taxonomy (`scripts/lib/pr_labels.sh`) to one PR |
| `backfill-pr-labels` | `backfill-pr-labels.sh` | `just pr-labels-backfill` | The same for every merged or closed PR with no label; `--limit`, `--nlp` (compare with `pr-label-nlp`), `--nlp-apply-unscoped` (fill only an `area/unscoped` gap) |
| `pr-label-nlp` | `pr_label_nlp.py` | (none) | A local TF-IDF classifier for `area/*`, kept to compare against the deterministic one, never applied on its own. `--pr N` or `--title`/`--body` |
| `ruleset-sync` | `ruleset-sync.sh` | `just rulesets-check`, `just rulesets-apply` | `check [owner/repo…]` diffs a repo's live branch ruleset against `.github/rulesets/main-rule.json`, plus that repo's bypass actors in `.github/rulesets/bypass-extras.json` (each with a reason; the `@consumers` key applies to every repo in `.github/consumers.txt`, `--consumers FILE` to override); `apply <owner/repo…>` creates or updates it; `--all-org ORG` covers an org |
| `ruleset-sync` | `ruleset-sync.sh` | `just rulesets-check`, `just rulesets-apply` | `check [owner/repo…]` diffs a repo's live branch rulesets, matched by name, against each manifest in `.github/rulesets/`: `main-rule.json` (a squash PR with 1 approval; org and repo admins bypass on a PR), plus that repo's bypass actors in `.github/rulesets/bypass-extras.json` (each with a reason; the `@consumers` key applies to every repo in `.github/consumers.txt`, `--consumers FILE` to override), and `ci-rule.json` (`ci-ok` from GitHub Actions, integration 15368, non-strict; no bypass actors, and no extras); prints `<repo> <ruleset>: ok\|drifted\|missing`; `apply <owner/repo…>` creates or updates each; `--manifest FILE`, once or more, replaces the set; `--all-org ORG` covers an org |
| `merge-key-check` | `merge_key_check.py` | `just quality` | Fails when `MERGE_BOT_KEY` appears in a workflow outside the allowlist `.github/merge-key-allowlist` (workflow files, one repo-relative path per line, `#` comments), or in a job that runs third-party actions or fetch/build commands, calls a reusable workflow (except a listed local one), sets `container`/`services` or a shell other than bash or sh, or sits beside workflow-level env carrying the key (MIP-0086 §5.6). Once the allowlist exists, `secrets: inherit` is flagged repo-wide |
| `agents-check` | `agents-check.sh` | `just agents-check` | Compare AGENTS.md's invariants block byte for byte with the pinned devkit's `agents/invariants.md` (`--block` or `MAROLA_INVARIANTS_BLOCK` to use another) |
| `docs-lint` | `docs_lint.py` | (none) | MIP-0074 §7's stale-content check over `README.md` and `docs/**/*.md`: undefined or unmarked foreign recipes, another repo's paths, split-era wording, `docs/index.md`, links leaving the repo |
Expand Down
Loading
Loading