Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions project/TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,4 +441,5 @@ project analysis or human-owned ticket inputs.
| **ticket-441** | [`README.md`](./ticket-441/README.md) | - | - | - | - | - |
| **ticket-442** | [`README.md`](./ticket-442/README.md) | - | - | - | - | - |
| **ticket-443** | [`README.md`](./ticket-443/README.md) | - | - | - | - | - |
| **ticket-445** | [`README.md`](./ticket-445/README.md) | - | - | - | - | - |
<!-- AUTO:TICKET_INDEX:END -->
19 changes: 19 additions & 0 deletions project/ticket-447/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Ticket 447: Support force staging and add returncode check for verified patch paths in commit_worktree

- **ID**: ticket-447
- **Owner**: gemini
- **Status**: IN_PROGRESS
- **Workflow state**: PUBLICATION
- **Created**: 2026-10-11

SESSION_EXECUTION_AUTHORIZATION: user requests continuation, test and merge. Koru verified patch staging must handle paths matched by broad repository gitignore and check staging exit codes before committing.

## Goal and scope

In `src/koru/queue/workspace.py`, when committing verified patch paths on a run branch or main, use force staging (`git add -f -- <paths>`) for explicitly passed patch paths and check command returncode so that files ignored by broad target `.gitignore` rules (e.g. `.planfile/`) can be committed properly to the run branch, and failures in `git add` are cleanly reported rather than failing ambiguously during `git commit`.

## Acceptance criteria

- [x] AC-01: `commit_worktree` uses `git add -f -- <paths>` when explicit paths are supplied and checks returncode before invoking `git commit`.
- [x] AC-02: `commit_on_main` uses `git add -f -- <paths>` when explicit paths are supplied.
- [x] AC-03: Regression tests verify that verified patch paths ignored by `.gitignore` are staged and committed successfully to the run branch.
75 changes: 75 additions & 0 deletions project/ticket-447/intent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"schema": "new-project.intent/v3",
"ticket": "ticket-447",
"summary": "Support force staging and add returncode check for verified patch paths in commit_worktree",
"workstream": "application",
"classification": {
"kind": "BUG",
"priority": "P2",
"origin": "health"
},
"allowedPaths": [
"project/ticket-447/**",
"TODO.md",
"project/TICKETS.md",
"src/koru/queue/workspace.py",
"tests/test_queue_workspace_retention.py"
],
"forbiddenPaths": [
"project/ticket-*/user-*.md"
],
"stacks": [],
"dependsOn": [],
"conflictsWith": [],
"integrationTicket": null,
"delivery": {
"acceptedBaseSha": "e960b60e6316e557fb7a0034b9fef48317d2de09",
"targetBranch": "main",
"outcome": "Ensure explicit verified patch paths are staged with -f in commit_worktree and commit_on_main, and verify staging exit code before commit.",
"nonGoals": [
"No changes to admission checks or governance rules",
"No changes to promotion modes or verification pipeline"
],
"complexity": "S",
"estimatedMinutes": 30,
"budgets": {
"maxImplementationFiles": 2,
"maxAffectedComponents": 1,
"maxPublicInterfaceChanges": 0,
"maxRuntimeDependencies": 0
},
"architecture": {
"status": "accepted",
"decision": "Use -f flag when staging explicit verified patch paths in commit_worktree and commit_on_main, and verify command exit code before commit.",
"components": [
{
"name": "queue-workspace",
"paths": [
"src/koru/queue/workspace.py",
"tests/test_queue_workspace_retention.py"
]
}
],
"responsibilityChanges": false,
"interfaceChanges": [],
"dataChanges": [],
"ui": {
"impact": "none",
"states": [],
"evidence": []
},
"rollback": "Revert the commit on ticket-447."
},
"runtimeDependencies": [],
"validation": [
{
"criterion": "AC-01",
"commands": [
"pytest tests/test_queue_workspace_retention.py",
"./project/governance-check.sh"
],
"evidence": "Regression test verifying that verified patch paths matched by .gitignore rules are staged and committed properly."
}
]
}
}
9 changes: 7 additions & 2 deletions src/koru/queue/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def commit_on_main(
) -> tuple[bool, str]:
"""Commit only the patch's files on the current branch."""
if paths:
staged = _git(project, "add", "--", *paths)
staged = _git(project, "add", "-f", "--", *paths)
else:
staged = _git(project, "add", "-A")
if staged.returncode != 0:
Expand All @@ -227,7 +227,12 @@ def commit_worktree(
switched = _git(worktree, "switch", "--quiet", "-c", branch)
if switched.returncode != 0:
return False, (switched.stderr or "").strip()[:300]
_git(worktree, "add", "--", *paths) if paths else _git(worktree, "add", "-A")
if paths:
staged = _git(worktree, "add", "-f", "--", *paths)
else:
staged = _git(worktree, "add", "-A")
if staged.returncode != 0:
return False, (staged.stderr or staged.stdout or "").strip()[:300]
committed = _git(worktree, "commit", "--quiet", "-m", message)
if committed.returncode != 0:
return False, (committed.stderr or committed.stdout or "").strip()[:300]
Expand Down
26 changes: 24 additions & 2 deletions tests/test_queue_workspace_retention.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ def concurrent_removal(project: Path, *args: str, **kwargs):

@pytest.mark.parametrize("linked", [False, True])
def test_governed_staging_requires_controller(repo: Path, linked: bool) -> None:
(repo / ".governance").mkdir()
gov = repo / ".governance"
gov.mkdir()
(gov / "manifest.json").write_text("{}")
root = repo
if linked:
root = repo.parent / "existing-linked"
Expand All @@ -145,7 +147,9 @@ def test_governed_staging_requires_controller(repo: Path, linked: bool) -> None:

@pytest.mark.parametrize("mode", ["branch", "apply"])
def test_governed_refusal_does_not_run_verifier_or_apply(repo: Path, mode: str) -> None:
(repo / ".governance").mkdir()
gov = repo / ".governance"
gov.mkdir()
(gov / "manifest.json").write_text("{}")
diff = "diff --git a/a.txt b/a.txt\n--- a/a.txt\n+++ b/a.txt\n@@ -1 +1 @@\n-old\n+new\n"
plan = build_patch_plan(repo, {"id": "T-1", "inputs": {"promotion_mode": mode}}, diff)
runner = Mock(side_effect=AssertionError("verifier must not run"))
Expand Down Expand Up @@ -183,3 +187,21 @@ def test_git_override_is_refused_before_any_git_effect(repo: Path, monkeypatch:
with pytest.raises(StagingAdmissionRequired, match="overrides"):
with staging_worktree(repo, ()):
pytest.fail("redirected workspace admitted")


def test_commit_worktree_stages_explicit_paths_even_if_ignored(repo: Path) -> None:
_repolab.commit_file(repo, ".gitignore", ".planfile/\n")
with staging_worktree(repo, ()) as staged:
assert staged is not None
planfile_file = staged / ".planfile" / "sync" / "state.yaml"
planfile_file.parent.mkdir(parents=True)
planfile_file.write_text("version: 1\n")
ok, detail = workspace.commit_worktree(
staged,
"koru/run-test-ignored",
"koru(T-1): commit test",
(".planfile/sync/state.yaml",),
)
assert ok, f"commit_worktree failed: {detail}"
committed_content = git(staged, "show", "koru/run-test-ignored:.planfile/sync/state.yaml")
assert committed_content.strip() == "version: 1"
Loading