Skip to content

[auto] #65 phase 1: deterministic planner-to-implementer handoff in SDLC workflows - #66

Closed
nutt-adam wants to merge 7 commits into
mainfrom
auto/issue-65-20260318220917
Closed

[auto] #65 phase 1: deterministic planner-to-implementer handoff in SDLC workflows#66
nutt-adam wants to merge 7 commits into
mainfrom
auto/issue-65-20260318220917

Conversation

@nutt-adam

@nutt-adam nutt-adam commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Automated SDLC cycle for #65.

  • planner: completed
  • implementation: completed
  • tests: updated
  • docs/changelog: updated
  • version: bumped if required

Summary by CodeRabbit

  • Documentation

    • Added canonical PR review loop docs and SDLC guidance detailing automated merge-gate behavior and recommended reviewer/approval practices
  • New Features

    • Optional merge-gate enforcement for land operations: requires green checks and resolved review threads before landing when enabled
    • Improved permission checks: blocked-command messages now include actionable allow-rule suggestions and reportable hints

@coderabbitai

coderabbitai Bot commented Mar 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds permission-suggestion hints and an environment-driven PR merge gate for land operations, updates automation to inject the gate, and adds documentation and workflow examples describing the PR review loop and enforcement behavior.

Changes

Cohort / File(s) Summary
Documentation
README.md, docs/CODEX_SDLC_ORCHESTRATION.md, docs/pr-review-loop.md
Adds canonical PR review loop and merge-gate guidance; documents enforcement via TT_ENFORCE_MERGE_GATE and updates runbook/checklist language.
Workflow Configuration
docs/examples/tutti-codex-sdlc.toml
Adds land_changes land step (fail-closed) to demonstrate enforced merge-gate in example workflow.
CLI — land flow
src/cli/land.rs
New run_with_options(..., enforce_merge_gate) entry; implements GH/GraphQL checks: find open PR, confirm required checks green, verify no unresolved review threads; helpers and unit tests added; gate toggled via env var constant.
Permissions model
src/permissions/mod.rs
Adds suggested_rule: Option<String> to CommandPolicyDecision; computes wildcard-prefix suggestions (first two tokens → "tok1 tok2 *") when commands are blocked; tests added.
CLI — permissions reporting
src/cli/permissions.rs
Propagates suggested_rule into PermissionCheckReport, JSON output, and user hints; persisting now records suggested_rule; tests updated.
Automation runtime
src/automation/mod.rs
Adds env-aware subcommand runner to inject TT_ENFORCE_MERGE_GATE=1 for land/review steps; policy denials include hint text and suggested_rule is logged in policy decision payloads; tests updated.
Packaging & changelog
Cargo.toml, CHANGELOG.md
Bumps version to 0.2.4 and adds changelog entry describing permission hints, merge gate, and new docs file.

Sequence Diagram

sequenceDiagram
    participant Automation as Automation (runner)
    participant LandCLI as Land CLI (run_with_options)
    participant GH as GitHub CLI (gh)
    participant API as GitHub GraphQL API

    Automation->>LandCLI: run_with_options(..., enforce_merge_gate=true)
    activate LandCLI

    LandCLI->>GH: find_open_pr_number (gh pr list)
    GH-->>LandCLI: PR number / none

    alt PR found
        LandCLI->>GH: ensure_required_checks_green (gh pr checks)
        GH-->>LandCLI: checks status

        LandCLI->>API: ensure_all_review_threads_resolved (gh api graphql)
        API-->>LandCLI: unresolved thread count

        alt all checks green & no unresolved threads
            LandCLI-->>Automation: allow land → proceed
        else gate fails
            LandCLI-->>Automation: fail closed with error details
        end
    else no PR found
        LandCLI-->>Automation: fail closed (no open PR)
    end
    deactivate LandCLI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hopped through threads and green-lit signs,
Suggested rules tucked in tidy lines.
A gentle gate keeps landing neat,
Reviews resolved—the branch’s treat.
Hooray for merges, small and sweet!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal but the PR template requirements are not fully met: versioning checkboxes are not marked, SemVer choice is not specified, validation section is empty, and release tag information is missing. Complete the template by marking relevant versioning checkboxes (version was bumped to 0.2.4 and CHANGELOG.md was updated), selecting PATCH as SemVer choice, confirming cargo test and CI status, and documenting the tag plan.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: implementing a deterministic SDLC workflow handoff with phase 1 completion, merge gate enforcement, and permission hint suggestions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auto/issue-65-20260318220917
📝 Coding Plan
  • Generate coding plan for human review comments

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

❤️ Share

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

nutt-adam and others added 2 commits March 18, 2026 22:23
…ule hints

Adds changelog, version bump, and README updates for v0.2.4 release
covering permissions suggest subcommand, land-step merge gate enforcement,
allow-rule hints on blocked commands, dry-run command field, and dependency
security fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add changelog entry and version bump for permission allow-rule hints
and PR merge gate enforcement on land steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cli/land.rs (1)

35-44: ⚠️ Potential issue | 🔴 Critical

Run the merge gate after materializing the branch state.

maybe_enforce_merge_gate() runs before commit_wip_if_needed() and before the --pr push, so auto-committed or newly pushed changes are not the ones whose checks and review threads were validated. That lets fresh, unreviewed code slip past the new gate.

🔒 Gate the exact head that will be landed
     if !force {
         ensure_git_clean(&resolved.project_root)?;
     }
     ensure_branch_exists(&resolved.project_root, &branch)?;
-    maybe_enforce_merge_gate(&resolved.project_root, &branch, enforce_merge_gate)?;
     let wip_committed = commit_wip_if_needed(&worktree_path, &resolved.agent_name)?;
 
     if pr {
         push_and_open_pr(&resolved.project_root, &branch)?;
         if wip_committed {
@@
         }
         return Ok(());
     }
+
+    maybe_enforce_merge_gate(&resolved.project_root, &branch, enforce_merge_gate)?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/land.rs` around lines 35 - 44, The merge gate is being run too early
(maybe_enforce_merge_gate is called before commit_wip_if_needed and before
push_and_open_pr), so change the call site to run the gate after the branch
state is fully materialized: remove the existing
maybe_enforce_merge_gate(&resolved.project_root, &branch, enforce_merge_gate)?;
call maybe_enforce_merge_gate only after commit_wip_if_needed(&worktree_path,
&resolved.agent_name)? has returned and, if pr is true, after
push_and_open_pr(&resolved.project_root, &branch)? completes (so the gate
validates the exact commit that will be landed); keep ensure_git_clean and
ensure_branch_exists where they are.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/permissions/mod.rs`:
- Around line 78-85: The function suggested_wildcard_prefix_rule currently
returns a broad wildcard rule (format!("{} {} *")) which is too permissive;
change suggested_wildcard_prefix_rule to return the exact two-token prefix
(e.g., format!("{} {}", tokens[0], tokens[1])) so it emits the bounded prefix
that matching_allow_rule() expects, and update the CLI fallback behavior that
persists suggestions (the "suggest --apply" path) to use the same narrower
two-token prefix instead of a wildcard to avoid unintentionally granting extra
permissions.

---

Outside diff comments:
In `@src/cli/land.rs`:
- Around line 35-44: The merge gate is being run too early
(maybe_enforce_merge_gate is called before commit_wip_if_needed and before
push_and_open_pr), so change the call site to run the gate after the branch
state is fully materialized: remove the existing
maybe_enforce_merge_gate(&resolved.project_root, &branch, enforce_merge_gate)?;
call maybe_enforce_merge_gate only after commit_wip_if_needed(&worktree_path,
&resolved.agent_name)? has returned and, if pr is true, after
push_and_open_pr(&resolved.project_root, &branch)? completes (so the gate
validates the exact commit that will be landed); keep ensure_git_clean and
ensure_branch_exists where they are.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7b11ee65-51ae-4d25-a319-4e2fdecce78d

📥 Commits

Reviewing files that changed from the base of the PR and between c354d6a and b4700e5.

📒 Files selected for processing (8)
  • README.md
  • docs/CODEX_SDLC_ORCHESTRATION.md
  • docs/examples/tutti-codex-sdlc.toml
  • docs/pr-review-loop.md
  • src/automation/mod.rs
  • src/cli/land.rs
  • src/cli/permissions.rs
  • src/permissions/mod.rs

Comment thread src/cli/land.rs
Comment on lines +224 to +275
let query = r#"query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
isResolved
}
}
}
}
}"#;

let output = Command::new("gh")
.args(["api", "graphql", "-f", &format!("query={query}")])
.args(["-F", &format!("owner={owner}")])
.args(["-F", &format!("name={name}")])
.args(["-F", &format!("number={pr_number}")])
.current_dir(project_root)
.output()?;

if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(TuttiError::Git(format!(
"merge gate failed to query review threads for PR #{}: {}",
pr_number,
stderr.trim()
)));
}

let payload: Value = serde_json::from_slice(&output.stdout).map_err(|e| {
TuttiError::State(format!(
"merge gate failed to parse review thread payload for PR #{}: {e}",
pr_number
))
})?;

let unresolved = unresolved_review_thread_count(&payload).ok_or_else(|| {
TuttiError::State(format!(
"merge gate could not determine unresolved review thread count for PR #{}",
pr_number
))
})?;

if unresolved == 0 {
Ok(())
} else {
Err(TuttiError::Git(format!(
"merge gate blocked: PR #{} has {} unresolved review thread(s). Resolve all threads before land/merge.",
pr_number, unresolved
)))
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C2 'reviewThreads\(first:\s*100\)|pageInfo|hasNextPage|endCursor' src/cli/land.rs

Repository: nutthouse/tutti

Length of output: 227


Paginate review-thread checks before declaring the gate green.

The GraphQL query only fetches reviewThreads(first: 100) and does not follow pagination, so a PR with more than 100 review threads can pass the gate even if later threads are unresolved.

🔁 Query sketch with pagination
-query($owner: String!, $name: String!, $number: Int!) {
+query($owner: String!, $name: String!, $number: Int!, $after: String) {
   repository(owner: $owner, name: $name) {
     pullRequest(number: $number) {
-      reviewThreads(first: 100) {
+      reviewThreads(first: 100, after: $after) {
+        pageInfo {
+          hasNextPage
+          endCursor
+        }
         nodes {
           isResolved
         }
       }
     }
   }
 }

Loop until hasNextPage is false before computing the unresolved count.

Comment thread src/permissions/mod.rs
Comment on lines +78 to +85
fn suggested_wildcard_prefix_rule(command_line: &str) -> Option<String> {
let tokens: Vec<&str> = command_line.split_whitespace().collect();
if tokens.len() < 2 {
return None;
}

Some(format!("{} {} *", tokens[0], tokens[1]))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Suggest the bounded prefix, not a wildcard rule.

A plain rule like cargo test already authorizes cargo test --quiet via matching_allow_rule(). Emitting cargo test * here is broader than needed, and tt permissions suggest --apply will persist that wider rule automatically. Please return the exact prefix here and align the CLI fallback with the same narrower form.

🔧 Narrower suggestion
-fn suggested_wildcard_prefix_rule(command_line: &str) -> Option<String> {
-    let tokens: Vec<&str> = command_line.split_whitespace().collect();
-    if tokens.len() < 2 {
-        return None;
-    }
-
-    Some(format!("{} {} *", tokens[0], tokens[1]))
+fn suggested_prefix_rule(command_line: &str) -> Option<String> {
+    let tokens: Vec<&str> = command_line.split_whitespace().collect();
+    match tokens.as_slice() {
+        [] => None,
+        [command] => Some((*command).to_string()),
+        [command, subcommand, ..] => Some(format!("{command} {subcommand}")),
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn suggested_wildcard_prefix_rule(command_line: &str) -> Option<String> {
let tokens: Vec<&str> = command_line.split_whitespace().collect();
if tokens.len() < 2 {
return None;
}
Some(format!("{} {} *", tokens[0], tokens[1]))
}
fn suggested_prefix_rule(command_line: &str) -> Option<String> {
let tokens: Vec<&str> = command_line.split_whitespace().collect();
match tokens.as_slice() {
[] => None,
[command] => Some((*command).to_string()),
[command, subcommand, ..] => Some(format!("{command} {subcommand}")),
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/permissions/mod.rs` around lines 78 - 85, The function
suggested_wildcard_prefix_rule currently returns a broad wildcard rule
(format!("{} {} *")) which is too permissive; change
suggested_wildcard_prefix_rule to return the exact two-token prefix (e.g.,
format!("{} {}", tokens[0], tokens[1])) so it emits the bounded prefix that
matching_allow_rule() expects, and update the CLI fallback behavior that
persists suggestions (the "suggest --apply" path) to use the same narrower
two-token prefix instead of a wildcard to avoid unintentionally granting extra
permissions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
README.md (1)

305-305: Clarify that the merge gate is environment-driven.

The documentation states that workflow land steps "enforce" a GitHub merge gate, but the implementation in src/cli/land.rs:121-142 shows this is controlled by an enabled parameter (via the TT_ENFORCE_MERGE_GATE environment variable). Users reading this might assume the gate is always enforced.

📝 Suggested clarification
-- Workflow `land` steps enforce a GitHub merge gate (required checks green + all PR review threads resolved)
+- Workflow `land` steps support an optional GitHub merge gate (enabled via `TT_ENFORCE_MERGE_GATE` env var) that ensures required checks are green and all PR review threads are resolved before allowing the land operation

Alternatively, if the behavior is documented elsewhere in the README or in a dedicated configuration section:

-- Workflow `land` steps enforce a GitHub merge gate (required checks green + all PR review threads resolved)
+- Workflow `land` steps enforce a GitHub merge gate when enabled (required checks green + all PR review threads resolved)

As per coding guidelines context, the error message in src/cli/land.rs explicitly mentions the TT_ENFORCE_MERGE_GATE environment variable, confirming this is a configurable feature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 305, The README line claiming workflow `land` steps
"enforce a GitHub merge gate" is misleading; update the wording to state the
merge gate is environment-driven and only enforced when the
TT_ENFORCE_MERGE_GATE variable is enabled (see implementation in src/cli/land.rs
where the `enabled` / `TT_ENFORCE_MERGE_MATE` check controls behavior). Change
the sentence to explicitly mention TT_ENFORCE_MERGE_GATE as the toggle, and
optionally add a short note or pointer to the `src/cli/land.rs` logic (the
`enabled` parameter) so users know how to enable/disable the merge gate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@README.md`:
- Line 305: The README line claiming workflow `land` steps "enforce a GitHub
merge gate" is misleading; update the wording to state the merge gate is
environment-driven and only enforced when the TT_ENFORCE_MERGE_GATE variable is
enabled (see implementation in src/cli/land.rs where the `enabled` /
`TT_ENFORCE_MERGE_MATE` check controls behavior). Change the sentence to
explicitly mention TT_ENFORCE_MERGE_GATE as the toggle, and optionally add a
short note or pointer to the `src/cli/land.rs` logic (the `enabled` parameter)
so users know how to enable/disable the merge gate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5de0763c-e528-4f58-9e5a-452081cd4932

📥 Commits

Reviewing files that changed from the base of the PR and between b4700e5 and b266040.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • CHANGELOG.md
  • Cargo.toml
  • README.md
✅ Files skipped from review due to trivial changes (1)
  • Cargo.toml

@nutt-adam nutt-adam closed this Mar 19, 2026
@nutt-adam
nutt-adam deleted the auto/issue-65-20260318220917 branch March 19, 2026 00:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants