dev-loops 0.2.6 → 0.3.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 +2 -1
- package/.claude/agents/developer.md +1 -0
- package/.claude/agents/fixer.md +1 -0
- package/.claude/agents/review.md +30 -0
- package/.claude/skills/copilot-pr-followup/SKILL.md +57 -3
- package/.claude/skills/dev-loop/SKILL.md +5 -5
- package/.claude/skills/docs/anti-patterns.md +2 -0
- package/.claude/skills/docs/copilot-loop-operations.md +2 -2
- package/.claude/skills/local-implementation/SKILL.md +17 -3
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +60 -0
- package/agents/dev-loop.agent.md +5 -1
- 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 +42 -7
- package/package.json +2 -2
- package/scripts/README.md +6 -5
- package/scripts/_cli-primitives.mjs +2 -0
- package/scripts/_core-helpers.mjs +1 -0
- package/scripts/claude/generate-claude-assets.mjs +12 -2
- package/scripts/claude/headless-dev-loop.mjs +53 -13
- package/scripts/claude/headless-info-smoke.mjs +45 -11
- package/scripts/docs/validate-links.mjs +20 -11
- package/scripts/github/build-adjacent-bundle.mjs +448 -0
- package/scripts/github/capture-review-threads.mjs +32 -14
- package/scripts/github/{create-draft-pr.mjs → create-pr.mjs} +28 -12
- package/scripts/github/detect-checkpoint-evidence.mjs +123 -15
- package/scripts/github/detect-linked-issue-pr.mjs +22 -10
- package/scripts/github/manage-sub-issues.mjs +37 -16
- package/scripts/github/post-gate-findings.mjs +392 -0
- package/scripts/github/probe-copilot-review.mjs +24 -12
- package/scripts/github/ready-for-review.mjs +17 -8
- package/scripts/github/reconcile-draft-gate.mjs +24 -12
- package/scripts/github/reply-resolve-review-threads.mjs +34 -15
- package/scripts/github/request-copilot-review.mjs +97 -19
- 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 +646 -41
- 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 +42 -21
- package/scripts/loop/build-handoff-envelope.mjs +32 -14
- package/scripts/loop/conductor-monitor.mjs +25 -9
- 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-change-scope.mjs +36 -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 +56 -28
- package/scripts/loop/detect-reviewer-loop-state.mjs +35 -16
- package/scripts/loop/detect-stale-runner.mjs +32 -14
- package/scripts/loop/detect-tracker-first-loop-state.mjs +38 -11
- 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 +23 -8
- package/scripts/loop/run-queue.mjs +87 -15
- 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/add-queue-item.mjs +60 -43
- package/scripts/projects/archive-done-items.mjs +506 -0
- package/scripts/projects/ensure-queue-board.mjs +65 -64
- package/scripts/projects/list-queue-items.mjs +57 -56
- package/scripts/projects/move-queue-item.mjs +123 -124
- package/scripts/projects/reorder-queue-item.mjs +384 -108
- package/scripts/projects/sync-item-status.mjs +198 -0
- package/scripts/refine/_refine-helpers.mjs +21 -9
- package/scripts/refine/verify.mjs +31 -13
- package/skills/copilot-pr-followup/SKILL.md +57 -3
- package/skills/dev-loop/SKILL.md +9 -5
- 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 +2 -2
- package/skills/local-implementation/SKILL.md +17 -3
|
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
6
7
|
import {
|
|
7
8
|
STEERING_KIND,
|
|
8
9
|
STEERING_RESULT,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
withStateFileLock,
|
|
32
33
|
} from "./_steering-state-file.mjs";
|
|
33
34
|
import { formatCliError } from "../_core-helpers.mjs";
|
|
34
|
-
import {
|
|
35
|
+
import { requireTokenValue as readSharedTokenValue } from "../_cli-primitives.mjs";
|
|
35
36
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
36
37
|
const SUBMIT_USAGE = `Usage:
|
|
37
38
|
steer-loop.mjs submit --repo <owner/name> --pr <number>
|
|
@@ -112,10 +113,9 @@ function runIdMismatchError(persistedRunId, requestedRunId) {
|
|
|
112
113
|
`run-id mismatch: --state-file contains run ${JSON.stringify(persistedRunId)} but --run-id is ${JSON.stringify(requestedRunId)}. Use the correct --run-id or point --state-file at the right file.`
|
|
113
114
|
);
|
|
114
115
|
}
|
|
115
|
-
function readRequiredOptionValue(
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
flag,
|
|
116
|
+
function readRequiredOptionValue(token, usage, { allowFlagLike = false } = {}) {
|
|
117
|
+
return readSharedTokenValue(
|
|
118
|
+
token,
|
|
119
119
|
(message) => usageError(message, usage),
|
|
120
120
|
{ flagPattern: allowFlagLike ? /$^/u : /^--/u },
|
|
121
121
|
);
|
|
@@ -139,7 +139,6 @@ function parsePositiveIntegerOption(raw, flag, usage) {
|
|
|
139
139
|
return Number(raw);
|
|
140
140
|
}
|
|
141
141
|
export function parseSubmitCliArgs(argv) {
|
|
142
|
-
const args = [...argv];
|
|
143
142
|
const options = {
|
|
144
143
|
help: false,
|
|
145
144
|
repo: undefined,
|
|
@@ -156,48 +155,74 @@ export function parseSubmitCliArgs(argv) {
|
|
|
156
155
|
copilotInputPath: undefined,
|
|
157
156
|
reviewerInputPath: undefined,
|
|
158
157
|
};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
const { tokens } = parseArgs({
|
|
159
|
+
args: [...argv],
|
|
160
|
+
options: {
|
|
161
|
+
help: { type: "boolean", short: "h" },
|
|
162
|
+
"run-id": { type: "string" },
|
|
163
|
+
repo: { type: "string" },
|
|
164
|
+
pr: { type: "string" },
|
|
165
|
+
kind: { type: "string" },
|
|
166
|
+
directive: { type: "string" },
|
|
167
|
+
seq: { type: "string" },
|
|
168
|
+
"state-file": { type: "string" },
|
|
169
|
+
"loop-state": { type: "string" },
|
|
170
|
+
"apply-mode": { type: "string" },
|
|
171
|
+
"event-id": { type: "string" },
|
|
172
|
+
"copilot-input": { type: "string" },
|
|
173
|
+
"reviewer-input": { type: "string" },
|
|
174
|
+
},
|
|
175
|
+
allowPositionals: true,
|
|
176
|
+
strict: false,
|
|
177
|
+
tokens: true,
|
|
178
|
+
});
|
|
179
|
+
for (const token of tokens) {
|
|
180
|
+
if (token.kind === "positional") {
|
|
181
|
+
throw usageError(`Unknown argument: ${token.value}`, SUBMIT_USAGE);
|
|
182
|
+
}
|
|
183
|
+
if (token.kind !== "option") {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (token.name === "help") {
|
|
162
187
|
options.help = true;
|
|
163
188
|
return options;
|
|
164
189
|
}
|
|
165
|
-
if (token === "
|
|
166
|
-
options.runId = readRequiredOptionValue(
|
|
190
|
+
if (token.name === "run-id") {
|
|
191
|
+
options.runId = readRequiredOptionValue(token, SUBMIT_USAGE).trim();
|
|
167
192
|
validateSafeRunId(options.runId, SUBMIT_USAGE);
|
|
168
193
|
continue;
|
|
169
194
|
}
|
|
170
|
-
if (token === "
|
|
171
|
-
options.repo = readRequiredOptionValue(
|
|
195
|
+
if (token.name === "repo") {
|
|
196
|
+
options.repo = readRequiredOptionValue(token, SUBMIT_USAGE).trim();
|
|
172
197
|
parseRepoSlugOption(options.repo, SUBMIT_USAGE);
|
|
173
198
|
continue;
|
|
174
199
|
}
|
|
175
|
-
if (token === "
|
|
176
|
-
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(
|
|
200
|
+
if (token.name === "pr") {
|
|
201
|
+
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(token, SUBMIT_USAGE), "--pr", SUBMIT_USAGE);
|
|
177
202
|
continue;
|
|
178
203
|
}
|
|
179
|
-
if (token === "
|
|
180
|
-
const val = readRequiredOptionValue(
|
|
204
|
+
if (token.name === "kind") {
|
|
205
|
+
const val = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
181
206
|
if (!VALID_KINDS.has(val)) {
|
|
182
207
|
throw usageError(`--kind must be one of: ${[...VALID_KINDS].join(", ")}`, SUBMIT_USAGE);
|
|
183
208
|
}
|
|
184
209
|
options.kind = val;
|
|
185
210
|
continue;
|
|
186
211
|
}
|
|
187
|
-
if (token === "
|
|
188
|
-
options.directive = readRequiredOptionValue(
|
|
212
|
+
if (token.name === "directive") {
|
|
213
|
+
options.directive = readRequiredOptionValue(token, SUBMIT_USAGE, { allowFlagLike: true }).trim();
|
|
189
214
|
continue;
|
|
190
215
|
}
|
|
191
|
-
if (token === "
|
|
192
|
-
options.seq = parsePositiveIntegerOption(readRequiredOptionValue(
|
|
216
|
+
if (token.name === "seq") {
|
|
217
|
+
options.seq = parsePositiveIntegerOption(readRequiredOptionValue(token, SUBMIT_USAGE), "--seq", SUBMIT_USAGE);
|
|
193
218
|
continue;
|
|
194
219
|
}
|
|
195
|
-
if (token === "
|
|
196
|
-
options.stateFile = readRequiredOptionValue(
|
|
220
|
+
if (token.name === "state-file") {
|
|
221
|
+
options.stateFile = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
197
222
|
continue;
|
|
198
223
|
}
|
|
199
|
-
if (token === "
|
|
200
|
-
const val = readRequiredOptionValue(
|
|
224
|
+
if (token.name === "loop-state") {
|
|
225
|
+
const val = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
201
226
|
if (!VALID_LOOP_STATES.has(val)) {
|
|
202
227
|
throw usageError(`--loop-state must be one of: ${[...VALID_LOOP_STATES].join(", ")}`, SUBMIT_USAGE);
|
|
203
228
|
}
|
|
@@ -205,27 +230,27 @@ export function parseSubmitCliArgs(argv) {
|
|
|
205
230
|
options.loopStateExplicit = true;
|
|
206
231
|
continue;
|
|
207
232
|
}
|
|
208
|
-
if (token === "
|
|
209
|
-
const val = readRequiredOptionValue(
|
|
233
|
+
if (token.name === "apply-mode") {
|
|
234
|
+
const val = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
210
235
|
if (!VALID_APPLY_MODES.has(val)) {
|
|
211
236
|
throw usageError(`--apply-mode must be one of: ${[...VALID_APPLY_MODES].join(", ")}`, SUBMIT_USAGE);
|
|
212
237
|
}
|
|
213
238
|
options.applyMode = val;
|
|
214
239
|
continue;
|
|
215
240
|
}
|
|
216
|
-
if (token === "
|
|
217
|
-
options.eventId = readRequiredOptionValue(
|
|
241
|
+
if (token.name === "event-id") {
|
|
242
|
+
options.eventId = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
218
243
|
continue;
|
|
219
244
|
}
|
|
220
|
-
if (token === "
|
|
221
|
-
options.copilotInputPath = readRequiredOptionValue(
|
|
245
|
+
if (token.name === "copilot-input") {
|
|
246
|
+
options.copilotInputPath = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
222
247
|
continue;
|
|
223
248
|
}
|
|
224
|
-
if (token === "
|
|
225
|
-
options.reviewerInputPath = readRequiredOptionValue(
|
|
249
|
+
if (token.name === "reviewer-input") {
|
|
250
|
+
options.reviewerInputPath = readRequiredOptionValue(token, SUBMIT_USAGE);
|
|
226
251
|
continue;
|
|
227
252
|
}
|
|
228
|
-
throw usageError(`Unknown argument: ${token}`, SUBMIT_USAGE);
|
|
253
|
+
throw usageError(`Unknown argument: ${token.rawName}`, SUBMIT_USAGE);
|
|
229
254
|
}
|
|
230
255
|
if (!options.help) {
|
|
231
256
|
if ((options.repo === undefined) !== (options.pr === undefined)) {
|
|
@@ -250,7 +275,6 @@ export function parseSubmitCliArgs(argv) {
|
|
|
250
275
|
return options;
|
|
251
276
|
}
|
|
252
277
|
export function parseStatusCliArgs(argv) {
|
|
253
|
-
const args = [...argv];
|
|
254
278
|
const options = {
|
|
255
279
|
help: false,
|
|
256
280
|
repo: undefined,
|
|
@@ -258,31 +282,49 @@ export function parseStatusCliArgs(argv) {
|
|
|
258
282
|
runId: undefined,
|
|
259
283
|
stateFile: undefined,
|
|
260
284
|
};
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
285
|
+
const { tokens } = parseArgs({
|
|
286
|
+
args: [...argv],
|
|
287
|
+
options: {
|
|
288
|
+
help: { type: "boolean", short: "h" },
|
|
289
|
+
"run-id": { type: "string" },
|
|
290
|
+
repo: { type: "string" },
|
|
291
|
+
pr: { type: "string" },
|
|
292
|
+
"state-file": { type: "string" },
|
|
293
|
+
},
|
|
294
|
+
allowPositionals: true,
|
|
295
|
+
strict: false,
|
|
296
|
+
tokens: true,
|
|
297
|
+
});
|
|
298
|
+
for (const token of tokens) {
|
|
299
|
+
if (token.kind === "positional") {
|
|
300
|
+
throw usageError(`Unknown argument: ${token.value}`, STATUS_USAGE);
|
|
301
|
+
}
|
|
302
|
+
if (token.kind !== "option") {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (token.name === "help") {
|
|
264
306
|
options.help = true;
|
|
265
307
|
return options;
|
|
266
308
|
}
|
|
267
|
-
if (token === "
|
|
268
|
-
options.runId = readRequiredOptionValue(
|
|
309
|
+
if (token.name === "run-id") {
|
|
310
|
+
options.runId = readRequiredOptionValue(token, STATUS_USAGE).trim();
|
|
269
311
|
validateSafeRunId(options.runId, STATUS_USAGE);
|
|
270
312
|
continue;
|
|
271
313
|
}
|
|
272
|
-
if (token === "
|
|
273
|
-
options.repo = readRequiredOptionValue(
|
|
314
|
+
if (token.name === "repo") {
|
|
315
|
+
options.repo = readRequiredOptionValue(token, STATUS_USAGE).trim();
|
|
274
316
|
parseRepoSlugOption(options.repo, STATUS_USAGE);
|
|
275
317
|
continue;
|
|
276
318
|
}
|
|
277
|
-
if (token === "
|
|
278
|
-
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(
|
|
319
|
+
if (token.name === "pr") {
|
|
320
|
+
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(token, STATUS_USAGE), "--pr", STATUS_USAGE);
|
|
279
321
|
continue;
|
|
280
322
|
}
|
|
281
|
-
if (token === "
|
|
282
|
-
options.stateFile = readRequiredOptionValue(
|
|
323
|
+
if (token.name === "state-file") {
|
|
324
|
+
options.stateFile = readRequiredOptionValue(token, STATUS_USAGE);
|
|
283
325
|
continue;
|
|
284
326
|
}
|
|
285
|
-
throw usageError(`Unknown argument: ${token}`, STATUS_USAGE);
|
|
327
|
+
throw usageError(`Unknown argument: ${token.rawName}`, STATUS_USAGE);
|
|
286
328
|
}
|
|
287
329
|
if (!options.help) {
|
|
288
330
|
if ((options.repo === undefined) !== (options.pr === undefined)) {
|
|
@@ -298,7 +340,6 @@ export function parseStatusCliArgs(argv) {
|
|
|
298
340
|
return options;
|
|
299
341
|
}
|
|
300
342
|
export function parsePromoteCliArgs(argv) {
|
|
301
|
-
const args = [...argv];
|
|
302
343
|
const options = {
|
|
303
344
|
help: false,
|
|
304
345
|
repo: undefined,
|
|
@@ -307,39 +348,58 @@ export function parsePromoteCliArgs(argv) {
|
|
|
307
348
|
stateFile: undefined,
|
|
308
349
|
loopState: undefined,
|
|
309
350
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
351
|
+
const { tokens } = parseArgs({
|
|
352
|
+
args: [...argv],
|
|
353
|
+
options: {
|
|
354
|
+
help: { type: "boolean", short: "h" },
|
|
355
|
+
"run-id": { type: "string" },
|
|
356
|
+
repo: { type: "string" },
|
|
357
|
+
pr: { type: "string" },
|
|
358
|
+
"state-file": { type: "string" },
|
|
359
|
+
"loop-state": { type: "string" },
|
|
360
|
+
},
|
|
361
|
+
allowPositionals: true,
|
|
362
|
+
strict: false,
|
|
363
|
+
tokens: true,
|
|
364
|
+
});
|
|
365
|
+
for (const token of tokens) {
|
|
366
|
+
if (token.kind === "positional") {
|
|
367
|
+
throw usageError(`Unknown argument: ${token.value}`, PROMOTE_USAGE);
|
|
368
|
+
}
|
|
369
|
+
if (token.kind !== "option") {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (token.name === "help") {
|
|
313
373
|
options.help = true;
|
|
314
374
|
return options;
|
|
315
375
|
}
|
|
316
|
-
if (token === "
|
|
317
|
-
options.runId = readRequiredOptionValue(
|
|
376
|
+
if (token.name === "run-id") {
|
|
377
|
+
options.runId = readRequiredOptionValue(token, PROMOTE_USAGE).trim();
|
|
318
378
|
validateSafeRunId(options.runId, PROMOTE_USAGE);
|
|
319
379
|
continue;
|
|
320
380
|
}
|
|
321
|
-
if (token === "
|
|
322
|
-
options.repo = readRequiredOptionValue(
|
|
381
|
+
if (token.name === "repo") {
|
|
382
|
+
options.repo = readRequiredOptionValue(token, PROMOTE_USAGE).trim();
|
|
323
383
|
parseRepoSlugOption(options.repo, PROMOTE_USAGE);
|
|
324
384
|
continue;
|
|
325
385
|
}
|
|
326
|
-
if (token === "
|
|
327
|
-
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(
|
|
386
|
+
if (token.name === "pr") {
|
|
387
|
+
options.pr = parsePositiveIntegerOption(readRequiredOptionValue(token, PROMOTE_USAGE), "--pr", PROMOTE_USAGE);
|
|
328
388
|
continue;
|
|
329
389
|
}
|
|
330
|
-
if (token === "
|
|
331
|
-
options.stateFile = readRequiredOptionValue(
|
|
390
|
+
if (token.name === "state-file") {
|
|
391
|
+
options.stateFile = readRequiredOptionValue(token, PROMOTE_USAGE);
|
|
332
392
|
continue;
|
|
333
393
|
}
|
|
334
|
-
if (token === "
|
|
335
|
-
const val = readRequiredOptionValue(
|
|
394
|
+
if (token.name === "loop-state") {
|
|
395
|
+
const val = readRequiredOptionValue(token, PROMOTE_USAGE);
|
|
336
396
|
if (!VALID_LOOP_STATES.has(val)) {
|
|
337
397
|
throw usageError(`--loop-state must be one of: ${[...VALID_LOOP_STATES].join(", ")}`, PROMOTE_USAGE);
|
|
338
398
|
}
|
|
339
399
|
options.loopState = val;
|
|
340
400
|
continue;
|
|
341
401
|
}
|
|
342
|
-
throw usageError(`Unknown argument: ${token}`, PROMOTE_USAGE);
|
|
402
|
+
throw usageError(`Unknown argument: ${token.rawName}`, PROMOTE_USAGE);
|
|
343
403
|
}
|
|
344
404
|
if (!options.help) {
|
|
345
405
|
if ((options.repo === undefined) !== (options.pr === undefined)) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
4
5
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
5
|
-
import { parseIssueNumber,
|
|
6
|
+
import { parseIssueNumber, requireTokenValue } from "../_cli-primitives.mjs";
|
|
6
7
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
7
8
|
import { detectInitialCopilotPrState, LINKED_PR_STATE } from "./detect-initial-copilot-pr-state.mjs";
|
|
8
9
|
import { enforcePersistentInternalWaitTimeout } from "@dev-loops/core/loop/timeout-policy";
|
|
@@ -94,7 +95,6 @@ export async function watchCopilotRunUntilComplete(
|
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
export function parseWatchInitialCopilotPrCliArgs(argv) {
|
|
97
|
-
const args = [...argv];
|
|
98
98
|
const options = {
|
|
99
99
|
help: false,
|
|
100
100
|
repo: undefined,
|
|
@@ -102,24 +102,40 @@ export function parseWatchInitialCopilotPrCliArgs(argv) {
|
|
|
102
102
|
pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,
|
|
103
103
|
timeoutMs: COPILOT_FIRST_DURABLE_WAIT_TIMEOUT_MS,
|
|
104
104
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const { tokens } = parseArgs({
|
|
106
|
+
args: [...argv],
|
|
107
|
+
options: {
|
|
108
|
+
help: { type: "boolean", short: "h" },
|
|
109
|
+
repo: { type: "string" },
|
|
110
|
+
issue: { type: "string" },
|
|
111
|
+
},
|
|
112
|
+
allowPositionals: true,
|
|
113
|
+
strict: false,
|
|
114
|
+
tokens: true,
|
|
115
|
+
});
|
|
116
|
+
for (const token of tokens) {
|
|
117
|
+
if (token.kind === "positional") {
|
|
118
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
119
|
+
}
|
|
120
|
+
if (token.kind !== "option") {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (token.name === "help") {
|
|
108
124
|
options.help = true;
|
|
109
125
|
return options;
|
|
110
126
|
}
|
|
111
|
-
if (REMOVED_FLAGS.has(token)) {
|
|
112
|
-
rejectRemovedFlag(token);
|
|
127
|
+
if (REMOVED_FLAGS.has(token.rawName)) {
|
|
128
|
+
rejectRemovedFlag(token.rawName);
|
|
113
129
|
}
|
|
114
|
-
if (token === "
|
|
115
|
-
options.repo =
|
|
130
|
+
if (token.name === "repo") {
|
|
131
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
116
132
|
continue;
|
|
117
133
|
}
|
|
118
|
-
if (token === "
|
|
119
|
-
options.issue = parseIssueNumber(
|
|
134
|
+
if (token.name === "issue") {
|
|
135
|
+
options.issue = parseIssueNumber(requireTokenValue(token, parseError), parseError);
|
|
120
136
|
continue;
|
|
121
137
|
}
|
|
122
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
138
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
123
139
|
}
|
|
124
140
|
if (options.repo === undefined || options.issue === undefined) {
|
|
125
141
|
throw parseError("watch-initial-copilot-pr requires both --repo <owner/name> and --issue <number>");
|
|
@@ -1,6 +1,7 @@
|
|
|
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: dev-loops project add --repo <owner/name> --project <number|id> --item <number>
|
|
6
7
|
|
|
@@ -23,52 +24,68 @@ Exit codes:
|
|
|
23
24
|
3 — project, field, column, or issue/PR not found
|
|
24
25
|
`.trim();
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
function parseCliArgs(argv) {
|
|
28
|
+
const parseError = (message) => Object.assign(new Error(message), { usage: USAGE });
|
|
29
|
+
const requireValue = (token, message) => {
|
|
30
|
+
const v = token.value;
|
|
31
|
+
if (typeof v !== "string" || v.length === 0 || v.startsWith("-")) {
|
|
32
|
+
throw parseError(message);
|
|
33
|
+
}
|
|
34
|
+
return v;
|
|
35
|
+
};
|
|
27
36
|
|
|
28
|
-
function parseArgs(argv) {
|
|
29
37
|
const args = {};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
const { tokens } = parseArgs({
|
|
39
|
+
args: [...argv],
|
|
40
|
+
options: {
|
|
41
|
+
repo: { type: "string" },
|
|
42
|
+
project: { type: "string" },
|
|
43
|
+
item: { type: "string" },
|
|
44
|
+
status: { type: "string" },
|
|
45
|
+
help: { type: "boolean", short: "h" },
|
|
46
|
+
},
|
|
47
|
+
allowPositionals: true,
|
|
48
|
+
strict: false,
|
|
49
|
+
tokens: true,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
for (const token of tokens) {
|
|
53
|
+
if (token.kind === "positional") {
|
|
54
|
+
throw Object.assign(new Error(`Unexpected argument: ${token.value}`), { code: "INVALID_ARGS", usage: USAGE });
|
|
37
55
|
}
|
|
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
|
-
|
|
62
|
-
|
|
56
|
+
if (token.kind !== "option") {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
switch (token.name) {
|
|
60
|
+
case "help":
|
|
61
|
+
if (token.value !== undefined) {
|
|
62
|
+
throw Object.assign(new Error(`Unknown flag: ${token.rawName}=${token.value}`), { code: "INVALID_ARGS", usage: USAGE });
|
|
63
|
+
}
|
|
64
|
+
args.help = true;
|
|
65
|
+
break;
|
|
66
|
+
case "repo":
|
|
67
|
+
args.repo = requireValue(token, "--repo requires a value (owner/name)");
|
|
68
|
+
break;
|
|
69
|
+
case "project":
|
|
70
|
+
args.project = requireValue(token, "--project requires a value (number or node ID)");
|
|
71
|
+
break;
|
|
72
|
+
case "item": {
|
|
73
|
+
const raw = requireValue(token, "--item requires a value (number)");
|
|
74
|
+
const val = Number(raw);
|
|
75
|
+
if (!Number.isInteger(val) || val < 1) {
|
|
76
|
+
throw Object.assign(
|
|
77
|
+
new Error(`--item must be a positive integer, got "${raw}"`),
|
|
78
|
+
{ code: "INVALID_ITEM" },
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
args.item = val;
|
|
82
|
+
break;
|
|
63
83
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
new Error(`Unexpected argument: ${arg}`),
|
|
70
|
-
{ code: "INVALID_ARGS", usage: USAGE },
|
|
71
|
-
);
|
|
84
|
+
case "status":
|
|
85
|
+
args.status = requireValue(token, "--status requires a value");
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
throw Object.assign(new Error(`Unknown flag: ${token.rawName}`), { code: "INVALID_ARGS", usage: USAGE });
|
|
72
89
|
}
|
|
73
90
|
}
|
|
74
91
|
return args;
|
|
@@ -499,7 +516,7 @@ async function main(args, { env = process.env, runChild } = {}) {
|
|
|
499
516
|
async function runCli(argv, { stdout = process.stdout, stderr = process.stderr, env = process.env } = {}) {
|
|
500
517
|
let args;
|
|
501
518
|
try {
|
|
502
|
-
args =
|
|
519
|
+
args = parseCliArgs(argv);
|
|
503
520
|
} catch (err) {
|
|
504
521
|
stderr.write(`${formatCliError(err)}\n`);
|
|
505
522
|
process.exitCode = 1;
|