dev-loops 0.2.7 → 0.4.0
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 +1 -1
- package/.claude/agents/dev-loop.md +1 -1
- package/.claude/agents/developer.md +1 -0
- package/.claude/agents/fixer.md +1 -0
- package/.claude/agents/review.md +30 -0
- package/.claude/hooks/_run-context.mjs +9 -16
- package/.claude/skills/copilot-pr-followup/SKILL.md +62 -6
- package/.claude/skills/dev-loop/SKILL.md +6 -6
- package/.claude/skills/docs/anti-patterns.md +2 -0
- package/.claude/skills/docs/copilot-loop-operations.md +3 -3
- package/.claude/skills/docs/issue-intake-procedure.md +4 -0
- package/.claude/skills/docs/merge-preconditions.md +65 -1
- package/.claude/skills/docs/stop-conditions.md +1 -0
- package/.claude/skills/docs/tracker-first-loop-state.md +6 -6
- package/.claude/skills/local-implementation/SKILL.md +25 -5
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +69 -0
- package/README.md +8 -2
- package/agents/developer.agent.md +1 -0
- package/agents/fixer.agent.md +1 -0
- package/agents/review.agent.md +30 -0
- package/cli/index.mjs +60 -8
- package/extension/README.md +1 -1
- package/package.json +3 -3
- package/scripts/README.md +8 -7
- package/scripts/_core-helpers.mjs +1 -0
- package/scripts/claude/headless-dev-loop.mjs +53 -13
- package/scripts/claude/headless-info-smoke.mjs +45 -11
- package/scripts/github/build-adjacent-bundle.mjs +448 -0
- package/scripts/github/{create-draft-pr.mjs → create-pr.mjs} +28 -12
- package/scripts/github/detect-checkpoint-evidence.mjs +95 -4
- package/scripts/github/offer-human-handoff.mjs +147 -0
- package/scripts/github/post-gate-findings.mjs +392 -0
- package/scripts/github/probe-ci-status.mjs +468 -0
- package/scripts/github/reconcile-draft-gate.mjs +2 -2
- package/scripts/github/request-copilot-review.mjs +72 -11
- package/scripts/github/resolve-handoff-candidates.mjs +412 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +599 -17
- package/scripts/github/verify-fresh-review-context.mjs +12 -1
- package/scripts/github/write-gate-context.mjs +634 -0
- package/scripts/github/write-gate-findings-log.mjs +1 -1
- package/scripts/loop/_stale-runner-detection.mjs +1 -1
- package/scripts/loop/_worktree-path.mjs +27 -0
- package/scripts/loop/cleanup-worktree.mjs +175 -0
- package/scripts/loop/copilot-pr-handoff.mjs +1 -1
- package/scripts/loop/detect-change-scope.mjs +36 -11
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +30 -18
- package/scripts/loop/detect-stale-runner.mjs +3 -4
- package/scripts/loop/detect-tracker-first-loop-state.mjs +38 -11
- package/scripts/loop/ensure-worktree.mjs +219 -0
- package/scripts/loop/outer-loop.mjs +1 -1
- package/scripts/loop/pr-runner-coordination.mjs +1 -1
- package/scripts/loop/pre-flight-gate.mjs +10 -7
- package/scripts/loop/pre-push-main-guard.mjs +4 -4
- package/scripts/loop/provision-worktree.mjs +243 -0
- package/scripts/loop/resolve-dev-loop-startup.mjs +5 -5
- package/scripts/loop/run-queue.mjs +112 -16
- package/scripts/loop/run-watch-cycle.mjs +75 -22
- package/scripts/projects/add-queue-item.mjs +80 -48
- package/scripts/projects/archive-done-items.mjs +136 -39
- package/scripts/projects/ensure-queue-board.mjs +67 -65
- package/scripts/projects/list-queue-items.mjs +59 -57
- package/scripts/projects/move-queue-item.mjs +125 -125
- package/scripts/projects/reorder-queue-item.mjs +67 -48
- package/scripts/projects/sync-item-status.mjs +199 -0
- package/skills/copilot-pr-followup/SKILL.md +62 -6
- package/skills/dev-loop/SKILL.md +2 -2
- package/skills/dev-loop/scripts/log-bash-exit-1.mjs +2 -2
- package/skills/dev-loop/scripts/phase-files.mjs +2 -2
- package/skills/docs/anti-patterns.md +2 -0
- package/skills/docs/copilot-loop-operations.md +3 -3
- package/skills/docs/issue-intake-procedure.md +4 -0
- package/skills/docs/merge-preconditions.md +65 -1
- package/skills/docs/stop-conditions.md +1 -0
- package/skills/docs/tracker-first-loop-state.md +6 -6
- package/skills/local-implementation/SKILL.md +25 -5
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
3
3
|
import { runChild as _runChild } from "../_cli-primitives.mjs";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
4
5
|
|
|
5
|
-
const USAGE = `Usage: dev-loops
|
|
6
|
+
const USAGE = `Usage: dev-loops queue move --repo <owner/name> --project <number|id> --item <number|node-id> --to-column <name>
|
|
7
|
+
(dev-loops project move … is a back-compat alias)
|
|
6
8
|
|
|
7
9
|
Move a GitHub Projects V2 item between Status columns.
|
|
8
10
|
|
|
@@ -23,47 +25,63 @@ Exit codes:
|
|
|
23
25
|
3 — project, field, column, or item not found
|
|
24
26
|
`.trim();
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
function parseCliArgs(argv) {
|
|
29
|
+
const parseError = (message) => Object.assign(new Error(message), { usage: USAGE });
|
|
30
|
+
const requireValue = (token, message) => {
|
|
31
|
+
const v = token.value;
|
|
32
|
+
if (typeof v !== "string" || v.length === 0 || v.startsWith("-")) {
|
|
33
|
+
throw parseError(message);
|
|
34
|
+
}
|
|
35
|
+
return v;
|
|
36
|
+
};
|
|
27
37
|
|
|
28
|
-
function parseArgs(argv) {
|
|
29
38
|
const args = {};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
const { tokens } = parseArgs({
|
|
40
|
+
args: [...argv],
|
|
41
|
+
options: {
|
|
42
|
+
repo: { type: "string" },
|
|
43
|
+
project: { type: "string" },
|
|
44
|
+
item: { type: "string" },
|
|
45
|
+
"to-column": { type: "string" },
|
|
46
|
+
help: { type: "boolean", short: "h" },
|
|
47
|
+
},
|
|
48
|
+
allowPositionals: true,
|
|
49
|
+
strict: false,
|
|
50
|
+
tokens: true,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
for (const token of tokens) {
|
|
54
|
+
if (token.kind === "positional") {
|
|
55
|
+
throw parseError(`Unexpected argument: ${token.value}`);
|
|
37
56
|
}
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
if (token.kind !== "option") {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
switch (token.name) {
|
|
61
|
+
case "help":
|
|
62
|
+
if (token.value !== undefined) {
|
|
63
|
+
throw parseError(`Unknown flag: ${token.rawName}=${token.value}`);
|
|
64
|
+
}
|
|
65
|
+
args.help = true;
|
|
66
|
+
break;
|
|
67
|
+
case "repo":
|
|
68
|
+
args.repo = requireValue(token, "--repo requires a value (owner/name)");
|
|
69
|
+
break;
|
|
70
|
+
case "project":
|
|
71
|
+
args.project = requireValue(token, "--project requires a value (number or node ID)");
|
|
72
|
+
break;
|
|
73
|
+
case "item":
|
|
74
|
+
args.item = requireValue(token, "--item requires a value (number or node ID)");
|
|
75
|
+
break;
|
|
76
|
+
case "to-column":
|
|
77
|
+
args.toColumn = requireValue(token, "--to-column requires a value");
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
throw parseError(`Unknown flag: ${token.rawName}`);
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
return args;
|
|
65
84
|
}
|
|
66
|
-
|
|
67
85
|
// ── Validation ───────────────────────────────────────────────────────────
|
|
68
86
|
|
|
69
87
|
const OWNER_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
|
@@ -210,7 +228,7 @@ const GET_PROJECT_ITEMS_BY_CONTENT = [
|
|
|
210
228
|
"query($projectId:ID!, $after:String) {",
|
|
211
229
|
" node(id:$projectId) {",
|
|
212
230
|
" ... on ProjectV2 {",
|
|
213
|
-
" items(first:
|
|
231
|
+
" items(first:100, after:$after, orderBy:{field:POSITION, direction:ASC}) {",
|
|
214
232
|
" pageInfo { hasNextPage endCursor }",
|
|
215
233
|
" nodes {",
|
|
216
234
|
" id",
|
|
@@ -223,8 +241,8 @@ const GET_PROJECT_ITEMS_BY_CONTENT = [
|
|
|
223
241
|
" }",
|
|
224
242
|
" }",
|
|
225
243
|
" content {",
|
|
226
|
-
" ... on Issue { number repository { nameWithOwner } }",
|
|
227
|
-
" ... on PullRequest { number repository { nameWithOwner } }",
|
|
244
|
+
" ... on Issue { __typename number repository { nameWithOwner } }",
|
|
245
|
+
" ... on PullRequest { __typename number repository { nameWithOwner } }",
|
|
228
246
|
" }",
|
|
229
247
|
" }",
|
|
230
248
|
" }",
|
|
@@ -233,30 +251,6 @@ const GET_PROJECT_ITEMS_BY_CONTENT = [
|
|
|
233
251
|
"}"
|
|
234
252
|
].join("\n");
|
|
235
253
|
|
|
236
|
-
const GET_PROJECT_ITEM = [
|
|
237
|
-
"query($projectId:ID!, $itemId:ID!) {",
|
|
238
|
-
" node(id:$projectId) {",
|
|
239
|
-
" ... on ProjectV2 {",
|
|
240
|
-
" item: item(id:$itemId) {",
|
|
241
|
-
" id",
|
|
242
|
-
" fieldValues(first:20) {",
|
|
243
|
-
" nodes {",
|
|
244
|
-
" ... on ProjectV2ItemFieldSingleSelectValue {",
|
|
245
|
-
" field { ... on ProjectV2SingleSelectField { id name } }",
|
|
246
|
-
" name",
|
|
247
|
-
" }",
|
|
248
|
-
" }",
|
|
249
|
-
" }",
|
|
250
|
-
" content {",
|
|
251
|
-
" ... on Issue { number title url }",
|
|
252
|
-
" ... on PullRequest { number title url }",
|
|
253
|
-
" }",
|
|
254
|
-
" }",
|
|
255
|
-
" }",
|
|
256
|
-
" }",
|
|
257
|
-
"}"
|
|
258
|
-
].join("\n");
|
|
259
|
-
|
|
260
254
|
const UPDATE_ITEM_FIELD = [
|
|
261
255
|
"mutation($projectId:ID!, $itemId:ID!, $fieldId:ID!, $optionId:String!) {",
|
|
262
256
|
" updateProjectV2ItemFieldValue(input:{projectId:$projectId, itemId:$itemId, fieldId:$fieldId, value:{singleSelectOptionId:$optionId}}) {",
|
|
@@ -337,6 +331,39 @@ async function listAllFields(projectId, env, runChild) {
|
|
|
337
331
|
return fields;
|
|
338
332
|
}
|
|
339
333
|
|
|
334
|
+
// ── Paginated item listing (position order) ──────────────────────────────
|
|
335
|
+
|
|
336
|
+
async function fetchAllItems(projectId, env, runChild) {
|
|
337
|
+
const items = [];
|
|
338
|
+
let after = null;
|
|
339
|
+
while (true) {
|
|
340
|
+
const vars = { projectId };
|
|
341
|
+
if (after) vars.after = after;
|
|
342
|
+
const payload = await ghGraphql(GET_PROJECT_ITEMS_BY_CONTENT, vars, env, runChild);
|
|
343
|
+
const connection = payload?.data?.node?.items;
|
|
344
|
+
const nodes = connection?.nodes ?? [];
|
|
345
|
+
items.push(...nodes);
|
|
346
|
+
const pageInfo = connection?.pageInfo ?? {};
|
|
347
|
+
if (!pageInfo.hasNextPage) break;
|
|
348
|
+
if (!pageInfo.endCursor) {
|
|
349
|
+
throw Object.assign(
|
|
350
|
+
new Error("Invalid items payload: hasNextPage is true but endCursor is missing"),
|
|
351
|
+
{ code: "GH_API_ERROR" },
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
after = pageInfo.endCursor;
|
|
355
|
+
}
|
|
356
|
+
return items;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function statusOf(node) {
|
|
360
|
+
const fvs = node?.fieldValues?.nodes ?? [];
|
|
361
|
+
for (const fv of fvs) {
|
|
362
|
+
if (fv && fv.field && fv.field.name === "Status") return fv.name;
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
340
367
|
// ── Exit code classification ────────────────────────────────────────────
|
|
341
368
|
|
|
342
369
|
function classifyExitCode(err) {
|
|
@@ -397,80 +424,53 @@ async function main(args, { env = process.env, runChild } = {}) {
|
|
|
397
424
|
);
|
|
398
425
|
}
|
|
399
426
|
|
|
400
|
-
// 4. Find the item
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
427
|
+
// 4. Find the item.
|
|
428
|
+
//
|
|
429
|
+
// Fetch the full board item list ONCE (paginated, position order) and resolve
|
|
430
|
+
// BOTH ref kinds against it. This reuses the proven pattern from
|
|
431
|
+
// reorder-queue-item / list-queue-items: a node-id ref matches by item.id, a
|
|
432
|
+
// number ref matches by content.number. Both are scoped to the requested repo
|
|
433
|
+
// so a cross-project ref fails closed with ITEM_NOT_FOUND. (The previous code
|
|
434
|
+
// used `ProjectV2.item` — a field that does not exist — for the node-id path,
|
|
435
|
+
// and a single non-paginated `items(first:10)` page for the number path, so it
|
|
436
|
+
// could not find items beyond the first page.)
|
|
437
|
+
const allItems = await fetchAllItems(project.id, env, child);
|
|
438
|
+
|
|
439
|
+
let match;
|
|
406
440
|
if (itemRef.kind === "id") {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}, env, child);
|
|
412
|
-
const item = itemPayload?.data?.node?.item;
|
|
413
|
-
if (!item) {
|
|
441
|
+
match = allItems.find(
|
|
442
|
+
(it) => it.id === itemRef.value && it.content?.repository?.nameWithOwner === repo,
|
|
443
|
+
);
|
|
444
|
+
if (!match) {
|
|
414
445
|
throw Object.assign(
|
|
415
|
-
new Error(`Item "${itemRef.value}" not found in project "${project.title}"`),
|
|
446
|
+
new Error(`Item "${itemRef.value}" not found in project "${project.title}" for repo "${repo}"`),
|
|
416
447
|
{ code: "ITEM_NOT_FOUND" },
|
|
417
448
|
);
|
|
418
449
|
}
|
|
419
|
-
itemId = item.id;
|
|
420
|
-
const fvs = item.fieldValues?.nodes ?? [];
|
|
421
|
-
for (const fv of fvs) {
|
|
422
|
-
if (fv && fv.field && fv.field.name === "Status") {
|
|
423
|
-
previousColumn = fv.name;
|
|
424
|
-
break;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
if (item.content) {
|
|
428
|
-
if (item.content.__typename === "Issue") {
|
|
429
|
-
issueNumber = item.content.number;
|
|
430
|
-
} else {
|
|
431
|
-
prNumber = item.content.number;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
450
|
} else {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const matchingItems = items.filter((it) => {
|
|
443
|
-
if (!it.content) return false;
|
|
444
|
-
const repoMatch = it.content.repository?.nameWithOwner === repo;
|
|
445
|
-
if (itemRef.kind === "number") {
|
|
446
|
-
return repoMatch && it.content.number === itemRef.value;
|
|
447
|
-
}
|
|
448
|
-
return repoMatch;
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
if (matchingItems.length === 0) {
|
|
451
|
+
match = allItems.find(
|
|
452
|
+
(it) =>
|
|
453
|
+
it.content &&
|
|
454
|
+
it.content.repository?.nameWithOwner === repo &&
|
|
455
|
+
it.content.number === itemRef.value,
|
|
456
|
+
);
|
|
457
|
+
if (!match) {
|
|
452
458
|
throw Object.assign(
|
|
453
459
|
new Error(`Item #${itemRef.value} not found in project "${project.title}" for repo "${repo}"`),
|
|
454
460
|
{ code: "ITEM_NOT_FOUND" },
|
|
455
461
|
);
|
|
456
462
|
}
|
|
463
|
+
}
|
|
457
464
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
if (match.content) {
|
|
469
|
-
if (match.content.__typename === "Issue") {
|
|
470
|
-
issueNumber = match.content.number;
|
|
471
|
-
} else {
|
|
472
|
-
prNumber = match.content.number;
|
|
473
|
-
}
|
|
465
|
+
const itemId = match.id;
|
|
466
|
+
const previousColumn = statusOf(match);
|
|
467
|
+
let issueNumber = null;
|
|
468
|
+
let prNumber = null;
|
|
469
|
+
if (match.content) {
|
|
470
|
+
if (match.content.__typename === "PullRequest") {
|
|
471
|
+
prNumber = match.content.number;
|
|
472
|
+
} else {
|
|
473
|
+
issueNumber = match.content.number;
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
476
|
|
|
@@ -520,7 +520,7 @@ async function main(args, { env = process.env, runChild } = {}) {
|
|
|
520
520
|
async function runCli(argv, { stdout = process.stdout, stderr = process.stderr, env = process.env } = {}) {
|
|
521
521
|
let args;
|
|
522
522
|
try {
|
|
523
|
-
args =
|
|
523
|
+
args = parseCliArgs(argv);
|
|
524
524
|
} catch (err) {
|
|
525
525
|
stderr.write(`${formatCliError(err)}\n`);
|
|
526
526
|
process.exitCode = 1;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
3
3
|
import { runChild as _runChild } from "../_cli-primitives.mjs";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
4
5
|
|
|
5
6
|
const USAGE = `Usage:
|
|
6
|
-
dev-loops
|
|
7
|
-
dev-loops
|
|
8
|
-
dev-loops
|
|
9
|
-
dev-loops
|
|
7
|
+
dev-loops queue reorder --repo <owner/name> --project <number|id> --item <number|node-id> [--after <number|node-id>]
|
|
8
|
+
dev-loops queue reorder move-to-top <ref> --repo <owner/name> --project <number|id>
|
|
9
|
+
dev-loops queue reorder move-after <ref> <after-ref> --repo <owner/name> --project <number|id>
|
|
10
|
+
dev-loops queue reorder order <ref1> <ref2> ... --repo <owner/name> --project <number|id>
|
|
11
|
+
(dev-loops project reorder … is a back-compat alias)
|
|
10
12
|
|
|
11
13
|
Reorder GitHub Projects V2 items by board position via updateProjectV2ItemPosition.
|
|
12
14
|
|
|
@@ -40,62 +42,79 @@ Exit codes:
|
|
|
40
42
|
`.trim();
|
|
41
43
|
|
|
42
44
|
const SUBCOMMANDS = new Set(["move-to-top", "move-after", "order"]);
|
|
43
|
-
const VALID_ARGS = new Set(["--repo", "--project", "--item", "--after", "--dry-run", "--help", "-h"]);
|
|
44
45
|
|
|
45
|
-
function
|
|
46
|
+
function parseCliArgs(argv) {
|
|
47
|
+
const parseError = (message) => Object.assign(new Error(message), { usage: USAGE });
|
|
48
|
+
const requireValue = (token, message) => {
|
|
49
|
+
const v = token.value;
|
|
50
|
+
if (typeof v !== "string" || v.length === 0 || v.startsWith("-")) {
|
|
51
|
+
throw parseError(message);
|
|
52
|
+
}
|
|
53
|
+
return v;
|
|
54
|
+
};
|
|
55
|
+
|
|
46
56
|
const args = { _positional: [] };
|
|
47
|
-
let
|
|
57
|
+
let rest = argv;
|
|
48
58
|
|
|
49
|
-
// Optional leading positional subcommand.
|
|
50
59
|
if (argv.length > 0 && SUBCOMMANDS.has(argv[0])) {
|
|
51
60
|
args._subcommand = argv[0];
|
|
52
|
-
|
|
53
|
-
}
|
|
61
|
+
rest = argv.slice(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { tokens } = parseArgs({
|
|
65
|
+
args: [...rest],
|
|
66
|
+
options: {
|
|
67
|
+
repo: { type: "string" },
|
|
68
|
+
project: { type: "string" },
|
|
69
|
+
item: { type: "string" },
|
|
70
|
+
after: { type: "string" },
|
|
71
|
+
"dry-run": { type: "boolean" },
|
|
72
|
+
help: { type: "boolean", short: "h" },
|
|
73
|
+
},
|
|
74
|
+
allowPositionals: true,
|
|
75
|
+
strict: false,
|
|
76
|
+
tokens: true,
|
|
77
|
+
});
|
|
54
78
|
|
|
55
|
-
for (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Positional ref (only meaningful with a subcommand).
|
|
59
|
-
args._positional.push(arg);
|
|
79
|
+
for (const token of tokens) {
|
|
80
|
+
if (token.kind === "positional") {
|
|
81
|
+
args._positional.push(token.value);
|
|
60
82
|
continue;
|
|
61
83
|
}
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
new Error(`Unknown flag: ${arg}`),
|
|
65
|
-
{ code: "INVALID_ARGS", usage: USAGE },
|
|
66
|
-
);
|
|
84
|
+
if (token.kind !== "option") {
|
|
85
|
+
continue;
|
|
67
86
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
switch (token.name) {
|
|
88
|
+
case "help":
|
|
89
|
+
if (token.value !== undefined) {
|
|
90
|
+
throw parseError(`Unknown flag: ${token.rawName}=${token.value}`);
|
|
91
|
+
}
|
|
92
|
+
args.help = true;
|
|
93
|
+
break;
|
|
94
|
+
case "repo":
|
|
95
|
+
args.repo = requireValue(token, "--repo requires a value (owner/name)");
|
|
96
|
+
break;
|
|
97
|
+
case "project":
|
|
98
|
+
args.project = requireValue(token, "--project requires a value (number or node ID)");
|
|
99
|
+
break;
|
|
100
|
+
case "item":
|
|
101
|
+
args.item = requireValue(token, "--item requires a value (number or node ID)");
|
|
102
|
+
break;
|
|
103
|
+
case "after":
|
|
104
|
+
args.after = requireValue(token, "--after requires a value (number or node ID)");
|
|
105
|
+
break;
|
|
106
|
+
case "dry-run":
|
|
107
|
+
if (token.value !== undefined) {
|
|
108
|
+
throw parseError(`Unknown flag: ${token.rawName}=${token.value}`);
|
|
109
|
+
}
|
|
110
|
+
args.dryRun = true;
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
throw parseError(`Unknown flag: ${token.rawName}`);
|
|
94
114
|
}
|
|
95
115
|
}
|
|
96
116
|
return args;
|
|
97
117
|
}
|
|
98
|
-
|
|
99
118
|
// ── Validation ───────────────────────────────────────────────────────────
|
|
100
119
|
|
|
101
120
|
const OWNER_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
|
@@ -747,7 +766,7 @@ async function main(args, { env = process.env, runChild } = {}) {
|
|
|
747
766
|
async function runCli(argv, { stdout = process.stdout, stderr = process.stderr, env = process.env } = {}) {
|
|
748
767
|
let args;
|
|
749
768
|
try {
|
|
750
|
-
args =
|
|
769
|
+
args = parseCliArgs(argv);
|
|
751
770
|
} catch (err) {
|
|
752
771
|
stderr.write(`${formatCliError(err)}\n`);
|
|
753
772
|
process.exitCode = 1;
|