dev-loops 0.2.5 → 0.2.7
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/.claude/.claude-plugin/plugin.json +9 -2
- package/.claude/agents/dev-loop.md +2 -1
- package/.claude/hooks/_bash-command-classify.mjs +164 -0
- package/.claude/hooks/_hook-decisions.mjs +130 -0
- package/.claude/hooks/_hook-io.mjs +1 -1
- package/.claude/hooks/_run-context.mjs +179 -0
- package/.claude/hooks/post-tool-use-merge.mjs +1 -1
- package/.claude/hooks/pre-tool-use-bash-gate.mjs +2 -2
- package/.claude/hooks/pre-tool-use-write-guard.mjs +1 -1
- package/.claude/skills/dev-loop/SKILL.md +5 -5
- package/.claude/skills/docs/merge-preconditions.md +15 -0
- package/.claude/skills/docs/pr-lifecycle-contract.md +1 -0
- package/CHANGELOG.md +52 -0
- package/agents/dev-loop.agent.md +5 -1
- package/cli/index.mjs +3 -1
- package/package.json +2 -2
- package/scripts/_cli-primitives.mjs +2 -0
- package/scripts/claude/generate-claude-assets.mjs +71 -3
- package/scripts/docs/validate-links.mjs +20 -11
- package/scripts/github/capture-review-threads.mjs +32 -14
- package/scripts/github/detect-checkpoint-evidence.mjs +28 -11
- package/scripts/github/detect-linked-issue-pr.mjs +22 -10
- package/scripts/github/manage-sub-issues.mjs +37 -16
- package/scripts/github/probe-copilot-review.mjs +24 -12
- package/scripts/github/ready-for-review.mjs +22 -10
- package/scripts/github/reconcile-draft-gate.mjs +22 -10
- package/scripts/github/reply-resolve-review-threads.mjs +34 -15
- package/scripts/github/request-copilot-review.mjs +28 -11
- package/scripts/github/resolve-tracker-local-spec.mjs +29 -12
- package/scripts/github/stage-reviewer-draft.mjs +32 -14
- package/scripts/github/upsert-checkpoint-verdict.mjs +49 -26
- package/scripts/github/write-gate-findings-log.mjs +41 -20
- package/scripts/loop/_loop-pr-aggregation.mjs +45 -0
- package/scripts/loop/build-handoff-envelope.mjs +32 -14
- package/scripts/loop/conductor-monitor.mjs +26 -47
- package/scripts/loop/conductor.mjs +31 -12
- package/scripts/loop/copilot-pr-handoff.mjs +31 -14
- package/scripts/loop/debt-remediate.mjs +28 -11
- package/scripts/loop/detect-copilot-loop-state.mjs +29 -12
- package/scripts/loop/detect-copilot-session-activity.mjs +29 -12
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +26 -10
- package/scripts/loop/detect-internal-only-pr.mjs +31 -13
- package/scripts/loop/detect-issue-refinement-artifact.mjs +29 -12
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +28 -11
- package/scripts/loop/detect-reviewer-loop-state.mjs +35 -16
- package/scripts/loop/detect-stale-runner.mjs +32 -14
- package/scripts/loop/detect-tracker-pr-state.mjs +23 -8
- package/scripts/loop/info.mjs +28 -10
- package/scripts/loop/inspect-run-viewer/cli.mjs +44 -21
- package/scripts/loop/inspect-run.mjs +35 -16
- package/scripts/loop/outer-loop.mjs +35 -16
- package/scripts/loop/pr-runner-coordination.mjs +31 -12
- package/scripts/loop/pre-commit-branch-guard.mjs +26 -9
- package/scripts/loop/pre-flight-gate.mjs +25 -9
- package/scripts/loop/pre-pr-ready-gate.mjs +24 -8
- package/scripts/loop/pre-push-main-guard.mjs +19 -5
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +23 -7
- package/scripts/loop/resolve-dev-loop-startup.mjs +29 -12
- package/scripts/loop/run-conductor-cycle.mjs +25 -47
- package/scripts/loop/run-refinement-audit.mjs +44 -22
- package/scripts/loop/run-watch-cycle.mjs +28 -12
- package/scripts/loop/steer-loop.mjs +122 -62
- package/scripts/loop/watch-initial-copilot-pr.mjs +28 -12
- package/scripts/projects/archive-done-items.mjs +410 -0
- package/scripts/projects/reorder-queue-item.mjs +334 -76
- package/scripts/refine/_refine-helpers.mjs +21 -9
- package/scripts/refine/verify.mjs +31 -13
- package/skills/dev-loop/SKILL.md +9 -5
- package/skills/docs/merge-preconditions.md +15 -0
- package/skills/docs/pr-lifecycle-contract.md +1 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
3
3
|
import {
|
|
4
4
|
buildParseError,
|
|
5
5
|
formatCliError,
|
|
6
6
|
isDirectCliRun,
|
|
7
|
-
parseJsonText,
|
|
8
7
|
} from "../_core-helpers.mjs";
|
|
9
8
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
10
9
|
import { detectPrGateCoordinationState } from "./detect-pr-gate-coordination-state.mjs";
|
|
@@ -14,9 +13,11 @@ import {
|
|
|
14
13
|
SUBAGENT_ACTIONS as SHARED_SUBAGENT_ACTIONS,
|
|
15
14
|
buildHandoffContractForConductorAction,
|
|
16
15
|
} from "./_handoff-contract.mjs";
|
|
16
|
+
import { listOpenPrs } from "./_loop-pr-aggregation.mjs";
|
|
17
|
+
import { parseArgs } from "node:util";
|
|
18
|
+
export { listOpenPrs };
|
|
17
19
|
const USAGE = `Usage: run-conductor-cycle.mjs --repo <owner/name>
|
|
18
20
|
Poll all open PRs, detect state, and output an ordered action queue.`.trim();
|
|
19
|
-
const OPEN_PR_LIST_LIMIT = 1000;
|
|
20
21
|
export const CHECKPOINT_ACTION_TO_CONDUCTOR_ACTION = Object.freeze({
|
|
21
22
|
[PR_CHECKPOINT_ACTION.ADDRESS_REVIEW_FEEDBACK]: "fix_threads",
|
|
22
23
|
[PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS]: "fix_threads",
|
|
@@ -63,22 +64,36 @@ export function actionRequiresApproval(action, autonomyStopAt = ["merge"]) {
|
|
|
63
64
|
}
|
|
64
65
|
const parseError = buildParseError(USAGE);
|
|
65
66
|
export function parseCliArgs(argv) {
|
|
66
|
-
const args = [...argv];
|
|
67
67
|
const options = {
|
|
68
68
|
help: false,
|
|
69
69
|
repo: undefined,
|
|
70
70
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const { tokens } = parseArgs({
|
|
72
|
+
args: [...argv],
|
|
73
|
+
options: {
|
|
74
|
+
help: { type: "boolean", short: "h" },
|
|
75
|
+
repo: { type: "string" },
|
|
76
|
+
},
|
|
77
|
+
allowPositionals: true,
|
|
78
|
+
strict: false,
|
|
79
|
+
tokens: true,
|
|
80
|
+
});
|
|
81
|
+
for (const token of tokens) {
|
|
82
|
+
if (token.kind === "positional") {
|
|
83
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
84
|
+
}
|
|
85
|
+
if (token.kind !== "option") {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (token.name === "help") {
|
|
74
89
|
options.help = true;
|
|
75
90
|
return options;
|
|
76
91
|
}
|
|
77
|
-
if (token === "
|
|
78
|
-
options.repo =
|
|
92
|
+
if (token.name === "repo") {
|
|
93
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
79
94
|
continue;
|
|
80
95
|
}
|
|
81
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
96
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
82
97
|
}
|
|
83
98
|
if (options.repo === undefined) {
|
|
84
99
|
throw parseError("run-conductor-cycle requires --repo <owner/name>");
|
|
@@ -90,43 +105,6 @@ export function parseCliArgs(argv) {
|
|
|
90
105
|
}
|
|
91
106
|
return options;
|
|
92
107
|
}
|
|
93
|
-
export async function listOpenPrs({ repo }, { env, ghCommand }) {
|
|
94
|
-
const result = await runChild(
|
|
95
|
-
ghCommand,
|
|
96
|
-
[
|
|
97
|
-
"pr",
|
|
98
|
-
"list",
|
|
99
|
-
"--repo",
|
|
100
|
-
repo,
|
|
101
|
-
"--state",
|
|
102
|
-
"open",
|
|
103
|
-
"--limit",
|
|
104
|
-
String(OPEN_PR_LIST_LIMIT),
|
|
105
|
-
"--json",
|
|
106
|
-
"number,title,url,isDraft,headRefName,author",
|
|
107
|
-
],
|
|
108
|
-
env,
|
|
109
|
-
);
|
|
110
|
-
if (result.code !== 0) {
|
|
111
|
-
const detail = result.stderr.trim() || `exit code ${result.code}`;
|
|
112
|
-
throw new Error(`gh command failed: ${detail}`);
|
|
113
|
-
}
|
|
114
|
-
const payload = parseJsonText(result.stdout);
|
|
115
|
-
if (!Array.isArray(payload)) {
|
|
116
|
-
throw new Error("Invalid gh pr list payload: expected an array");
|
|
117
|
-
}
|
|
118
|
-
return payload
|
|
119
|
-
.map((pr) => ({
|
|
120
|
-
number: Number.isInteger(pr?.number) ? pr.number : null,
|
|
121
|
-
title: typeof pr?.title === "string" ? pr.title : "",
|
|
122
|
-
url: typeof pr?.url === "string" ? pr.url : null,
|
|
123
|
-
isDraft: Boolean(pr?.isDraft),
|
|
124
|
-
headRefName: typeof pr?.headRefName === "string" ? pr.headRefName : null,
|
|
125
|
-
authorLogin: typeof pr?.author?.login === "string" ? pr.author.login : null,
|
|
126
|
-
}))
|
|
127
|
-
.filter((pr) => pr.number !== null)
|
|
128
|
-
.sort((left, right) => left.number - right.number);
|
|
129
|
-
}
|
|
130
108
|
export async function detectPrState(
|
|
131
109
|
pr,
|
|
132
110
|
{
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
5
|
-
import { parsePositiveInteger,
|
|
5
|
+
import { parsePositiveInteger, requireTokenValue, runCommand } from "../_cli-primitives.mjs";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
6
7
|
const DEFAULT_MAX_LINES = 1000;
|
|
7
8
|
const DEFAULT_DUPLICATE_WINDOW_LINES = 4;
|
|
8
9
|
const DEFAULT_BRANCH_THRESHOLD = 25;
|
|
@@ -43,7 +44,6 @@ const PRIORITY_ORDER = new Map([
|
|
|
43
44
|
["low", 2],
|
|
44
45
|
]);
|
|
45
46
|
export function parseRefinementAuditCliArgs(argv) {
|
|
46
|
-
const args = [...argv];
|
|
47
47
|
const options = {
|
|
48
48
|
help: false,
|
|
49
49
|
paths: undefined,
|
|
@@ -55,57 +55,79 @@ export function parseRefinementAuditCliArgs(argv) {
|
|
|
55
55
|
thinWrapperMaxLines: DEFAULT_THIN_WRAPPER_MAX_LINES,
|
|
56
56
|
output: undefined,
|
|
57
57
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const { tokens } = parseArgs({
|
|
59
|
+
args: [...argv],
|
|
60
|
+
options: {
|
|
61
|
+
help: { type: "boolean", short: "h" },
|
|
62
|
+
paths: { type: "string" },
|
|
63
|
+
"paths-file": { type: "string" },
|
|
64
|
+
root: { type: "string" },
|
|
65
|
+
"max-lines": { type: "string" },
|
|
66
|
+
"duplicate-window-lines": { type: "string" },
|
|
67
|
+
"branch-threshold": { type: "string" },
|
|
68
|
+
"thin-wrapper-max-lines": { type: "string" },
|
|
69
|
+
output: { type: "string" },
|
|
70
|
+
},
|
|
71
|
+
allowPositionals: true,
|
|
72
|
+
strict: false,
|
|
73
|
+
tokens: true,
|
|
74
|
+
});
|
|
75
|
+
for (const token of tokens) {
|
|
76
|
+
if (token.kind === "positional") {
|
|
77
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
78
|
+
}
|
|
79
|
+
if (token.kind !== "option") {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (token.name === "help") {
|
|
61
83
|
options.help = true;
|
|
62
84
|
return options;
|
|
63
85
|
}
|
|
64
|
-
if (token === "
|
|
65
|
-
options.paths =
|
|
86
|
+
if (token.name === "paths") {
|
|
87
|
+
options.paths = requireTokenValue(token, parseError, { flagPattern: /^-/u });
|
|
66
88
|
continue;
|
|
67
89
|
}
|
|
68
|
-
if (token === "
|
|
69
|
-
options.pathsFile =
|
|
90
|
+
if (token.name === "paths-file") {
|
|
91
|
+
options.pathsFile = requireTokenValue(token, parseError, { flagPattern: /^-/u });
|
|
70
92
|
continue;
|
|
71
93
|
}
|
|
72
|
-
if (token === "
|
|
73
|
-
options.root =
|
|
94
|
+
if (token.name === "root") {
|
|
95
|
+
options.root = requireTokenValue(token, parseError, { flagPattern: /^-/u });
|
|
74
96
|
continue;
|
|
75
97
|
}
|
|
76
|
-
if (token === "
|
|
77
|
-
options.maxLines = parsePositiveInteger(
|
|
98
|
+
if (token.name === "max-lines") {
|
|
99
|
+
options.maxLines = parsePositiveInteger(requireTokenValue(token, parseError), "--max-lines", parseError);
|
|
78
100
|
continue;
|
|
79
101
|
}
|
|
80
|
-
if (token === "
|
|
102
|
+
if (token.name === "duplicate-window-lines") {
|
|
81
103
|
options.duplicateWindowLines = parsePositiveInteger(
|
|
82
|
-
|
|
104
|
+
requireTokenValue(token, parseError),
|
|
83
105
|
"--duplicate-window-lines",
|
|
84
106
|
parseError,
|
|
85
107
|
);
|
|
86
108
|
continue;
|
|
87
109
|
}
|
|
88
|
-
if (token === "
|
|
110
|
+
if (token.name === "branch-threshold") {
|
|
89
111
|
options.branchThreshold = parsePositiveInteger(
|
|
90
|
-
|
|
112
|
+
requireTokenValue(token, parseError),
|
|
91
113
|
"--branch-threshold",
|
|
92
114
|
parseError,
|
|
93
115
|
);
|
|
94
116
|
continue;
|
|
95
117
|
}
|
|
96
|
-
if (token === "
|
|
118
|
+
if (token.name === "thin-wrapper-max-lines") {
|
|
97
119
|
options.thinWrapperMaxLines = parsePositiveInteger(
|
|
98
|
-
|
|
120
|
+
requireTokenValue(token, parseError),
|
|
99
121
|
"--thin-wrapper-max-lines",
|
|
100
122
|
parseError,
|
|
101
123
|
);
|
|
102
124
|
continue;
|
|
103
125
|
}
|
|
104
|
-
if (token === "
|
|
105
|
-
options.output =
|
|
126
|
+
if (token.name === "output") {
|
|
127
|
+
options.output = requireTokenValue(token, parseError, { flagPattern: /^-/u });
|
|
106
128
|
continue;
|
|
107
129
|
}
|
|
108
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
130
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
109
131
|
}
|
|
110
132
|
if (options.paths !== undefined && options.pathsFile !== undefined) {
|
|
111
133
|
throw parseError("Specify exactly one of --paths or --paths-file");
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { parsePrNumber,
|
|
3
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
4
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
5
5
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
6
6
|
import { DEV_LOOP_CONTRACT_TRACE_CLASSIFICATION } from "@dev-loops/core/loop/public-dev-loop-routing";
|
|
7
7
|
import { watchCopilotReview } from "../github/probe-copilot-review.mjs";
|
|
8
8
|
import { runHandoff } from "./copilot-pr-handoff.mjs";
|
|
9
9
|
import { detectCopilotSessionActivity } from "./detect-copilot-session-activity.mjs";
|
|
10
|
+
import { parseArgs } from "node:util";
|
|
10
11
|
import {
|
|
11
12
|
EXTERNAL_HEALTHY_WAIT_TIMEOUT_POLICY,
|
|
12
13
|
enforceExternalHealthyWaitTimeout,
|
|
@@ -178,30 +179,45 @@ function buildWatchCycleContractTrace({
|
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
export function parseWatchCycleCliArgs(argv) {
|
|
181
|
-
const args = [...argv];
|
|
182
182
|
const options = {
|
|
183
183
|
help: false,
|
|
184
184
|
repo: undefined,
|
|
185
185
|
pr: undefined,
|
|
186
186
|
};
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
const { tokens } = parseArgs({
|
|
188
|
+
args: [...argv],
|
|
189
|
+
options: {
|
|
190
|
+
help: { type: "boolean", short: "h" },
|
|
191
|
+
repo: { type: "string" },
|
|
192
|
+
pr: { type: "string" },
|
|
193
|
+
},
|
|
194
|
+
allowPositionals: true,
|
|
195
|
+
strict: false,
|
|
196
|
+
tokens: true,
|
|
197
|
+
});
|
|
198
|
+
for (const token of tokens) {
|
|
199
|
+
if (token.kind === "positional") {
|
|
200
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
201
|
+
}
|
|
202
|
+
if (token.kind !== "option") {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (token.name === "help") {
|
|
190
206
|
options.help = true;
|
|
191
207
|
return options;
|
|
192
208
|
}
|
|
193
|
-
if (REMOVED_FLAGS.has(token)) {
|
|
194
|
-
rejectRemovedFlag(token);
|
|
209
|
+
if (REMOVED_FLAGS.has(token.rawName)) {
|
|
210
|
+
rejectRemovedFlag(token.rawName);
|
|
195
211
|
}
|
|
196
|
-
if (token === "
|
|
197
|
-
options.repo =
|
|
212
|
+
if (token.name === "repo") {
|
|
213
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
198
214
|
continue;
|
|
199
215
|
}
|
|
200
|
-
if (token === "
|
|
201
|
-
options.pr = parsePrNumber(
|
|
216
|
+
if (token.name === "pr") {
|
|
217
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
202
218
|
continue;
|
|
203
219
|
}
|
|
204
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
220
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
205
221
|
}
|
|
206
222
|
if (options.repo === undefined || options.pr === undefined) {
|
|
207
223
|
throw parseError("run-watch-cycle requires both --repo <owner/name> and --pr <number>");
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
6
7
|
import {
|
|
7
8
|
STEERING_KIND,
|
|
8
9
|
STEERING_RESULT,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
withStateFileLock,
|
|
32
33
|
} from "./_steering-state-file.mjs";
|
|
33
34
|
import { formatCliError } from "../_core-helpers.mjs";
|
|
34
|
-
import {
|
|
35
|
+
import { requireTokenValue as readSharedTokenValue } from "../_cli-primitives.mjs";
|
|
35
36
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
36
37
|
const SUBMIT_USAGE = `Usage:
|
|
37
38
|
steer-loop.mjs submit --repo <owner/name> --pr <number>
|
|
@@ -112,10 +113,9 @@ function runIdMismatchError(persistedRunId, requestedRunId) {
|
|
|
112
113
|
`run-id mismatch: --state-file contains run ${JSON.stringify(persistedRunId)} but --run-id is ${JSON.stringify(requestedRunId)}. Use the correct --run-id or point --state-file at the right file.`
|
|
113
114
|
);
|
|
114
115
|
}
|
|
115
|
-
function readRequiredOptionValue(
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
flag,
|
|
116
|
+
function readRequiredOptionValue(token, usage, { allowFlagLike = false } = {}) {
|
|
117
|
+
return readSharedTokenValue(
|
|
118
|
+
token,
|
|
119
119
|
(message) => usageError(message, usage),
|
|
120
120
|
{ flagPattern: allowFlagLike ? /$^/u : /^--/u },
|
|
121
121
|
);
|
|
@@ -139,7 +139,6 @@ function parsePositiveIntegerOption(raw, flag, usage) {
|
|
|
139
139
|
return Number(raw);
|
|
140
140
|
}
|
|
141
141
|
export function parseSubmitCliArgs(argv) {
|
|
142
|
-
const args = [...argv];
|
|
143
142
|
const options = {
|
|
144
143
|
help: false,
|
|
145
144
|
repo: undefined,
|
|
@@ -156,48 +155,74 @@ export function parseSubmitCliArgs(argv) {
|
|
|
156
155
|
copilotInputPath: undefined,
|
|
157
156
|
reviewerInputPath: undefined,
|
|
158
157
|
};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
const { tokens } = parseArgs({
|
|
159
|
+
args: [...argv],
|
|
160
|
+
options: {
|
|
161
|
+
help: { type: "boolean", short: "h" },
|
|
162
|
+
"run-id": { type: "string" },
|
|
163
|
+
repo: { type: "string" },
|
|
164
|
+
pr: { type: "string" },
|
|
165
|
+
kind: { type: "string" },
|
|
166
|
+
directive: { type: "string" },
|
|
167
|
+
seq: { type: "string" },
|
|
168
|
+
"state-file": { type: "string" },
|
|
169
|
+
"loop-state": { type: "string" },
|
|
170
|
+
"apply-mode": { type: "string" },
|
|
171
|
+
"event-id": { type: "string" },
|
|
172
|
+
"copilot-input": { type: "string" },
|
|
173
|
+
"reviewer-input": { type: "string" },
|
|
174
|
+
},
|
|
175
|
+
allowPositionals: true,
|
|
176
|
+
strict: false,
|
|
177
|
+
tokens: true,
|
|
178
|
+
});
|
|
179
|
+
for (const token of tokens) {
|
|
180
|
+
if (token.kind === "positional") {
|
|
181
|
+
throw usageError(`Unknown argument: ${token.value}`, SUBMIT_USAGE);
|
|
182
|
+
}
|
|
183
|
+
if (token.kind !== "option") {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (token.name === "help") {
|
|
162
187
|
options.help = true;
|
|
163
188
|
return options;
|
|
164
189
|
}
|
|
165
|
-
if (token === "
|
|
166
|
-
options.runId = readRequiredOptionValue(
|
|
190
|
+
if (token.name === "run-id") {
|
|
191
|
+
options.runId = readRequiredOptionValue(token, SUBMIT_USAGE).trim();
|
|
167
192
|
validateSafeRunId(options.runId, SUBMIT_USAGE);
|
|
168
193
|
continue;
|
|
169
194
|
}
|
|
170
|
-
if (token === "
|
|
171
|
-
options.repo = readRequiredOptionValue(
|
|
195
|
+
if (token.name === "repo") {
|
|
196
|
+
options.repo = readRequiredOptionValue(token, SUBMIT_USAGE).trim();
|
|
172
197
|
parseRepoSlugOption(options.repo, SUBMIT_USAGE);
|
|
173
198
|
continue;
|
|
174
199
|
}
|
|
175
|
-
if (token === "
|
|
176
|
-
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(
|
|
200
|
+
if (token.name === "pr") {
|
|
201
|
+
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(token, SUBMIT_USAGE), "--pr", SUBMIT_USAGE);
|
|
177
202
|
continue;
|
|
178
203
|
}
|
|
179
|
-
if (token === "
|
|
180
|
-
const val = readRequiredOptionValue(
|
|
204
|
+
if (token.name === "kind") {
|
|
205
|
+
const val = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
181
206
|
if (!VALID_KINDS.has(val)) {
|
|
182
207
|
throw usageError(`--kind must be one of: ${[...VALID_KINDS].join(", ")}`, SUBMIT_USAGE);
|
|
183
208
|
}
|
|
184
209
|
options.kind = val;
|
|
185
210
|
continue;
|
|
186
211
|
}
|
|
187
|
-
if (token === "
|
|
188
|
-
options.directive = readRequiredOptionValue(
|
|
212
|
+
if (token.name === "directive") {
|
|
213
|
+
options.directive = readRequiredOptionValue(token, SUBMIT_USAGE, { allowFlagLike: true }).trim();
|
|
189
214
|
continue;
|
|
190
215
|
}
|
|
191
|
-
if (token === "
|
|
192
|
-
options.seq = parsePositiveIntegerOption(readRequiredOptionValue(
|
|
216
|
+
if (token.name === "seq") {
|
|
217
|
+
options.seq = parsePositiveIntegerOption(readRequiredOptionValue(token, SUBMIT_USAGE), "--seq", SUBMIT_USAGE);
|
|
193
218
|
continue;
|
|
194
219
|
}
|
|
195
|
-
if (token === "
|
|
196
|
-
options.stateFile = readRequiredOptionValue(
|
|
220
|
+
if (token.name === "state-file") {
|
|
221
|
+
options.stateFile = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
197
222
|
continue;
|
|
198
223
|
}
|
|
199
|
-
if (token === "
|
|
200
|
-
const val = readRequiredOptionValue(
|
|
224
|
+
if (token.name === "loop-state") {
|
|
225
|
+
const val = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
201
226
|
if (!VALID_LOOP_STATES.has(val)) {
|
|
202
227
|
throw usageError(`--loop-state must be one of: ${[...VALID_LOOP_STATES].join(", ")}`, SUBMIT_USAGE);
|
|
203
228
|
}
|
|
@@ -205,27 +230,27 @@ export function parseSubmitCliArgs(argv) {
|
|
|
205
230
|
options.loopStateExplicit = true;
|
|
206
231
|
continue;
|
|
207
232
|
}
|
|
208
|
-
if (token === "
|
|
209
|
-
const val = readRequiredOptionValue(
|
|
233
|
+
if (token.name === "apply-mode") {
|
|
234
|
+
const val = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
210
235
|
if (!VALID_APPLY_MODES.has(val)) {
|
|
211
236
|
throw usageError(`--apply-mode must be one of: ${[...VALID_APPLY_MODES].join(", ")}`, SUBMIT_USAGE);
|
|
212
237
|
}
|
|
213
238
|
options.applyMode = val;
|
|
214
239
|
continue;
|
|
215
240
|
}
|
|
216
|
-
if (token === "
|
|
217
|
-
options.eventId = readRequiredOptionValue(
|
|
241
|
+
if (token.name === "event-id") {
|
|
242
|
+
options.eventId = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
218
243
|
continue;
|
|
219
244
|
}
|
|
220
|
-
if (token === "
|
|
221
|
-
options.copilotInputPath = readRequiredOptionValue(
|
|
245
|
+
if (token.name === "copilot-input") {
|
|
246
|
+
options.copilotInputPath = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
222
247
|
continue;
|
|
223
248
|
}
|
|
224
|
-
if (token === "
|
|
225
|
-
options.reviewerInputPath = readRequiredOptionValue(
|
|
249
|
+
if (token.name === "reviewer-input") {
|
|
250
|
+
options.reviewerInputPath = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
226
251
|
continue;
|
|
227
252
|
}
|
|
228
|
-
throw usageError(`Unknown argument: ${token}`, SUBMIT_USAGE);
|
|
253
|
+
throw usageError(`Unknown argument: ${token.rawName}`, SUBMIT_USAGE);
|
|
229
254
|
}
|
|
230
255
|
if (!options.help) {
|
|
231
256
|
if ((options.repo === undefined) !== (options.pr === undefined)) {
|
|
@@ -250,7 +275,6 @@ export function parseSubmitCliArgs(argv) {
|
|
|
250
275
|
return options;
|
|
251
276
|
}
|
|
252
277
|
export function parseStatusCliArgs(argv) {
|
|
253
|
-
const args = [...argv];
|
|
254
278
|
const options = {
|
|
255
279
|
help: false,
|
|
256
280
|
repo: undefined,
|
|
@@ -258,31 +282,49 @@ export function parseStatusCliArgs(argv) {
|
|
|
258
282
|
runId: undefined,
|
|
259
283
|
stateFile: undefined,
|
|
260
284
|
};
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
285
|
+
const { tokens } = parseArgs({
|
|
286
|
+
args: [...argv],
|
|
287
|
+
options: {
|
|
288
|
+
help: { type: "boolean", short: "h" },
|
|
289
|
+
"run-id": { type: "string" },
|
|
290
|
+
repo: { type: "string" },
|
|
291
|
+
pr: { type: "string" },
|
|
292
|
+
"state-file": { type: "string" },
|
|
293
|
+
},
|
|
294
|
+
allowPositionals: true,
|
|
295
|
+
strict: false,
|
|
296
|
+
tokens: true,
|
|
297
|
+
});
|
|
298
|
+
for (const token of tokens) {
|
|
299
|
+
if (token.kind === "positional") {
|
|
300
|
+
throw usageError(`Unknown argument: ${token.value}`, STATUS_USAGE);
|
|
301
|
+
}
|
|
302
|
+
if (token.kind !== "option") {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (token.name === "help") {
|
|
264
306
|
options.help = true;
|
|
265
307
|
return options;
|
|
266
308
|
}
|
|
267
|
-
if (token === "
|
|
268
|
-
options.runId = readRequiredOptionValue(
|
|
309
|
+
if (token.name === "run-id") {
|
|
310
|
+
options.runId = readRequiredOptionValue(token, STATUS_USAGE).trim();
|
|
269
311
|
validateSafeRunId(options.runId, STATUS_USAGE);
|
|
270
312
|
continue;
|
|
271
313
|
}
|
|
272
|
-
if (token === "
|
|
273
|
-
options.repo = readRequiredOptionValue(
|
|
314
|
+
if (token.name === "repo") {
|
|
315
|
+
options.repo = readRequiredOptionValue(token, STATUS_USAGE).trim();
|
|
274
316
|
parseRepoSlugOption(options.repo, STATUS_USAGE);
|
|
275
317
|
continue;
|
|
276
318
|
}
|
|
277
|
-
if (token === "
|
|
278
|
-
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(
|
|
319
|
+
if (token.name === "pr") {
|
|
320
|
+
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(token, STATUS_USAGE), "--pr", STATUS_USAGE);
|
|
279
321
|
continue;
|
|
280
322
|
}
|
|
281
|
-
if (token === "
|
|
282
|
-
options.stateFile = readRequiredOptionValue(
|
|
323
|
+
if (token.name === "state-file") {
|
|
324
|
+
options.stateFile = readRequiredOptionValue(token, STATUS_USAGE);
|
|
283
325
|
continue;
|
|
284
326
|
}
|
|
285
|
-
throw usageError(`Unknown argument: ${token}`, STATUS_USAGE);
|
|
327
|
+
throw usageError(`Unknown argument: ${token.rawName}`, STATUS_USAGE);
|
|
286
328
|
}
|
|
287
329
|
if (!options.help) {
|
|
288
330
|
if ((options.repo === undefined) !== (options.pr === undefined)) {
|
|
@@ -298,7 +340,6 @@ export function parseStatusCliArgs(argv) {
|
|
|
298
340
|
return options;
|
|
299
341
|
}
|
|
300
342
|
export function parsePromoteCliArgs(argv) {
|
|
301
|
-
const args = [...argv];
|
|
302
343
|
const options = {
|
|
303
344
|
help: false,
|
|
304
345
|
repo: undefined,
|
|
@@ -307,39 +348,58 @@ export function parsePromoteCliArgs(argv) {
|
|
|
307
348
|
stateFile: undefined,
|
|
308
349
|
loopState: undefined,
|
|
309
350
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
351
|
+
const { tokens } = parseArgs({
|
|
352
|
+
args: [...argv],
|
|
353
|
+
options: {
|
|
354
|
+
help: { type: "boolean", short: "h" },
|
|
355
|
+
"run-id": { type: "string" },
|
|
356
|
+
repo: { type: "string" },
|
|
357
|
+
pr: { type: "string" },
|
|
358
|
+
"state-file": { type: "string" },
|
|
359
|
+
"loop-state": { type: "string" },
|
|
360
|
+
},
|
|
361
|
+
allowPositionals: true,
|
|
362
|
+
strict: false,
|
|
363
|
+
tokens: true,
|
|
364
|
+
});
|
|
365
|
+
for (const token of tokens) {
|
|
366
|
+
if (token.kind === "positional") {
|
|
367
|
+
throw usageError(`Unknown argument: ${token.value}`, PROMOTE_USAGE);
|
|
368
|
+
}
|
|
369
|
+
if (token.kind !== "option") {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (token.name === "help") {
|
|
313
373
|
options.help = true;
|
|
314
374
|
return options;
|
|
315
375
|
}
|
|
316
|
-
if (token === "
|
|
317
|
-
options.runId = readRequiredOptionValue(
|
|
376
|
+
if (token.name === "run-id") {
|
|
377
|
+
options.runId = readRequiredOptionValue(token, PROMOTE_USAGE).trim();
|
|
318
378
|
validateSafeRunId(options.runId, PROMOTE_USAGE);
|
|
319
379
|
continue;
|
|
320
380
|
}
|
|
321
|
-
if (token === "
|
|
322
|
-
options.repo = readRequiredOptionValue(
|
|
381
|
+
if (token.name === "repo") {
|
|
382
|
+
options.repo = readRequiredOptionValue(token, PROMOTE_USAGE).trim();
|
|
323
383
|
parseRepoSlugOption(options.repo, PROMOTE_USAGE);
|
|
324
384
|
continue;
|
|
325
385
|
}
|
|
326
|
-
if (token === "
|
|
327
|
-
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(
|
|
386
|
+
if (token.name === "pr") {
|
|
387
|
+
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(token, PROMOTE_USAGE), "--pr", PROMOTE_USAGE);
|
|
328
388
|
continue;
|
|
329
389
|
}
|
|
330
|
-
if (token === "
|
|
331
|
-
options.stateFile = readRequiredOptionValue(
|
|
390
|
+
if (token.name === "state-file") {
|
|
391
|
+
options.stateFile = readRequiredOptionValue(token, PROMOTE_USAGE);
|
|
332
392
|
continue;
|
|
333
393
|
}
|
|
334
|
-
if (token === "
|
|
335
|
-
const val = readRequiredOptionValue(
|
|
394
|
+
if (token.name === "loop-state") {
|
|
395
|
+
const val = readRequiredOptionValue(token, PROMOTE_USAGE);
|
|
336
396
|
if (!VALID_LOOP_STATES.has(val)) {
|
|
337
397
|
throw usageError(`--loop-state must be one of: ${[...VALID_LOOP_STATES].join(", ")}`, PROMOTE_USAGE);
|
|
338
398
|
}
|
|
339
399
|
options.loopState = val;
|
|
340
400
|
continue;
|
|
341
401
|
}
|
|
342
|
-
throw usageError(`Unknown argument: ${token}`, PROMOTE_USAGE);
|
|
402
|
+
throw usageError(`Unknown argument: ${token.rawName}`, PROMOTE_USAGE);
|
|
343
403
|
}
|
|
344
404
|
if (!options.help) {
|
|
345
405
|
if ((options.repo === undefined) !== (options.pr === undefined)) {
|