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
|
@@ -18,10 +18,11 @@ import { readFile } from "node:fs/promises";
|
|
|
18
18
|
import { detectRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
21
|
-
import {
|
|
21
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
22
22
|
import { buildDevLoopHandoffEnvelope } from "@dev-loops/core/loop/handoff-envelope";
|
|
23
23
|
import { loadDevLoopConfig } from "@dev-loops/core/config";
|
|
24
24
|
import { createPiAdapter } from "@dev-loops/core/harness";
|
|
25
|
+
import { parseArgs } from "node:util";
|
|
25
26
|
|
|
26
27
|
const USAGE = `Usage: build-handoff-envelope.mjs --input <path>
|
|
27
28
|
Build a deterministic handoff envelope from startup resolver output and settings.
|
|
@@ -56,7 +57,6 @@ function parseFlagJson(raw, flagName, parseErrorFn) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export function parseBuildHandoffEnvelopeCliArgs(argv) {
|
|
59
|
-
const args = [...argv];
|
|
60
60
|
const options = {
|
|
61
61
|
help: false,
|
|
62
62
|
inputPath: undefined,
|
|
@@ -65,29 +65,47 @@ export function parseBuildHandoffEnvelopeCliArgs(argv) {
|
|
|
65
65
|
repo: undefined,
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const { tokens } = parseArgs({
|
|
69
|
+
args: [...argv],
|
|
70
|
+
options: {
|
|
71
|
+
help: { type: "boolean", short: "h" },
|
|
72
|
+
input: { type: "string" },
|
|
73
|
+
"gate-state": { type: "string" },
|
|
74
|
+
overrides: { type: "string" },
|
|
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") {
|
|
71
89
|
options.help = true;
|
|
72
90
|
return options;
|
|
73
91
|
}
|
|
74
|
-
if (token === "
|
|
75
|
-
options.inputPath =
|
|
92
|
+
if (token.name === "input") {
|
|
93
|
+
options.inputPath = requireTokenValue(token, parseError);
|
|
76
94
|
continue;
|
|
77
95
|
}
|
|
78
|
-
if (token === "
|
|
79
|
-
options.gateState =
|
|
96
|
+
if (token.name === "gate-state") {
|
|
97
|
+
options.gateState = requireTokenValue(token, parseError);
|
|
80
98
|
continue;
|
|
81
99
|
}
|
|
82
|
-
if (token === "
|
|
83
|
-
options.overrides =
|
|
100
|
+
if (token.name === "overrides") {
|
|
101
|
+
options.overrides = requireTokenValue(token, parseError);
|
|
84
102
|
continue;
|
|
85
103
|
}
|
|
86
|
-
if (token === "
|
|
87
|
-
options.repo =
|
|
104
|
+
if (token.name === "repo") {
|
|
105
|
+
options.repo = requireTokenValue(token, parseError);
|
|
88
106
|
continue;
|
|
89
107
|
}
|
|
90
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
108
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
91
109
|
}
|
|
92
110
|
|
|
93
111
|
if (!options.inputPath) {
|
|
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { access, open, readFile, readdir } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
7
7
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
8
8
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
9
9
|
import { autoDetectSnapshot } from "./detect-copilot-loop-state.mjs";
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
parseRecordedHandoffContract,
|
|
14
14
|
} from "./_handoff-contract.mjs";
|
|
15
15
|
import { interpretLoopState, summarizeLoopInterpretation } from "@dev-loops/core/loop/copilot-loop-state";
|
|
16
|
+
import { listOpenPrs } from "./_loop-pr-aggregation.mjs";
|
|
17
|
+
import { parseArgs } from "node:util";
|
|
16
18
|
const USAGE = `Usage: conductor-monitor.mjs --repo <owner/name> [--auto-resume]
|
|
17
19
|
Aggregate Copilot-loop status across all open PRs in one repo.
|
|
18
20
|
Required:
|
|
@@ -80,7 +82,6 @@ Exit codes:
|
|
|
80
82
|
0 Success
|
|
81
83
|
1 Argument error, gh failure, or indeterminate PR status`.trim();
|
|
82
84
|
const parseError = buildParseError(USAGE);
|
|
83
|
-
const OPEN_PR_LIST_LIMIT = 1000;
|
|
84
85
|
const DEFAULT_SESSION_ROOT = path.join(os.homedir(), ".pi", "agent", "sessions");
|
|
85
86
|
const RUN_STATE = {
|
|
86
87
|
COMPLETED: "completed",
|
|
@@ -113,27 +114,42 @@ const MANUAL_REASON = {
|
|
|
113
114
|
HANDOFF_CONTRACT_MISMATCH: "handoff_contract_mismatch",
|
|
114
115
|
};
|
|
115
116
|
function parseCliArgs(argv) {
|
|
116
|
-
const args = [...argv];
|
|
117
117
|
const options = {
|
|
118
118
|
help: false,
|
|
119
119
|
repo: undefined,
|
|
120
120
|
autoResume: false,
|
|
121
121
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
const { tokens } = parseArgs({
|
|
123
|
+
args: [...argv],
|
|
124
|
+
options: {
|
|
125
|
+
help: { type: "boolean", short: "h" },
|
|
126
|
+
repo: { type: "string" },
|
|
127
|
+
"auto-resume": { type: "boolean" },
|
|
128
|
+
},
|
|
129
|
+
allowPositionals: true,
|
|
130
|
+
strict: false,
|
|
131
|
+
tokens: true,
|
|
132
|
+
});
|
|
133
|
+
for (const token of tokens) {
|
|
134
|
+
if (token.kind === "positional") {
|
|
135
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
136
|
+
}
|
|
137
|
+
if (token.kind !== "option") {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (token.name === "help") {
|
|
125
141
|
options.help = true;
|
|
126
142
|
return options;
|
|
127
143
|
}
|
|
128
|
-
if (token === "
|
|
129
|
-
options.repo =
|
|
144
|
+
if (token.name === "repo") {
|
|
145
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
130
146
|
continue;
|
|
131
147
|
}
|
|
132
|
-
if (token === "
|
|
148
|
+
if (token.name === "auto-resume") {
|
|
133
149
|
options.autoResume = true;
|
|
134
150
|
continue;
|
|
135
151
|
}
|
|
136
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
152
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
137
153
|
}
|
|
138
154
|
if (options.repo === undefined) {
|
|
139
155
|
throw parseError("conductor-monitor requires --repo <owner/name>");
|
|
@@ -145,43 +161,6 @@ function parseCliArgs(argv) {
|
|
|
145
161
|
}
|
|
146
162
|
return options;
|
|
147
163
|
}
|
|
148
|
-
async function listOpenPrs({ repo }, { env, ghCommand }) {
|
|
149
|
-
const result = await runChild(
|
|
150
|
-
ghCommand,
|
|
151
|
-
[
|
|
152
|
-
"pr",
|
|
153
|
-
"list",
|
|
154
|
-
"--repo",
|
|
155
|
-
repo,
|
|
156
|
-
"--state",
|
|
157
|
-
"open",
|
|
158
|
-
"--limit",
|
|
159
|
-
String(OPEN_PR_LIST_LIMIT),
|
|
160
|
-
"--json",
|
|
161
|
-
"number,title,url,isDraft,headRefName,author",
|
|
162
|
-
],
|
|
163
|
-
env,
|
|
164
|
-
);
|
|
165
|
-
if (result.code !== 0) {
|
|
166
|
-
const detail = result.stderr.trim() || `exit code ${result.code}`;
|
|
167
|
-
throw new Error(`gh command failed: ${detail}`);
|
|
168
|
-
}
|
|
169
|
-
const payload = parseJsonText(result.stdout);
|
|
170
|
-
if (!Array.isArray(payload)) {
|
|
171
|
-
throw new Error("Invalid gh pr list payload: expected an array");
|
|
172
|
-
}
|
|
173
|
-
return payload
|
|
174
|
-
.map((pr) => ({
|
|
175
|
-
number: Number.isInteger(pr?.number) ? pr.number : null,
|
|
176
|
-
title: typeof pr?.title === "string" ? pr.title : "",
|
|
177
|
-
url: typeof pr?.url === "string" ? pr.url : null,
|
|
178
|
-
isDraft: Boolean(pr?.isDraft),
|
|
179
|
-
headRefName: typeof pr?.headRefName === "string" ? pr.headRefName : null,
|
|
180
|
-
authorLogin: typeof pr?.author?.login === "string" ? pr.author.login : null,
|
|
181
|
-
}))
|
|
182
|
-
.filter((pr) => pr.number !== null)
|
|
183
|
-
.sort((left, right) => left.number - right.number);
|
|
184
|
-
}
|
|
185
164
|
function summarizePrDisposition(loopDisposition) {
|
|
186
165
|
switch (loopDisposition) {
|
|
187
166
|
case "pending":
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runConductorCycle } from "./run-conductor-cycle.mjs";
|
|
3
3
|
import { runConductorMonitor } from "./conductor-monitor.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
5
5
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
6
6
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
7
7
|
import {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@dev-loops/core/config";
|
|
13
13
|
import { readFileSync } from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
|
+
import { parseArgs } from "node:util";
|
|
15
16
|
const USAGE = `Usage: conductor.mjs --repo <owner/name> [--auto-resume] [--cycle-only] [--monitor-only] [--require-retrospective]
|
|
16
17
|
Unified conductor entrypoint for dev-loop lifecycle orchestration.`.trim();
|
|
17
18
|
const parseError = buildParseError(USAGE);
|
|
@@ -43,7 +44,6 @@ function checkRetrospectiveGate(cwd, requireRetrospective) {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
function parseCliArgs(argv) {
|
|
46
|
-
const args = [...argv];
|
|
47
47
|
const options = {
|
|
48
48
|
help: false,
|
|
49
49
|
repo: undefined,
|
|
@@ -52,33 +52,52 @@ function parseCliArgs(argv) {
|
|
|
52
52
|
monitorOnly: false,
|
|
53
53
|
requireRetrospective: false,
|
|
54
54
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
const { tokens } = parseArgs({
|
|
56
|
+
args: [...argv],
|
|
57
|
+
options: {
|
|
58
|
+
help: { type: "boolean", short: "h" },
|
|
59
|
+
repo: { type: "string" },
|
|
60
|
+
"auto-resume": { type: "boolean" },
|
|
61
|
+
"cycle-only": { type: "boolean" },
|
|
62
|
+
"monitor-only": { type: "boolean" },
|
|
63
|
+
"require-retrospective": { type: "boolean" },
|
|
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") {
|
|
58
77
|
options.help = true;
|
|
59
78
|
return options;
|
|
60
79
|
}
|
|
61
|
-
if (token === "
|
|
62
|
-
options.repo =
|
|
80
|
+
if (token.name === "repo") {
|
|
81
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
63
82
|
continue;
|
|
64
83
|
}
|
|
65
|
-
if (token === "
|
|
84
|
+
if (token.name === "auto-resume") {
|
|
66
85
|
options.autoResume = true;
|
|
67
86
|
continue;
|
|
68
87
|
}
|
|
69
|
-
if (token === "
|
|
88
|
+
if (token.name === "cycle-only") {
|
|
70
89
|
options.cycleOnly = true;
|
|
71
90
|
continue;
|
|
72
91
|
}
|
|
73
|
-
if (token === "
|
|
92
|
+
if (token.name === "monitor-only") {
|
|
74
93
|
options.monitorOnly = true;
|
|
75
94
|
continue;
|
|
76
95
|
}
|
|
77
|
-
if (token === "
|
|
96
|
+
if (token.name === "require-retrospective") {
|
|
78
97
|
options.requireRetrospective = true;
|
|
79
98
|
continue;
|
|
80
99
|
}
|
|
81
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
100
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
82
101
|
}
|
|
83
102
|
if (options.repo === undefined) {
|
|
84
103
|
throw parseError("conductor requires --repo <owner/name>");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { buildParseError, formatCliError, isCopilotLogin, isDirectCliRun, normalizeTimestamp } from "../_core-helpers.mjs";
|
|
3
|
-
import { parsePrNumber,
|
|
3
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
4
|
import { detectRepoSlug, parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
5
5
|
import { resolveRunId } from "@dev-loops/core/loop/run-context";
|
|
6
6
|
import path from "node:path";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
EXTERNAL_HEALTHY_WAIT_TIMEOUT_POLICY,
|
|
17
17
|
enforceExternalHealthyWaitTimeout,
|
|
18
18
|
} from "@dev-loops/core/loop/timeout-policy";
|
|
19
|
+
import { parseArgs } from "node:util";
|
|
19
20
|
import {
|
|
20
21
|
DEFAULT_POLL_INTERVAL_MS,
|
|
21
22
|
COPILOT_REVIEW_WAIT_TIMEOUT_MS,
|
|
@@ -128,39 +129,55 @@ function rejectRemovedFlag(token) {
|
|
|
128
129
|
);
|
|
129
130
|
}
|
|
130
131
|
export function parseHandoffCliArgs(argv, { cwd = process.cwd() } = {}) {
|
|
131
|
-
const args = [...argv];
|
|
132
132
|
const options = {
|
|
133
133
|
help: false,
|
|
134
134
|
repo: undefined,
|
|
135
135
|
pr: undefined,
|
|
136
136
|
watchStatus: undefined,
|
|
137
137
|
};
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
const { tokens } = parseArgs({
|
|
139
|
+
args: [...argv],
|
|
140
|
+
options: {
|
|
141
|
+
help: { type: "boolean", short: "h" },
|
|
142
|
+
repo: { type: "string" },
|
|
143
|
+
pr: { type: "string" },
|
|
144
|
+
"watch-status": { type: "string" },
|
|
145
|
+
},
|
|
146
|
+
allowPositionals: true,
|
|
147
|
+
strict: false,
|
|
148
|
+
tokens: true,
|
|
149
|
+
});
|
|
150
|
+
for (const token of tokens) {
|
|
151
|
+
if (token.kind === "positional") {
|
|
152
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
153
|
+
}
|
|
154
|
+
if (token.kind !== "option") {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (token.name === "help") {
|
|
141
158
|
options.help = true;
|
|
142
159
|
return options;
|
|
143
160
|
}
|
|
144
|
-
if (REMOVED_FLAGS.has(token)) {
|
|
145
|
-
rejectRemovedFlag(token);
|
|
161
|
+
if (REMOVED_FLAGS.has(token.rawName)) {
|
|
162
|
+
rejectRemovedFlag(token.rawName);
|
|
146
163
|
}
|
|
147
|
-
if (token === "
|
|
148
|
-
options.repo =
|
|
164
|
+
if (token.name === "repo") {
|
|
165
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
149
166
|
continue;
|
|
150
167
|
}
|
|
151
|
-
if (token === "
|
|
152
|
-
options.pr = parsePrNumber(
|
|
168
|
+
if (token.name === "pr") {
|
|
169
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
153
170
|
continue;
|
|
154
171
|
}
|
|
155
|
-
if (token === "
|
|
156
|
-
const watchStatus =
|
|
172
|
+
if (token.name === "watch-status") {
|
|
173
|
+
const watchStatus = requireTokenValue(token, parseError).trim().toLowerCase();
|
|
157
174
|
if (!VALID_WATCH_STATUSES.has(watchStatus)) {
|
|
158
175
|
throw parseError(`--watch-status must be one of: ${[...VALID_WATCH_STATUSES].join(", ")}`);
|
|
159
176
|
}
|
|
160
177
|
options.watchStatus = watchStatus;
|
|
161
178
|
continue;
|
|
162
179
|
}
|
|
163
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
180
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
164
181
|
}
|
|
165
182
|
if (options.pr === undefined) {
|
|
166
183
|
throw parseError("copilot-pr-handoff requires --pr <number>");
|
|
@@ -15,9 +15,10 @@ import { readFile } from "node:fs/promises";
|
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { parseArgs } from "node:util";
|
|
18
19
|
|
|
19
20
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
20
|
-
import {
|
|
21
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
21
22
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
22
23
|
import { DebtSignalSchema } from "@dev-loops/core/debt/signal";
|
|
23
24
|
import { clusterSignalsEnriched } from "@dev-loops/core/debt/cluster";
|
|
@@ -150,28 +151,44 @@ function buildReport(signalsCount, findingsCount, results) {
|
|
|
150
151
|
// ============================================================================
|
|
151
152
|
|
|
152
153
|
export async function runCli(argv) {
|
|
153
|
-
const args = [...argv];
|
|
154
154
|
const options = { input: undefined, repo: undefined, dryRun: false, help: false };
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
const { tokens } = parseArgs({
|
|
157
|
+
args: [...argv],
|
|
158
|
+
options: {
|
|
159
|
+
help: { type: "boolean", short: "h" },
|
|
160
|
+
input: { type: "string" },
|
|
161
|
+
repo: { type: "string" },
|
|
162
|
+
"dry-run": { type: "boolean" },
|
|
163
|
+
},
|
|
164
|
+
allowPositionals: true,
|
|
165
|
+
strict: false,
|
|
166
|
+
tokens: true,
|
|
167
|
+
});
|
|
168
|
+
for (const token of tokens) {
|
|
169
|
+
if (token.kind === "positional") {
|
|
170
|
+
throw parseError(`Unknown flag: ${token.value}`);
|
|
171
|
+
}
|
|
172
|
+
if (token.kind !== "option") {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (token.name === "help") {
|
|
159
176
|
options.help = true;
|
|
160
177
|
break;
|
|
161
178
|
}
|
|
162
|
-
if (token === "
|
|
163
|
-
options.input =
|
|
179
|
+
if (token.name === "input") {
|
|
180
|
+
options.input = requireTokenValue(token, parseError);
|
|
164
181
|
continue;
|
|
165
182
|
}
|
|
166
|
-
if (token === "
|
|
167
|
-
options.repo =
|
|
183
|
+
if (token.name === "repo") {
|
|
184
|
+
options.repo = requireTokenValue(token, parseError);
|
|
168
185
|
continue;
|
|
169
186
|
}
|
|
170
|
-
if (token === "
|
|
187
|
+
if (token.name === "dry-run") {
|
|
171
188
|
options.dryRun = true;
|
|
172
189
|
continue;
|
|
173
190
|
}
|
|
174
|
-
throw parseError(`Unknown flag: ${token}`);
|
|
191
|
+
throw parseError(`Unknown flag: ${token.rawName}`);
|
|
175
192
|
}
|
|
176
193
|
|
|
177
194
|
if (options.help) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
5
6
|
import {
|
|
6
7
|
buildParseError,
|
|
7
8
|
formatCliError,
|
|
@@ -58,32 +59,48 @@ Exit codes:
|
|
|
58
59
|
const VALID_OVERRIDE_STATUSES = new Set(["requested", "already-requested", "unavailable", "none", "failed"]);
|
|
59
60
|
const parseError = buildParseError(USAGE);
|
|
60
61
|
export function parseDetectCliArgs(argv) {
|
|
61
|
-
const args = [...argv];
|
|
62
62
|
const options = {
|
|
63
63
|
help: false,
|
|
64
64
|
inputPath: undefined,
|
|
65
65
|
repo: undefined,
|
|
66
66
|
pr: undefined,
|
|
67
67
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const { tokens } = parseArgs({
|
|
69
|
+
args: [...argv],
|
|
70
|
+
options: {
|
|
71
|
+
help: { type: "boolean", short: "h" },
|
|
72
|
+
input: { type: "string" },
|
|
73
|
+
repo: { type: "string" },
|
|
74
|
+
pr: { type: "string" },
|
|
75
|
+
},
|
|
76
|
+
allowPositionals: true,
|
|
77
|
+
strict: false,
|
|
78
|
+
tokens: true,
|
|
79
|
+
});
|
|
80
|
+
for (const token of tokens) {
|
|
81
|
+
if (token.kind === "positional") {
|
|
82
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
83
|
+
}
|
|
84
|
+
if (token.kind !== "option") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (token.name === "help") {
|
|
71
88
|
options.help = true;
|
|
72
89
|
return options;
|
|
73
90
|
}
|
|
74
|
-
if (token === "
|
|
75
|
-
options.inputPath =
|
|
91
|
+
if (token.name === "input") {
|
|
92
|
+
options.inputPath = requireTokenValue(token, parseError);
|
|
76
93
|
continue;
|
|
77
94
|
}
|
|
78
|
-
if (token === "
|
|
79
|
-
options.repo =
|
|
95
|
+
if (token.name === "repo") {
|
|
96
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
80
97
|
continue;
|
|
81
98
|
}
|
|
82
|
-
if (token === "
|
|
83
|
-
options.pr = parsePrNumber(
|
|
99
|
+
if (token.name === "pr") {
|
|
100
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
84
101
|
continue;
|
|
85
102
|
}
|
|
86
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
103
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
87
104
|
}
|
|
88
105
|
if (options.inputPath !== undefined) {
|
|
89
106
|
if (options.repo !== undefined || options.pr !== undefined) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
3
|
-
import { parsePositiveInteger,
|
|
3
|
+
import { parsePositiveInteger, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
4
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
5
6
|
const USAGE = `Usage: detect-copilot-session-activity.mjs --repo <owner/name> --branch <name> [--limit <number>]
|
|
6
7
|
Detect Copilot GitHub Actions session activity on a branch.
|
|
7
8
|
Required:
|
|
@@ -41,32 +42,48 @@ const COPILOT_RUN_NAME_PATTERNS = Object.freeze([
|
|
|
41
42
|
]);
|
|
42
43
|
const parseError = buildParseError(USAGE);
|
|
43
44
|
export function parseDetectCopilotSessionActivityCliArgs(argv) {
|
|
44
|
-
const args = [...argv];
|
|
45
45
|
const options = {
|
|
46
46
|
help: false,
|
|
47
47
|
repo: undefined,
|
|
48
48
|
branch: undefined,
|
|
49
49
|
limit: DEFAULT_LIMIT,
|
|
50
50
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const { tokens } = parseArgs({
|
|
52
|
+
args: [...argv],
|
|
53
|
+
options: {
|
|
54
|
+
help: { type: "boolean", short: "h" },
|
|
55
|
+
repo: { type: "string" },
|
|
56
|
+
branch: { type: "string" },
|
|
57
|
+
limit: { type: "string" },
|
|
58
|
+
},
|
|
59
|
+
allowPositionals: true,
|
|
60
|
+
strict: false,
|
|
61
|
+
tokens: true,
|
|
62
|
+
});
|
|
63
|
+
for (const token of tokens) {
|
|
64
|
+
if (token.kind === "positional") {
|
|
65
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
66
|
+
}
|
|
67
|
+
if (token.kind !== "option") {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (token.name === "help") {
|
|
54
71
|
options.help = true;
|
|
55
72
|
return options;
|
|
56
73
|
}
|
|
57
|
-
if (token === "
|
|
58
|
-
options.repo =
|
|
74
|
+
if (token.name === "repo") {
|
|
75
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
59
76
|
continue;
|
|
60
77
|
}
|
|
61
|
-
if (token === "
|
|
62
|
-
options.branch =
|
|
78
|
+
if (token.name === "branch") {
|
|
79
|
+
options.branch = requireTokenValue(token, parseError).trim();
|
|
63
80
|
continue;
|
|
64
81
|
}
|
|
65
|
-
if (token === "
|
|
66
|
-
options.limit = parsePositiveInteger(
|
|
82
|
+
if (token.name === "limit") {
|
|
83
|
+
options.limit = parsePositiveInteger(requireTokenValue(token, parseError), "--limit", parseError);
|
|
67
84
|
continue;
|
|
68
85
|
}
|
|
69
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
86
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
70
87
|
}
|
|
71
88
|
if (options.repo === undefined || options.branch === undefined || options.branch.length === 0) {
|
|
72
89
|
throw parseError("detect-copilot-session-activity requires both --repo <owner/name> and --branch <name>");
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
3
|
-
import { parseIssueNumber,
|
|
3
|
+
import { parseIssueNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
4
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
5
5
|
import { detectLinkedIssuePr } from "../github/detect-linked-issue-pr.mjs";
|
|
6
6
|
import { detectCopilotSessionActivity } from "./detect-copilot-session-activity.mjs";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
7
8
|
const USAGE = `Usage: detect-initial-copilot-pr-state.mjs --repo <owner/name> --issue <number>
|
|
8
9
|
Detect whether an assigned issue is still on the bootstrap-only Copilot draft PR
|
|
9
10
|
or has moved into normal linked-PR follow-up.
|
|
@@ -79,27 +80,42 @@ const INITIAL_COPILOT_PR_FACTS_QUERY = [
|
|
|
79
80
|
].join("\n");
|
|
80
81
|
const parseError = buildParseError(USAGE);
|
|
81
82
|
export function parseDetectInitialCopilotPrStateCliArgs(argv) {
|
|
82
|
-
const args = [...argv];
|
|
83
83
|
const options = {
|
|
84
84
|
help: false,
|
|
85
85
|
repo: undefined,
|
|
86
86
|
issue: undefined,
|
|
87
87
|
};
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
const { tokens } = parseArgs({
|
|
89
|
+
args: [...argv],
|
|
90
|
+
options: {
|
|
91
|
+
help: { type: "boolean", short: "h" },
|
|
92
|
+
repo: { type: "string" },
|
|
93
|
+
issue: { type: "string" },
|
|
94
|
+
},
|
|
95
|
+
allowPositionals: true,
|
|
96
|
+
strict: false,
|
|
97
|
+
tokens: true,
|
|
98
|
+
});
|
|
99
|
+
for (const token of tokens) {
|
|
100
|
+
if (token.kind === "positional") {
|
|
101
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
102
|
+
}
|
|
103
|
+
if (token.kind !== "option") {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (token.name === "help") {
|
|
91
107
|
options.help = true;
|
|
92
108
|
return options;
|
|
93
109
|
}
|
|
94
|
-
if (token === "
|
|
95
|
-
options.repo =
|
|
110
|
+
if (token.name === "repo") {
|
|
111
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
96
112
|
continue;
|
|
97
113
|
}
|
|
98
|
-
if (token === "
|
|
99
|
-
options.issue = parseIssueNumber(
|
|
114
|
+
if (token.name === "issue") {
|
|
115
|
+
options.issue = parseIssueNumber(requireTokenValue(token, parseError), parseError);
|
|
100
116
|
continue;
|
|
101
117
|
}
|
|
102
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
118
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
103
119
|
}
|
|
104
120
|
if (options.repo === undefined || options.issue === undefined) {
|
|
105
121
|
throw parseError("detect-initial-copilot-pr-state requires both --repo <owner/name> and --issue <number>");
|