agentplane 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/AGENTS.md +2 -2
- package/assets/agents/CODER.json +4 -0
- package/assets/agents/CREATOR.json +1 -0
- package/assets/agents/DOCS.json +2 -1
- package/assets/agents/INTEGRATOR.json +2 -1
- package/assets/agents/ORCHESTRATOR.json +2 -0
- package/assets/agents/PLANNER.json +3 -1
- package/assets/agents/REVIEWER.json +1 -0
- package/assets/agents/TESTER.json +2 -2
- package/assets/agents/UPDATER.json +1 -0
- package/assets/agents/UPGRADER.json +1 -1
- package/assets/policy/governance.md +3 -4
- package/assets/policy/incidents.md +20 -88
- package/assets/policy/workflow.branch_pr.md +1 -1
- package/assets/policy/workflow.direct.md +2 -2
- package/bin/agentplane.js +114 -4
- package/bin/runtime-watch.js +1 -0
- package/bin/stale-dist-policy.d.ts +1 -1
- package/bin/stale-dist-policy.js +19 -1
- package/dist/.build-manifest.json +251 -166
- package/dist/cli/bootstrap-guide.d.ts.map +1 -1
- package/dist/cli/bootstrap-guide.js +3 -2
- package/dist/cli/command-guide.d.ts.map +1 -1
- package/dist/cli/command-guide.js +2 -1
- package/dist/cli/command-invocations.d.ts.map +1 -1
- package/dist/cli/command-invocations.js +4 -1
- package/dist/cli/run-cli/command-catalog/core.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog/core.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog/core.js +6 -1
- package/dist/cli/run-cli/command-catalog/project.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog/project.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog/project.js +3 -1
- package/dist/cli/run-cli/command-catalog/task.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog/task.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog/task.js +10 -0
- package/dist/cli/run-cli/command-catalog.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/core/preflight.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/core/preflight.js +44 -1
- package/dist/cli/run-cli.test-helpers.d.ts +1 -0
- package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
- package/dist/cli/run-cli.test-helpers.js +26 -0
- package/dist/commands/branch/cleanup-merged.d.ts +3 -0
- package/dist/commands/branch/cleanup-merged.d.ts.map +1 -1
- package/dist/commands/branch/cleanup-merged.js +149 -36
- package/dist/commands/branch/work-start.d.ts.map +1 -1
- package/dist/commands/branch/work-start.js +137 -1
- package/dist/commands/cleanup/merged.command.d.ts +2 -0
- package/dist/commands/cleanup/merged.command.d.ts.map +1 -1
- package/dist/commands/cleanup/merged.command.js +24 -0
- package/dist/commands/doctor/branch-pr.d.ts +4 -0
- package/dist/commands/doctor/branch-pr.d.ts.map +1 -0
- package/dist/commands/doctor/branch-pr.js +96 -0
- package/dist/commands/doctor/fixes.d.ts +5 -0
- package/dist/commands/doctor/fixes.d.ts.map +1 -1
- package/dist/commands/doctor/fixes.js +70 -0
- package/dist/commands/doctor.run.d.ts.map +1 -1
- package/dist/commands/doctor.run.js +6 -1
- package/dist/commands/finish.run.d.ts.map +1 -1
- package/dist/commands/finish.run.js +11 -0
- package/dist/commands/finish.spec.d.ts +11 -0
- package/dist/commands/finish.spec.d.ts.map +1 -1
- package/dist/commands/finish.spec.js +51 -0
- package/dist/commands/guard/impl/close-message.d.ts.map +1 -1
- package/dist/commands/guard/impl/close-message.js +23 -6
- package/dist/commands/guard/impl/commands.d.ts +1 -0
- package/dist/commands/guard/impl/commands.d.ts.map +1 -1
- package/dist/commands/guard/impl/commands.js +94 -2
- package/dist/commands/guard/impl/env.d.ts +1 -0
- package/dist/commands/guard/impl/env.d.ts.map +1 -1
- package/dist/commands/guard/impl/env.js +1 -0
- package/dist/commands/hooks/index.d.ts +1 -1
- package/dist/commands/hooks/index.d.ts.map +1 -1
- package/dist/commands/hooks/index.js +139 -6
- package/dist/commands/incidents/collect.command.d.ts.map +1 -1
- package/dist/commands/incidents/collect.command.js +12 -7
- package/dist/commands/incidents/incidents.command.js +1 -1
- package/dist/commands/incidents/shared.d.ts +34 -0
- package/dist/commands/incidents/shared.d.ts.map +1 -1
- package/dist/commands/incidents/shared.js +166 -12
- package/dist/commands/pr/check.d.ts.map +1 -1
- package/dist/commands/pr/check.js +241 -135
- package/dist/commands/pr/close-superseded.d.ts +9 -0
- package/dist/commands/pr/close-superseded.d.ts.map +1 -0
- package/dist/commands/pr/close-superseded.js +129 -0
- package/dist/commands/pr/close.d.ts +11 -0
- package/dist/commands/pr/close.d.ts.map +1 -0
- package/dist/commands/pr/close.js +116 -0
- package/dist/commands/pr/index.d.ts +2 -0
- package/dist/commands/pr/index.d.ts.map +1 -1
- package/dist/commands/pr/index.js +2 -0
- package/dist/commands/pr/integrate/artifacts.d.ts +7 -0
- package/dist/commands/pr/integrate/artifacts.d.ts.map +1 -1
- package/dist/commands/pr/integrate/artifacts.js +66 -1
- package/dist/commands/pr/integrate/cmd.d.ts.map +1 -1
- package/dist/commands/pr/integrate/cmd.js +43 -2
- package/dist/commands/pr/integrate/internal/bootstrap-guidance.d.ts +8 -0
- package/dist/commands/pr/integrate/internal/bootstrap-guidance.d.ts.map +1 -0
- package/dist/commands/pr/integrate/internal/bootstrap-guidance.js +59 -0
- package/dist/commands/pr/integrate/internal/cleanup.d.ts +1 -11
- package/dist/commands/pr/integrate/internal/cleanup.d.ts.map +1 -1
- package/dist/commands/pr/integrate/internal/cleanup.js +1 -46
- package/dist/commands/pr/integrate/internal/finalize.d.ts.map +1 -1
- package/dist/commands/pr/integrate/internal/finalize.js +43 -12
- package/dist/commands/pr/integrate/internal/github-protection.d.ts +5 -0
- package/dist/commands/pr/integrate/internal/github-protection.d.ts.map +1 -0
- package/dist/commands/pr/integrate/internal/github-protection.js +13 -0
- package/dist/commands/pr/integrate/internal/merge.d.ts.map +1 -1
- package/dist/commands/pr/integrate/internal/merge.js +36 -13
- package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.d.ts +13 -0
- package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.d.ts.map +1 -0
- package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.js +25 -0
- package/dist/commands/pr/integrate/internal/pre-integrate-bootstrap.d.ts +15 -0
- package/dist/commands/pr/integrate/internal/pre-integrate-bootstrap.d.ts.map +1 -0
- package/dist/commands/pr/integrate/internal/pre-integrate-bootstrap.js +35 -0
- package/dist/commands/pr/integrate/internal/prepare.d.ts +4 -2
- package/dist/commands/pr/integrate/internal/prepare.d.ts.map +1 -1
- package/dist/commands/pr/integrate/internal/prepare.js +109 -38
- package/dist/commands/pr/internal/auto-commit.d.ts +7 -0
- package/dist/commands/pr/internal/auto-commit.d.ts.map +1 -0
- package/dist/commands/pr/internal/auto-commit.js +64 -0
- package/dist/commands/pr/internal/freshness.d.ts +21 -0
- package/dist/commands/pr/internal/freshness.d.ts.map +1 -0
- package/dist/commands/pr/internal/freshness.js +52 -0
- package/dist/commands/pr/internal/gh-api.d.ts +6 -0
- package/dist/commands/pr/internal/gh-api.d.ts.map +1 -0
- package/dist/commands/pr/internal/gh-api.js +80 -0
- package/dist/commands/pr/internal/pr-paths.d.ts +10 -0
- package/dist/commands/pr/internal/pr-paths.d.ts.map +1 -1
- package/dist/commands/pr/internal/pr-paths.js +10 -0
- package/dist/commands/pr/internal/review-template.d.ts.map +1 -1
- package/dist/commands/pr/internal/review-template.js +37 -4
- package/dist/commands/pr/internal/sync.d.ts +9 -0
- package/dist/commands/pr/internal/sync.d.ts.map +1 -1
- package/dist/commands/pr/internal/sync.js +531 -124
- package/dist/commands/pr/open.d.ts +1 -0
- package/dist/commands/pr/open.d.ts.map +1 -1
- package/dist/commands/pr/open.js +24 -2
- package/dist/commands/pr/pr.command.d.ts +15 -0
- package/dist/commands/pr/pr.command.d.ts.map +1 -1
- package/dist/commands/pr/pr.command.js +118 -2
- package/dist/commands/pr/update.d.ts.map +1 -1
- package/dist/commands/pr/update.js +71 -2
- package/dist/commands/release/apply.command.d.ts +3 -1
- package/dist/commands/release/apply.command.d.ts.map +1 -1
- package/dist/commands/release/apply.command.js +356 -34
- package/dist/commands/release/apply.mutation.d.ts.map +1 -1
- package/dist/commands/release/apply.mutation.js +1 -0
- package/dist/commands/release/apply.preflight.d.ts.map +1 -1
- package/dist/commands/release/apply.preflight.js +1 -1
- package/dist/commands/release/apply.reporting.d.ts +1 -0
- package/dist/commands/release/apply.reporting.d.ts.map +1 -1
- package/dist/commands/release/apply.reporting.js +12 -8
- package/dist/commands/release/apply.types.d.ts +13 -0
- package/dist/commands/release/apply.types.d.ts.map +1 -1
- package/dist/commands/release/plan.command.d.ts.map +1 -1
- package/dist/commands/release/plan.command.js +48 -0
- package/dist/commands/shared/gh-transport.d.ts +16 -0
- package/dist/commands/shared/gh-transport.d.ts.map +1 -0
- package/dist/commands/shared/gh-transport.js +71 -0
- package/dist/commands/shared/git-diff.d.ts +3 -1
- package/dist/commands/shared/git-diff.d.ts.map +1 -1
- package/dist/commands/shared/git-diff.js +10 -2
- package/dist/commands/shared/git-ops.d.ts +1 -0
- package/dist/commands/shared/git-ops.d.ts.map +1 -1
- package/dist/commands/shared/git-ops.js +15 -0
- package/dist/commands/shared/git-worktree.d.ts +2 -0
- package/dist/commands/shared/git-worktree.d.ts.map +1 -1
- package/dist/commands/shared/git-worktree.js +22 -2
- package/dist/commands/shared/merged-branch-cleanup.d.ts +12 -0
- package/dist/commands/shared/merged-branch-cleanup.d.ts.map +1 -0
- package/dist/commands/shared/merged-branch-cleanup.js +46 -0
- package/dist/commands/shared/post-commit-pr-artifacts.d.ts +9 -0
- package/dist/commands/shared/post-commit-pr-artifacts.d.ts.map +1 -0
- package/dist/commands/shared/post-commit-pr-artifacts.js +57 -0
- package/dist/commands/shared/pr-meta.d.ts +20 -0
- package/dist/commands/shared/pr-meta.d.ts.map +1 -1
- package/dist/commands/shared/pr-meta.js +125 -0
- package/dist/commands/shared/task-backend.d.ts +7 -0
- package/dist/commands/shared/task-backend.d.ts.map +1 -1
- package/dist/commands/shared/task-backend.js +71 -27
- package/dist/commands/shared/task-local-freshness.d.ts +2 -0
- package/dist/commands/shared/task-local-freshness.d.ts.map +1 -1
- package/dist/commands/shared/task-local-freshness.js +7 -1
- package/dist/commands/task/close-duplicate.d.ts.map +1 -1
- package/dist/commands/task/close-duplicate.js +34 -1
- package/dist/commands/task/derive.js +1 -1
- package/dist/commands/task/doc-template.d.ts.map +1 -1
- package/dist/commands/task/doc-template.js +7 -11
- package/dist/commands/task/findings-add.command.d.ts +20 -0
- package/dist/commands/task/findings-add.command.d.ts.map +1 -0
- package/dist/commands/task/findings-add.command.js +165 -0
- package/dist/commands/task/findings.command.d.ts +7 -0
- package/dist/commands/task/findings.command.d.ts.map +1 -0
- package/dist/commands/task/findings.command.js +20 -0
- package/dist/commands/task/findings.d.ts +63 -0
- package/dist/commands/task/findings.d.ts.map +1 -0
- package/dist/commands/task/findings.js +188 -0
- package/dist/commands/task/finish-shared.d.ts +2 -0
- package/dist/commands/task/finish-shared.d.ts.map +1 -1
- package/dist/commands/task/finish-shared.js +56 -1
- package/dist/commands/task/finish.d.ts +10 -0
- package/dist/commands/task/finish.d.ts.map +1 -1
- package/dist/commands/task/finish.js +125 -6
- package/dist/commands/task/hosted-close-pr.command.d.ts +11 -0
- package/dist/commands/task/hosted-close-pr.command.d.ts.map +1 -0
- package/dist/commands/task/hosted-close-pr.command.js +449 -0
- package/dist/commands/task/hosted-close.command.d.ts.map +1 -1
- package/dist/commands/task/hosted-close.command.js +234 -19
- package/dist/commands/task/hosted-merge-sync.d.ts +41 -0
- package/dist/commands/task/hosted-merge-sync.d.ts.map +1 -1
- package/dist/commands/task/hosted-merge-sync.js +291 -17
- package/dist/commands/task/index.d.ts +1 -0
- package/dist/commands/task/index.d.ts.map +1 -1
- package/dist/commands/task/index.js +1 -0
- package/dist/commands/task/new.d.ts +1 -0
- package/dist/commands/task/new.d.ts.map +1 -1
- package/dist/commands/task/new.js +71 -1
- package/dist/commands/task/new.spec.d.ts.map +1 -1
- package/dist/commands/task/new.spec.js +7 -0
- package/dist/commands/task/normalize.command.d.ts +2 -0
- package/dist/commands/task/normalize.command.d.ts.map +1 -1
- package/dist/commands/task/normalize.command.js +45 -0
- package/dist/commands/task/normalize.d.ts +2 -0
- package/dist/commands/task/normalize.d.ts.map +1 -1
- package/dist/commands/task/normalize.js +85 -8
- package/dist/commands/task/plan.d.ts.map +1 -1
- package/dist/commands/task/plan.js +7 -10
- package/dist/commands/task/shared/docs.d.ts +6 -0
- package/dist/commands/task/shared/docs.d.ts.map +1 -1
- package/dist/commands/task/shared/docs.js +14 -0
- package/dist/commands/task/shared/transitions.d.ts.map +1 -1
- package/dist/commands/task/shared/transitions.js +11 -1
- package/dist/commands/task/shared.d.ts +1 -1
- package/dist/commands/task/shared.d.ts.map +1 -1
- package/dist/commands/task/shared.js +1 -1
- package/dist/commands/task/start.d.ts.map +1 -1
- package/dist/commands/task/start.js +7 -10
- package/dist/commands/task/task.command.d.ts.map +1 -1
- package/dist/commands/task/task.command.js +4 -0
- package/dist/commands/task/verify-command-shared.d.ts +19 -0
- package/dist/commands/task/verify-command-shared.d.ts.map +1 -1
- package/dist/commands/task/verify-command-shared.js +152 -1
- package/dist/commands/task/verify-ok.command.d.ts.map +1 -1
- package/dist/commands/task/verify-ok.command.js +15 -2
- package/dist/commands/task/verify-record.d.ts +36 -0
- package/dist/commands/task/verify-record.d.ts.map +1 -1
- package/dist/commands/task/verify-record.js +166 -11
- package/dist/commands/task/verify-rework.command.d.ts.map +1 -1
- package/dist/commands/task/verify-rework.command.js +15 -2
- package/dist/commands/task/verify-show.command.d.ts +1 -1
- package/dist/commands/task/verify-show.command.d.ts.map +1 -1
- package/dist/commands/task/verify-show.command.js +28 -1
- package/dist/commands/verify.run.d.ts.map +1 -1
- package/dist/commands/verify.run.js +12 -0
- package/dist/commands/verify.spec.d.ts +2 -6
- package/dist/commands/verify.spec.d.ts.map +1 -1
- package/dist/commands/verify.spec.js +30 -3
- package/dist/runtime/incidents/index.d.ts +1 -1
- package/dist/runtime/incidents/index.d.ts.map +1 -1
- package/dist/runtime/incidents/resolve.d.ts.map +1 -1
- package/dist/runtime/incidents/resolve.js +319 -73
- package/dist/runtime/incidents/types.d.ts +14 -2
- package/dist/runtime/incidents/types.d.ts.map +1 -1
- package/dist/shared/env.d.ts +1 -0
- package/dist/shared/env.d.ts.map +1 -1
- package/dist/shared/env.js +22 -1
- package/dist/shared/protected-paths.d.ts +1 -1
- package/dist/shared/protected-paths.d.ts.map +1 -1
- package/dist/shared/protected-paths.js +4 -0
- package/package.json +2 -2
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { usageError } from "../../cli/spec/errors.js";
|
|
3
|
+
import { createCliEmitter } from "../../cli/output.js";
|
|
4
|
+
import { mapBackendError } from "../../cli/error-map.js";
|
|
5
|
+
import { exitCodeForError } from "../../cli/exit-codes.js";
|
|
6
|
+
import { fileExists } from "../../cli/fs-utils.js";
|
|
7
|
+
import { CliError } from "../../shared/errors.js";
|
|
8
|
+
import { execFileAsync, gitEnv } from "../shared/git.js";
|
|
9
|
+
import { cleanupMergedLocalBranch } from "../shared/merged-branch-cleanup.js";
|
|
10
|
+
import { loadTaskFromContext, resolveTaskBranchFromContext, } from "../shared/task-backend.js";
|
|
11
|
+
import { parseTaskIdFromCloseBranch } from "../shared/git-worktree.js";
|
|
12
|
+
import { parsePrMeta } from "../shared/pr-meta.js";
|
|
13
|
+
import { resolveDefaultGithubRepo, runGhApiJson } from "../pr/internal/gh-api.js";
|
|
14
|
+
import { resolvePrPaths } from "../pr/internal/pr-paths.js";
|
|
15
|
+
import { resolveBaseBranch } from "@agentplaneorg/core";
|
|
16
|
+
export const taskHostedClosePrSpec = {
|
|
17
|
+
id: ["task", "hosted-close-pr"],
|
|
18
|
+
group: "Task",
|
|
19
|
+
summary: "Open one or more follow-up hosted closure PRs from remote task-close branches.",
|
|
20
|
+
args: [{ name: "task-id", required: true, variadic: true, valueHint: "<task-id>" }],
|
|
21
|
+
options: [
|
|
22
|
+
{
|
|
23
|
+
kind: "string",
|
|
24
|
+
name: "branch",
|
|
25
|
+
valueHint: "<name>",
|
|
26
|
+
description: "Optional explicit task-close branch name (default: derive from task PR metadata or remote refs).",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
kind: "string",
|
|
30
|
+
name: "repo",
|
|
31
|
+
valueHint: "<owner/name>",
|
|
32
|
+
description: "Optional GitHub owner/repo override (defaults to origin remote).",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
examples: [
|
|
36
|
+
{
|
|
37
|
+
cmd: "agentplane task hosted-close-pr 202604091218-JREJ4K",
|
|
38
|
+
why: "Open the hosted closure PR after the workflow left a manual handoff comment.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
cmd: "agentplane task hosted-close-pr 202604091725-CB0Y6S 202604091725-H21SCP",
|
|
42
|
+
why: "Open multiple pending hosted closure PRs in one batch after a closure-wave merge.",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
validateRaw: (raw) => {
|
|
46
|
+
const taskIds = Array.isArray(raw.args["task-id"])
|
|
47
|
+
? raw.args["task-id"]
|
|
48
|
+
: typeof raw.args["task-id"] === "string"
|
|
49
|
+
? [raw.args["task-id"]]
|
|
50
|
+
: [];
|
|
51
|
+
const normalizedTaskIds = taskIds.map((taskId) => String(taskId).trim()).filter(Boolean);
|
|
52
|
+
if (normalizedTaskIds.length === 0) {
|
|
53
|
+
throw usageError({
|
|
54
|
+
spec: taskHostedClosePrSpec,
|
|
55
|
+
message: "Invalid value for task-id: empty.",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (typeof raw.opts.branch === "string" && raw.opts.branch.trim() === "") {
|
|
59
|
+
throw usageError({
|
|
60
|
+
spec: taskHostedClosePrSpec,
|
|
61
|
+
message: "Invalid value for --branch: empty.",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (typeof raw.opts.repo === "string" && raw.opts.repo.trim() === "") {
|
|
65
|
+
throw usageError({
|
|
66
|
+
spec: taskHostedClosePrSpec,
|
|
67
|
+
message: "Invalid value for --repo: empty.",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
parse: (raw) => ({
|
|
72
|
+
taskIds: (Array.isArray(raw.args["task-id"])
|
|
73
|
+
? raw.args["task-id"]
|
|
74
|
+
: typeof raw.args["task-id"] === "string"
|
|
75
|
+
? [raw.args["task-id"]]
|
|
76
|
+
: [])
|
|
77
|
+
.map((taskId) => String(taskId).trim())
|
|
78
|
+
.filter(Boolean),
|
|
79
|
+
branch: typeof raw.opts.branch === "string" ? raw.opts.branch : null,
|
|
80
|
+
repo: typeof raw.opts.repo === "string" ? raw.opts.repo : null,
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
async function resolveGithubRepo(opts) {
|
|
84
|
+
const repo = opts.repoOverride?.trim() ?? "";
|
|
85
|
+
if (repo)
|
|
86
|
+
return repo;
|
|
87
|
+
return await resolveDefaultGithubRepo(opts.gitRoot);
|
|
88
|
+
}
|
|
89
|
+
function selectMergedPullRecord(opts) {
|
|
90
|
+
const merged = opts.pulls.filter((record) => {
|
|
91
|
+
if (typeof record.merged_at !== "string" || record.merged_at.trim().length === 0)
|
|
92
|
+
return false;
|
|
93
|
+
const headRef = record.head?.ref?.trim() ?? "";
|
|
94
|
+
if (opts.sourceBranch && headRef && headRef !== opts.sourceBranch)
|
|
95
|
+
return false;
|
|
96
|
+
const baseRef = record.base?.ref?.trim() ?? "";
|
|
97
|
+
if (opts.baseBranch && baseRef && baseRef !== opts.baseBranch)
|
|
98
|
+
return false;
|
|
99
|
+
return true;
|
|
100
|
+
});
|
|
101
|
+
if (merged.length === 0)
|
|
102
|
+
return null;
|
|
103
|
+
if (typeof opts.prNumber === "number" && opts.prNumber > 0) {
|
|
104
|
+
const exact = merged.find((record) => Number(record.number ?? 0) === opts.prNumber);
|
|
105
|
+
if (exact)
|
|
106
|
+
return exact;
|
|
107
|
+
}
|
|
108
|
+
return ([...merged].toSorted((left, right) => {
|
|
109
|
+
const leftAt = Date.parse(left.merged_at ?? "");
|
|
110
|
+
const rightAt = Date.parse(right.merged_at ?? "");
|
|
111
|
+
return Number.isNaN(rightAt) || Number.isNaN(leftAt) ? 0 : rightAt - leftAt;
|
|
112
|
+
})[0] ?? null);
|
|
113
|
+
}
|
|
114
|
+
async function resolveHostedCloseMergeRecord(opts) {
|
|
115
|
+
const owner = opts.repo.split("/")[0]?.trim() ?? "";
|
|
116
|
+
if (!owner)
|
|
117
|
+
return null;
|
|
118
|
+
const query = new URLSearchParams({
|
|
119
|
+
state: "closed",
|
|
120
|
+
head: `${owner}:${opts.sourceBranch}`,
|
|
121
|
+
});
|
|
122
|
+
if (opts.baseBranch)
|
|
123
|
+
query.set("base", opts.baseBranch);
|
|
124
|
+
const records = await runGhApiJson(opts.gitRoot, [
|
|
125
|
+
`repos/${opts.repo}/pulls?${query.toString()}`,
|
|
126
|
+
]);
|
|
127
|
+
return selectMergedPullRecord({
|
|
128
|
+
pulls: Array.isArray(records) ? records : [],
|
|
129
|
+
sourceBranch: opts.sourceBranch,
|
|
130
|
+
baseBranch: opts.baseBranch,
|
|
131
|
+
prNumber: opts.prNumber,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async function resolveHostedCloseMergeRecordByCommit(opts) {
|
|
135
|
+
const records = await runGhApiJson(opts.gitRoot, [
|
|
136
|
+
`repos/${opts.repo}/commits/${opts.mergeCommit}/pulls`,
|
|
137
|
+
]);
|
|
138
|
+
return selectMergedPullRecord({
|
|
139
|
+
pulls: Array.isArray(records) ? records : [],
|
|
140
|
+
sourceBranch: opts.sourceBranch,
|
|
141
|
+
baseBranch: opts.baseBranch,
|
|
142
|
+
prNumber: opts.prNumber,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async function readHostedCloseState(opts) {
|
|
146
|
+
const task = await loadTaskFromContext({ ctx: opts.ctx, taskId: opts.taskId });
|
|
147
|
+
const taskBranch = await resolveTaskBranchFromContext({ ctx: opts.ctx, taskId: opts.taskId });
|
|
148
|
+
const { metaPath, config } = await resolvePrPaths({
|
|
149
|
+
ctx: opts.ctx,
|
|
150
|
+
cwd: opts.cwd,
|
|
151
|
+
rootOverride: opts.rootOverride,
|
|
152
|
+
taskId: opts.taskId,
|
|
153
|
+
});
|
|
154
|
+
if (config.workflow_mode !== "branch_pr") {
|
|
155
|
+
throw new CliError({
|
|
156
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
157
|
+
code: "E_USAGE",
|
|
158
|
+
message: `Invalid workflow_mode: ${config.workflow_mode} (expected branch_pr)`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (!(await fileExists(metaPath))) {
|
|
162
|
+
return { meta: null, task, taskBranch };
|
|
163
|
+
}
|
|
164
|
+
const meta = parsePrMeta(await readFile(metaPath, "utf8"), opts.taskId);
|
|
165
|
+
return { meta, task, taskBranch };
|
|
166
|
+
}
|
|
167
|
+
async function listRemoteTaskCloseBranches(opts) {
|
|
168
|
+
const { stdout } = await execFileAsync("git", ["ls-remote", "--heads", "origin", `task-close/${opts.taskId}/*`], {
|
|
169
|
+
cwd: opts.gitRoot,
|
|
170
|
+
env: gitEnv(),
|
|
171
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
172
|
+
});
|
|
173
|
+
return stdout
|
|
174
|
+
.split("\n")
|
|
175
|
+
.map((line) => line.trim())
|
|
176
|
+
.filter((line) => line.length > 0)
|
|
177
|
+
.map((line) => line.split(/\s+/, 2)[1] ?? "")
|
|
178
|
+
.map((ref) => ref.replace(/^refs\/heads\//, ""))
|
|
179
|
+
.filter((ref) => ref.length > 0);
|
|
180
|
+
}
|
|
181
|
+
function shortSha(value) {
|
|
182
|
+
return value.trim().slice(0, 12);
|
|
183
|
+
}
|
|
184
|
+
function stripBranchRef(branch) {
|
|
185
|
+
return branch.startsWith("refs/heads/") ? branch.slice("refs/heads/".length) : branch;
|
|
186
|
+
}
|
|
187
|
+
async function resolveHostedCloseBranch(opts) {
|
|
188
|
+
const remoteBranches = await listRemoteTaskCloseBranches({
|
|
189
|
+
gitRoot: opts.gitRoot,
|
|
190
|
+
taskId: opts.taskId,
|
|
191
|
+
});
|
|
192
|
+
const explicitBranch = stripBranchRef(opts.explicitBranch?.trim() ?? "");
|
|
193
|
+
if (explicitBranch) {
|
|
194
|
+
const parsedTaskId = parseTaskIdFromCloseBranch(explicitBranch);
|
|
195
|
+
if (parsedTaskId && parsedTaskId !== opts.taskId) {
|
|
196
|
+
throw new CliError({
|
|
197
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
198
|
+
code: "E_VALIDATION",
|
|
199
|
+
message: `Branch ${explicitBranch} does not belong to task ${opts.taskId}.`,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (!remoteBranches.includes(explicitBranch)) {
|
|
203
|
+
throw new CliError({
|
|
204
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
205
|
+
code: "E_VALIDATION",
|
|
206
|
+
message: `Remote hosted closure branch not found: ${explicitBranch}.`,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return explicitBranch;
|
|
210
|
+
}
|
|
211
|
+
if (opts.mergeCommit) {
|
|
212
|
+
const expected = `task-close/${opts.taskId}/${shortSha(opts.mergeCommit)}`;
|
|
213
|
+
if (remoteBranches.includes(expected))
|
|
214
|
+
return expected;
|
|
215
|
+
if (remoteBranches.length === 1)
|
|
216
|
+
return remoteBranches[0] ?? expected;
|
|
217
|
+
if (remoteBranches.length > 1) {
|
|
218
|
+
throw new CliError({
|
|
219
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
220
|
+
code: "E_VALIDATION",
|
|
221
|
+
message: `Multiple remote hosted closure branches match ${opts.taskId}: ${remoteBranches.join(", ")} (use --branch).`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
throw new CliError({
|
|
225
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
226
|
+
code: "E_VALIDATION",
|
|
227
|
+
message: `Remote hosted closure branch not found for ${opts.taskId}: ${expected}.`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (remoteBranches.length === 1)
|
|
231
|
+
return remoteBranches[0] ?? "";
|
|
232
|
+
if (remoteBranches.length > 1) {
|
|
233
|
+
throw new CliError({
|
|
234
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
235
|
+
code: "E_VALIDATION",
|
|
236
|
+
message: `Multiple remote hosted closure branches match ${opts.taskId}: ${remoteBranches.join(", ")} (use --branch).`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
throw new CliError({
|
|
240
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
241
|
+
code: "E_VALIDATION",
|
|
242
|
+
message: `Could not resolve remote hosted closure branch for ${opts.taskId}.`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function buildHostedClosePrTitle(taskId) {
|
|
246
|
+
return `📝 ${taskId} task: close after hosted merge`;
|
|
247
|
+
}
|
|
248
|
+
function buildHostedClosePrBody(opts) {
|
|
249
|
+
const prLine = typeof opts.prNumber === "number" && opts.prNumber > 0
|
|
250
|
+
? `Automated closure for merged task PR #${opts.prNumber}.`
|
|
251
|
+
: "Automated closure for merged task PR.";
|
|
252
|
+
return [
|
|
253
|
+
prLine,
|
|
254
|
+
"",
|
|
255
|
+
`- task_id: \`${opts.taskId}\``,
|
|
256
|
+
`- source_branch: \`${opts.sourceBranch}\``,
|
|
257
|
+
`- merge_sha: \`${opts.mergeSha}\``,
|
|
258
|
+
"",
|
|
259
|
+
"This PR contains only tracked task artifacts produced by the hosted branch_pr closure flow.",
|
|
260
|
+
].join("\n");
|
|
261
|
+
}
|
|
262
|
+
function normalizeGithubPrLink(prNumber, prUrl, verb) {
|
|
263
|
+
return prUrl?.trim()
|
|
264
|
+
? `${verb} GitHub PR #${prNumber}: ${prUrl.trim()}`
|
|
265
|
+
: `${verb} GitHub PR #${prNumber}`;
|
|
266
|
+
}
|
|
267
|
+
async function maybeCleanupMergedTaskBranch(opts) {
|
|
268
|
+
try {
|
|
269
|
+
const cleanup = await cleanupMergedLocalBranch({
|
|
270
|
+
gitRoot: opts.gitRoot,
|
|
271
|
+
branch: opts.branch,
|
|
272
|
+
});
|
|
273
|
+
if (!cleanup.removedBranch && !cleanup.removedWorktree) {
|
|
274
|
+
if (cleanup.skippedReason === "current_worktree") {
|
|
275
|
+
opts.output.info(`local merged branch cleanup skipped: ${opts.branch} is the current checkout`);
|
|
276
|
+
}
|
|
277
|
+
else if (cleanup.skippedReason === "outside_repo") {
|
|
278
|
+
opts.output.info(`local merged branch cleanup skipped: ${opts.branch} is attached to a worktree outside the current checkout root`);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const details = [];
|
|
283
|
+
if (cleanup.removedWorktree && cleanup.worktreePath) {
|
|
284
|
+
details.push(`removed worktree ${cleanup.worktreePath}`);
|
|
285
|
+
}
|
|
286
|
+
if (cleanup.removedBranch) {
|
|
287
|
+
details.push(`deleted branch ${opts.branch}`);
|
|
288
|
+
}
|
|
289
|
+
opts.output.info(`local merged branch cleanup: ${details.join("; ")}`);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
293
|
+
opts.output.warn(`local merged branch cleanup failed for ${opts.branch}: ${message}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function openHostedClosePr(opts) {
|
|
297
|
+
const output = createCliEmitter();
|
|
298
|
+
const { meta, task, taskBranch } = await readHostedCloseState({
|
|
299
|
+
ctx: opts.ctx,
|
|
300
|
+
cwd: opts.cwd,
|
|
301
|
+
rootOverride: opts.rootOverride,
|
|
302
|
+
taskId: opts.taskId,
|
|
303
|
+
});
|
|
304
|
+
const gitRoot = opts.ctx.resolvedProject.gitRoot;
|
|
305
|
+
const repo = await resolveGithubRepo({ gitRoot, repoOverride: opts.repo ?? null });
|
|
306
|
+
const defaultBaseBranch = await resolveBaseBranch({
|
|
307
|
+
cwd: opts.cwd,
|
|
308
|
+
rootOverride: opts.rootOverride ?? null,
|
|
309
|
+
cliBaseOpt: null,
|
|
310
|
+
mode: opts.ctx.config.workflow_mode,
|
|
311
|
+
});
|
|
312
|
+
let sourceBranch = meta?.branch?.trim() ?? taskBranch?.trim() ?? "";
|
|
313
|
+
let baseBranch = meta?.base?.trim() ?? defaultBaseBranch?.trim() ?? "";
|
|
314
|
+
let mergedRecord = sourceBranch.length > 0 &&
|
|
315
|
+
(meta?.status !== "MERGED" || !(meta?.merge_commit?.trim() ?? task.commit?.hash?.trim() ?? ""))
|
|
316
|
+
? await resolveHostedCloseMergeRecord({
|
|
317
|
+
gitRoot,
|
|
318
|
+
repo,
|
|
319
|
+
sourceBranch,
|
|
320
|
+
baseBranch: baseBranch || null,
|
|
321
|
+
prNumber: typeof meta?.pr_number === "number" ? meta.pr_number : null,
|
|
322
|
+
})
|
|
323
|
+
: null;
|
|
324
|
+
const localMergeCommit = meta?.merge_commit?.trim() ?? task.commit?.hash?.trim() ?? "";
|
|
325
|
+
if (!mergedRecord && localMergeCommit) {
|
|
326
|
+
mergedRecord = await resolveHostedCloseMergeRecordByCommit({
|
|
327
|
+
gitRoot,
|
|
328
|
+
repo,
|
|
329
|
+
mergeCommit: localMergeCommit,
|
|
330
|
+
sourceBranch: sourceBranch || null,
|
|
331
|
+
baseBranch: baseBranch || null,
|
|
332
|
+
prNumber: typeof meta?.pr_number === "number" ? meta.pr_number : null,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (!sourceBranch) {
|
|
336
|
+
sourceBranch = mergedRecord?.head?.ref?.trim() ?? "";
|
|
337
|
+
}
|
|
338
|
+
if (!baseBranch) {
|
|
339
|
+
baseBranch = mergedRecord?.base?.ref?.trim() ?? defaultBaseBranch?.trim() ?? "";
|
|
340
|
+
}
|
|
341
|
+
const mergeCommit = meta?.merge_commit?.trim() ??
|
|
342
|
+
task.commit?.hash?.trim() ??
|
|
343
|
+
mergedRecord?.merge_commit_sha?.trim() ??
|
|
344
|
+
"";
|
|
345
|
+
if (!sourceBranch) {
|
|
346
|
+
throw new CliError({
|
|
347
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
348
|
+
code: "E_VALIDATION",
|
|
349
|
+
message: `Missing hosted close source branch for ${opts.taskId}.`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (!mergeCommit) {
|
|
353
|
+
throw new CliError({
|
|
354
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
355
|
+
code: "E_VALIDATION",
|
|
356
|
+
message: `Missing hosted close merge commit for ${opts.taskId}.`,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
if (meta?.status !== "MERGED" && !mergedRecord?.merged_at) {
|
|
360
|
+
throw new CliError({
|
|
361
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
362
|
+
code: "E_USAGE",
|
|
363
|
+
message: `Task ${opts.taskId} is not in MERGED hosted-close state.`,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const branch = await resolveHostedCloseBranch({
|
|
367
|
+
gitRoot,
|
|
368
|
+
taskId: opts.taskId,
|
|
369
|
+
explicitBranch: opts.branch ?? null,
|
|
370
|
+
mergeCommit,
|
|
371
|
+
});
|
|
372
|
+
await maybeCleanupMergedTaskBranch({
|
|
373
|
+
output,
|
|
374
|
+
gitRoot,
|
|
375
|
+
branch: sourceBranch,
|
|
376
|
+
});
|
|
377
|
+
const owner = repo.split("/")[0]?.trim() ?? "";
|
|
378
|
+
const existingQuery = new URLSearchParams({
|
|
379
|
+
state: "open",
|
|
380
|
+
head: `${owner}:${branch}`,
|
|
381
|
+
});
|
|
382
|
+
const existing = await runGhApiJson(gitRoot, [
|
|
383
|
+
`repos/${repo}/pulls?${existingQuery.toString()}`,
|
|
384
|
+
]);
|
|
385
|
+
const existingPr = Array.isArray(existing) ? (existing[0] ?? null) : null;
|
|
386
|
+
const existingNumber = Number(existingPr?.number ?? 0);
|
|
387
|
+
if (Number.isInteger(existingNumber) && existingNumber > 0) {
|
|
388
|
+
output.success("task hosted-close-pr", opts.taskId, normalizeGithubPrLink(existingNumber, existingPr?.html_url ?? null, "linked to"));
|
|
389
|
+
return 0;
|
|
390
|
+
}
|
|
391
|
+
const created = await runGhApiJson(gitRoot, [
|
|
392
|
+
`repos/${repo}/pulls`,
|
|
393
|
+
"-X",
|
|
394
|
+
"POST",
|
|
395
|
+
"-f",
|
|
396
|
+
`title=${buildHostedClosePrTitle(opts.taskId)}`,
|
|
397
|
+
"-f",
|
|
398
|
+
`body=${buildHostedClosePrBody({
|
|
399
|
+
taskId: opts.taskId,
|
|
400
|
+
prNumber: typeof meta?.pr_number === "number"
|
|
401
|
+
? meta.pr_number
|
|
402
|
+
: typeof mergedRecord?.number === "number"
|
|
403
|
+
? mergedRecord.number
|
|
404
|
+
: null,
|
|
405
|
+
sourceBranch,
|
|
406
|
+
mergeSha: mergeCommit,
|
|
407
|
+
})}`,
|
|
408
|
+
"-f",
|
|
409
|
+
`head=${branch}`,
|
|
410
|
+
"-f",
|
|
411
|
+
`base=${baseBranch}`,
|
|
412
|
+
]);
|
|
413
|
+
const createdNumber = Number(created.number ?? 0);
|
|
414
|
+
if (!Number.isInteger(createdNumber) || createdNumber <= 0) {
|
|
415
|
+
throw new CliError({
|
|
416
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
417
|
+
code: "E_VALIDATION",
|
|
418
|
+
message: `GitHub did not return a valid PR number for hosted closure branch ${branch}.`,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
output.success("task hosted-close-pr", opts.taskId, normalizeGithubPrLink(createdNumber, created.html_url ?? null, "created"));
|
|
422
|
+
return 0;
|
|
423
|
+
}
|
|
424
|
+
export function makeRunTaskHostedClosePrHandler(getCtx) {
|
|
425
|
+
return async (ctx, p) => {
|
|
426
|
+
try {
|
|
427
|
+
const commandCtx = await getCtx("task hosted-close-pr");
|
|
428
|
+
for (const taskId of p.taskIds) {
|
|
429
|
+
await openHostedClosePr({
|
|
430
|
+
ctx: commandCtx,
|
|
431
|
+
cwd: ctx.cwd,
|
|
432
|
+
rootOverride: ctx.rootOverride,
|
|
433
|
+
taskId,
|
|
434
|
+
branch: p.branch,
|
|
435
|
+
repo: p.repo,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return 0;
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
if (err instanceof CliError)
|
|
442
|
+
throw err;
|
|
443
|
+
throw mapBackendError(err, {
|
|
444
|
+
command: "task hosted-close-pr",
|
|
445
|
+
root: ctx.rootOverride ?? null,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-close.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/hosted-close.command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAStE,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"hosted-close.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/hosted-close.command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAStE,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAOrF,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,qBAAqB,CAqClE,CAAC;AAmYF,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC9E,KAAK,UAAU,EAAE,QAAQ,qBAAqB,KAAG,OAAO,CAAC,MAAM,CAAC,CA2B/E"}
|