| name | murmurent-push |
| description | Murmurent-aware stage/commit/push for a murmurent-enabled repo. Excludes per-machine + secret-shaped files, refuses to commit large files that belong in append_only/, never touches /data/lab_vm/immutable|append_only (or the legacy raw|refined), and posts a release note to the project's own Slack channel after the push. |
| user_invocable | true |
Stage changed files, create a descriptive commit, push to the remote tracking branch, and post a Slack release note — but with murmurent-specific safety rules that go beyond plain git. This skill is for murmurent-ready repos — a .murmurent.yaml readiness marker (or a legacy CHARTER.md bootstrap) at the root. For a non-murmurent repo, use /commit-push instead.
Pre-flight (do this BEFORE staging)
-
Confirm the repo is murmurent-ready. The working tree must contain .murmurent.yaml (the readiness marker) or a legacy CHARTER.md at its root. If neither, stop and tell the user "this repo isn't murmurent-ready — use /commit-push instead." (A legacy CHARTER.md also means: suggest murmurent repo upgrade after the push.)
-
Refuse if the diff touches /data/lab_vm/immutable/ or /data/lab_vm/append_only/ (or the legacy raw//refined/, which stay recognized during the transition). Those paths are governed per murmurent's data-storage rule (rules/data-storage.md): immutable/ is read-only source data and append_only/ is append-only outputs. The hook layer would block it anyway, but check git status for any path matching ^data/lab_vm/(immutable|append_only|raw|refined)/ and refuse the commit with a clear explanation. (Note: project repos under ~/repos/<name>/ should never contain such paths; this guard catches misconfigured .gitignore or symlinks.)
-
Refuse secret-shaped filenames. Before staging, run git status --porcelain and look for changes to filenames matching any of:
*.env (but .env.example is OK if values are placeholders)
*.pem, *.p12, *.pfx, *.key
id_rsa, id_ed25519, id_dsa, id_ecdsa (any id_* private-key file)
*_rsa, *_ed25519 (alternate private-key names)
*passphrase*, *credentials*, .netrc, .pgpass, .aws/credentials
*.crt containing a private key (rare; check content if present)
If any are present, stop, list them, and ask the user to confirm or remove. Do NOT proceed by default.
-
Scan staged content for secrets. Filename checks (step 3) miss a hardcoded credential inside a .py/.R/.yaml/.sh file. After staging (or against the already-staged set), run the deterministic content scanner:
murmurent security secrets-scan --staged
It scans the STAGED blobs (exactly what the push would publish) and prints a redacted table — it never echoes the raw secret. Gate on the exit code:
- exit 2 (a
block-severity hit: private-key block, AWS/GitHub/Slack/Google token, …) → STOP. Do NOT commit or push. Show the redacted findings and tell the user to remove the secret and load it from an env var / secret store instead. If it is a genuine false positive, they may add an inline # pragma: allowlist secret (or # noqa: secret) on that line, or confirm explicitly — only then re-run and proceed.
- exit 0 with
warn notices (a secret-looking assignment) → surface the warnings but you may proceed; encourage the user to confirm each is not a live credential.
- exit 0 clean → continue.
Never paste an un-redacted secret back to the user; rely on the scanner's redacted output.
-
Refuse large files in tracked dirs. Scan unstaged + untracked files. If any single file under data/, src/, exp/, or obsolete/ is larger than 1 MB, stop and tell the user "move this to <data_root>/append_only/<project>/ instead" (per rules/project-structure.md: data/ is for tiny in-repo files only). The threshold is 1 MB because GitHub will warn at 50 MB and reject at 100 MB; 1 MB catches it early.
-
Skip .claude/settings.json even if it shows up changed. The per-project .claude/settings.json is .gitignored by project_cc_init.bootstrap_local because it contains machine-absolute paths and per-machine permission allowlists. If .gitignore is missing the entry, add the entry to .gitignore first (echo '.claude/settings.json' >> .gitignore) and include that .gitignore change in the commit — but do NOT stage .claude/settings.json itself.
-
Validate .murmurent.yaml / CHARTER.md if modified. The marker must parse as YAML with murmurent: (schema int) and lab:; a legacy CHARTER needs project, lead, members, sensitivity. If broken, refuse the commit with the specific error.
-
Validate MEMBERS file if it was modified. Should be one @handle per line. Refuse if a line doesn't match ^@[A-Za-z0-9_-]+$.
Stage + commit + push
Once the pre-flight passes, follow the standard commit-push flow with these murmurent conventions:
-
Stage selectively. Use git add <specific files> rather than git add -A or git add . so the skips from steps 3-5 stick.
-
Commit message style. Lead with a short imperative title (≤70 chars). Body explains the why, not the what. If CHARTER.md, MEMBERS, or .claude/agents/ symlinks changed, call that out explicitly in the body — those are governance-level changes a reader needs to notice.
-
Always pass the commit message via a HEREDOC to preserve formatting (see CLAUDE.md "Git Safety Protocol" section). End with:
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-
Never --amend — create a new commit per CLAUDE.md.
-
Never --no-verify — murmurent hooks (raw_guard, protected_paths, phi_check, audit) are load-bearing.
-
Never push --force — the lab's commit history is the audit trail. If a rebase is truly needed, ask the user first.
After the push: Slack release note
Post the release note to the project's own Slack channel — this repo is a
project-attached repo, so its activity belongs in the project's channel, not the
centre-wide dev channel. Resolve the channel id first:
murmurent project channel
- If it prints a channel id → post there with
mcp__claude_ai_Slack__slack_send_message (channel_id = that id).
- If it exits with "no Slack channel yet" → the project isn't provisioned.
Tell the user to run
murmurent project provision-slack <project> (PI only), and
skip the Slack post (don't fall back to #claude-test — a project's notes go
to its own channel only). The push still succeeded.
- If it exits with "not inside a project repo" → this isn't a project after
all; post no Slack note (a ready-but-unattached repo has no project channel — that's normal).
Message body:
- First line:
**<repo-name>** · · — _<commit-title>_
- Second paragraph: one or two sentences summarising what changed and why (not what files — readers can
git log -p for that).
- Footer line (required, per rules/slack.md): exactly
All worship me and I will let you serve me.
Use mcp__claude_ai_Slack__slack_send_message, NOT mcp__slack__slack_post_message — the latter's bot identity isn't in the channel and will silently fail with not_in_channel. (The bot is added to each project channel at provision time; provision-slack/reconcile keep its membership current.)
What to refuse and why
| Situation | Action | Reason |
|---|
Repo lacks a readiness marker (.murmurent.yaml / legacy CHARTER.md) | Refuse; suggest /commit-push | Not murmurent-ready |
Diff touches data/lab_vm/immutable|append_only/ (or legacy raw|refined/) | Refuse | rules/data-storage.md (governed data) |
| Secret-shaped filename in diff | Stop and ask | Prevent credential leak |
murmurent security secrets-scan --staged exits non-zero (block hit) | Refuse; show redacted findings | Hardcoded credential in file content would reach GitHub |
File > 1 MB in data//src//exp//obsolete/ | Refuse; suggest append_only/ | rules/project-structure.md |
.claude/settings.json staged | Drop from stage; ensure in .gitignore | Machine-absolute paths leak across collaborators |
| Marker/CHARTER invalid YAML / missing fields | Refuse | corrupt readiness metadata |
User asks for --force/--no-verify/--amend | Ask for explicit confirmation | Defaults from CLAUDE.md |
What this skill explicitly does NOT do
- Does not run tests. Run them yourself before invoking, or chain with
/test.
- Does not bump version numbers, edit CHANGELOG, or tag releases.
- Does not push to multiple remotes — only the current branch's tracking remote.
To back a project's repo up to a second GitHub account/org, use managed
mirrors:
murmurent project mirror add <repo> <org/name>, then /murmurent-project-push
pushes origin and each mirror, reporting every destination separately.
- Does not amend or squash existing commits.
Examples
Happy path:
> /murmurent-push
Pre-flight ✓
- readiness marker present
- no /data/lab_vm/* in diff
- no secret-shaped files
- no secrets in content ✓ (secrets-scan --staged: clean)
- largest in-repo file: 24 KB (data/sample.csv)
Staging 3 files: src/init.py, exp/00_qc/run_all.py, README.md
Commit: "init: switch QC normalisation to scran"
Pushed origin/main -> 4f3b21a
Slack: posted to the project channel (#dcis_sc_tutorial)
Refusal:
> /murmurent-push
Pre-flight ✗
- File data/raw_dump.bam is 412 MB — move to /data/lab_vm/append_only/<project>/.
rules/project-structure.md: data/ is for tiny in-repo files only.
Aborting. No changes staged.