Skip to content

feat: epos integration#5

Merged
php-workx merged 14 commits into
mainfrom
feat/epso-integration
May 14, 2026
Merged

feat: epos integration#5
php-workx merged 14 commits into
mainfrom
feat/epso-integration

Conversation

@php-workx
Copy link
Copy Markdown
Owner

@php-workx php-workx commented May 8, 2026

Summary by CodeRabbit

  • New Features

    • Added repository-wide validation to reject unsafe ticket and run identifiers before any filesystem or artifact access.
    • Integrated a new ticket-store backend and improved claim lifecycle (acquire/renew/release/reconcile) with stronger lease-fence semantics.
    • More deterministic Git subprocess behavior for stable CLI/test runs.
  • Bug Fixes

    • Corrected diff handling for empty/binary untracked files.
    • Stricter validation for pending integration transactions (missing refs rejected).
  • Chores

    • Bumped Go toolchain and dependencies; added new runtime/dependency support for the ticket adapter.

Review Change Stack

@php-workx php-workx self-assigned this May 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Introduces a new EPOS ticket/claims adapter and migrates engine, CLI, and tests from TKMD to EPOS. Adds strict identifier/worktree validation, hardens git subprocess environment, updates diff handling, bumps dependencies, and removes TKMD code. Documentation and build scripts are updated accordingly.

Changes

EPOS adapter migration and safety hardening

Layer / File(s) Summary
EPOS cutover with validation and tests
internal/adapters/ticketstore/epos/*, internal/engine/*, internal/cli/*, internal/adapters/repo/git/*, pkg/verk/types.go, go.mod, docs/*, scripts/*, justfile, cmd/verk/main_test.go, internal/e2e/*, internal/adapters/verify/command/*, and deletions under internal/adapters/ticketstore/tkmd/*
Implements EPOS ticket/claim adapter (load/save, children/ready, claims, live-state), migrates engine/CLI to EPOS, adds artifact/worktree ID validation and git env hardening, adjusts diff for binaries, updates docs and build scripts, and removes TKMD code with comprehensive tests.

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: 15

Caution

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

⚠️ Outside diff range comments (2)
internal/adapters/repo/git/repo.go (1)

113-117: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't silently fall back to the linked worktree root.

If --path-format=absolute isn't supported (Git < 2.31.0), returning r.root makes CreateWorktree anchor new worktrees under a linked checkout instead of the main worktree. That reintroduces the nested-worktree case this PR is explicitly trying to avoid.

🛠️ Safer fallback
 	commonDir, err := gitOutput(r.root, "rev-parse", "--path-format=absolute", "--git-common-dir")
 	if err != nil {
-		// Fallback: if git doesn't support --path-format, use the current root
-		return r.root, nil //nolint:nilerr // intentional fallback — error handled by returning root
+		commonDir, err = gitOutput(r.root, "rev-parse", "--git-common-dir")
+		if err != nil {
+			return "", err
+		}
 	}
 	commonDir = filepath.Clean(strings.TrimRight(commonDir, "\r\n"))
+	if !filepath.IsAbs(commonDir) {
+		commonDir = filepath.Join(r.root, commonDir)
+	}
🤖 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 `@internal/adapters/repo/git/repo.go` around lines 113 - 117, Don't silently
fall back to r.root when git doesn't support --path-format; instead call
gitOutput(r.root, "rev-parse", "--git-common-dir") to get the common dir, then
if that output is a relative path resolve it against r.root (e.g.,
filepath.Join(r.root, commonDir)) to produce an absolute path, return that
absolute commonDir, and only return an error if gitOutput fails — update the
logic around the commonDir variable where gitOutput(...
"--path-format=absolute", "--git-common-dir") is used so CreateWorktree doesn't
get anchored to r.root.
internal/engine/epic_gate.go (1)

577-620: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't opt epic closure checks out of the isolated subprocess env.

Both verify-runner calls pass "" for the new worktree/state hint. That disables the cache-root isolation added in this PR, so epic closure and derived checks will still share host/global caches instead of running under the worktree-scoped .verk/process-cache.

Suggested fix
-		results, err := verifycommand.RunQualityCommands(ctx, repoRoot, "", cfg.EpicClosureCommands, cfg)
+		results, err := verifycommand.RunQualityCommands(ctx, repoRoot, repoRoot, cfg.EpicClosureCommands, cfg)
@@
-			results, err := verifycommand.RunCommands(ctx, repoRoot, "", commands, cfg)
+			results, err := verifycommand.RunCommands(ctx, repoRoot, repoRoot, commands, cfg)

Based on learnings: If a task touches worktree behavior, preserve the cache-root semantics described in README.md.

🤖 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 `@internal/engine/epic_gate.go` around lines 577 - 620, In executeEpicChecks,
don't pass the empty string as the worktree/state hint to
verifycommand.RunQualityCommands and verifycommand.RunCommands because that
opt-outs of the new cache-root isolation; update both calls
(verifycommand.RunQualityCommands and verifycommand.RunCommands) to pass the
appropriate worktree hint (e.g., repoRoot or the worktree-scoped hint you use
elsewhere) instead of "" so epic closure commands and derived checks run under
the worktree-scoped cache root.
🧹 Nitpick comments (8)
internal/engine/closeout.go (1)

272-313: ⚡ Quick win

Improve type-mismatch diagnostics in variadic closeout parsing.

parseCloseoutRequest(args ...any) silently drops unsupported types, so a stale caller can fail with only closeout requires ticket metadata, which is hard to debug during adapter migration. Including received arg types in that error would make failures immediately actionable.

Proposed patch
 func parseCloseoutRequest(args ...any) (closeoutRequest, error) {
 	var req closeoutRequest
+	gotTypes := make([]string, 0, len(args))
 
 	for _, arg := range args {
+		gotTypes = append(gotTypes, fmt.Sprintf("%T", arg))
 		switch v := arg.(type) {
 		case epos.Ticket:
 			req.ticket = v
 		case *epos.Ticket:
 			if v != nil {
 				req.ticket = *v
 			}
@@
 	}
 
 	if req.ticket.ID == "" {
-		return closeoutRequest{}, fmt.Errorf("closeout requires ticket metadata")
+		return closeoutRequest{}, fmt.Errorf(
+			"closeout requires ticket metadata; received arg types: %s",
+			strings.Join(gotTypes, ", "),
+		)
 	}
🤖 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 `@internal/engine/closeout.go` around lines 272 - 313, The parseCloseoutRequest
variadic parser currently ignores unsupported argument types, leading to
unhelpful "closeout requires ticket metadata" errors; update
parseCloseoutRequest to collect the runtime types of all args (e.g., via
fmt.Sprintf("%T", arg) or reflect.TypeOf) and include a concise summary of
received argument types in the returned error when req.ticket.ID == "" (and/or
include unsupported types encountered), referencing parseCloseoutRequest,
closeoutRequest and ticket.ID so callers can immediately see what types were
passed during adapter migrations.
docs/plans/worker-isolation.md (1)

349-367: Add the repo-standard verification targets here.

This checklist should call out just pre-commit and just test-race so the documented validation path matches the repo's required gates for engine/worktree/orchestration changes.

Based on learnings: Use the repo's just targets instead of ad hoc command bundles; run at least just pre-commit before engine changes, and run just test-race if the change affects orchestration, isolation, or cleanup 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 `@docs/plans/worker-isolation.md` around lines 349 - 367, Update the
"Verification" checklist to include the repo-standard Just targets by adding
explicit steps to run `just pre-commit` (before engine/worktree changes) and
`just test-race` (when changes affect orchestration, isolation, or cleanup
semantics); modify the Verification section (the numbered checklist under the
"Verification" header) to insert these two targets as required gates alongside
the existing unit/integration/resume tests so the documented validation path
matches repo requirements.
internal/adapters/ticketstore/epos/claims.go (1)

653-655: 💤 Low value

Heuristic for distinguishing lease ID from release reason may be fragile.

The looksLikeLeaseID function checks for "lease-" or "fence-" prefixes. If a release reason accidentally starts with these prefixes (e.g., "lease expired by timeout"), it would be misclassified as a lease ID, causing validation to fail.

Consider documenting this constraint or using a more robust disambiguation strategy (e.g., always requiring explicit argument positions when both are needed).

🤖 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 `@internal/adapters/ticketstore/epos/claims.go` around lines 653 - 655, The
heuristic in looksLikeLeaseID is too loose and can misclassify freeform release
reasons that start with "lease-" or "fence-"; update looksLikeLeaseID to use a
stricter pattern (e.g., a regex that requires the expected suffix format like
lease-[0-9a-fA-F]+ or a UUID pattern for fence-) or change call sites to require
an explicit positional flag instead of guessing; also add a short
comment/docstring on looksLikeLeaseID explaining the accepted lease/fence
formats so future callers know the constraint.
internal/adapters/ticketstore/epos/convert.go (1)

300-311: 💤 Low value

Consider extending integer type coverage in asInt.

The function handles int, int64, and float64, but silently returns 0 for other numeric types like int32, uint, uint64. If YAML unmarshaling or external sources provide these types, valid integers could be lost.

♻️ Optional: extend numeric type handling
 func asInt(v any) int {
 	switch t := v.(type) {
 	case int:
 		return t
 	case int64:
 		return int(t)
+	case int32:
+		return int(t)
+	case uint:
+		return int(t)
+	case uint64:
+		return int(t)
 	case float64:
 		return int(t)
 	default:
 		return 0
 	}
 }
🤖 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 `@internal/adapters/ticketstore/epos/convert.go` around lines 300 - 311, The
asInt function currently only handles int, int64 and float64 and returns 0 for
other numeric types; update asInt to handle additional numeric types (e.g.,
int8, int16, int32, uint, uint8, uint16, uint32, uint64) by adding type switch
cases that convert them to int safely (casting where appropriate) and optionally
handle json.Number or string numeric input by parsing to int; ensure conversions
for unsigned types check for overflow if needed and keep the function name asInt
to locate the change.
docs/epos-integration.md (1)

218-230: ⚡ Quick win

Align the execution plan with the repo’s just gates.

The phase and verification sections still center ad hoc go build ./... / go test ./... commands, but this change touches engine/worktree/isolation behavior. The handoff should explicitly call out just pre-commit and just test-race, with just check as the final gate, so the plan matches the repo’s expected validation path. Based on learnings: Use the repo's just targets instead of ad hoc command bundles, and for worktree/isolation changes run at least just pre-commit and just test-race.

Also applies to: 387-399, 424-425

🤖 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 `@docs/epos-integration.md` around lines 218 - 230, Update the execution and
verification steps in the "Phase 1 — Pre-flight + dependency add" section (and
the other referenced blocks) to use the repository's just targets instead of ad
hoc go commands: replace step 5's "go mod tidy && go build ./..." and the
"**Exit:** go build ./... + go test ./... green" with explicit calls to "just
pre-commit" and "just test-race" during worktree/isolation validation and use
"just check" as the final gate; apply the same replacement to the other affected
blocks (the later sections noted in the comment) so all verification steps
consistently call just pre-commit, just test-race, and just check.
internal/engine/wave_commands_test.go (1)

24-24: ⚡ Quick win

Add a test that exercises non-empty workDir behavior.

Line 24 updates the signature but still validates only the empty/default path. Please add one case that passes an explicit workDir and asserts outputs are written there (not repo root), so the new argument is actually covered.

✅ Suggested test addition
 func TestRunWaveVerificationLoop_RunsWaveCommandsInAdditionToQualityCommands(t *testing.T) {
@@
 	err := runWaveVerificationLoop(context.Background(), makeEpicReq(repoRoot, adapter), cfg, wave, wavePath, nil, "")
@@
 }
+
+func TestRunWaveVerificationLoop_UsesExplicitWorkDir(t *testing.T) {
+	repoRoot, wavePath, wave := makeWaveVerifyFixture(t)
+	workDir := filepath.Join(repoRoot, "wt")
+	if err := os.MkdirAll(workDir, 0o755); err != nil {
+		t.Fatalf("mkdir workDir: %v", err)
+	}
+	adapter := runtimefake.New(nil, nil)
+	cfg := policy.DefaultConfig()
+	cfg.Verification.QualityCommands = []policy.QualityCommand{
+		{Path: ".", Run: []string{"printf scoped >> wave-checks.log"}},
+	}
+
+	err := runWaveVerificationLoop(context.Background(), makeEpicReq(repoRoot, adapter), cfg, wave, wavePath, nil, workDir)
+	if err != nil {
+		t.Fatalf("expected nil error, got: %v", err)
+	}
+	if _, err := os.Stat(filepath.Join(repoRoot, "wave-checks.log")); !os.IsNotExist(err) {
+		t.Fatalf("expected no repo-root output file when workDir is set, got err=%v", err)
+	}
+	content, err := os.ReadFile(filepath.Join(workDir, "wave-checks.log"))
+	if err != nil {
+		t.Fatalf("read workDir wave checks log: %v", err)
+	}
+	if got, want := string(content), "scoped"; got != want {
+		t.Fatalf("expected workDir output %q, got %q", want, got)
+	}
+}

Based on learnings: "If a task touches worktree behavior, preserve the cache-root semantics described in README.md."

🤖 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 `@internal/engine/wave_commands_test.go` at line 24, The test currently calls
runWaveVerificationLoop only with an empty/default workDir; add a new subtest in
wave_commands_test.go that calls runWaveVerificationLoop (via
makeEpicReq(repoRoot, adapter) or similar) but passes a non-empty workDir (e.g.,
a temp dir) and then asserts that outputs/artifacts (the same files the existing
test checks) are created under that workDir and not under repoRoot; ensure you
clean up the temp dir and preserve the cache-root semantics described in README
when constructing the expected paths so the new argument is actually covered.
internal/adapters/verify/command/runner.go (1)

181-185: ⚡ Quick win

Update the QualityCommand.Path contract comment.

The implementation now resolves qc.Path from workDir, not directly from repoRoot. Leaving the old comment here will send future callers and config changes at the wrong base directory once verification runs inside a worktree.

🤖 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 `@internal/adapters/verify/command/runner.go` around lines 181 - 185, Update
the comment for QualityCommand.Path to reflect that paths are resolved relative
to workDir (the current working tree used by RunQualityCommands) rather than
repoRoot; change the contract text near the QualityCommand.Path declaration and
any related docblock in runner.go to state that qc.Path is interpreted relative
to workDir so callers/configs use the worktree base when verification runs
inside a worktree.
internal/engine/wave_verify.go (1)

31-205: Please gate this flow with the repo's race coverage.

This PR changes worktree isolation, wave verification resume, and pending integration cleanup semantics. It is worth running the repo’s engine/worktree checks with just pre-commit and just test-race before merge.

Based on learnings: Run at least just pre-commit before engine changes, and run just test-race if the change affects orchestration, isolation, or cleanup 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 `@internal/engine/wave_verify.go` around lines 31 - 205, Add a guard at the
start of runWaveVerificationLoop to run the repo's race/engine checks and abort
if they fail: call a helper like ensureRepoRaceCoverage(ctx, req.RepoRoot) (or
implement verifyPreCommitAndRace that runs the equivalent of `just pre-commit`
and `just test-race`) before computing plan := waveDerivationPlan(...); if that
helper returns an error, return it (wrap with context like "race coverage checks
failed") so wave verification is gated by the repo's race/engine checks.
🤖 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 `@AGENTS.md`:
- Around line 37-42: The "Useful focused runs" block conflicts with the repo
rule to use the project's just targets; either remove the ad hoc go test lines
or mark them explicitly as exceptions and explain why (e.g., for quick local
troubleshooting) and link or reference the canonical just targets (the "just"
targets / rule) so readers know the preferred commands; update the AGENTS.md
section title or a one-line note so it clearly resolves the contradiction.

In `@docs/epos-integration.md`:
- Around line 5-6: The handoff document contains hardcoded workstation-specific
absolute paths (e.g., /Users/runger/workspaces/...) which break portability;
update the occurrences (including the ones around lines referenced) to use
repo-relative paths or explicit placeholders (e.g., $VERK_ROOT or $EPOS_ROOT)
and update any example commands to reference these placeholders or relative
paths instead of absolute user directories so CI and other developers can run
them reliably.

In `@docs/plans/2026-04-21-ticket-quality-gate.md`:
- Around line 232-233: The plan mixes old `tkmd` names with the new `epos`
adapter: update every occurrence of `tkmd.Ticket`, `tkmd.LoadTicket`,
`tkmd.SaveTicket`, and `tkmd.ListAllChildren` (and any other `tkmd.*`
references) to the corresponding `epos` names (e.g., `epos.Ticket`,
`epos.LoadTicket`, `epos.SaveTicket`, `epos.ListAllChildren`) so the task
descriptions, instructions, and reuse lines consistently reference `epos`
throughout the document; search for `tkmd.` tokens in the plan and replace them
with `epos.` and verify the two reuse bullets already pointing to
`internal/adapters/ticketstore/epos/*` align with the updated symbols.

In `@internal/adapters/runtime/cacheenv.go`:
- Around line 81-93: The function resolveProcessStateRoot should not create a
new .verk directory when filepath.EvalSymlinks(verkPath) fails because that
masks worktree mappings; instead fail fast. In resolveProcessStateRoot, remove
or bypass the os.MkdirAll(verkPath, 0o755) fallback and return a wrapped error
indicating that resolving verkPath (variable verkPath) failed; keep the initial
Abs/TrimSpace handling and the successful EvalSymlinks branch unchanged so
callers can detect unresolved worktree cases and handle canonical state-root
resolution.

In `@internal/adapters/ticketstore/epos/live.go`:
- Around line 34-35: The persisted LastSeenLiveClaimPath uses filepath.Join
which emits OS-specific separators; change liveClaimRelativePath to use
path.Join instead of filepath.Join so the stored relative path always uses
forward slashes, update the corresponding test in claims_test.go to assert using
path.Join (not filepath.Join), and adjust imports (add "path" and remove
"path/filepath" if it becomes unused) so the on-disk JSON format is stable
across platforms.

In `@internal/adapters/verify/command/runner_test.go`:
- Around line 311-339: The test creates workDir as an unrelated temp dir and
never ties it to repoRoot, so the prefix check is ineffective; change workDir to
be a path under repoRoot (e.g. workDir := filepath.Join(repoRoot, "worktree")),
create that directory, and then keep the existing
BuildIsolatedProcessEnv(verificationEnv(nil), repoRoot) call but assert that
TMPDIR, GOCACHE, GOTMPDIR, XDG_CACHE_HOME do not have a prefix matching the
canonicalized workDir (use filepath.Clean and filepath.Separator as already
used) so the test verifies isolation against the real worktree location; update
references to workDir in the prefix check accordingly in
TestVerificationEnv_IsolatesToolCachesOutsideWorkingTreeOutputs.

In `@internal/cli/run_current_pointer_test.go`:
- Around line 38-58: testGitEnv currently strips repo-local GIT_* entries but
still allows system/global Git configs to be read; update testGitEnv (and the
similar block at the other occurrence) to disable system/global configs by
appending environment variables such as GIT_CONFIG_NOSYSTEM=1 plus a sanitized
home/config location (e.g. set HOME and XDG_CONFIG_HOME to an isolated empty
directory path or a known non-existent path) so Git cannot pick up ~/.gitconfig
or system config; change references to testGitEnv to include these additional
env entries alongside the existing GIT_CONFIG_* and core.hooksPath entries.

In `@internal/e2e/helpers_test.go`:
- Around line 55-72: testGitEnv currently filters only GIT_DIR-style vars but
leaves GIT_CONFIG_*/GIT_CONFIG_COUNT and doesn't disable hooks; update
testGitEnv to also strip any environment entries whose key starts with
"GIT_CONFIG_" and "GIT_CONFIG_COUNT" and ensure it sets core.hooksPath to an
empty path for repository commands (e.g., by adding "GIT_CONFIG_PARAMETERS=" or
an env that forces core.hooksPath to empty) so initRepo/git commit cannot read
host config or run host hooks; search for the function testGitEnv and the
initRepo usage to implement these changes and ensure the returned env vector
includes the necessary variables that harden git (remove GIT_CONFIG_* and
GIT_CONFIG_COUNT and add the hook-disable env) before returning.

In `@internal/engine/resume.go`:
- Around line 708-739: resumeEpicMode currently dereferences waveManager inside
the merge branch (calling changedFilesFromManager and waveManager.ChangedFiles)
without guarding for nil, which can crash when worktree preparation was skipped;
add a nil-check at the start of that branch (the block starting with "if
len(mergeEligibleTicketIDs) > 0 && conflictErr == nil && acceptErr == nil") to
skip merge-related operations when waveManager == nil, or alternatively handle
the case by returning a clear error after cleaning up; specifically, ensure you
check waveManager == nil before calling changedFilesFromManager,
waveManager.ChangedFiles, waveManager.WorktreePath or any methods on waveManager
so resumeEpicMode and prepareWaveIntegration/integration.ApplyAcceptedTicketRefs
only run when waveManager is present.

In `@internal/engine/ticket_run_test.go`:
- Around line 249-255: The linked worktree is being created inside repoRoot
(worktreePath := filepath.Join(repoRoot, "worktree-review")), which can mask
bugs that ignore WorktreePath; change the setup to create the worktree outside
repoRoot (e.g., use os.MkdirTemp to get a temp directory and assign worktreePath
to that temp dir, or join a parent of repoRoot), then call mustRunGit with the
new worktreePath and create/commit "ticket-only.txt" there; also add cleanup
(defer os.RemoveAll(worktreePath)) so the external temp worktree is removed
after the test.

In `@internal/engine/wave_verify.go`:
- Around line 1172-1179: The code currently rejects recovery unless
finalChangedFiles exactly equals tx.ChangedFiles; change this to accept cases
where the finalChangedFiles include the originally recorded tx.ChangedFiles
(i.e., tx.ChangedFiles is a subset of finalChangedFiles) so legitimately
added/replaced files during repair don’t block clearing the pending cursor. In
the block after finalIntegratedChangedFiles(...) replace the samePathSet(...)
equality check with a subset check that verifies every path in tx.ChangedFiles
exists in finalChangedFiles, and if so continue to call
persistCompletedPendingWaveIntegration(...); only return false when any
tx.ChangedFiles entry is missing from finalChangedFiles.
- Around line 1091-1098: The code blindly reapplies tx.AcceptedRefs during
rehydrate which can be empty for older/partial pending_wave_integration blobs
and cause accepted ticket changes to be dropped; add a fail-closed guard in the
rehydrate path: before calling
integration.ApplyAcceptedTicketRefs(context.Background(), tx.AcceptedRefs) check
that tx.AcceptedRefs is present and non-empty (or otherwise validate its shape)
and if it is missing/empty return a clear error (after cleaning up the
integration via integration.Cleanup()) instead of proceeding; update logic
around prepareWaveIntegration, ApplyAcceptedTicketRefs and any callers that
assume missing accepted_refs is safe so the system preserves previous behavior
for incompatible on-disk payloads.

In `@internal/engine/worktree.go`:
- Around line 84-106: Reject unsafe run/ticket ID values before using them in
filesystem paths or git refs: add a validation helper (e.g.,
validateRunOrTicketID) that checks trimmed IDs for empty, any path separators
(os.PathSeparator or "/" and "\"), any ".." path traversal segments, and
ref-special sequences (leading '.', ending '.', or sequences invalid for git
refs), then call it from WorktreeManager.CreateWorktree (validate wm.runID and
ticketID before building worktreePath and refs) and from the other location
referenced (lines ~337-343) so the same validated values are reused whenever
constructing filepath.Join(...) or refs/verk/runs/.... If validation fails,
return a clear error; keep locking and rest of CreateWorktree unchanged.
- Around line 1356-1375: resolveWithinRoot currently only does lexical checks;
update it to be symlink-aware by resolving existing ancestor paths (not the
possibly-nonexistent target) and rejecting any that escape absRoot. After
computing absRoot and absTarget, walk from filepath.Dir(absTarget) up to absRoot
(inclusive) and for each existing ancestor call filepath.EvalSymlinks (or
os.Lstat + EvalSymlinks) to get the real path and verify the resolved path has
absRoot as its prefix; if any resolved ancestor is outside absRoot return an
error like "target %q is outside root %q". Keep the original lexical Rel check
as a final guard but ensure ancestors that are symlinks that would escape are
detected and rejected in resolveWithinRoot.

---

Outside diff comments:
In `@internal/adapters/repo/git/repo.go`:
- Around line 113-117: Don't silently fall back to r.root when git doesn't
support --path-format; instead call gitOutput(r.root, "rev-parse",
"--git-common-dir") to get the common dir, then if that output is a relative
path resolve it against r.root (e.g., filepath.Join(r.root, commonDir)) to
produce an absolute path, return that absolute commonDir, and only return an
error if gitOutput fails — update the logic around the commonDir variable where
gitOutput(... "--path-format=absolute", "--git-common-dir") is used so
CreateWorktree doesn't get anchored to r.root.

In `@internal/engine/epic_gate.go`:
- Around line 577-620: In executeEpicChecks, don't pass the empty string as the
worktree/state hint to verifycommand.RunQualityCommands and
verifycommand.RunCommands because that opt-outs of the new cache-root isolation;
update both calls (verifycommand.RunQualityCommands and
verifycommand.RunCommands) to pass the appropriate worktree hint (e.g., repoRoot
or the worktree-scoped hint you use elsewhere) instead of "" so epic closure
commands and derived checks run under the worktree-scoped cache root.

---

Nitpick comments:
In `@docs/epos-integration.md`:
- Around line 218-230: Update the execution and verification steps in the "Phase
1 — Pre-flight + dependency add" section (and the other referenced blocks) to
use the repository's just targets instead of ad hoc go commands: replace step
5's "go mod tidy && go build ./..." and the "**Exit:** go build ./... + go test
./... green" with explicit calls to "just pre-commit" and "just test-race"
during worktree/isolation validation and use "just check" as the final gate;
apply the same replacement to the other affected blocks (the later sections
noted in the comment) so all verification steps consistently call just
pre-commit, just test-race, and just check.

In `@docs/plans/worker-isolation.md`:
- Around line 349-367: Update the "Verification" checklist to include the
repo-standard Just targets by adding explicit steps to run `just pre-commit`
(before engine/worktree changes) and `just test-race` (when changes affect
orchestration, isolation, or cleanup semantics); modify the Verification section
(the numbered checklist under the "Verification" header) to insert these two
targets as required gates alongside the existing unit/integration/resume tests
so the documented validation path matches repo requirements.

In `@internal/adapters/ticketstore/epos/claims.go`:
- Around line 653-655: The heuristic in looksLikeLeaseID is too loose and can
misclassify freeform release reasons that start with "lease-" or "fence-";
update looksLikeLeaseID to use a stricter pattern (e.g., a regex that requires
the expected suffix format like lease-[0-9a-fA-F]+ or a UUID pattern for fence-)
or change call sites to require an explicit positional flag instead of guessing;
also add a short comment/docstring on looksLikeLeaseID explaining the accepted
lease/fence formats so future callers know the constraint.

In `@internal/adapters/ticketstore/epos/convert.go`:
- Around line 300-311: The asInt function currently only handles int, int64 and
float64 and returns 0 for other numeric types; update asInt to handle additional
numeric types (e.g., int8, int16, int32, uint, uint8, uint16, uint32, uint64) by
adding type switch cases that convert them to int safely (casting where
appropriate) and optionally handle json.Number or string numeric input by
parsing to int; ensure conversions for unsigned types check for overflow if
needed and keep the function name asInt to locate the change.

In `@internal/adapters/verify/command/runner.go`:
- Around line 181-185: Update the comment for QualityCommand.Path to reflect
that paths are resolved relative to workDir (the current working tree used by
RunQualityCommands) rather than repoRoot; change the contract text near the
QualityCommand.Path declaration and any related docblock in runner.go to state
that qc.Path is interpreted relative to workDir so callers/configs use the
worktree base when verification runs inside a worktree.

In `@internal/engine/closeout.go`:
- Around line 272-313: The parseCloseoutRequest variadic parser currently
ignores unsupported argument types, leading to unhelpful "closeout requires
ticket metadata" errors; update parseCloseoutRequest to collect the runtime
types of all args (e.g., via fmt.Sprintf("%T", arg) or reflect.TypeOf) and
include a concise summary of received argument types in the returned error when
req.ticket.ID == "" (and/or include unsupported types encountered), referencing
parseCloseoutRequest, closeoutRequest and ticket.ID so callers can immediately
see what types were passed during adapter migrations.

In `@internal/engine/wave_commands_test.go`:
- Line 24: The test currently calls runWaveVerificationLoop only with an
empty/default workDir; add a new subtest in wave_commands_test.go that calls
runWaveVerificationLoop (via makeEpicReq(repoRoot, adapter) or similar) but
passes a non-empty workDir (e.g., a temp dir) and then asserts that
outputs/artifacts (the same files the existing test checks) are created under
that workDir and not under repoRoot; ensure you clean up the temp dir and
preserve the cache-root semantics described in README when constructing the
expected paths so the new argument is actually covered.

In `@internal/engine/wave_verify.go`:
- Around line 31-205: Add a guard at the start of runWaveVerificationLoop to run
the repo's race/engine checks and abort if they fail: call a helper like
ensureRepoRaceCoverage(ctx, req.RepoRoot) (or implement verifyPreCommitAndRace
that runs the equivalent of `just pre-commit` and `just test-race`) before
computing plan := waveDerivationPlan(...); if that helper returns an error,
return it (wrap with context like "race coverage checks failed") so wave
verification is gated by the repo's race/engine checks.
🪄 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

Run ID: e49b0295-2e29-4460-8724-055f5d24a0a5

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9d0f9 and f48d0c9.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (83)
  • .gitignore
  • .verk/config.yaml
  • AGENTS.md
  • CLAUDE.md
  • README.md
  • cmd/verk/main_test.go
  • doc.go
  • docs/epos-integration.md
  • docs/plans/2026-04-19-verk-as-skill-cross-agent.md
  • docs/plans/2026-04-21-agent-profiles.md
  • docs/plans/2026-04-21-ticket-quality-gate.md
  • docs/plans/INDEX.md
  • docs/plans/done/initial_v1.md
  • docs/plans/worker-isolation.md
  • docs/review-prompts/2026-04-25-worker-isolation-distributed-workflow-crash-recovery-reviewer.md
  • docs/review-prompts/2026-04-25-worker-isolation-git-plumbing-repository-safety-reviewer.md
  • go.mod
  • internal/adapters/repo/git/repo.go
  • internal/adapters/repo/git/repo_test.go
  • internal/adapters/runtime/cacheenv.go
  • internal/adapters/runtime/cacheenv_test.go
  • internal/adapters/runtime/claude/adapter.go
  • internal/adapters/runtime/claude/adapter_test.go
  • internal/adapters/runtime/claude/adapter_unix_test.go
  • internal/adapters/runtime/codex/adapter.go
  • internal/adapters/runtime/codex/adapter_test.go
  • internal/adapters/runtime/prompt.go
  • internal/adapters/runtime/prompt_test.go
  • internal/adapters/runtime/types.go
  • internal/adapters/ticketstore/epos/claims.go
  • internal/adapters/ticketstore/epos/claims_test.go
  • internal/adapters/ticketstore/epos/containment.go
  • internal/adapters/ticketstore/epos/convert.go
  • internal/adapters/ticketstore/epos/doc.go
  • internal/adapters/ticketstore/epos/live.go
  • internal/adapters/ticketstore/epos/store.go
  • internal/adapters/ticketstore/epos/store_test.go
  • internal/adapters/ticketstore/epos/types.go
  • internal/adapters/ticketstore/epos/types_test.go
  • internal/adapters/ticketstore/tkmd/claims.go
  • internal/adapters/ticketstore/tkmd/claims_lock_unix.go
  • internal/adapters/ticketstore/tkmd/claims_lock_windows.go
  • internal/adapters/ticketstore/tkmd/claims_test.go
  • internal/adapters/ticketstore/tkmd/store.go
  • internal/adapters/ticketstore/tkmd/store_test.go
  • internal/adapters/verify/command/runner.go
  • internal/adapters/verify/command/runner_test.go
  • internal/cli/run.go
  • internal/cli/run_blocked_test.go
  • internal/cli/run_current_pointer_test.go
  • internal/cli/run_persistence_test.go
  • internal/e2e/epic_multi_wave_test.go
  • internal/e2e/helpers_test.go
  • internal/e2e/resume_claim_recovery_test.go
  • internal/engine/closeout.go
  • internal/engine/closeout_test.go
  • internal/engine/epic_gate.go
  • internal/engine/epic_gate_test.go
  • internal/engine/epic_run.go
  • internal/engine/epic_run_test.go
  • internal/engine/intake.go
  • internal/engine/intake_test.go
  • internal/engine/ops_support.go
  • internal/engine/reopen_test.go
  • internal/engine/resume.go
  • internal/engine/resume_test.go
  • internal/engine/status.go
  • internal/engine/status_test.go
  • internal/engine/ticket_run.go
  • internal/engine/ticket_run_test.go
  • internal/engine/ticket_validation.go
  • internal/engine/ticket_validation_test.go
  • internal/engine/wave_commands_test.go
  • internal/engine/wave_scheduler.go
  • internal/engine/wave_verify.go
  • internal/engine/wave_verify_test.go
  • internal/engine/worktree.go
  • internal/engine/worktree_test.go
  • internal/state/types.go
  • justfile
  • pkg/verk/types.go
  • scripts/pre-commit
  • scripts/pre-push
💤 Files with no reviewable changes (7)
  • internal/adapters/ticketstore/tkmd/claims_lock_unix.go
  • internal/adapters/ticketstore/tkmd/store.go
  • internal/adapters/ticketstore/tkmd/claims_lock_windows.go
  • internal/adapters/ticketstore/tkmd/claims.go
  • internal/adapters/ticketstore/tkmd/claims_test.go
  • .verk/config.yaml
  • internal/adapters/ticketstore/tkmd/store_test.go

Comment thread AGENTS.md Outdated
Comment thread docs/epos-integration.md Outdated
Comment thread docs/plans/2026-04-21-ticket-quality-gate.md
Comment thread internal/adapters/repo/git/repo.go
Comment thread internal/adapters/runtime/cacheenv.go
Comment thread internal/engine/ticket_run_test.go Outdated
Comment thread internal/engine/wave_verify.go
Comment thread internal/engine/wave_verify.go
Comment thread internal/engine/worktree.go Outdated
Comment thread internal/engine/worktree.go
@php-workx php-workx force-pushed the feat/epso-integration branch from f48d0c9 to 06da653 Compare May 11, 2026 05:39
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 (1)
internal/engine/worktree_test.go (1)

1269-1281: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a consistent “current” timestamp for claim seeding in both tests.

2026-04-02 12:00:00 UTC is already in the past as of May 11, 2026, so the stale test can accidentally seed an expired lease and pass for the wrong reason. The active test also uses a different semantic (now + 24h). Using time.Now().UTC() in both places keeps intent clear.

Suggested patch
-	now := time.Date(2026, 4, 2, 12, 0, 0, 0, time.UTC)
+	now := time.Now().UTC()
...
-	now := time.Now().UTC().Add(24 * time.Hour)
+	now := time.Now().UTC()

Also applies to: 1364-1376

🤖 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 `@internal/engine/worktree_test.go` around lines 1269 - 1281, Tests seed claims
with inconsistent timestamps (a hardcoded past `now` and a different `now+24h`),
which can make the stale test pass for the wrong reason; replace the hardcoded
time.Date and the other ad-hoc timestamp with a single consistent current time
variable (e.g., currentTime := time.Now().UTC()) and use that same variable when
calling epos.AcquireClaim for both the stale and active tests (use currentTime
for the stale lease and currentTime.Add(24*time.Hour) for the active lease) so
both tests use the same "now" reference.
♻️ Duplicate comments (1)
internal/cli/run_current_pointer_test.go (1)

38-86: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden testGitEnv against host git config leakage.

Line 52+ still allows global/system git config to influence git commands, which keeps this test host-dependent/flaky. This is the same unresolved concern previously raised.

Suggested patch
 func testGitEnv() []string {
 	env := os.Environ()
-	out := make([]string, 0, len(env)+4)
+	out := make([]string, 0, len(env)+6)
@@
 	out = append(out,
 		"GIT_OPTIONAL_LOCKS=0",
+		"GIT_CONFIG_GLOBAL="+os.DevNull,
+		"GIT_CONFIG_NOSYSTEM=1",
 		"GIT_CONFIG_COUNT=1",
 		"GIT_CONFIG_KEY_0=core.hooksPath",
-		"GIT_CONFIG_VALUE_0=/dev/null",
+		"GIT_CONFIG_VALUE_0="+os.DevNull,
 	)
 	return out
 }
@@
 	case "GIT_ALTERNATE_OBJECT_DIRECTORIES",
 		"GIT_COMMON_DIR",
 		"GIT_CONFIG",
 		"GIT_CONFIG_COUNT",
+		"GIT_CONFIG_GLOBAL",
+		"GIT_CONFIG_NOSYSTEM",
 		"GIT_CONFIG_PARAMETERS",
 		"GIT_DIR",
@@
 		"GIT_SUPER_PREFIX",
+		"GIT_OPTIONAL_LOCKS",
 		"GIT_WORK_TREE":
 		return true
#!/bin/bash
# Verify git test env hardening flags are present in test helpers.
rg -nC3 --type=go 'func testGitEnv\(|GIT_CONFIG_GLOBAL|GIT_CONFIG_NOSYSTEM|GIT_OPTIONAL_LOCKS|GIT_CONFIG_VALUE_0=.*(os\.DevNull|/dev/null)'
🤖 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 `@internal/cli/run_current_pointer_test.go` around lines 38 - 86, testGitEnv
still allows host global/system git config to leak; update the helper so it
neutralizes those by (1) adding "GIT_CONFIG_NOSYSTEM=1" and
"GIT_CONFIG_GLOBAL=/dev/null" to the appended env in testGitEnv and (2) adding
"GIT_CONFIG_GLOBAL" and "GIT_CONFIG_NOSYSTEM" to the isGitLocalEnv switch so any
existing host values are filtered out from os.Environ before we set the hardened
values; use the existing function names testGitEnv and isGitLocalEnv to locate
the 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.

Outside diff comments:
In `@internal/engine/worktree_test.go`:
- Around line 1269-1281: Tests seed claims with inconsistent timestamps (a
hardcoded past `now` and a different `now+24h`), which can make the stale test
pass for the wrong reason; replace the hardcoded time.Date and the other ad-hoc
timestamp with a single consistent current time variable (e.g., currentTime :=
time.Now().UTC()) and use that same variable when calling epos.AcquireClaim for
both the stale and active tests (use currentTime for the stale lease and
currentTime.Add(24*time.Hour) for the active lease) so both tests use the same
"now" reference.

---

Duplicate comments:
In `@internal/cli/run_current_pointer_test.go`:
- Around line 38-86: testGitEnv still allows host global/system git config to
leak; update the helper so it neutralizes those by (1) adding
"GIT_CONFIG_NOSYSTEM=1" and "GIT_CONFIG_GLOBAL=/dev/null" to the appended env in
testGitEnv and (2) adding "GIT_CONFIG_GLOBAL" and "GIT_CONFIG_NOSYSTEM" to the
isGitLocalEnv switch so any existing host values are filtered out from
os.Environ before we set the hardened values; use the existing function names
testGitEnv and isGitLocalEnv to locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8a7c84c9-1485-419f-b1f6-002ed4db8199

📥 Commits

Reviewing files that changed from the base of the PR and between f48d0c9 and 06da653.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (54)
  • cmd/verk/main_test.go
  • doc.go
  • docs/epos-integration.md
  • docs/plans/2026-04-19-verk-as-skill-cross-agent.md
  • docs/plans/2026-04-21-agent-profiles.md
  • docs/plans/2026-04-21-ticket-quality-gate.md
  • docs/plans/done/initial_v1.md
  • go.mod
  • internal/adapters/ticketstore/epos/claims.go
  • internal/adapters/ticketstore/epos/claims_test.go
  • internal/adapters/ticketstore/epos/containment.go
  • internal/adapters/ticketstore/epos/convert.go
  • internal/adapters/ticketstore/epos/doc.go
  • internal/adapters/ticketstore/epos/live.go
  • internal/adapters/ticketstore/epos/store.go
  • internal/adapters/ticketstore/epos/store_test.go
  • internal/adapters/ticketstore/epos/types.go
  • internal/adapters/ticketstore/epos/types_test.go
  • internal/adapters/ticketstore/tkmd/claims.go
  • internal/adapters/ticketstore/tkmd/claims_lock_unix.go
  • internal/adapters/ticketstore/tkmd/claims_lock_windows.go
  • internal/adapters/ticketstore/tkmd/claims_test.go
  • internal/adapters/ticketstore/tkmd/store.go
  • internal/adapters/ticketstore/tkmd/store_test.go
  • internal/cli/run.go
  • internal/cli/run_blocked_test.go
  • internal/cli/run_current_pointer_test.go
  • internal/cli/run_persistence_test.go
  • internal/e2e/epic_multi_wave_test.go
  • internal/e2e/helpers_test.go
  • internal/e2e/resume_claim_recovery_test.go
  • internal/engine/closeout.go
  • internal/engine/closeout_test.go
  • internal/engine/epic_gate.go
  • internal/engine/epic_gate_test.go
  • internal/engine/epic_run.go
  • internal/engine/epic_run_test.go
  • internal/engine/intake.go
  • internal/engine/intake_test.go
  • internal/engine/ops_support.go
  • internal/engine/reopen_test.go
  • internal/engine/resume.go
  • internal/engine/resume_test.go
  • internal/engine/status.go
  • internal/engine/status_test.go
  • internal/engine/ticket_run.go
  • internal/engine/ticket_run_test.go
  • internal/engine/ticket_validation_test.go
  • internal/engine/wave_scheduler.go
  • internal/engine/worktree_test.go
  • justfile
  • pkg/verk/types.go
  • scripts/pre-commit
  • scripts/pre-push
💤 Files with no reviewable changes (6)
  • internal/adapters/ticketstore/tkmd/claims_lock_windows.go
  • internal/adapters/ticketstore/tkmd/store.go
  • internal/adapters/ticketstore/tkmd/claims_lock_unix.go
  • internal/adapters/ticketstore/tkmd/claims_test.go
  • internal/adapters/ticketstore/tkmd/claims.go
  • internal/adapters/ticketstore/tkmd/store_test.go
✅ Files skipped from review due to trivial changes (9)
  • docs/plans/2026-04-19-verk-as-skill-cross-agent.md
  • internal/adapters/ticketstore/epos/types.go
  • doc.go
  • docs/plans/done/initial_v1.md
  • internal/adapters/ticketstore/epos/doc.go
  • internal/engine/intake.go
  • docs/plans/2026-04-21-agent-profiles.md
  • go.mod
  • docs/plans/2026-04-21-ticket-quality-gate.md
🚧 Files skipped from review as they are similar to previous changes (27)
  • internal/engine/ops_support.go
  • internal/e2e/helpers_test.go
  • internal/engine/status.go
  • internal/engine/ticket_validation_test.go
  • internal/engine/epic_gate_test.go
  • internal/engine/reopen_test.go
  • internal/adapters/ticketstore/epos/containment.go
  • internal/cli/run_blocked_test.go
  • internal/adapters/ticketstore/epos/live.go
  • internal/engine/intake_test.go
  • internal/e2e/epic_multi_wave_test.go
  • internal/adapters/ticketstore/epos/store.go
  • cmd/verk/main_test.go
  • internal/engine/closeout_test.go
  • internal/cli/run.go
  • internal/cli/run_persistence_test.go
  • internal/e2e/resume_claim_recovery_test.go
  • internal/adapters/ticketstore/epos/convert.go
  • internal/adapters/ticketstore/epos/types_test.go
  • internal/adapters/ticketstore/epos/claims.go
  • internal/adapters/ticketstore/epos/store_test.go
  • internal/engine/epic_gate.go
  • internal/engine/ticket_run_test.go
  • internal/engine/ticket_run.go
  • internal/engine/epic_run.go
  • internal/engine/resume.go
  • internal/engine/epic_run_test.go

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

🤖 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 `@internal/e2e/helpers_test.go`:
- Around line 68-80: The environment sanitizer in testGitEnv() is missing
GIT_TEMPLATE_DIR, allowing host templates to leak into temp repos; add
"GIT_TEMPLATE_DIR" to the switch-case list of keys that are skipped (the same
list that currently contains "GIT_DIR", "GIT_WORK_TREE", ...
"GIT_CONFIG_PARAMETERS") so it is filtered out before building the returned env
and the hardcoded config/hook entries are appended.

In `@internal/engine/resume.go`:
- Around line 217-223: The call to epos.ReleaseClaim in resume.go should not
ignore unexpected errors: check the returned error from
epos.ReleaseClaim(artifacts.RepoRoot, req.RunID, ticketID,
"resume_reacquisition") and if it is non-nil and not the known sentinel for
“missing claim” (e.g., ErrClaimNotFound or equivalent from the epos package),
return or propagate that error instead of proceeding to AcquireClaim; apply the
same pattern to the other silent drops of ReleaseClaim found around the other
resume code paths (the occurrences mentioned at the later locations) so only the
allowed-not-found sentinel is ignored and all other errors fail fast.

In `@internal/engine/wave_verify.go`:
- Around line 1185-1186: The current shortcut uses
pathSetIncludes(finalChangedFiles, tx.ChangedFiles) even when tx.ChangedFiles is
nil/empty, which vacuously succeeds; change the condition to require
tx.ChangedFiles to be non-empty (e.g., len(tx.ChangedFiles) > 0) AND
pathSetIncludes(...) before treating the wave as already applied so
empty/missing changed_files fall through to the normal completion path; make the
same guard update for the analogous checks in the same block (the other
pathSetIncludes usages around the 1191-1201 range) so all early-return shortcuts
require a non-empty tx.ChangedFiles.
🪄 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

Run ID: e069c515-05a9-4068-83c2-62f29237965b

📥 Commits

Reviewing files that changed from the base of the PR and between 06da653 and 044fb8f.

📒 Files selected for processing (23)
  • cmd/verk/main_test.go
  • docs/epos-integration.md
  • docs/plans/2026-04-21-ticket-quality-gate.md
  • docs/plans/worker-isolation.md
  • internal/adapters/repo/git/repo.go
  • internal/adapters/repo/git/repo_test.go
  • internal/adapters/ticketstore/epos/claims.go
  • internal/adapters/ticketstore/epos/claims_test.go
  • internal/adapters/ticketstore/epos/convert.go
  • internal/adapters/ticketstore/epos/live.go
  • internal/adapters/verify/command/runner.go
  • internal/adapters/verify/command/runner_test.go
  • internal/cli/run_current_pointer_test.go
  • internal/e2e/helpers_test.go
  • internal/engine/closeout.go
  • internal/engine/epic_gate.go
  • internal/engine/resume.go
  • internal/engine/ticket_run_test.go
  • internal/engine/wave_commands_test.go
  • internal/engine/wave_verify.go
  • internal/engine/wave_verify_test.go
  • internal/engine/worktree.go
  • internal/engine/worktree_test.go
✅ Files skipped from review due to trivial changes (3)
  • internal/adapters/verify/command/runner_test.go
  • internal/adapters/verify/command/runner.go
  • docs/plans/2026-04-21-ticket-quality-gate.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • cmd/verk/main_test.go
  • internal/engine/ticket_run_test.go
  • internal/adapters/ticketstore/epos/claims_test.go
  • internal/adapters/ticketstore/epos/convert.go

Comment thread internal/e2e/helpers_test.go Outdated
Comment thread internal/engine/resume.go
Comment thread internal/engine/wave_verify.go Outdated
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 (2)
internal/engine/wave_verify_test.go (1)

87-97: ⚡ Quick win

Add explicit empty-slice coverage for transaction files.

You validate nil, but not []string{}. Adding that assertion locks in the intended “nil/empty both reject” contract.

Proposed test addition
 func TestPathSetIncludesAllowsAdditionalFinalFiles(t *testing.T) {
 	if !pathSetIncludes([]string{"docs/a.md", "docs/b.md"}, []string{"docs/a.md"}) {
 		t.Fatal("expected superset final file list to include original transaction files")
 	}
 	if pathSetIncludes([]string{"docs/b.md"}, []string{"docs/a.md"}) {
 		t.Fatal("expected missing original transaction file to be rejected")
 	}
 	if pathSetIncludes([]string{"docs/a.md"}, nil) {
 		t.Fatal("expected empty transaction file list to be rejected")
 	}
+	if pathSetIncludes([]string{"docs/a.md"}, []string{}) {
+		t.Fatal("expected explicit empty transaction file list to be rejected")
+	}
 }
🤖 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 `@internal/engine/wave_verify_test.go` around lines 87 - 97, Update the
TestPathSetIncludesAllowsAdditionalFinalFiles test to assert that an explicitly
empty slice is rejected the same as nil: inside
TestPathSetIncludesAllowsAdditionalFinalFiles, add a check calling
pathSetIncludes([]string{"docs/a.md"}, []string{}) and fail the test (t.Fatal)
if it returns true so the contract that nil/empty transaction file lists are
rejected is enforced; reference the existing test name
TestPathSetIncludesAllowsAdditionalFinalFiles and the helper pathSetIncludes to
locate where to add the assertion.
internal/engine/resume_test.go (1)

1052-1053: ⚡ Quick win

Rename closed test fixture variable to reflect initial blocked state.

At Line 1052, closed is initialized as epos.StatusBlocked. Renaming it (e.g., willClose/eventuallyClosed) would reduce cognitive friction when reading this test.

🤖 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 `@internal/engine/resume_test.go` around lines 1052 - 1053, Rename the test
fixture variable currently named closed (created via epicChildTicket(...,
epos.StatusBlocked, ...)) to a name that reflects its initial blocked state such
as eventuallyClosed (or willClose); update the declaration that calls
epicChildTicket and every subsequent reference to that variable in this test
(assertions, uses in setup/teardown, and any logs) so the code reads clearly
that the ticket starts blocked (epos.StatusBlocked) and later transitions as the
test expects.
🤖 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 `@internal/engine/resume_test.go`:
- Around line 1052-1053: Rename the test fixture variable currently named closed
(created via epicChildTicket(..., epos.StatusBlocked, ...)) to a name that
reflects its initial blocked state such as eventuallyClosed (or willClose);
update the declaration that calls epicChildTicket and every subsequent reference
to that variable in this test (assertions, uses in setup/teardown, and any logs)
so the code reads clearly that the ticket starts blocked (epos.StatusBlocked)
and later transitions as the test expects.

In `@internal/engine/wave_verify_test.go`:
- Around line 87-97: Update the TestPathSetIncludesAllowsAdditionalFinalFiles
test to assert that an explicitly empty slice is rejected the same as nil:
inside TestPathSetIncludesAllowsAdditionalFinalFiles, add a check calling
pathSetIncludes([]string{"docs/a.md"}, []string{}) and fail the test (t.Fatal)
if it returns true so the contract that nil/empty transaction file lists are
rejected is enforced; reference the existing test name
TestPathSetIncludesAllowsAdditionalFinalFiles and the helper pathSetIncludes to
locate where to add the assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a7f27391-06e2-4849-b5a2-20b6a73affc4

📥 Commits

Reviewing files that changed from the base of the PR and between 044fb8f and 12b6f6b.

📒 Files selected for processing (7)
  • internal/adapters/ticketstore/epos/claims.go
  • internal/adapters/ticketstore/epos/claims_test.go
  • internal/e2e/helpers_test.go
  • internal/engine/resume.go
  • internal/engine/resume_test.go
  • internal/engine/wave_verify.go
  • internal/engine/wave_verify_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/engine/wave_verify.go
  • internal/engine/resume.go
  • internal/adapters/ticketstore/epos/claims.go
  • internal/e2e/helpers_test.go
  • internal/adapters/ticketstore/epos/claims_test.go

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 (1)
internal/engine/epic_run.go (1)

1632-1651: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Claim-held children will now be reported as status=open.

After the switch to claim-aware epos.ListReadyChildren, an open child can be filtered out solely because another run holds an active claim. This helper still only looks at markdown status/deps, so the blocked summary and BlockedRunError can hide the real wait-on-lease condition and tell the operator the wrong thing.

🤖 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 `@internal/engine/epic_run.go` around lines 1632 - 1651, describeNotReady
currently treats open-but-claim-held children as plain "status=open"; update
describeNotReady to detect claim-held tickets (check the ticket's claim fields
such as Claim, ClaimHolder, ClaimRunID or similar on epos.Ticket) and return a
distinct message like "held by claim" or "waiting on lease (held by <id>)" when
a claim exists, falling back to the existing deps/status logic otherwise; also
ensure the BlockedRunError path (and any callers relying on describeNotReady)
will surface this new claim-aware string so operators see a lease/claim wait
instead of "open".
🧹 Nitpick comments (2)
internal/engine/runlock.go (1)

44-46: 💤 Low value

Consider whether the unexported wrapper is needed.

The validateArtifactIdentifier wrapper simply forwards to ValidateArtifactIdentifier. If both functions have identical behavior, consider using ValidateArtifactIdentifier directly at call sites unless there's a planned divergence.

🤖 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 `@internal/engine/runlock.go` around lines 44 - 46, The private wrapper
validateArtifactIdentifier just forwards to the exported
ValidateArtifactIdentifier and offers no added behavior; either remove
validateArtifactIdentifier and replace all its call sites to call
ValidateArtifactIdentifier directly, or if you intend future divergence keep the
wrapper but add a comment explaining its purpose. Locate usages of
validateArtifactIdentifier and update them to call ValidateArtifactIdentifier
(or preserve the wrapper and document it) and run tests/build to ensure no
references remain.
internal/engine/ops_support.go (1)

478-484: ⚡ Quick win

Make this helper write ready, or rename it.

setTicketReady now persists epos.StatusOpen, which makes the helper name misleading and drops the explicit ready state introduced elsewhere in this PR.

Suggested change
 func setTicketReady(repoRoot, ticketID string) error {
 	ticket, err := epos.LoadTicket(ticketMarkdownPath(repoRoot, ticketID))
 	if err != nil {
 		return err
 	}
-	ticket.Status = epos.StatusOpen
+	ticket.Status = epos.StatusReady
 	return epos.SaveTicket(ticketMarkdownPath(repoRoot, ticketID), ticket)
 }
🤖 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 `@internal/engine/ops_support.go` around lines 478 - 484, The helper
setTicketReady currently loads a ticket and sets ticket.Status = epos.StatusOpen
which contradicts its name and the new explicit "ready" state; change the
implementation to persist epos.StatusReady instead of epos.StatusOpen (i.e., set
ticket.Status = epos.StatusReady) and keep the rest (LoadTicket, SaveTicket,
ticketMarkdownPath) unchanged, or if the intended semantics were to mark tickets
open, rename the function to setTicketOpen and leave the status as
epos.StatusOpen; also update any callers to use the new status value or new
function name accordingly.
🤖 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 `@internal/engine/epic_run.go`:
- Around line 1632-1651: describeNotReady currently treats open-but-claim-held
children as plain "status=open"; update describeNotReady to detect claim-held
tickets (check the ticket's claim fields such as Claim, ClaimHolder, ClaimRunID
or similar on epos.Ticket) and return a distinct message like "held by claim" or
"waiting on lease (held by <id>)" when a claim exists, falling back to the
existing deps/status logic otherwise; also ensure the BlockedRunError path (and
any callers relying on describeNotReady) will surface this new claim-aware
string so operators see a lease/claim wait instead of "open".

---

Nitpick comments:
In `@internal/engine/ops_support.go`:
- Around line 478-484: The helper setTicketReady currently loads a ticket and
sets ticket.Status = epos.StatusOpen which contradicts its name and the new
explicit "ready" state; change the implementation to persist epos.StatusReady
instead of epos.StatusOpen (i.e., set ticket.Status = epos.StatusReady) and keep
the rest (LoadTicket, SaveTicket, ticketMarkdownPath) unchanged, or if the
intended semantics were to mark tickets open, rename the function to
setTicketOpen and leave the status as epos.StatusOpen; also update any callers
to use the new status value or new function name accordingly.

In `@internal/engine/runlock.go`:
- Around line 44-46: The private wrapper validateArtifactIdentifier just
forwards to the exported ValidateArtifactIdentifier and offers no added
behavior; either remove validateArtifactIdentifier and replace all its call
sites to call ValidateArtifactIdentifier directly, or if you intend future
divergence keep the wrapper but add a comment explaining its purpose. Locate
usages of validateArtifactIdentifier and update them to call
ValidateArtifactIdentifier (or preserve the wrapper and document it) and run
tests/build to ensure no references remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84a3f4e1-ba87-42d0-9c5e-be4b9e268ead

📥 Commits

Reviewing files that changed from the base of the PR and between 12b6f6b and 4bc75b8.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (20)
  • cmd/verk/main_test.go
  • docs/plans/done/epos-integration.md
  • go.mod
  • internal/adapters/ticketstore/epos/convert.go
  • internal/adapters/ticketstore/epos/store_test.go
  • internal/cli/run.go
  • internal/cli/run_current_pointer_test.go
  • internal/engine/epic_run.go
  • internal/engine/epic_run_test.go
  • internal/engine/ops_support.go
  • internal/engine/reopen.go
  • internal/engine/reopen_test.go
  • internal/engine/resume.go
  • internal/engine/runlock.go
  • internal/engine/runlock_test.go
  • internal/engine/runlock_unix.go
  • internal/engine/runlock_windows.go
  • internal/engine/status_test.go
  • internal/engine/ticket_run.go
  • internal/engine/ticket_run_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • go.mod
  • internal/engine/reopen_test.go
  • internal/adapters/ticketstore/epos/convert.go
  • internal/engine/ticket_run_test.go
  • internal/engine/epic_run_test.go

@php-workx php-workx merged commit 47644bc into main May 14, 2026
11 checks passed
@php-workx php-workx deleted the feat/epso-integration branch May 14, 2026 09:29
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