Skip to content

feat: add plan-to-git CLI#3

Merged
skulidropek merged 12 commits into
mainfrom
issue-2
May 31, 2026
Merged

feat: add plan-to-git CLI#3
skulidropek merged 12 commits into
mainfrom
issue-2

Conversation

@skulidropek
Copy link
Copy Markdown
Member

@skulidropek skulidropek commented May 31, 2026

Summary

  • Replace the template sum app with a Codex-first plan capture CLI.
  • Store explicit plan blocks and planning Q/A decisions in a local stack.
  • Sync the current branch plan stack into the PR body via gh markers.

Verification

  • cargo fmt --all -- --check
  • cargo test
  • cargo clippy --all-targets --all-features
  • cargo package --allow-dirty

Agent Plan Stack

Branch: issue-2 at 979a739.

1. Plan

Source: codex - Captured: 2026-05-30T10:58:24.691Z

Codex Plan-to-Git MVP

Summary

Build the repo into a real Rust CLI named plan-to-git that automatically captures explicit Codex plan blocks and planning Q/A decisions, stores them as a local stack in .agent-plan.json, and syncs the full stack into the current branch PR when one exists.

MVP source is Codex first. Current branch issue-2 has no PR, so the expected first behavior is local stack persistence; when a PR later exists, the next hook/sync run uploads all queued plans.

Key Changes

  • Replace the template sum app with plan-to-git:

    • Rename package/lib/bin to plan-to-git / plan_to_git.
    • Remove sum code, update README/examples/tests.
    • Add direct deps: serde, serde_json, chrono, sha2; add tempfile as a dev dependency.
    • Add a minor changelog fragment.
  • Public CLI:

    • plan-to-git hook --source codex: reads Codex hook JSON from stdin, handles Stop and UserPromptSubmit.
    • plan-to-git sync: manually re-runs GitHub PR sync for queued stack items.
    • plan-to-git show: prints the local stack.
    • plan-to-git render: prints the markdown block that would be inserted into the PR.
    • plan-to-git clear: clears local stack only with --yes.
  • Local state:

    • Write repo-root .agent-plan.json; add it to .gitignore as local sync state.
    • Schema version 1, with repo, branch, head_sha, items, and pending_questions.
    • Stack item kinds: plan and decision.
    • Deduplicate by normalized content hash so repeated hook runs do not duplicate the same plan.
  • Capture behavior:

    • On Codex Stop, inspect only last_assistant_message; do not parse raw ~/.codex transcripts.
    • Capture only marked plan blocks: <proposed_plan>...</proposed_plan> and explicit Accepted Plan blocks.
    • If the assistant asks planning questions but emits no plan, store them as pending_questions.
    • On next UserPromptSubmit, attach the user prompt as a decision item answering pending questions.
    • Redact secrets before writing state or PR markdown.
  • GitHub PR sync:

    • Use git to resolve repo root, branch, HEAD SHA, and remote; use gh pr view / gh pr edit --body-file.
    • If no PR exists for the current branch, exit successfully after saving the local stack.
    • If a PR exists, render all stack items into:
      &lt;!-- plan-to-git:start --&gt; ... &lt;!-- plan-to-git:end --&gt;
    • Append the block if absent, replace it if both markers exist, and fail without editing if only one marker exists.

Test Plan

  • Unit tests:

    • marked plan extraction from <proposed_plan> and Accepted Plan;
    • rejection of unmarked/raw transcript-like text;
    • Q/A pending question capture and answer pairing;
    • redaction of token/API-key-like strings;
    • .agent-plan.json round trip and content-hash dedupe;
    • PR body append/replace/partial-marker failure.
  • Integration tests:

    • hook --source codex with fixture Stop JSON writes a plan item.
    • hook --source codex with Stop question then UserPromptSubmit writes a decision item.
    • sync with fake gh reporting no PR preserves queued items and exits success.
    • sync with fake gh PR body updates only the marker block.
    • No real GitHub mutation in tests; use fake git/gh via temp PATH.
  • Verification commands after Rust toolchain is available:

    • cargo fmt --check
    • cargo clippy --all-targets --all-features
    • cargo test

Assumptions And References

  • Default is automatic capture plus automatic PR sync; no manual accept gate in MVP.
  • Claude support is intentionally deferred, but the state model keeps source extensible.
  • Current container has codex/gh, but cargo/rustc are unavailable, so implementation verification needs a Rust toolchain first.
  • Sources checked: Codex hooks, Codex AGENTS.md, Claude Code hooks.

2. Plan

Source: codex - Captured: 2026-05-31T13:45:16.373Z

Тестовый План Для plan-to-git

Summary

Проверить PR #3: #3

Цель тестирования: подтвердить, что CLI корректно захватывает планы из Codex hooks, сохраняет локальный stack, синхронизирует plan/Q&A в PR body, не пишет в stdout hook-команды и не смешивает планы разных веток.

Automated Checks

  • Запустить:
    cargo fmt --all -- --check
    cargo test
    cargo clippy --all-targets --all-features
    cargo package --allow-dirty
  • Ожидаемый результат:
    • форматирование без diff;
    • все unit/integration/doc tests проходят;
    • clippy без warning/error;
    • crate package собирается и верифицируется.

Functional Scenarios

  • Stop hook с <proposed_plan>:

    • команда: plan-to-git hook --source codex;
    • stdin: Codex JSON с hook_event_name: "Stop" и last_assistant_message;
    • ожидаемо: .agent-plan.json содержит plan item, stdout пустой.
  • Stop hook с вопросом агента:

    • stdin: Codex JSON с last_assistant_message вида Should ...?;
    • ожидаемо: вопрос сохраняется в pending_questions, stdout пустой.
  • UserPromptSubmit hook с ответом пользователя:

    • stdin: Codex JSON с hook_event_name: "UserPromptSubmit" и prompt;
    • ожидаемо: pending question превращается в Planning Decision, pending_questions очищается.
  • PR sync при открытом PR:

    • gh pr view находит PR текущей ветки;
    • plan-to-git обновляет PR body через gh api PATCH;
    • ожидаемо: PR содержит блок &lt;!-- plan-to-git:start --&gt; ... &lt;!-- plan-to-git:end --&gt;.
  • PR sync без открытого PR:

    • gh pr view возвращает no pull requests found;
    • ожидаемо: команда завершается успешно, stack остаётся локально, PR не обновляется.

Regression And Safety Cases

  • Unmarked raw transcript не должен сохраняться как план.
  • Секреты/token/API key-like строки должны редактироваться перед записью.
  • Повторный одинаковый plan hook не должен создавать duplicate item.
  • Если PR body содержит только один marker, sync должен fail, не перезаписывая описание.
  • При наличии items из другой ветки render/sync должен публиковать только items текущей ветки.
  • Hook-команда не должна писать в stdout; любые diagnostic logs только в stderr.

Real PR Acceptance Criteria

  • В PR feat: add plan-to-git CLI #3 body есть:
    • Real PR Dogfood Plan;
    • вопрос Should agent questions...;
    • ответ пользователя upload both...;
    • marker block plan-to-git:start/end.
  • Проверка:
    gh pr view 3 --json body --jq '.body'
  • Рабочее дерево после проверки не содержит новых tracked изменений; untracked .playwright-mcp/ не относится к тесту и не добавляется в commit.

Assumptions

  • Тестируем Codex-first MVP; Claude hooks не входят в этот тестовый проход.
  • Реальная GitHub-проверка выполняется через PR feat: add plan-to-git CLI #3.
  • Для локальных integration tests используется fake git/gh, чтобы не мутировать реальные PR.

3. Plan

Source: codex - Captured: 2026-05-31T13:55:36.095Z

Hook Config Live Test

  • Verify Codex Stop hook invokes plan-to-git automatically.

4. Plan

Source: codex - Captured: 2026-05-31T13:55:54.180Z

Hook JSON Test

  • test

5. Plan

Source: codex - Captured: 2026-05-31T13:56:48.365Z

Hooks JSON Live Test

  • Verify Codex Stop hook invokes plan-to-git from hooks.json.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

Warning

Review limit reached

@skulidropek, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 55 minutes and 43 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f922a1ee-138d-49d9-afa1-337ca892a61f

📥 Commits

Reviewing files that changed from the base of the PR and between 0314192 and 7dce5c5.

📒 Files selected for processing (1)
  • .github/workflows/release.yml
📝 Walkthrough

Walkthrough

Converts the template crate into the plan-to-git CLI: captures explicitly marked agent plans from Codex hooks, redacts secrets, persists a deduplicated Agent Plan Stack to .agent-plan.json, renders plan content, and posts new PR comments for unposted items. Adds Codex session import tooling, comprehensive unit and integration tests, and CLI subcommands.

Changes

Plan-to-Git MVP

Layer / File(s) Summary
Project configuration and metadata
Cargo.toml, .gitignore, README.md, changelog.d/*, examples/basic_usage.rs
Crate metadata and package name updated to plan-to-git; lib/bin names and dependencies adjusted; .agent-plan.json added to .gitignore; README and changelog updated; example rewritten to show plan rendering.
Error handling and git discovery
src/error.rs, src/git.rs
Adds AppError/AppResult and GitContext with discover that runs git commands and parses GitHub remote slug.
Plan state persistence model
src/store.rs
Introduces AgentPlanState, plan/decision/pending-question types, posted comment history, deduplication via SHA-256, context setters, add/answer helpers, and JSON load/save with schema handling.
Plan extraction and normalization
src/normalize.rs
Adds CapturedPlan, extract_marked_plans (handles <proposed_plan> blocks and accepted-plan headings), and extract_questions with deduplication and limits.
Content safety via redaction
src/redact.rs
Adds redact with regex patterns to scrub API keys, bearer tokens, GitHub/Slack/AWS keys and similar secrets before storage.
Rendering and PR marker management
src/render.rs, src/pr_body.rs
render_plan_block and render_plan_comment format stack/comment outputs; upsert_marker_block appends or replaces marker regions; nested marker escaping implemented.
GitHub PR synchronization (comments)
src/github.rs
sync_state now posts a new PR issue comment for unposted items via gh api, returns Commented status, and records posted comment metadata.
Codex hook processing and import
src/capture.rs, src/codex_history.rs
process_codex_hook handles Stop and UserPromptSubmit events to capture plans/questions and answer pending questions; import_codex_history scans JSONL sessions, skips rendered stacks, matches sessions to repo/branch context, and imports idempotently.
CLI entrypoint and exports
src/main.rs, src/lib.rs
Clap-based CLI (hook, import-codex, sync, show, render, clear) with dispatch logic; lib.rs exposes functional modules.
Examples and tests
examples/basic_usage.rs, tests/unit/*, tests/integration/*
Example updated. Unit tests cover extraction, redaction, state behavior, rendering, marker upsert. Integration tests exercise CLI flows with fake git/gh, comment posting, and import idempotency.

Sequence Diagram

sequenceDiagram
  participant CodexHook
  participant plan_to_git_CLI
  participant GitCLI
  participant AgentPlanState
  participant gh_CLI
  CodexHook->>plan_to_git_CLI: send hook JSON (stdin)
  plan_to_git_CLI->>GitCLI: git rev-parse / branch / head / remote (discover)
  plan_to_git_CLI->>AgentPlanState: load_state(.agent-plan.json)
  plan_to_git_CLI->>AgentPlanState: add_plan / add_pending_question / answer_pending_questions
  AgentPlanState-->>plan_to_git_CLI: save_state (if changed)
  plan_to_git_CLI->>gh_CLI: gh pr view --json number
  plan_to_git_CLI->>plan_to_git_CLI: render_plan_comment for unposted items
  plan_to_git_CLI->>gh_CLI: gh api --method POST repos/{repo}/issues/{pr}/comments --input <file>
  gh_CLI-->>plan_to_git_CLI: returned comment id
  plan_to_git_CLI->>AgentPlanState: mark_items_commented (record comment)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I munched through hooks and trimmed each plan in sight,
I hid the keys and polished text till bright,
I stacked decisions, stored them in a file,
Then hopped to PRs and left a kindly smile,
Small rabbit, big sync — mission done tonight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: add plan-to-git CLI' accurately summarizes the main objective: introducing a new Codex-first plan capture CLI to replace the template sum app.
Description check ✅ Passed The PR description is comprehensive and clearly related to the changeset. It details the plan-to-git CLI implementation, local state management, capture behavior, GitHub sync functionality, and verification steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 issue-2

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.

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 415c276
Status: success
Files: 6 (2.44 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/capture.rs (1)

124-134: 💤 Low value

Function name overstates selectivity — it drains all pending questions.

drain_relevant_questions uses drain(..) and applies no relevance filter; every outstanding question set is consumed and answered by a single prompt. If indiscriminate draining is intended, consider renaming to reflect that (e.g. drain_all_pending_questions); otherwise add the relevance filtering the name implies.

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

In `@src/capture.rs` around lines 124 - 134, The function drain_relevant_questions
currently consumes every PendingQuestion via pending_questions.drain(..) but
applies no relevance filter, so either rename it to reflect the behavior (e.g.,
drain_all_pending_questions) or implement true relevance filtering: keep the
name and add a predicate that inspects each PendingQuestion (or its questions)
to decide relevancy before removing/collecting them (use retain or selective
drain with drain_filter or manual iteration), ensure duplicates are still
deduplicated as before, and update any call sites that rely on the old
name/semantics (reference function drain_relevant_questions, the
pending_questions.drain(..) usage, and the PendingQuestion.questions field).
tests/integration/cli.rs (1)

32-56: 💤 Low value

Optional: reuse the run_hook helper here.

This block duplicates the spawn/write/wait logic already in run_hook (Lines 104-126). You could call run_hook(&repo_dir, &bin_dir, &payload) and then read the state file, keeping the two tests consistent.

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

In `@tests/integration/cli.rs` around lines 32 - 56, The test duplicates the
spawn/write/wait logic already implemented in run_hook; replace the manual
Command creation block in the test with a call to run_hook(&repo_dir, &bin_dir,
&payload) and then proceed to read STATE_FILE_NAME and assert on its contents
(e.g., assert!(state.contains("Capture plan"))), so both tests reuse the shared
helper and remain consistent.
src/main.rs (1)

50-63: ⚡ Quick win

Hook failures won’t block Codex unless the hook exits with code 2

This Hook error path only logs to stderr and doesn’t set a failure exit code, so Codex won’t hit its “block” gate (Codex blocks only when the hook exits with code 2; other non-zero codes are non-blocking). If hook failures should block the agent run, exit/return with 2 on run_hook errors; otherwise the current behavior matches Codex’s semantics.

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

In `@src/main.rs` around lines 50 - 63, The Hook arm currently only prints errors
from run_hook and does not cause a blocking exit; update the Commands::Hook {
source } branch so that when run_hook(*source) returns Err it exits with code 2
(e.g., call std::process::exit(2) or propagate an Err that main maps to exit
code 2) instead of merely logging, so hook failures trigger Codex’s blocking
behavior; locate the match arm handling Commands::Hook and the run_hook call to
apply this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/capture.rs`:
- Around line 124-134: The function drain_relevant_questions currently consumes
every PendingQuestion via pending_questions.drain(..) but applies no relevance
filter, so either rename it to reflect the behavior (e.g.,
drain_all_pending_questions) or implement true relevance filtering: keep the
name and add a predicate that inspects each PendingQuestion (or its questions)
to decide relevancy before removing/collecting them (use retain or selective
drain with drain_filter or manual iteration), ensure duplicates are still
deduplicated as before, and update any call sites that rely on the old
name/semantics (reference function drain_relevant_questions, the
pending_questions.drain(..) usage, and the PendingQuestion.questions field).

In `@src/main.rs`:
- Around line 50-63: The Hook arm currently only prints errors from run_hook and
does not cause a blocking exit; update the Commands::Hook { source } branch so
that when run_hook(*source) returns Err it exits with code 2 (e.g., call
std::process::exit(2) or propagate an Err that main maps to exit code 2) instead
of merely logging, so hook failures trigger Codex’s blocking behavior; locate
the match arm handling Commands::Hook and the run_hook call to apply this
change.

In `@tests/integration/cli.rs`:
- Around line 32-56: The test duplicates the spawn/write/wait logic already
implemented in run_hook; replace the manual Command creation block in the test
with a call to run_hook(&repo_dir, &bin_dir, &payload) and then proceed to read
STATE_FILE_NAME and assert on its contents (e.g.,
assert!(state.contains("Capture plan"))), so both tests reuse the shared helper
and remain consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 21ed2029-89d8-4f61-8bcf-f4a429969899

📥 Commits

Reviewing files that changed from the base of the PR and between 6987d9a and b2d3c02.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • .gitignore
  • Cargo.toml
  • README.md
  • changelog.d/20260530_104500_plan_to_git_mvp.md
  • examples/basic_usage.rs
  • src/capture.rs
  • src/error.rs
  • src/git.rs
  • src/github.rs
  • src/lib.rs
  • src/main.rs
  • src/normalize.rs
  • src/pr_body.rs
  • src/redact.rs
  • src/render.rs
  • src/store.rs
  • src/sum.rs
  • tests/integration/cli.rs
  • tests/integration/mod.rs
  • tests/integration/sum.rs
  • tests/unit/mod.rs
  • tests/unit/plan_capture.rs
  • tests/unit/sum.rs
💤 Files with no reviewable changes (3)
  • tests/integration/sum.rs
  • tests/unit/sum.rs
  • src/sum.rs

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 94587af
Status: success
Files: 12 (4.72 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
src/github.rs (1)

89-101: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cleanup failure can mask a successful PR update.

If gh api ... PATCH succeeds but fs::remove_file fails, the early return Err(error.into()) reports the sync as failed even though the PR body was updated. Treat temp-file cleanup as best-effort and decide success from the command status.

🐛 Proposed fix
-    let remove_result = fs::remove_file(&request_file);
-    let output = output?;
-    if let Err(error) = remove_result {
-        return Err(error.into());
-    }
-
-    if output.status.success() {
-        return Ok(());
-    }
+    let _ = fs::remove_file(&request_file);
+    let output = output?;
+    if output.status.success() {
+        return Ok(());
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/github.rs` around lines 89 - 101, The cleanup error handling currently
returns early on fs::remove_file(&request_file) (remove_result) before checking
the gh command result (output.status.success()), which can report failure even
when the PR update succeeded; change the control flow so you first inspect
output.status.success() to determine overall success, then attempt
fs::remove_file(&request_file) as a best-effort cleanup — if the command
succeeded but remove_file returns Err, do not convert that into an overall Err
(instead log or ignore the cleanup error), and only propagate errors from the gh
command (using stderr and AppError::new) when output.status.success() is false;
update the branches around remove_result, output, and the final
Err(AppError::new(...)) to reflect this ordering.
src/capture.rs (1)

110-114: ⚠️ Potential issue | 🟠 Major

Avoid blocking synchronous hooks with untimed gh sync calls

In src/capture.rs (process_codex_hook, ~110-114), github::sync_state(&context, &state) is called on every Stop/UserPromptSubmit handling path. When there are current-branch items, sync_state runs gh pr view --json number,body (and potentially gh api ... PATCH) via Command::output() with no timeout, so a slow/hung gh can stall the user session.

  • Gate sync_state behind changed (and/or only when the rendered plan block could differ) or move it off the hook critical path.
  • Enforce a timeout around the gh process externally (since gh pr view/gh api don’t provide a request-timeout flag) or implement a Rust-side process timeout.

Minor: drain_relevant_questions drains & deduplicates all pending questions (no “relevance” filtering), so the name is misleading.

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

In `@src/capture.rs` around lines 110 - 114, The current hook handler
process_codex_hook always calls github::sync_state(&context, &state) which can
block the hook because github::sync_state runs gh CLI commands without a
timeout; only call github::sync_state when state actually changed or when the
rendered plan block could differ (i.e., gate the call behind the existing
changed boolean or a specific plan-diff check), or defer it off the critical
path by spawning it as a background task/thread so the hook returns immediately.
Additionally, implement a Rust-side timeout inside github::sync_state for any
Command::output() calls that invoke gh (or switch to a process API that supports
wait-with-timeout) to avoid hangs, and consider renaming
drain_relevant_questions to a name reflecting its behavior (e.g.,
drain_and_dedupe_pending_questions) to match its actual logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/capture.rs`:
- Around line 110-114: The current hook handler process_codex_hook always calls
github::sync_state(&context, &state) which can block the hook because
github::sync_state runs gh CLI commands without a timeout; only call
github::sync_state when state actually changed or when the rendered plan block
could differ (i.e., gate the call behind the existing changed boolean or a
specific plan-diff check), or defer it off the critical path by spawning it as a
background task/thread so the hook returns immediately. Additionally, implement
a Rust-side timeout inside github::sync_state for any Command::output() calls
that invoke gh (or switch to a process API that supports wait-with-timeout) to
avoid hangs, and consider renaming drain_relevant_questions to a name reflecting
its behavior (e.g., drain_and_dedupe_pending_questions) to match its actual
logic.

In `@src/github.rs`:
- Around line 89-101: The cleanup error handling currently returns early on
fs::remove_file(&request_file) (remove_result) before checking the gh command
result (output.status.success()), which can report failure even when the PR
update succeeded; change the control flow so you first inspect
output.status.success() to determine overall success, then attempt
fs::remove_file(&request_file) as a best-effort cleanup — if the command
succeeded but remove_file returns Err, do not convert that into an overall Err
(instead log or ignore the cleanup error), and only propagate errors from the gh
command (using stderr and AppError::new) when output.status.success() is false;
update the branches around remove_result, output, and the final
Err(AppError::new(...)) to reflect this ordering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e262ddb-0c0b-4b19-8610-f15892f0cf3e

📥 Commits

Reviewing files that changed from the base of the PR and between b2d3c02 and 94587af.

📒 Files selected for processing (11)
  • README.md
  • changelog.d/20260530_104500_plan_to_git_mvp.md
  • examples/basic_usage.rs
  • src/capture.rs
  • src/codex_history.rs
  • src/github.rs
  • src/lib.rs
  • src/main.rs
  • src/store.rs
  • tests/integration/cli.rs
  • tests/unit/plan_capture.rs
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib.rs
  • changelog.d/20260530_104500_plan_to_git_mvp.md
  • src/store.rs
  • tests/unit/plan_capture.rs

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 4ef7872
Status: success
Files: 12 (4.85 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 283dcb7
Status: success
Files: 12 (4.92 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 979a739
Status: success
Files: 12 (5.03 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 002a9f9
Status: success
Files: 13 (5.33 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

@skulidropek
Copy link
Copy Markdown
Member Author

Agent Plan Update

Branch: issue-2 at 002a9f9.

1. Plan

Source: codex - Captured: 2026-05-30T10:58:24.691Z

Codex Plan-to-Git MVP

Summary

Build the repo into a real Rust CLI named plan-to-git that automatically captures explicit Codex plan blocks and planning Q/A decisions, stores them as a local stack in .agent-plan.json, and syncs the full stack into the current branch PR when one exists.

MVP source is Codex first. Current branch issue-2 has no PR, so the expected first behavior is local stack persistence; when a PR later exists, the next hook/sync run uploads all queued plans.

Key Changes

  • Replace the template sum app with plan-to-git:

    • Rename package/lib/bin to plan-to-git / plan_to_git.
    • Remove sum code, update README/examples/tests.
    • Add direct deps: serde, serde_json, chrono, sha2; add tempfile as a dev dependency.
    • Add a minor changelog fragment.
  • Public CLI:

    • plan-to-git hook --source codex: reads Codex hook JSON from stdin, handles Stop and UserPromptSubmit.
    • plan-to-git sync: manually re-runs GitHub PR sync for queued stack items.
    • plan-to-git show: prints the local stack.
    • plan-to-git render: prints the markdown block that would be inserted into the PR.
    • plan-to-git clear: clears local stack only with --yes.
  • Local state:

    • Write repo-root .agent-plan.json; add it to .gitignore as local sync state.
    • Schema version 1, with repo, branch, head_sha, items, and pending_questions.
    • Stack item kinds: plan and decision.
    • Deduplicate by normalized content hash so repeated hook runs do not duplicate the same plan.
  • Capture behavior:

    • On Codex Stop, inspect only last_assistant_message; do not parse raw ~/.codex transcripts.
    • Capture only marked plan blocks: <proposed_plan>...</proposed_plan> and explicit Accepted Plan blocks.
    • If the assistant asks planning questions but emits no plan, store them as pending_questions.
    • On next UserPromptSubmit, attach the user prompt as a decision item answering pending questions.
    • Redact secrets before writing state or PR markdown.
  • GitHub PR sync:

    • Use git to resolve repo root, branch, HEAD SHA, and remote; use gh pr view / gh pr edit --body-file.
    • If no PR exists for the current branch, exit successfully after saving the local stack.
    • If a PR exists, render all stack items into:
      &lt;!-- plan-to-git:start --&gt; ... &lt;!-- plan-to-git:end --&gt;
    • Append the block if absent, replace it if both markers exist, and fail without editing if only one marker exists.

Test Plan

  • Unit tests:

    • marked plan extraction from <proposed_plan> and Accepted Plan;
    • rejection of unmarked/raw transcript-like text;
    • Q/A pending question capture and answer pairing;
    • redaction of token/API-key-like strings;
    • .agent-plan.json round trip and content-hash dedupe;
    • PR body append/replace/partial-marker failure.
  • Integration tests:

    • hook --source codex with fixture Stop JSON writes a plan item.
    • hook --source codex with Stop question then UserPromptSubmit writes a decision item.
    • sync with fake gh reporting no PR preserves queued items and exits success.
    • sync with fake gh PR body updates only the marker block.
    • No real GitHub mutation in tests; use fake git/gh via temp PATH.
  • Verification commands after Rust toolchain is available:

    • cargo fmt --check
    • cargo clippy --all-targets --all-features
    • cargo test

Assumptions And References

  • Default is automatic capture plus automatic PR sync; no manual accept gate in MVP.
  • Claude support is intentionally deferred, but the state model keeps source extensible.
  • Current container has codex/gh, but cargo/rustc are unavailable, so implementation verification needs a Rust toolchain first.
  • Sources checked: Codex hooks, Codex AGENTS.md, Claude Code hooks.

2. Plan

Source: codex - Captured: 2026-05-31T13:45:16.373Z

Тестовый План Для plan-to-git

Summary

Проверить PR #3: #3

Цель тестирования: подтвердить, что CLI корректно захватывает планы из Codex hooks, сохраняет локальный stack, синхронизирует plan/Q&A в PR body, не пишет в stdout hook-команды и не смешивает планы разных веток.

Automated Checks

  • Запустить:
    cargo fmt --all -- --check
    cargo test
    cargo clippy --all-targets --all-features
    cargo package --allow-dirty
  • Ожидаемый результат:
    • форматирование без diff;
    • все unit/integration/doc tests проходят;
    • clippy без warning/error;
    • crate package собирается и верифицируется.

Functional Scenarios

  • Stop hook с <proposed_plan>:

    • команда: plan-to-git hook --source codex;
    • stdin: Codex JSON с hook_event_name: "Stop" и last_assistant_message;
    • ожидаемо: .agent-plan.json содержит plan item, stdout пустой.
  • Stop hook с вопросом агента:

    • stdin: Codex JSON с last_assistant_message вида Should ...?;
    • ожидаемо: вопрос сохраняется в pending_questions, stdout пустой.
  • UserPromptSubmit hook с ответом пользователя:

    • stdin: Codex JSON с hook_event_name: "UserPromptSubmit" и prompt;
    • ожидаемо: pending question превращается в Planning Decision, pending_questions очищается.
  • PR sync при открытом PR:

    • gh pr view находит PR текущей ветки;
    • plan-to-git обновляет PR body через gh api PATCH;
    • ожидаемо: PR содержит блок &lt;!-- plan-to-git:start --&gt; ... &lt;!-- plan-to-git:end --&gt;.
  • PR sync без открытого PR:

    • gh pr view возвращает no pull requests found;
    • ожидаемо: команда завершается успешно, stack остаётся локально, PR не обновляется.

Regression And Safety Cases

  • Unmarked raw transcript не должен сохраняться как план.
  • Секреты/token/API key-like строки должны редактироваться перед записью.
  • Повторный одинаковый plan hook не должен создавать duplicate item.
  • Если PR body содержит только один marker, sync должен fail, не перезаписывая описание.
  • При наличии items из другой ветки render/sync должен публиковать только items текущей ветки.
  • Hook-команда не должна писать в stdout; любые diagnostic logs только в stderr.

Real PR Acceptance Criteria

  • В PR feat: add plan-to-git CLI #3 body есть:
    • Real PR Dogfood Plan;
    • вопрос Should agent questions...;
    • ответ пользователя upload both...;
    • marker block plan-to-git:start/end.
  • Проверка:
    gh pr view 3 --json body --jq '.body'
  • Рабочее дерево после проверки не содержит новых tracked изменений; untracked .playwright-mcp/ не относится к тесту и не добавляется в commit.

Assumptions

  • Тестируем Codex-first MVP; Claude hooks не входят в этот тестовый проход.
  • Реальная GitHub-проверка выполняется через PR feat: add plan-to-git CLI #3.
  • Для локальных integration tests используется fake git/gh, чтобы не мутировать реальные PR.

3. Plan

Source: codex - Captured: 2026-05-31T13:55:36.095Z

Hook Config Live Test

  • Verify Codex Stop hook invokes plan-to-git automatically.

4. Plan

Source: codex - Captured: 2026-05-31T13:55:54.180Z

Hook JSON Test

  • test

5. Plan

Source: codex - Captured: 2026-05-31T13:56:48.365Z

Hooks JSON Live Test

  • Verify Codex Stop hook invokes plan-to-git from hooks.json.

@skulidropek
Copy link
Copy Markdown
Member Author

Agent Plan Update

Branch: issue-2 at 002a9f9.

1. Plan

Source: codex - Captured: 2026-05-31T19:55:23Z

Demo PR Comment Plan

  • This demo plan was captured from a Codex-style hook payload.
  • plan-to-git should post it as a new PR comment, not edit the PR description.

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: ab2af39
Status: success
Files: 13 (5.46 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 7cb2131
Status: success
Files: 14 (5.93 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
src/capture.rs (1)

91-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only answer pending questions from the active branch/session.

drain_relevant_questions() empties the entire repo-wide queue, so a prompt on branch B can consume and clear questions that were captured on branch A. Filter by the current branch/session before draining, otherwise decisions get attached to unrelated questions.

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

In `@src/capture.rs` around lines 91 - 92, drain_relevant_questions currently
empties the global state.pending_questions which lets questions from other
branches/sessions be consumed; update the logic so only questions belonging to
the active branch/session are drained and answered. Specifically, before calling
drain_relevant_questions or within it, filter state.pending_questions by the
current branch/session id (the same identifier used when capturing questions)
and remove only those matching entries (e.g., collect matching questions into a
temp Vec and retain the rest in state.pending_questions), then pass that
filtered set to state.answer_pending_questions/NewDecision so decisions are
attached only to questions from the active branch/session.
src/store.rs (1)

250-255: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope pending-question dedupe by branch/session.

This hash only uses the question text, so the same question asked on another branch collapses into the existing entry and never gets recorded for that branch. That breaks the later Q/A pairing flow because the second branch no longer has its own pending question to answer.

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

In `@src/store.rs` around lines 250 - 255, The dedupe currently only uses
question_hash = stable_hash(&questions.join("\n")) and then checks
pending_questions by comparing item_id("question", &question_hash), which
collapses identical questions across branches/sessions; include branch or
session scope in the identity used for dedupe by incorporating the
branch/session identifier into the hash (e.g. combine branch_id or session_id
when computing question_hash) or by updating the pending_questions predicate to
also compare the question's branch/session field alongside question.id
(referencing question_hash, pending_questions, stable_hash, and item_id to
locate the logic).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/capture.rs`:
- Around line 114-117: The GitHub comment creation in
github::sync_state(&context, &mut state) is non-idempotent because
posted_comments can be modified only after the external API call; to fix, make
the persist-before-external-write: mark/append the items as "posted" in
state.posted_comments (or set a pending flag) and call save_state(&state_path,
&state) before calling github::sync_state, then perform the API call and, on
success, clear any transient flags; alternatively implement an idempotency check
inside github::sync_state that queries GitHub for an existing comment (by PR +
unique token) before creating a new one and only create if absent, and ensure
save_state is retried/checked so posted_comments is durably stored.

In `@src/store.rs`:
- Around line 224-229: The matches_current_branch method currently treats all
items as matching when self.branch is None, which can cause unrelated
branch-scoped PlanStackItem entries to be processed; change
matches_current_branch (in src/store.rs) to only return true when both branches
are Some and equal OR when item.branch is None (unscoped) — i.e., if self.branch
is None, do not match items with Some(item_branch); additionally, update
sync_state to either skip syncing when self.branch is None or ensure it relies
on the tightened matches_current_branch behavior to avoid posting branch-scoped
items to the wrong PR.

In `@tests/unit/plan_capture.rs`:
- Around line 287-300: The test pr_body_replaces_through_last_marker currently
has one START_MARKER and two END_MARKER tokens which doesn't exercise multiple
complete marker sections; update the fixture so there are two full marker blocks
(i.e., two START_MARKER ... END_MARKER pairs) before the “stale” text, then call
upsert_marker_block(&original, &block) as before and assert the old contents
from both blocks are removed and replaced by the new block; locate the test
function pr_body_replaces_through_last_marker and modify the original string to
include two complete marker sections to match the test name and intended
scenario.

---

Outside diff comments:
In `@src/capture.rs`:
- Around line 91-92: drain_relevant_questions currently empties the global
state.pending_questions which lets questions from other branches/sessions be
consumed; update the logic so only questions belonging to the active
branch/session are drained and answered. Specifically, before calling
drain_relevant_questions or within it, filter state.pending_questions by the
current branch/session id (the same identifier used when capturing questions)
and remove only those matching entries (e.g., collect matching questions into a
temp Vec and retain the rest in state.pending_questions), then pass that
filtered set to state.answer_pending_questions/NewDecision so decisions are
attached only to questions from the active branch/session.

In `@src/store.rs`:
- Around line 250-255: The dedupe currently only uses question_hash =
stable_hash(&questions.join("\n")) and then checks pending_questions by
comparing item_id("question", &question_hash), which collapses identical
questions across branches/sessions; include branch or session scope in the
identity used for dedupe by incorporating the branch/session identifier into the
hash (e.g. combine branch_id or session_id when computing question_hash) or by
updating the pending_questions predicate to also compare the question's
branch/session field alongside question.id (referencing question_hash,
pending_questions, stable_hash, and item_id to locate the logic).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d29013ce-9686-4bce-9d86-95d89a3bf830

📥 Commits

Reviewing files that changed from the base of the PR and between 94587af and ab2af39.

📒 Files selected for processing (12)
  • README.md
  • changelog.d/20260530_104500_plan_to_git_mvp.md
  • src/capture.rs
  • src/codex_history.rs
  • src/github.rs
  • src/main.rs
  • src/normalize.rs
  • src/pr_body.rs
  • src/render.rs
  • src/store.rs
  • tests/integration/cli.rs
  • tests/unit/plan_capture.rs
✅ Files skipped from review due to trivial changes (2)
  • changelog.d/20260530_104500_plan_to_git_mvp.md
  • README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main.rs
  • src/codex_history.rs
  • src/normalize.rs

Comment thread src/capture.rs
Comment on lines +114 to +117
let sync_status = github::sync_state(&context, &mut state)?;
if changed || !state.items.is_empty() || !state.pending_questions.is_empty() {
save_state(&state_path, &state)?;
}
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 | 🏗️ Heavy lift

This comment-post path is still non-idempotent across save failures.

sync_state() can create the GitHub comment before posted_comments is persisted. If the process exits or save_state() fails after the API call, the next run will see the same items as unposted and create a duplicate PR comment. This needs a durable/idempotent guard around the external write.

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

In `@src/capture.rs` around lines 114 - 117, The GitHub comment creation in
github::sync_state(&context, &mut state) is non-idempotent because
posted_comments can be modified only after the external API call; to fix, make
the persist-before-external-write: mark/append the items as "posted" in
state.posted_comments (or set a pending flag) and call save_state(&state_path,
&state) before calling github::sync_state, then perform the API call and, on
success, clear any transient flags; alternatively implement an idempotency check
inside github::sync_state that queries GitHub for an existing comment (by PR +
unique token) before creating a new one and only create if absent, and ensure
save_state is retried/checked so posted_comments is durably stored.

Comment thread src/store.rs
Comment on lines +224 to +229
fn matches_current_branch(&self, item: &PlanStackItem) -> bool {
match (item.branch.as_deref(), self.branch.as_deref()) {
(Some(item_branch), Some(branch)) => item_branch == branch,
(Some(_), None) | (None, _) => true,
}
}
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 | ⚡ Quick win

Fail closed when the current branch is unknown.

When self.branch is None (detached HEAD, shallow CI checkout, etc.), this treats every stored item as current. sync_state() will then consider unrelated branch items eligible and can post them to the wrong PR. Missing branch context should only match unscoped items, or skip sync entirely.

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

In `@src/store.rs` around lines 224 - 229, The matches_current_branch method
currently treats all items as matching when self.branch is None, which can cause
unrelated branch-scoped PlanStackItem entries to be processed; change
matches_current_branch (in src/store.rs) to only return true when both branches
are Some and equal OR when item.branch is None (unscoped) — i.e., if self.branch
is None, do not match items with Some(item_branch); additionally, update
sync_state to either skip syncing when self.branch is None or ensure it relies
on the tightened matches_current_branch behavior to avoid posting branch-scoped
items to the wrong PR.

Comment on lines +287 to +300
#[test]
fn pr_body_replaces_through_last_marker() {
let original =
format!("Intro\n\n{START_MARKER}\nold\n{END_MARKER}\nstale\n{END_MARKER}\nOutro");
let block = format!("{START_MARKER}\nnew\n{END_MARKER}");

let replaced = upsert_marker_block(&original, &block).expect("replace should work");

assert!(replaced.contains("Intro"));
assert!(replaced.contains("new"));
assert!(replaced.contains("Outro"));
assert!(!replaced.contains("old"));
assert!(!replaced.contains("stale"));
}
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 | 🟡 Minor | ⚡ Quick win

Make this fixture match the scenario named by the test.

This body has one START_MARKER and two END_MARKERs, so it does not actually cover “multiple marker sections.” As written, it can pass while upsert_marker_block still mishandles two complete marker blocks.

Suggested test fixture
 fn pr_body_replaces_through_last_marker() {
-    let original =
-        format!("Intro\n\n{START_MARKER}\nold\n{END_MARKER}\nstale\n{END_MARKER}\nOutro");
+    let original = format!(
+        "Intro\n\n{START_MARKER}\nold-1\n{END_MARKER}\nstale\n{START_MARKER}\nold-2\n{END_MARKER}\nOutro"
+    );
     let block = format!("{START_MARKER}\nnew\n{END_MARKER}");
 
     let replaced = upsert_marker_block(&original, &block).expect("replace should work");
 
     assert!(replaced.contains("Intro"));
     assert!(replaced.contains("new"));
     assert!(replaced.contains("Outro"));
-    assert!(!replaced.contains("old"));
+    assert!(!replaced.contains("old-1"));
+    assert!(!replaced.contains("old-2"));
     assert!(!replaced.contains("stale"));
 }
📝 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
#[test]
fn pr_body_replaces_through_last_marker() {
let original =
format!("Intro\n\n{START_MARKER}\nold\n{END_MARKER}\nstale\n{END_MARKER}\nOutro");
let block = format!("{START_MARKER}\nnew\n{END_MARKER}");
let replaced = upsert_marker_block(&original, &block).expect("replace should work");
assert!(replaced.contains("Intro"));
assert!(replaced.contains("new"));
assert!(replaced.contains("Outro"));
assert!(!replaced.contains("old"));
assert!(!replaced.contains("stale"));
}
#[test]
fn pr_body_replaces_through_last_marker() {
let original = format!(
"Intro\n\n{START_MARKER}\nold-1\n{END_MARKER}\nstale\n{START_MARKER}\nold-2\n{END_MARKER}\nOutro"
);
let block = format!("{START_MARKER}\nnew\n{END_MARKER}");
let replaced = upsert_marker_block(&original, &block).expect("replace should work");
assert!(replaced.contains("Intro"));
assert!(replaced.contains("new"));
assert!(replaced.contains("Outro"));
assert!(!replaced.contains("old-1"));
assert!(!replaced.contains("old-2"));
assert!(!replaced.contains("stale"));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/plan_capture.rs` around lines 287 - 300, The test
pr_body_replaces_through_last_marker currently has one START_MARKER and two
END_MARKER tokens which doesn't exercise multiple complete marker sections;
update the fixture so there are two full marker blocks (i.e., two START_MARKER
... END_MARKER pairs) before the “stale” text, then call
upsert_marker_block(&original, &block) as before and assert the old contents
from both blocks are removed and replaced by the new block; locate the test
function pr_body_replaces_through_last_marker and modify the original string to
include two complete marker sections to match the test name and intended
scenario.

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 85b5b9e
Status: success
Files: 14 (5.99 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 0314192
Status: success
Files: 16 (7.29 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

@skulidropek
Copy link
Copy Markdown
Member Author

skulidropek commented May 31, 2026

AI Session Backup

Commit: 7dce5c5
Status: success
Files: 16 (7.46 MB)
Links: README | Manifest

git status

On branch issue-2
Your branch is up to date with 'origin/issue-2'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.playwright-mcp/

nothing added to commit but untracked files present (use "git add" to track)

@skulidropek skulidropek merged commit 11f25a5 into main May 31, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant