diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-22 16:59:56 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-22 16:59:56 -0500 |
| commit | 3cb467e6aa4356f9912d661ef12d581d61b65cb6 (patch) | |
| tree | 6cc1e0634dedcdbbf68fcfc6d6696492fa96ad82 /teams/deepsat | |
| parent | b37e4f8cb494ca14b9a00c967c7df8bd8d0a9ee1 (diff) | |
| download | rulesets-3cb467e6aa4356f9912d661ef12d581d61b65cb6.tar.gz rulesets-3cb467e6aa4356f9912d661ef12d581d61b65cb6.zip | |
feat: split team publishing rules into an installable overlay
The global commits.md carried DeepSat-specific publishing steps — Linear ticket-state moves, the Slack notification protocol with its channel ID and engineer names, the deepsat.ghe.com host, the team merge norm. Those are symlinked into every project on the machine, so they sat as dead weight in personal repos and risked misfiring where there's no Linear ticket to move or Slack mpdm to ping.
I split them out. commits.md keeps the universal skeleton (identity, attribution, commit format, the review-and-publish gate, verification) and replaces the team steps with seams: "run the project's publishing overlay here if it defines one," the same pattern startup.org uses for startup-extras. A project with no overlay runs the complete flow, just without ticket and chat integration.
The DeepSat specifics move to teams/deepsat/claude/rules/publishing.md. That file is not a global rule — install-team.sh copies it into one project's .claude/rules/ (make install-team TEAM=deepsat PROJECT=...), keyed on the PROJECT argument, so only the named project gets it. Location decides distribution: claude-rules/ is the global-symlink set, teams/ is targeted-copy, so the overlay reaches DeepSat and nowhere else.
The startup freshness check (sync-language-bundle.sh) now covers team overlays alongside language bundles: a process_bundle function handles both, with a team syncing only its own rule (no generic rules, hooks, or settings — those belong to a language bundle). A drifted overlay rule auto-fixes from canonical at the project's next startup, the same mechanism language bundles already ride.
Tested: 3 new bats cases (team overlay clean / drifted-and-fixed / does-not-pull-generic-rules) on top of the 11 existing; install-team + sync verified end-to-end against a temp project. make test green, shellcheck clean.
Diffstat (limited to 'teams/deepsat')
| -rw-r--r-- | teams/deepsat/claude/rules/publishing.md | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/teams/deepsat/claude/rules/publishing.md b/teams/deepsat/claude/rules/publishing.md new file mode 100644 index 0000000..c919d3c --- /dev/null +++ b/teams/deepsat/claude/rules/publishing.md @@ -0,0 +1,67 @@ +# DeepSat Publishing Overlay + +Applies to: `**/*` — but only present in the DeepSat work project. This file is **not** a global rule. It is copied into a single project's `.claude/rules/` via `make install-team TEAM=deepsat PROJECT=<path>` and loads only there. Its canonical source lives in `teams/deepsat/claude/rules/publishing.md` in the rulesets repo. + +This overlay extends the publish flow in the global `commits.md` with DeepSat's ticket and chat integration. The global flow defines the universal skeleton (identity, attribution, commit format, the review-and-publish gate, verification); the seams in that flow say "if the project defines a publishing overlay, run its steps here." This is that overlay. A project without it commits, opens PRs, and reviews exactly as the global flow describes — just without the Linear and Slack steps below. + +## Ticket system (Linear) + +DeepSat tracks work in Linear. Tickets carry IDs like `SE-289`. + +**PR title.** Append the ticket ID in parentheses to the conventional-commit subject: `refactor: remove dead if-count check in admin (SE-289)`. If there is no ticket, omit the parenthetical. + +**PR body cross-link.** Include a `Linear: [<TICKET-ID>](<linear-url>)` line so Linear's GitHub integration auto-cross-links the PR to the ticket. If there is no ticket, state it explicitly (`Linear: n/a`) so reviewers know it was considered. + +**Linear ticket bodies** (when you write or update a ticket) carry two sections, in order: + +1. **Problem** — what's wrong, with enough detail that a teammate can recognize the same failure mode in their own work. +2. **Fix** — what changed (or what's proposed). + +The causal "why" and the test verification belong in the PR, not the ticket. Linear's GitHub integration auto-cross-links once the PR body includes the `Linear:` line, so the ticket reader reaches the PR without a body-level link. + +## After `gh pr create` (ticket reconciliation) + +Run these where the global PR-description subflow says to run the project's post-create overlay steps: + +1. Post a comment on the linked Linear ticket with the PR URL (Linear MCP `save_comment`, or open the ticket manually if MCP is unavailable). This closes the ticket→PR direction of the cross-link. +2. Move the Linear ticket to the **Dev Review** status (`save_issue` with the Dev Review state ID, or the Linear UI). The ticket should not remain "In Progress" once a PR is open against it. + +## GitHub Enterprise host + +DeepSat's GitHub is `deepsat.ghe.com`, not `github.com`. Pass `--hostname deepsat.ghe.com` to `gh api` calls (the `gh pr` porcelain picks the host up from the remote, but raw `gh api` needs it explicit). + +## Slack review notification + +Run this where the global PR-review Shape 1 flow says to run the project's review-notification overlay step. + +**Notify the PR author on Slack** (`APPROVE` or `REQUEST_CHANGES` verdicts only). After the review posts, send a one-line Slack message to the PR-review group DM `C0B1B0NH2N5` (the mpdm with Craig, Eric, Vrezh, Kostya — *not* `C0AM2MWHCJU`, which is a separate 3-person Craig/Vrezh/Kostya mpdm and is *not* an "#ai" channel despite older notes calling it that) via the `slack-deepsat` MCP. No `/voice personal` pass — the message is short and templated. Two cases: + +- Approved: `<@author-slack-id> Approved PR #N.\n<pr-url>` +- Changes requested: `<@author-slack-id> Changes Requested PR #N\n<pr-url>` + +Replace `#N` with the PR number and `<pr-url>` with the GHE URL of the PR. Always lead with an `<@author-slack-id>` mention so the engineer who owns the merge decision gets pinged — the message is useless without it. Resolve the Slack user ID via the slack-deepsat `users_search` tool using the PR author's name or email. The URL goes on its own line, immediately after the message. If the MCP doesn't have a post-message tool registered (for example, where the Slack MCP is read-only), surface that to the user and skip the post — don't fall back to a different channel or asking the user to paste it. + +**MCP payload format.** The `mcp__slack-deepsat__conversations_add_message` tool's `payload` parameter is the **raw Slack mrkdwn message body**, not a Slack API JSON envelope. Pass the message body string directly: + +``` +payload: <@U09AUV4087N> Approved PR #171. +https://deepsat.ghe.com/deepsat/orchestration_dashboard_mvp/pull/171 +``` + +Wrapping the body in `{"text": "..."}` posts the literal JSON object as the message — the `<@>` mention strips to plain text, escaped `\n` renders as the letter `n`, and the `text:` key prefix appears in the post. The engineer doesn't get pinged. Use real newlines in the string (a literal newline character), not the escape sequence. The MCP has no delete method, so a garbled post can only be removed manually from Slack — surface garble to the user immediately so they can clean it up before reposting. + +Slack mrkdwn that the `payload` string supports: `<@USER_ID>` for user mentions, `<#CHANNEL_ID>` for channel refs, real newlines for line breaks, `*bold*`, `_italic_`, `` `code` `` and ``` ``` ``` blocks, and `<url|label>` for labeled links. + +**Thread under the engineer's review-request post.** Before posting, scan the channel's recent history (`mcp__slack-deepsat__conversations_history`) for the engineer's original post containing the PR URL — typically a "New PR" message or just the PR link. Post the verdict notification with `thread_ts=<original-message-ts>` so it lands in the reply thread off that original. This keeps each PR's review state contained in one thread instead of fragmenting across top-level posts in a mixed feed. The same threading rule applies to both `APPROVE` and `REQUEST_CHANGES` verdicts. + +**No review-request post found?** Don't auto-pick a fallback. Ask the user which they prefer: + +- Post the verdict at the top level of the channel. +- DM the engineer directly. +- Don't post at all. + +Skip Slack for `event=COMMENT` reviews (informal feedback, not a verdict) and for issue-thread comments and inline replies. Approve and request-changes are the only verdicts that warrant the notification. + +## Merge practice + +The team's practice is **approve-then-author-merges**, not approve-and-merge. The Slack notification above hands the merge decision to the PR author. Reviewing a PR never authorizes merging it; the global `Merge Strategy` rules apply only to merges you perform on your own branches, and each of those needs its own explicit user confirmation. |
