dev-loops 0.2.6 → 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.
Files changed (58) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/agents/dev-loop.md +2 -1
  3. package/.claude/skills/dev-loop/SKILL.md +5 -5
  4. package/CHANGELOG.md +17 -0
  5. package/agents/dev-loop.agent.md +5 -1
  6. package/cli/index.mjs +3 -1
  7. package/package.json +2 -2
  8. package/scripts/_cli-primitives.mjs +2 -0
  9. package/scripts/claude/generate-claude-assets.mjs +12 -2
  10. package/scripts/docs/validate-links.mjs +20 -11
  11. package/scripts/github/capture-review-threads.mjs +32 -14
  12. package/scripts/github/detect-checkpoint-evidence.mjs +28 -11
  13. package/scripts/github/detect-linked-issue-pr.mjs +22 -10
  14. package/scripts/github/manage-sub-issues.mjs +37 -16
  15. package/scripts/github/probe-copilot-review.mjs +24 -12
  16. package/scripts/github/ready-for-review.mjs +17 -8
  17. package/scripts/github/reconcile-draft-gate.mjs +22 -10
  18. package/scripts/github/reply-resolve-review-threads.mjs +34 -15
  19. package/scripts/github/request-copilot-review.mjs +28 -11
  20. package/scripts/github/resolve-tracker-local-spec.mjs +29 -12
  21. package/scripts/github/stage-reviewer-draft.mjs +32 -14
  22. package/scripts/github/upsert-checkpoint-verdict.mjs +49 -26
  23. package/scripts/github/write-gate-findings-log.mjs +41 -20
  24. package/scripts/loop/build-handoff-envelope.mjs +32 -14
  25. package/scripts/loop/conductor-monitor.mjs +25 -9
  26. package/scripts/loop/conductor.mjs +31 -12
  27. package/scripts/loop/copilot-pr-handoff.mjs +31 -14
  28. package/scripts/loop/debt-remediate.mjs +28 -11
  29. package/scripts/loop/detect-copilot-loop-state.mjs +29 -12
  30. package/scripts/loop/detect-copilot-session-activity.mjs +29 -12
  31. package/scripts/loop/detect-initial-copilot-pr-state.mjs +26 -10
  32. package/scripts/loop/detect-internal-only-pr.mjs +31 -13
  33. package/scripts/loop/detect-issue-refinement-artifact.mjs +29 -12
  34. package/scripts/loop/detect-pr-gate-coordination-state.mjs +26 -10
  35. package/scripts/loop/detect-reviewer-loop-state.mjs +35 -16
  36. package/scripts/loop/detect-stale-runner.mjs +32 -14
  37. package/scripts/loop/detect-tracker-pr-state.mjs +23 -8
  38. package/scripts/loop/info.mjs +28 -10
  39. package/scripts/loop/inspect-run-viewer/cli.mjs +44 -21
  40. package/scripts/loop/inspect-run.mjs +35 -16
  41. package/scripts/loop/outer-loop.mjs +35 -16
  42. package/scripts/loop/pr-runner-coordination.mjs +31 -12
  43. package/scripts/loop/pre-commit-branch-guard.mjs +26 -9
  44. package/scripts/loop/pre-flight-gate.mjs +25 -9
  45. package/scripts/loop/pre-pr-ready-gate.mjs +24 -8
  46. package/scripts/loop/pre-push-main-guard.mjs +19 -5
  47. package/scripts/loop/pre-write-remote-freshness-guard.mjs +23 -7
  48. package/scripts/loop/resolve-dev-loop-startup.mjs +29 -12
  49. package/scripts/loop/run-conductor-cycle.mjs +23 -8
  50. package/scripts/loop/run-refinement-audit.mjs +44 -22
  51. package/scripts/loop/run-watch-cycle.mjs +28 -12
  52. package/scripts/loop/steer-loop.mjs +122 -62
  53. package/scripts/loop/watch-initial-copilot-pr.mjs +28 -12
  54. package/scripts/projects/archive-done-items.mjs +410 -0
  55. package/scripts/projects/reorder-queue-item.mjs +334 -76
  56. package/scripts/refine/_refine-helpers.mjs +21 -9
  57. package/scripts/refine/verify.mjs +31 -13
  58. package/skills/dev-loop/SKILL.md +9 -5
@@ -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, requireOptionValue, runChild } from "../_cli-primitives.mjs";
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 args = [...argv];
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
- while (args.length > 0) {
46
- const token = args.shift();
47
- if (token === "--help" || token === "-h") {
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 === "--repo") {
52
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
63
+ if (token.name === "repo") {
64
+ options.repo = requireTokenValue(token, parseError).trim();
53
65
  continue;
54
66
  }
55
- if (token === "--pr") {
56
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
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
- requireOptionValue,
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 args = [...argv];
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
- while (args.length > 0) {
47
- const token = args.shift();
48
- if (token === "--help" || token === "-h") {
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 === "--repo") {
53
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
71
+ if (token.name === "repo") {
72
+ options.repo = requireTokenValue(token, parseError).trim();
54
73
  continue;
55
74
  }
56
- if (token === "--pr") {
57
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
75
+ if (token.name === "pr") {
76
+ options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
58
77
  continue;
59
78
  }
60
- if (token === "--author") {
61
- options.author = requireOptionValue(args, "--author", parseError).trim();
79
+ if (token.name === "author") {
80
+ options.author = requireTokenValue(token, parseError).trim();
62
81
  continue;
63
82
  }
64
- if (token === "--message") {
65
- options.message = requireOptionValue(args, "--message", parseError);
83
+ if (token.name === "message") {
84
+ options.message = requireTokenValue(token, parseError);
66
85
  continue;
67
86
  }
68
- if (token === "--resolve") {
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, requireOptionValue, runChild } from "../_cli-primitives.mjs";
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 args = [...argv];
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
- while (args.length > 0) {
63
- const token = args.shift();
64
- if (token === "--help" || token === "-h") {
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 === "--force-rerequest-review") {
85
+ if (token.name === "force-rerequest-review") {
69
86
  options.forceRerequestReview = true;
70
87
  continue;
71
88
  }
72
- if (token === "--repo") {
73
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
89
+ if (token.name === "repo") {
90
+ options.repo = requireTokenValue(token, parseError).trim();
74
91
  continue;
75
92
  }
76
- if (token === "--pr") {
77
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
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, requireOptionValue, runChild } from "../_cli-primitives.mjs";
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 args = [...argv];
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
- while (args.length > 0) {
71
- const token = args.shift();
72
- if (token === "--help" || token === "-h") {
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 === "--repo") {
77
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
93
+ if (token.name === "repo") {
94
+ options.repo = requireTokenValue(token, parseError).trim();
78
95
  continue;
79
96
  }
80
- if (token === "--issue") {
81
- options.issue = parseIssueNumber(requireOptionValue(args, "--issue", parseError), parseError);
97
+ if (token.name === "issue") {
98
+ options.issue = parseIssueNumber(requireTokenValue(token, parseError), parseError);
82
99
  continue;
83
100
  }
84
- if (token === "--issue-url") {
85
- options.issueUrl = requireOptionValue(args, "--issue-url", parseError).trim();
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 { parsePositiveInteger, requireOptionValue } from "../_cli-primitives.mjs";
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 args = [...argv];
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
- while (args.length > 0) {
31
- const token = args.shift();
32
- if (token === "--help" || token === "-h") {
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 === "--repo") {
37
- options.repo = requireOptionValue(args, "--repo").trim();
54
+ if (token.name === "repo") {
55
+ options.repo = requireTokenValue(token).trim();
38
56
  continue;
39
57
  }
40
- if (token === "--pr") {
41
- options.pr = parsePositiveInteger(requireOptionValue(args, "--pr"), "--pr");
58
+ if (token.name === "pr") {
59
+ options.pr = parsePositiveInteger(requireTokenValue(token), "--pr");
42
60
  continue;
43
61
  }
44
- if (token === "--review-file") {
45
- options.reviewFile = requireOptionValue(args, "--review-file");
62
+ if (token.name === "review-file") {
63
+ options.reviewFile = requireTokenValue(token);
46
64
  continue;
47
65
  }
48
- if (token === "--local-state-output") {
49
- options.localStateOutput = requireOptionValue(args, "--local-state-output");
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 { parsePrNumber, requireOptionValue, runChild } from "../_cli-primitives.mjs";
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 args = [...argv];
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
- while (args.length > 0) {
215
- const token = args.shift();
216
- if (token === "--help" || token === "-h") {
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 === "--repo") {
224
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
246
+ if (token.name === "repo") {
247
+ options.repo = requireTokenValue(token, parseError).trim();
225
248
  continue;
226
249
  }
227
- if (token === "--pr") {
228
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
250
+ if (token.name === "pr") {
251
+ options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
229
252
  continue;
230
253
  }
231
- if (token === "--gate") {
232
- const gate = normalizeGateName(requireOptionValue(args, "--gate", parseError));
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 === "--head-sha") {
240
- const headSha = normalizeHeadSha(requireOptionValue(args, "--head-sha", parseError));
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 === "--verdict") {
248
- const verdict = normalizeVerdict(requireOptionValue(args, "--verdict", parseError));
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 === "--findings-summary") {
256
- options.findingsSummary = normalizeRequiredText(requireOptionValue(args, "--findings-summary", parseError), "--findings-summary");
278
+ if (token.name === "findings-summary") {
279
+ options.findingsSummary = normalizeRequiredText(requireTokenValue(token, parseError), "--findings-summary");
257
280
  continue;
258
281
  }
259
- if (token === "--findings-file") {
260
- const rawPath = requireOptionValue(args, "--findings-file", parseError).trim();
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 === "--next-action") {
268
- options.nextAction = normalizeRequiredText(requireOptionValue(args, "--next-action", parseError), "--next-action");
290
+ if (token.name === "next-action") {
291
+ options.nextAction = normalizeRequiredText(requireTokenValue(token, parseError), "--next-action");
269
292
  continue;
270
293
  }
271
- if (token === "--findings-severity-counts") {
272
- const raw = requireOptionValue(args, "--findings-severity-counts", parseError);
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 { parsePrNumber, requireOptionValue } from "../_cli-primitives.mjs";
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 args = [...argv];
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
- while (args.length > 0) {
103
- const token = args.shift();
104
- if (token === "--help" || token === "-h") {
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 === "--repo") {
108
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
128
+ if (token.name === "repo") {
129
+ options.repo = requireTokenValue(token, parseError).trim();
109
130
  continue;
110
131
  }
111
- if (token === "--pr") {
112
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
132
+ if (token.name === "pr") {
133
+ options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
113
134
  continue;
114
135
  }
115
- if (token === "--gate") {
116
- const gate = normalizeGate(requireOptionValue(args, "--gate", parseError));
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 === "--head-sha") {
122
- const sha = normalizeHeadSha(requireOptionValue(args, "--head-sha", parseError));
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 === "--verdict") {
128
- const verdict = normalizeVerdict(requireOptionValue(args, "--verdict", parseError));
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 === "--findings") {
134
- options.findings = requireOptionValue(args, "--findings", parseError);
154
+ if (token.name === "findings") {
155
+ options.findings = requireTokenValue(token, parseError);
135
156
  continue;
136
157
  }
137
- if (token === "--tmp-root") {
138
- options.tmpRoot = requireOptionValue(args, "--tmp-root", parseError).trim();
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);