Skip to content

fix(brief): default fork-contribution PRs to the captain's fork instead of upstream - #34

Merged
trillium merged 2 commits into
mainfrom
fm/fork-contribution-pr-target-default
Aug 2, 2026
Merged

fix(brief): default fork-contribution PRs to the captain's fork instead of upstream#34
trillium merged 2 commits into
mainfrom
fm/fork-contribution-pr-target-default

Conversation

@trillium

@trillium trillium commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Intent

Fix fork-contribution PRs defaulting to upstream instead of the captain's fork: fm-brief.sh now detects legacy vs swapped clone shapes and scaffolds explicit gh pr create --repo trillium/ / stop-and-escalate instructions per mode; project-management skill documents the required origin=fork/upstream=original clone shape; new bin/fm-fork-origin-check.sh advisory scan; tests added for both.

What Changed

  • bin/fm-brief.sh now detects legacy vs. correctly-swapped fork clone shapes and scaffolds mode-specific instructions (explicit gh pr create --repo trillium/<repo>, stop-and-escalate on an unset-up clone) so push-mode ship briefs on fork-contribution projects stop defaulting PRs to upstream.
  • Added bin/fm-fork-origin-check.sh, a read-only advisory scan that classifies a clone's origin/upstream remote shape, plus its test suite (tests/fm-fork-origin-check.test.sh) and a docs/scripts.md inventory entry.
  • Documented the required origin=fork / upstream=original clone convention in .agents/skills/project-management/SKILL.md and updated AGENTS.md; extended tests/fm-brief.test.sh to cover legacy, swapped, direct-PR, and exempt clone shapes.

Risk Assessment

✅ Low: The change is additive prose/tooling: it extends fork-detection logic to two clone shapes (legacy/swapped) with mode-specific brief text, adds a new standalone read-only advisory script not wired into any automatic execution path, and preserves byte-identical brief output for ordinary (non-fork) projects. Logic was traced end-to-end (heredoc parsing, remote-owner detection, mode branching) with no bugs found, and both new/changed behaviors have direct test coverage.

Testing

Ran the two relevant automated suites (fm-brief.test.sh, fm-fork-origin-check.test.sh) — all cases pass — then independently reproduced the scenario end-to-end outside the test harness by creating three real git clones (legacy unswapped, correctly swapped, and SSH-origin) and generating actual briefs via bin/fm-brief.sh: the legacy no-mistakes clone correctly gets a stop-and-escalate instruction, the swapped no-mistakes clone gets a confirm-and-proceed instruction, and the direct-PR clone gets an explicit gh pr create --repo trillium/&lt;repo&gt; override — matching the intended fix. The advisory scanner also ran cleanly against the same clones. No issues found; no leftover artifacts in the working tree.

Evidence: End-to-end demo: generated brief fork-rule sections for legacy/swapped/direct-PR clone shapes plus advisory scan output
=== fm-brief.sh generated fork-contribution PR-target rules (end-to-end demo) ===

--- legacy (unswapped) clone + no-mistakes mode: worker told to STOP ---
# Fork-based project: origin is not yet the fork - STOP before running no-mistakes
This project's PRs must land in the captain's own `trillium/rango` fork, NEVER upstream; the PR only goes upstream on the captain's explicit word.
`origin` here is still the upstream repository, not the `trillium/rango` fork. no-mistakes opens its PR against whatever `origin` is configured to, so running it now would open the PR against upstream.
This clone's remotes need the captain-approved origin swap (origin -> the `trillium/rango` fork, upstream -> the current origin) before no-mistakes can run safely here; that change is outside this task's worktree.
Do NOT run `no-mistakes init --fork-url` as a workaround: it pushes to the named fork while still opening the PR against `origin` (upstream), reproducing the same failure.
Append `blocked: project clone's origin is not the trillium fork yet, no-mistakes would target upstream` and stop; do not attempt any workaround that could open a PR against upstream.


--- swapped clone (origin=fork, upstream=original) + no-mistakes mode: allowed to proceed ---
# Fork-based project: PRs stay in the fork, never upstream
This project's PRs must land in the captain's own `trillium/rango` fork, NEVER upstream; the PR only goes upstream on the captain's explicit word.
`origin` here is already the `trillium/rango` fork, with `upstream` as a separate remote, so no-mistakes's normal PR-open behavior already targets the fork - do not change that.
Never run `no-mistakes init --fork-url` or any similar remote reconfiguration on this project: that flag pushes to the named fork while still opening the PR against `origin`, which is the exact wrong direction here.
Never stop to ask fork-vs-local: always target the fork.
**CRITICAL:** if no-mistakes ever proposes or opens a PR against anything other than `trillium/rango`, stop and escalate immediately rather than letting it proceed.


--- legacy clone + direct-PR mode: explicit --repo override given ---
# Fork-based project: PRs stay in the fork, never upstream
This project's PRs must land in the captain's own `trillium/rango` fork, NEVER upstream; the PR only goes upstream on the captain's explicit word.
If the fork does not exist yet, create it with `gh-axi` before pushing.
Push your branch to the fork - `git push git@github.com:trillium/rango.git fm/demo-dp:fm/demo-dp` if `origin` here is still upstream, or plain `git push origin fm/demo-dp` once `origin` is the fork.
Open the PR with an explicit repo override so it can never default to upstream: `gh pr create --repo trillium/rango --base <fork-default-branch> --head fm/demo-dp`.
Never omit that `--repo trillium/rango` override, and never stop to ask fork-vs-local: always target the fork.
**CRITICAL:** a PR opened against the upstream repo must NEVER happen automatically. If targeting the fork is not possible, stop and get direct captain confirmation before any upstream PR attempt.


=== bin/fm-fork-origin-check.sh advisory scan output against the same clones ===
no misconfigured fork-contribution clones found

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-brief.test.sh (all cases pass, including the new fork-contribution PR-target rule test covering legacy/swapped/direct-PR/exempt clone shapes)
  • bash tests/fm-fork-origin-check.test.sh (all 3 new test cases pass: full clone-shape classification, read-only/always-exit-0 behavior, missing-registry handling)
  • Manual end-to-end: ran bin/fm-brief.sh against three freshly created git clones (legacy unswapped upstream-origin clone, correctly swapped origin=fork/upstream=original clone, and an SSH-origin direct-PR clone) and inspected the generated brief.md fork-contribution sections
  • Manual end-to-end: ran bin/fm-fork-origin-check.sh against the same demo clones to confirm the advisory scan runs cleanly and reports as expected
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Summary by CodeRabbit

  • New Features
    • Added an advisory tool to scan project clones for incomplete or incorrect fork configurations.
    • Ship briefs now provide clearer pull request destination guidance for fork-based contributions.
  • Documentation
    • Added guidance for configuring and repairing fork remotes.
    • Documented the new fork-configuration scanner.
  • Bug Fixes
    • Improved handling of legacy and swapped fork setups, while preserving local-only and standard project workflows.

…in's fork

The rango PR kunchenguid#712 incident happened because the existing fork-first rule in
fm-brief.sh's scaffold was prose-only and didn't survive contact with
no-mistakes's default PR-target behavior or the (backwards, for this use
case) 'no-mistakes init --fork-url' flag.

- bin/fm-brief.sh: detect both the legacy (origin still upstream) and
  swapped (origin=trillium fork, upstream remote present) clone shapes, and
  scaffold mode- and shape-specific instructions: explicit gh pr create
  --repo trillium/<repo> form for direct-PR, a confirm-and-forbid-fork-url
  rule for an already-swapped no-mistakes clone, and a stop-and-escalate
  instruction when a no-mistakes clone hasn't been swapped yet (no safe
  worker-side override exists in that shape).
- .agents/skills/project-management/SKILL.md: document the required
  origin=fork/upstream=original clone shape for fork-contribution projects,
  and call out that --fork-url implements the opposite workflow.
- AGENTS.md: update section 11's one-line summary to match.
- bin/fm-fork-origin-check.sh: new read-only advisory scan for registered
  clones stuck in the legacy or partially-swapped shape.
- tests/fm-brief.test.sh, tests/fm-fork-origin-check.test.sh: cover every
  clone/mode combination.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Fork-contribution handling now distinguishes legacy and correctly swapped Git remotes. Brief generation applies clone-state and delivery-mode rules. A read-only diagnostic scanner reports incomplete configurations and includes regression coverage.

Changes

Fork origin workflow

Layer / File(s) Summary
Fork-aware brief routing
bin/fm-brief.sh, AGENTS.md, tests/fm-brief.test.sh
Brief generation classifies legacy and swapped remotes and emits direct-PR, swapped-clone, or blocked legacy-clone instructions. Tests cover these states and existing exemptions.
Origin diagnostic scanner
bin/fm-fork-origin-check.sh, tests/fm-fork-origin-check.test.sh, docs/scripts.md
The new read-only scanner checks registered clones, remote ownership, upstream configuration, and optional GitHub CLI fork metadata. Tests cover findings, clean scans, missing registries, and read-only behavior.
Fork contribution procedures
.agents/skills/project-management/SKILL.md
Project-management guidance documents fork setup, fork creation, legacy clone repair, initialization restrictions, and the diagnostic utility.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • trillium/firstmate#12: Earlier fork-first logic that this change extends with clone-state handling and explicit PR targeting.

Suggested reviewers: kunchenguid

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: directing fork-contribution pull requests to the captain's fork by default.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fm/fork-contribution-pr-target-default

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
bin/fm-fork-origin-check.sh (2)

50-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the owner-parsing logic instead of duplicating it.

owner_of reimplements the same owner-extraction steps already present in bin/fm-brief.sh's fork_repo_for_origin (strip .git, strip trailing /, split on the last /, then on the last :). Two independent copies of this URL-parsing logic risk silently diverging if an edge case (new host format, unusual URL shape) is fixed in only one file. This codebase already uses a *-lib.sh convention for shared helpers between scripts (see the many fm-*-lib.sh entries in docs/scripts.md); extract this logic into such a shared file and source it from both bin/fm-brief.sh and bin/fm-fork-origin-check.sh.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/fm-fork-origin-check.sh` around lines 50 - 58, Extract the shared
owner-parsing steps from owner_of and fork_repo_for_origin into an appropriate
bin/*-lib.sh helper, preserving the existing normalization behavior. Source that
shared library from both bin/fm-fork-origin-check.sh and bin/fm-brief.sh, and
update both callers to reuse the helper instead of maintaining duplicate parsing
logic.

78-83: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard the gh repo view call with a timeout.

The best-effort GitHub lookup has no timeout. A network stall on any single registered project would hang the entire triage scan rather than degrading gracefully to "skip, never misreport" as the header describes for other failure modes.

🛡️ Proposed fix to bound the external call
-  is_fork=$(gh repo view "trillium/$name" --json isFork -q .isFork 2>/dev/null) || continue
+  is_fork=$(timeout 10 gh repo view "trillium/$name" --json isFork -q .isFork 2>/dev/null) || continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/fm-fork-origin-check.sh` around lines 78 - 83, Bound the `gh repo view`
invocation in the fork-detection loop with a short timeout, preserving the
existing failure path that skips the project when the command times out or
otherwise fails. Keep the `is_fork` check and candidate reporting unchanged so
stalled lookups degrade to skipping rather than blocking the scan.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/fm-fork-origin-check.sh`:
- Around line 84-86: Update the project-name extraction in the here-document
within the fork-origin check flow to reuse the registry parser contract provided
by bin/fm-project-mode.sh instead of the inline awk expression. Preserve the
existing iteration behavior while ensuring data/projects.md entries are
interpreted through that parser’s supported registry format.

---

Nitpick comments:
In `@bin/fm-fork-origin-check.sh`:
- Around line 50-58: Extract the shared owner-parsing steps from owner_of and
fork_repo_for_origin into an appropriate bin/*-lib.sh helper, preserving the
existing normalization behavior. Source that shared library from both
bin/fm-fork-origin-check.sh and bin/fm-brief.sh, and update both callers to
reuse the helper instead of maintaining duplicate parsing logic.
- Around line 78-83: Bound the `gh repo view` invocation in the fork-detection
loop with a short timeout, preserving the existing failure path that skips the
project when the command times out or otherwise fails. Keep the `is_fork` check
and candidate reporting unchanged so stalled lookups degrade to skipping rather
than blocking the scan.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: badc82d2-9d47-43ca-8dd6-219c4a543833

📥 Commits

Reviewing files that changed from the base of the PR and between 6bdc17a and 63c449f.

📒 Files selected for processing (7)
  • .agents/skills/project-management/SKILL.md
  • AGENTS.md
  • bin/fm-brief.sh
  • bin/fm-fork-origin-check.sh
  • docs/scripts.md
  • tests/fm-brief.test.sh
  • tests/fm-fork-origin-check.test.sh

Comment on lines +84 to +86
done <<EOF
$(awk '$1=="-"{print $2}' "$REG")
EOF

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
file=$(fd -t f '^fm-project-mode\.sh$' bin)
echo "== bin/fm-project-mode.sh header (parser contract) =="
sed -n '1,60p' "$file"
echo "== fm-fork-origin-check.sh registry read =="
rg -n 'awk|REG' bin/fm-fork-origin-check.sh

Repository: trillium/firstmate

Length of output: 2827


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== fm-fork-origin-check.sh relevant sections =="
sed -n '1,130p' bin/fm-fork-origin-check.sh

echo "== data/projects.md sample/line count =="
for f in data/projects.md **/projects.md; do
  [ -n "${f:-}" ] && [ -f "$f" ] || continue
  echo "--- $f ($(wc -l < "$f") lines) ---"
  sed -n '1,120p' "$f"
done

echo "== project-management skill registry guidance =="
skill=$(fd -t f '^SKILL\.md$' .agents/skills/project-management)
for s in "${skill[@]:-}"; do
  [ -n "$s" ] || continue
  echo "--- $s ---"
  sed -n '1,220p' "$s"
done

echo "== all fm-fork-origin-check.sh awk invocations =="
awk 'BEGIN{p=0} /awk \'{print NR": "$0} /awk '\''\$1=="-"\{print \$2'\'' "$REG"\n/ /EOF/' bin/fm-fork-origin-check.sh

Repository: trillium/firstmate

Length of output: 11766


Use bin/fm-project-mode.sh’s registry parser contract here.

bin/fm-fork-origin-check.sh still extracts project names with awk '$1=="-"{print $2}', but the project-management skill requires data/projects.md readers to use the registry format and parser contract owned by bin/fm-project-mode.sh. This simplified extraction does not follow that contract and may miss or misread registry line shapes intended by bin/fm-project-mode.sh.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/fm-fork-origin-check.sh` around lines 84 - 86, Update the project-name
extraction in the here-document within the fork-origin check flow to reuse the
registry parser contract provided by bin/fm-project-mode.sh instead of the
inline awk expression. Preserve the existing iteration behavior while ensuring
data/projects.md entries are interpreted through that parser’s supported
registry format.

@trillium
trillium merged commit 139db80 into main Aug 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant