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,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
2
3
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
3
|
-
import { parsePrNumber,
|
|
4
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
5
|
import { loadDevLoopConfig, resolveGateConfig } from "@dev-loops/core/config";
|
|
5
6
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
6
7
|
import { detectCheckpointEvidence } from "./detect-checkpoint-evidence.mjs";
|
|
@@ -36,27 +37,38 @@ Exit codes:
|
|
|
36
37
|
1 Argument error, gh failure, or unrecoverable state`.trim();
|
|
37
38
|
const parseError = buildParseError(USAGE);
|
|
38
39
|
export function parseReconcileDraftGateCliArgs(argv) {
|
|
39
|
-
const
|
|
40
|
+
const { tokens } = parseArgs({
|
|
41
|
+
args: [...argv],
|
|
42
|
+
options: { help: { type: "boolean", short: "h" }, repo: { type: "string" }, pr: { type: "string" } },
|
|
43
|
+
allowPositionals: true,
|
|
44
|
+
strict: false,
|
|
45
|
+
tokens: true,
|
|
46
|
+
});
|
|
40
47
|
const options = {
|
|
41
48
|
help: false,
|
|
42
49
|
repo: undefined,
|
|
43
50
|
pr: undefined,
|
|
44
51
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
for (const token of tokens) {
|
|
53
|
+
if (token.kind === "positional") {
|
|
54
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
55
|
+
}
|
|
56
|
+
if (token.kind !== "option") {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (token.name === "help") {
|
|
48
60
|
options.help = true;
|
|
49
61
|
return options;
|
|
50
62
|
}
|
|
51
|
-
if (token === "
|
|
52
|
-
options.repo =
|
|
63
|
+
if (token.name === "repo") {
|
|
64
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
53
65
|
continue;
|
|
54
66
|
}
|
|
55
|
-
if (token === "
|
|
56
|
-
options.pr = parsePrNumber(
|
|
67
|
+
if (token.name === "pr") {
|
|
68
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
57
69
|
continue;
|
|
58
70
|
}
|
|
59
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
71
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
60
72
|
}
|
|
61
73
|
const missing = ["repo", "pr"].filter((key) => options[key] === undefined);
|
|
62
74
|
if (missing.length > 0) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
2
3
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
3
4
|
import {
|
|
4
5
|
parsePrNumber,
|
|
5
|
-
|
|
6
|
+
requireTokenValue,
|
|
6
7
|
} from "../_cli-primitives.mjs";
|
|
7
8
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
8
9
|
import {
|
|
@@ -34,7 +35,20 @@ Exit codes:
|
|
|
34
35
|
1 Argument error or gh/runtime failure`.trim();
|
|
35
36
|
const parseError = buildParseError(USAGE);
|
|
36
37
|
export function parseReplyResolveThreadsCliArgs(argv) {
|
|
37
|
-
const
|
|
38
|
+
const { tokens } = parseArgs({
|
|
39
|
+
args: [...argv],
|
|
40
|
+
options: {
|
|
41
|
+
help: { type: "boolean", short: "h" },
|
|
42
|
+
repo: { type: "string" },
|
|
43
|
+
pr: { type: "string" },
|
|
44
|
+
author: { type: "string" },
|
|
45
|
+
message: { type: "string" },
|
|
46
|
+
resolve: { type: "boolean" },
|
|
47
|
+
},
|
|
48
|
+
allowPositionals: true,
|
|
49
|
+
strict: false,
|
|
50
|
+
tokens: true,
|
|
51
|
+
});
|
|
38
52
|
const options = {
|
|
39
53
|
help: false,
|
|
40
54
|
repo: undefined,
|
|
@@ -43,33 +57,38 @@ export function parseReplyResolveThreadsCliArgs(argv) {
|
|
|
43
57
|
message: undefined,
|
|
44
58
|
resolve: false,
|
|
45
59
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
for (const token of tokens) {
|
|
61
|
+
if (token.kind === "positional") {
|
|
62
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
63
|
+
}
|
|
64
|
+
if (token.kind !== "option") {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (token.name === "help") {
|
|
49
68
|
options.help = true;
|
|
50
69
|
return options;
|
|
51
70
|
}
|
|
52
|
-
if (token === "
|
|
53
|
-
options.repo =
|
|
71
|
+
if (token.name === "repo") {
|
|
72
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
54
73
|
continue;
|
|
55
74
|
}
|
|
56
|
-
if (token === "
|
|
57
|
-
options.pr = parsePrNumber(
|
|
75
|
+
if (token.name === "pr") {
|
|
76
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
58
77
|
continue;
|
|
59
78
|
}
|
|
60
|
-
if (token === "
|
|
61
|
-
options.author =
|
|
79
|
+
if (token.name === "author") {
|
|
80
|
+
options.author = requireTokenValue(token, parseError).trim();
|
|
62
81
|
continue;
|
|
63
82
|
}
|
|
64
|
-
if (token === "
|
|
65
|
-
options.message =
|
|
83
|
+
if (token.name === "message") {
|
|
84
|
+
options.message = requireTokenValue(token, parseError);
|
|
66
85
|
continue;
|
|
67
86
|
}
|
|
68
|
-
if (token === "
|
|
87
|
+
if (token.name === "resolve") {
|
|
69
88
|
options.resolve = true;
|
|
70
89
|
continue;
|
|
71
90
|
}
|
|
72
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
91
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
73
92
|
}
|
|
74
93
|
if (options.repo === undefined || options.pr === undefined) {
|
|
75
94
|
throw parseError("Replying and resolving review threads requires both --repo <owner/name> and --pr <number>");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
2
3
|
import {
|
|
3
4
|
buildParseError,
|
|
4
5
|
formatCliError,
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
parseReviewThreads,
|
|
8
9
|
summarizeCopilotReviews,
|
|
9
10
|
} from "../_core-helpers.mjs";
|
|
10
|
-
import { parsePrNumber,
|
|
11
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
11
12
|
import { fetchGithubReviewThreadsPayload } from "./capture-review-threads.mjs";
|
|
12
13
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
13
14
|
import { buildSnapshotFromPrFacts, interpretLoopState } from "@dev-loops/core/loop/copilot-loop-state";
|
|
@@ -52,32 +53,48 @@ Exit codes:
|
|
|
52
53
|
1 Argument error or gh failure`.trim();
|
|
53
54
|
const parseError = buildParseError(USAGE);
|
|
54
55
|
export function parseRequestCliArgs(argv) {
|
|
55
|
-
const
|
|
56
|
+
const { tokens } = parseArgs({
|
|
57
|
+
args: [...argv],
|
|
58
|
+
options: {
|
|
59
|
+
help: { type: "boolean", short: "h" },
|
|
60
|
+
"force-rerequest-review": { type: "boolean" },
|
|
61
|
+
repo: { type: "string" },
|
|
62
|
+
pr: { type: "string" },
|
|
63
|
+
},
|
|
64
|
+
allowPositionals: true,
|
|
65
|
+
strict: false,
|
|
66
|
+
tokens: true,
|
|
67
|
+
});
|
|
56
68
|
const options = {
|
|
57
69
|
help: false,
|
|
58
70
|
repo: undefined,
|
|
59
71
|
pr: undefined,
|
|
60
72
|
forceRerequestReview: false,
|
|
61
73
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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") {
|
|
65
82
|
options.help = true;
|
|
66
83
|
return options;
|
|
67
84
|
}
|
|
68
|
-
if (token === "
|
|
85
|
+
if (token.name === "force-rerequest-review") {
|
|
69
86
|
options.forceRerequestReview = true;
|
|
70
87
|
continue;
|
|
71
88
|
}
|
|
72
|
-
if (token === "
|
|
73
|
-
options.repo =
|
|
89
|
+
if (token.name === "repo") {
|
|
90
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
74
91
|
continue;
|
|
75
92
|
}
|
|
76
|
-
if (token === "
|
|
77
|
-
options.pr = parsePrNumber(
|
|
93
|
+
if (token.name === "pr") {
|
|
94
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
78
95
|
continue;
|
|
79
96
|
}
|
|
80
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
97
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
81
98
|
}
|
|
82
99
|
if (options.repo === undefined || options.pr === undefined) {
|
|
83
100
|
throw parseError("Requesting Copilot review requires both --repo <owner/name> and --pr <number>");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
2
3
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
3
|
-
import { parseIssueNumber,
|
|
4
|
+
import { parseIssueNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
5
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
5
6
|
const ISSUE_JSON_FIELDS = "number,title,body,url,state";
|
|
6
7
|
const USAGE = `Usage: resolve-tracker-local-spec.mjs (--repo <owner/name> --issue <number> | --issue-url <github-issue-url>)
|
|
@@ -60,32 +61,48 @@ export function parseGitHubIssueUrl(value) {
|
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
63
|
export function parseResolveTrackerLocalSpecCliArgs(argv) {
|
|
63
|
-
const
|
|
64
|
+
const { tokens } = parseArgs({
|
|
65
|
+
args: [...argv],
|
|
66
|
+
options: {
|
|
67
|
+
help: { type: "boolean", short: "h" },
|
|
68
|
+
repo: { type: "string" },
|
|
69
|
+
issue: { type: "string" },
|
|
70
|
+
"issue-url": { type: "string" },
|
|
71
|
+
},
|
|
72
|
+
allowPositionals: true,
|
|
73
|
+
strict: false,
|
|
74
|
+
tokens: true,
|
|
75
|
+
});
|
|
64
76
|
const options = {
|
|
65
77
|
help: false,
|
|
66
78
|
repo: undefined,
|
|
67
79
|
issue: undefined,
|
|
68
80
|
issueUrl: undefined,
|
|
69
81
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
for (const token of tokens) {
|
|
83
|
+
if (token.kind === "positional") {
|
|
84
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
85
|
+
}
|
|
86
|
+
if (token.kind !== "option") {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (token.name === "help") {
|
|
73
90
|
options.help = true;
|
|
74
91
|
return options;
|
|
75
92
|
}
|
|
76
|
-
if (token === "
|
|
77
|
-
options.repo =
|
|
93
|
+
if (token.name === "repo") {
|
|
94
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
78
95
|
continue;
|
|
79
96
|
}
|
|
80
|
-
if (token === "
|
|
81
|
-
options.issue = parseIssueNumber(
|
|
97
|
+
if (token.name === "issue") {
|
|
98
|
+
options.issue = parseIssueNumber(requireTokenValue(token, parseError), parseError);
|
|
82
99
|
continue;
|
|
83
100
|
}
|
|
84
|
-
if (token === "
|
|
85
|
-
options.issueUrl =
|
|
101
|
+
if (token.name === "issue-url") {
|
|
102
|
+
options.issueUrl = requireTokenValue(token, parseError).trim();
|
|
86
103
|
continue;
|
|
87
104
|
}
|
|
88
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
105
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
89
106
|
}
|
|
90
107
|
const usingIssueUrl = typeof options.issueUrl === "string";
|
|
91
108
|
const usingRepoIssue = options.repo !== undefined || options.issue !== undefined;
|
|
@@ -3,7 +3,8 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import { parsePositiveInteger, requireTokenValue } from "../_cli-primitives.mjs";
|
|
7
8
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
8
9
|
import { buildDraftReviewPayload } from "@dev-loops/core/loop/reviewer-loop-state";
|
|
9
10
|
const HELP = `Usage: stage-reviewer-draft.mjs --repo <owner/name> --pr <number> --review-file <path> [--local-state-output <path>]
|
|
@@ -19,7 +20,19 @@ Exit codes:
|
|
|
19
20
|
1 Error
|
|
20
21
|
`;
|
|
21
22
|
export function parseStageDraftCliArgs(argv) {
|
|
22
|
-
const
|
|
23
|
+
const { tokens } = parseArgs({
|
|
24
|
+
args: [...argv],
|
|
25
|
+
options: {
|
|
26
|
+
help: { type: "boolean", short: "h" },
|
|
27
|
+
repo: { type: "string" },
|
|
28
|
+
pr: { type: "string" },
|
|
29
|
+
"review-file": { type: "string" },
|
|
30
|
+
"local-state-output": { type: "string" },
|
|
31
|
+
},
|
|
32
|
+
allowPositionals: true,
|
|
33
|
+
strict: false,
|
|
34
|
+
tokens: true,
|
|
35
|
+
});
|
|
23
36
|
const options = {
|
|
24
37
|
repo: undefined,
|
|
25
38
|
pr: undefined,
|
|
@@ -27,29 +40,34 @@ export function parseStageDraftCliArgs(argv) {
|
|
|
27
40
|
localStateOutput: undefined,
|
|
28
41
|
help: false,
|
|
29
42
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
for (const token of tokens) {
|
|
44
|
+
if (token.kind === "positional") {
|
|
45
|
+
throw new Error(`Unknown argument: ${token.value}`);
|
|
46
|
+
}
|
|
47
|
+
if (token.kind !== "option") {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (token.name === "help") {
|
|
33
51
|
options.help = true;
|
|
34
52
|
return options;
|
|
35
53
|
}
|
|
36
|
-
if (token === "
|
|
37
|
-
options.repo =
|
|
54
|
+
if (token.name === "repo") {
|
|
55
|
+
options.repo = requireTokenValue(token).trim();
|
|
38
56
|
continue;
|
|
39
57
|
}
|
|
40
|
-
if (token === "
|
|
41
|
-
options.pr = parsePositiveInteger(
|
|
58
|
+
if (token.name === "pr") {
|
|
59
|
+
options.pr = parsePositiveInteger(requireTokenValue(token), "--pr");
|
|
42
60
|
continue;
|
|
43
61
|
}
|
|
44
|
-
if (token === "
|
|
45
|
-
options.reviewFile =
|
|
62
|
+
if (token.name === "review-file") {
|
|
63
|
+
options.reviewFile = requireTokenValue(token);
|
|
46
64
|
continue;
|
|
47
65
|
}
|
|
48
|
-
if (token === "
|
|
49
|
-
options.localStateOutput =
|
|
66
|
+
if (token.name === "local-state-output") {
|
|
67
|
+
options.localStateOutput = requireTokenValue(token);
|
|
50
68
|
continue;
|
|
51
69
|
}
|
|
52
|
-
throw new Error(`Unknown argument: ${token}`);
|
|
70
|
+
throw new Error(`Unknown argument: ${token.rawName}`);
|
|
53
71
|
}
|
|
54
72
|
if (!options.repo || !options.pr || !options.reviewFile) {
|
|
55
73
|
throw new Error(
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
4
4
|
import { loadDevLoopConfig, resolveGateConfig, resolveRefinementConfig } from "@dev-loops/core/config";
|
|
5
|
-
import {
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
6
7
|
import { truncateText } from "@dev-loops/core/bash-exit-one";
|
|
7
8
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
8
9
|
import { loadPrGateCoordinationContext } from "../loop/detect-pr-gate-coordination-state.mjs";
|
|
@@ -198,7 +199,24 @@ export function summarizeCheckpointVerdictText(value, limit = MAX_GATE_COMMENT_T
|
|
|
198
199
|
return smartTruncate(verboseSummary ?? flat, limit);
|
|
199
200
|
}
|
|
200
201
|
export function parseUpsertCheckpointVerdictCliArgs(argv) {
|
|
201
|
-
const
|
|
202
|
+
const { tokens } = parseArgs({
|
|
203
|
+
args: [...argv],
|
|
204
|
+
options: {
|
|
205
|
+
help: { type: "boolean", short: "h" },
|
|
206
|
+
repo: { type: "string" },
|
|
207
|
+
pr: { type: "string" },
|
|
208
|
+
gate: { type: "string" },
|
|
209
|
+
"head-sha": { type: "string" },
|
|
210
|
+
verdict: { type: "string" },
|
|
211
|
+
"findings-summary": { type: "string" },
|
|
212
|
+
"findings-file": { type: "string" },
|
|
213
|
+
"next-action": { type: "string" },
|
|
214
|
+
"findings-severity-counts": { type: "string" },
|
|
215
|
+
},
|
|
216
|
+
allowPositionals: true,
|
|
217
|
+
strict: false,
|
|
218
|
+
tokens: true,
|
|
219
|
+
});
|
|
202
220
|
const options = {
|
|
203
221
|
help: false,
|
|
204
222
|
repo: undefined,
|
|
@@ -211,65 +229,70 @@ export function parseUpsertCheckpointVerdictCliArgs(argv) {
|
|
|
211
229
|
nextAction: undefined,
|
|
212
230
|
findingsSeverityCounts: undefined,
|
|
213
231
|
};
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
for (const token of tokens) {
|
|
233
|
+
if (token.kind === "positional") {
|
|
234
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
235
|
+
}
|
|
236
|
+
if (token.kind !== "option") {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (token.name === "help") {
|
|
217
240
|
options.help = true;
|
|
218
241
|
return options;
|
|
219
242
|
}
|
|
220
|
-
if (REMOVED_FLAGS.has(token)) {
|
|
221
|
-
rejectRemovedFlag(token);
|
|
243
|
+
if (REMOVED_FLAGS.has(token.rawName)) {
|
|
244
|
+
rejectRemovedFlag(token.rawName);
|
|
222
245
|
}
|
|
223
|
-
if (token === "
|
|
224
|
-
options.repo =
|
|
246
|
+
if (token.name === "repo") {
|
|
247
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
225
248
|
continue;
|
|
226
249
|
}
|
|
227
|
-
if (token === "
|
|
228
|
-
options.pr = parsePrNumber(
|
|
250
|
+
if (token.name === "pr") {
|
|
251
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
229
252
|
continue;
|
|
230
253
|
}
|
|
231
|
-
if (token === "
|
|
232
|
-
const gate = normalizeGateName(
|
|
254
|
+
if (token.name === "gate") {
|
|
255
|
+
const gate = normalizeGateName(requireTokenValue(token, parseError));
|
|
233
256
|
if (!gate) {
|
|
234
257
|
throw parseError("--gate must be one of: draft_gate, pre_approval_gate");
|
|
235
258
|
}
|
|
236
259
|
options.gate = gate;
|
|
237
260
|
continue;
|
|
238
261
|
}
|
|
239
|
-
if (token === "
|
|
240
|
-
const headSha = normalizeHeadSha(
|
|
262
|
+
if (token.name === "head-sha") {
|
|
263
|
+
const headSha = normalizeHeadSha(requireTokenValue(token, parseError));
|
|
241
264
|
if (!headSha) {
|
|
242
265
|
throw parseError("--head-sha must be a 7-64 character hexadecimal SHA");
|
|
243
266
|
}
|
|
244
267
|
options.headSha = headSha;
|
|
245
268
|
continue;
|
|
246
269
|
}
|
|
247
|
-
if (token === "
|
|
248
|
-
const verdict = normalizeVerdict(
|
|
270
|
+
if (token.name === "verdict") {
|
|
271
|
+
const verdict = normalizeVerdict(requireTokenValue(token, parseError));
|
|
249
272
|
if (!verdict) {
|
|
250
273
|
throw parseError("--verdict must be one of: clean, findings_present, blocked");
|
|
251
274
|
}
|
|
252
275
|
options.verdict = verdict;
|
|
253
276
|
continue;
|
|
254
277
|
}
|
|
255
|
-
if (token === "
|
|
256
|
-
options.findingsSummary = normalizeRequiredText(
|
|
278
|
+
if (token.name === "findings-summary") {
|
|
279
|
+
options.findingsSummary = normalizeRequiredText(requireTokenValue(token, parseError), "--findings-summary");
|
|
257
280
|
continue;
|
|
258
281
|
}
|
|
259
|
-
if (token === "
|
|
260
|
-
const rawPath =
|
|
282
|
+
if (token.name === "findings-file") {
|
|
283
|
+
const rawPath = requireTokenValue(token, parseError).trim();
|
|
261
284
|
if (rawPath.length === 0) {
|
|
262
285
|
throw parseError("--findings-file must be a non-empty path");
|
|
263
286
|
}
|
|
264
287
|
options.findingsFile = rawPath;
|
|
265
288
|
continue;
|
|
266
289
|
}
|
|
267
|
-
if (token === "
|
|
268
|
-
options.nextAction = normalizeRequiredText(
|
|
290
|
+
if (token.name === "next-action") {
|
|
291
|
+
options.nextAction = normalizeRequiredText(requireTokenValue(token, parseError), "--next-action");
|
|
269
292
|
continue;
|
|
270
293
|
}
|
|
271
|
-
if (token === "
|
|
272
|
-
const raw =
|
|
294
|
+
if (token.name === "findings-severity-counts") {
|
|
295
|
+
const raw = requireTokenValue(token, parseError);
|
|
273
296
|
let parsed;
|
|
274
297
|
try {
|
|
275
298
|
parsed = JSON.parse(raw);
|
|
@@ -289,7 +312,7 @@ export function parseUpsertCheckpointVerdictCliArgs(argv) {
|
|
|
289
312
|
options.findingsSeverityCounts = counts;
|
|
290
313
|
continue;
|
|
291
314
|
}
|
|
292
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
315
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
293
316
|
}
|
|
294
317
|
const missing = ["repo", "pr", "headSha", "verdict", "findingsSummary", "nextAction"]
|
|
295
318
|
.filter((key) => options[key] === undefined);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { parsePrNumber, requireTokenValue } from "../_cli-primitives.mjs";
|
|
5
6
|
import { formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
6
7
|
const USAGE = `Usage: write-gate-findings-log.mjs --repo <owner/name> --pr <number> --gate <draft_gate|pre_approval_gate> --head-sha <sha> --verdict <clean|findings_present|blocked> --findings <json> [--tmp-root <path>]
|
|
7
8
|
Write a durable <gate>-<headSha>.json log under deterministic tmp/ paths.
|
|
@@ -89,7 +90,22 @@ function parseFindingsJson(raw) {
|
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
92
|
export function parseWriteGateFindingsLogCliArgs(argv) {
|
|
92
|
-
const
|
|
93
|
+
const { tokens } = parseArgs({
|
|
94
|
+
args: [...argv],
|
|
95
|
+
options: {
|
|
96
|
+
help: { type: "boolean", short: "h" },
|
|
97
|
+
repo: { type: "string" },
|
|
98
|
+
pr: { type: "string" },
|
|
99
|
+
gate: { type: "string" },
|
|
100
|
+
"head-sha": { type: "string" },
|
|
101
|
+
verdict: { type: "string" },
|
|
102
|
+
findings: { type: "string" },
|
|
103
|
+
"tmp-root": { type: "string" },
|
|
104
|
+
},
|
|
105
|
+
allowPositionals: true,
|
|
106
|
+
strict: false,
|
|
107
|
+
tokens: true,
|
|
108
|
+
});
|
|
93
109
|
const options = {
|
|
94
110
|
repo: undefined,
|
|
95
111
|
pr: undefined,
|
|
@@ -99,46 +115,51 @@ export function parseWriteGateFindingsLogCliArgs(argv) {
|
|
|
99
115
|
findings: undefined,
|
|
100
116
|
tmpRoot: "tmp",
|
|
101
117
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
118
|
+
for (const token of tokens) {
|
|
119
|
+
if (token.kind === "positional") {
|
|
120
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
121
|
+
}
|
|
122
|
+
if (token.kind !== "option") {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (token.name === "help") {
|
|
105
126
|
return { help: true };
|
|
106
127
|
}
|
|
107
|
-
if (token === "
|
|
108
|
-
options.repo =
|
|
128
|
+
if (token.name === "repo") {
|
|
129
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
109
130
|
continue;
|
|
110
131
|
}
|
|
111
|
-
if (token === "
|
|
112
|
-
options.pr = parsePrNumber(
|
|
132
|
+
if (token.name === "pr") {
|
|
133
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
113
134
|
continue;
|
|
114
135
|
}
|
|
115
|
-
if (token === "
|
|
116
|
-
const gate = normalizeGate(
|
|
136
|
+
if (token.name === "gate") {
|
|
137
|
+
const gate = normalizeGate(requireTokenValue(token, parseError));
|
|
117
138
|
if (!gate) throw parseError("--gate must be draft_gate or pre_approval_gate");
|
|
118
139
|
options.gate = gate;
|
|
119
140
|
continue;
|
|
120
141
|
}
|
|
121
|
-
if (token === "
|
|
122
|
-
const sha = normalizeHeadSha(
|
|
142
|
+
if (token.name === "head-sha") {
|
|
143
|
+
const sha = normalizeHeadSha(requireTokenValue(token, parseError));
|
|
123
144
|
if (!sha) throw parseError("--head-sha must be a 7-64 character hex SHA");
|
|
124
145
|
options.headSha = sha;
|
|
125
146
|
continue;
|
|
126
147
|
}
|
|
127
|
-
if (token === "
|
|
128
|
-
const verdict = normalizeVerdict(
|
|
148
|
+
if (token.name === "verdict") {
|
|
149
|
+
const verdict = normalizeVerdict(requireTokenValue(token, parseError));
|
|
129
150
|
if (!verdict) throw parseError("--verdict must be clean, findings_present, or blocked");
|
|
130
151
|
options.verdict = verdict;
|
|
131
152
|
continue;
|
|
132
153
|
}
|
|
133
|
-
if (token === "
|
|
134
|
-
options.findings =
|
|
154
|
+
if (token.name === "findings") {
|
|
155
|
+
options.findings = requireTokenValue(token, parseError);
|
|
135
156
|
continue;
|
|
136
157
|
}
|
|
137
|
-
if (token === "
|
|
138
|
-
options.tmpRoot =
|
|
158
|
+
if (token.name === "tmp-root") {
|
|
159
|
+
options.tmpRoot = requireTokenValue(token, parseError).trim();
|
|
139
160
|
continue;
|
|
140
161
|
}
|
|
141
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
162
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
142
163
|
}
|
|
143
164
|
const missing = ["repo", "pr", "gate", "headSha", "verdict", "findings"]
|
|
144
165
|
.filter(k => options[k] === undefined);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Shared loop PR-aggregation helpers used by both conductor-monitor.mjs and
|
|
2
|
+
// run-conductor-cycle.mjs. Spawns `gh`, so this lives at the scripts level
|
|
3
|
+
// rather than in @dev-loops/core.
|
|
4
|
+
import { runChild } from "../_cli-primitives.mjs";
|
|
5
|
+
import { parseJsonText } from "../_core-helpers.mjs";
|
|
6
|
+
|
|
7
|
+
export const OPEN_PR_LIST_LIMIT = 1000;
|
|
8
|
+
|
|
9
|
+
export async function listOpenPrs({ repo }, { env, ghCommand }) {
|
|
10
|
+
const result = await runChild(
|
|
11
|
+
ghCommand,
|
|
12
|
+
[
|
|
13
|
+
"pr",
|
|
14
|
+
"list",
|
|
15
|
+
"--repo",
|
|
16
|
+
repo,
|
|
17
|
+
"--state",
|
|
18
|
+
"open",
|
|
19
|
+
"--limit",
|
|
20
|
+
String(OPEN_PR_LIST_LIMIT),
|
|
21
|
+
"--json",
|
|
22
|
+
"number,title,url,isDraft,headRefName,author",
|
|
23
|
+
],
|
|
24
|
+
env,
|
|
25
|
+
);
|
|
26
|
+
if (result.code !== 0) {
|
|
27
|
+
const detail = result.stderr.trim() || `exit code ${result.code}`;
|
|
28
|
+
throw new Error(`gh command failed: ${detail}`);
|
|
29
|
+
}
|
|
30
|
+
const payload = parseJsonText(result.stdout);
|
|
31
|
+
if (!Array.isArray(payload)) {
|
|
32
|
+
throw new Error("Invalid gh pr list payload: expected an array");
|
|
33
|
+
}
|
|
34
|
+
return payload
|
|
35
|
+
.map((pr) => ({
|
|
36
|
+
number: Number.isInteger(pr?.number) ? pr.number : null,
|
|
37
|
+
title: typeof pr?.title === "string" ? pr.title : "",
|
|
38
|
+
url: typeof pr?.url === "string" ? pr.url : null,
|
|
39
|
+
isDraft: Boolean(pr?.isDraft),
|
|
40
|
+
headRefName: typeof pr?.headRefName === "string" ? pr.headRefName : null,
|
|
41
|
+
authorLogin: typeof pr?.author?.login === "string" ? pr.author.login : null,
|
|
42
|
+
}))
|
|
43
|
+
.filter((pr) => pr.number !== null)
|
|
44
|
+
.sort((left, right) => left.number - right.number);
|
|
45
|
+
}
|