Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8f01884
fix(disclosure): the marker must end the BODY, plus five missed posti…
d-morrison Aug 25, 2026
703f905
Merge origin/main
d-morrison Aug 25, 2026
17bfcb3
docs: correct the test-count delta in the previous commit message
d-morrison Aug 25, 2026
a667d2a
docs(hook): state what discloses() actually enforces, and whose flag …
d-morrison Aug 25, 2026
10909ff
fix(hook): gate close/reopen over the whole segment, not a lookahead
d-morrison Aug 25, 2026
ed03e06
Merge remote-tracking branch 'origin/main' into HEAD
dem-extra1 Aug 25, 2026
3b8d04e
fix(hook): recognize attached-flag shorthand and equals syntax for di…
dem-extra1 Aug 25, 2026
cf195e4
fix(hook): match all three pflag short-flag spellings, and anchor them
d-morrison Aug 25, 2026
5c577a5
Merge a concurrent session's fix for the same findings, keeping both …
d-morrison Aug 25, 2026
5f5aa20
fix(hook): the marker was forgeable, and nine other cross-vendor find…
d-morrison Aug 25, 2026
9336348
fix(hook): a flag token is never preceded by a quote
d-morrison Aug 25, 2026
1f90cee
fix(hook): recognize --form body=@file and --form body=\ as unreadabl…
dem-extra1 Aug 25, 2026
15b63d9
fix(hook): one field-flag list, and a positive flag boundary
d-morrison Aug 25, 2026
d3e8fb3
Merge remote-tracking branch 'origin/main' into fix/disclosure-cross-…
dem-extra1 Aug 25, 2026
501feb1
test: document remaining tokenization gap for #2189
dem-extra1 Aug 25, 2026
38a851c
ums: document gh run list -w ambiguity on shared workflow names
dem-extra1 Aug 25, 2026
5b2b030
fix(hooks): tighten flag-shape check to avoid matching hyphenated bodies
dem-extra1 Aug 25, 2026
d4d7c55
fix(hooks): document em-dash literal gap in docstring and test
dem-extra1 Aug 25, 2026
c448587
Merge branch 'main' of https://github.com/Morrison-Lab/ai-config into…
dem-extra1 Aug 25, 2026
5e7b053
fix(docs): apply semantic line breaks to recent main merge
dem-extra1 Aug 25, 2026
6a32948
fix(docs): trim a blank line from preferences.md to satisfy size check
dem-extra1 Aug 25, 2026
2de9dd5
Merge branch 'main' into fix/disclosure-cross-vendor-findings
dem-extra1 Aug 25, 2026
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
11 changes: 9 additions & 2 deletions hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -362,10 +362,17 @@
"scripts/check-pr-fully-clean.py matches that emoji as a",
"REVIEW_BODY_MARKERS entry, so a disclosed claim comment would be",
"admitted into the fully-clean verdict scan as a review -- and a claim",
"carries no findings, so it would scan as a CLEAN one. That is the",
"carries no findings, so nothing blocking is found there. That is the",
"false-clean failure shared/workflow/fully-clean.md already records for",
"a human-authored self-review, arriving through the very mechanism",
"added to make authorship legible."
"added to make authorship legible.",
"",
"Admission is necessary and not sufficient: the comment must",
"also name the head SHA to count toward criterion 2. The emoji",
"removes the one filter standing between a claim comment and",
"that scan, rather than single-handedly manufacturing a clean",
"verdict. Corrected after a cross-vendor review; the fragment",
"and the hook's user-facing warning say the same thing now."
],
"_note_cannot_see_every_body": [
"--body-file, --editor, `-F <file>` and an interpolated $BODY all put the",
Expand Down
377 changes: 351 additions & 26 deletions hooks/require-agent-disclosure.py

Large diffs are not rendered by default.

309 changes: 302 additions & 7 deletions hooks/test-require-agent-disclosure.py

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions memories/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,10 @@ in [`github-repo-transfers.md`](github-repo-transfers.md).
`7b006485`, whose `review / claude-review` check run carries
`conclusion: cancelled` while its dependent `review / require-review` carries
`conclusion: failure`.)
- **`gh run list -w "Workflow Name"` fails if multiple workflow files share the same `name:` field in their YAML.**
The command exits 1 with `could not resolve to a unique workflow; found: workflow-a.yml workflow-b.yml`.
This happens commonly when reusable workflows (like a review action) are called by multiple caller workflows, or when two different files just happen to use the same `name`.
The fix is to query by the **exact filename** instead of the display name: `gh run list -w workflow-b.yml`.

## gh — stale remote URL causes cryptic `gh pr create` failure
- `gh pr create` fails with `Head sha can't be blank, Base sha can't be blank, No commits between <owner>:main and <other-owner>:<branch>` when `origin` points to an **old repo URL** (e.g. after a GitHub repo transfer/rename).
Expand Down
4 changes: 2 additions & 2 deletions memories/preferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@
Never finish a turn leaving in-flight PRs unmonitored without an active scheduled timer.
(User directive / CAI, 2026-08-17.)


- When there's a well-scoped next step --- a filed follow-up issue, a sequenced item, an obvious continuation of the current work --- just start it; don't pause to ask "want me to keep going?" first.
The answer is a standing yes.
This removes the extra "should I continue?" pause between already-scoped steps; it does NOT override holding for genuinely ambiguous or architecturally significant decisions.
Expand Down Expand Up @@ -320,6 +319,8 @@
The existing instruction already covered this; the gap was execution discipline in a fast multi-merge loop, not missing guidance --- re-read this bullet at the top of every "pick the next backlog item" cycle.
In a multi-AGENT pipeline, UMS runs at BOTH levels: each subagent runs UMS once ITS PR merges (it stops after reporting CLEAN, so the coordinator resumes it post-merge with a "your PR merged, run UMS" nudge --- or the agent-launch spec bakes in a final UMS step), and the coordinator runs its own UMS for the cross-PR orchestration learnings no single subagent can see (merge-order sequencing, conflict-cascade handling, pipeline mechanics).
Each agent writes its OWN memory file plus one MEMORY.md index line to keep the conflict surface small; avoid rewriting shared memory bodies concurrently. (Learned on sparta 2026-07-01.)
- After ANY PR merges to main (under mwc, post-merge, or manual merge), IMMEDIATELY and autonomously sweep all open PRs in the repository for merge conflicts (`gh pr list --state open --json number,title,headRefName,mergeable,mergeStateStatus`).
For any PR reporting `CONFLICTING` or `UNKNOWN`, fetch main, test the merge, resolve the conflict in an isolated worktree, and push the sync commit proactively without waiting for the user to point it out or ask for it. (Learned on ai-config, 2026-08-24: "cai: you should have checked PR conflicts on your own".)
- Keep it simple.
Don't over-explain or ask permission for straightforward fixes --- just do them.
- Don't re-ask a decision that's already settled and built.
Expand Down Expand Up @@ -440,7 +441,6 @@
(Measured 2026-08-21 on ai-config#1884: two `memories/` files were treated for hours as a peer session's in-flight work.
Both additions were already on `main` in fuller form, and the diff had also rewritten three *correct* relative links into broken ones --- the `check-links.py` failure being blamed on that session all along.)


- **Don't touch anyone else's branch.**
**Do:** only push to or modify branches I created in my own worktree.
**Don't:** push commits, force-push, checkout, or edit branches belonging to another session or user --- even if the content looks worth keeping or the branch looks abandoned.
Expand Down
10 changes: 9 additions & 1 deletion scripts/sync-codex-skill-wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,15 @@ def reference_doc(mappings: dict) -> str:
> [!IMPORTANT]
> **Every comment-posting operation below carries the agent-disclosure marker in
> its body**, on its own line after a blank line:
> `_Posted by Claude Code (AI agent) --- not written by a human._`
> `_Posted by <your agent> (AI agent) --- not written by a human._`
>
> Substitute your own agent's name --- this registry is read by every model, so a
> hard-coded `Claude Code` would have a Codex or Gemini session misattribute its
> own comment.
> Keep the rest of the line verbatim.
> Check the substituted name against `scripts/check-pr-fully-clean.py`'s
> `REVIEW_BODY_MARKERS` too --- `code review` is one of them, so an agent named
> for code review would reintroduce the false-clean the emoji ban prevents.
>
> This registry is the substitution point for remote/web sessions, which have no
> `gh` at all --- so a marker-free template here is a marker-free comment there, in
Expand Down
13 changes: 9 additions & 4 deletions shared/workflow/disclose-agent-authorship.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ _Posted by Claude Code (AI agent) --- not written by a human._
```

**It deliberately does not use the robot emoji.**
That looks like the obvious choice, and it is the one thing the marker must avoid: `scripts/check-pr-fully-clean.py` matches the bare emoji as a `REVIEW_BODY_MARKERS` entry, so any comment carrying it is admitted into the verdict scan as a review item.
A disclosure footer on every agent comment would therefore turn every claim, every status note, and every deferral into something the fully-clean checker reads as a review --- and a claim comment carries no findings, so it would scan as a **clean** one.
That is the false-clean failure [`fully-clean`](fully-clean.md) already describes for a human-authored self-review, arriving through the very mechanism added to make authorship legible.
That looks like the obvious choice, and it is the one thing the marker must avoid: `scripts/check-pr-fully-clean.py` matches the bare emoji as a `REVIEW_BODY_MARKERS` entry, so a comment carrying it is *admitted* into the verdict scan as a review item.
A disclosure footer on every agent comment would therefore turn every claim, every status note, and every deferral into something the fully-clean checker can read as a review --- and a claim comment carries no findings, so it would scan as a **clean** one.

**Admission is necessary and not sufficient, which is worth stating precisely.**
The comment must ALSO name the current HEAD SHA to count toward criterion 2.
A synthetic `Working on this 🤖` with no SHA returns `No review comment has been posted evaluating HEAD SHA`, not a clean verdict.
So the emoji does not single-handedly manufacture a false clean --- it removes the one filter standing between a claim comment and the verdict scan, and leaves a SHA mention as the only thing still separating them.
A claim comment that quotes the head SHA is ordinary, which is why the gap is worth closing at the marker rather than relying on the second gate.

The marker above collides with none of the checker's `REVIEW_BODY_MARKERS` (the robot emoji, `### ` plus that emoji, `code review`, `**claude finished`, `### verdict`, `verdict:`) nor with any `REVIEW_AGENT_MARKERS` entry, verified against `scripts/check-pr-fully-clean.py` on 2026-08-24.
Check a replacement marker against both tuples before changing it.
Expand Down Expand Up @@ -86,7 +91,7 @@ The literal `--body "@...` grep finds the two Dependabot sites and misses the th

An earlier draft of this passage gave a different and wrong reason --- that the handle is never spelled contiguously in a source file, because a diff view would summon the bot.
That is false twice over.
The handle appears 248 times across this corpus's markdown, counting every file but this one --- the command below is itself an occurrence, so a figure that included this file would move each time the file was edited, and both earlier drafts of this sentence were wrong for exactly that reason.
The handle appears in the hundreds across this corpus's markdown --- run the command below for the figure at your commit, rather than trusting one written here, since it moves whenever any file mentioning the handle changes.
And [`memories/mention-triggers.md`](../../memories/mention-triggers.md) states the gate as `contains(github.event.comment.body, '@claude')`, over comment, review and issue bodies --- file contents are not among them.
The practice of not spelling it applies to text that becomes a comment, which is what that file scopes it to.

Expand Down
26 changes: 21 additions & 5 deletions skills/gi/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,29 @@ issue. Two signals must **both** be clear (`gh issue list` in step 1 returns
titles, labels, and assignees but neither comment text nor linked PRs, so
check both explicitly here).

**(1) No "Working on this" claim in the most recent comment:**
**(1) No live "Working on this" claim on the issue:**

```bash
# GitHub — read the issue's latest comment:
gh issue view <N> --json comments --jq '.comments | last | .body' | cat # READ_ISSUE_COMMENTS
# GitHub -- read the claim/release exchange, not just the newest comment:
gh issue view <N> --json comments \
--jq '[.comments[] | select(.body | test("hold off|paws off|back off|unclaim|released|PR is free|now mergeable"; "i"))] | last | "\(.createdAt) \(.author.login): \(.body)"' # READ_ISSUE_COMMENTS
gh issue view <N> --json updatedAt --jq .updatedAt # VIEW_ISSUE -- latest activity
```

**Reading only `.comments | last` is the bug this replaces.**
A claim is live for two hours from the most recent *activity*, so any unrelated comment posted after it --- a status note, a bot's build result, a question --- becomes the last comment while the claim is still binding.
The claim then goes invisible and this check reports the issue free, which is the parallel-session collision the whole convention exists to prevent.
Filter to the exchange and take the last member of *that*.

**Both timestamps are needed, which is why the second command is there.**
The claim's own `createdAt` says when it was made; the issue's `updatedAt` says
when the thread last saw activity, and the 2-hour rule expires on *activity*,
not on the claim's age.
A day-old claim followed by a comment thirty minutes ago is **live**; the same
claim with nothing after it is **expired**.
Reading only the claim's body cannot tell those apart, so it cannot decide the
question the step is asking.

Match the two-word invariant `hold off`, or either retired wording `paws off` / `back off`, case-insensitively --- then **exclude the comment if it also carries a release term** (`unclaim|released|PR is free|now mergeable`), because the retired release wording `... done --- paws off released.` contains `paws off` and would otherwise read as a live claim.
See [`claim-pr`](../../shared/workflow/claim-pr.md)'s "Match the two-word invariant".
If a live claim stands, skip the issue --- unless the claim has expired: no push or comment on the issue in over 2 hours, per [`claim-pr`](../../shared/workflow/claim-pr.md)'s expiration rule.
Expand Down Expand Up @@ -115,8 +131,8 @@ If an open PR already exists for the issue:

### 5. Check history

Before implementing, invoke the `check-history` skill to review merged
MRs/PRs that touched the same area. Don't undo past progress.
Before implementing, invoke the `check-history` skill to review merged MRs/PRs that touched the same area.
Don't undo past progress.

### 6. Claim the issue

Expand Down
10 changes: 9 additions & 1 deletion skills/post-merge/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,15 @@ conflicting PR can sit in `UNKNOWN` and get missed if you filter for
"A conflict your sweep found is not a conflict your merge caused"
and "A stacked PR is the one conflict that intersection cannot attribute".
3. **Check claim status.**
Read the most recent comment.
Read the most recent comment **of the claim/release exchange**, not the most recent comment overall:

```bash
gh pr view <N> --json comments \
--jq '[.comments[] | select(.body | test("hold off|paws off|back off|unclaim|released|PR is free|now mergeable"; "i"))] | last | .body' # READ_PR_COMMENTS
```

Any unrelated comment posted after a claim --- a status note, a bot result --- becomes the newest comment while the claim is still live, since a claim expires on activity rather than on age.
Reading the newest comment alone therefore reports a claimed PR as free.
Match the two-word invariant, `hold off` or either retired wording `paws off` / `back off`, case-insensitively --- never a whole sentence.
The PR and issue claims differ after those two words, and the dash between them is an em-dash in this file's own claim emitter (step 4), so a quoted prefix misses claims this very skill posts.
See [`claim-pr`](../../shared/workflow/claim-pr.md)'s "Match the two-word invariant".
Expand Down
2 changes: 1 addition & 1 deletion skills/push/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ A released PR would read as live-claimed, and this skill would refuse a legitima
Derive the release terms rather than copying this list, which is a snapshot of what the corpus posts today: `grep -rn "unclaim\|released\|PR is free\|now mergeable" skills/ commands/`.
A matcher narrowed to the new phrase returns nothing on such a thread, which reads exactly like an unclaimed one --- see [`claim-pr`](../../shared/workflow/claim-pr.md).

If the latest claim comment is from someone **other than you**, hasn't been unclaimed, and is still live --- the PR shows a push or comment within the last 2 hours, per [`claim-pr`](../../shared/workflow/claim-pr.md)'s expiration rule --- **do not push.**
The query returns the whole claim/release exchange, newest last, so read its **last** member: if that is a *claim* rather than a release, and it is from someone **other than you**, and it is still live --- the PR shows a push or comment within the last 2 hours, per [`claim-pr`](../../shared/workflow/claim-pr.md)'s expiration rule --- **do not push.**
Ask the user.
An expired claim (over 2 idle hours) no longer blocks on its own, but take it over with a fresh claim comment and run this skill's other checks (branch-head advance, `@claude` run in flight) before pushing.

Expand Down
4 changes: 3 additions & 1 deletion skills/rescue-closed/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ Before touching any item, **claim it** (`claim-pr`) so parallel sessions or the
**Issue:**

```bash
gh issue reopen <N> --comment "Reviving: <why it still matters>." # REOPEN_ISSUE
gh issue reopen <N> --comment "Reviving: <why it still matters>.

_Posted by Claude Code (AI agent) --- not written by a human._" # REOPEN_ISSUE
```

If reopening is wrong — a messy thread, or scope has shifted — file a fresh issue
Expand Down
12 changes: 10 additions & 2 deletions tool-mappings.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ operation to the equivalent GitHub MCP tool so any model can run a skill.
> [!IMPORTANT]
> **Every comment-posting operation below carries the agent-disclosure marker in
> its body**, on its own line after a blank line:
> `_Posted by Claude Code (AI agent) --- not written by a human._`
> `_Posted by <your agent> (AI agent) --- not written by a human._`
>
> Substitute your own agent's name --- this registry is read by every model, so a
> hard-coded `Claude Code` would have a Codex or Gemini session misattribute its
> own comment.
> Keep the rest of the line verbatim.
> Check the substituted name against `scripts/check-pr-fully-clean.py`'s
> `REVIEW_BODY_MARKERS` too --- `code review` is one of them, so an agent named
> for code review would reintroduce the false-clean the emoji ban prevents.
>
> This registry is the substitution point for remote/web sessions, which have no
> `gh` at all --- so a marker-free template here is a marker-free comment there, in
Expand Down Expand Up @@ -61,7 +69,7 @@ operation to the equivalent GitHub MCP tool so any model can run a skill.
| `CREATE_ISSUE` | Open a new issue. | `gh issue create` | `mcp__github__issue_write (method=create)` |
| `COMMENT_ISSUE` | Post a comment on an issue. **The body ends with the agent-disclosure marker** --- see [`disclose-agent-authorship`](shared/workflow/disclose-agent-authorship.md). | `gh issue comment "<N>" --body "..."` | `mcp__github__add_issue_comment` |
| `CLOSE_ISSUE` | Close an issue with a reason. | `gh issue close "<N>" --reason "..."` | `mcp__github__issue_write (method=update, state=closed, state_reason=...)` |
| `REOPEN_ISSUE` | Reopen a closed issue. | `gh issue reopen "<N>" --comment "..."` | `mcp__github__issue_write (method=update, state=open)` |
| `REOPEN_ISSUE` | Reopen a closed issue. **The body ends with the agent-disclosure marker** --- see [`disclose-agent-authorship`](shared/workflow/disclose-agent-authorship.md). | `gh issue reopen "<N>" --comment "..."` | `mcp__github__issue_write (method=update, state=open)` |
| `LABEL_ISSUE` | Set an issue's labels. The two behave differently and are not interchangeable: `--add-label` ADDS to the existing set, while the MCP path REPLACES the whole set, so pass the union of existing and new labels there. The MCP path also silently creates an unknown label name instead of rejecting it. | `gh issue edit "<N>" --add-label "..."` | `mcp__github__issue_write (method=update, labels=[...])` |
| `GET_LABEL` | Read a single label's name, color, and description. There is no MCP tool to create or update a label; use gh label create/edit, or gh api from a workflow. | `gh api "repos/<owner>/<repo>/labels/<name>"` | `mcp__github__get_label` |
| `LIST_DISCUSSIONS` | List a repository's discussions. Readable over REST; writes are GraphQL-only. | `gh api repos/{owner}/{repo}/discussions` | `mcp__github__list_discussions` |
Expand Down
2 changes: 1 addition & 1 deletion tool-mappings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ operations:
cli: gh issue close "<N>" --reason "..."
github_mcp: mcp__github__issue_write (method=update, state=closed, state_reason=...)
- id: REOPEN_ISSUE
description: Reopen a closed issue.
description: Reopen a closed issue. **The body ends with the agent-disclosure marker** --- see [`disclose-agent-authorship`](shared/workflow/disclose-agent-authorship.md).
cli: gh issue reopen "<N>" --comment "..."
github_mcp: mcp__github__issue_write (method=update, state=open)
- id: LABEL_ISSUE
Expand Down
Loading