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
|
@@ -3,8 +3,9 @@ import { statSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
5
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
6
|
-
import { parsePrNumber,
|
|
6
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
7
7
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
8
|
+
import { parseArgs } from "node:util";
|
|
8
9
|
|
|
9
10
|
const USAGE = `Usage: detect-internal-only-pr.mjs --repo <owner/name> --pr <number> [--config <path>]
|
|
10
11
|
Detect whether a PR only touches internal tooling files (scripts, docs, tests, config)
|
|
@@ -118,7 +119,6 @@ function buildPatternMatchers(patterns) {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
export function parseCliArgs(argv) {
|
|
121
|
-
const args = [...argv];
|
|
122
122
|
const options = {
|
|
123
123
|
help: false,
|
|
124
124
|
repo: undefined,
|
|
@@ -126,29 +126,47 @@ export function parseCliArgs(argv) {
|
|
|
126
126
|
config: undefined,
|
|
127
127
|
labelCheck: false,
|
|
128
128
|
};
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
const { tokens } = parseArgs({
|
|
130
|
+
args: [...argv],
|
|
131
|
+
options: {
|
|
132
|
+
help: { type: "boolean", short: "h" },
|
|
133
|
+
repo: { type: "string" },
|
|
134
|
+
pr: { type: "string" },
|
|
135
|
+
config: { type: "string" },
|
|
136
|
+
"label-check": { type: "boolean" },
|
|
137
|
+
},
|
|
138
|
+
allowPositionals: true,
|
|
139
|
+
strict: false,
|
|
140
|
+
tokens: true,
|
|
141
|
+
});
|
|
142
|
+
for (const token of tokens) {
|
|
143
|
+
if (token.kind === "positional") {
|
|
144
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
145
|
+
}
|
|
146
|
+
if (token.kind !== "option") {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (token.name === "help") {
|
|
132
150
|
options.help = true;
|
|
133
151
|
return options;
|
|
134
152
|
}
|
|
135
|
-
if (token === "
|
|
136
|
-
options.repo =
|
|
153
|
+
if (token.name === "repo") {
|
|
154
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
137
155
|
continue;
|
|
138
156
|
}
|
|
139
|
-
if (token === "
|
|
140
|
-
options.pr = parsePrNumber(
|
|
157
|
+
if (token.name === "pr") {
|
|
158
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
141
159
|
continue;
|
|
142
160
|
}
|
|
143
|
-
if (token === "
|
|
144
|
-
options.config =
|
|
161
|
+
if (token.name === "config") {
|
|
162
|
+
options.config = requireTokenValue(token, parseError).trim();
|
|
145
163
|
continue;
|
|
146
164
|
}
|
|
147
|
-
if (token === "
|
|
165
|
+
if (token.name === "label-check") {
|
|
148
166
|
options.labelCheck = true;
|
|
149
167
|
continue;
|
|
150
168
|
}
|
|
151
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
169
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
152
170
|
}
|
|
153
171
|
if (options.repo === undefined || options.pr === undefined) {
|
|
154
172
|
throw parseError("detect-internal-only-pr requires both --repo <owner/name> and --pr <number>");
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
5
5
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
6
7
|
import {
|
|
7
8
|
detectIssueRefinementArtifact,
|
|
8
9
|
REFINEMENT_SOURCE,
|
|
@@ -33,36 +34,52 @@ Error output (stderr, JSON):
|
|
|
33
34
|
{ "ok": false, "error": "...", "usage": "..." }`.trim();
|
|
34
35
|
const parseError = buildParseError(USAGE);
|
|
35
36
|
export function parseDetectIssueRefinementArtifactCliArgs(argv) {
|
|
36
|
-
const args = [...argv];
|
|
37
37
|
const options = {
|
|
38
38
|
help: false,
|
|
39
39
|
repo: undefined,
|
|
40
40
|
issue: undefined,
|
|
41
41
|
input: undefined,
|
|
42
42
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const { tokens } = parseArgs({
|
|
44
|
+
args: [...argv],
|
|
45
|
+
options: {
|
|
46
|
+
help: { type: "boolean", short: "h" },
|
|
47
|
+
repo: { type: "string" },
|
|
48
|
+
issue: { type: "string" },
|
|
49
|
+
input: { type: "string" },
|
|
50
|
+
},
|
|
51
|
+
allowPositionals: true,
|
|
52
|
+
strict: false,
|
|
53
|
+
tokens: true,
|
|
54
|
+
});
|
|
55
|
+
for (const token of tokens) {
|
|
56
|
+
if (token.kind === "positional") {
|
|
57
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
58
|
+
}
|
|
59
|
+
if (token.kind !== "option") {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (token.name === "help") {
|
|
46
63
|
options.help = true;
|
|
47
64
|
return options;
|
|
48
65
|
}
|
|
49
|
-
if (token === "
|
|
50
|
-
options.repo =
|
|
66
|
+
if (token.name === "repo") {
|
|
67
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
51
68
|
continue;
|
|
52
69
|
}
|
|
53
|
-
if (token === "
|
|
54
|
-
const value =
|
|
70
|
+
if (token.name === "issue") {
|
|
71
|
+
const value = requireTokenValue(token, parseError);
|
|
55
72
|
if (!/^\d+$/.test(value) || Number(value) === 0) {
|
|
56
73
|
throw parseError("--issue must be a positive integer");
|
|
57
74
|
}
|
|
58
75
|
options.issue = Number(value);
|
|
59
76
|
continue;
|
|
60
77
|
}
|
|
61
|
-
if (token === "
|
|
62
|
-
options.input =
|
|
78
|
+
if (token.name === "input") {
|
|
79
|
+
options.input = requireTokenValue(token, parseError).trim();
|
|
63
80
|
continue;
|
|
64
81
|
}
|
|
65
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
82
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
66
83
|
}
|
|
67
84
|
const hasInput = typeof options.input === "string" && options.input.length > 0;
|
|
68
85
|
const hasRemote = typeof options.repo === "string" && options.repo.length > 0 && Number.isInteger(options.issue);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
parseReviewThreads,
|
|
12
12
|
summarizeCopilotReviews,
|
|
13
13
|
} from "../_core-helpers.mjs";
|
|
14
|
-
import { parsePrNumber,
|
|
14
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
15
15
|
import { loadDevLoopConfig, resolveGateConfig, resolveRefinementConfig, resolveWorkflowConfig } from "@dev-loops/core/config";
|
|
16
16
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
17
17
|
import { buildSnapshotFromPrFacts, interpretLoopState, summarizeLoopInterpretation } from "@dev-loops/core/loop/copilot-loop-state";
|
|
@@ -19,6 +19,7 @@ import { evaluatePrGateCoordination, PR_CHECKPOINT, PR_CHECKPOINT_ACTION } from
|
|
|
19
19
|
import { shouldGuardCopilotReviewRequest } from "@dev-loops/core/loop/pr-gate-coordination";
|
|
20
20
|
import { fetchGithubReviewThreadsPayload } from "../github/capture-review-threads.mjs";
|
|
21
21
|
import { detectCheckpointEvidence } from "../github/detect-checkpoint-evidence.mjs";
|
|
22
|
+
import { parseArgs } from "node:util";
|
|
22
23
|
const UNMERGED_GIT_STATUS_CODES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
23
24
|
const USAGE = `Usage: detect-pr-gate-coordination-state.mjs --repo <owner/name> --pr <number>
|
|
24
25
|
Determine which PR gate/transition is legal next for a pull request.
|
|
@@ -70,27 +71,42 @@ Exit codes:
|
|
|
70
71
|
1 Argument error or gh/runtime failure`.trim();
|
|
71
72
|
const parseError = buildParseError(USAGE);
|
|
72
73
|
export function parseDetectPrGateCoordinationCliArgs(argv) {
|
|
73
|
-
const args = [...argv];
|
|
74
74
|
const options = {
|
|
75
75
|
help: false,
|
|
76
76
|
repo: undefined,
|
|
77
77
|
pr: undefined,
|
|
78
78
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const { tokens } = parseArgs({
|
|
80
|
+
args: [...argv],
|
|
81
|
+
options: {
|
|
82
|
+
help: { type: "boolean", short: "h" },
|
|
83
|
+
repo: { type: "string" },
|
|
84
|
+
pr: { type: "string" },
|
|
85
|
+
},
|
|
86
|
+
allowPositionals: true,
|
|
87
|
+
strict: false,
|
|
88
|
+
tokens: true,
|
|
89
|
+
});
|
|
90
|
+
for (const token of tokens) {
|
|
91
|
+
if (token.kind === "positional") {
|
|
92
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
93
|
+
}
|
|
94
|
+
if (token.kind !== "option") {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (token.name === "help") {
|
|
82
98
|
options.help = true;
|
|
83
99
|
return options;
|
|
84
100
|
}
|
|
85
|
-
if (token === "
|
|
86
|
-
options.repo =
|
|
101
|
+
if (token.name === "repo") {
|
|
102
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
87
103
|
continue;
|
|
88
104
|
}
|
|
89
|
-
if (token === "
|
|
90
|
-
options.pr = parsePrNumber(
|
|
105
|
+
if (token.name === "pr") {
|
|
106
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
91
107
|
continue;
|
|
92
108
|
}
|
|
93
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
109
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
94
110
|
}
|
|
95
111
|
if (options.repo === undefined || options.pr === undefined) {
|
|
96
112
|
throw parseError("detect-pr-gate-coordination-state requires both --repo <owner/name> and --pr <number>");
|
|
@@ -147,7 +163,7 @@ async function fetchRequestedReviewers({ repo, pr }, { env = process.env, ghComm
|
|
|
147
163
|
async function fetchPrFacts({ repo, pr }, { env = process.env, ghCommand = "gh" } = {}) {
|
|
148
164
|
const result = await runChild(
|
|
149
165
|
ghCommand,
|
|
150
|
-
["pr", "view", String(pr), "--repo", repo, "--json", "number,state,isDraft,headRefOid,mergeStateStatus,body,closingIssuesReferences,reviews,statusCheckRollup"],
|
|
166
|
+
["pr", "view", String(pr), "--repo", repo, "--json", "number,state,isDraft,headRefOid,mergeStateStatus,body,title,closingIssuesReferences,reviews,statusCheckRollup"],
|
|
151
167
|
env,
|
|
152
168
|
);
|
|
153
169
|
if (result.code !== 0) {
|
|
@@ -381,6 +397,7 @@ export async function detectPrGateCoordinationState(options, runtime = {}) {
|
|
|
381
397
|
prDraft: Boolean(context.prData?.isDraft),
|
|
382
398
|
prClosed: String(context.prData?.state || "").toUpperCase() === "CLOSED",
|
|
383
399
|
prMerged: String(context.prData?.state || "").toUpperCase() === "MERGED",
|
|
400
|
+
prTitle: context.prData?.title,
|
|
384
401
|
mergeStateStatus: context.mergeStateStatus,
|
|
385
402
|
conflictFiles: context.conflictFiles,
|
|
386
403
|
lifecycleState: context.interpretation.state,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
5
|
import { formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
5
6
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
6
7
|
import {
|
|
@@ -26,7 +27,20 @@ function parseBool(value, flag) {
|
|
|
26
27
|
throw new Error(`${flag} must be true or false`);
|
|
27
28
|
}
|
|
28
29
|
export function parseDetectReviewerCliArgs(argv) {
|
|
29
|
-
const
|
|
30
|
+
const { tokens } = parseArgs({
|
|
31
|
+
args: [...argv],
|
|
32
|
+
options: {
|
|
33
|
+
help: { type: "boolean", short: "h" },
|
|
34
|
+
input: { type: "string" },
|
|
35
|
+
repo: { type: "string" },
|
|
36
|
+
pr: { type: "string" },
|
|
37
|
+
"review-requested": { type: "string" },
|
|
38
|
+
"local-state": { type: "string" },
|
|
39
|
+
},
|
|
40
|
+
allowPositionals: true,
|
|
41
|
+
strict: false,
|
|
42
|
+
tokens: true,
|
|
43
|
+
});
|
|
30
44
|
const options = {
|
|
31
45
|
inputPath: undefined,
|
|
32
46
|
repo: undefined,
|
|
@@ -35,36 +49,41 @@ export function parseDetectReviewerCliArgs(argv) {
|
|
|
35
49
|
localStatePath: undefined,
|
|
36
50
|
help: false,
|
|
37
51
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
for (const token of tokens) {
|
|
53
|
+
if (token.kind === "positional") {
|
|
54
|
+
throw new Error(`Unknown argument: ${token.value}`);
|
|
55
|
+
}
|
|
56
|
+
if (token.kind !== "option") {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (token.name === "help") {
|
|
41
60
|
options.help = true;
|
|
42
61
|
return options;
|
|
43
62
|
}
|
|
44
|
-
if (token === "
|
|
45
|
-
options.inputPath =
|
|
63
|
+
if (token.name === "input") {
|
|
64
|
+
options.inputPath = requireTokenValue(token);
|
|
46
65
|
continue;
|
|
47
66
|
}
|
|
48
|
-
if (token === "
|
|
49
|
-
options.repo =
|
|
67
|
+
if (token.name === "repo") {
|
|
68
|
+
options.repo = requireTokenValue(token).trim();
|
|
50
69
|
continue;
|
|
51
70
|
}
|
|
52
|
-
if (token === "
|
|
53
|
-
options.pr = parsePrNumber(
|
|
71
|
+
if (token.name === "pr") {
|
|
72
|
+
options.pr = parsePrNumber(requireTokenValue(token));
|
|
54
73
|
continue;
|
|
55
74
|
}
|
|
56
|
-
if (token === "
|
|
75
|
+
if (token.name === "review-requested") {
|
|
57
76
|
options.reviewRequestedOverride = parseBool(
|
|
58
|
-
|
|
77
|
+
requireTokenValue(token),
|
|
59
78
|
"--review-requested",
|
|
60
79
|
);
|
|
61
80
|
continue;
|
|
62
81
|
}
|
|
63
|
-
if (token === "
|
|
64
|
-
options.localStatePath =
|
|
82
|
+
if (token.name === "local-state") {
|
|
83
|
+
options.localStatePath = requireTokenValue(token);
|
|
65
84
|
continue;
|
|
66
85
|
}
|
|
67
|
-
throw new Error(`Unknown argument: ${token}`);
|
|
86
|
+
throw new Error(`Unknown argument: ${token.rawName}`);
|
|
68
87
|
}
|
|
69
88
|
if (options.inputPath !== undefined) {
|
|
70
89
|
if (options.repo !== undefined || options.pr !== undefined) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
4
|
-
import { parsePrNumber,
|
|
4
|
+
import { parsePrNumber, requireTokenValue } from "../_cli-primitives.mjs";
|
|
5
5
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
6
6
|
import { resolveRunId as resolveEnvRunId } from "@dev-loops/core/loop/run-context";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
7
8
|
import {
|
|
8
9
|
detectStaleRunner,
|
|
9
10
|
STALE_RUNNER_ERROR,
|
|
@@ -50,7 +51,6 @@ Exit codes:
|
|
|
50
51
|
1 Argument error or stale/exit-signal condition detected`.trim();
|
|
51
52
|
const parseError = buildParseError(USAGE);
|
|
52
53
|
function parseCliArgs(argv) {
|
|
53
|
-
const args = [...argv];
|
|
54
54
|
const options = {
|
|
55
55
|
help: false,
|
|
56
56
|
repo: undefined,
|
|
@@ -58,22 +58,40 @@ function parseCliArgs(argv) {
|
|
|
58
58
|
staleRunnerMaxAgeMs: undefined,
|
|
59
59
|
runId: undefined,
|
|
60
60
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
const { tokens } = parseArgs({
|
|
62
|
+
args: [...argv],
|
|
63
|
+
options: {
|
|
64
|
+
help: { type: "boolean", short: "h" },
|
|
65
|
+
repo: { type: "string" },
|
|
66
|
+
pr: { type: "string" },
|
|
67
|
+
"stale-runner-max-age-ms": { type: "string" },
|
|
68
|
+
"run-id": { type: "string" },
|
|
69
|
+
},
|
|
70
|
+
allowPositionals: true,
|
|
71
|
+
strict: false,
|
|
72
|
+
tokens: true,
|
|
73
|
+
});
|
|
74
|
+
for (const token of tokens) {
|
|
75
|
+
if (token.kind === "positional") {
|
|
76
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
77
|
+
}
|
|
78
|
+
if (token.kind !== "option") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (token.name === "help") {
|
|
64
82
|
options.help = true;
|
|
65
83
|
return options;
|
|
66
84
|
}
|
|
67
|
-
if (token === "
|
|
68
|
-
options.repo =
|
|
85
|
+
if (token.name === "repo") {
|
|
86
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
69
87
|
continue;
|
|
70
88
|
}
|
|
71
|
-
if (token === "
|
|
72
|
-
options.pr = parsePrNumber(
|
|
89
|
+
if (token.name === "pr") {
|
|
90
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
73
91
|
continue;
|
|
74
92
|
}
|
|
75
|
-
if (token === "
|
|
76
|
-
const raw =
|
|
93
|
+
if (token.name === "stale-runner-max-age-ms") {
|
|
94
|
+
const raw = requireTokenValue(token, parseError).trim();
|
|
77
95
|
const parsed = Number(raw);
|
|
78
96
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
79
97
|
throw parseError(`--stale-runner-max-age-ms must be a positive integer (ms), got: ${raw}`);
|
|
@@ -81,11 +99,11 @@ function parseCliArgs(argv) {
|
|
|
81
99
|
options.staleRunnerMaxAgeMs = Math.floor(parsed);
|
|
82
100
|
continue;
|
|
83
101
|
}
|
|
84
|
-
if (token === "
|
|
85
|
-
options.runId =
|
|
102
|
+
if (token.name === "run-id") {
|
|
103
|
+
options.runId = requireTokenValue(token, parseError).trim();
|
|
86
104
|
continue;
|
|
87
105
|
}
|
|
88
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
106
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
89
107
|
}
|
|
90
108
|
if (options.repo === undefined || options.pr === undefined) {
|
|
91
109
|
throw parseError("detect-stale-runner requires both --repo <owner/name> and --pr <number>");
|
|
@@ -3,7 +3,8 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { buildParseError, formatCliError, parseJsonText } from "../_core-helpers.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
7
8
|
import {
|
|
8
9
|
interpretTrackerPrState,
|
|
9
10
|
normalizeTrackerPrSnapshot,
|
|
@@ -51,22 +52,36 @@ Exit codes:
|
|
|
51
52
|
1 Argument error or runtime failure`.trim();
|
|
52
53
|
const parseError = buildParseError(USAGE);
|
|
53
54
|
export function parseDetectTrackerPrCliArgs(argv) {
|
|
54
|
-
const args = [...argv];
|
|
55
55
|
const options = {
|
|
56
56
|
help: false,
|
|
57
57
|
inputPath: undefined,
|
|
58
58
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const { tokens } = parseArgs({
|
|
60
|
+
args: [...argv],
|
|
61
|
+
options: {
|
|
62
|
+
help: { type: "boolean", short: "h" },
|
|
63
|
+
input: { type: "string" },
|
|
64
|
+
},
|
|
65
|
+
allowPositionals: true,
|
|
66
|
+
strict: false,
|
|
67
|
+
tokens: true,
|
|
68
|
+
});
|
|
69
|
+
for (const token of tokens) {
|
|
70
|
+
if (token.kind === "positional") {
|
|
71
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
72
|
+
}
|
|
73
|
+
if (token.kind !== "option") {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (token.name === "help") {
|
|
62
77
|
options.help = true;
|
|
63
78
|
return options;
|
|
64
79
|
}
|
|
65
|
-
if (token === "
|
|
66
|
-
options.inputPath =
|
|
80
|
+
if (token.name === "input") {
|
|
81
|
+
options.inputPath = requireTokenValue(token, parseError);
|
|
67
82
|
continue;
|
|
68
83
|
}
|
|
69
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
84
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
70
85
|
}
|
|
71
86
|
if (options.inputPath === undefined) {
|
|
72
87
|
throw parseError("--input <path> is required");
|
package/scripts/loop/info.mjs
CHANGED
|
@@ -3,9 +3,10 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { requireTokenValue, parsePositiveInteger } from "../_cli-primitives.mjs";
|
|
7
7
|
import { detectRepoSlug, normalizeRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
8
8
|
import { runContextEnv } from "@dev-loops/core/loop/run-context";
|
|
9
|
+
import { parseArgs } from "node:util";
|
|
9
10
|
|
|
10
11
|
// REPO_ROOT resolves to the git repo root (scripts/loop/info.mjs → scripts/ → repo/)
|
|
11
12
|
const REPO_ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url)), "..");
|
|
@@ -27,16 +28,33 @@ Exit codes:
|
|
|
27
28
|
const parseError = buildParseError(USAGE);
|
|
28
29
|
|
|
29
30
|
function parseCliArgs(argv) {
|
|
30
|
-
const args = [...argv];
|
|
31
31
|
const opts = { help: false, issue: undefined, pr: undefined, json: false, repo: undefined };
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const { tokens } = parseArgs({
|
|
33
|
+
args: [...argv],
|
|
34
|
+
options: {
|
|
35
|
+
help: { type: "boolean", short: "h" },
|
|
36
|
+
json: { type: "boolean" },
|
|
37
|
+
issue: { type: "string" },
|
|
38
|
+
pr: { type: "string" },
|
|
39
|
+
repo: { type: "string" },
|
|
40
|
+
},
|
|
41
|
+
allowPositionals: true,
|
|
42
|
+
strict: false,
|
|
43
|
+
tokens: true,
|
|
44
|
+
});
|
|
45
|
+
for (const token of tokens) {
|
|
46
|
+
if (token.kind === "positional") {
|
|
47
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
48
|
+
}
|
|
49
|
+
if (token.kind !== "option") {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (token.name === "help") { opts.help = true; return opts; }
|
|
53
|
+
if (token.name === "json") { opts.json = true; continue; }
|
|
54
|
+
if (token.name === "issue") { opts.issue = parsePositiveInteger(requireTokenValue(token, parseError), "--issue", parseError); continue; }
|
|
55
|
+
if (token.name === "pr") { opts.pr = parsePositiveInteger(requireTokenValue(token, parseError), "--pr", parseError); continue; }
|
|
56
|
+
if (token.name === "repo") { opts.repo = requireTokenValue(token, parseError); continue; }
|
|
57
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
40
58
|
}
|
|
41
59
|
const modes = [opts.issue, opts.pr].filter(v => v !== undefined).length;
|
|
42
60
|
if (modes > 1) throw parseError("--issue and --pr are mutually exclusive");
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
DEFAULT_PORT,
|
|
4
4
|
USAGE,
|
|
5
5
|
} from "./constants.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import { requireTokenValue } from "../../_cli-primitives.mjs";
|
|
7
8
|
import { normalizeInspectionTarget } from "../_inspect-run-viewer-adapter.mjs";
|
|
8
9
|
|
|
9
10
|
export function parseInspectRunViewerCliError(message) {
|
|
@@ -47,7 +48,6 @@ export function normalizeCliRepoOption(rawRepo) {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export function parseInspectRunViewerCliArgs(argv) {
|
|
50
|
-
const args = [...argv];
|
|
51
51
|
const options = {
|
|
52
52
|
help: false,
|
|
53
53
|
repo: undefined,
|
|
@@ -60,48 +60,71 @@ export function parseInspectRunViewerCliArgs(argv) {
|
|
|
60
60
|
restart: false,
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
const { tokens } = parseArgs({
|
|
64
|
+
args: [...argv],
|
|
65
|
+
options: {
|
|
66
|
+
help: { type: "boolean", short: "h" },
|
|
67
|
+
repo: { type: "string" },
|
|
68
|
+
pr: { type: "boolean" },
|
|
69
|
+
host: { type: "string" },
|
|
70
|
+
port: { type: "string" },
|
|
71
|
+
"allow-non-localhost": { type: "boolean" },
|
|
72
|
+
restart: { type: "boolean" },
|
|
73
|
+
"steering-state-file": { type: "string" },
|
|
74
|
+
"copilot-input": { type: "string" },
|
|
75
|
+
"reviewer-input": { 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 parseInspectRunViewerCliError(`Unknown argument: ${token.value}`);
|
|
84
|
+
}
|
|
85
|
+
if (token.kind !== "option") {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (token.name === "help") {
|
|
66
89
|
options.help = true;
|
|
67
90
|
return options;
|
|
68
91
|
}
|
|
69
|
-
if (token === "
|
|
70
|
-
options.repo =
|
|
92
|
+
if (token.name === "repo") {
|
|
93
|
+
options.repo = requireTokenValue(token, parseInspectRunViewerCliError);
|
|
71
94
|
continue;
|
|
72
95
|
}
|
|
73
|
-
if (token === "
|
|
96
|
+
if (token.name === "pr") {
|
|
74
97
|
throw parseInspectRunViewerCliError("--pr is no longer supported on the CLI; choose a PR with ?pr=<number> in the viewer URL");
|
|
75
98
|
}
|
|
76
|
-
if (token === "
|
|
77
|
-
options.host = parseHost(
|
|
99
|
+
if (token.name === "host") {
|
|
100
|
+
options.host = parseHost(requireTokenValue(token, parseInspectRunViewerCliError));
|
|
78
101
|
continue;
|
|
79
102
|
}
|
|
80
|
-
if (token === "
|
|
81
|
-
options.port = parsePort(
|
|
103
|
+
if (token.name === "port") {
|
|
104
|
+
options.port = parsePort(requireTokenValue(token, parseInspectRunViewerCliError));
|
|
82
105
|
continue;
|
|
83
106
|
}
|
|
84
|
-
if (token === "
|
|
107
|
+
if (token.name === "allow-non-localhost") {
|
|
85
108
|
options.allowNonLocalhost = true;
|
|
86
109
|
continue;
|
|
87
110
|
}
|
|
88
|
-
if (token === "
|
|
111
|
+
if (token.name === "restart") {
|
|
89
112
|
options.restart = true;
|
|
90
113
|
continue;
|
|
91
114
|
}
|
|
92
|
-
if (token === "
|
|
93
|
-
options.steeringStateFile =
|
|
115
|
+
if (token.name === "steering-state-file") {
|
|
116
|
+
options.steeringStateFile = requireTokenValue(token, parseInspectRunViewerCliError);
|
|
94
117
|
continue;
|
|
95
118
|
}
|
|
96
|
-
if (token === "
|
|
97
|
-
options.copilotInputPath =
|
|
119
|
+
if (token.name === "copilot-input") {
|
|
120
|
+
options.copilotInputPath = requireTokenValue(token, parseInspectRunViewerCliError);
|
|
98
121
|
continue;
|
|
99
122
|
}
|
|
100
|
-
if (token === "
|
|
101
|
-
options.reviewerInputPath =
|
|
123
|
+
if (token.name === "reviewer-input") {
|
|
124
|
+
options.reviewerInputPath = requireTokenValue(token, parseInspectRunViewerCliError);
|
|
102
125
|
continue;
|
|
103
126
|
}
|
|
104
|
-
throw parseInspectRunViewerCliError(`Unknown argument: ${token}`);
|
|
127
|
+
throw parseInspectRunViewerCliError(`Unknown argument: ${token.rawName}`);
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
if (!options.help) {
|