From 9761b3440279f92f08aa0b5a870a112f89b044b5 Mon Sep 17 00:00:00 2001 From: max rovkin Date: Tue, 7 Jul 2026 17:42:35 +0500 Subject: [PATCH 1/2] Add recoverable Puber workflow blockers --- .kent/project-contract.md | 20 +- .kent/workflows/README.md | 4 + .../workflows/puber-bugfix-investigation.json | 198 +++- .kent/workflows/puber-dependency-update.json | 198 +++- .kent/workflows/puber-feature-delivery.json | 286 +++++- .../workflows/puber-refactor-with-audit.json | 198 +++- .kent/workflows/puber-release.json | 969 +++++++++++++++++- .kent/workflows/puber-smoke-test.json | 252 ++++- .kent/workflows/puber-test-coverage.json | 198 +++- 9 files changed, 2237 insertions(+), 86 deletions(-) diff --git a/.kent/project-contract.md b/.kent/project-contract.md index a8cef11..a3719d9 100644 --- a/.kent/project-contract.md +++ b/.kent/project-contract.md @@ -89,6 +89,22 @@ worktree metadata until Kent rebind behavior is fixed. merged PR. - Cleanup always emits `cleanup_report`; skipped cleanup is a valid result and must be visible. +## Recoverable Blocking Policy + +`blocked` is a terminal sink and should be used only when continuing would be unsafe without changing task scope, +credentials, or project policy. Temporary external problems must use recoverable transitions instead: + +- `retry_later`: CI/GitHub is still starting or unavailable, device/MCP/emulator access is temporarily unavailable, or + release automation is still starting. This transition must require user approval before retrying the same node. +- `needs_changes`: the task branch, PR, CI fallout, or release branch needs recoverable fixes such as rebase, conflict + resolution, or branch metadata repair. This transition must require user approval before returning to `fix` or + `prepare`. +- `not_ready`: release tag publication was approved before the PR was merged or visible on `origin/master`; return to + `ci_monitor` after user action instead of ending in terminal `blocked`. + +Already terminal-blocked tasks cannot be moved back to `backlog` with `kent task move`; create a replacement task with +the previous task context when recovery is needed after the terminal transition was applied. + ## Release Policy Use `Puber Release` for human-facing release tasks. @@ -108,8 +124,8 @@ Use generic workflow graph keys and project-prefixed live workflow names: `Puber Dependency Update`, `Puber Test Coverage`, `Puber Smoke Test`, `Puber Release`. - Node keys: `plan`, `implement`, `audit`, `fix`, `smoke`, `prepare`, `compliance`, `ship_pr`, `ci_monitor`, `publish`, `monitor`, `cleanup`, `done`, `blocked`. -- Transition IDs: `implement`, `continue_implementation`, `audit`, `needs_changes`, `smoke`, `ship_pr`, `monitor_ci`, - `done`, `blocked`. +- Transition IDs: `implement`, `continue_implementation`, `audit`, `needs_changes`, `retry_later`, `not_ready`, `smoke`, + `ship_pr`, `monitor_ci`, `done`, `blocked`. - Portable params: `workspace_path`, `plan_path`, `audit_report`, `review_report`, `verification_report`, `pr_url`, `branch_name`, `pr_report`, `ci_report`, `compliance_report`, `release_version`, `release_type`, `release_branch`, `release_tag`, `version_bump_commit`, `target_commit`, `tag_push_status`, `release_report`, `blocker_reason`, diff --git a/.kent/workflows/README.md b/.kent/workflows/README.md index b54f359..a4e961a 100644 --- a/.kent/workflows/README.md +++ b/.kent/workflows/README.md @@ -57,6 +57,10 @@ Legacy split release workflows (`Puber Release Preparation` and `Puber Release P no-diff/report-only/smoke-only cases and must explain that through `pr_report`. - `ci_monitor` never merges PRs and never pushes new commits. CI failures go back to fix/review/compliance before another PR/CI pass. +- `blocked` is terminal and must be reserved for unrecoverable situations. Recoverable external waits should use + `retry_later` back to the same node with human approval; recoverable PR/branch/CI fallout should use `needs_changes` + back to `fix` or `prepare` with human approval; release publication that is approved before the PR is merged should + use `not_ready` back to `ci_monitor`. - Smoke workflows must acquire a shared mobile resource lock through `.kent/adapters/mobile/emulator-resource-lock.sh` before installing, launching, or controlling an emulator/device. When multiple `adb` emulators are already running, agents should acquire any free emulator-specific lock and pass that serial diff --git a/.kent/workflows/puber-bugfix-investigation.json b/.kent/workflows/puber-bugfix-investigation.json index fc11743..2b9bc56 100644 --- a/.kent/workflows/puber-bugfix-investigation.json +++ b/.kent/workflows/puber-bugfix-investigation.json @@ -3,7 +3,7 @@ "id": "workflow-d80d00fc-529a-41b5-8668-c4851002fb44", "name": "Puber Bugfix Investigation", "description": "Reproduce, diagnose, fix, verify, and conservatively clean up a Puber Android bugfix.", - "version": 58 + "version": 66 }, "nodes": [ { @@ -243,6 +243,22 @@ "transition_id": "blocked", "display_name": "Blocked", "description": "CI monitoring is blocked by GitHub access, missing check data, or unsafe repository state." + }, + { + "id": "group-45f7bf57-5517-4732-a3c9-42b4eacf209f", + "workflow_id": "workflow-d80d00fc-529a-41b5-8668-c4851002fb44", + "source_node_id": "node-db8f389d-9764-4a8d-9c77-edf70644f483", + "transition_id": "needs_changes", + "display_name": "Needs Changes", + "description": "PR creation/update hit a recoverable branch, rebase, conflict, or metadata issue; user approval required before recovery work." + }, + { + "id": "group-1ff045c0-3774-4a8a-acdc-3b20c26129f0", + "workflow_id": "workflow-d80d00fc-529a-41b5-8668-c4851002fb44", + "source_node_id": "node-8cfedf0a-355b-43d8-a2d6-e9bf02dd2c5e", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "CI or GitHub is temporarily unavailable, still starting, or missing an expected run; user approval required before retrying monitoring." } ], "edges": [ @@ -481,7 +497,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked if PR creation/update cannot be completed safely and provide blocker_reason.", + "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with needs_changes when PR creation/update is blocked by recoverable repository, branch, or PR state such as merge conflicts, non-fast-forward push, branch needing rebase, or missing PR metadata that can be fixed in the task worktree; provide workspace_path and blocker_reason. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked only when continuing would be unsafe without changing task scope, credentials, or project policy; provide blocker_reason.", "parameters": [ { "key": "compliance_report", @@ -541,11 +557,11 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with blocked if checks cannot be inspected and the risk cannot be accepted; provide blocker_reason.", + "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later when checks cannot be inspected because GitHub/CI is still starting, rate-limited, temporarily unavailable, or an expected run has not appeared yet; provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if checks cannot be inspected and retrying would be unsafe or misleading; provide blocker_reason.", "parameters": [ { "key": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "key": "branch_name", @@ -553,7 +569,7 @@ }, { "key": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -657,6 +673,60 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "id": "edge-763e1d90-e640-4ca5-9022-96c02b9c0b6c", + "workflow_id": "workflow-d80d00fc-529a-41b5-8668-c4851002fb44", + "transition_group_id": "group-45f7bf57-5517-4732-a3c9-42b4eacf209f", + "key": "pr_fix", + "target_node_id": "node-c41f562e-465b-4296-99db-63fb61376288", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Fix recoverable Puber PR creation/update issues only.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Previous PR blocker: {{.Params.blocker_reason}}. Work in {{.Params.workspace_path}}. Apply only the minimum changes needed to make the task branch/PR shippable, such as rebasing, resolving merge conflicts, fixing branch metadata, or repairing the PR-ready commit set. Do not expand product scope. Verify narrowly, then continue through the workflow's normal review/compliance path. Complete with blocked only if the recovery is unsafe or requires user credentials/policy changes.", + "parameters": [ + { + "key": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "key": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-9dc72a39-bece-4a49-a9cf-81465c335d4e", + "workflow_id": "workflow-d80d00fc-529a-41b5-8668-c4851002fb44", + "transition_group_id": "group-1ff045c0-3774-4a8a-acdc-3b20c26129f0", + "key": "ci_retry", + "target_node_id": "node-8cfedf0a-355b-43d8-a2d6-e9bf02dd2c5e", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry Puber PR check monitoring after an approved temporary CI/GitHub wait.\n\nRead .kent/project-contract.md and AGENTS.md first. Previous CI status: {{.Params.ci_report}}. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}} again. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later if CI/GitHub is still temporarily unavailable and provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if retrying is no longer useful or safe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "key": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "key": "workspace_path", + "description": "Path to the explicit .todo/bugfix-* workspace." + }, + { + "key": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ], "derived_wiring": { @@ -773,7 +843,7 @@ "possible_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -781,7 +851,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." }, { "name": "pr_report", @@ -811,6 +881,10 @@ { "name": "blocker_reason", "description": "Why CI monitoring cannot complete, in the task language when practical." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." } ] }, @@ -983,7 +1057,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -991,7 +1065,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1047,6 +1121,40 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "transition_group_id": "group-45f7bf57-5517-4732-a3c9-42b4eacf209f", + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-1ff045c0-3774-4a8a-acdc-3b20c26129f0", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Path to the explicit .todo/bugfix-* workspace." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ], "edges": [ @@ -1375,7 +1483,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1383,7 +1491,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1484,6 +1592,74 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "edge_id": "edge-763e1d90-e640-4ca5-9022-96c02b9c0b6c", + "input_bindings": [ + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-9dc72a39-bece-4a49-a9cf-81465c335d4e", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Path to the explicit .todo/bugfix-* workspace." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ] } diff --git a/.kent/workflows/puber-dependency-update.json b/.kent/workflows/puber-dependency-update.json index 4913fe0..3abe370 100644 --- a/.kent/workflows/puber-dependency-update.json +++ b/.kent/workflows/puber-dependency-update.json @@ -3,7 +3,7 @@ "id": "workflow-ee05cfa8-8fc8-49e7-8eda-2e3893424ad2", "name": "Puber Dependency Update", "description": "Update Puber Gradle dependencies or tooling, verify fallout, fix issues, and clean up safely.", - "version": 51 + "version": 59 }, "nodes": [ { @@ -235,6 +235,22 @@ "transition_id": "blocked", "display_name": "Blocked", "description": "CI monitoring is blocked by GitHub access, missing check data, or unsafe repository state." + }, + { + "id": "group-cdf973e7-cab4-4a13-83bd-1a615dc642e2", + "workflow_id": "workflow-ee05cfa8-8fc8-49e7-8eda-2e3893424ad2", + "source_node_id": "node-e3459660-22bc-4ad2-bd8e-190745ecc83e", + "transition_id": "needs_changes", + "display_name": "Needs Changes", + "description": "PR creation/update hit a recoverable branch, rebase, conflict, or metadata issue; user approval required before recovery work." + }, + { + "id": "group-5a27510e-031f-4255-a500-59d190274e44", + "workflow_id": "workflow-ee05cfa8-8fc8-49e7-8eda-2e3893424ad2", + "source_node_id": "node-09339573-c966-460d-aad9-82d0f53ef9fc", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "CI or GitHub is temporarily unavailable, still starting, or missing an expected run; user approval required before retrying monitoring." } ], "edges": [ @@ -446,7 +462,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked if PR creation/update cannot be completed safely and provide blocker_reason.", + "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with needs_changes when PR creation/update is blocked by recoverable repository, branch, or PR state such as merge conflicts, non-fast-forward push, branch needing rebase, or missing PR metadata that can be fixed in the task worktree; provide workspace_path and blocker_reason. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked only when continuing would be unsafe without changing task scope, credentials, or project policy; provide blocker_reason.", "parameters": [ { "key": "compliance_report", @@ -506,11 +522,11 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with blocked if checks cannot be inspected and the risk cannot be accepted; provide blocker_reason.", + "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later when checks cannot be inspected because GitHub/CI is still starting, rate-limited, temporarily unavailable, or an expected run has not appeared yet; provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if checks cannot be inspected and retrying would be unsafe or misleading; provide blocker_reason.", "parameters": [ { "key": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "key": "branch_name", @@ -518,7 +534,7 @@ }, { "key": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -622,6 +638,60 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "id": "edge-7fa56ec2-0d6b-4529-9e56-0d0fdd69ffe7", + "workflow_id": "workflow-ee05cfa8-8fc8-49e7-8eda-2e3893424ad2", + "transition_group_id": "group-cdf973e7-cab4-4a13-83bd-1a615dc642e2", + "key": "pr_fix", + "target_node_id": "node-d51d551d-30cb-4948-8cbe-62d4d34fda9e", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Fix recoverable Puber PR creation/update issues only.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Previous PR blocker: {{.Params.blocker_reason}}. Work in {{.Params.workspace_path}}. Apply only the minimum changes needed to make the task branch/PR shippable, such as rebasing, resolving merge conflicts, fixing branch metadata, or repairing the PR-ready commit set. Do not expand product scope. Verify narrowly, then continue through the workflow's normal review/compliance path. Complete with blocked only if the recovery is unsafe or requires user credentials/policy changes.", + "parameters": [ + { + "key": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "key": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-2a67a605-53e4-4bd5-8219-917934b132ef", + "workflow_id": "workflow-ee05cfa8-8fc8-49e7-8eda-2e3893424ad2", + "transition_group_id": "group-5a27510e-031f-4255-a500-59d190274e44", + "key": "ci_retry", + "target_node_id": "node-09339573-c966-460d-aad9-82d0f53ef9fc", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry Puber PR check monitoring after an approved temporary CI/GitHub wait.\n\nRead .kent/project-contract.md and AGENTS.md first. Previous CI status: {{.Params.ci_report}}. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}} again. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later if CI/GitHub is still temporarily unavailable and provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if retrying is no longer useful or safe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "key": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "key": "workspace_path", + "description": "Path to the explicit .todo/dependency-update-* workspace." + }, + { + "key": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ], "derived_wiring": { @@ -734,7 +804,7 @@ "possible_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -742,7 +812,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." }, { "name": "pr_report", @@ -772,6 +842,10 @@ { "name": "blocker_reason", "description": "Why CI monitoring cannot complete, in the task language when practical." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." } ] }, @@ -927,7 +1001,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -935,7 +1009,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -991,6 +1065,40 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "transition_group_id": "group-cdf973e7-cab4-4a13-83bd-1a615dc642e2", + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-5a27510e-031f-4255-a500-59d190274e44", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Path to the explicit .todo/dependency-update-* workspace." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ], "edges": [ @@ -1285,7 +1393,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1293,7 +1401,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1394,6 +1502,74 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "edge_id": "edge-7fa56ec2-0d6b-4529-9e56-0d0fdd69ffe7", + "input_bindings": [ + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-2a67a605-53e4-4bd5-8219-917934b132ef", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Path to the explicit .todo/dependency-update-* workspace." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ] } diff --git a/.kent/workflows/puber-feature-delivery.json b/.kent/workflows/puber-feature-delivery.json index 1c2c3f5..88f2bed 100644 --- a/.kent/workflows/puber-feature-delivery.json +++ b/.kent/workflows/puber-feature-delivery.json @@ -3,7 +3,7 @@ "id": "workflow-fff53dd7-12b2-4506-af57-6493008f6ff9", "name": "Puber Feature Delivery", "description": "Plan, implement, audit, fix, optionally smoke-test, and conservatively clean up a Puber Android feature.", - "version": 74 + "version": 87 }, "nodes": [ { @@ -309,6 +309,30 @@ "transition_id": "blocked", "display_name": "Blocked", "description": "CI monitoring is blocked by GitHub access, missing check data, or unsafe repository state." + }, + { + "id": "group-55863a97-3ddc-465a-92c2-94389c992537", + "workflow_id": "workflow-fff53dd7-12b2-4506-af57-6493008f6ff9", + "source_node_id": "node-c09db099-fbc4-474b-be0f-2e04447e7bd4", + "transition_id": "needs_changes", + "display_name": "Needs Changes", + "description": "PR creation/update hit a recoverable branch, rebase, conflict, or metadata issue; user approval required before recovery work." + }, + { + "id": "group-2289b446-bf32-4815-87e2-e561447f772d", + "workflow_id": "workflow-fff53dd7-12b2-4506-af57-6493008f6ff9", + "source_node_id": "node-e83dfbfe-4816-4a05-933a-703b38648ef8", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "CI or GitHub is temporarily unavailable, still starting, or missing an expected run; user approval required before retrying monitoring." + }, + { + "id": "group-fa1d36b6-c72c-4828-8116-333044f4c7c8", + "workflow_id": "workflow-fff53dd7-12b2-4506-af57-6493008f6ff9", + "source_node_id": "node-3f862fff-37e7-47e3-810c-46efe5cdafce", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "Device, MCP, build, or emulator-lock access is temporarily unavailable; user approval required before retrying smoke." } ], "edges": [ @@ -468,7 +492,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Run a focused Puber device smoke test.\n\nRead .kent/project-contract.md first. Use .kent/commands/smoke-test.md for workspace {{.Params.workspace_path}} and audit report {{.Params.audit_report}}. Before touching any emulator/device, use .kent/adapters/mobile/emulator-resource-lock.sh to discover running adb emulators and acquire any free emulator-specific lock. Pass the selected serial to every adb command with adb -s. Physical devices, including a real TV, are forbidden unless the task/user explicitly names or allows that physical device. Never rely on adb default target selection. If all running emulators are busy, inspect lock owners and start a second emulator only when the task/user explicitly allows parallel device usage and you can acquire a distinct lock for it. Install a trap immediately after acquiring a lock so it is released on failure.\n\nAlways build a fresh devDebug APK before device testing, install it with explicit adb -s \"$DEVICE_SERIAL\" install -r app/build/outputs/apk/dev/debug/app-dev-debug.apk, force-stop the app, and launch com.kino.puber.stage/com.kino.puber.MainActivity. Do not use Gradle install* tasks for smoke tests. Use Mobile MCP through .kent/adapters/mcp wrappers, save useful artifacts under build/test-artifacts or the feature workspace, and release the mobile resource lock after reporting.\n\nComplete with done if smoke passes. Complete with needs_changes if smoke finds implementation issues and provide workspace_path, audit_report, and smoke_report. Complete with blocked and blocker_reason if device/MCP/build/resource locking access is unavailable.", + "prompt_template": "Run a focused Puber device smoke test.\n\nRead .kent/project-contract.md first. Use .kent/commands/smoke-test.md for workspace {{.Params.workspace_path}} and audit report {{.Params.audit_report}}. Before touching any emulator/device, use .kent/adapters/mobile/emulator-resource-lock.sh to discover running adb emulators and acquire any free emulator-specific lock. Pass the selected serial to every adb command with adb -s. Physical devices, including a real TV, are forbidden unless the task/user explicitly names or allows that physical device. Never rely on adb default target selection. If all running emulators are busy, inspect lock owners and start a second emulator only when the task/user explicitly allows parallel device usage and you can acquire a distinct lock for it. Install a trap immediately after acquiring a lock so it is released on failure.\n\nAlways build a fresh devDebug APK before device testing, install it with explicit adb -s \"$DEVICE_SERIAL\" install -r app/build/outputs/apk/dev/debug/app-dev-debug.apk, force-stop the app, and launch com.kino.puber.stage/com.kino.puber.MainActivity. Do not use Gradle install* tasks for smoke tests. Use Mobile MCP through .kent/adapters/mcp wrappers, save useful artifacts under build/test-artifacts or the feature workspace, and release the mobile resource lock after reporting.\n\nComplete with done if smoke passes. Complete with needs_changes if smoke finds implementation issues and provide workspace_path, audit_report, and smoke_report. Complete with retry_later if device/MCP/build/resource locking access is temporarily unavailable and provide workspace_path, audit_report, and blocker_reason. Complete with blocked only for unrecoverable smoke blockers that cannot be solved by waiting, freeing an emulator, or user approval; provide blocker_reason.", "parameters": [ { "key": "workspace_path", @@ -659,7 +683,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked if PR creation/update cannot be completed safely and provide blocker_reason.", + "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with needs_changes when PR creation/update is blocked by recoverable repository, branch, or PR state such as merge conflicts, non-fast-forward push, branch needing rebase, or missing PR metadata that can be fixed in the task worktree; provide workspace_path and blocker_reason. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked only when continuing would be unsafe without changing task scope, credentials, or project policy; provide blocker_reason.", "parameters": [ { "key": "compliance_report", @@ -719,11 +743,11 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with blocked if checks cannot be inspected and the risk cannot be accepted; provide blocker_reason.", + "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later when checks cannot be inspected because GitHub/CI is still starting, rate-limited, temporarily unavailable, or an expected run has not appeared yet; provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if checks cannot be inspected and retrying would be unsafe or misleading; provide blocker_reason.", "parameters": [ { "key": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "key": "branch_name", @@ -731,7 +755,7 @@ }, { "key": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -835,6 +859,87 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "id": "edge-0cadaa9a-929c-4be1-8a1a-0a5ac37e35f9", + "workflow_id": "workflow-fff53dd7-12b2-4506-af57-6493008f6ff9", + "transition_group_id": "group-55863a97-3ddc-465a-92c2-94389c992537", + "key": "pr_fix", + "target_node_id": "node-9062f417-dae0-4134-8ce6-941b08acd8c9", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Fix recoverable Puber PR creation/update issues only.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Previous PR blocker: {{.Params.blocker_reason}}. Work in {{.Params.workspace_path}}. Apply only the minimum changes needed to make the task branch/PR shippable, such as rebasing, resolving merge conflicts, fixing branch metadata, or repairing the PR-ready commit set. Do not expand product scope. Verify narrowly, then continue through the workflow's normal review/compliance path. Complete with blocked only if the recovery is unsafe or requires user credentials/policy changes.", + "parameters": [ + { + "key": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "key": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-8c123a54-1084-4840-9bed-b33875a8c593", + "workflow_id": "workflow-fff53dd7-12b2-4506-af57-6493008f6ff9", + "transition_group_id": "group-2289b446-bf32-4815-87e2-e561447f772d", + "key": "ci_retry", + "target_node_id": "node-e83dfbfe-4816-4a05-933a-703b38648ef8", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry Puber PR check monitoring after an approved temporary CI/GitHub wait.\n\nRead .kent/project-contract.md and AGENTS.md first. Previous CI status: {{.Params.ci_report}}. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}} again. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later if CI/GitHub is still temporarily unavailable and provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if retrying is no longer useful or safe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "key": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "key": "workspace_path", + "description": "Path to the explicit .todo/ workspace." + }, + { + "key": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "id": "edge-c1c066fd-7607-4896-a4cf-50ee1135e488", + "workflow_id": "workflow-fff53dd7-12b2-4506-af57-6493008f6ff9", + "transition_group_id": "group-fa1d36b6-c72c-4828-8116-333044f4c7c8", + "key": "smoke_retry", + "target_node_id": "node-3f862fff-37e7-47e3-810c-46efe5cdafce", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry the focused Puber device smoke test after an approved temporary blocker wait.\n\nRead .kent/project-contract.md and .kent/commands/smoke-test.md first. Previous blocker: {{.Params.blocker_reason}}. Use workspace {{.Params.workspace_path}} and audit report {{.Params.audit_report}}. Follow the same emulator locking, physical-device prohibition, fresh devDebug build/install, and adb -s rules as the original smoke node.\n\nComplete with done if smoke passes. Complete with needs_changes if smoke finds implementation issues and provide workspace_path, audit_report, and smoke_report. Complete with retry_later if device/MCP/build/resource locking access is still temporarily unavailable and provide workspace_path, audit_report, and blocker_reason. Complete with blocked only for unrecoverable smoke blockers; provide blocker_reason.", + "parameters": [ + { + "key": "workspace_path", + "description": "Path to the explicit .todo/ workspace." + }, + { + "key": "audit_report", + "description": "Path to the audit report for the feature workspace." + }, + { + "key": "blocker_reason", + "description": "Short human-readable blocker explanation. Use the same language as the task/user request when practical; include the missing access/input/check and next action needed." + } + ] } ], "derived_wiring": { @@ -969,7 +1074,7 @@ "possible_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -977,7 +1082,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." }, { "name": "pr_report", @@ -1007,6 +1112,10 @@ { "name": "blocker_reason", "description": "Why CI monitoring cannot complete, in the task language when practical." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." } ] }, @@ -1233,7 +1342,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1241,7 +1350,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1297,6 +1406,57 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "transition_group_id": "group-55863a97-3ddc-465a-92c2-94389c992537", + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-2289b446-bf32-4815-87e2-e561447f772d", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Path to the explicit .todo/ workspace." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "transition_group_id": "group-fa1d36b6-c72c-4828-8116-333044f4c7c8", + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the explicit .todo/ workspace." + }, + { + "name": "audit_report", + "description": "Path to the audit report for the feature workspace." + }, + { + "name": "blocker_reason", + "description": "Short human-readable blocker explanation. Use the same language as the task/user request when practical; include the missing access/input/check and next action needed." + } + ] } ], "edges": [ @@ -1721,7 +1881,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1729,7 +1889,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1830,6 +1990,108 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "edge_id": "edge-0cadaa9a-929c-4be1-8a1a-0a5ac37e35f9", + "input_bindings": [ + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-8c123a54-1084-4840-9bed-b33875a8c593", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Path to the explicit .todo/ workspace." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "edge_id": "edge-c1c066fd-7607-4896-a4cf-50ee1135e488", + "input_bindings": [ + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "audit_report", + "source": "transition_output", + "field": "audit_report" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the explicit .todo/ workspace." + }, + { + "name": "audit_report", + "description": "Path to the audit report for the feature workspace." + }, + { + "name": "blocker_reason", + "description": "Short human-readable blocker explanation. Use the same language as the task/user request when practical; include the missing access/input/check and next action needed." + } + ] } ] } diff --git a/.kent/workflows/puber-refactor-with-audit.json b/.kent/workflows/puber-refactor-with-audit.json index d54c4c0..d097f8b 100644 --- a/.kent/workflows/puber-refactor-with-audit.json +++ b/.kent/workflows/puber-refactor-with-audit.json @@ -3,7 +3,7 @@ "id": "workflow-bed92924-4c7e-4c24-8322-aa29d26573ca", "name": "Puber Refactor With Audit", "description": "Plan, audit, implement, review, fix, and conservatively clean up a Puber Android refactor or migration.", - "version": 60 + "version": 68 }, "nodes": [ { @@ -268,6 +268,22 @@ "transition_id": "blocked", "display_name": "Blocked", "description": "CI monitoring is blocked by GitHub access, missing check data, or unsafe repository state." + }, + { + "id": "group-3f2ad7e9-f194-4210-aef9-41defb759b7d", + "workflow_id": "workflow-bed92924-4c7e-4c24-8322-aa29d26573ca", + "source_node_id": "node-c3d52a51-d482-47ab-993a-45ad30ce22c9", + "transition_id": "needs_changes", + "display_name": "Needs Changes", + "description": "PR creation/update hit a recoverable branch, rebase, conflict, or metadata issue; user approval required before recovery work." + }, + { + "id": "group-86b8ea6e-fecf-4219-ad3f-578d91728364", + "workflow_id": "workflow-bed92924-4c7e-4c24-8322-aa29d26573ca", + "source_node_id": "node-92b2e510-465a-4f75-bb6d-4b2d0413d59c", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "CI or GitHub is temporarily unavailable, still starting, or missing an expected run; user approval required before retrying monitoring." } ], "edges": [ @@ -547,7 +563,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked if PR creation/update cannot be completed safely and provide blocker_reason.", + "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with needs_changes when PR creation/update is blocked by recoverable repository, branch, or PR state such as merge conflicts, non-fast-forward push, branch needing rebase, or missing PR metadata that can be fixed in the task worktree; provide workspace_path and blocker_reason. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked only when continuing would be unsafe without changing task scope, credentials, or project policy; provide blocker_reason.", "parameters": [ { "key": "compliance_report", @@ -607,11 +623,11 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with blocked if checks cannot be inspected and the risk cannot be accepted; provide blocker_reason.", + "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later when checks cannot be inspected because GitHub/CI is still starting, rate-limited, temporarily unavailable, or an expected run has not appeared yet; provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if checks cannot be inspected and retrying would be unsafe or misleading; provide blocker_reason.", "parameters": [ { "key": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "key": "branch_name", @@ -619,7 +635,7 @@ }, { "key": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -723,6 +739,60 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "id": "edge-d58e8c4b-3571-491b-ae44-8b767839eef4", + "workflow_id": "workflow-bed92924-4c7e-4c24-8322-aa29d26573ca", + "transition_group_id": "group-3f2ad7e9-f194-4210-aef9-41defb759b7d", + "key": "pr_fix", + "target_node_id": "node-813ddb92-db58-4688-b8c7-77b8134f31be", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Fix recoverable Puber PR creation/update issues only.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Previous PR blocker: {{.Params.blocker_reason}}. Work in {{.Params.workspace_path}}. Apply only the minimum changes needed to make the task branch/PR shippable, such as rebasing, resolving merge conflicts, fixing branch metadata, or repairing the PR-ready commit set. Do not expand product scope. Verify narrowly, then continue through the workflow's normal review/compliance path. Complete with blocked only if the recovery is unsafe or requires user credentials/policy changes.", + "parameters": [ + { + "key": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "key": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-f179b95f-a2a5-4d7f-9614-2dea3124b5d2", + "workflow_id": "workflow-bed92924-4c7e-4c24-8322-aa29d26573ca", + "transition_group_id": "group-86b8ea6e-fecf-4219-ad3f-578d91728364", + "key": "ci_retry", + "target_node_id": "node-92b2e510-465a-4f75-bb6d-4b2d0413d59c", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry Puber PR check monitoring after an approved temporary CI/GitHub wait.\n\nRead .kent/project-contract.md and AGENTS.md first. Previous CI status: {{.Params.ci_report}}. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}} again. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later if CI/GitHub is still temporarily unavailable and provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if retrying is no longer useful or safe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "key": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "key": "workspace_path", + "description": "Workspace containing the implementation to fix." + }, + { + "key": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ], "derived_wiring": { @@ -856,7 +926,7 @@ "possible_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -864,7 +934,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." }, { "name": "pr_report", @@ -894,6 +964,10 @@ { "name": "blocker_reason", "description": "Why CI monitoring cannot complete, in the task language when practical." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." } ] }, @@ -1088,7 +1162,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1096,7 +1170,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1152,6 +1226,40 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "transition_group_id": "group-3f2ad7e9-f194-4210-aef9-41defb759b7d", + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-86b8ea6e-fecf-4219-ad3f-578d91728364", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Workspace containing the implementation to fix." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ], "edges": [ @@ -1521,7 +1629,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1529,7 +1637,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1630,6 +1738,74 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "edge_id": "edge-d58e8c4b-3571-491b-ae44-8b767839eef4", + "input_bindings": [ + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-f179b95f-a2a5-4d7f-9614-2dea3124b5d2", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Workspace containing the implementation to fix." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ] } diff --git a/.kent/workflows/puber-release.json b/.kent/workflows/puber-release.json index 6c01c94..285a6c9 100644 --- a/.kent/workflows/puber-release.json +++ b/.kent/workflows/puber-release.json @@ -3,7 +3,7 @@ "id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", "name": "Puber Release", "description": "Prepare the next Puber release, create the version bump PR, monitor CI, then publish the release tag after approval.", - "version": 53 + "version": 77 }, "nodes": [ { @@ -219,6 +219,54 @@ "transition_id": "done", "display_name": "Done", "description": "Cleanup report is ready; finish the release task." + }, + { + "id": "group-a8b20339-ff99-40a1-ae5c-52eee6de1f0c", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "source_node_id": "node-77c2b3c6-04e4-4e03-a964-2aa5c47259c8", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "Required compliance sources or user context are temporarily unavailable; user approval required before retrying review." + }, + { + "id": "group-e21a073b-10f2-402b-94e5-8b1239288c6a", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "source_node_id": "node-b72f177e-2ffe-4bf9-a018-ba65361119fa", + "transition_id": "needs_changes", + "display_name": "Needs Changes", + "description": "Release PR creation/update hit a recoverable branch, rebase, conflict, push, or metadata issue; user approval required before recovery work." + }, + { + "id": "group-bc018baf-d599-4df5-a3f4-b6e992f46491", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "source_node_id": "node-8497fa66-4fc6-457d-8c0b-7e1da150bd26", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "Release CI or GitHub is temporarily unavailable, still starting, or missing an expected run; user approval required before retrying monitoring." + }, + { + "id": "group-c1e0d59b-b781-4d62-9ffc-ee47fabdfe95", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "source_node_id": "node-feeb41cd-253b-401b-add9-888b0237abe9", + "transition_id": "not_ready", + "display_name": "Not Ready", + "description": "Tag publication was approved before the release PR was merged or visible on origin/master; return to release PR monitoring after user action." + }, + { + "id": "group-5871795e-997a-4747-ae57-c74588cabd1d", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "source_node_id": "node-feeb41cd-253b-401b-add9-888b0237abe9", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "Release tag publication hit a temporary external availability issue; user approval required before retrying publication." + }, + { + "id": "group-94f3424d-ea81-4e04-b33f-d79fcb548a83", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "source_node_id": "node-f5349410-9178-4fb1-a909-0b5dad73cfe4", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "Release automation is still starting or temporarily unavailable; user approval required before retrying monitoring." } ], "edges": [ @@ -246,7 +294,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Run mandatory Puber Compliance Review for the prepared release.\n\nRead .kent/commands/compliance-review.md, .kent/project-contract.md, .kent/commands/release-branch.md, .kent/commands/release-tag.md, and AGENTS.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Reviewed scope: release {{.Params.release_version}} ({{.Params.release_type}}) on branch {{.Params.release_branch}} in workspace {{.Params.workspace_path}}, commit {{.Params.version_bump_commit}}, intended tag {{.Params.release_tag}}, verification {{.Params.verification_summary}}. Review only compliance with release rules, task requirements, AGENTS.md, and workflow contract. Do not edit files.\n\nComplete with done and provide compliance_report when no compliance violations are found. Complete with needs_changes and provide workspace_path plus compliance_report when release preparation must be fixed. Complete with blocked and provide blocker_reason if required sources are missing or contradictory.", + "prompt_template": "Run mandatory Puber Compliance Review for the prepared release.\n\nRead .kent/commands/compliance-review.md, .kent/project-contract.md, .kent/commands/release-branch.md, .kent/commands/release-tag.md, and AGENTS.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Reviewed scope: release {{.Params.release_version}} ({{.Params.release_type}}) on branch {{.Params.release_branch}} in workspace {{.Params.workspace_path}}, commit {{.Params.version_bump_commit}}, intended tag {{.Params.release_tag}}, verification {{.Params.verification_summary}}. Review only compliance with release rules, task requirements, AGENTS.md, and workflow contract. Do not edit files.\n\nComplete with done and provide compliance_report when no compliance violations are found. Complete with needs_changes and provide workspace_path plus compliance_report when release preparation must be fixed. Complete with retry_later when required sources or user-provided context are temporarily unavailable and provide release_version, release_type, release_branch, release_tag, workspace_path, version_bump_commit, verification_summary, and blocker_reason. Complete with blocked only if required sources are contradictory or continuing would be unsafe; provide blocker_reason.", "parameters": [ { "key": "release_version", @@ -307,7 +355,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Create or update the Puber release version-bump PR.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, .kent/commands/release-branch.md, .kent/commands/release-tag.md, and AGENTS.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Compliance result: {{.Params.compliance_report}}. Release context from preparation: version {{.Params.compliance.release_version}}, type {{.Params.compliance.release_type}}, branch {{.Params.compliance.release_branch}}, tag {{.Params.compliance.release_tag}}, workspace {{.Params.compliance.workspace_path}}.\n\nCommit only release-task changes if anything remains uncommitted, push {{.Params.compliance.release_branch}} to origin, and create or update a GitHub PR for the version bump. Do not merge the PR. Do not push directly to master. Do not create or push a release tag in this node.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, workspace_path, release_version, and release_tag. Complete with blocked if PR creation/update cannot be completed safely and provide blocker_reason.", + "prompt_template": "Create or update the Puber release version-bump PR.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, .kent/commands/release-branch.md, .kent/commands/release-tag.md, and AGENTS.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Compliance result: {{.Params.compliance_report}}. Release context from preparation: version {{.Params.compliance.release_version}}, type {{.Params.compliance.release_type}}, branch {{.Params.compliance.release_branch}}, tag {{.Params.compliance.release_tag}}, workspace {{.Params.compliance.workspace_path}}.\n\nCommit only release-task changes if anything remains uncommitted, push {{.Params.compliance.release_branch}} to origin, and create or update a GitHub PR for the version bump. Do not merge the PR. Do not push directly to master. Do not create or push a release tag in this node.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, workspace_path, release_version, and release_tag. Complete with needs_changes when PR creation/update is blocked by recoverable branch, rebase, conflict, push, or PR metadata state; provide workspace_path, release_version, release_type, release_branch, release_tag, and blocker_reason. Complete with blocked only if PR creation/update cannot continue safely without changing release scope, credentials, or project policy; provide blocker_reason.", "parameters": [ { "key": "compliance_report", @@ -367,7 +415,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Monitor the Puber release PR checks.\n\nRead .kent/project-contract.md, .kent/commands/release-branch.md, and .kent/commands/release-tag.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Inspect PR {{.Params.pr_url}} for release {{.Params.release_version}} / tag {{.Params.release_tag}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with publish when checks pass or are intentionally absent; this transition requires user approval before tag publication. Provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with needs_changes if checks failed due to release changes and provide workspace_path plus ci_report. Complete with blocked and provide blocker_reason if checks cannot be inspected and risk cannot be accepted.", + "prompt_template": "Monitor the Puber release PR checks.\n\nRead .kent/project-contract.md, .kent/commands/release-branch.md, and .kent/commands/release-tag.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Inspect PR {{.Params.pr_url}} for release {{.Params.release_version}} / tag {{.Params.release_tag}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with publish when checks pass or are intentionally absent; this transition requires user approval before tag publication. Provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with needs_changes if checks failed due to release changes and provide workspace_path plus ci_report. Complete with retry_later when checks cannot be inspected because GitHub/CI is still starting, rate-limited, temporarily unavailable, or an expected run has not appeared yet; provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with blocked only if checks cannot be inspected and retrying would be unsafe or misleading; provide blocker_reason.", "parameters": [ { "key": "pr_url", @@ -420,7 +468,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Publish the approved Puber release tag.\n\nRead .kent/project-contract.md, .kent/commands/release-tag.md, and AGENTS.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Before creating any tag, fetch origin/master and verify that PR {{.Params.pr_url}} is merged and origin/master contains currentVersion {{.Params.release_version}}. If the PR is not merged or master does not contain the version bump, complete blocked with blocker_reason. Verify {{.Params.release_tag}} does not exist locally/remotely unless it already points to the intended master commit. Create and push {{.Params.release_tag}} on the verified origin/master commit. Do not merge PRs and do not push directly to master.\n\nComplete with monitor and provide release_version, release_tag, target_commit, pr_url, and tag_push_status. Complete with blocked and provide blocker_reason if publication is unsafe.", + "prompt_template": "Publish the approved Puber release tag.\n\nRead .kent/project-contract.md, .kent/commands/release-tag.md, and AGENTS.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Before creating any tag, fetch origin/master and verify that PR {{.Params.pr_url}} is merged and origin/master contains currentVersion {{.Params.release_version}}. Verify {{.Params.release_tag}} does not exist locally/remotely unless it already points to the intended master commit. Create and push {{.Params.release_tag}} on the verified origin/master commit. Do not merge PRs and do not push directly to master.\n\nComplete with monitor and provide release_version, release_tag, target_commit, pr_url, and tag_push_status when the tag is pushed or already correct. Complete with not_ready if the PR is not merged yet or origin/master does not yet contain the version bump; provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with retry_later for temporary GitHub/network/tag-push availability problems where retrying later is safe; provide pr_url, release_version, release_tag, branch_name, workspace_path, ci_report, and blocker_reason. Complete with blocked only if publication is unsafe, such as the tag already pointing to a different commit; provide blocker_reason.", "parameters": [ { "key": "pr_url", @@ -500,7 +548,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Monitor Puber release automation after tag publication.\n\nRead .kent/project-contract.md and .kent/commands/release-tag.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Check available GitHub/CI/release automation for {{.Params.release_tag}} when configured. If no automation is configured, report that monitoring is skipped. Complete with done if green or intentionally skipped; complete with blocked and provide blocker_reason if automation fails or cannot be inspected.", + "prompt_template": "Monitor Puber release automation after tag publication.\n\nRead .kent/project-contract.md and .kent/commands/release-tag.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Check available GitHub/CI/release automation for {{.Params.release_tag}} when configured. If no automation is configured, report that monitoring is skipped. Complete with done if green or intentionally skipped. Complete with retry_later if release automation is still starting or temporarily unavailable and provide release_version, release_tag, target_commit, pr_url, tag_push_status, and release_report. Complete with blocked only if automation fails or cannot be inspected after retry would be misleading; provide blocker_reason.", "parameters": [ { "key": "release_version", @@ -596,6 +644,252 @@ "description": "Summary of cleanup performed or deliberately skipped." } ] + }, + { + "id": "edge-cf64d3bb-e98a-4d84-ba3f-783f6e64ed78", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "transition_group_id": "group-a8b20339-ff99-40a1-ae5c-52eee6de1f0c", + "key": "compliance_retry", + "target_node_id": "node-77c2b3c6-04e4-4e03-a964-2aa5c47259c8", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry mandatory Puber Compliance Review for the prepared release after an approved temporary blocker wait.\n\nRead .kent/commands/compliance-review.md, .kent/project-contract.md, .kent/commands/release-branch.md, .kent/commands/release-tag.md, and AGENTS.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance, but do not block solely because that file is absent. Previous blocker: {{.Params.blocker_reason}}. Reviewed scope: release {{.Params.release_version}} ({{.Params.release_type}}) on branch {{.Params.release_branch}} in workspace {{.Params.workspace_path}}, commit {{.Params.version_bump_commit}}, intended tag {{.Params.release_tag}}, verification {{.Params.verification_summary}}. Review only compliance with release rules, task requirements, AGENTS.md, and workflow contract. Do not edit files.\n\nComplete with done and provide compliance_report when no compliance violations are found. Complete with needs_changes and provide workspace_path plus compliance_report when release preparation must be fixed. Complete with retry_later if required sources or user-provided context are still temporarily unavailable and provide release_version, release_type, release_branch, release_tag, workspace_path, version_bump_commit, verification_summary, and blocker_reason. Complete with blocked only if continuing is unsafe; provide blocker_reason.", + "parameters": [ + { + "key": "release_version", + "description": "Prepared release version, for example 1.4.0." + }, + { + "key": "release_type", + "description": "Version bump type: minor by default, patch or major only when explicitly requested." + }, + { + "key": "release_branch", + "description": "Release branch containing the version bump." + }, + { + "key": "release_tag", + "description": "Release tag that should be created after the PR is merged, for example v1.4.0." + }, + { + "key": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "key": "version_bump_commit", + "description": "Commit hash containing the version bump." + }, + { + "key": "verification_summary", + "description": "Commands run and their pass/fail results." + }, + { + "key": "blocker_reason", + "description": "Why compliance review cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-6018e22d-2198-45ed-a4b3-bdfe93775e78", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "transition_group_id": "group-e21a073b-10f2-402b-94e5-8b1239288c6a", + "key": "pr_fix", + "target_node_id": "node-86293208-dd6b-4ff6-89d4-d6567a8b4e55", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Fix recoverable Puber release PR creation/update issues only.\n\nRead .kent/project-contract.md, .kent/commands/release-branch.md, .kent/commands/release-tag.md, .kent/commands/ship-pr.md, and AGENTS.md first. If .kent/commands/release.md exists, use it as supplementary unified guidance. Previous PR blocker: {{.Params.blocker_reason}}. Release context: version {{.Params.release_version}}, type {{.Params.release_type}}, branch {{.Params.release_branch}}, tag {{.Params.release_tag}}, workspace {{.Params.workspace_path}}.\n\nApply only the minimum changes needed to make the release branch/PR shippable, such as rebasing, resolving conflicts, fixing branch metadata, or repairing the version-bump commit. Do not create or push a release tag. Verify narrowly, then complete with compliance and provide release_version, release_type, release_branch, release_tag, workspace_path, version_bump_commit, and verification_summary. Complete with blocked only if recovery is unsafe or requires user credentials/policy changes.", + "parameters": [ + { + "key": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "key": "release_version", + "description": "Release version prepared by this workflow." + }, + { + "key": "release_type", + "description": "Version bump type: minor by default, patch or major only when explicitly requested." + }, + { + "key": "release_branch", + "description": "Release branch containing the version bump." + }, + { + "key": "release_tag", + "description": "Release tag to create after the PR is merged." + }, + { + "key": "blocker_reason", + "description": "Why release PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-75fce052-321b-4f1a-855b-ce692f89e46b", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "transition_group_id": "group-bc018baf-d599-4df5-a3f4-b6e992f46491", + "key": "ci_retry", + "target_node_id": "node-8497fa66-4fc6-457d-8c0b-7e1da150bd26", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry Puber release PR check monitoring after an approved temporary CI/GitHub wait.\n\nRead .kent/project-contract.md, .kent/commands/release-branch.md, and .kent/commands/release-tag.md first. Previous CI status: {{.Params.ci_report}}. Inspect PR {{.Params.pr_url}} for release {{.Params.release_version}} / tag {{.Params.release_tag}} on branch {{.Params.branch_name}} again. Do not merge the PR. Do not push new commits from this node.\n\nComplete with publish when checks pass or are intentionally absent; this transition requires user approval before tag publication. Provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with needs_changes if checks failed due to release changes and provide workspace_path plus ci_report. Complete with retry_later if CI/GitHub is still temporarily unavailable and provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with blocked only if retrying is no longer useful or safe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the release pull request." + }, + { + "key": "release_version", + "description": "Release version prepared by this workflow." + }, + { + "key": "release_tag", + "description": "Release tag to create after the PR is merged." + }, + { + "key": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "key": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "key": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "id": "edge-3772a318-3329-4c26-82d1-7118a7ba5932", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "transition_group_id": "group-c1e0d59b-b781-4d62-9ffc-ee47fabdfe95", + "key": "publish_not_ready", + "target_node_id": "node-8497fa66-4fc6-457d-8c0b-7e1da150bd26", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Resume Puber release PR monitoring after tag publication was approved too early.\n\nRead .kent/project-contract.md, .kent/commands/release-branch.md, and .kent/commands/release-tag.md first. Previous publication readiness report: {{.Params.ci_report}}. Monitor PR {{.Params.pr_url}} for release {{.Params.release_version}} / tag {{.Params.release_tag}} on branch {{.Params.branch_name}}. Do not merge the PR. Do not push new commits from this node.\n\nComplete with publish when checks pass or are intentionally absent and the PR is ready for user-approved tag publication; provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with needs_changes if checks failed due to release changes and provide workspace_path plus ci_report. Complete with retry_later if CI/GitHub is temporarily unavailable and provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with blocked only if retrying is no longer useful or safe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "key": "release_version", + "description": "Published release version." + }, + { + "key": "release_tag", + "description": "Pushed release tag." + }, + { + "key": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "key": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "key": "ci_report", + "description": "Publication readiness or CI/check status report." + } + ] + }, + { + "id": "edge-66a9f114-a244-4321-ace5-ffd759cc3af4", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "transition_group_id": "group-5871795e-997a-4747-ae57-c74588cabd1d", + "key": "publish_retry", + "target_node_id": "node-feeb41cd-253b-401b-add9-888b0237abe9", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry approved Puber release tag publication after a temporary external blocker.\n\nRead .kent/project-contract.md, .kent/commands/release-tag.md, and AGENTS.md first. Previous publication blocker: {{.Params.blocker_reason}}. Release context: PR {{.Params.pr_url}}, version {{.Params.release_version}}, tag {{.Params.release_tag}}, branch {{.Params.branch_name}}, workspace {{.Params.workspace_path}}. Before creating any tag, fetch origin/master and verify that the PR is merged and origin/master contains currentVersion {{.Params.release_version}}. Do not merge PRs and do not push directly to master.\n\nComplete with monitor and provide release_version, release_tag, target_commit, pr_url, and tag_push_status when the tag is pushed or already correct. Complete with not_ready if the PR is not merged yet or origin/master does not yet contain the version bump; provide pr_url, release_version, release_tag, branch_name, workspace_path, and ci_report. Complete with retry_later if the external publication blocker persists and retrying is safe; provide pr_url, release_version, release_tag, branch_name, workspace_path, ci_report, and blocker_reason. Complete with blocked only if publication is unsafe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "key": "release_version", + "description": "Published release version." + }, + { + "key": "release_tag", + "description": "Pushed release tag." + }, + { + "key": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "key": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "key": "ci_report", + "description": "Publication readiness or CI/check status report." + }, + { + "key": "blocker_reason", + "description": "Why release tag publication cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-1782e3e8-0549-4b08-b6cb-0750d6c35fc2", + "workflow_id": "workflow-42287243-1486-45e6-8f8d-3ecddba896be", + "transition_group_id": "group-94f3424d-ea81-4e04-b33f-d79fcb548a83", + "key": "monitor_retry", + "target_node_id": "node-f5349410-9178-4fb1-a909-0b5dad73cfe4", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry Puber release automation monitoring after an approved temporary wait.\n\nRead .kent/project-contract.md and .kent/commands/release-tag.md first. Previous release monitoring report: {{.Params.release_report}}. Check available GitHub/CI/release automation for {{.Params.release_tag}} on commit {{.Params.target_commit}} again. If no automation is configured, report that monitoring is skipped.\n\nComplete with done if green or intentionally skipped. Complete with retry_later if release automation is still starting or temporarily unavailable and provide release_version, release_tag, target_commit, pr_url, tag_push_status, and release_report. Complete with blocked only if automation fails or cannot be inspected after retry would be misleading; provide blocker_reason.", + "parameters": [ + { + "key": "release_version", + "description": "Published release version." + }, + { + "key": "release_tag", + "description": "Pushed release tag." + }, + { + "key": "target_commit", + "description": "Master commit targeted by the release tag." + }, + { + "key": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "key": "tag_push_status", + "description": "Result of local/remote tag creation and push." + }, + { + "key": "release_report", + "description": "Release publication and automation monitoring report." + } + ] } ], "derived_wiring": { @@ -654,18 +948,42 @@ { "name": "blocker_reason", "description": "Why compliance review cannot complete, in the task language when practical." - } - ] - }, - { - "node_id": "node-b72f177e-2ffe-4bf9-a018-ba65361119fa", - "possible_provision_fields": [ - { - "name": "pr_url", - "description": "URL of the release pull request." }, { - "name": "branch_name", + "name": "release_version", + "description": "Prepared release version, for example 1.4.0." + }, + { + "name": "release_type", + "description": "Version bump type: minor by default, patch or major only when explicitly requested." + }, + { + "name": "release_branch", + "description": "Release branch containing the version bump." + }, + { + "name": "release_tag", + "description": "Release tag that should be created after the PR is merged, for example v1.4.0." + }, + { + "name": "version_bump_commit", + "description": "Commit hash containing the version bump." + }, + { + "name": "verification_summary", + "description": "Commands run and their pass/fail results." + } + ] + }, + { + "node_id": "node-b72f177e-2ffe-4bf9-a018-ba65361119fa", + "possible_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the release pull request." + }, + { + "name": "branch_name", "description": "Name of the pushed release branch." }, { @@ -683,6 +1001,14 @@ { "name": "blocker_reason", "description": "Why release PR creation/update cannot complete, in the task language when practical." + }, + { + "name": "release_type", + "description": "Version bump type: minor by default, patch or major only when explicitly requested." + }, + { + "name": "release_branch", + "description": "Release branch containing the version bump." } ] }, @@ -745,6 +1071,18 @@ { "name": "blocker_reason", "description": "Why release tag publication cannot complete, in the task language when practical." + }, + { + "name": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "ci_report", + "description": "Publication readiness or CI/check status report." } ] }, @@ -758,6 +1096,26 @@ { "name": "blocker_reason", "description": "Why release automation monitoring cannot complete, in the task language when practical." + }, + { + "name": "release_version", + "description": "Published release version." + }, + { + "name": "release_tag", + "description": "Pushed release tag." + }, + { + "name": "target_commit", + "description": "Master commit targeted by the release tag." + }, + { + "name": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "name": "tag_push_status", + "description": "Result of local/remote tag creation and push." } ] }, @@ -999,6 +1357,192 @@ "description": "Summary of cleanup performed or deliberately skipped." } ] + }, + { + "transition_group_id": "group-a8b20339-ff99-40a1-ae5c-52eee6de1f0c", + "required_provision_fields": [ + { + "name": "release_version", + "description": "Prepared release version, for example 1.4.0." + }, + { + "name": "release_type", + "description": "Version bump type: minor by default, patch or major only when explicitly requested." + }, + { + "name": "release_branch", + "description": "Release branch containing the version bump." + }, + { + "name": "release_tag", + "description": "Release tag that should be created after the PR is merged, for example v1.4.0." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "version_bump_commit", + "description": "Commit hash containing the version bump." + }, + { + "name": "verification_summary", + "description": "Commands run and their pass/fail results." + }, + { + "name": "blocker_reason", + "description": "Why compliance review cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-e21a073b-10f2-402b-94e5-8b1239288c6a", + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "release_version", + "description": "Release version prepared by this workflow." + }, + { + "name": "release_type", + "description": "Version bump type: minor by default, patch or major only when explicitly requested." + }, + { + "name": "release_branch", + "description": "Release branch containing the version bump." + }, + { + "name": "release_tag", + "description": "Release tag to create after the PR is merged." + }, + { + "name": "blocker_reason", + "description": "Why release PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-bc018baf-d599-4df5-a3f4-b6e992f46491", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the release pull request." + }, + { + "name": "release_version", + "description": "Release version prepared by this workflow." + }, + { + "name": "release_tag", + "description": "Release tag to create after the PR is merged." + }, + { + "name": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "transition_group_id": "group-c1e0d59b-b781-4d62-9ffc-ee47fabdfe95", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "name": "release_version", + "description": "Published release version." + }, + { + "name": "release_tag", + "description": "Pushed release tag." + }, + { + "name": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "ci_report", + "description": "Publication readiness or CI/check status report." + } + ] + }, + { + "transition_group_id": "group-5871795e-997a-4747-ae57-c74588cabd1d", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "name": "release_version", + "description": "Published release version." + }, + { + "name": "release_tag", + "description": "Pushed release tag." + }, + { + "name": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "ci_report", + "description": "Publication readiness or CI/check status report." + }, + { + "name": "blocker_reason", + "description": "Why release tag publication cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-94f3424d-ea81-4e04-b33f-d79fcb548a83", + "required_provision_fields": [ + { + "name": "release_version", + "description": "Published release version." + }, + { + "name": "release_tag", + "description": "Pushed release tag." + }, + { + "name": "target_commit", + "description": "Master commit targeted by the release tag." + }, + { + "name": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "name": "tag_push_status", + "description": "Result of local/remote tag creation and push." + }, + { + "name": "release_report", + "description": "Release publication and automation monitoring report." + } + ] } ], "edges": [ @@ -1433,6 +1977,399 @@ "description": "Summary of cleanup performed or deliberately skipped." } ] + }, + { + "edge_id": "edge-cf64d3bb-e98a-4d84-ba3f-783f6e64ed78", + "input_bindings": [ + { + "name": "release_version", + "source": "transition_output", + "field": "release_version" + }, + { + "name": "release_type", + "source": "transition_output", + "field": "release_type" + }, + { + "name": "release_branch", + "source": "transition_output", + "field": "release_branch" + }, + { + "name": "release_tag", + "source": "transition_output", + "field": "release_tag" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "version_bump_commit", + "source": "transition_output", + "field": "version_bump_commit" + }, + { + "name": "verification_summary", + "source": "transition_output", + "field": "verification_summary" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "release_version", + "description": "Prepared release version, for example 1.4.0." + }, + { + "name": "release_type", + "description": "Version bump type: minor by default, patch or major only when explicitly requested." + }, + { + "name": "release_branch", + "description": "Release branch containing the version bump." + }, + { + "name": "release_tag", + "description": "Release tag that should be created after the PR is merged, for example v1.4.0." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "version_bump_commit", + "description": "Commit hash containing the version bump." + }, + { + "name": "verification_summary", + "description": "Commands run and their pass/fail results." + }, + { + "name": "blocker_reason", + "description": "Why compliance review cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-6018e22d-2198-45ed-a4b3-bdfe93775e78", + "input_bindings": [ + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "release_version", + "source": "transition_output", + "field": "release_version" + }, + { + "name": "release_type", + "source": "transition_output", + "field": "release_type" + }, + { + "name": "release_branch", + "source": "transition_output", + "field": "release_branch" + }, + { + "name": "release_tag", + "source": "transition_output", + "field": "release_tag" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "release_version", + "description": "Release version prepared by this workflow." + }, + { + "name": "release_type", + "description": "Version bump type: minor by default, patch or major only when explicitly requested." + }, + { + "name": "release_branch", + "description": "Release branch containing the version bump." + }, + { + "name": "release_tag", + "description": "Release tag to create after the PR is merged." + }, + { + "name": "blocker_reason", + "description": "Why release PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-75fce052-321b-4f1a-855b-ce692f89e46b", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "release_version", + "source": "transition_output", + "field": "release_version" + }, + { + "name": "release_tag", + "source": "transition_output", + "field": "release_tag" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the release pull request." + }, + { + "name": "release_version", + "description": "Release version prepared by this workflow." + }, + { + "name": "release_tag", + "description": "Release tag to create after the PR is merged." + }, + { + "name": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "edge_id": "edge-3772a318-3329-4c26-82d1-7118a7ba5932", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "release_version", + "source": "transition_output", + "field": "release_version" + }, + { + "name": "release_tag", + "source": "transition_output", + "field": "release_tag" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "name": "release_version", + "description": "Published release version." + }, + { + "name": "release_tag", + "description": "Pushed release tag." + }, + { + "name": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "ci_report", + "description": "Publication readiness or CI/check status report." + } + ] + }, + { + "edge_id": "edge-66a9f114-a244-4321-ace5-ffd759cc3af4", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "release_version", + "source": "transition_output", + "field": "release_version" + }, + { + "name": "release_tag", + "source": "transition_output", + "field": "release_tag" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "name": "release_version", + "description": "Published release version." + }, + { + "name": "release_tag", + "description": "Pushed release tag." + }, + { + "name": "branch_name", + "description": "Name of the pushed release branch." + }, + { + "name": "workspace_path", + "description": "Path to the worktree or checkout containing the release bump." + }, + { + "name": "ci_report", + "description": "Publication readiness or CI/check status report." + }, + { + "name": "blocker_reason", + "description": "Why release tag publication cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-1782e3e8-0549-4b08-b6cb-0750d6c35fc2", + "input_bindings": [ + { + "name": "release_version", + "source": "transition_output", + "field": "release_version" + }, + { + "name": "release_tag", + "source": "transition_output", + "field": "release_tag" + }, + { + "name": "target_commit", + "source": "transition_output", + "field": "target_commit" + }, + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "tag_push_status", + "source": "transition_output", + "field": "tag_push_status" + }, + { + "name": "release_report", + "source": "transition_output", + "field": "release_report" + } + ], + "required_provision_fields": [ + { + "name": "release_version", + "description": "Published release version." + }, + { + "name": "release_tag", + "description": "Pushed release tag." + }, + { + "name": "target_commit", + "description": "Master commit targeted by the release tag." + }, + { + "name": "pr_url", + "description": "URL of the merged release pull request." + }, + { + "name": "tag_push_status", + "description": "Result of local/remote tag creation and push." + }, + { + "name": "release_report", + "description": "Release publication and automation monitoring report." + } + ] } ] } diff --git a/.kent/workflows/puber-smoke-test.json b/.kent/workflows/puber-smoke-test.json index 466e63b..c27f164 100644 --- a/.kent/workflows/puber-smoke-test.json +++ b/.kent/workflows/puber-smoke-test.json @@ -3,7 +3,7 @@ "id": "workflow-0b5d2d8a-ce13-402c-98ff-11823327e173", "name": "Puber Smoke Test", "description": "Run focused Puber Android TV smoke tests, optionally fix approved findings, and clean up safely.", - "version": 56 + "version": 68 }, "nodes": [ { @@ -210,6 +210,30 @@ "transition_id": "blocked", "display_name": "Blocked", "description": "CI monitoring is blocked by GitHub access, missing check data, or unsafe repository state." + }, + { + "id": "group-6e4c4804-a437-4104-ae83-50636b721a45", + "workflow_id": "workflow-0b5d2d8a-ce13-402c-98ff-11823327e173", + "source_node_id": "node-5ada6e8d-1e36-4c6a-9ca2-1f7b6e334b59", + "transition_id": "needs_changes", + "display_name": "Needs Changes", + "description": "PR creation/update hit a recoverable branch, rebase, conflict, or metadata issue; user approval required before recovery work." + }, + { + "id": "group-577dc04b-4042-42a6-88ce-3b33ac7d929e", + "workflow_id": "workflow-0b5d2d8a-ce13-402c-98ff-11823327e173", + "source_node_id": "node-d4c0080d-88fe-49d3-b5b2-f7d662cc6be9", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "CI or GitHub is temporarily unavailable, still starting, or missing an expected run; user approval required before retrying monitoring." + }, + { + "id": "group-f0e033a4-349f-46be-bf5b-325feb0526e3", + "workflow_id": "workflow-0b5d2d8a-ce13-402c-98ff-11823327e173", + "source_node_id": "node-1ec7322f-4f0b-472d-87b3-129e4f184bb0", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "Device, MCP, build, or emulator-lock access is temporarily unavailable; user approval required before retrying smoke." } ], "edges": [ @@ -224,7 +248,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Run the Puber smoke-test flow for this Kent task.\n\nRead .kent/project-contract.md first, then use .kent/commands/smoke-test.md. Treat the task body as the target feature/screen and device notes. Before touching any emulator/device, use .kent/adapters/mobile/emulator-resource-lock.sh to discover running adb emulators and acquire any free emulator-specific lock. Pass the selected serial to every adb command with adb -s. Physical devices, including a real TV, are forbidden unless the task/user explicitly names or allows that physical device. Never rely on adb default target selection. If all running emulators are busy, inspect lock owners and start a second emulator only when the task/user explicitly allows parallel device usage and you can acquire a distinct lock for it. Install a trap immediately after acquiring a lock so it is released on failure.\n\nFollow AGENTS.md MCP Mobile Testing rules: always build a fresh devDebug APK before device testing, install it with explicit adb -s \"$DEVICE_SERIAL\" install -r app/build/outputs/apk/dev/debug/app-dev-debug.apk, force-stop the app, and launch com.kino.puber.stage/com.kino.puber.MainActivity. Do not use Gradle install* tasks for smoke tests. Do this even if the task/user says the app is already running. Prefer UI tree/assertions over screenshots, and save artifacts only for issues. Release the mobile resource lock after reporting.\n\nDo not edit production code in this smoke node. Complete with done if the smoke test passes or is accepted. Complete with needs_changes if you found an actionable app issue and provide smoke_report, reproduction_steps, and artifacts_path. Complete with blocked if device/MCP/build/access/resource locking is unavailable and provide blocker_reason." + "prompt_template": "Run the Puber smoke-test flow for this Kent task.\n\nRead .kent/project-contract.md first, then use .kent/commands/smoke-test.md. Treat the task body as the target feature/screen and device notes. Before touching any emulator/device, use .kent/adapters/mobile/emulator-resource-lock.sh to discover running adb emulators and acquire any free emulator-specific lock. Pass the selected serial to every adb command with adb -s. Physical devices, including a real TV, are forbidden unless the task/user explicitly names or allows that physical device. Never rely on adb default target selection. If all running emulators are busy, inspect lock owners and start a second emulator only when the task/user explicitly allows parallel device usage and you can acquire a distinct lock for it. Install a trap immediately after acquiring a lock so it is released on failure.\n\nFollow AGENTS.md MCP Mobile Testing rules: always build a fresh devDebug APK before device testing, install it with explicit adb -s \"$DEVICE_SERIAL\" install -r app/build/outputs/apk/dev/debug/app-dev-debug.apk, force-stop the app, and launch com.kino.puber.stage/com.kino.puber.MainActivity. Do not use Gradle install* tasks for smoke tests. Do this even if the task/user says the app is already running. Prefer UI tree/assertions over screenshots, and save artifacts only for issues. Release the mobile resource lock after reporting.\n\nDo not edit production code in this smoke node. Complete with done if the smoke test passes or is accepted. Complete with needs_changes if you found an actionable app issue and provide smoke_report, reproduction_steps, and artifacts_path. Complete with retry_later if device/MCP/build/access/resource locking is temporarily unavailable and provide blocker_reason. Complete with blocked only for unrecoverable smoke blockers that cannot be solved by waiting, freeing an emulator, or user approval; provide blocker_reason." }, { "id": "edge-16af50c0-868d-4b74-94c5-cebf92c4a1bb", @@ -358,7 +382,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked if PR creation/update cannot be completed safely and provide blocker_reason.", + "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with needs_changes when PR creation/update is blocked by recoverable repository, branch, or PR state such as merge conflicts, non-fast-forward push, branch needing rebase, or missing PR metadata that can be fixed in the task worktree; provide workspace_path and blocker_reason. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked only when continuing would be unsafe without changing task scope, credentials, or project policy; provide blocker_reason.", "parameters": [ { "key": "compliance_report", @@ -414,11 +438,11 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with blocked if checks cannot be inspected and the risk cannot be accepted; provide blocker_reason.", + "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later when checks cannot be inspected because GitHub/CI is still starting, rate-limited, temporarily unavailable, or an expected run has not appeared yet; provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if checks cannot be inspected and retrying would be unsafe or misleading; provide blocker_reason.", "parameters": [ { "key": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "key": "branch_name", @@ -426,7 +450,7 @@ }, { "key": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -530,6 +554,79 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "id": "edge-4e74cbe0-4b41-4658-84d3-ff92680a2867", + "workflow_id": "workflow-0b5d2d8a-ce13-402c-98ff-11823327e173", + "transition_group_id": "group-6e4c4804-a437-4104-ae83-50636b721a45", + "key": "pr_fix", + "target_node_id": "node-a8823a49-9b62-44f8-8ac2-3cc3d7a2eb08", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Fix recoverable Puber PR creation/update issues only.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Previous PR blocker: {{.Params.blocker_reason}}. Work in {{.Params.workspace_path}}. Apply only the minimum changes needed to make the task branch/PR shippable, such as rebasing, resolving merge conflicts, fixing branch metadata, or repairing the PR-ready commit set. Do not expand product scope. Verify narrowly, then continue through the workflow's normal review/compliance path. Complete with blocked only if the recovery is unsafe or requires user credentials/policy changes.", + "parameters": [ + { + "key": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "key": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-dc8e9bed-e437-494f-9de2-5aacde485b77", + "workflow_id": "workflow-0b5d2d8a-ce13-402c-98ff-11823327e173", + "transition_group_id": "group-577dc04b-4042-42a6-88ce-3b33ac7d929e", + "key": "ci_retry", + "target_node_id": "node-d4c0080d-88fe-49d3-b5b2-f7d662cc6be9", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry Puber PR check monitoring after an approved temporary CI/GitHub wait.\n\nRead .kent/project-contract.md and AGENTS.md first. Previous CI status: {{.Params.ci_report}}. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}} again. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later if CI/GitHub is still temporarily unavailable and provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if retrying is no longer useful or safe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "key": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "key": "workspace_path", + "description": "Path to the task workspace or worktree used for PR creation." + }, + { + "key": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "id": "edge-c8e20165-5c59-45db-a532-d05ae80c167e", + "workflow_id": "workflow-0b5d2d8a-ce13-402c-98ff-11823327e173", + "transition_group_id": "group-f0e033a4-349f-46be-bf5b-325feb0526e3", + "key": "smoke_retry", + "target_node_id": "node-1ec7322f-4f0b-472d-87b3-129e4f184bb0", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry the Puber smoke-test flow after an approved temporary blocker wait.\n\nRead .kent/project-contract.md and .kent/commands/smoke-test.md first. Previous blocker: {{.Params.blocker_reason}}. Treat the task body as the target feature/screen and device notes. Follow the same emulator locking, physical-device prohibition, fresh devDebug build/install, and adb -s rules as the original smoke node.\n\nDo not edit production code in this smoke node. Complete with done if the smoke test passes or is accepted. Complete with needs_changes if you found an actionable app issue and provide smoke_report, reproduction_steps, and artifacts_path. Complete with retry_later if device/MCP/build/access/resource locking is still temporarily unavailable and provide blocker_reason. Complete with blocked only for unrecoverable smoke blockers; provide blocker_reason.", + "parameters": [ + { + "key": "blocker_reason", + "description": "Why smoke testing cannot continue, in the task language when practical." + } + ] } ], "derived_wiring": { @@ -609,7 +706,7 @@ "possible_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -617,7 +714,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." }, { "name": "pr_report", @@ -647,6 +744,10 @@ { "name": "blocker_reason", "description": "Why CI monitoring cannot complete, in the task language when practical." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." } ] }, @@ -754,7 +855,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -762,7 +863,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -818,6 +919,49 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "transition_group_id": "group-6e4c4804-a437-4104-ae83-50636b721a45", + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-577dc04b-4042-42a6-88ce-3b33ac7d929e", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Path to the task workspace or worktree used for PR creation." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "transition_group_id": "group-f0e033a4-349f-46be-bf5b-325feb0526e3", + "required_provision_fields": [ + { + "name": "blocker_reason", + "description": "Why smoke testing cannot continue, in the task language when practical." + } + ] } ], "edges": [ @@ -1013,7 +1157,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1021,7 +1165,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1122,6 +1266,90 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "edge_id": "edge-4e74cbe0-4b41-4658-84d3-ff92680a2867", + "input_bindings": [ + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-dc8e9bed-e437-494f-9de2-5aacde485b77", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Path to the task workspace or worktree used for PR creation." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] + }, + { + "edge_id": "edge-c8e20165-5c59-45db-a532-d05ae80c167e", + "input_bindings": [ + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "blocker_reason", + "description": "Why smoke testing cannot continue, in the task language when practical." + } + ] } ] } diff --git a/.kent/workflows/puber-test-coverage.json b/.kent/workflows/puber-test-coverage.json index 719154b..c5f9606 100644 --- a/.kent/workflows/puber-test-coverage.json +++ b/.kent/workflows/puber-test-coverage.json @@ -3,7 +3,7 @@ "id": "workflow-139803ac-9f20-4f0a-9db3-6dfaf474566a", "name": "Puber Test Coverage", "description": "Plan, add, review, fix, and clean up focused Puber Android test coverage improvements.", - "version": 58 + "version": 66 }, "nodes": [ { @@ -268,6 +268,22 @@ "transition_id": "blocked", "display_name": "Blocked", "description": "CI monitoring is blocked by GitHub access, missing check data, or unsafe repository state." + }, + { + "id": "group-aabeeadc-dc6d-44be-bac3-85994483ec8a", + "workflow_id": "workflow-139803ac-9f20-4f0a-9db3-6dfaf474566a", + "source_node_id": "node-64b0c5de-26d8-478b-9629-f424e68454b6", + "transition_id": "needs_changes", + "display_name": "Needs Changes", + "description": "PR creation/update hit a recoverable branch, rebase, conflict, or metadata issue; user approval required before recovery work." + }, + { + "id": "group-82060b25-1237-42ba-a1f6-f608cc248c9e", + "workflow_id": "workflow-139803ac-9f20-4f0a-9db3-6dfaf474566a", + "source_node_id": "node-796ba26f-05bd-4ebf-99a9-b39cabf09d51", + "transition_id": "retry_later", + "display_name": "Retry Later", + "description": "CI or GitHub is temporarily unavailable, still starting, or missing an expected run; user approval required before retrying monitoring." } ], "edges": [ @@ -539,7 +555,7 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked if PR creation/update cannot be completed safely and provide blocker_reason.", + "prompt_template": "Create or update the Puber pull request for this completed task.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Compliance result: {{.Params.compliance_report}}.\n\nIf this task produced repository changes, commit only task-related files, push the current task branch to origin, and create or update a GitHub PR. Do not merge the PR. Do not push directly to master/main. If there are no repository changes and the task is report-only or smoke-only, complete with done and provide pr_report explaining why PR is not applicable.\n\nComplete with monitor_ci when a PR exists and provide pr_url, branch_name, and workspace_path. Complete with needs_changes when PR creation/update is blocked by recoverable repository, branch, or PR state such as merge conflicts, non-fast-forward push, branch needing rebase, or missing PR metadata that can be fixed in the task worktree; provide workspace_path and blocker_reason. Complete with done only for PR-not-applicable/no-diff cases and provide pr_report. Complete with blocked only when continuing would be unsafe without changing task scope, credentials, or project policy; provide blocker_reason.", "parameters": [ { "key": "compliance_report", @@ -599,11 +615,11 @@ "context_source": { "kind": "immediate_source" }, - "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with blocked if checks cannot be inspected and the risk cannot be accepted; provide blocker_reason.", + "prompt_template": "Monitor Puber PR checks.\n\nRead .kent/project-contract.md and AGENTS.md first. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}}. Use GitHub CLI when available. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later when checks cannot be inspected because GitHub/CI is still starting, rate-limited, temporarily unavailable, or an expected run has not appeared yet; provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if checks cannot be inspected and retrying would be unsafe or misleading; provide blocker_reason.", "parameters": [ { "key": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "key": "branch_name", @@ -611,7 +627,7 @@ }, { "key": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -715,6 +731,60 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "id": "edge-a3f77960-2d03-4750-b33d-d2fb6162a6a0", + "workflow_id": "workflow-139803ac-9f20-4f0a-9db3-6dfaf474566a", + "transition_group_id": "group-aabeeadc-dc6d-44be-bac3-85994483ec8a", + "key": "pr_fix", + "target_node_id": "node-d9f0e401-7a7b-4993-99a1-a18d3d075fee", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Fix recoverable Puber PR creation/update issues only.\n\nRead .kent/project-contract.md, .kent/commands/ship-pr.md, and AGENTS.md first. Previous PR blocker: {{.Params.blocker_reason}}. Work in {{.Params.workspace_path}}. Apply only the minimum changes needed to make the task branch/PR shippable, such as rebasing, resolving merge conflicts, fixing branch metadata, or repairing the PR-ready commit set. Do not expand product scope. Verify narrowly, then continue through the workflow's normal review/compliance path. Complete with blocked only if the recovery is unsafe or requires user credentials/policy changes.", + "parameters": [ + { + "key": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "key": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "id": "edge-a183ffc5-f22d-46e2-bb71-84e217252667", + "workflow_id": "workflow-139803ac-9f20-4f0a-9db3-6dfaf474566a", + "transition_group_id": "group-82060b25-1237-42ba-a1f6-f608cc248c9e", + "key": "ci_retry", + "target_node_id": "node-796ba26f-05bd-4ebf-99a9-b39cabf09d51", + "requires_approval": true, + "context_mode": "compact_and_continue_session", + "context_source": { + "kind": "immediate_source" + }, + "prompt_template": "Retry Puber PR check monitoring after an approved temporary CI/GitHub wait.\n\nRead .kent/project-contract.md and AGENTS.md first. Previous CI status: {{.Params.ci_report}}. Inspect PR {{.Params.pr_url}} on branch {{.Params.branch_name}} again. Do not merge the PR. Do not push new commits from this node.\n\nComplete with done if CI/checks pass, are absent, or are intentionally skipped with a clear reason; provide ci_report and pr_url. Complete with needs_changes if CI failed due to task changes and provide workspace_path plus ci_report. Complete with retry_later if CI/GitHub is still temporarily unavailable and provide pr_url, branch_name, workspace_path, and ci_report. Complete with blocked only if retrying is no longer useful or safe; provide blocker_reason.", + "parameters": [ + { + "key": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "key": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "key": "workspace_path", + "description": "Workspace containing the test coverage implementation." + }, + { + "key": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ], "derived_wiring": { @@ -840,7 +910,7 @@ "possible_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -848,7 +918,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." }, { "name": "pr_report", @@ -878,6 +948,10 @@ { "name": "blocker_reason", "description": "Why CI monitoring cannot complete, in the task language when practical." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." } ] }, @@ -1064,7 +1138,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1072,7 +1146,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1128,6 +1202,40 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "transition_group_id": "group-aabeeadc-dc6d-44be-bac3-85994483ec8a", + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "transition_group_id": "group-82060b25-1237-42ba-a1f6-f608cc248c9e", + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Workspace containing the test coverage implementation." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ], "edges": [ @@ -1479,7 +1587,7 @@ "required_provision_fields": [ { "name": "pr_url", - "description": "URL of the created or updated pull request." + "description": "URL of the pull request." }, { "name": "branch_name", @@ -1487,7 +1595,7 @@ }, { "name": "workspace_path", - "description": "Path to the task workspace or worktree used for PR creation." + "description": "Path to the worktree or checkout used for PR creation." } ] }, @@ -1588,6 +1696,74 @@ "description": "Why CI monitoring cannot complete, in the task language when practical." } ] + }, + { + "edge_id": "edge-a3f77960-2d03-4750-b33d-d2fb6162a6a0", + "input_bindings": [ + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "blocker_reason", + "source": "transition_output", + "field": "blocker_reason" + } + ], + "required_provision_fields": [ + { + "name": "workspace_path", + "description": "Path to the worktree or checkout used for PR creation." + }, + { + "name": "blocker_reason", + "description": "Why PR creation/update cannot complete, in the task language when practical." + } + ] + }, + { + "edge_id": "edge-a183ffc5-f22d-46e2-bb71-84e217252667", + "input_bindings": [ + { + "name": "pr_url", + "source": "transition_output", + "field": "pr_url" + }, + { + "name": "branch_name", + "source": "transition_output", + "field": "branch_name" + }, + { + "name": "workspace_path", + "source": "transition_output", + "field": "workspace_path" + }, + { + "name": "ci_report", + "source": "transition_output", + "field": "ci_report" + } + ], + "required_provision_fields": [ + { + "name": "pr_url", + "description": "URL of the created or updated pull request." + }, + { + "name": "branch_name", + "description": "Name of the pushed task branch." + }, + { + "name": "workspace_path", + "description": "Workspace containing the test coverage implementation." + }, + { + "name": "ci_report", + "description": "CI/check status report, including failures, skipped checks, or accepted absent checks." + } + ] } ] } From 2fb3f9964f21fd38602af5a0b1266904dde4fae9 Mon Sep 17 00:00:00 2001 From: max rovkin Date: Tue, 7 Jul 2026 23:05:45 +0500 Subject: [PATCH 2/2] Add player mark watched control --- .../interactor/player/PlayerInteractor.kt | 15 ++ .../player/component/PlayerButtonRow.kt | 34 +++- .../player/component/PlayerControlsOverlay.kt | 7 + .../player/component/PlayerOverlayLayers.kt | 3 + .../feature/player/component/PlayerPreview.kt | 18 ++ .../ui/feature/player/model/PlayerAction.kt | 1 + .../feature/player/model/PlayerViewState.kt | 2 + .../feature/player/vm/ContentStateFactory.kt | 3 + .../puber/ui/feature/player/vm/PlayerVM.kt | 144 ++++++++++++++-- app/src/main/res/values/player.xml | 2 + .../interactor/player/PlayerInteractorTest.kt | 57 +++++++ .../ui/feature/player/vm/PlayerVMTest.kt | 155 +++++++++++++++++- 12 files changed, 425 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/kino/puber/domain/interactor/player/PlayerInteractor.kt b/app/src/main/java/com/kino/puber/domain/interactor/player/PlayerInteractor.kt index d07e01e..4ea9d20 100644 --- a/app/src/main/java/com/kino/puber/domain/interactor/player/PlayerInteractor.kt +++ b/app/src/main/java/com/kino/puber/domain/interactor/player/PlayerInteractor.kt @@ -24,6 +24,7 @@ internal data class ResolvedMedia( val videoNumber: Int?, val episodeId: Int?, val episodeTitle: String?, + val isCurrentMediaWatched: Boolean, val isSeries: Boolean, val hasNext: Boolean, val hasPrevious: Boolean, @@ -70,6 +71,7 @@ internal class PlayerInteractor( videoNumber = episode?.number, episodeId = episode?.id, episodeTitle = episode?.title, + isCurrentMediaWatched = isWatched(episode?.watched), isSeries = true, hasNext = resolvedSeason != null && resolvedEpisode != null && findNextEpisode(item, resolvedSeason, resolvedEpisode) != null, @@ -89,6 +91,7 @@ internal class PlayerInteractor( videoNumber = video?.number, episodeId = null, episodeTitle = null, + isCurrentMediaWatched = isWatched(item.watched), isSeries = false, hasNext = false, hasPrevious = false, @@ -103,6 +106,8 @@ internal class PlayerInteractor( else -> false } + private fun isWatched(watched: Int?): Boolean = watched == WATCHED_STATUS + fun findEpisode(item: Item, seasonNumber: Int, episodeNumber: Int) = item.seasons ?.find { it.number == seasonNumber } @@ -223,6 +228,16 @@ internal class PlayerInteractor( api.toggleWatchingStatus(id, status = WATCHED_STATUS, season = season, video = videoNumber) } + suspend fun markCurrentAsWatched(id: Int, season: Int? = null, episode: Int? = null): Item { + api.toggleWatchingStatus( + id = id, + status = WATCHED_STATUS, + season = season, + video = episode, + ).getOrThrow() + return itemDetailsRepository.refresh(id) + } + suspend fun setEpisodeWatched(id: Int, season: Int, episode: Int, watched: Boolean): Item { api.toggleWatchingStatus( id = id, diff --git a/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerButtonRow.kt b/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerButtonRow.kt index 1ad2782..4b676c9 100644 --- a/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerButtonRow.kt +++ b/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerButtonRow.kt @@ -30,6 +30,11 @@ import androidx.tv.material3.ButtonDefaults import androidx.tv.material3.Icon import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text +import com.adamglin.PhosphorIcons +import com.adamglin.phosphoricons.Duotone +import com.adamglin.phosphoricons.Fill +import com.adamglin.phosphoricons.duotone.Eye +import com.adamglin.phosphoricons.fill.Eye import com.kino.puber.R @Composable @@ -58,6 +63,8 @@ internal data class PlayerButtonRowState( val isPlaying: Boolean, val hasNextEpisode: Boolean, val hasPreviousEpisode: Boolean, + val canMarkCurrentWatched: Boolean, + val isCurrentMediaWatched: Boolean, ) @Composable @@ -72,6 +79,19 @@ private fun PrimaryControls( onClick = actions.onTogglePlayPause, focusRequester = focusRequesters.firstButton, ) + if (state.canMarkCurrentWatched) { + PlayerButton( + text = stringResource(R.string.player_button_mark_watched), + icon = if (state.isCurrentMediaWatched) { + PhosphorIcons.Fill.Eye + } else { + PhosphorIcons.Duotone.Eye + }, + onClick = actions.onMarkCurrentWatchedClick, + selected = state.isCurrentMediaWatched, + contentDescription = stringResource(R.string.player_button_mark_watched_content_description), + ) + } if (!state.isMovie) { PlayerButton( text = stringResource(R.string.player_button_episodes), @@ -154,15 +174,17 @@ private fun PlayerButton( icon: ImageVector, onClick: () -> Unit, modifier: Modifier = Modifier, + selected: Boolean = false, + contentDescription: String? = null, ) { Button( onClick = onClick, modifier = modifier, - colors = transparentButtonColors(), + colors = if (selected) selectedButtonColors() else transparentButtonColors(), ) { Icon( imageVector = icon, - contentDescription = null, + contentDescription = contentDescription, modifier = Modifier.size(20.dp), ) Spacer(modifier = Modifier.width(8.dp)) @@ -180,3 +202,11 @@ private fun transparentButtonColors() = ButtonDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.primary, focusedContentColor = MaterialTheme.colorScheme.onPrimary, ) + +@Composable +private fun selectedButtonColors() = ButtonDefaults.colors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.primary, + focusedContainerColor = MaterialTheme.colorScheme.primary, + focusedContentColor = MaterialTheme.colorScheme.onPrimary, +) diff --git a/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerControlsOverlay.kt b/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerControlsOverlay.kt index 595dd42..187b6b2 100644 --- a/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerControlsOverlay.kt +++ b/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerControlsOverlay.kt @@ -30,7 +30,10 @@ internal fun PlayerControlsOverlay( isPlaying: Boolean, hasNextEpisode: Boolean, hasPreviousEpisode: Boolean, + canMarkCurrentWatched: Boolean, + isCurrentMediaWatched: Boolean, onEpisodesClick: () -> Unit, + onMarkCurrentWatchedClick: () -> Unit, onAudioSubtitlesClick: () -> Unit, onVideoSettingsClick: () -> Unit, onNextEpisodeClick: () -> Unit, @@ -67,9 +70,12 @@ internal fun PlayerControlsOverlay( isPlaying = isPlaying, hasNextEpisode = hasNextEpisode, hasPreviousEpisode = hasPreviousEpisode, + canMarkCurrentWatched = canMarkCurrentWatched, + isCurrentMediaWatched = isCurrentMediaWatched, ), actions = PlayerControlActions( onEpisodesClick = onEpisodesClick, + onMarkCurrentWatchedClick = onMarkCurrentWatchedClick, onAudioSubtitlesClick = onAudioSubtitlesClick, onVideoSettingsClick = onVideoSettingsClick, onNextEpisodeClick = onNextEpisodeClick, @@ -99,6 +105,7 @@ private data class ProgressBarState( internal data class PlayerControlActions( val onEpisodesClick: () -> Unit, + val onMarkCurrentWatchedClick: () -> Unit, val onAudioSubtitlesClick: () -> Unit, val onVideoSettingsClick: () -> Unit, val onNextEpisodeClick: () -> Unit, diff --git a/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerOverlayLayers.kt b/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerOverlayLayers.kt index c0e7d1e..30e8a57 100644 --- a/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerOverlayLayers.kt +++ b/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerOverlayLayers.kt @@ -75,7 +75,10 @@ internal fun PlayerControlsLayer( isPlaying = content.isPlaying, hasNextEpisode = content.hasNextEpisode, hasPreviousEpisode = content.hasPreviousEpisode, + canMarkCurrentWatched = content.canMarkCurrentWatched, + isCurrentMediaWatched = content.isCurrentMediaWatched, onEpisodesClick = rememberAction(onAction, PlayerAction.OpenEpisodesPanel), + onMarkCurrentWatchedClick = rememberAction(onAction, PlayerAction.MarkCurrentWatched), onAudioSubtitlesClick = rememberAction(onAction, PlayerAction.OpenAudioSubtitlesPanel), onVideoSettingsClick = rememberAction(onAction, PlayerAction.OpenVideoSettingsPanel), onNextEpisodeClick = rememberAction(onAction, PlayerAction.NextEpisode), diff --git a/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerPreview.kt b/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerPreview.kt index 13ce0f6..24ee571 100644 --- a/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerPreview.kt +++ b/app/src/main/java/com/kino/puber/ui/feature/player/component/PlayerPreview.kt @@ -92,6 +92,8 @@ private fun previewSeriesContent( seekIndicator: SeekIndicatorState? = null, nextEpisodeCountdown: Int? = null, resumeDialog: ResumeDialogState? = null, + canMarkCurrentWatched: Boolean = true, + isCurrentMediaWatched: Boolean = false, ) = PlayerContentState( title = "Кибердеревня", subtitle = "2 сезон, 6 серия, Что там, за железным занавесом?", @@ -122,6 +124,8 @@ private fun previewSeriesContent( isMovie = false, hasNextEpisode = true, hasPreviousEpisode = true, + canMarkCurrentWatched = canMarkCurrentWatched, + isCurrentMediaWatched = isCurrentMediaWatched, nextEpisodeCountdown = nextEpisodeCountdown, resumeDialog = resumeDialog, episodes = previewEpisodes, @@ -132,6 +136,8 @@ private fun previewMovieContent( controlsVisible: Boolean = true, activePanel: ActivePanel = ActivePanel.None, resumeDialog: ResumeDialogState? = null, + canMarkCurrentWatched: Boolean = true, + isCurrentMediaWatched: Boolean = false, ) = PlayerContentState( title = "Граф Монте-Кристо", subtitle = null, @@ -162,6 +168,8 @@ private fun previewMovieContent( isMovie = true, hasNextEpisode = false, hasPreviousEpisode = false, + canMarkCurrentWatched = canMarkCurrentWatched, + isCurrentMediaWatched = isCurrentMediaWatched, nextEpisodeCountdown = null, resumeDialog = resumeDialog, episodes = null, @@ -212,6 +220,16 @@ private fun MovieControlsPreview() = PuberTheme { ) } +@Preview(name = "Movie — watched control active", device = TV_1080p) +@Composable +private fun MovieWatchedControlsPreview() = PuberTheme { + PlayerScreenContent( + state = PlayerViewState.Content(previewMovieContent(isCurrentMediaWatched = true)), + onAction = {}, + exoPlayer = { null }, + ) +} + @Preview(name = "Series — controls hidden + seek forward", device = TV_1080p) @Composable private fun SeekForwardPreview() = PuberTheme { diff --git a/app/src/main/java/com/kino/puber/ui/feature/player/model/PlayerAction.kt b/app/src/main/java/com/kino/puber/ui/feature/player/model/PlayerAction.kt index 3f97207..5b36eb5 100644 --- a/app/src/main/java/com/kino/puber/ui/feature/player/model/PlayerAction.kt +++ b/app/src/main/java/com/kino/puber/ui/feature/player/model/PlayerAction.kt @@ -39,6 +39,7 @@ internal sealed class PlayerAction : UIAction { data class SelectEpisodeById(val episodeId: Int) : PlayerAction() data class EpisodeWatchedChanged(val item: VideoItemUIState, val watched: Boolean) : PlayerAction() data class SeasonWatchedChanged(val item: VideoItemUIState, val watched: Boolean) : PlayerAction() + data object MarkCurrentWatched : PlayerAction() data object NextEpisode : PlayerAction() data object PreviousEpisode : PlayerAction() data object CancelNextEpisodeCountdown : PlayerAction() diff --git a/app/src/main/java/com/kino/puber/ui/feature/player/model/PlayerViewState.kt b/app/src/main/java/com/kino/puber/ui/feature/player/model/PlayerViewState.kt index 24568b1..3ad1993 100644 --- a/app/src/main/java/com/kino/puber/ui/feature/player/model/PlayerViewState.kt +++ b/app/src/main/java/com/kino/puber/ui/feature/player/model/PlayerViewState.kt @@ -58,6 +58,8 @@ internal data class PlayerContentState( val isMovie: Boolean, val hasNextEpisode: Boolean, val hasPreviousEpisode: Boolean, + val canMarkCurrentWatched: Boolean, + val isCurrentMediaWatched: Boolean, // Next episode countdown val nextEpisodeCountdown: Int?, diff --git a/app/src/main/java/com/kino/puber/ui/feature/player/vm/ContentStateFactory.kt b/app/src/main/java/com/kino/puber/ui/feature/player/vm/ContentStateFactory.kt index 595bd22..06440c9 100644 --- a/app/src/main/java/com/kino/puber/ui/feature/player/vm/ContentStateFactory.kt +++ b/app/src/main/java/com/kino/puber/ui/feature/player/vm/ContentStateFactory.kt @@ -51,6 +51,9 @@ internal class ContentStateFactory(private val mapper: PlayerUIMapper) { isMovie = !resolved.isSeries, hasNextEpisode = resolved.hasNext, hasPreviousEpisode = resolved.hasPrevious, + canMarkCurrentWatched = !resolved.isSeries || + (resolved.seasonNumber != null && resolved.episodeNumber != null), + isCurrentMediaWatched = resolved.isCurrentMediaWatched, nextEpisodeCountdown = null, resumeDialog = resumeDialog, episodes = if (resolved.isSeries) mapper.mapEpisodes(item) else null, diff --git a/app/src/main/java/com/kino/puber/ui/feature/player/vm/PlayerVM.kt b/app/src/main/java/com/kino/puber/ui/feature/player/vm/PlayerVM.kt index 0ce61fc..bc8a3e8 100644 --- a/app/src/main/java/com/kino/puber/ui/feature/player/vm/PlayerVM.kt +++ b/app/src/main/java/com/kino/puber/ui/feature/player/vm/PlayerVM.kt @@ -52,7 +52,10 @@ internal class PlayerVM( override val initialViewState: PlayerViewState = PlayerViewState.Loading override fun dispatchError(error: ErrorEntity) { - updateViewState(PlayerViewState.Error(error.message)) + when (stateValue) { + is PlayerViewState.Content -> showMessage(error.message) + else -> updateViewState(PlayerViewState.Error(error.message)) + } } private inline fun updateContent(crossinline update: PlayerContentState.() -> PlayerContentState) { @@ -278,6 +281,7 @@ internal class PlayerVM( is PlayerAction.SelectEpisodeById -> selectEpisodeById(action.episodeId) is PlayerAction.EpisodeWatchedChanged -> onEpisodeWatchedChanged(action.item, action.watched) is PlayerAction.SeasonWatchedChanged -> onSeasonWatchedChanged(action.item, action.watched) + is PlayerAction.MarkCurrentWatched -> markCurrentMediaWatched() is PlayerAction.NextEpisode -> playNextEpisode() is PlayerAction.PreviousEpisode -> playPreviousEpisode() is PlayerAction.CancelNextEpisodeCountdown -> cancelCountdown() @@ -562,7 +566,7 @@ internal class PlayerVM( runCatching { interactor.setEpisodeWatched(params.itemId, season, episode, watched) }.onSuccess { updated -> - updateEpisodes(updated) + syncContentWithItem(updated) showMessage( resources.getString( if (watched) { @@ -584,7 +588,7 @@ internal class PlayerVM( runCatching { interactor.setSeasonWatched(params.itemId, season, watched) }.onSuccess { updated -> - updateEpisodes(updated) + syncContentWithItem(updated) showMessage( resources.getString( if (watched) { @@ -600,9 +604,125 @@ internal class PlayerVM( } } - private fun updateEpisodes(item: Item) { - currentMedia = currentMedia?.copy(item = item) - updateContent { copy(episodes = mapper.mapEpisodes(item)) } + private fun markCurrentMediaWatched() { + val media = currentMedia ?: return + val content = (stateValue as? PlayerViewState.Content)?.content ?: return + if (content.isCurrentMediaWatched || progressTracker.isMarkedWatched || !content.canMarkCurrentWatched) return + + val isMovie = content.isMovie + val season = if (isMovie) null else media.seasonNumber ?: return + val episode = if (isMovie) null else media.episodeNumber ?: return + + launch { + runCatching { + interactor.markCurrentAsWatched( + id = params.itemId, + season = season, + episode = episode, + ) + }.onSuccess { updated -> + progressTracker.markAsWatched() + syncContentWithItem(updated, fallbackCurrentWatched = true) + showMessage( + resources.getString( + if (isMovie) { + R.string.video_details_watched_added + } else { + R.string.context_menu_episode_watched + } + ) + ) + }.onFailure { throwable -> + showMessage(errorHandler.map(throwable).message) + } + } + } + + private fun syncContentWithItem(item: Item, fallbackCurrentWatched: Boolean? = null) { + val media = currentMedia ?: return + val content = (stateValue as? PlayerViewState.Content)?.content ?: return + val watched = item.currentMediaWatched( + isMovie = content.isMovie, + seasonNumber = media.seasonNumber, + episodeNumber = media.episodeNumber, + ) ?: fallbackCurrentWatched ?: content.isCurrentMediaWatched + val episodes = if (content.isMovie) { + content.episodes + } else { + mapper.mapEpisodes(item) ?: content.episodes + } + + currentMedia = media.copy(item = item) + syncProgressTracker(watched) + updateContent { + copy( + isCurrentMediaWatched = watched, + episodes = episodes, + ) + } + } + + private fun syncCurrentMediaWatchedLocally(watched: Boolean) { + val media = currentMedia ?: return + val content = (stateValue as? PlayerViewState.Content)?.content ?: return + val updated = media.item.withCurrentMediaWatched( + watched = watched, + isMovie = content.isMovie, + seasonNumber = media.seasonNumber, + episodeNumber = media.episodeNumber, + ) + syncContentWithItem(updated, fallbackCurrentWatched = watched) + } + + private fun Item.currentMediaWatched(isMovie: Boolean, seasonNumber: Int?, episodeNumber: Int?): Boolean? { + return if (isMovie) { + watched?.let(::isWatchedStatus) + } else { + seasons + ?.find { it.number == seasonNumber } + ?.episodes + ?.find { it.number == episodeNumber } + ?.watched + ?.let(::isWatchedStatus) + } + } + + private fun Item.withCurrentMediaWatched( + watched: Boolean, + isMovie: Boolean, + seasonNumber: Int?, + episodeNumber: Int?, + ): Item { + val status = if (watched) WATCHED_STATUS else UNWATCHED_STATUS + return if (isMovie) { + copy(watched = status) + } else { + copy( + seasons = seasons?.map { season -> + if (season.number != seasonNumber) { + season + } else { + season.copy( + episodes = season.episodes?.map { episode -> + if (episode.number == episodeNumber) episode.copy(watched = status) else episode + } + ) + } + } + ) + } + } + + private fun syncProgressTracker(watched: Boolean) { + if (watched) { + progressTracker.markAsWatched() + } else { + progressTracker.reset() + } + } + + private fun isWatchedStatus(status: Int): Boolean { + return status == WATCHED_STATUS } private fun currentEpisode(): CurrentEpisode? { @@ -625,7 +745,7 @@ internal class PlayerVM( } private fun onPlaybackEnded() { - markCurrentAsWatched() + autoMarkCurrentAsWatched() val state = stateValue as? PlayerViewState.Content ?: return val content = state.content when { @@ -908,7 +1028,7 @@ internal class PlayerVM( val skipState = state.activeSkipSegment ?: return if (skipState.type == SkipSegmentType.CREDITS && !state.isMovie && state.hasNextEpisode) { - markCurrentAsWatched() + autoMarkCurrentAsWatched() playNextEpisode() } else { playbackController.seekTo(skipState.targetPositionMs) @@ -939,13 +1059,13 @@ internal class PlayerVM( if (duration <= 0) return val remaining = duration - playbackController.currentPosition if (remaining < duration * AUTO_MARK_WATCHED_THRESHOLD) { - markCurrentAsWatched() + autoMarkCurrentAsWatched() } } - private fun markCurrentAsWatched() { + private fun autoMarkCurrentAsWatched() { if (progressTracker.isMarkedWatched) return - progressTracker.markAsWatched() + syncCurrentMediaWatchedLocally(watched = true) launch { interactor.markAsWatched( id = params.itemId, @@ -984,6 +1104,8 @@ internal class PlayerVM( } private companion object { + const val WATCHED_STATUS = 1 + const val UNWATCHED_STATUS = 0 const val CONTROLS_HIDE_DELAY_MS = 3000L const val SEEK_INDICATOR_HIDE_DELAY_MS = 1500L const val PROGRESS_SYNC_INTERVAL_MS = 30_000L diff --git a/app/src/main/res/values/player.xml b/app/src/main/res/values/player.xml index a18282b..10b3427 100644 --- a/app/src/main/res/values/player.xml +++ b/app/src/main/res/values/player.xml @@ -3,6 +3,8 @@ Буферизация… Серии + Просмотрено + Отметить просмотренным Аудио и субтитры Видео diff --git a/app/src/test/kotlin/com/kino/puber/domain/interactor/player/PlayerInteractorTest.kt b/app/src/test/kotlin/com/kino/puber/domain/interactor/player/PlayerInteractorTest.kt index 96cfa38..2f6a827 100644 --- a/app/src/test/kotlin/com/kino/puber/domain/interactor/player/PlayerInteractorTest.kt +++ b/app/src/test/kotlin/com/kino/puber/domain/interactor/player/PlayerInteractorTest.kt @@ -7,10 +7,14 @@ import com.kino.puber.data.api.models.ItemType import com.kino.puber.data.api.models.Season import com.kino.puber.data.api.models.VideoFile import com.kino.puber.data.api.models.VideoUrl +import com.kino.puber.data.api.models.WatchingToggleResponse import com.kino.puber.data.repository.ItemDetailsRepository import com.kino.puber.data.repository.PlayerPreferencesRepository +import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk +import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNotNull @@ -262,6 +266,7 @@ class PlayerInteractorTest { fun resolveMedia_movie_returnsCorrectFlags() { val result = interactor.resolveMedia(movieItem, seasonNumber = null, episodeNumber = null) assertFalse(result.isSeries) + assertFalse(result.isCurrentMediaWatched) assertFalse(result.hasNext) assertFalse(result.hasPrevious) assertNull(result.seasonNumber) @@ -270,6 +275,12 @@ class PlayerInteractorTest { assertNull(result.episodeTitle) } + @Test + fun resolveMedia_movie_setsWatchedStateFromItem() { + val result = interactor.resolveMedia(movieItem.copy(watched = 1), seasonNumber = null, episodeNumber = null) + assertTrue(result.isCurrentMediaWatched) + } + @Test fun resolveMedia_series_computesHasNextAndHasPrevious() { // Middle episode: s1e2 — has both next (s1e3) and previous (s1e1) @@ -305,5 +316,51 @@ class PlayerInteractorTest { assertEquals(episode1.number, result.videoNumber) } + @Test + fun resolveMedia_series_setsWatchedStateFromEpisode() { + val watchedEpisode = episode2.copy(watched = 1) + val item = serialItem.copy( + seasons = listOf(season1.copy(episodes = listOf(episode1, watchedEpisode, episode3)), season2) + ) + + val result = interactor.resolveMedia(item, seasonNumber = 1, episodeNumber = 2) + + assertTrue(result.isCurrentMediaWatched) + } + + @Test + fun markCurrentAsWatched_movieMarksAndRefreshesItem() = runTest { + val refreshed = movieItem.copy(watched = 1) + coEvery { + api.toggleWatchingStatus(id = 10, status = 1, season = null, video = null) + } returns Result.success(WatchingToggleResponse(status = 1, watched = 1)) + coEvery { itemDetailsRepository.refresh(10) } returns refreshed + + val result = interactor.markCurrentAsWatched(id = 10) + + assertEquals(refreshed, result) + coVerify(exactly = 1) { + api.toggleWatchingStatus(id = 10, status = 1, season = null, video = null) + } + coVerify(exactly = 1) { itemDetailsRepository.refresh(10) } + } + + @Test + fun markCurrentAsWatched_episodeUsesSeasonAndEpisodeThenRefreshesItem() = runTest { + val refreshed = serialItem.copy(seasons = listOf(season1.copy(episodes = listOf(episode1.copy(watched = 1))))) + coEvery { + api.toggleWatchingStatus(id = 42, status = 1, season = 1, video = 1) + } returns Result.success(WatchingToggleResponse(status = 1, watched = 1)) + coEvery { itemDetailsRepository.refresh(42) } returns refreshed + + val result = interactor.markCurrentAsWatched(id = 42, season = 1, episode = 1) + + assertEquals(refreshed, result) + coVerify(exactly = 1) { + api.toggleWatchingStatus(id = 42, status = 1, season = 1, video = 1) + } + coVerify(exactly = 1) { itemDetailsRepository.refresh(42) } + } + // endregion } diff --git a/app/src/test/kotlin/com/kino/puber/ui/feature/player/vm/PlayerVMTest.kt b/app/src/test/kotlin/com/kino/puber/ui/feature/player/vm/PlayerVMTest.kt index 45fd071..164af5b 100644 --- a/app/src/test/kotlin/com/kino/puber/ui/feature/player/vm/PlayerVMTest.kt +++ b/app/src/test/kotlin/com/kino/puber/ui/feature/player/vm/PlayerVMTest.kt @@ -1,9 +1,15 @@ package com.kino.puber.ui.feature.player.vm import com.kino.puber.core.error.ErrorHandler +import com.kino.puber.core.error.ErrorEntity import com.kino.puber.core.ui.navigation.AppRouter +import com.kino.puber.core.ui.uikit.component.moviesList.VideoGridUIState +import com.kino.puber.core.ui.uikit.component.moviesList.VideoItemUIState +import com.kino.puber.data.api.models.Episode import com.kino.puber.data.api.models.Item import com.kino.puber.data.api.models.ItemType +import com.kino.puber.data.api.models.Season +import com.kino.puber.data.api.models.SkipSegmentType import com.kino.puber.domain.interactor.player.PlayerInteractor import com.kino.puber.domain.interactor.player.ResolvedMedia import com.kino.puber.domain.interactor.player.SkipSegmentInteractor @@ -20,18 +26,18 @@ import com.kino.puber.ui.feature.player.model.PlayerViewState import com.kino.puber.ui.feature.player.model.ResumeDialogState import com.kino.puber.ui.feature.player.model.SkipSegmentUIState import com.kino.puber.ui.feature.player.model.SubtitleTrackUIState -import com.kino.puber.data.api.models.SkipSegmentType import com.kino.puber.util.FakeResourceProvider import com.kino.puber.util.MainDispatcherExtension -import org.junit.jupiter.api.Assertions.assertFalse import io.mockk.clearAllMocks import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue @@ -103,6 +109,7 @@ class PlayerVMTest { every { mapper.formatSeekOffset(any(), any()) } returns "+10s" every { mapper.buildTitle(any(), any(), any()) } returns "Title" every { mapper.buildSubtitle(any(), any(), any(), any()) } returns "Sub" + every { mapper.mapEpisodes(any()) } returns null every { mapper.mapSkipSegmentLabel(any()) } returns "Skip" every { mapper.defaultSoundModeLabel() } returns "Stereo" every { playbackController.setCallback(capture(callbackSlot)) } returns Unit @@ -668,6 +675,125 @@ class PlayerVMTest { // endregion + // region Manual mark watched + + @Test + fun markCurrentWatched_movieCallsInteractorWithoutEpisodeAndUpdatesState() { + val updated = testItem.copy(type = ItemType.MOVIE, watched = 1) + coEvery { contentStateFactory.build(any(), any(), any(), any(), any(), any()) } returns testContentState.copy( + isMovie = true, + hasNextEpisode = false, + hasPreviousEpisode = false, + ) + coEvery { + interactor.markCurrentAsWatched(id = 42, season = null, episode = null) + } returns updated + val vm = startedVM() + + vm.onAction(PlayerAction.MarkCurrentWatched) + + assertTrue(contentState(vm).isCurrentMediaWatched) + coVerify(exactly = 1) { + interactor.markCurrentAsWatched(id = 42, season = null, episode = null) + } + } + + @Test + fun markCurrentWatched_episodePassesSeasonAndEpisodeAndUpdatesEpisodes() { + val updated = testItem.copy(watched = 1) + val updatedEpisodes = VideoGridUIState(emptyList()) + coEvery { interactor.markCurrentAsWatched(id = 42, season = 1, episode = 1) } returns updated + every { mapper.mapEpisodes(updated) } returns updatedEpisodes + val vm = startedVM() + + vm.onAction(PlayerAction.MarkCurrentWatched) + + assertTrue(contentState(vm).isCurrentMediaWatched) + assertEquals(updatedEpisodes, contentState(vm).episodes) + coVerify(exactly = 1) { interactor.markCurrentAsWatched(id = 42, season = 1, episode = 1) } + } + + @Test + fun markCurrentWatched_alreadyWatchedDoesNotCallInteractor() { + coEvery { contentStateFactory.build(any(), any(), any(), any(), any(), any()) } returns testContentState.copy( + isCurrentMediaWatched = true, + ) + val vm = startedVM() + + vm.onAction(PlayerAction.MarkCurrentWatched) + + coVerify(exactly = 0) { interactor.markCurrentAsWatched(any(), any(), any()) } + } + + @Test + fun markCurrentWatched_failurePreservesStateAndMapsError() { + val failure = IllegalStateException("Failed") + coEvery { interactor.markCurrentAsWatched(id = 42, season = 1, episode = 1) } throws failure + every { errorHandler.map(failure) } returns ErrorEntity(message = "Mapped", code = "test") + val vm = startedVM() + + vm.onAction(PlayerAction.MarkCurrentWatched) + + assertFalse(contentState(vm).isCurrentMediaWatched) + verify(exactly = 1) { errorHandler.map(failure) } + } + + @Test + fun markCurrentWatched_afterAutoMarkDoesNotDuplicateManualApiCall() { + val vm = startedVM() + + callbackSlot.captured.onPlaybackEnded() + vm.onAction(PlayerAction.MarkCurrentWatched) + + assertTrue(contentState(vm).isCurrentMediaWatched) + coVerify(exactly = 1) { interactor.markAsWatched(id = 42, season = 1, videoNumber = 1) } + coVerify(exactly = 0) { interactor.markCurrentAsWatched(any(), any(), any()) } + } + + @Test + fun episodeWatchedChanged_currentEpisodeUpdatesWatchedButtonState() { + val updated = testItem.withCurrentEpisodeWatched(true) + val updatedEpisodes = VideoGridUIState(emptyList()) + coEvery { interactor.setEpisodeWatched(42, season = 1, episode = 1, watched = true) } returns updated + every { mapper.mapEpisodes(updated) } returns updatedEpisodes + val vm = startedVM() + + vm.onAction(PlayerAction.EpisodeWatchedChanged(currentEpisodeItem, watched = true)) + + assertTrue(contentState(vm).isCurrentMediaWatched) + assertEquals(updatedEpisodes, contentState(vm).episodes) + } + + @Test + fun episodeWatchedChanged_currentEpisodeUnwatchedUpdatesWatchedButtonState() { + val updated = testItem.withCurrentEpisodeWatched(false) + coEvery { contentStateFactory.build(any(), any(), any(), any(), any(), any()) } returns testContentState.copy( + isCurrentMediaWatched = true, + ) + coEvery { interactor.setEpisodeWatched(42, season = 1, episode = 1, watched = false) } returns updated + val vm = startedVM() + + vm.onAction(PlayerAction.EpisodeWatchedChanged(currentEpisodeItem, watched = false)) + + assertFalse(contentState(vm).isCurrentMediaWatched) + } + + @Test + fun seasonWatchedChanged_currentSeasonUnwatchedUpdatesWatchedButtonState() { + val updated = testItem.withCurrentEpisodeWatched(false) + coEvery { contentStateFactory.build(any(), any(), any(), any(), any(), any()) } returns testContentState.copy( + isCurrentMediaWatched = true, + ) + coEvery { interactor.setSeasonWatched(42, season = 1, watched = false) } returns updated + val vm = startedVM() + + vm.onAction(PlayerAction.SeasonWatchedChanged(currentEpisodeItem, watched = false)) + + assertFalse(contentState(vm).isCurrentMediaWatched) + } + + // endregion + // region Back navigation (additional branches) @Test @@ -703,11 +829,33 @@ class PlayerVMTest { private val testItem = Item(id = 42, title = "Breaking Bad", type = ItemType.SERIAL, watched = 5, new = 3) + private val currentEpisodeItem = VideoItemUIState( + id = 101, + title = "1. Pilot", + imageUrl = "", + bigImageUrl = "", + seasonNumber = 1, + episodeNumber = 1, + ) + + private fun Item.withCurrentEpisodeWatched(watched: Boolean): Item { + val status = if (watched) 1 else 0 + return copy( + seasons = listOf( + Season( + id = 1, + number = 1, + episodes = listOf(Episode(id = 101, number = 1, title = "Pilot", watched = status)), + ) + ) + ) + } + private val testResolvedMedia = ResolvedMedia( files = emptyList(), audios = emptyList(), subtitles = emptyList(), watchingTime = null, duration = 2400, videoNumber = 1, episodeId = 101, episodeTitle = "Pilot", isSeries = true, hasNext = true, - hasPrevious = true, seasonNumber = 1, episodeNumber = 1, + isCurrentMediaWatched = false, hasPrevious = true, seasonNumber = 1, episodeNumber = 1, ) private val testSubtitleTracks = listOf( @@ -735,6 +883,7 @@ class PlayerVMTest { bufferPresets = listOf(BufferPresetUIState(0, "Auto", BufferPreset.AUTO)), selectedBufferPresetIndex = 0, isMovie = false, hasNextEpisode = true, hasPreviousEpisode = true, nextEpisodeCountdown = null, + canMarkCurrentWatched = true, isCurrentMediaWatched = false, resumeDialog = null, episodes = null, currentEpisodeId = 101, ) }