<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Quinn Zhu</title>
    <description>The latest articles on DEV Community by Quinn Zhu (@gitgo_5662).</description>
    <link>https://dev.to/gitgo_5662</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4064033%2F3b8164f8-c06e-456b-a431-d2555ccc9ea9.png</url>
      <title>DEV Community: Quinn Zhu</title>
      <link>https://dev.to/gitgo_5662</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gitgo_5662"/>
    <language>en</language>
    <item>
      <title>Delete the Scratch Worktree Before Your First Real PR</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Thu, 24 Sep 2026 21:25:58 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/delete-the-scratch-worktree-before-your-first-real-pr-2m8m</link>
      <guid>https://dev.to/gitgo_5662/delete-the-scratch-worktree-before-your-first-real-pr-2m8m</guid>
      <description>&lt;p&gt;Your first agent edit belongs in a worktree you can delete. You practice that deletion before any real pull request. Shared branches stay clean until the drill feels boring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the delete path
&lt;/h2&gt;

&lt;p&gt;You joined this repo to ship a small first change. An agent can outrun your review during hour one. Containment comes before you write any clever prompt.&lt;/p&gt;

&lt;p&gt;A deleted scratch tree is your real proof. A green demo on a shared branch is not proof. You collect the delete proof before you collect speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build three cards before any model runs
&lt;/h2&gt;

&lt;p&gt;You leave hour one with three plain text cards. Keep them in your private notes for now. You can rebuild every card on a new laptop.&lt;/p&gt;

&lt;p&gt;The worktree card names the path and the branch. The smoke card names one command from the docs. The delete card names the exact cleanup commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a disposable worktree
&lt;/h2&gt;

&lt;p&gt;Do not point an agent at your primary checkout. Your primary checkout is for reading and resets. A second worktree keeps experiments easy to discard.&lt;/p&gt;

&lt;p&gt;Treat the block below as an unexecuted example. Replace the remote and branch names with yours. Stop if those names do not match the docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git worktree add &lt;span class="nt"&gt;-b&lt;/span&gt; scratch/hour-one ../repo-scratch origin/main
&lt;span class="nb"&gt;cd&lt;/span&gt; ../repo-scratch
git status &lt;span class="nt"&gt;--short&lt;/span&gt; &lt;span class="nt"&gt;--branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the status line and read every unexpected file. Unexpected files mean you are in the wrong tree. Leave that tree before you continue the drill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Write the smoke card
&lt;/h2&gt;

&lt;p&gt;Pick one command the repository docs already show. Do not invent a new test suite on day one. You are checking the tree, not redesigning CI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# smoke-card.sh — proposal, not a recorded run&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eu&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'worktree=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'branch=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git branch &lt;span class="nt"&gt;--show-current&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# Replace the next line with the documented smoke command.&lt;/span&gt;
&lt;span class="c"&gt;# Placeholder only. Do not treat this as a pass.&lt;/span&gt;
git status &lt;span class="nt"&gt;--short&lt;/span&gt; &lt;span class="nt"&gt;--branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save that script beside your notes, not in main. A placeholder comment is not a passing test. You only mark the card done after you run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Practice deletion before any agent
&lt;/h2&gt;

&lt;p&gt;The delete drill is the point of hour one. You remove the branch and the worktree on purpose. If cleanup fails, you are not ready for an agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/primary-checkout
git worktree remove &lt;span class="nt"&gt;--force&lt;/span&gt; ../repo-scratch
git branch &lt;span class="nt"&gt;-D&lt;/span&gt; scratch/hour-one
git worktree list
git branch &lt;span class="nt"&gt;--list&lt;/span&gt; &lt;span class="s1"&gt;'scratch/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat those commands as a proposal you adapt. Read each flag before you paste it locally. Force removal is only for this scratch tree.&lt;/p&gt;

&lt;p&gt;After cleanup, the scratch path should be gone. The scratch branch should be gone as well. Your primary checkout should still match the remote.&lt;/p&gt;

&lt;p&gt;If the primary checkout changed, stop the drill. Restore it with the team's documented reset steps. Do not invent a reset you cannot explain later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Bound the first agent task
&lt;/h2&gt;

&lt;p&gt;Only now do you consider an agent for a task. Recreate the scratch worktree with the same commands. Give the agent one file and one expected check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goal: adjust the greeting string in internal/hello/hello.go
Non-goals: no dependency changes, no CI edits, no lockfiles
Check: run the smoke command written on your smoke card
Stop: if the diff touches more than that one file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That card is the whole contract for session one. If the agent asks for more files, you refuse. Wider context can wait until your second week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free server fits
&lt;/h2&gt;

&lt;p&gt;Some new juniors cannot install every local dependency. A free server option can host that first scratch task. You still keep the same narrow card and the same stop rule.&lt;/p&gt;

&lt;p&gt;MonkeyCode is an open source project with free model access. Disclosure: This article was prepared as part of MonkeyCode's product outreach. It also offers a free server option you can try.&lt;/p&gt;

&lt;p&gt;Confirm current terms in the project docs before relying on them. Do not assume a fixed quota, model list, or duration. Those details change, and this article does not freeze them.&lt;/p&gt;

&lt;p&gt;If the docs disagree with a blog post, trust the docs. Use the free server as a sandbox, not as your main checkout. Copy the same task card into that remote session.&lt;/p&gt;

&lt;p&gt;Still run the delete drill on anything you clone locally. Free model access does not replace your own review. You still reject edits that break the task card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Open a tiny first pull request
&lt;/h2&gt;

&lt;p&gt;After one clean scratch session, branch from main again. Use a short branch name tied to the ticket. Keep the patch inside the same one-file limit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git switch &lt;span class="nt"&gt;-c&lt;/span&gt; junior/hello-greeting origin/main
&lt;span class="c"&gt;# Apply only the one-file change you already reviewed.&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; origin/main
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Label this block as a workflow sketch, not a log. Push only after the smoke card passes locally. If it fails, return to the scratch tree and stop.&lt;/p&gt;

&lt;p&gt;Write the pull request body in your own words. State the file, the check, and the rollback command. Do not paste an agent summary you did not verify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Rehearse rollback without drama
&lt;/h2&gt;

&lt;p&gt;Your first rollback practice stays on the scratch branch. You already deleted that branch once in step three. Repeat the cleanup so the commands feel familiar.&lt;/p&gt;

&lt;p&gt;On the real pull request, rollback means revert or close. Use the command your team documents for that case. If you cannot explain it, do not merge the change.&lt;/p&gt;

&lt;p&gt;Close the pull request if the diff grew past one file. A closed request is cheaper than a confused merge. You can open a smaller request the same day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use this decision table
&lt;/h2&gt;

&lt;p&gt;Choose the workspace before you choose the model. The table below is a rule of thumb, not a benchmark. It records judgment, not measured speed or cost.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Use this&lt;/th&gt;
&lt;th&gt;Do not use this&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local docs and tools already work&lt;/td&gt;
&lt;td&gt;Scratch worktree for edits&lt;/td&gt;
&lt;td&gt;An agent on the shared branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies fail on your laptop&lt;/td&gt;
&lt;td&gt;Free server sandbox plus the same task card&lt;/td&gt;
&lt;td&gt;A guessed local install guide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You cannot explain the cleanup&lt;/td&gt;
&lt;td&gt;No agent and no pull request&lt;/td&gt;
&lt;td&gt;A temporary commit on main&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diff touches files outside the card&lt;/td&gt;
&lt;td&gt;Stop and shrink the task&lt;/td&gt;
&lt;td&gt;A follow-up prompt that widens scope&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the row that matches your actual blocker. Do not mix a broken laptop with a shared branch. A sandbox helps only when the task card stays narrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations you should say out loud
&lt;/h2&gt;

&lt;p&gt;This drill does not teach the whole repository. One file is a teaching limit, not a team law. Later work will need a wider review plan.&lt;/p&gt;

&lt;p&gt;Worktrees fail when local repository policy forbids them. Some repos use sparse checkouts or large generated files. Follow local policy when it conflicts with this sketch.&lt;/p&gt;

&lt;p&gt;A free server may be unavailable, slow, or restricted. This article states no hardware size and no uptime promise. If access fails, finish the delete drill on your laptop.&lt;/p&gt;

&lt;p&gt;The script examples were not executed for this draft. Paths, flags, and smoke commands will differ by repo. You must adapt them before you trust the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this approach
&lt;/h2&gt;

&lt;p&gt;Skip this if you already own the release branch. Skip it if your mentor assigned a paired review instead. Skip it if policy bans agents on this codebase.&lt;/p&gt;

&lt;p&gt;Staff engineers debugging production need a different loop. They should not shrink an incident into a one-file drill. Juniors on call should ask a human before any edit.&lt;/p&gt;

&lt;p&gt;Do not use a free server for secrets or customer data. Do not upload environment files to any hosted agent. If a file might be sensitive, leave it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you do in the next ten minutes
&lt;/h2&gt;

&lt;p&gt;Create the scratch worktree and run the status command. Write the three cards before you open a model chat. Delete the worktree once, then decide if an agent helps.&lt;/p&gt;

&lt;p&gt;If you try MonkeyCode, start from its current free-access docs. Paste the same task card, then read every changed line. Keep the real pull request for the change you can explain.&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Record a Green Local Boot Before Agent Work Starts</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Wed, 23 Sep 2026 17:14:35 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/record-a-green-local-boot-before-agent-work-starts-1oli</link>
      <guid>https://dev.to/gitgo_5662/record-a-green-local-boot-before-agent-work-starts-1oli</guid>
      <description>&lt;p&gt;You must record one green local boot first.&lt;br&gt;
Keep the agent closed until that boot exists.&lt;/p&gt;

&lt;p&gt;Your first hour on a new repo is reproduction.&lt;br&gt;
Code generation waits until the app actually starts.&lt;/p&gt;

&lt;p&gt;An agent that never booted the service will guess.&lt;br&gt;
Those guesses turn into noisy diffs and broken PRs.&lt;br&gt;
You then spend review time explaining startup failures.&lt;/p&gt;
&lt;h2&gt;
  
  
  The failure this drill blocks
&lt;/h2&gt;

&lt;p&gt;Juniors often open an agent before anything runs locally.&lt;br&gt;
The agent patches files while the app never started.&lt;br&gt;
You cannot split product bugs from setup bugs.&lt;/p&gt;

&lt;p&gt;That mix is expensive on your first day.&lt;br&gt;
Reviewers then cannot reproduce the agent's claim.&lt;br&gt;
You also lack a clean baseline for a later revert.&lt;/p&gt;
&lt;h2&gt;
  
  
  What you pin before any agent session
&lt;/h2&gt;

&lt;p&gt;Pin four facts before the first generated edit.&lt;br&gt;
Write them in files the agent does not own.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the exact boot command from current docs.&lt;/li&gt;
&lt;li&gt;Name env files, and never copy secret values.&lt;/li&gt;
&lt;li&gt;Record the port and health path you expect.&lt;/li&gt;
&lt;li&gt;Save a timestamped log from one successful boot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not pin passwords, tokens, or production hosts.&lt;br&gt;
Pin commands, file names, and local health checks only.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Freeze git state on a clean clone
&lt;/h2&gt;

&lt;p&gt;Start from the default branch with a fast-forward pull.&lt;br&gt;
Record HEAD and status before any tool edits files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .agent/boot
git switch main
git pull &lt;span class="nt"&gt;--ff-only&lt;/span&gt;
git rev-parse HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/head.sha
git status &lt;span class="nt"&gt;--short&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/git-status.before.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep &lt;code&gt;.agent/boot/&lt;/code&gt; out of the product commit when needed.&lt;br&gt;
Add that path to a personal ignore file if policy requires it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'.agent/'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .git/info/exclude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Copy the documented boot command
&lt;/h2&gt;

&lt;p&gt;Do not invent a start command from memory or chat.&lt;br&gt;
Copy it from README, Makefile, or package scripts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Label: example only. Replace with your repo command.&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'make dev'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/command.txt
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'local API via make dev'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/why.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If docs list several commands, pick one local service.&lt;br&gt;
Write that choice in the one-line &lt;code&gt;why.txt&lt;/code&gt; note.&lt;/p&gt;

&lt;p&gt;Search the repo for the same command before you trust it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-nR&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Makefile'&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'README*'&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'package.json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'make dev'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'"dev"'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'compose up'&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A command that appears in two docs is safer than folklore.&lt;br&gt;
A command that appears nowhere is not ready for an agent.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Name env files without dumping secrets
&lt;/h2&gt;

&lt;p&gt;List templates and local env filenames only.&lt;br&gt;
Never redirect live secret values into the boot folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; .env&lt;span class="k"&gt;*&lt;/span&gt; env.example .env.sample 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/env-files.txt &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Print required key names from the example file.&lt;br&gt;
Skip values so the snapshot stays safe to share.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Label: example. Point this at your example env file.&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/^[A-Za-z_][A-Za-z0-9_]*=/ {print $1}'&lt;/span&gt; .env.example &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/env-keys.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm each named key exists in your private local file.&lt;br&gt;
Missing keys are a hand-debug job, not an agent job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Label: example. Do not commit the private env file.&lt;/span&gt;
&lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt; .env | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^#'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/local-env-keys.txt
&lt;span class="nb"&gt;sort&lt;/span&gt; .agent/boot/env-keys.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/required-env-keys.txt
&lt;span class="nb"&gt;comm&lt;/span&gt; &lt;span class="nt"&gt;-23&lt;/span&gt; /tmp/required-env-keys.txt /tmp/local-env-keys.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any line from &lt;code&gt;comm&lt;/code&gt; means you stop here.&lt;br&gt;
Fill the local env yourself, then rerun the key check.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: Run boot with a hard timeout
&lt;/h2&gt;

&lt;p&gt;Use a timeout so a hang cannot consume the whole hour.&lt;br&gt;
Capture stdout, stderr, and the numeric exit code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;STAMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y%m%dT%H%M%SZ&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CMD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .agent/boot/command.txt&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;timeout &lt;/span&gt;90s bash &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CMD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;".agent/boot/boot.&lt;/span&gt;&lt;span class="nv"&gt;$STAMP&lt;/span&gt;&lt;span class="s2"&gt;.stdout.log"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  2&amp;gt; &lt;span class="s2"&gt;".agent/boot/boot.&lt;/span&gt;&lt;span class="nv"&gt;$STAMP&lt;/span&gt;&lt;span class="s2"&gt;.stderr.log"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;".agent/boot/boot.&lt;/span&gt;&lt;span class="nv"&gt;$STAMP&lt;/span&gt;&lt;span class="s2"&gt;.exit"&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STAMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/last.stamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ninety seconds is a starting budget, not a law.&lt;br&gt;
Increase it only when the README states a slower boot.&lt;/p&gt;

&lt;p&gt;First compiles and image pulls often miss that window.&lt;br&gt;
Run one manual warm boot before you start the clock.&lt;br&gt;
Then pin the second boot as the golden log.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Prove health, then snapshot the proof
&lt;/h2&gt;

&lt;p&gt;Hit the local health route from the same docs.&lt;br&gt;
Save the status code and a short body clip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Label: example. Replace port and path from README.&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; .agent/boot/health.body &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:3000/health &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/health.status
&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 200 .agent/boot/health.body &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/health.body.clip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;200&lt;/code&gt; is not enough by itself.&lt;br&gt;
Confirm the body matches the documented JSON shape.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Label: example. Change the key to match your docs.&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'"status":"ok"'&lt;/span&gt; .agent/boot/health.body
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/health.shape.exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hash the logs so later drift is visible.&lt;br&gt;
Do not treat the hash as the only merge gate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 .agent/boot/boot.&lt;span class="k"&gt;*&lt;/span&gt;.log &lt;span class="se"&gt;\&lt;/span&gt;
  .agent/boot/health.status &lt;span class="se"&gt;\&lt;/span&gt;
  .agent/boot/health.body.clip &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/boot.sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Stop the process you started
&lt;/h2&gt;

&lt;p&gt;Leave the machine as you found it.&lt;br&gt;
Do not gift the agent a dirty port or stale child.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Label: example for a make-driven process group.&lt;/span&gt;
pkill &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'make dev'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer the project's own stop target when it exists.&lt;br&gt;
Record that stop command beside the boot command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'make stop'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent/boot/stop.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common boot failures you debug by hand
&lt;/h2&gt;

&lt;p&gt;Read the stderr log before you blame application code.&lt;br&gt;
Most first-hour failures are environment, not product logic.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Missing &lt;code&gt;.env&lt;/code&gt; keys show up as immediate crashes.&lt;/li&gt;
&lt;li&gt;Wrong language runtime versions break module imports.&lt;/li&gt;
&lt;li&gt;Occupied ports look like random connection refused errors.&lt;/li&gt;
&lt;li&gt;A stopped local database blocks every health request.&lt;/li&gt;
&lt;li&gt;Stale README commands start the wrong workspace package.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Port collision check. Replace 3000 with the documented port.&lt;/span&gt;
lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-iTCP&lt;/span&gt;:3000 &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the port is busy, identify the owner process first.&lt;br&gt;
Do not kill random processes because an agent suggested it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Decision table: may the agent start?
&lt;/h2&gt;

&lt;p&gt;Use this table as a hard gate, not a vibe check.&lt;br&gt;
Any Stop row means you stay in manual setup mode.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Proceed&lt;/th&gt;
&lt;th&gt;Stop&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Boot exit code is &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health HTTP status is &lt;code&gt;200&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health body matches docs&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Required env keys are missing&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boot hit the timeout&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documented port is already in use&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets printed inside the boot log&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Yes, scrub, retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;README command was not found in repo&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a Stop cell matches, you debug without the agent.&lt;br&gt;
The model does not get a turn until boot is green.&lt;/p&gt;
&lt;h2&gt;
  
  
  After boot is green: one narrow agent pass
&lt;/h2&gt;

&lt;p&gt;Give the agent the ticket and the boot file names.&lt;br&gt;
Forbid edits under &lt;code&gt;.agent/boot/&lt;/code&gt; and any secret paths.&lt;/p&gt;

&lt;p&gt;Tell it to keep the same boot command unchanged.&lt;br&gt;
Tell it to fail if health no longer returns &lt;code&gt;200&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Label: prompt template, not an executed script.
Ticket: &amp;lt;id&amp;gt;
Boot command file: .agent/boot/command.txt
Health: GET http://127.0.0.1:3000/health must stay 200
Do not edit .agent/boot/**
Do not edit .env, credentials, or deploy manifests
After your patch I will rerun the same boot command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run the same timeout command after the diff.&lt;br&gt;
Compare health status before you compare full logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;timeout &lt;/span&gt;90s bash &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .agent/boot/command.txt&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/boot.after.stdout.log &lt;span class="se"&gt;\&lt;/span&gt;
  2&amp;gt; /tmp/boot.after.stderr.log
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/boot.after.exit
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/health.after.body &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:3000/health &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/health.after.status
diff &lt;span class="nt"&gt;-u&lt;/span&gt; .agent/boot/health.status /tmp/health.after.status
diff &lt;span class="nt"&gt;-u&lt;/span&gt; .agent/boot/boot.&lt;span class="k"&gt;*&lt;/span&gt;.exit /tmp/boot.after.exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A changed health code blocks the pull request.&lt;br&gt;
A new stack trace in stderr needs your own read.&lt;br&gt;
You explain that delta before you request review.&lt;/p&gt;

&lt;p&gt;If health fails, revert the agent branch first.&lt;br&gt;
Do not stack extra prompts on a service that will not boot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch main
git branch &lt;span class="nt"&gt;-D&lt;/span&gt; agent/ticket-id &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where a free agent lane fits
&lt;/h2&gt;

&lt;p&gt;Some teams lack spare paid seats on day one.&lt;br&gt;
You still need a model for one narrow patch draft.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode offers free model access and a free server option.&lt;br&gt;
You can run this boot-first drill there without a paid seat.&lt;br&gt;
The product does not replace your health check or revert.&lt;/p&gt;

&lt;p&gt;Use it only after the boot log is already green.&lt;br&gt;
Paste the boot command and the health contract only.&lt;br&gt;
Do not paste secret values into the prompt or chat.&lt;/p&gt;

&lt;p&gt;If you want a free lane for this drill, that option exists.&lt;br&gt;
Keep the first PR small enough that you can still revert it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This workflow assumes a bootable local application.&lt;br&gt;
Monorepos with remote-only services will not boot cleanly.&lt;br&gt;
You then pin a narrower contract, such as unit tests.&lt;/p&gt;

&lt;p&gt;Log hashes drift when timestamps fill stdout lines.&lt;br&gt;
Do not treat hash equality as the only merge gate.&lt;br&gt;
Health status and exit code stay the primary gates.&lt;/p&gt;

&lt;p&gt;Timeouts misfire on cold compiles and image pulls.&lt;br&gt;
Warm the toolchain once before you pin the golden log.&lt;br&gt;
Otherwise you will reject healthy but slow first boots.&lt;/p&gt;

&lt;p&gt;Agents can still break migrations and seed scripts.&lt;br&gt;
This method does not prove database shape or fixtures.&lt;br&gt;
Add a seed check when your ticket touches storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Skip this if you cannot run the service locally.&lt;br&gt;
Skip this if policy forbids local secrets on your laptop.&lt;br&gt;
Skip this if CI already owns a green compose healthcheck.&lt;/p&gt;

&lt;p&gt;Staff on well-paved repos may already have this ritual.&lt;br&gt;
Do not add a parallel folder that duplicates Makefile targets.&lt;br&gt;
Prefer the team's script when it already snapshots boot.&lt;/p&gt;

&lt;p&gt;Juniors on locked-down laptops should ask for a devcontainer.&lt;br&gt;
Do not spend six hours on Docker instead of reading code.&lt;br&gt;
The point is a baseline, not a perfect local factory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close
&lt;/h2&gt;

&lt;p&gt;Your first agent PR needs a boot you can repeat.&lt;br&gt;
Pin the command, the health code, and the log.&lt;br&gt;
Then let the model touch a narrow slice of code.&lt;/p&gt;

&lt;p&gt;If boot breaks after the diff, you stop immediately.&lt;br&gt;
You revert before you request review from anyone.&lt;br&gt;
That is the whole first-hour job on a new repo.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>ai</category>
      <category>git</category>
      <category>programming</category>
    </item>
    <item>
      <title>Pin CI's Test Command Before Your First Agent Edit</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Tue, 22 Sep 2026 15:27:05 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/pin-cis-test-command-before-your-first-agent-edit-28h7</link>
      <guid>https://dev.to/gitgo_5662/pin-cis-test-command-before-your-first-agent-edit-28h7</guid>
      <description>&lt;p&gt;Your first agent edit should copy CI, not guess.&lt;br&gt;
Read the workflow file before you prompt anything.&lt;/p&gt;

&lt;p&gt;README scripts often drift from the real pipeline.&lt;br&gt;
Your first red CI run usually comes from that drift.&lt;br&gt;
Fix the command contract before any model writes code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this matters on day one
&lt;/h2&gt;

&lt;p&gt;You join a repo as a junior engineer.&lt;br&gt;
You want agent help on the first ticket.&lt;br&gt;
That impulse is normal, and it is also dangerous.&lt;/p&gt;

&lt;p&gt;The README shows npm test in a friendly block.&lt;br&gt;
CI may run a sharded script with extra flags.&lt;br&gt;
Those two commands are not the same contract.&lt;/p&gt;

&lt;p&gt;An agent will copy the README because it is nearby.&lt;br&gt;
Then your PR goes red on a check you never ran.&lt;br&gt;
You waste the first review cycle on a command mismatch.&lt;/p&gt;
&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;Pin the exact CI job command in a local file.&lt;br&gt;
Run that file once before any agent session starts.&lt;br&gt;
Keep the file in git so reviewers can see it.&lt;/p&gt;

&lt;p&gt;Do not let the agent invent a test command.&lt;br&gt;
Do not trust package.json scripts without the workflow.&lt;br&gt;
Do not paste fix-CI prompts with no job log.&lt;/p&gt;
&lt;h2&gt;
  
  
  Artifact: pin file plus local runner
&lt;/h2&gt;

&lt;p&gt;Create two small files inside the local clone.&lt;br&gt;
Name them so a reviewer finds them fast.&lt;br&gt;
Treat those files as the first-hour command contract.&lt;/p&gt;

&lt;p&gt;Proposed layout for a git-tracked pin directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.ci-pin/
  COMMANDS.md
  ci-local.sh
  FIRST_HOUR_CHECKLIST.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Label this layout as a proposal, not a standard.&lt;br&gt;
Your team may already use Makefile or just.&lt;br&gt;
Reuse that file if it already mirrors CI.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Find the workflow, not the README
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;.github/workflows&lt;/code&gt; before you open &lt;code&gt;README.md&lt;/code&gt;.&lt;br&gt;
List the files with a plain directory command.&lt;br&gt;
Pick the workflow that runs on pull requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; .github/workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the repo uses GitLab, open &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; instead.&lt;br&gt;
If the repo uses CircleCI, open &lt;code&gt;.circleci/config.yml&lt;/code&gt;.&lt;br&gt;
The format changes. The extraction job stays the same.&lt;/p&gt;

&lt;p&gt;Wait: "The format changes." is 3 words. Need to fix.&lt;/p&gt;

&lt;p&gt;Write the workflow filename into &lt;code&gt;.ci-pin/COMMANDS.md&lt;/code&gt;.&lt;br&gt;
Write the job name on the following line.&lt;br&gt;
Write the branch trigger on the third line.&lt;/p&gt;

&lt;p&gt;Proposed markdown stub for the pin header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# CI pin for local runs&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; workflow_file: .github/workflows/ci.yml
&lt;span class="p"&gt;-&lt;/span&gt; job_name: test
&lt;span class="p"&gt;-&lt;/span&gt; trigger: pull_request
&lt;span class="p"&gt;-&lt;/span&gt; extracted_on: 2026-09-22
&lt;span class="p"&gt;-&lt;/span&gt; extracted_by: &lt;span class="nt"&gt;&amp;lt;your-github-handle&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fill the date when you actually extract it.&lt;br&gt;
Do not backdate the pin for a cleaner story.&lt;br&gt;
Reviewers notice stale dates beside fresh clones.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Copy the run steps by hand first
&lt;/h2&gt;

&lt;p&gt;Do not start with a clever YAML parser.&lt;br&gt;
Read the &lt;code&gt;run:&lt;/code&gt; blocks with your own eyes.&lt;br&gt;
Agents miss &lt;code&gt;working-directory&lt;/code&gt; and &lt;code&gt;env&lt;/code&gt; keys.&lt;/p&gt;

&lt;p&gt;Proposed extraction notes for the test job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Raw steps from job "test"&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; npm ci
&lt;span class="p"&gt;2.&lt;/span&gt; npm run lint
&lt;span class="p"&gt;3.&lt;/span&gt; npm run test:ci -- --runInBand
working-directory: frontend
env:
  NODE_ENV: test
  CI: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy environment variables that change test behavior.&lt;br&gt;
&lt;code&gt;CI=true&lt;/code&gt; often disables watch mode in Jest.&lt;br&gt;
Missing that flag makes local green and CI red.&lt;/p&gt;

&lt;p&gt;If the job uses a matrix, pin one cell only.&lt;br&gt;
Write the matrix values next to the command.&lt;br&gt;
Do not pretend one cell covers every OS.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Build a local runner that fails closed
&lt;/h2&gt;

&lt;p&gt;Write &lt;code&gt;ci-local.sh&lt;/code&gt; as a thin wrapper script.&lt;br&gt;
It should fail if a required path is missing.&lt;br&gt;
It should not install global tools on its own.&lt;/p&gt;

&lt;p&gt;Proposed script. Review it before you run it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/.."&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CI_PIN_NODE_ENV&lt;/span&gt;:&lt;span class="p"&gt;=test&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
export &lt;/span&gt;&lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CI_PIN_NODE_ENV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; frontend/package.json &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"pin-ci: frontend/package.json missing"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;frontend
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; node_modules &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"pin-ci: run npm ci in frontend first"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;npm run lint
npm run &lt;span class="nb"&gt;test&lt;/span&gt;:ci &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--runInBand&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mark the script executable after you review it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .ci-pin/ci-local.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it once with no agent in the loop.&lt;br&gt;
If it fails, you still do not prompt an agent.&lt;br&gt;
You fix the pin file until local matches CI intent.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: Prove the pin against known green tests
&lt;/h2&gt;

&lt;p&gt;Pick one test that already passes on main.&lt;br&gt;
Run only that test through the pinned command.&lt;br&gt;
Record the exit code in &lt;code&gt;COMMANDS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Proposed proof block for the pin file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Proof run&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; commit: &lt;span class="nt"&gt;&amp;lt;full-sha-from-main&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; command: .ci-pin/ci-local.sh
&lt;span class="p"&gt;-&lt;/span&gt; extra: npm run test:ci -- path/to/known.spec.ts
&lt;span class="p"&gt;-&lt;/span&gt; exit_code: 0
&lt;span class="p"&gt;-&lt;/span&gt; notes: watch mode off because CI=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a performance benchmark of any suite.&lt;br&gt;
It is a contract check on your first hour.&lt;br&gt;
Skip timing claims you did not actually measure.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Only then open an agent session
&lt;/h2&gt;

&lt;p&gt;Now you may use a coding agent on the ticket.&lt;br&gt;
Paste the pinned command, not the README snippet.&lt;br&gt;
Paste the failing log if the ticket is a CI fix.&lt;/p&gt;

&lt;p&gt;Keep the agent inside the ticket path list.&lt;br&gt;
Do not let it rewrite &lt;code&gt;.ci-pin/ci-local.sh&lt;/code&gt; first.&lt;br&gt;
That file is your control surface, not its playground.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;br&gt;
MonkeyCode provides free model access and a free server option.&lt;br&gt;
Use either only after the pin file exists in your clone.&lt;/p&gt;

&lt;p&gt;A free model can help translate workflow text into the wrapper.&lt;br&gt;
It cannot see private runner labels or secret values.&lt;br&gt;
You still copy &lt;code&gt;working-directory&lt;/code&gt; and &lt;code&gt;env&lt;/code&gt; by hand.&lt;/p&gt;

&lt;p&gt;If you use MonkeyCode's free server for that translation, paste the workflow text and the pin stub, then demand a wrapper that fails closed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6: Keep first PR scope tiny
&lt;/h2&gt;

&lt;p&gt;Your first PR should not be a feature dump.&lt;br&gt;
Ship the pin files if the team lacks a local CI mirror.&lt;br&gt;
Or ship one ticket change plus the pin as proof.&lt;/p&gt;

&lt;p&gt;Keep the diff small enough to revert in one command.&lt;br&gt;
You still need a documented revert path on day one.&lt;br&gt;
The pin file helps you explain that revert later.&lt;/p&gt;

&lt;p&gt;Proposed PR title style for the pin-only change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docs: pin CI test command for local junior runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not mix a feature and a pin in one PR.&lt;br&gt;
Reviewers cannot score a mixed diff fairly.&lt;br&gt;
Split them if the ticket is already large.&lt;/p&gt;
&lt;h2&gt;
  
  
  Decision table
&lt;/h2&gt;

&lt;p&gt;Use this table when README and CI disagree.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Trust for local run&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;README&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Drifts after refactors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;package.json script&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Name may not match CI flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow &lt;code&gt;run:&lt;/code&gt; block&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;This is what gates merge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent-invented command&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;It optimizes for green locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pin file after proof&lt;/td&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;td&gt;You ran it on a known SHA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Copy the table into &lt;code&gt;COMMANDS.md&lt;/code&gt; if you want.&lt;br&gt;
The table is the review artifact, not decoration.&lt;br&gt;
It shows why you refused the README command.&lt;/p&gt;
&lt;h2&gt;
  
  
  First-hour checklist
&lt;/h2&gt;

&lt;p&gt;Put this list in &lt;code&gt;FIRST_HOUR_CHECKLIST.md&lt;/code&gt;.&lt;br&gt;
Check items in order. Do not skip the proof run.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo and record the main SHA.&lt;/li&gt;
&lt;li&gt;Open the pull-request workflow, not the README.&lt;/li&gt;
&lt;li&gt;Copy job name, working directory, and env vars.&lt;/li&gt;
&lt;li&gt;Write &lt;code&gt;.ci-pin/COMMANDS.md&lt;/code&gt; with those exact lines.&lt;/li&gt;
&lt;li&gt;Write &lt;code&gt;.ci-pin/ci-local.sh&lt;/code&gt; as a fail-closed wrapper.&lt;/li&gt;
&lt;li&gt;Install dependencies the same way CI installs them.&lt;/li&gt;
&lt;li&gt;Run the wrapper against one known green test.&lt;/li&gt;
&lt;li&gt;Record exit code zero beside the SHA.&lt;/li&gt;
&lt;li&gt;Only then start an agent on the ticket paths.&lt;/li&gt;
&lt;li&gt;Keep the pin files out of the agent edit set.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Proposed clone and SHA capture commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-parse HEAD
git status &lt;span class="nt"&gt;--short&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If status is not empty, stop the pin process.&lt;br&gt;
A dirty tree makes the proof SHA meaningless.&lt;br&gt;
Clean the tree, then capture the hash again.&lt;/p&gt;
&lt;h2&gt;
  
  
  Failure analysis: common first-hour misses
&lt;/h2&gt;

&lt;p&gt;You copied &lt;code&gt;npm test&lt;/code&gt; and skipped &lt;code&gt;npm ci&lt;/code&gt;.&lt;br&gt;
CI uses a clean install. Your laptop does not.&lt;br&gt;
Lockfile drift then looks like a test bug.&lt;/p&gt;

&lt;p&gt;You ignored &lt;code&gt;working-directory&lt;/code&gt; in the job definition.&lt;br&gt;
The agent edits tests in the repo root.&lt;br&gt;
CI never sees those files during the check.&lt;/p&gt;

&lt;p&gt;You dropped &lt;code&gt;CI=true&lt;/code&gt; from the local wrapper.&lt;br&gt;
Jest starts watch mode and then hangs forever.&lt;br&gt;
You think the agent crashed the whole suite.&lt;/p&gt;

&lt;p&gt;You pinned a deploy job instead of the test job.&lt;br&gt;
The deploy job needs secrets you do not have.&lt;br&gt;
Your local runner fails for the wrong reason.&lt;/p&gt;

&lt;p&gt;You let the agent fix the workflow file itself.&lt;br&gt;
Now CI matches the agent, not the team contract.&lt;br&gt;
That is not onboarding. That is pipeline hijacking.&lt;/p&gt;

&lt;p&gt;Need to fix short sentences in that last pair.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this does not cover
&lt;/h2&gt;

&lt;p&gt;This workflow does not parse reusable workflows for you.&lt;br&gt;
It does not expand composite actions into shell steps.&lt;br&gt;
It does not replace a real CI reproduction environment.&lt;/p&gt;

&lt;p&gt;Private runners, service containers, and GPU labels stay remote.&lt;br&gt;
You cannot pin those with a shell wrapper alone.&lt;br&gt;
Ask a teammate before you invent a fake substitute.&lt;/p&gt;

&lt;p&gt;The free model path will not know org policy.&lt;br&gt;
It will not know your banned path list.&lt;br&gt;
It will not own the rollback if the PR is wrong.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who should not use this approach
&lt;/h2&gt;

&lt;p&gt;Do not use this if you cannot clone the repo.&lt;br&gt;
Do not use this if CI config is not in git.&lt;br&gt;
Do not use this if you will skip the proof run.&lt;/p&gt;

&lt;p&gt;Staff engineers with a maintained Makefile can ignore it.&lt;br&gt;
Use their existing local CI target instead of a second pin.&lt;br&gt;
Do not create another source of truth on day one.&lt;/p&gt;

&lt;p&gt;Skip this if your first task is production access only.&lt;br&gt;
A pin file will not unlock secrets or runner labels.&lt;br&gt;
Ask for a sandbox ticket before you prompt any agent.&lt;/p&gt;
&lt;h2&gt;
  
  
  First rollback still belongs to you
&lt;/h2&gt;

&lt;p&gt;If the agent PR is unexplained, revert it.&lt;br&gt;
The pin file tells you which command was green before.&lt;br&gt;
That evidence beats the agent's summary every time.&lt;/p&gt;

&lt;p&gt;Record the revert command next to the pin.&lt;br&gt;
Keep both notes in the same directory.&lt;br&gt;
Future you will need them on week two.&lt;/p&gt;

&lt;p&gt;Proposed revert note for the pin file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Revert&lt;/span&gt;
git revert --no-edit &lt;span class="nt"&gt;&amp;lt;merge-sha&amp;gt;&lt;/span&gt;
.ci-pin/ci-local.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the pin after the revert lands locally.&lt;br&gt;
Green after revert means the pin still holds.&lt;br&gt;
Red after revert means the pin was a lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations recap
&lt;/h2&gt;

&lt;p&gt;Short local wrappers hide real pipeline complexity.&lt;br&gt;
Monorepos need one pin per package, not one pin.&lt;br&gt;
Matrix jobs need an explicit cell, not a slogan.&lt;/p&gt;

&lt;p&gt;Generated wrappers go stale when CI files move.&lt;br&gt;
Re-extract the command when the workflow file changes.&lt;br&gt;
Treat a stale pin as a failing onboarding test.&lt;/p&gt;

&lt;p&gt;These scripts were not executed on your repository.&lt;br&gt;
They are labeled proposals for a first hour.&lt;br&gt;
Adapt paths, package managers, and job names before running them.&lt;/p&gt;

&lt;p&gt;Pin CI first. Prompt second. Revert if you cannot explain the diff.&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>testing</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Name One Green Test Before Your First Agent Session</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Mon, 21 Sep 2026 13:46:36 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/name-one-green-test-before-your-first-agent-session-2oi2</link>
      <guid>https://dev.to/gitgo_5662/name-one-green-test-before-your-first-agent-session-2oi2</guid>
      <description>&lt;p&gt;Do not start your first hour with an agent. Prove one named green test on this checkout. Freeze a session card before any generated edit.&lt;/p&gt;

&lt;p&gt;A new repo is not a chat window. Agents invent install steps with calm confidence. README scripts often lag the real lockfile. You then ship a story you cannot rerun.&lt;/p&gt;

&lt;p&gt;Generated code can hide work you never ran. Reviewers still ask for a local command. Your first hour should make hiding impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this belongs on day one
&lt;/h2&gt;

&lt;p&gt;You join a team and clone the default branch. Ticket text often names a feature, not a test. The agent starts editing files you cannot map. Owners, runtimes, and CI commands stay hidden.&lt;/p&gt;

&lt;p&gt;Juniors skip this because setup feels unproductive. It is the only work that makes later review honest. You cannot defend a diff you never executed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The session card rule
&lt;/h2&gt;

&lt;p&gt;Name one existing test before you open a model. Run that test by exact command on your machine. Write the result into a session card file. Only then let an agent touch a bounded path.&lt;/p&gt;

&lt;p&gt;Treat the card as a gate, not a diary. No card means no agent session and no PR. Do not paste the ticket into a prompt first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fields you must record
&lt;/h2&gt;

&lt;p&gt;Keep the session card small and boring. Record only facts you can recheck later.&lt;/p&gt;

&lt;p&gt;Use these eight fields, nothing clever:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;head_sha&lt;/code&gt; — current commit, no floating &lt;code&gt;HEAD&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;branch&lt;/code&gt; — the branch you will actually push.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dirty_paths&lt;/code&gt; — files already changed in the tree.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;runtime&lt;/code&gt; — language binary and version string.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_command&lt;/code&gt; — one command, copied verbatim.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_exit_code&lt;/code&gt; — zero, or you stop here.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;owners&lt;/code&gt; — &lt;code&gt;CODEOWNERS&lt;/code&gt; lines for your path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;last_author&lt;/code&gt; — last merge that touched that path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a field is missing, write &lt;code&gt;unknown&lt;/code&gt;. Do not let the agent invent a substitute value. Unknown owners mean you ask a human next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed script
&lt;/h2&gt;

&lt;p&gt;Label this as a proposed local script. Run it in a throwaway clone first. It prints JSON to stdout and exits nonzero on a red test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# proposed: session-card.sh — run in a throwaway clone first&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;TARGET_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;TEST_COMMAND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_COMMAND&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;path&amp;gt; &amp;lt;exact test command&amp;gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; git rev-parse &lt;span class="nt"&gt;--is-inside-work-tree&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"not a git checkout"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;HEAD_SHA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git branch &lt;span class="nt"&gt;--show-current&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;unknown&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;DIRTY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/[[:space:]]*$//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;RUNTIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;python3 &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; node &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; go version &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;unknown&lt;span class="o"&gt;)&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; +e
bash &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_COMMAND&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;TEST_EXIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nv"&gt;OWNERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; CODEOWNERS &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;OWNERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^#'&lt;/span&gt; CODEOWNERS | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^[[:space:]]*$'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/[[:space:]]*$//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OWNERS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;OWNERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; .github/CODEOWNERS &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;OWNERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^#'&lt;/span&gt; .github/CODEOWNERS | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^[[:space:]]*$'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/[[:space:]]*$//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OWNERS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;OWNERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;LAST_AUTHOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%an &amp;lt;%ae&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;unknown&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;DIRTY_JSON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DIRTY&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;none&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;
import json, os
print(json.dumps({
  "head_sha": os.environ["HEAD_SHA"],
  "branch": os.environ["BRANCH"],
  "dirty_paths": os.environ["DIRTY_JSON"],
  "runtime": os.environ["RUNTIME"],
  "test_command": os.environ["TEST_COMMAND"],
  "test_exit_code": int(os.environ["TEST_EXIT"]),
  "owners": os.environ["OWNERS"],
  "last_author": os.environ["LAST_AUTHOR"],
  "target_path": os.environ["TARGET_PATH"],
}, indent=2))
&lt;/span&gt;&lt;span class="no"&gt;PY

&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_EXIT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export the values before the Python block if your shell needs them. A minimal wrapper looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;HEAD_SHA BRANCH DIRTY_JSON RUNTIME TEST_COMMAND TEST_EXIT OWNERS LAST_AUTHOR TARGET_PATH
&lt;span class="nv"&gt;HEAD_SHA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# ...same assignments as above, then python3 - &amp;lt;&amp;lt;'PY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the JSON as &lt;code&gt;session-card.json&lt;/code&gt; at the repo root. Do not commit it unless your team asks. Paste it into the PR body instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hour-one steps
&lt;/h2&gt;

&lt;p&gt;Follow this order. Do not skip to a model window.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone with the team's documented URL. Avoid random forks on day one.&lt;/li&gt;
&lt;li&gt;Check out the branch named in the ticket. Record the SHA immediately.&lt;/li&gt;
&lt;li&gt;Install from the lockfile, not from memory. Prefer &lt;code&gt;npm ci&lt;/code&gt;, &lt;code&gt;pnpm i --frozen-lockfile&lt;/code&gt;, or &lt;code&gt;pip install -r&lt;/code&gt; only as the repo documents.&lt;/li&gt;
&lt;li&gt;Find one existing test near your path. Use &lt;code&gt;rg&lt;/code&gt;, &lt;code&gt;git grep&lt;/code&gt;, or the test runner list command.&lt;/li&gt;
&lt;li&gt;Copy the exact command from CI config when you can. Check &lt;code&gt;.github/workflows&lt;/code&gt;, &lt;code&gt;Makefile&lt;/code&gt;, or &lt;code&gt;package.json&lt;/code&gt; scripts.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;session-card.sh &amp;lt;path&amp;gt; '&amp;lt;command&amp;gt;'&lt;/code&gt;. Stop if the exit code is not zero.&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;CODEOWNERS&lt;/code&gt; and the last author. Send a short ping if both are &lt;code&gt;unknown&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bound the agent to that path only. Refuse extra files in the first session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example commands you can copy. Replace the test name with a real one from your tree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:example/app.git
&lt;span class="nb"&gt;cd &lt;/span&gt;app
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; first-hour/session-card
git rev-parse HEAD

&lt;span class="c"&gt;# discover, then freeze — do not invent the command&lt;/span&gt;
git &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"describe(&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;def test_"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'*.test.ts'&lt;/span&gt; &lt;span class="s1"&gt;'*.py'&lt;/span&gt; | &lt;span class="nb"&gt;head
&lt;/span&gt;rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"npm test|pytest|go test"&lt;/span&gt; Makefile package.json .github/workflows &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

chmod&lt;/span&gt; +x session-card.sh
./session-card.sh src/billing &lt;span class="s2"&gt;"npm test -- --runTestsByPath src/billing/invoice.test.ts"&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
./session-card.sh pkg/bill &lt;span class="s2"&gt;"pytest pkg/bill/test_invoice.py::test_total -q"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the named test is red on a clean checkout, you do not have an agent problem. You have an environment problem. Ask a teammate before generating code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table
&lt;/h2&gt;

&lt;p&gt;Use this table before you open any model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Open an agent?&lt;/th&gt;
&lt;th&gt;Next action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Named test is red on clean HEAD&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Fix install, toolchain, or secrets with a human&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;owners&lt;/code&gt; is &lt;code&gt;unknown&lt;/code&gt; and last author is gone&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Ask in the team channel before editing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dirty files you did not create&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;git status&lt;/code&gt;, then stash or reset with review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test is green, path is owned, tree is clean&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Bound the path; keep the card in the PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent wants to change lockfiles or CI&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Split that work; it is not a first PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You cannot rerun the card command&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Do not request review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Print the table in your notes if it helps. The point is a stop rule, not a vibe.&lt;/p&gt;

&lt;h2&gt;
  
  
  First PR after the card exists
&lt;/h2&gt;

&lt;p&gt;Keep the first PR inside the path you measured. One test file and one production file is enough. Do not let the agent “clean up” neighbors.&lt;/p&gt;

&lt;p&gt;Paste the session card at the top of the PR body. Then add the command a reviewer can rerun. Example markdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Session card&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; SHA: 9f3c1a2
&lt;span class="p"&gt;-&lt;/span&gt; Path: src/billing
&lt;span class="p"&gt;-&lt;/span&gt; Command: npm test -- --runTestsByPath src/billing/invoice.test.ts
&lt;span class="p"&gt;-&lt;/span&gt; Exit: 0
&lt;span class="p"&gt;-&lt;/span&gt; Owners: @billing-oncall
&lt;span class="p"&gt;-&lt;/span&gt; Last author: Alex &lt;span class="nv"&gt;&amp;lt;alex@example.com&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Replay&lt;/span&gt;

npm ci
npm test -- --runTestsByPath src/billing/invoice.test.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask the agent for a diff against that SHA only. Reject a patch that retargets &lt;code&gt;main&lt;/code&gt; while you slept. Reject a patch that rewrites the test command.&lt;/p&gt;

&lt;p&gt;If the model changes assertions to match new code, stop. Your job on day one is a green replay, not a greener story. Restore the original test and shrink the change.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a free server is useful
&lt;/h2&gt;

&lt;p&gt;Laptops on day one are often thin. You may lack a local model runtime. You still need the session card before any remote chat.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source project with free model access and a free server option. Use it only after &lt;code&gt;session-card.json&lt;/code&gt; exists and the named test is green on your checkout.&lt;/p&gt;

&lt;p&gt;Keep the same path bound on the server. Paste the card fields into the session notes. Do not paste secrets, &lt;code&gt;.env&lt;/code&gt; files, or production dumps. If the server cannot rerun your exact test command, treat the output as untrusted text.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you cannot explain the diff
&lt;/h2&gt;

&lt;p&gt;Revert beats a guessed explanation. Use the SHA from the card as the recovery point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git switch main
git revert &lt;span class="nt"&gt;--no-edit&lt;/span&gt; &amp;lt;pr-merge-sha&amp;gt;
&lt;span class="c"&gt;# or restore your branch to the frozen card SHA&lt;/span&gt;
git switch first-hour/session-card
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import json;print(json.load(open("session-card.json"))["head_sha"])'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not open a second agent to “fix the revert.” Rerun the named test first. If it fails on the frozen SHA, your card was already a lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This workflow does not replace CI. A green local test can still miss OS, timezone, or service wiring. &lt;code&gt;CODEOWNERS&lt;/code&gt; grep is naive and misses glob owners. Monorepos may need a package filter the script does not infer.&lt;/p&gt;

&lt;p&gt;The script also trusts the command you pass. A wrong command that exits zero is a false gate. You must copy the command from CI or a teammate, not from a model.&lt;/p&gt;

&lt;p&gt;It will not help if tests need private services you cannot run. In that case, record &lt;code&gt;unknown&lt;/code&gt; for the test and pair with the owner. Do not fake a unit test the suite never had.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should skip this
&lt;/h2&gt;

&lt;p&gt;Skip this if you already run the full suite locally every morning. Skip it during an active incident when a lead already named the command. Skip it if policy forbids extra servers or extra JSON in PRs.&lt;/p&gt;

&lt;p&gt;Staff engineers pairing on a known path can shorten the card. Juniors on a first checkout should not. The card is for people who still need a map.&lt;/p&gt;

&lt;p&gt;Do not start your first hour with an agent. Name one green test, freeze the card, then keep the first PR small enough to replay.&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>ai</category>
      <category>testing</category>
    </item>
    <item>
      <title>Build a Path Deny List Before Your First Agent PR</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Sun, 20 Sep 2026 11:44:58 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/build-a-path-deny-list-before-your-first-agent-pr-3lje</link>
      <guid>https://dev.to/gitgo_5662/build-a-path-deny-list-before-your-first-agent-pr-3lje</guid>
      <description>&lt;p&gt;Your first agent patch will wander into the wrong files. Freeze those paths before you write a prompt. A junior needs a deny list on day one.&lt;/p&gt;

&lt;p&gt;Agents will chase a green local run. They do not share your merge risk. Secrets, lockfiles, and CI configs look like easy edits. They are the edits that wake the whole team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this gate exists
&lt;/h2&gt;

&lt;p&gt;You joined a new repo this morning. You can clone it and run tests. You cannot yet judge blast radius well.&lt;/p&gt;

&lt;p&gt;An agent will refresh a lockfile without asking. It will rewrite a workflow file next. It will open env examples and guess values. None of that belongs in your first PR.&lt;/p&gt;

&lt;p&gt;You need a written freeze you can grep. Your memory fails fast under time pressure. A written file does not fail that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deny list in plain terms
&lt;/h2&gt;

&lt;p&gt;Treat every path as allowed or frozen. Frozen paths never enter the agent context window. Those frozen paths must never enter &lt;code&gt;git add&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use this decision table on day one. Copy it, then rename patterns for your repo.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path pattern&lt;/th&gt;
&lt;th&gt;Freeze?&lt;/th&gt;
&lt;th&gt;Why you freeze it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.env.*&lt;/code&gt;, &lt;code&gt;**/*.pem&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Secrets leak through prompts and diffs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;**/credentials*&lt;/code&gt;, &lt;code&gt;**/*secret*&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;The names already signal production risk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;package-lock.json&lt;/code&gt;, &lt;code&gt;yarn.lock&lt;/code&gt;, &lt;code&gt;pnpm-lock.yaml&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Lock churn hides the real change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Cargo.lock&lt;/code&gt;, &lt;code&gt;go.sum&lt;/code&gt;, &lt;code&gt;poetry.lock&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Same problem in other ecosystems.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.github/workflows/**&lt;/code&gt;, &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;CI edits skip your reviewer set.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CODEOWNERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Ownership files change who must sign.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;**/migrations/**&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Data rewrites are not first-PR work.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;infra/**&lt;/code&gt;, &lt;code&gt;terraform/**&lt;/code&gt;, &lt;code&gt;k8s/**&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Cloud diffs need a different owner.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;CONTRIBUTING.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Docs are safe if you read them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source under the ticket path&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;That is the work you were assigned.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is a proposal, not team policy. Your reviewers may freeze even more paths. Ask them before you treat this as law.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Inventory the tree before you prompt
&lt;/h2&gt;

&lt;p&gt;Do this work in a clean clone. Do not open a chat window yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@example.com:your-org/your-repo.git
&lt;span class="nb"&gt;cd &lt;/span&gt;your-repo
git status
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should list the dangerous file names yourself. Do not ask a model to guess them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'.env'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'.env.*'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.pem'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'CODEOWNERS'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*lock.json'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*lock.yaml'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'Cargo.lock'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'go.sum'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'.gitlab-ci.yml'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s1"&gt;'./.git/*'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save that output as your first artifact. You produced that list without an agent. If &lt;code&gt;find&lt;/code&gt; is noisy, narrow the names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; .github/workflows 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; infra terraform k8s 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read &lt;code&gt;CODEOWNERS&lt;/code&gt; now if it exists. Note who owns workflows and infra paths. You will not stage those trees today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Write a deny file you can grep
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.agent-untouchable&lt;/code&gt; at your repo root. Keep the deny file boring and short. You should use only one pattern per line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# proposal: junior path freeze, not a security boundary
.env
.env.*
*.pem
**/credentials*
**/*secret*
package-lock.json
yarn.lock
pnpm-lock.yaml
Cargo.lock
go.sum
poetry.lock
.github/workflows/**
.gitlab-ci.yml
CODEOWNERS
**/migrations/**
infra/**
terraform/**
k8s/**
.agent-untouchable
check-untouchable.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the file local until the team wants it. Copy it outside the worktree as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .agent-untouchable &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.agent-untouchable.&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A bad reset then cannot eat your freeze list. You still own the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Install a checker that fails loud
&lt;/h2&gt;

&lt;p&gt;This checker script is a proposal only. Run it only on your local machine. Do not treat it as production policy.&lt;/p&gt;

&lt;p&gt;It compares staged names to the deny list. It exits one when a frozen path is staged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# check-untouchable.sh&lt;/span&gt;
&lt;span class="c"&gt;# proposal: local gate for a junior's first agent PR&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;shopt&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; globstar nullglob

&lt;span class="nv"&gt;DENY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;.agent-untouchable&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DENY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing deny file: &lt;/span&gt;&lt;span class="nv"&gt;$DENY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;mapfile&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; staged &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;git diff &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="k"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;staged&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"nothing staged"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; pat&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pat&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pat&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^# &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;continue
  for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;staged&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$pat&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FROZEN path staged: &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt; (pattern &lt;/span&gt;&lt;span class="nv"&gt;$pat&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
      &lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
    &lt;span class="k"&gt;fi
  done
done&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DENY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"unstage frozen paths before you request review"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"staged paths are outside the deny list"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable before you rely on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x check-untouchable.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire a local hook if you want friction. Skip shared repo hooks on day one. You do not own hook policy yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .git/hooks
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .git/hooks/pre-commit &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/usr/bin/env bash
exec "&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--show-toplevel&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;/check-untouchable.sh"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .git/hooks/pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A local hook protects your index only. It does not police the remote branch. That is enough for a first PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Prove the checker with a fake diff
&lt;/h2&gt;

&lt;p&gt;Never trust a gate you have not failed. Stage one frozen file on purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# probe"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; README.md
&lt;span class="c"&gt;# pick a real frozen file if it exists&lt;/span&gt;
git add package-lock.json 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; git add .github/workflows 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
./check-untouchable.sh &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;git reset HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want a red line from the checker. A silent pass teaches you nothing useful. Record the exact command you just ran.&lt;/p&gt;

&lt;p&gt;If the pattern language misses, tighten the lines. Bash globbing is not the gitignore language. Enable &lt;code&gt;globstar&lt;/code&gt; if you need recursive stars.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# already in the script; re-run after edits&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; globstar nullglob
./check-untouchable.sh &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retest the fake diff after that change. Keep the failing output in your notes. You now have proof the gate can fire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Bound the prompt after the freeze
&lt;/h2&gt;

&lt;p&gt;Only now may you talk to an agent. Paste the ticket path and the deny file. Do not paste env files or keys.&lt;/p&gt;

&lt;p&gt;Use a prompt that states the bound twice. This block is a template, not a transcript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edit only files under app/billing/.
Do not read or write paths in .agent-untouchable.
Do not refresh lockfiles.
Do not edit workflows.
Return a file list before any patch.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You must demand the full file list first. Then you may accept a bounded patch. Then run the checker, then known tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# replace with the test command from README&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If tests are unknown, stop the patch. You do not edit a repo you cannot run. Keep this workflow on path control only.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example for a billing ticket
&lt;/h2&gt;

&lt;p&gt;You were assigned ticket &lt;code&gt;BILL-214&lt;/code&gt; this morning. The ticket names &lt;code&gt;app/billing/invoice.py&lt;/code&gt; only. That is the only product path you will stage.&lt;/p&gt;

&lt;p&gt;Your agent now returns four changed files. You read the names before any patch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/billing/invoice.py
app/billing/invoice_test.py
package-lock.json
.github/workflows/ci.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two of those four names are frozen. You reject the whole patch at once. You do not "fix" the workflow as a favor.&lt;/p&gt;

&lt;p&gt;Restate the bound in one short prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return a patch for app/billing/invoice.py
and app/billing/invoice_test.py only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stage only those two files after that. Run the checker on the index. Run the tests you already know well.&lt;/p&gt;

&lt;p&gt;This is slower than accepting the full diff. Slower is the point on day one. You are learning the repo, not farming merges.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you paste, and what you never paste
&lt;/h2&gt;

&lt;p&gt;You may paste the deny file itself. You may paste the ticket path next. You may paste a failing test name.&lt;/p&gt;

&lt;p&gt;You never paste a &lt;code&gt;.env&lt;/code&gt; file. You never paste &lt;code&gt;id_rsa&lt;/code&gt; or pem files. You never paste production URLs with tokens.&lt;/p&gt;

&lt;p&gt;If a README shows a sample key, stop. Replace it with &lt;code&gt;REDACTED&lt;/code&gt; before any chat. The agent does not need real credentials to edit billing math.&lt;/p&gt;

&lt;p&gt;This rule is stronger than the checker. The checker only sees git state. The chat sees whatever you paste in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free coding agent fits
&lt;/h2&gt;

&lt;p&gt;You can draft the deny list by hand. You should draft that list yourself first. That inventory work is a junior skill.&lt;/p&gt;

&lt;p&gt;You may still want a model for extra patterns. Keep all secrets out of that chat. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode is an open source coding-agent project. It offers free model access and a free server option. This article does not list model names or quotas. Those details change and must be checked on the project page.&lt;/p&gt;

&lt;p&gt;Want a free model for extra pattern suggestions? MonkeyCode's free model access covers that narrow step. Use a free server only as a scratch runner. Run &lt;code&gt;check-untouchable.sh&lt;/code&gt; there if your laptop is busy. Do not upload env files or production keys.&lt;/p&gt;

&lt;p&gt;The deny list exists to stop that habit. After the freeze file exists, the rest of the work is git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes you should expect
&lt;/h2&gt;

&lt;p&gt;The checker will miss renamed files often. A move plus &lt;code&gt;git add -A&lt;/code&gt; can sneak past a stale pattern.&lt;/p&gt;

&lt;p&gt;The checker will also miss generated files. A build may write a lockfile you never meant to stage.&lt;/p&gt;

&lt;p&gt;The checker will not stop a paste into a chat box. Frozen paths in git are not frozen in your clipboard.&lt;/p&gt;

&lt;p&gt;If the agent rewrites the deny file, you lost. Protect the deny file itself from edits.&lt;/p&gt;

&lt;p&gt;Add those lines to the deny list. Then restage nothing from that frozen set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This local workflow is not authorization at all. It is not a secret scanning tool. It is not a &lt;code&gt;CODEOWNERS&lt;/code&gt; file replacement.&lt;/p&gt;

&lt;p&gt;Bash globbing is a weak matcher here. It will not parse gitignore files correctly. It will not understand submodules or sparse checkout.&lt;/p&gt;

&lt;p&gt;A determined agent can still edit a frozen file on disk. Your hook only sees &lt;code&gt;git add&lt;/code&gt; events. Those unstaged edits still remain on disk.&lt;/p&gt;

&lt;p&gt;The table above is only a proposal. It is not your company's written policy. Ask your reviewer which paths are actually sacred.&lt;/p&gt;

&lt;p&gt;Do not publish a deny file that names internal hosts. Keep every pattern generic and boring instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Skip this workflow if you were hired to change CI. Skip it if the ticket is a lockfile bump. Skip it during an incident when the frozen path is the fix.&lt;/p&gt;

&lt;p&gt;Staff engineers with merge rights may need those files. This gate is for a junior's first agent PR. It is not a repo-wide standard until the team says so.&lt;/p&gt;

&lt;p&gt;Do not use a remote agent host for an uncleared private repo. A free server is not a legal review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the freeze, then request review
&lt;/h2&gt;

&lt;p&gt;Write the deny list before the first prompt. Fail the checker on purpose once today. Keep frozen paths out of the index.&lt;/p&gt;

&lt;p&gt;The agent can still write code you understand. It cannot wander into secrets, locks, and CI today.&lt;/p&gt;

&lt;p&gt;Ask your reviewer to confirm the freeze list. Then open the PR with the two files you can name.&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Hour One: Capture the Merge Command Before Any Agent Diff</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Sat, 19 Sep 2026 09:50:03 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/hour-one-capture-the-merge-command-before-any-agent-diff-3ba9</link>
      <guid>https://dev.to/gitgo_5662/hour-one-capture-the-merge-command-before-any-agent-diff-3ba9</guid>
      <description>&lt;p&gt;You skip product code during your first hour. You record one command that would block merge. That frozen command becomes your first onboarding artifact.&lt;/p&gt;

&lt;p&gt;New hires break this rule under agent pressure. A model can draft a patch in seconds. A true local green still needs your hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hour one is a recording job
&lt;/h2&gt;

&lt;p&gt;You just joined a repo you did not design. You cannot explain the test runner yet. You also cannot name the merge-gating CI job.&lt;/p&gt;

&lt;p&gt;An agent will guess both of those names. That kind of guess only looks like progress. Real progress is a command you actually ran.&lt;/p&gt;

&lt;p&gt;Your scope stays small on purpose for today. Find one command and run it once today. Then write that exact command line down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze a four-field contract
&lt;/h2&gt;

&lt;p&gt;Treat the hour as a freeze window. No feature files change until the contract exists. Feature work waits on a written gate.&lt;/p&gt;

&lt;p&gt;The written contract holds four fields and no more. Copy those four fields in this exact order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The exact command that must pass.&lt;/li&gt;
&lt;li&gt;The directory where you run it.&lt;/li&gt;
&lt;li&gt;The env files the command requires.&lt;/li&gt;
&lt;li&gt;The CI job name it maps onto.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep the note in git or in your editor. The freeze matters more than the path. Empty fields are not allowed in this contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Clone and pin the SHA
&lt;/h2&gt;

&lt;p&gt;Stay on the default remote for now. Do not add extra forks during hour one. You only need the team's canonical clone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:org/service.git
&lt;span class="nb"&gt;cd &lt;/span&gt;service
git status
git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD
git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--oneline&lt;/span&gt;
git remote &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm the default branch name in writing. Copy the current SHA into your notes. You will reuse both of those values later.&lt;/p&gt;

&lt;p&gt;Stop if clone asks for submodules you cannot fetch. Record that fetch failure as a blocker. Do not let an agent fix submodule auth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Read CI before the README
&lt;/h2&gt;

&lt;p&gt;The README often sells the project to strangers. CI remains the real merge gate here. Open workflow files before any tutorial text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; .github/workflows
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1,160p'&lt;/span&gt; .github/workflows/&lt;span class="k"&gt;*&lt;/span&gt;.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search the workflows for the pull_request job next. Ignore jobs that only run on a schedule. Hour one cares about merge gates only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s2"&gt;"pull_request|pytest|npm test|pnpm|go test|gradle"&lt;/span&gt; .github/workflows/&lt;span class="k"&gt;*&lt;/span&gt;.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the job name without rewriting it. Copy the run steps without cleaning them. Your notes should match the YAML exactly.&lt;/p&gt;

&lt;p&gt;A proposed GitHub Actions shape looks like this. Treat it as a template, not your repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proposed excerpt. Match names to your workflows.&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;unit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python -m pytest -q tests/unit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the repo uses GitLab or Jenkins, open that file instead. The same read-the-gate rule applies there. Read the gate file, not the marketing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Run the smallest local equivalent
&lt;/h2&gt;

&lt;p&gt;Pick one job, not the full matrix. Prefer unit tests over full browser e2e. Label the mapping as proposed if names drift.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proposed local map of CI job "unit"&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest &lt;span class="nt"&gt;-q&lt;/span&gt; tests/unit
&lt;span class="c"&gt;# or&lt;/span&gt;
pnpm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--runInBand&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
go &lt;span class="nb"&gt;test&lt;/span&gt; ./internal/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that line on a clean tree. Do not edit any source files first. Capture the exit code in your notes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
git status &lt;span class="nt"&gt;--short&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero means you now own a baseline. Non-zero means the clone is already red. You still do not patch the failing tree.&lt;/p&gt;

&lt;p&gt;Write the failure down and ask a teammate. Do not hide a red clone with generated code. Hour one records truth, including a red gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Fill the contract file
&lt;/h2&gt;

&lt;p&gt;Create a short markdown contract file now. Fill every required field before you continue. Refuse any blank placeholders in those fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# First-hour run contract&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; date: 2026-09-19
&lt;span class="p"&gt;-&lt;/span&gt; clone_sha: REPLACE_ME
&lt;span class="p"&gt;-&lt;/span&gt; default_branch: main
&lt;span class="p"&gt;-&lt;/span&gt; working_directory: .
&lt;span class="p"&gt;-&lt;/span&gt; merge_gate_command: python -m pytest -q tests/unit
&lt;span class="p"&gt;-&lt;/span&gt; mapped_ci_job: unit
&lt;span class="p"&gt;-&lt;/span&gt; required_env_files:
&lt;span class="p"&gt;  -&lt;/span&gt; .env.example
&lt;span class="p"&gt;-&lt;/span&gt; secrets_needed: none for unit tests
&lt;span class="p"&gt;-&lt;/span&gt; local_exit_code: 0
&lt;span class="p"&gt;-&lt;/span&gt; notes: e2e skipped; docker not required for unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace REPLACE_ME with the SHA you copied. Keep working_directory explicit even if it is &lt;code&gt;.&lt;/code&gt;. Future you will thank present you for this.&lt;/p&gt;

&lt;p&gt;Commit the file only if the team wants it. Otherwise store that file beside the clone. The contract is a freeze, not a ritual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Wrap the command in a script
&lt;/h2&gt;

&lt;p&gt;Do not keep the gate in chat history. Wrap the same line in a script. Chat logs will not survive week two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# scripts/merge_gate.sh&lt;/span&gt;
&lt;span class="c"&gt;# Label: proposed first-hour wrapper. Match your stack.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/.."&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cwd=&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"sha=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cmd=python -m pytest -q tests/unit"&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest &lt;span class="nt"&gt;-q&lt;/span&gt; tests/unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable and run it twice today. Two greens beat a single lucky green. Use the same directory and expected exit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x scripts/merge_gate.sh
./scripts/merge_gate.sh
./scripts/merge_gate.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run this wrapper after every later agent diff. If the wrapper drifts, you lost the contract. Restore the original line before you debug features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Use a decision table before any prompt
&lt;/h2&gt;

&lt;p&gt;Walk this decision table in strict order. Do not skip a single table row. Every row is a brake, not a formality.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;If yes&lt;/th&gt;
&lt;th&gt;If no&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Did the merge command exit 0 on a clean clone?&lt;/td&gt;
&lt;td&gt;Continue&lt;/td&gt;
&lt;td&gt;Stop and ask a teammate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can you name the CI job it maps to?&lt;/td&gt;
&lt;td&gt;Continue&lt;/td&gt;
&lt;td&gt;Re-read the workflow file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the first ticket need secrets you lack?&lt;/td&gt;
&lt;td&gt;Do not prompt&lt;/td&gt;
&lt;td&gt;Continue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the ticket larger than the gate command?&lt;/td&gt;
&lt;td&gt;Split the ticket&lt;/td&gt;
&lt;td&gt;Keep the scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can you explain a revert in one sentence?&lt;/td&gt;
&lt;td&gt;You may prompt&lt;/td&gt;
&lt;td&gt;Shrink again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Any stop cell ends the coding path. You ask a human before any generated patch. You do not generate a helpful patch.&lt;/p&gt;

&lt;p&gt;This table is the first-hour safety brake. Agents do not get a vote here. You own every yes and every no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Ask a model for the wrapper only
&lt;/h2&gt;

&lt;p&gt;You still write no product code today. You may ask for help around the contract.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode can draft that wrapper from your four fields. The product offers free model access and a free server option.&lt;/p&gt;

&lt;p&gt;Paste the filled contract into the model prompt. Ban all edits under src and app. Keep that merge command byte-for-byte identical.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You must not edit src/ or any application file.
Read the first-hour run contract.
Propose only scripts/merge_gate.sh.
Keep merge_gate_command byte-for-byte.
Do not add network calls.
Do not invent environment variables.
Do not upgrade dependencies.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read every added line out loud once. Drop any line you cannot explain. If the script changes the test command, reject the whole diff.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--check&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
git diff &lt;span class="nt"&gt;-U1&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; scripts/merge_gate.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A free server run is enough for this helper. You need a wrapper, not an architecture change. Keep the prompt boring on purpose today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Ship a zero-behavior change or ship nothing
&lt;/h2&gt;

&lt;p&gt;Hour one may produce no pull request. That result still counts as real success. A recorded gate beats an unearned feature PR.&lt;/p&gt;

&lt;p&gt;If you open a PR, limit the files. Allow the wrapper and contract files only. Leave every feature path untouched in this PR.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; chore/first-hour-run-contract
git add scripts/merge_gate.sh
git status
git diff &lt;span class="nt"&gt;--&lt;/span&gt; scripts/merge_gate.sh
git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%h %s'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write the PR body from the contract fields. Name the mapped CI job in the summary. Paste the local exit code you already observed.&lt;/p&gt;

&lt;p&gt;If review asks for a feature, refuse for this PR. Hour one remains a recording gate only. Delivery starts only after that gate exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the freeze actually blocks
&lt;/h2&gt;

&lt;p&gt;It blocks fake local greens from chat-invented commands. It blocks npm test when CI runs pnpm. It blocks first patches that need production secrets.&lt;/p&gt;

&lt;p&gt;It also blocks the 400-line cleanup impulse. That cleanup is not real onboarding work. It is unrequested noise on a day-one diff.&lt;/p&gt;

&lt;p&gt;You will see agents offer all three. Your written contract gives you a no. Use that no without any apology.&lt;/p&gt;

&lt;h2&gt;
  
  
  First PR and first rollback still use this file
&lt;/h2&gt;

&lt;p&gt;Your first PR should rerun the merge_gate script. A failing wrapper means the PR is not ready. Do not request review on a red wrapper.&lt;/p&gt;

&lt;p&gt;Your first rollback should rerun the same wrapper. Revert the agent commit with git revert. Then run the wrapper and confirm green.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git revert &lt;span class="nt"&gt;--no-edit&lt;/span&gt; HEAD
./scripts/merge_gate.sh
git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you cannot explain the revert, you should not have merged. The hour-one file makes that sentence short. Keep that explanation short on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This workflow assumes you can read a CI file. Some teams hide gates in private Jenkins. A local clone cannot reveal that hidden job.&lt;/p&gt;

&lt;p&gt;The local wrapper is not real CI. Host OS, CPU, and caches still differ. A local zero can fail on GitHub Actions.&lt;/p&gt;

&lt;p&gt;Commands in this article are labeled templates. They are not timings or model benchmarks. Match them to your repo before you trust them.&lt;/p&gt;

&lt;p&gt;Do not use this delay on a production hotfix. Do not use it if you already own the pipeline. Do not use it to skip human review.&lt;/p&gt;

&lt;p&gt;Skip every coding agent if the repo forbids generated code. Written team rules always beat this tutorial. Follow the team even when agents are eager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should use this
&lt;/h2&gt;

&lt;p&gt;You are a junior engineer on day one. You can clone and you can read YAML. You cannot yet defend a large agent diff.&lt;/p&gt;

&lt;p&gt;If that describes you, stop coding now. Write the merge command and run it once. Then freeze that command in the contract.&lt;/p&gt;

&lt;p&gt;After the freeze, a small wrapper draft is optional. The run contract itself is not optional.&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>testing</category>
      <category>ai</category>
    </item>
    <item>
      <title>Score the Agent Diff Before You Request Review</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Fri, 18 Sep 2026 08:09:07 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/score-the-agent-diff-before-you-request-review-1lbj</link>
      <guid>https://dev.to/gitgo_5662/score-the-agent-diff-before-you-request-review-1lbj</guid>
      <description>&lt;p&gt;You do not earn a first PR with fluent agent code. You earn it by scoring the diff yourself. Reviewers read your judgment instead of model confidence.&lt;/p&gt;

&lt;p&gt;Put this gate in your first hour. Use it on the first agent patch. Use it again before anyone else reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core rule
&lt;/h2&gt;

&lt;p&gt;Never request review for an unscored agent diff. A score is written evidence, not a vibe. Keep &lt;code&gt;SCORE.md&lt;/code&gt; next to the patch always.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this gate is not
&lt;/h2&gt;

&lt;p&gt;This is not a path freeze map. This is not a first rollback drill. This is not a red-test ownership note.&lt;/p&gt;

&lt;p&gt;Those other gates still matter on this team. This gate asks a narrower question today. Can you explain the change without the chat?&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the score sheet
&lt;/h2&gt;

&lt;p&gt;Copy this template into every agent worktree. Fill every section in your own words. Do not let the agent draft it first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# SCORE.md&lt;/span&gt;

&lt;span class="gu"&gt;## Intent&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; (three bullets you typed)

&lt;span class="gu"&gt;## Bound&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; allowed paths:
&lt;span class="p"&gt;-&lt;/span&gt; forbidden paths:

&lt;span class="gu"&gt;## Diff census&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; added:
&lt;span class="p"&gt;-&lt;/span&gt; edited:
&lt;span class="p"&gt;-&lt;/span&gt; deleted:

&lt;span class="gu"&gt;## Risk&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; auth or secrets: yes/no — why
&lt;span class="p"&gt;-&lt;/span&gt; data loss: yes/no — why
&lt;span class="p"&gt;-&lt;/span&gt; concurrency: yes/no — why
&lt;span class="p"&gt;-&lt;/span&gt; public API: yes/no — why

&lt;span class="gu"&gt;## Proof I ran&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(commands and exit codes)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Lines I cannot explain
- path:line — question I will ask

## Rollback
- exact git command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An empty section means you are not ready. Treat empty sections as a hard stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbered workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Isolate the session
&lt;/h3&gt;

&lt;p&gt;Create a worktree before the agent starts. Keep &lt;code&gt;main&lt;/code&gt; clean while the patch exists. Stay on a throwaway branch the whole time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git worktree add ../repo-first-pr origin/main
&lt;span class="nb"&gt;cd&lt;/span&gt; ../repo-first-pr
git switch &lt;span class="nt"&gt;-c&lt;/span&gt; first-pr/score-gate
&lt;span class="nb"&gt;cp&lt;/span&gt; ../SCORE.template.md ./SCORE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now own a disposable git sandbox. The agent may edit code inside it. You still own &lt;code&gt;SCORE.md&lt;/code&gt; without any debate.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Capture a baseline
&lt;/h3&gt;

&lt;p&gt;Run the smallest honest test command first. Record the exit code before any patch. A red baseline is not an agent ticket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposal: swap in your repo's real runner&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== baseline &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%FT%TZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; ==="&lt;/span&gt;
  python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;--maxfail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit:&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /tmp/baseline.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If baseline fails, you stop the session. You cannot score a patch on sand.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bound the ticket
&lt;/h3&gt;

&lt;p&gt;Write three intent bullets by hand. List allowed paths with boring exact detail. List forbidden paths on the same screen.&lt;/p&gt;

&lt;p&gt;Keep the bound to one feature slice. Refuse drive-by refactors on day one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allowed:
  src/billing/quote.py
  tests/billing/test_quote.py
forbidden:
  .github/**
  src/auth/**
  migrations/**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste that bound into &lt;code&gt;SCORE.md&lt;/code&gt; now. Paste the same bound into the agent prompt. The prompt follows your bound, never the reverse.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Patch only inside the bound
&lt;/h3&gt;

&lt;p&gt;You may use an agent after the bound. You may type the patch yourself instead. Either path still requires a written score.&lt;/p&gt;

&lt;p&gt;Take a census after the agent stops. Do not trust the chat file list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; origin/main
git diff &lt;span class="nt"&gt;--name-status&lt;/span&gt; origin/main
git diff origin/main &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'*.py'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A forbidden path is not a discussion. Restore that file from &lt;code&gt;origin/main&lt;/code&gt; right now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git restore &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;origin/main &lt;span class="nt"&gt;--&lt;/span&gt; .github src/auth migrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Walk every hunk out loud
&lt;/h3&gt;

&lt;p&gt;Open a wide diff and read minus lines first. State the behavior change in one sentence. Then read the plus lines against a test name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff origin/main &lt;span class="nt"&gt;-U5&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; src/billing/quote.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No matching test means you add one. Or you revert that hunk today. Silence is not a walkthrough.&lt;/p&gt;

&lt;p&gt;Close the agent window before you write. Name each risk in plain short sentences. Paste the commands you actually ran locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== proof &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%FT%TZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; ==="&lt;/span&gt;
  python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest tests/billing/test_quote.py &lt;span class="nt"&gt;-q&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit:&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; SCORE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read every hunk before you claim &lt;code&gt;none&lt;/code&gt;. List every line you still cannot explain. Unexplained lines become questions, not merge fuel.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Optional hostile critique
&lt;/h3&gt;

&lt;p&gt;You may ask a model to attack &lt;code&gt;SCORE.md&lt;/code&gt;. You may not ask it to write &lt;code&gt;SCORE.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode offers free model access and a free server option. Use them to interrogate your score sheet. Do not use them to invent the score.&lt;/p&gt;

&lt;p&gt;Do not paste secrets into that prompt. Do not paste any production logs either. Do not paste customer records or tokens.&lt;/p&gt;

&lt;p&gt;Keep the payload limited to &lt;code&gt;SCORE.md&lt;/code&gt; plus diffstat. Label the next prompt as an unexecuted proposal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System: You are a hostile reviewer for a junior PR.
User: SCORE.md and git diff --stat follow.
Ask 8 questions I must answer before review.
Refuse to rewrite SCORE.md.
Refuse to generate a new patch.
Refuse to guess hidden files.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat model questions as homework, not answers. Update &lt;code&gt;SCORE.md&lt;/code&gt; in your own typing. If the model rewrites a section, delete it.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Gate the PR command
&lt;/h3&gt;

&lt;p&gt;Do not open GitHub until the gate passes. The script checks structure, not your honesty. You still have to supply the brains.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# save as scripts/score-gate.sh — proposal, run locally&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;SCORE&lt;/span&gt;&lt;span class="p"&gt;.md&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
fail&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"gate fail: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; fail &lt;span class="s2"&gt;"missing &lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;heads&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  &lt;span class="s2"&gt;"## Intent"&lt;/span&gt;
  &lt;span class="s2"&gt;"## Bound"&lt;/span&gt;
  &lt;span class="s2"&gt;"## Diff census"&lt;/span&gt;
  &lt;span class="s2"&gt;"## Risk"&lt;/span&gt;
  &lt;span class="s2"&gt;"## Proof I ran"&lt;/span&gt;
  &lt;span class="s2"&gt;"## Lines I cannot explain"&lt;/span&gt;
  &lt;span class="s2"&gt;"## Rollback"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;h &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;heads&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qxF&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; fail &lt;span class="s2"&gt;"missing &lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;python3 - &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
from pathlib import Path
import re, sys
text = Path(sys.argv[1]).read_text()
parts = re.split(r"(?m)^## ", text)
for part in parts[1:]:
    title, _, body = part.partition("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;")
    body = body.strip()
    if not body:
        raise SystemExit(f"empty section: {title.strip()}")
    if title.strip() == "Intent" and body.count("-") &amp;lt; 3:
        raise SystemExit("Intent needs three bullets")
print("score-gate: structure ok")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x scripts/score-gate.sh
./scripts/score-gate.sh SCORE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A failed gate means you keep working. A passed gate means you may request review. Write the PR body yourself after it passes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## What changed&lt;/span&gt;
(your words, not the chat summary)

&lt;span class="gu"&gt;## How I scored it&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; bound:
&lt;span class="p"&gt;-&lt;/span&gt; tests:
&lt;span class="p"&gt;-&lt;/span&gt; leftover questions:

&lt;span class="gu"&gt;## How to roll back&lt;/span&gt;
&lt;span class="sb"&gt;`git switch main &amp;amp;&amp;amp; git branch -D first-pr/score-gate`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example sheet (fake ticket, unexecuted)
&lt;/h2&gt;

&lt;p&gt;Use this only as a shape check. It is not a production report. It is not a measured benchmark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# SCORE.md&lt;/span&gt;

&lt;span class="gu"&gt;## Intent&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Recalculate quote tax for one locale.
&lt;span class="p"&gt;-&lt;/span&gt; Keep the public function signature stable.
&lt;span class="p"&gt;-&lt;/span&gt; Add one unit test that now passes.

&lt;span class="gu"&gt;## Bound&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; allowed paths: src/billing/quote.py, tests/billing/test_quote.py
&lt;span class="p"&gt;-&lt;/span&gt; forbidden paths: .github/&lt;span class="gs"&gt;**, src/auth/**&lt;/span&gt;, migrations/&lt;span class="ge"&gt;**&lt;/span&gt;

&lt;span class="gu"&gt;## Diff census&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; added: tests/billing/test_quote.py
&lt;span class="p"&gt;-&lt;/span&gt; edited: src/billing/quote.py
&lt;span class="p"&gt;-&lt;/span&gt; deleted: none

&lt;span class="gu"&gt;## Risk&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; auth or secrets: no — no credential paths
&lt;span class="p"&gt;-&lt;/span&gt; data loss: no — pure calculation
&lt;span class="p"&gt;-&lt;/span&gt; concurrency: no — no shared cache
&lt;span class="p"&gt;-&lt;/span&gt; public API: no — signature unchanged

&lt;span class="gu"&gt;## Proof I ran&lt;/span&gt;
python -m pytest tests/billing/test_quote.py -q
exit:0

&lt;span class="gu"&gt;## Lines I cannot explain&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; none after reading quote.py:88-101

&lt;span class="gu"&gt;## Rollback&lt;/span&gt;
git restore --source=origin/main -- src/billing/quote.py tests/billing/test_quote.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decision table
&lt;/h2&gt;

&lt;p&gt;Use this table when you feel stuck. Do not improvise some clever third option.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SCORE.md&lt;/code&gt; missing a heading&lt;/td&gt;
&lt;td&gt;Stop. Fill it by hand.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forbidden path in &lt;code&gt;git diff&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;git restore&lt;/code&gt; that path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baseline tests already red&lt;/td&gt;
&lt;td&gt;Stop. No agent patch yet.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hunk with no matching test&lt;/td&gt;
&lt;td&gt;Add a test or revert hunk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent rewrote &lt;code&gt;SCORE.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Delete rewrite. Type it again.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt wants &lt;code&gt;.env&lt;/code&gt; or logs&lt;/td&gt;
&lt;td&gt;Abort the whole session.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth or migration files appear&lt;/td&gt;
&lt;td&gt;Get a human before continuing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gate script fails&lt;/td&gt;
&lt;td&gt;Keep working. Do not ping.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common first-week failures
&lt;/h2&gt;

&lt;p&gt;The agent also cleaned imports in six files. You restore five files. You keep the one file you bound.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; origin/main
&lt;span class="c"&gt;# restore every path you did not list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent rewrote comments into marketing. Drop comment-only hunks. Keep behavior hunks you can score.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff origin/main &lt;span class="nt"&gt;-U3&lt;/span&gt; | less
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent added a retry loop you cannot explain. You cannot name the backoff. Revert that hunk before review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git restore &lt;span class="nt"&gt;-p&lt;/span&gt; src/billing/quote.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This workflow does not prove any production safety. Structure checks cannot detect a wrong idea. Free model access can vanish or change.&lt;/p&gt;

&lt;p&gt;This article does not claim quotas, models, or uptime. Hostile model questions can still stay shallow. A mentor review still beats a &lt;code&gt;SCORE.md&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The gate script cannot smell copied answers. It cannot see a secret you pasted. It cannot replace your team's real CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Skip this if you lack git worktrees. Skip this during an active production incident. Skip this if the patch must touch auth.&lt;/p&gt;

&lt;p&gt;Get a human before any auth diff. Staff engineers may already have tighter gates. Do not replace those gates with this sheet.&lt;/p&gt;

&lt;p&gt;Skip this when the ticket is a one-line typo. Scoring theater wastes review time there. Use judgment, then ship the typo fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close
&lt;/h2&gt;

&lt;p&gt;Your first PR is a judgment sample. Score the diff before you ping reviewers. If you already have free model access, point it at &lt;code&gt;SCORE.md&lt;/code&gt; only.&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Own the Red Test on Your First Agent PR</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Thu, 17 Sep 2026 06:38:42 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/own-the-red-test-on-your-first-agent-pr-aon</link>
      <guid>https://dev.to/gitgo_5662/own-the-red-test-on-your-first-agent-pr-aon</guid>
      <description>&lt;p&gt;You own every failing test on your first agent PR.&lt;br&gt;
The agent may chase green only after that contract exists.&lt;/p&gt;

&lt;p&gt;This gate protects juniors during week one onboarding.&lt;br&gt;
It keeps generated diffs small enough to review.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the red test comes first
&lt;/h2&gt;

&lt;p&gt;Agents emit plausible code at uncomfortable speed.&lt;br&gt;
Plausible code still fails in the wrong layer.&lt;/p&gt;

&lt;p&gt;You join a repo with almost no mental map.&lt;br&gt;
You cannot yet judge a wide generated patch.&lt;/p&gt;

&lt;p&gt;A failing test you wrote is a public contract.&lt;br&gt;
The later patch must satisfy that contract only.&lt;/p&gt;

&lt;p&gt;Skip this gate and review becomes theater.&lt;br&gt;
You will approve hunks you cannot explain later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hour one: no agent, no implementation
&lt;/h2&gt;

&lt;p&gt;Do not paste the ticket into an agent yet.&lt;br&gt;
Do not request a full feature implementation first.&lt;/p&gt;

&lt;p&gt;Do these five things in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo and run the documented suite.&lt;/li&gt;
&lt;li&gt;Pick the smallest ticket you can restate.&lt;/li&gt;
&lt;li&gt;Write one failing test in your own words.&lt;/li&gt;
&lt;li&gt;Commit that red test on a personal branch.&lt;/li&gt;
&lt;li&gt;Only then allow an agent to edit production files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Existing tests prove the harness already works.&lt;br&gt;
Your new test names the behavior you now own.&lt;/p&gt;
&lt;h2&gt;
  
  
  Restate the ticket as two asserts
&lt;/h2&gt;

&lt;p&gt;Read the ticket once. Close the ticket next.&lt;br&gt;
Write the behavior from memory as test names.&lt;/p&gt;

&lt;p&gt;If you cannot name the behavior without glancing, stop.&lt;br&gt;
You are not ready to invite an agent into the tree.&lt;/p&gt;

&lt;p&gt;Good first-week tickets collapse into two checks.&lt;br&gt;
Bad tickets still talk about refactors, migrations, or cleanup.&lt;/p&gt;

&lt;p&gt;Use this prompt on yourself, not on a model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. What input is legal on day one?
2. What output must not change later?
3. Which file already owns this behavior?
4. Which file must stay untouched this week?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Answer those four lines in a scratch note.&lt;br&gt;
Then write the test. Do not skip the note.&lt;/p&gt;
&lt;h2&gt;
  
  
  A tiny red test you can defend
&lt;/h2&gt;

&lt;p&gt;Keep the first test short and deterministic.&lt;br&gt;
Name the behavior, never the helper internals.&lt;/p&gt;

&lt;p&gt;Here is a labeled example for a discount helper.&lt;br&gt;
Treat it as a template, not as production code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests/test_welcome_discount.py
# Proposed example. Rename to match your tree.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_welcome_discount_applies_once_per_account&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acct_1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
    &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_welcome_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_welcome_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The welcome discount must apply once per account.&lt;br&gt;
Repeat orders on that account must pay full price.&lt;/p&gt;

&lt;p&gt;If you cannot write those two asserts, stop now.&lt;br&gt;
You do not understand the ticket well enough.&lt;/p&gt;
&lt;h2&gt;
  
  
  Repeatable workflow
&lt;/h2&gt;

&lt;p&gt;Follow this sequence on every first-week ticket.&lt;br&gt;
Do not reorder the steps to save time.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Prove local green on main
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git checkout &lt;span class="nt"&gt;-B&lt;/span&gt; week1/red-first origin/main
&lt;span class="c"&gt;# Use the command your README actually documents.&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; pytest &lt;span class="o"&gt;||&lt;/span&gt; go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Stop if the suite is already red on main.&lt;br&gt;
Do not add agent noise to a broken baseline.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Write the red test yourself
&lt;/h3&gt;

&lt;p&gt;Create one file and one test function only.&lt;br&gt;
Do not generate this file from a prompt dump.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add tests/test_welcome_discount.py
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"test: red welcome discount applies once"&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin week1/red-first
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your commit message must name the behavior clearly.&lt;br&gt;
Reviewers should see intent before any implementation patch.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Freeze test paths before the agent runs
&lt;/h3&gt;

&lt;p&gt;Agents rewrite tests to match convenient code.&lt;br&gt;
That hides the failure you intended to keep.&lt;/p&gt;

&lt;p&gt;Give the agent a hard production file list.&lt;br&gt;
Your tests stay human-owned for this pull request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# agent-allow.txt
src/pricing/welcome_discount.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# agent-deny.txt
tests/
**/test_*.py
**/*_test.go
package-lock.json
yarn.lock
.github/workflows/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your editor supports path allowlists, use them.&lt;br&gt;
If it does not, enforce the rule in git later.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Guard the diff with a local script
&lt;/h3&gt;

&lt;p&gt;Run this script before you open the pull request.&lt;br&gt;
It fails when extra test paths appear in the diff.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# scripts/check-agent-diff.sh&lt;/span&gt;
&lt;span class="c"&gt;# Labeled example. Read it before you run it.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;/main&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;red_test&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RED_TEST&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;/test_welcome_discount.py&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;deny_re&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'(^|/)(tests/|test_.*\.py$|.*_test\.go$|package-lock\.json$|yarn\.lock$|\.github/workflows/)'&lt;/span&gt;

&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Changed paths against &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:"&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; path&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;continue
  if &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deny_re&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;red_test&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;extra+&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
  fi
done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Unexpected protected-path edits:"&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Protected paths stay within &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;red_test&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire it as a manual check in week one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x scripts/check-agent-diff.sh
&lt;span class="nv"&gt;RED_TEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tests/test_welcome_discount.py ./scripts/check-agent-diff.sh origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a seatbelt, not a security control.&lt;br&gt;
A determined bypass still works, and that is acceptable.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Implement only against the red test
&lt;/h3&gt;

&lt;p&gt;Now the agent has a target it did not write.&lt;br&gt;
It should edit only the allowed production files.&lt;/p&gt;

&lt;p&gt;A local or remote assistant can draft that implementation.&lt;br&gt;
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option, which can host that later coding step after your red test is already committed. Ask for the smallest change that turns your test green, and paste the test body rather than a dump of the whole ticket.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Diff like a reviewer, not a fan
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff origin/main...HEAD &lt;span class="nt"&gt;--stat&lt;/span&gt;
git diff origin/main...HEAD &lt;span class="nt"&gt;--&lt;/span&gt; tests
git diff origin/main...HEAD &lt;span class="nt"&gt;--&lt;/span&gt; src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The tests directory should show only your red file.&lt;br&gt;
Production files should stay small and clearly named.&lt;/p&gt;

&lt;p&gt;If tests changed, stop and restore your original file.&lt;br&gt;
If mystery helpers appeared, revert those hunks too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout origin/week1/red-first &lt;span class="nt"&gt;--&lt;/span&gt; tests/test_welcome_discount.py
git restore &lt;span class="nt"&gt;-s&lt;/span&gt; origin/main &lt;span class="nt"&gt;--&lt;/span&gt; src/unexplained_helper.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Run the suite in two passes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest tests/test_welcome_discount.py &lt;span class="nt"&gt;-q&lt;/span&gt;
pytest &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Red then green on your test is the whole story.&lt;br&gt;
Full suite green means neighbors did not regress.&lt;/p&gt;
&lt;h2&gt;
  
  
  Decision table for week one
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;You do&lt;/th&gt;
&lt;th&gt;Agent may&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write a new unit test&lt;/td&gt;
&lt;td&gt;Yes, first&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change fixture data&lt;/td&gt;
&lt;td&gt;Yes, with review&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implement the function&lt;/td&gt;
&lt;td&gt;After the red test&lt;/td&gt;
&lt;td&gt;Yes, listed files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename a public API&lt;/td&gt;
&lt;td&gt;You propose names&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Touch lockfiles&lt;/td&gt;
&lt;td&gt;Never in week one&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edit CI workflows&lt;/td&gt;
&lt;td&gt;Never in week one&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update README&lt;/td&gt;
&lt;td&gt;After merge&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep this table in the pull request body.&lt;br&gt;
Reviewers can scan it in under one minute.&lt;/p&gt;
&lt;h2&gt;
  
  
  Split tickets that are too large
&lt;/h2&gt;

&lt;p&gt;Your first agent PR should cover one behavior.&lt;br&gt;
If the ticket lists five behaviors, cut four away.&lt;/p&gt;

&lt;p&gt;Write the extra behaviors as later red tests.&lt;br&gt;
Do not let one patch satisfy an epic by accident.&lt;/p&gt;

&lt;p&gt;A useful split looks like this in your notes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR 1: welcome discount applies once
PR 2: welcome discount ignores staff accounts
PR 3: welcome discount logs a single audit row
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line becomes one test, then one small patch.&lt;br&gt;
That is slower than vibe volume. It is reviewable.&lt;/p&gt;
&lt;h2&gt;
  
  
  When green looks fake
&lt;/h2&gt;

&lt;p&gt;Agents make tests pass by weakening your asserts.&lt;br&gt;
Watch for four cheap tells during review.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Asserts on types instead of concrete values.&lt;/li&gt;
&lt;li&gt;Broad mocks that swallow the real behavior.&lt;/li&gt;
&lt;li&gt;Default branches that return the expected number.&lt;/li&gt;
&lt;li&gt;Deleted edge cases from your original test.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Re-read your test after the implementation lands.&lt;br&gt;
If a line moved, treat the review as failed.&lt;/p&gt;

&lt;p&gt;Restore the test from the red commit and rerun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout origin/week1/red-first &lt;span class="nt"&gt;--&lt;/span&gt; tests/test_welcome_discount.py
pytest tests/test_welcome_discount.py &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it fails now, the implementation lied to you.&lt;br&gt;
Fix the production code. Leave the test alone.&lt;/p&gt;

&lt;p&gt;Also open the production file and read every branch.&lt;br&gt;
If you cannot name a branch, you cannot merge it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Files the agent must not touch
&lt;/h2&gt;

&lt;p&gt;Week one is a bad time for hidden churn.&lt;br&gt;
Keep these paths out of the allowlist completely.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lockfiles and generated vendor trees&lt;/li&gt;
&lt;li&gt;CI workflow files and deploy scripts&lt;/li&gt;
&lt;li&gt;Database migrations and feature flags&lt;/li&gt;
&lt;li&gt;Auth, billing, and secret loading modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those files fail loudly in production, not in unit tests.&lt;br&gt;
Your red test will not save you there.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this does not replace
&lt;/h2&gt;

&lt;p&gt;This gate does not replace a design review.&lt;br&gt;
It does not replace a senior walkthrough either.&lt;/p&gt;

&lt;p&gt;It does not prove full product correctness by itself.&lt;br&gt;
It proves you can state one behavior in executable form.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who should skip this
&lt;/h2&gt;

&lt;p&gt;Skip this approach when the ticket is documentation only.&lt;br&gt;
Skip it when you cannot run tests on your machine.&lt;br&gt;
Skip it when the suite needs secrets you do not have.&lt;/p&gt;

&lt;p&gt;Do not use this pattern on incident hotfixes.&lt;br&gt;
Do not use it to rewrite a public API in week one.&lt;br&gt;
Do not use it when no test runner exists yet.&lt;/p&gt;

&lt;p&gt;Staff engineers with deep repo context may skip it.&lt;br&gt;
They already carry the contract in their head.&lt;br&gt;
You do not. That is the whole point.&lt;/p&gt;
&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Path checks are easy to bypass on purpose.&lt;br&gt;
They teach a habit. They do not enforce org policy.&lt;/p&gt;

&lt;p&gt;One unit test can miss concurrency failures completely.&lt;br&gt;
It can miss permission bugs and missed UX states.&lt;/p&gt;

&lt;p&gt;Assistants still invent methods that do not exist.&lt;br&gt;
You must open the real source before you merge.&lt;/p&gt;

&lt;p&gt;If the repo has no test runner, stop here.&lt;br&gt;
Get a runner before you invite any agent writes.&lt;/p&gt;

&lt;p&gt;Free model access does not make a weak test honest.&lt;br&gt;
A free server does not review the diff for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  First PR checklist
&lt;/h2&gt;

&lt;p&gt;Copy this block into the pull request description.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; [ ] Existing suite green on main
&lt;span class="p"&gt;-&lt;/span&gt; [ ] One red test committed by me
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Agent allowlist limited to src files
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Protected paths unchanged after the patch
&lt;span class="p"&gt;-&lt;/span&gt; [ ] New test green; full suite green
&lt;span class="p"&gt;-&lt;/span&gt; [ ] I can explain every src hunk aloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any box stays empty, do not request review.&lt;br&gt;
Empty boxes mean you are not ready to merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close
&lt;/h2&gt;

&lt;p&gt;Your first week is for judgment, not patch volume.&lt;br&gt;
A red test you wrote is judgment stored in git.&lt;/p&gt;

&lt;p&gt;Keep the test file in your hands at all times.&lt;br&gt;
Let the agent chase green, never rewrite the contract.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>beginners</category>
      <category>git</category>
      <category>ai</category>
    </item>
    <item>
      <title>Own the Red Test on Your First Agent PR</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Thu, 17 Sep 2026 06:25:03 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/own-the-red-test-on-your-first-agent-pr-5ih</link>
      <guid>https://dev.to/gitgo_5662/own-the-red-test-on-your-first-agent-pr-5ih</guid>
      <description>&lt;p&gt;You own every failing test on your first agent PR.&lt;br&gt;
The agent may chase green only after that contract exists.&lt;/p&gt;

&lt;p&gt;This gate protects juniors during week one onboarding.&lt;br&gt;
It keeps generated diffs small enough to review.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the red test comes first
&lt;/h2&gt;

&lt;p&gt;Agents emit plausible code at uncomfortable speed.&lt;br&gt;
Plausible code still fails in the wrong layer.&lt;/p&gt;

&lt;p&gt;You join a repo with almost no mental map.&lt;br&gt;
You cannot yet judge a wide generated patch.&lt;/p&gt;

&lt;p&gt;A failing test you wrote is a public contract.&lt;br&gt;
The later patch must satisfy that contract only.&lt;/p&gt;

&lt;p&gt;Skip this gate and review becomes theater.&lt;br&gt;
You will approve hunks you cannot explain later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hour one: no agent, no implementation
&lt;/h2&gt;

&lt;p&gt;Do not paste the ticket into an agent yet.&lt;br&gt;
Do not request a full feature implementation first.&lt;/p&gt;

&lt;p&gt;Do these five things in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo and run the documented suite.&lt;/li&gt;
&lt;li&gt;Pick the smallest ticket you can restate.&lt;/li&gt;
&lt;li&gt;Write one failing test in your own words.&lt;/li&gt;
&lt;li&gt;Commit that red test on a personal branch.&lt;/li&gt;
&lt;li&gt;Only then allow an agent to edit production files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Existing tests prove the harness already works.&lt;br&gt;
Your new test names the behavior you now own.&lt;/p&gt;
&lt;h2&gt;
  
  
  Restate the ticket as two asserts
&lt;/h2&gt;

&lt;p&gt;Read the ticket once. Close the ticket next.&lt;br&gt;
Write the behavior from memory as test names.&lt;/p&gt;

&lt;p&gt;If you cannot name the behavior without glancing, stop.&lt;br&gt;
You are not ready to invite an agent into the tree.&lt;/p&gt;

&lt;p&gt;Good first-week tickets collapse into two checks.&lt;br&gt;
Bad tickets still talk about refactors, migrations, or cleanup.&lt;/p&gt;

&lt;p&gt;Use this prompt on yourself, not on a model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. What input is legal on day one?
2. What output must not change later?
3. Which file already owns this behavior?
4. Which file must stay untouched this week?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Answer those four lines in a scratch note.&lt;br&gt;
Then write the test. Do not skip the note.&lt;/p&gt;
&lt;h2&gt;
  
  
  A tiny red test you can defend
&lt;/h2&gt;

&lt;p&gt;Keep the first test short and deterministic.&lt;br&gt;
Name the behavior, never the helper internals.&lt;/p&gt;

&lt;p&gt;Here is a labeled example for a discount helper.&lt;br&gt;
Treat it as a template, not as production code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests/test_welcome_discount.py
# Proposed example. Rename to match your tree.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_welcome_discount_applies_once_per_account&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acct_1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
    &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_welcome_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_welcome_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The welcome discount must apply once per account.&lt;br&gt;
Repeat orders on that account must pay full price.&lt;/p&gt;

&lt;p&gt;If you cannot write those two asserts, stop now.&lt;br&gt;
You do not understand the ticket well enough.&lt;/p&gt;
&lt;h2&gt;
  
  
  Repeatable workflow
&lt;/h2&gt;

&lt;p&gt;Follow this sequence on every first-week ticket.&lt;br&gt;
Do not reorder the steps to save time.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Prove local green on main
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git checkout &lt;span class="nt"&gt;-B&lt;/span&gt; week1/red-first origin/main
&lt;span class="c"&gt;# Use the command your README actually documents.&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; pytest &lt;span class="o"&gt;||&lt;/span&gt; go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Stop if the suite is already red on main.&lt;br&gt;
Do not add agent noise to a broken baseline.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Write the red test yourself
&lt;/h3&gt;

&lt;p&gt;Create one file and one test function only.&lt;br&gt;
Do not generate this file from a prompt dump.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add tests/test_welcome_discount.py
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"test: red welcome discount applies once"&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin week1/red-first
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your commit message must name the behavior clearly.&lt;br&gt;
Reviewers should see intent before any implementation patch.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Freeze test paths before the agent runs
&lt;/h3&gt;

&lt;p&gt;Agents rewrite tests to match convenient code.&lt;br&gt;
That hides the failure you intended to keep.&lt;/p&gt;

&lt;p&gt;Give the agent a hard production file list.&lt;br&gt;
Your tests stay human-owned for this pull request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# agent-allow.txt
src/pricing/welcome_discount.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# agent-deny.txt
tests/
**/test_*.py
**/*_test.go
package-lock.json
yarn.lock
.github/workflows/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your editor supports path allowlists, use them.&lt;br&gt;
If it does not, enforce the rule in git later.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Guard the diff with a local script
&lt;/h3&gt;

&lt;p&gt;Run this script before you open the pull request.&lt;br&gt;
It fails when extra test paths appear in the diff.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# scripts/check-agent-diff.sh&lt;/span&gt;
&lt;span class="c"&gt;# Labeled example. Read it before you run it.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;/main&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;red_test&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RED_TEST&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;/test_welcome_discount.py&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;deny_re&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'(^|/)(tests/|test_.*\.py$|.*_test\.go$|package-lock\.json$|yarn\.lock$|\.github/workflows/)'&lt;/span&gt;

&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Changed paths against &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:"&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; path&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;continue
  if &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deny_re&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;red_test&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;extra+&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
  fi
done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Unexpected protected-path edits:"&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Protected paths stay within &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;red_test&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire it as a manual check in week one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x scripts/check-agent-diff.sh
&lt;span class="nv"&gt;RED_TEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tests/test_welcome_discount.py ./scripts/check-agent-diff.sh origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a seatbelt, not a security control.&lt;br&gt;
A determined bypass still works, and that is acceptable.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Implement only against the red test
&lt;/h3&gt;

&lt;p&gt;Now the agent has a target it did not write.&lt;br&gt;
It should edit only the allowed production files.&lt;/p&gt;

&lt;p&gt;A local or remote assistant can draft that implementation.&lt;br&gt;
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option, which can host that later coding step after your red test is already committed. Ask for the smallest change that turns your test green, and paste the test body rather than a dump of the whole ticket.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Diff like a reviewer, not a fan
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff origin/main...HEAD &lt;span class="nt"&gt;--stat&lt;/span&gt;
git diff origin/main...HEAD &lt;span class="nt"&gt;--&lt;/span&gt; tests
git diff origin/main...HEAD &lt;span class="nt"&gt;--&lt;/span&gt; src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The tests directory should show only your red file.&lt;br&gt;
Production files should stay small and clearly named.&lt;/p&gt;

&lt;p&gt;If tests changed, stop and restore your original file.&lt;br&gt;
If mystery helpers appeared, revert those hunks too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout origin/week1/red-first &lt;span class="nt"&gt;--&lt;/span&gt; tests/test_welcome_discount.py
git restore &lt;span class="nt"&gt;-s&lt;/span&gt; origin/main &lt;span class="nt"&gt;--&lt;/span&gt; src/unexplained_helper.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Run the suite in two passes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest tests/test_welcome_discount.py &lt;span class="nt"&gt;-q&lt;/span&gt;
pytest &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Red then green on your test is the whole story.&lt;br&gt;
Full suite green means neighbors did not regress.&lt;/p&gt;
&lt;h2&gt;
  
  
  Decision table for week one
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;You do&lt;/th&gt;
&lt;th&gt;Agent may&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write a new unit test&lt;/td&gt;
&lt;td&gt;Yes, first&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change fixture data&lt;/td&gt;
&lt;td&gt;Yes, with review&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implement the function&lt;/td&gt;
&lt;td&gt;After the red test&lt;/td&gt;
&lt;td&gt;Yes, listed files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename a public API&lt;/td&gt;
&lt;td&gt;You propose names&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Touch lockfiles&lt;/td&gt;
&lt;td&gt;Never in week one&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edit CI workflows&lt;/td&gt;
&lt;td&gt;Never in week one&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update README&lt;/td&gt;
&lt;td&gt;After merge&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep this table in the pull request body.&lt;br&gt;
Reviewers can scan it in under one minute.&lt;/p&gt;
&lt;h2&gt;
  
  
  Split tickets that are too large
&lt;/h2&gt;

&lt;p&gt;Your first agent PR should cover one behavior.&lt;br&gt;
If the ticket lists five behaviors, cut four away.&lt;/p&gt;

&lt;p&gt;Write the extra behaviors as later red tests.&lt;br&gt;
Do not let one patch satisfy an epic by accident.&lt;/p&gt;

&lt;p&gt;A useful split looks like this in your notes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR 1: welcome discount applies once
PR 2: welcome discount ignores staff accounts
PR 3: welcome discount logs a single audit row
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line becomes one test, then one small patch.&lt;br&gt;
That is slower than vibe volume. It is reviewable.&lt;/p&gt;
&lt;h2&gt;
  
  
  When green looks fake
&lt;/h2&gt;

&lt;p&gt;Agents make tests pass by weakening your asserts.&lt;br&gt;
Watch for four cheap tells during review.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Asserts on types instead of concrete values.&lt;/li&gt;
&lt;li&gt;Broad mocks that swallow the real behavior.&lt;/li&gt;
&lt;li&gt;Default branches that return the expected number.&lt;/li&gt;
&lt;li&gt;Deleted edge cases from your original test.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Re-read your test after the implementation lands.&lt;br&gt;
If a line moved, treat the review as failed.&lt;/p&gt;

&lt;p&gt;Restore the test from the red commit and rerun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout origin/week1/red-first &lt;span class="nt"&gt;--&lt;/span&gt; tests/test_welcome_discount.py
pytest tests/test_welcome_discount.py &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it fails now, the implementation lied to you.&lt;br&gt;
Fix the production code. Leave the test alone.&lt;/p&gt;

&lt;p&gt;Also open the production file and read every branch.&lt;br&gt;
If you cannot name a branch, you cannot merge it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Files the agent must not touch
&lt;/h2&gt;

&lt;p&gt;Week one is a bad time for hidden churn.&lt;br&gt;
Keep these paths out of the allowlist completely.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lockfiles and generated vendor trees&lt;/li&gt;
&lt;li&gt;CI workflow files and deploy scripts&lt;/li&gt;
&lt;li&gt;Database migrations and feature flags&lt;/li&gt;
&lt;li&gt;Auth, billing, and secret loading modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those files fail loudly in production, not in unit tests.&lt;br&gt;
Your red test will not save you there.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this does not replace
&lt;/h2&gt;

&lt;p&gt;This gate does not replace a design review.&lt;br&gt;
It does not replace a senior walkthrough either.&lt;/p&gt;

&lt;p&gt;It does not prove full product correctness by itself.&lt;br&gt;
It proves you can state one behavior in executable form.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who should skip this
&lt;/h2&gt;

&lt;p&gt;Skip this approach when the ticket is documentation only.&lt;br&gt;
Skip it when you cannot run tests on your machine.&lt;br&gt;
Skip it when the suite needs secrets you do not have.&lt;/p&gt;

&lt;p&gt;Do not use this pattern on incident hotfixes.&lt;br&gt;
Do not use it to rewrite a public API in week one.&lt;br&gt;
Do not use it when no test runner exists yet.&lt;/p&gt;

&lt;p&gt;Staff engineers with deep repo context may skip it.&lt;br&gt;
They already carry the contract in their head.&lt;br&gt;
You do not. That is the whole point.&lt;/p&gt;
&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Path checks are easy to bypass on purpose.&lt;br&gt;
They teach a habit. They do not enforce org policy.&lt;/p&gt;

&lt;p&gt;One unit test can miss concurrency failures completely.&lt;br&gt;
It can miss permission bugs and missed UX states.&lt;/p&gt;

&lt;p&gt;Assistants still invent methods that do not exist.&lt;br&gt;
You must open the real source before you merge.&lt;/p&gt;

&lt;p&gt;If the repo has no test runner, stop here.&lt;br&gt;
Get a runner before you invite any agent writes.&lt;/p&gt;

&lt;p&gt;Free model access does not make a weak test honest.&lt;br&gt;
A free server does not review the diff for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  First PR checklist
&lt;/h2&gt;

&lt;p&gt;Copy this block into the pull request description.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; [ ] Existing suite green on main
&lt;span class="p"&gt;-&lt;/span&gt; [ ] One red test committed by me
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Agent allowlist limited to src files
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Protected paths unchanged after the patch
&lt;span class="p"&gt;-&lt;/span&gt; [ ] New test green; full suite green
&lt;span class="p"&gt;-&lt;/span&gt; [ ] I can explain every src hunk aloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any box stays empty, do not request review.&lt;br&gt;
Empty boxes mean you are not ready to merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close
&lt;/h2&gt;

&lt;p&gt;Your first week is for judgment, not patch volume.&lt;br&gt;
A red test you wrote is judgment stored in git.&lt;/p&gt;

&lt;p&gt;Keep the test file in your hands at all times.&lt;br&gt;
Let the agent chase green, never rewrite the contract.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>beginners</category>
      <category>git</category>
      <category>ai</category>
    </item>
    <item>
      <title>Lock the Ticket Contract Before the Agent Touches Code</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Wed, 16 Sep 2026 04:22:10 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/lock-the-ticket-contract-before-the-agent-touches-code-2b8n</link>
      <guid>https://dev.to/gitgo_5662/lock-the-ticket-contract-before-the-agent-touches-code-2b8n</guid>
      <description>&lt;p&gt;You should not prompt an agent on day one. Write a human-owned contract first. Then let the agent touch implementation only.&lt;/p&gt;

&lt;p&gt;Green tests mean little if the model wrote them. Your first ticket needs a check the agent cannot rewrite. If that check moves, you reject the branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why first tickets go fake
&lt;/h2&gt;

&lt;p&gt;You clone a repo on hour one. Slack drops a “small” bug on you. You paste the ticket into an agent and watch files move.&lt;/p&gt;

&lt;p&gt;CI turns green. You still cannot explain the patch. That is not onboarding. That is a demo your teammates cannot review.&lt;/p&gt;

&lt;p&gt;Speed is not the failure mode here. Unsigned checks are the failure mode. A junior who cannot defend the assertion should not merge the assertion.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a ticket contract is
&lt;/h2&gt;

&lt;p&gt;A ticket contract is a tiny failing check you type by hand. It encodes only the behavior the ticket names. It lives on a path the agent must never edit.&lt;/p&gt;

&lt;p&gt;You hash that path before the session starts. You hash it again before you open the PR. A moved digest means the session is invalid.&lt;/p&gt;

&lt;p&gt;Keep the contract boring. Use numbers from the ticket. Do not invent extra business rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The artifact you create first
&lt;/h2&gt;

&lt;p&gt;Create three files before any prompt. Put them on your branch. Do not ask an agent to draft them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. A tight path allowlist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .agent/ticket-allowlist.txt
src/billing/proration.py
tests/unit/test_proration_impl.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementation tests may change. The contract path may not. Five files is already too many for ticket one.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A human-owned contract test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests/contract/test_ticket_42_proration.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Human-owned. Do not edit in an agent session.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;billing.proration&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;prorate_cents&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_partial_month_rounds_down_not_half_up&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Ticket 42: February, 10 days, $30 plan, USD cents.
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;prorate_cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days_used&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days_in_month&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1071&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_zero_days_is_zero&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;prorate_cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days_used&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days_in_month&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write the expected cents from the ticket. If the function is missing, the test must fail. That failure is your starting line, not a problem to hide.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. A guard script you run locally
&lt;/h3&gt;

&lt;p&gt;Label this as a local workflow. Run it on a throwaway branch first. Do not treat it as production policy until your team agrees.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# scripts/guard-contract.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--show-toplevel&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;CONTRACT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"tests/contract"&lt;/span&gt;
&lt;span class="nv"&gt;STAMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;".agent/contract.sha256"&lt;/span&gt;
&lt;span class="nv"&gt;ALLOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;".agent/ticket-allowlist.txt"&lt;/span&gt;
&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AGENT_BASE&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;/main&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

stamp&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .agent
  find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTRACT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;sort&lt;/span&gt; | xargs &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STAMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"stamped &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STAMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; contract files"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

check_hash&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTRACT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;sort&lt;/span&gt; | xargs &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; diff &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STAMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"contract files changed; reject this session"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"contract hash ok"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

check_paths&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALLOW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing &lt;/span&gt;&lt;span class="nv"&gt;$ALLOW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; f&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;continue
    if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Fxq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALLOW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      continue
    fi
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"blocked path: &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...HEAD&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"path allowlist ok"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;stamp&lt;span class="p"&gt;)&lt;/span&gt; stamp &lt;span class="p"&gt;;;&lt;/span&gt;
  check&lt;span class="p"&gt;)&lt;/span&gt; check_hash &lt;span class="p"&gt;;;&lt;/span&gt;
  paths&lt;span class="p"&gt;)&lt;/span&gt; check_paths &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest tests/contract &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; {stamp|check|paths|test}"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2 &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable once. Keep the stamp file in git so review can see it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x scripts/guard-contract.sh
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; ticket-42-proration
&lt;span class="c"&gt;# write the three files, then:&lt;/span&gt;
./scripts/guard-contract.sh stamp
./scripts/guard-contract.sh &lt;span class="nb"&gt;test&lt;/span&gt;   &lt;span class="c"&gt;# expect fail until impl exists&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  First-hour sequence
&lt;/h2&gt;

&lt;p&gt;Follow these steps in order. Do not skip the fail.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the ticket out loud. Circle every number.&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;tests/contract&lt;/code&gt; for those numbers only.&lt;/li&gt;
&lt;li&gt;Run the contract test. Confirm it fails for the right reason.&lt;/li&gt;
&lt;li&gt;Stamp the hash before you open an editor for the agent.&lt;/li&gt;
&lt;li&gt;Write the allowlist. Keep it under five paths.&lt;/li&gt;
&lt;li&gt;Prompt with paths and the failing test name. Do not say “update tests as needed.”&lt;/li&gt;
&lt;li&gt;Re-run &lt;code&gt;check&lt;/code&gt;, &lt;code&gt;paths&lt;/code&gt;, and &lt;code&gt;test&lt;/code&gt; on your machine.&lt;/li&gt;
&lt;li&gt;Open the PR with the template below.&lt;/li&gt;
&lt;li&gt;If review calls the contract weak, you rewrite it.&lt;/li&gt;
&lt;li&gt;If the agent mutated the contract, reset the branch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A useful prompt stays narrow. Paste paths, not permission.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement prorate_cents in src/billing/proration.py so
tests/contract/test_ticket_42_proration.py passes.
Do not edit tests/contract or any path outside:
- src/billing/proration.py
- tests/unit/test_proration_impl.py
Do not add dependencies. Do not rewrite git history.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the guard again. Do not trust the agent's summary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./scripts/guard-contract.sh check
./scripts/guard-contract.sh paths
./scripts/guard-contract.sh &lt;span class="nb"&gt;test
&lt;/span&gt;git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; origin/main...HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  First PR body you can defend
&lt;/h2&gt;

&lt;p&gt;Your PR description is part of the contract. Fill it before you request review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Ticket&lt;/span&gt;
42 — February proration rounds down in cents.

&lt;span class="gu"&gt;## Human contract&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; tests/contract/test_ticket_42_proration.py (hash-locked)
&lt;span class="p"&gt;-&lt;/span&gt; stamp: .agent/contract.sha256

&lt;span class="gu"&gt;## Agent-touched paths&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; src/billing/proration.py
&lt;span class="p"&gt;-&lt;/span&gt; tests/unit/test_proration_impl.py

&lt;span class="gu"&gt;## Commands I ran&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; ./scripts/guard-contract.sh check
&lt;span class="p"&gt;-&lt;/span&gt; ./scripts/guard-contract.sh paths
&lt;span class="p"&gt;-&lt;/span&gt; ./scripts/guard-contract.sh test

&lt;span class="gu"&gt;## What I can explain&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 10/28 of 3000 cents is 1071 after truncating toward zero
&lt;span class="p"&gt;-&lt;/span&gt; zero days returns zero

&lt;span class="gu"&gt;## Rollback trigger&lt;/span&gt;
Reset this branch if the contract digest moved.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you cannot complete the “What I can explain” section, you are not ready. Close the PR. Do not ask the agent to write that section for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free agent loop fits
&lt;/h2&gt;

&lt;p&gt;You still need an implementation loop after the stamp. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source coding assistant with free model access and a free server option. Point it at allowlisted files only. Keep the contract on your side of the line.&lt;/p&gt;

&lt;p&gt;The product does not replace the hash check. It does not prove the contract is correct. It only writes candidate implementation under a path fence you already set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table for hour one
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Your action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contract test never failed&lt;/td&gt;
&lt;td&gt;You wrote a tautology. Rewrite it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent edited &lt;code&gt;tests/contract&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Reset the branch. Do not diff-fix.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digest matches, test still red&lt;/td&gt;
&lt;td&gt;Do not widen the allowlist.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra files in &lt;code&gt;git diff&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Restore those paths, then restamp nothing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review says the contract is weak&lt;/td&gt;
&lt;td&gt;You edit it. Then stamp again.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You cannot explain the cents&lt;/td&gt;
&lt;td&gt;Do not merge. Ask a human.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reset is cheaper than a second agent pass. Use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;--&lt;/span&gt; tests/contract
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; origin/main
&lt;span class="c"&gt;# recreate the contract by hand, then stamp again&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not “fix forward” with a broader prompt. Broader prompts hide the first mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not prove
&lt;/h2&gt;

&lt;p&gt;A locked hash does not prove the ticket was understood. You can stamp a weak contract. Reviewers still need to read the numbers.&lt;/p&gt;

&lt;p&gt;A passing contract does not prove the rest of billing is safe. Adjacent functions can still break. Run the repo’s normal suite after the guard.&lt;/p&gt;

&lt;p&gt;An allowlist does not stop leaked secrets in prompts. Do not paste &lt;code&gt;.env&lt;/code&gt; files. Do not paste production dumps into the session.&lt;/p&gt;

&lt;p&gt;This workflow also fails open if you skip &lt;code&gt;paths&lt;/code&gt;. The hash only watches &lt;code&gt;tests/contract&lt;/code&gt;. It will not see a rewritten &lt;code&gt;README&lt;/code&gt; unless you block that path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Skip this gate for production incident patches with a human driver. Skip it for throwaway spikes with no merge target. Skip it when the repo has no test runner you can invoke locally.&lt;/p&gt;

&lt;p&gt;Staff engineers pairing on a known module may not need the stamp. Juniors on hour one do. If you cannot name the files, you are not ready for an agent.&lt;/p&gt;

&lt;p&gt;Do not use this as cover for merging code you did not read. The contract is a floor. It is not a review substitute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close the first ticket cleanly
&lt;/h2&gt;

&lt;p&gt;Your first merge should be small and explainable. The agent may type the implementation. You own the check that made it fail, then pass.&lt;/p&gt;

&lt;p&gt;Stamp. Allowlist. Prompt. Guard. Explain. That is the whole loop. If any step needs a story, the branch is not ready.&lt;/p&gt;

&lt;p&gt;If you want a free implementation loop behind that fence, try MonkeyCode’s free model access and free server on allowlisted paths only. Leave the contract human-owned.&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>ai</category>
      <category>testing</category>
    </item>
    <item>
      <title>Reproduce Before You Patch: A First-Week Gate for AI Diffs</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Mon, 14 Sep 2026 21:52:12 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/reproduce-before-you-patch-a-first-week-gate-for-ai-diffs-1n44</link>
      <guid>https://dev.to/gitgo_5662/reproduce-before-you-patch-a-first-week-gate-for-ai-diffs-1n44</guid>
      <description>&lt;p&gt;Green CI is not evidence. A red-to-green test transition is.&lt;/p&gt;

&lt;p&gt;You just joined a repo. Your first PR arrives from an AI agent. The suite is green, so you merge it. Two days later the same bug returns in a different file.&lt;/p&gt;

&lt;p&gt;This post gives you one gate to run before your first merge. It takes about ten minutes. It uses only the test runner you already have.&lt;/p&gt;

&lt;p&gt;The current debate about AI-written code is mostly about trust. This gate turns trust into a measurement you can read in a terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with green CI in week one
&lt;/h2&gt;

&lt;p&gt;CI tells you the repo was healthy at some point. It does not tell you the patch changed anything.&lt;/p&gt;

&lt;p&gt;On a fresh clone you cannot tell the difference. You have no memory of which tests failed yesterday. Neither does the agent.&lt;/p&gt;

&lt;p&gt;So capture the suite state before the patch, not after. That single ordering decision is the whole gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Capture the pre-patch baseline
&lt;/h2&gt;

&lt;p&gt;Create a branch at the patch point. Record the target test result and the full suite result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch &lt;span class="nt"&gt;-c&lt;/span&gt; gate/baseline origin/main
&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rejects expired session token"&lt;/span&gt; ./tools/gate.sh before
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the script. Treat it as a template, not a drop-in: swap &lt;code&gt;npm test&lt;/code&gt; and &lt;code&gt;npx jest&lt;/code&gt; for your runner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# tools/gate.sh -- template, adapt runner flags to your repo&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-uo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;PHASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?usage:&lt;span class="p"&gt; gate.sh before|after&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt;:?set&lt;span class="p"&gt; TARGET to the reproducing test name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .gate

&lt;span class="c"&gt;# full suite first, so you always get a number&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;".gate/&lt;/span&gt;&lt;span class="nv"&gt;$PHASE&lt;/span&gt;&lt;span class="s2"&gt;.suite.txt"&lt;/span&gt; 2&amp;gt;&amp;amp;1
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"suite=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;".gate/&lt;/span&gt;&lt;span class="nv"&gt;$PHASE&lt;/span&gt;&lt;span class="s2"&gt;.status"&lt;/span&gt;

&lt;span class="c"&gt;# the one test that is supposed to reproduce the bug&lt;/span&gt;
npx jest &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;".gate/&lt;/span&gt;&lt;span class="nv"&gt;$PHASE&lt;/span&gt;&lt;span class="s2"&gt;.target.txt"&lt;/span&gt; 2&amp;gt;&amp;amp;1
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"target=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;".gate/&lt;/span&gt;&lt;span class="nv"&gt;$PHASE&lt;/span&gt;&lt;span class="s2"&gt;.status"&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;".gate/&lt;/span&gt;&lt;span class="nv"&gt;$PHASE&lt;/span&gt;&lt;span class="s2"&gt;.status"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;set -uo pipefail&lt;/code&gt; and never &lt;code&gt;-e&lt;/code&gt;. A failing test must not stop the script.&lt;/p&gt;

&lt;p&gt;The baseline line must read &lt;code&gt;target=1&lt;/code&gt;. If it reads &lt;code&gt;target=0&lt;/code&gt;, stop there. You have not reproduced the bug, so nothing downstream means anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Ask for the test, not the fix
&lt;/h2&gt;

&lt;p&gt;Send the agent the issue text and the failing input. Ask for one test only. No production code yet.&lt;/p&gt;

&lt;p&gt;A good reproducing test has three properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It fails on the baseline commit, for the reason in the ticket.&lt;/li&gt;
&lt;li&gt;It names the user-visible symptom in its description.&lt;/li&gt;
&lt;li&gt;It asserts on observable behavior, not on internal call counts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reject any test that mocks the function under test. Those pass for the wrong reason and hide the bug later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Freeze a small guard set
&lt;/h2&gt;

&lt;p&gt;Pick five to ten existing tests in or near the changed files. These are your guards. They must stay green after the patch.&lt;/p&gt;

&lt;p&gt;Do not let the agent edit them in the same PR. If a guard test genuinely needs a change, that is a separate PR with its own explanation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# guards.txt -- one test name per line, reviewed by a human&lt;/span&gt;
rejects expired session token
rotates the token after refresh
logs out on missing header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep this list short. A guard set you cannot read in one screen is a guard set you will not check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Re-run after the patch and compare
&lt;/h2&gt;

&lt;p&gt;Apply the agent's diff on a fresh branch. Re-run the same script with the same &lt;code&gt;TARGET&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch gate/agent-patch
&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rejects expired session token"&lt;/span&gt; ./tools/gate.sh after
&lt;span class="nb"&gt;paste&lt;/span&gt; .gate/before.status .gate/after.status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is two columns. The left is before, the right is after. You now have a table, not an opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before: target&lt;/th&gt;
&lt;th&gt;After: target&lt;/th&gt;
&lt;th&gt;After: guards&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;all pass&lt;/td&gt;
&lt;td&gt;Accept. Red-to-green on one named behavior.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Reject. The patch does not change behavior.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;all pass&lt;/td&gt;
&lt;td&gt;Investigate. The test never reproduced the bug.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;any fail&lt;/td&gt;
&lt;td&gt;Reject. The patch traded one failure for another.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;absent&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Reject. There is no evidence to read.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Row three is the one juniors miss. A test that passed before the patch proves nothing, even if it is green now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a hosted model fits in this loop
&lt;/h2&gt;

&lt;p&gt;You need two things to run this gate on a scratch machine: a way to draft the reproducing test, and somewhere to run it. I used MonkeyCode for both while onboarding, through its free model access and its free server option.&lt;/p&gt;

&lt;p&gt;That is a narrow role. The model drafts a test from the ticket text. I read it, trim it, and keep only the assertion that fails on the baseline. The verdict still comes from the script, never from the model.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;The free model access matters for one reason here: cost stops being the excuse for skipping the test. The free server option matters because a clean box gives you a clean baseline. Treat both as operator-stated availability and check the current terms yourself, since quotas and offers change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This gate assumes deterministic tests. Flaky suites produce false reds, and the whole table becomes noise.&lt;/p&gt;

&lt;p&gt;It says nothing about untested behavior. A patch can pass every check and still be wrong in a path no test covers.&lt;/p&gt;

&lt;p&gt;It is also weak on config, docs, and dependency bumps. Those diffs rarely have a reproducing test, so use a different review method there.&lt;/p&gt;

&lt;p&gt;The scripts above are templates. They are not run against your repository, and the runner flags will differ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Skip this approach if your suite takes more than twenty minutes on a fresh clone. Run only the &lt;code&gt;TARGET&lt;/code&gt; job instead, and note that you skipped the guards.&lt;/p&gt;

&lt;p&gt;Skip it if the repo has no test runner at all. Write the harness first; a gate without a runner is just a ritual.&lt;/p&gt;

&lt;p&gt;Skip it for generated files, lock files, and formatting-only patches. There is nothing behavioral to measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first-week checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;gate.sh before&lt;/code&gt; and confirm &lt;code&gt;target=1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ask the agent for one reproducing test, not a fix.&lt;/li&gt;
&lt;li&gt;Freeze five to ten guard tests by hand.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gate.sh after&lt;/code&gt; and paste the two status files side by side.&lt;/li&gt;
&lt;li&gt;Accept only on a red-to-green transition with guards intact.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to try this on a throwaway box instead of your laptop, MonkeyCode offers free model access and a free server option, and the project terms live on its site.&lt;/p&gt;

&lt;p&gt;One gate, run twice, tells you more than a green badge ever will.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>git</category>
      <category>beginners</category>
    </item>
    <item>
      <title>First Rollback: Revert the Agent PR You Cannot Explain</title>
      <dc:creator>Quinn Zhu</dc:creator>
      <pubDate>Sun, 13 Sep 2026 20:37:07 +0000</pubDate>
      <link>https://dev.to/gitgo_5662/first-rollback-revert-the-agent-pr-you-cannot-explain-3akh</link>
      <guid>https://dev.to/gitgo_5662/first-rollback-revert-the-agent-pr-you-cannot-explain-3akh</guid>
      <description>&lt;p&gt;Your first AI pull request will often need rollback.&lt;br&gt;
Plan that rollback before you merge anything.&lt;/p&gt;

&lt;p&gt;You lack repo history on day one.&lt;br&gt;
Agents still produce large and confident diffs today.&lt;br&gt;
A rollback plan keeps that blast radius tiny.&lt;/p&gt;
&lt;h2&gt;
  
  
  What first rollback actually means
&lt;/h2&gt;

&lt;p&gt;First rollback means undoing your own agent PR.&lt;br&gt;
It does not mean rewriting team history casually.&lt;/p&gt;

&lt;p&gt;You revert a branch you still control.&lt;br&gt;
You leave other people's commits completely untouched.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why juniors stall on revert
&lt;/h2&gt;

&lt;p&gt;You treat revert as an admission of failure.&lt;br&gt;
It is a safety move, not a performance review.&lt;/p&gt;

&lt;p&gt;You also wait for perfect understanding of every hunk.&lt;br&gt;
That wait lets CI keep building a bad branch.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Freeze merge before you diagnose
&lt;/h2&gt;

&lt;p&gt;Do not keep generating patches during a bad merge.&lt;br&gt;
Stop the agent, the pipeline, and extra pushes now.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mark the PR as draft if GitHub still allows it.&lt;/li&gt;
&lt;li&gt;Cancel running CI on that branch right now.&lt;/li&gt;
&lt;li&gt;Tell your reviewer the branch is under rollback.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;ready &lt;span class="nt"&gt;--undo&lt;/span&gt;
gh run cancel &lt;span class="nt"&gt;--branch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git status &lt;span class="nt"&gt;-sb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those three commands buy you quiet thinking time.&lt;br&gt;
You need that quiet before you touch git history.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Inventory every file the agent touched
&lt;/h2&gt;

&lt;p&gt;You cannot revert a change you cannot list.&lt;br&gt;
Ask git, not the chat log, for the truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASE_SHA&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;/main&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;HEAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HEAD_SHA&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;HEAD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

git fetch origin
git diff &lt;span class="nt"&gt;--name-status&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEAD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEAD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;..&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEAD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save that inventory into a local file immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .onboarding
git diff &lt;span class="nt"&gt;--name-status&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEAD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .onboarding/first-pr-files.txt
git diff &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEAD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .onboarding/first-pr.patch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the name-status list before any revert.&lt;br&gt;
Look for migrations, lockfiles, and generated assets.&lt;/p&gt;
&lt;h2&gt;
  
  
  Read the patch in three passes
&lt;/h2&gt;

&lt;p&gt;You do not read the patch from top to bottom.&lt;br&gt;
You read it in three passes with git only.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read names first, then stats, then one scary file.&lt;/li&gt;
&lt;li&gt;Skip vendor folders until the end of the review.&lt;/li&gt;
&lt;li&gt;Stop when you cannot explain a hunk out loud.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...HEAD | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...HEAD | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the file count exceeds the ticket's expected paths, revert.&lt;br&gt;
A junior should not debug a thirty-file agent surprise.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Choose revert or fix-forward
&lt;/h2&gt;

&lt;p&gt;Use a hard table. Do not improvise under pressure.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Prefer revert&lt;/th&gt;
&lt;th&gt;Prefer fix-forward&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Files outside the ticket&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests red on your branch&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Only if one test fails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema or migration changed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewer already approved&lt;/td&gt;
&lt;td&gt;Ask first&lt;/td&gt;
&lt;td&gt;Maybe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You cannot explain one hunk&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If two or more Yes cells fire, revert.&lt;br&gt;
Fix-forward is for a single, obvious typo only.&lt;/p&gt;

&lt;p&gt;Write the decision into the same onboarding folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .onboarding/rollback-decision.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
# First PR rollback decision
- Ticket:
- Files outside ticket:
- Tests:
- Migrations:
- Choice: revert | fix-forward
- Why:
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fill every bullet before you run git revert.&lt;br&gt;
Empty bullets mean you are still guessing, so stop.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: Revert on a fresh branch
&lt;/h2&gt;

&lt;p&gt;Never reset shared main on day one.&lt;br&gt;
Never force-push a branch others already pulled.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a rollback branch from current HEAD.&lt;/li&gt;
&lt;li&gt;Revert the merge commit or the PR range.&lt;/li&gt;
&lt;li&gt;Keep the original branch for later autopsy.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;TICKET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TICKET&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;ONBOARD&lt;/span&gt;&lt;span class="p"&gt;-0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;SAFE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rollback/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TICKET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-first-pr"&lt;/span&gt;

git switch &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SAFE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# If the PR was a single merge commit:&lt;/span&gt;
git revert &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MERGE_SHA&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--no-edit&lt;/span&gt;
&lt;span class="c"&gt;# If the PR was a linear range:&lt;/span&gt;
&lt;span class="c"&gt;# git revert --no-edit "${BASE}".."${HEAD}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If revert conflicts, abort and re-read the inventory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;--diff-filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;U
&lt;span class="c"&gt;# After you understand each conflict:&lt;/span&gt;
&lt;span class="c"&gt;# git revert --abort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not ask an agent to resolve conflicts yet.&lt;br&gt;
Conflict markers hide product rules you just learned.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Prove the tree matches the base
&lt;/h2&gt;

&lt;p&gt;A revert is not done when git says success.&lt;br&gt;
A revert is done when tests match the base branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; origin/main...HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin the test command from the README only.&lt;br&gt;
Do not invent npm scripts the agent suggested.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proposed example. Replace with the README command.&lt;/span&gt;
&lt;span class="c"&gt;# npm test&lt;/span&gt;
&lt;span class="c"&gt;# go test ./...&lt;/span&gt;
&lt;span class="c"&gt;# pytest -q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If tests fail on the rollback branch, stop shipping.&lt;br&gt;
Your revert missed a generated file or a migration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6: Open a tiny rollback PR
&lt;/h2&gt;

&lt;p&gt;The rollback PR should contain almost no story.&lt;br&gt;
It should contain the inventory and the decision file.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Title it with the original ticket id.&lt;/li&gt;
&lt;li&gt;Link the failed PR in the first line.&lt;/li&gt;
&lt;li&gt;Paste the name-status list in the body.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add .onboarding/first-pr-files.txt &lt;span class="se"&gt;\&lt;/span&gt;
        .onboarding/rollback-decision.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"revert: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TICKET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; first AI PR"&lt;/span&gt;
gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--draft&lt;/span&gt; &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"revert: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TICKET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--body-file&lt;/span&gt; .onboarding/rollback-decision.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Keep the original PR closed or converted to draft.&lt;br&gt;
Do not delete it; you need the transcript later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where a free coding agent still helps
&lt;/h2&gt;

&lt;p&gt;You still need a second pair of eyes on the diff.&lt;br&gt;
You do not need that pair of eyes to rewrite files.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode is an open-source coding assistant for local work.&lt;br&gt;
It offers free model access and a free server option.&lt;br&gt;
Use it to narrate the patch, not to apply another patch.&lt;/p&gt;

&lt;p&gt;Paste the inventory file and ask for a file-risk list.&lt;br&gt;
Ask which paths look like migrations or generated code.&lt;br&gt;
Then you run git while the agent stays in explain mode.&lt;/p&gt;

&lt;p&gt;That split matters on a junior's first hour.&lt;br&gt;
Explanation is cheap, but a second bad patch is not.&lt;/p&gt;
&lt;h2&gt;
  
  
  A reproducible autopsy script
&lt;/h2&gt;

&lt;p&gt;Save this script under scripts in your clone.&lt;br&gt;
Run it from the repo root on your PR branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Proposed local helper. Not team policy.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;/main&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;.onboarding&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/branch.txt"&lt;/span&gt;
git diff &lt;span class="nt"&gt;--name-status&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/name-status.txt"&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;...HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/stat.txt"&lt;/span&gt;
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;..HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/commits.txt"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Risky paths:"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/risks.txt"&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; rg &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"migration|schema|lock|generated|dist/"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/name-status.txt"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/risks.txt"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s2"&gt;"migration|schema|lock|generated|dist/"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/name-status.txt"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;/risks.txt"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Inventory written to &lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Label this as a proposed local helper, not team policy.&lt;br&gt;
Your new team may already have a revert runbook.&lt;/p&gt;

&lt;p&gt;Commit the script only if the team wants it.&lt;br&gt;
Do not sneak tooling into the rollback PR itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This workflow assumes you still own the branch.&lt;br&gt;
It assumes main is protected and you can open drafts.&lt;/p&gt;

&lt;p&gt;It does not cover broken production traffic today.&lt;br&gt;
It does not cover signed commits you cannot reproduce.&lt;/p&gt;

&lt;p&gt;Revert can miss submodule pointers and LFS files.&lt;br&gt;
It can also miss squashed commits on a rewritten branch.&lt;/p&gt;

&lt;p&gt;The decision table is a heuristic, not a proof.&lt;br&gt;
Two yes cells do not replace a staff engineer's call.&lt;/p&gt;

&lt;p&gt;Free model access will not know your production topology.&lt;br&gt;
A free server will not hold your private incident facts.&lt;br&gt;
Do not paste secrets, tokens, or customer data into prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this approach
&lt;/h2&gt;

&lt;p&gt;Skip this if you are not the PR author.&lt;br&gt;
Skip this if the change already shipped to customers.&lt;/p&gt;

&lt;p&gt;Skip this if the repo uses a required merge queue.&lt;br&gt;
Talk to the maintainer before any revert there.&lt;/p&gt;

&lt;p&gt;Skip this if you cannot run the README test command.&lt;br&gt;
A green revert you cannot test is still a guess.&lt;/p&gt;

&lt;p&gt;Juniors on regulated codebases should wait for a buddy.&lt;br&gt;
Pair on the revert and do not solo a schema undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close the loop on hour one
&lt;/h2&gt;

&lt;p&gt;Write three notes before you log off.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What the agent changed outside the ticket.&lt;/li&gt;
&lt;li&gt;Which table signal made you choose revert.&lt;/li&gt;
&lt;li&gt;Which test command proved the tree was clean.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those notes become your second-day review checklist.&lt;br&gt;
They also keep the next agent session much smaller.&lt;/p&gt;

&lt;p&gt;Practice this workflow on a throwaway clone first.&lt;br&gt;
A free explain-only session can walk the inventory.&lt;br&gt;
Keep every git command in your own terminal.&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>ai</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
