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.
Files changed (92) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/agents/dev-loop.md +2 -1
  3. package/.claude/agents/developer.md +1 -0
  4. package/.claude/agents/fixer.md +1 -0
  5. package/.claude/agents/review.md +30 -0
  6. package/.claude/skills/copilot-pr-followup/SKILL.md +57 -3
  7. package/.claude/skills/dev-loop/SKILL.md +5 -5
  8. package/.claude/skills/docs/anti-patterns.md +2 -0
  9. package/.claude/skills/docs/copilot-loop-operations.md +2 -2
  10. package/.claude/skills/local-implementation/SKILL.md +17 -3
  11. package/AGENTS.md +1 -1
  12. package/CHANGELOG.md +60 -0
  13. package/agents/dev-loop.agent.md +5 -1
  14. package/agents/developer.agent.md +1 -0
  15. package/agents/fixer.agent.md +1 -0
  16. package/agents/review.agent.md +30 -0
  17. package/cli/index.mjs +42 -7
  18. package/package.json +2 -2
  19. package/scripts/README.md +6 -5
  20. package/scripts/_cli-primitives.mjs +2 -0
  21. package/scripts/_core-helpers.mjs +1 -0
  22. package/scripts/claude/generate-claude-assets.mjs +12 -2
  23. package/scripts/claude/headless-dev-loop.mjs +53 -13
  24. package/scripts/claude/headless-info-smoke.mjs +45 -11
  25. package/scripts/docs/validate-links.mjs +20 -11
  26. package/scripts/github/build-adjacent-bundle.mjs +448 -0
  27. package/scripts/github/capture-review-threads.mjs +32 -14
  28. package/scripts/github/{create-draft-pr.mjs → create-pr.mjs} +28 -12
  29. package/scripts/github/detect-checkpoint-evidence.mjs +123 -15
  30. package/scripts/github/detect-linked-issue-pr.mjs +22 -10
  31. package/scripts/github/manage-sub-issues.mjs +37 -16
  32. package/scripts/github/post-gate-findings.mjs +392 -0
  33. package/scripts/github/probe-copilot-review.mjs +24 -12
  34. package/scripts/github/ready-for-review.mjs +17 -8
  35. package/scripts/github/reconcile-draft-gate.mjs +24 -12
  36. package/scripts/github/reply-resolve-review-threads.mjs +34 -15
  37. package/scripts/github/request-copilot-review.mjs +97 -19
  38. package/scripts/github/resolve-tracker-local-spec.mjs +29 -12
  39. package/scripts/github/stage-reviewer-draft.mjs +32 -14
  40. package/scripts/github/upsert-checkpoint-verdict.mjs +646 -41
  41. package/scripts/github/verify-fresh-review-context.mjs +12 -1
  42. package/scripts/github/write-gate-context.mjs +634 -0
  43. package/scripts/github/write-gate-findings-log.mjs +42 -21
  44. package/scripts/loop/build-handoff-envelope.mjs +32 -14
  45. package/scripts/loop/conductor-monitor.mjs +25 -9
  46. package/scripts/loop/conductor.mjs +31 -12
  47. package/scripts/loop/copilot-pr-handoff.mjs +31 -14
  48. package/scripts/loop/debt-remediate.mjs +28 -11
  49. package/scripts/loop/detect-change-scope.mjs +36 -11
  50. package/scripts/loop/detect-copilot-loop-state.mjs +29 -12
  51. package/scripts/loop/detect-copilot-session-activity.mjs +29 -12
  52. package/scripts/loop/detect-initial-copilot-pr-state.mjs +26 -10
  53. package/scripts/loop/detect-internal-only-pr.mjs +31 -13
  54. package/scripts/loop/detect-issue-refinement-artifact.mjs +29 -12
  55. package/scripts/loop/detect-pr-gate-coordination-state.mjs +56 -28
  56. package/scripts/loop/detect-reviewer-loop-state.mjs +35 -16
  57. package/scripts/loop/detect-stale-runner.mjs +32 -14
  58. package/scripts/loop/detect-tracker-first-loop-state.mjs +38 -11
  59. package/scripts/loop/detect-tracker-pr-state.mjs +23 -8
  60. package/scripts/loop/info.mjs +28 -10
  61. package/scripts/loop/inspect-run-viewer/cli.mjs +44 -21
  62. package/scripts/loop/inspect-run.mjs +35 -16
  63. package/scripts/loop/outer-loop.mjs +35 -16
  64. package/scripts/loop/pr-runner-coordination.mjs +31 -12
  65. package/scripts/loop/pre-commit-branch-guard.mjs +26 -9
  66. package/scripts/loop/pre-flight-gate.mjs +25 -9
  67. package/scripts/loop/pre-pr-ready-gate.mjs +24 -8
  68. package/scripts/loop/pre-push-main-guard.mjs +19 -5
  69. package/scripts/loop/pre-write-remote-freshness-guard.mjs +23 -7
  70. package/scripts/loop/resolve-dev-loop-startup.mjs +29 -12
  71. package/scripts/loop/run-conductor-cycle.mjs +23 -8
  72. package/scripts/loop/run-queue.mjs +87 -15
  73. package/scripts/loop/run-refinement-audit.mjs +44 -22
  74. package/scripts/loop/run-watch-cycle.mjs +28 -12
  75. package/scripts/loop/steer-loop.mjs +122 -62
  76. package/scripts/loop/watch-initial-copilot-pr.mjs +28 -12
  77. package/scripts/projects/add-queue-item.mjs +60 -43
  78. package/scripts/projects/archive-done-items.mjs +506 -0
  79. package/scripts/projects/ensure-queue-board.mjs +65 -64
  80. package/scripts/projects/list-queue-items.mjs +57 -56
  81. package/scripts/projects/move-queue-item.mjs +123 -124
  82. package/scripts/projects/reorder-queue-item.mjs +384 -108
  83. package/scripts/projects/sync-item-status.mjs +198 -0
  84. package/scripts/refine/_refine-helpers.mjs +21 -9
  85. package/scripts/refine/verify.mjs +31 -13
  86. package/skills/copilot-pr-followup/SKILL.md +57 -3
  87. package/skills/dev-loop/SKILL.md +9 -5
  88. package/skills/dev-loop/scripts/log-bash-exit-1.mjs +2 -2
  89. package/skills/dev-loop/scripts/phase-files.mjs +2 -2
  90. package/skills/docs/anti-patterns.md +2 -0
  91. package/skills/docs/copilot-loop-operations.md +2 -2
  92. package/skills/local-implementation/SKILL.md +17 -3
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import process from "node:process";
3
3
  import { execFileSync } from "node:child_process";
4
+ import { parseArgs } from "node:util";
4
5
  import { interpretTrackerLoopState } from "@dev-loops/core/loop/tracker-first-loop-state";
6
+
5
7
  function showHelp() {
6
8
  process.stdout.write(`Usage: detect-tracker-first-loop-state.mjs --repo <owner/name> --issue <number>
7
9
  Detect tracker-first loop state for a GitHub issue.
@@ -15,40 +17,65 @@ Exit codes:
15
17
  `);
16
18
  process.exit(0);
17
19
  }
18
- function parseArgs() {
19
- const args = process.argv.slice(2);
20
+
21
+ function parseCliArgs(argv) {
22
+ const { tokens } = parseArgs({
23
+ args: [...argv],
24
+ options: {
25
+ repo: { type: "string" },
26
+ issue: { type: "string" },
27
+ help: { type: "boolean", short: "h" },
28
+ },
29
+ allowPositionals: true,
30
+ strict: false,
31
+ tokens: true,
32
+ });
33
+
20
34
  const opts = { repo: null, issue: null };
21
- for (let i = 0; i < args.length; i++) {
22
- if (args[i] === "--help" || args[i] === "-h") {
23
- showHelp();
35
+ for (const token of tokens) {
36
+ if (token.kind === "option") {
37
+ if (token.name === "help") {
38
+ if (token.value !== undefined) {
39
+ throw new Error(`unknown argument: ${token.rawName}=${token.value}`);
40
+ }
41
+ showHelp();
42
+ }
43
+ if (token.name === "repo") {
44
+ opts.repo = token.value ?? null;
45
+ continue;
46
+ }
47
+ if (token.name === "issue") {
48
+ opts.issue = token.value ?? null;
49
+ }
24
50
  }
25
- if (args[i] === "--repo" && i + 1 < args.length) opts.repo = args[++i];
26
- else if (args[i] === "--issue" && i + 1 < args.length) opts.issue = args[++i];
27
51
  }
28
52
  return opts;
29
53
  }
54
+
30
55
  async function main() {
31
- const opts = parseArgs();
32
- if (!opts.repo || !opts.issue) {
56
+ const rawOpts = parseCliArgs(process.argv.slice(2));
57
+ if (!rawOpts.repo || !rawOpts.issue) {
33
58
  process.stderr.write(
34
59
  JSON.stringify({ ok: false, error: "--repo and --issue required" }) + "\n"
35
60
  );
36
61
  process.exitCode = 1;
37
62
  return;
38
63
  }
64
+ const repo = rawOpts.repo;
65
+ const issue = rawOpts.issue;
39
66
  let rawState = "";
40
67
  let prContext = null;
41
68
  try {
42
69
  const issueJson = execFileSync(
43
70
  "gh",
44
- ["issue", "view", String(opts.issue), "--repo", opts.repo, "--json", "state,title", "--jq", ".state"],
71
+ ["issue", "view", String(issue), "--repo", repo, "--json", "state,title", "--jq", ".state"],
45
72
  { encoding: "utf8" }
46
73
  ).trim();
47
74
  rawState = issueJson;
48
75
  try {
49
76
  const prJson = execFileSync(
50
77
  "gh",
51
- ["pr", "list", "--repo", opts.repo, "--search", `${opts.issue} in:body`, "--state", "open", "--json", "number,state,headRefName", "--jq", ".[0]"],
78
+ ["pr", "list", "--repo", repo, "--search", `${issue} in:body`, "--state", "open", "--json", "number,state,headRefName", "--jq", ".[0]"],
52
79
  { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
53
80
  ).trim();
54
81
  if (prJson) prContext = JSON.parse(prJson);
@@ -3,7 +3,8 @@ import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { buildParseError, formatCliError, parseJsonText } from "../_core-helpers.mjs";
6
- import { requireOptionValue } from "../_cli-primitives.mjs";
6
+ import { requireTokenValue } from "../_cli-primitives.mjs";
7
+ import { parseArgs } from "node:util";
7
8
  import {
8
9
  interpretTrackerPrState,
9
10
  normalizeTrackerPrSnapshot,
@@ -51,22 +52,36 @@ Exit codes:
51
52
  1 Argument error or runtime failure`.trim();
52
53
  const parseError = buildParseError(USAGE);
53
54
  export function parseDetectTrackerPrCliArgs(argv) {
54
- const args = [...argv];
55
55
  const options = {
56
56
  help: false,
57
57
  inputPath: undefined,
58
58
  };
59
- while (args.length > 0) {
60
- const token = args.shift();
61
- if (token === "--help" || token === "-h") {
59
+ const { tokens } = parseArgs({
60
+ args: [...argv],
61
+ options: {
62
+ help: { type: "boolean", short: "h" },
63
+ input: { type: "string" },
64
+ },
65
+ allowPositionals: true,
66
+ strict: false,
67
+ tokens: true,
68
+ });
69
+ for (const token of tokens) {
70
+ if (token.kind === "positional") {
71
+ throw parseError(`Unknown argument: ${token.value}`);
72
+ }
73
+ if (token.kind !== "option") {
74
+ continue;
75
+ }
76
+ if (token.name === "help") {
62
77
  options.help = true;
63
78
  return options;
64
79
  }
65
- if (token === "--input") {
66
- options.inputPath = requireOptionValue(args, "--input", parseError);
80
+ if (token.name === "input") {
81
+ options.inputPath = requireTokenValue(token, parseError);
67
82
  continue;
68
83
  }
69
- throw parseError(`Unknown argument: ${token}`);
84
+ throw parseError(`Unknown argument: ${token.rawName}`);
70
85
  }
71
86
  if (options.inputPath === undefined) {
72
87
  throw parseError("--input <path> is required");
@@ -3,9 +3,10 @@ import { execFileSync } from "node:child_process";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
6
- import { requireOptionValue, parsePositiveInteger } from "../_cli-primitives.mjs";
6
+ import { requireTokenValue, parsePositiveInteger } from "../_cli-primitives.mjs";
7
7
  import { detectRepoSlug, normalizeRepoSlug } from "@dev-loops/core/github/repo-slug";
8
8
  import { runContextEnv } from "@dev-loops/core/loop/run-context";
9
+ import { parseArgs } from "node:util";
9
10
 
10
11
  // REPO_ROOT resolves to the git repo root (scripts/loop/info.mjs → scripts/ → repo/)
11
12
  const REPO_ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url)), "..");
@@ -27,16 +28,33 @@ Exit codes:
27
28
  const parseError = buildParseError(USAGE);
28
29
 
29
30
  function parseCliArgs(argv) {
30
- const args = [...argv];
31
31
  const opts = { help: false, issue: undefined, pr: undefined, json: false, repo: undefined };
32
- while (args.length > 0) {
33
- const token = args.shift();
34
- if (token === "--help" || token === "-h") { opts.help = true; return opts; }
35
- if (token === "--json") { opts.json = true; continue; }
36
- if (token === "--issue") { opts.issue = parsePositiveInteger(requireOptionValue(args, "--issue", parseError), "--issue", parseError); continue; }
37
- if (token === "--pr") { opts.pr = parsePositiveInteger(requireOptionValue(args, "--pr", parseError), "--pr", parseError); continue; }
38
- if (token === "--repo") { opts.repo = requireOptionValue(args, "--repo", parseError); continue; }
39
- throw parseError(`Unknown argument: ${token}`);
32
+ const { tokens } = parseArgs({
33
+ args: [...argv],
34
+ options: {
35
+ help: { type: "boolean", short: "h" },
36
+ json: { type: "boolean" },
37
+ issue: { type: "string" },
38
+ pr: { type: "string" },
39
+ repo: { type: "string" },
40
+ },
41
+ allowPositionals: true,
42
+ strict: false,
43
+ tokens: true,
44
+ });
45
+ for (const token of tokens) {
46
+ if (token.kind === "positional") {
47
+ throw parseError(`Unknown argument: ${token.value}`);
48
+ }
49
+ if (token.kind !== "option") {
50
+ continue;
51
+ }
52
+ if (token.name === "help") { opts.help = true; return opts; }
53
+ if (token.name === "json") { opts.json = true; continue; }
54
+ if (token.name === "issue") { opts.issue = parsePositiveInteger(requireTokenValue(token, parseError), "--issue", parseError); continue; }
55
+ if (token.name === "pr") { opts.pr = parsePositiveInteger(requireTokenValue(token, parseError), "--pr", parseError); continue; }
56
+ if (token.name === "repo") { opts.repo = requireTokenValue(token, parseError); continue; }
57
+ throw parseError(`Unknown argument: ${token.rawName}`);
40
58
  }
41
59
  const modes = [opts.issue, opts.pr].filter(v => v !== undefined).length;
42
60
  if (modes > 1) throw parseError("--issue and --pr are mutually exclusive");
@@ -3,7 +3,8 @@ import {
3
3
  DEFAULT_PORT,
4
4
  USAGE,
5
5
  } from "./constants.mjs";
6
- import { requireOptionValue } from "../../_cli-primitives.mjs";
6
+ import { parseArgs } from "node:util";
7
+ import { requireTokenValue } from "../../_cli-primitives.mjs";
7
8
  import { normalizeInspectionTarget } from "../_inspect-run-viewer-adapter.mjs";
8
9
 
9
10
  export function parseInspectRunViewerCliError(message) {
@@ -47,7 +48,6 @@ export function normalizeCliRepoOption(rawRepo) {
47
48
  }
48
49
 
49
50
  export function parseInspectRunViewerCliArgs(argv) {
50
- const args = [...argv];
51
51
  const options = {
52
52
  help: false,
53
53
  repo: undefined,
@@ -60,48 +60,71 @@ export function parseInspectRunViewerCliArgs(argv) {
60
60
  restart: false,
61
61
  };
62
62
 
63
- while (args.length > 0) {
64
- const token = args.shift();
65
- if (token === "--help" || token === "-h") {
63
+ const { tokens } = parseArgs({
64
+ args: [...argv],
65
+ options: {
66
+ help: { type: "boolean", short: "h" },
67
+ repo: { type: "string" },
68
+ pr: { type: "boolean" },
69
+ host: { type: "string" },
70
+ port: { type: "string" },
71
+ "allow-non-localhost": { type: "boolean" },
72
+ restart: { type: "boolean" },
73
+ "steering-state-file": { type: "string" },
74
+ "copilot-input": { type: "string" },
75
+ "reviewer-input": { type: "string" },
76
+ },
77
+ allowPositionals: true,
78
+ strict: false,
79
+ tokens: true,
80
+ });
81
+ for (const token of tokens) {
82
+ if (token.kind === "positional") {
83
+ throw parseInspectRunViewerCliError(`Unknown argument: ${token.value}`);
84
+ }
85
+ if (token.kind !== "option") {
86
+ continue;
87
+ }
88
+ if (token.name === "help") {
66
89
  options.help = true;
67
90
  return options;
68
91
  }
69
- if (token === "--repo") {
70
- options.repo = requireOptionValue(args, "--repo", parseInspectRunViewerCliError);
92
+ if (token.name === "repo") {
93
+ options.repo = requireTokenValue(token, parseInspectRunViewerCliError);
71
94
  continue;
72
95
  }
73
- if (token === "--pr") {
96
+ if (token.name === "pr") {
74
97
  throw parseInspectRunViewerCliError("--pr is no longer supported on the CLI; choose a PR with ?pr=<number> in the viewer URL");
75
98
  }
76
- if (token === "--host") {
77
- options.host = parseHost(requireOptionValue(args, "--host", parseInspectRunViewerCliError));
99
+ if (token.name === "host") {
100
+ options.host = parseHost(requireTokenValue(token, parseInspectRunViewerCliError));
78
101
  continue;
79
102
  }
80
- if (token === "--port") {
81
- options.port = parsePort(requireOptionValue(args, "--port", parseInspectRunViewerCliError));
103
+ if (token.name === "port") {
104
+ options.port = parsePort(requireTokenValue(token, parseInspectRunViewerCliError));
82
105
  continue;
83
106
  }
84
- if (token === "--allow-non-localhost") {
107
+ if (token.name === "allow-non-localhost") {
85
108
  options.allowNonLocalhost = true;
86
109
  continue;
87
110
  }
88
- if (token === "--restart") {
111
+ if (token.name === "restart") {
89
112
  options.restart = true;
90
113
  continue;
91
114
  }
92
- if (token === "--steering-state-file") {
93
- options.steeringStateFile = requireOptionValue(args, "--steering-state-file", parseInspectRunViewerCliError);
115
+ if (token.name === "steering-state-file") {
116
+ options.steeringStateFile = requireTokenValue(token, parseInspectRunViewerCliError);
94
117
  continue;
95
118
  }
96
- if (token === "--copilot-input") {
97
- options.copilotInputPath = requireOptionValue(args, "--copilot-input", parseInspectRunViewerCliError);
119
+ if (token.name === "copilot-input") {
120
+ options.copilotInputPath = requireTokenValue(token, parseInspectRunViewerCliError);
98
121
  continue;
99
122
  }
100
- if (token === "--reviewer-input") {
101
- options.reviewerInputPath = requireOptionValue(args, "--reviewer-input", parseInspectRunViewerCliError);
123
+ if (token.name === "reviewer-input") {
124
+ options.reviewerInputPath = requireTokenValue(token, parseInspectRunViewerCliError);
102
125
  continue;
103
126
  }
104
- throw parseInspectRunViewerCliError(`Unknown argument: ${token}`);
127
+ throw parseInspectRunViewerCliError(`Unknown argument: ${token.rawName}`);
105
128
  }
106
129
 
107
130
  if (!options.help) {
@@ -2,7 +2,7 @@
2
2
  import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { parsePrNumber, requireOptionValue, runChild } from "../_cli-primitives.mjs";
5
+ import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
6
6
  import { buildParseError, formatCliError, parseJsonText, parseReviewThreads } from "../_core-helpers.mjs";
7
7
  import { fetchGithubReviewThreadsPayload } from "../github/capture-review-threads.mjs";
8
8
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
@@ -22,6 +22,7 @@ import {
22
22
  STEERING_KIND,
23
23
  } from "@dev-loops/core/loop/steering";
24
24
  import { validateSteeringStateTarget } from "./_steering-state-file.mjs";
25
+ import { parseArgs } from "node:util";
25
26
  const USAGE = `Usage: inspect-run.mjs --repo <owner/name> --pr <number>
26
27
  Read-only run inspection for the Copilot PR outer-loop family.
27
28
  Produces a single JSON snapshot describing the current state of one
@@ -72,7 +73,6 @@ Exit codes:
72
73
  1 Argument error or unexpected runtime failure`.trim();
73
74
  const parseError = buildParseError(USAGE);
74
75
  export function parseInspectRunCliArgs(argv) {
75
- const args = [...argv];
76
76
  const options = {
77
77
  help: false,
78
78
  repo: undefined,
@@ -81,33 +81,52 @@ export function parseInspectRunCliArgs(argv) {
81
81
  copilotInputPath: undefined,
82
82
  reviewerInputPath: undefined,
83
83
  };
84
- while (args.length > 0) {
85
- const token = args.shift();
86
- if (token === "--help" || token === "-h") {
84
+ const { tokens } = parseArgs({
85
+ args: [...argv],
86
+ options: {
87
+ help: { type: "boolean", short: "h" },
88
+ repo: { type: "string" },
89
+ pr: { type: "string" },
90
+ "steering-state-file": { type: "string" },
91
+ "copilot-input": { type: "string" },
92
+ "reviewer-input": { type: "string" },
93
+ },
94
+ allowPositionals: true,
95
+ strict: false,
96
+ tokens: true,
97
+ });
98
+ for (const token of tokens) {
99
+ if (token.kind === "positional") {
100
+ throw parseError(`Unknown argument: ${token.value}`);
101
+ }
102
+ if (token.kind !== "option") {
103
+ continue;
104
+ }
105
+ if (token.name === "help") {
87
106
  options.help = true;
88
107
  return options;
89
108
  }
90
- if (token === "--repo") {
91
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
109
+ if (token.name === "repo") {
110
+ options.repo = requireTokenValue(token, parseError).trim();
92
111
  continue;
93
112
  }
94
- if (token === "--pr") {
95
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
113
+ if (token.name === "pr") {
114
+ options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
96
115
  continue;
97
116
  }
98
- if (token === "--steering-state-file") {
99
- options.steeringStateFile = requireOptionValue(args, "--steering-state-file", parseError);
117
+ if (token.name === "steering-state-file") {
118
+ options.steeringStateFile = requireTokenValue(token, parseError);
100
119
  continue;
101
120
  }
102
- if (token === "--copilot-input") {
103
- options.copilotInputPath = requireOptionValue(args, "--copilot-input", parseError);
121
+ if (token.name === "copilot-input") {
122
+ options.copilotInputPath = requireTokenValue(token, parseError);
104
123
  continue;
105
124
  }
106
- if (token === "--reviewer-input") {
107
- options.reviewerInputPath = requireOptionValue(args, "--reviewer-input", parseError);
125
+ if (token.name === "reviewer-input") {
126
+ options.reviewerInputPath = requireTokenValue(token, parseError);
108
127
  continue;
109
128
  }
110
- throw parseError(`Unknown argument: ${token}`);
129
+ throw parseError(`Unknown argument: ${token.rawName}`);
111
130
  }
112
131
  if (!options.help) {
113
132
  if (options.repo === undefined || options.pr === undefined) {
@@ -2,7 +2,7 @@
2
2
  import { mkdir, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { parsePrNumber, requireOptionValue, runChild } from "../_cli-primitives.mjs";
5
+ import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
6
6
  import { buildParseError, formatCliError, parseJsonText } from "../_core-helpers.mjs";
7
7
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
8
8
  import {
@@ -25,6 +25,7 @@ import {
25
25
  resolveEffectiveAsyncStartMode,
26
26
  } from "@dev-loops/core/loop/async-start-contract";
27
27
  import { loadDevLoopConfig, resolveConductorModel, resolveAutonomyStopAt, resolveWorkflowConfig } from "@dev-loops/core/config";
28
+ import { parseArgs } from "node:util";
28
29
  const USAGE = `Usage: outer-loop.mjs --repo <owner/name> --pr <number>
29
30
  Thin outer-loop wrapper for the Copilot PR remediation loop.
30
31
  Detects current PR state from both the Copilot inner loop and the reviewer
@@ -88,7 +89,6 @@ Exit codes:
88
89
  1 Argument error, gh/git failure, or indeterminate state`.trim();
89
90
  const parseError = buildParseError(USAGE);
90
91
  export function parseOuterLoopCliArgs(argv) {
91
- const args = [...argv];
92
92
  const options = {
93
93
  help: false,
94
94
  repo: undefined,
@@ -97,33 +97,52 @@ export function parseOuterLoopCliArgs(argv) {
97
97
  copilotInputPath: undefined,
98
98
  reviewerInputPath: undefined,
99
99
  };
100
- while (args.length > 0) {
101
- const token = args.shift();
102
- if (token === "--help" || token === "-h") {
100
+ const { tokens } = parseArgs({
101
+ args: [...argv],
102
+ options: {
103
+ help: { type: "boolean", short: "h" },
104
+ repo: { type: "string" },
105
+ pr: { type: "string" },
106
+ "checkpoint-dir": { type: "string" },
107
+ "copilot-input": { type: "string" },
108
+ "reviewer-input": { type: "string" },
109
+ },
110
+ allowPositionals: true,
111
+ strict: false,
112
+ tokens: true,
113
+ });
114
+ for (const token of tokens) {
115
+ if (token.kind === "positional") {
116
+ throw parseError(`Unknown argument: ${token.value}`);
117
+ }
118
+ if (token.kind !== "option") {
119
+ continue;
120
+ }
121
+ if (token.name === "help") {
103
122
  options.help = true;
104
123
  return options;
105
124
  }
106
- if (token === "--repo") {
107
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
125
+ if (token.name === "repo") {
126
+ options.repo = requireTokenValue(token, parseError).trim();
108
127
  continue;
109
128
  }
110
- if (token === "--pr") {
111
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
129
+ if (token.name === "pr") {
130
+ options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
112
131
  continue;
113
132
  }
114
- if (token === "--checkpoint-dir") {
115
- options.checkpointDir = requireOptionValue(args, "--checkpoint-dir", parseError);
133
+ if (token.name === "checkpoint-dir") {
134
+ options.checkpointDir = requireTokenValue(token, parseError);
116
135
  continue;
117
136
  }
118
- if (token === "--copilot-input") {
119
- options.copilotInputPath = requireOptionValue(args, "--copilot-input", parseError);
137
+ if (token.name === "copilot-input") {
138
+ options.copilotInputPath = requireTokenValue(token, parseError);
120
139
  continue;
121
140
  }
122
- if (token === "--reviewer-input") {
123
- options.reviewerInputPath = requireOptionValue(args, "--reviewer-input", parseError);
141
+ if (token.name === "reviewer-input") {
142
+ options.reviewerInputPath = requireTokenValue(token, parseError);
124
143
  continue;
125
144
  }
126
- throw parseError(`Unknown argument: ${token}`);
145
+ throw parseError(`Unknown argument: ${token.rawName}`);
127
146
  }
128
147
  if (!options.help) {
129
148
  if (options.repo === undefined || options.pr === undefined) {
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import process from "node:process";
3
+ import { parseArgs } from "node:util";
3
4
  import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
4
- import { parsePrNumber, requireOptionValue } from "../_cli-primitives.mjs";
5
+ import { parsePrNumber, requireTokenValue } from "../_cli-primitives.mjs";
5
6
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
6
7
  import { resolveRunId as resolveEnvRunId } from "@dev-loops/core/loop/run-context";
7
8
  import {
@@ -42,29 +43,47 @@ function parseCliArgs(argv) {
42
43
  return options;
43
44
  }
44
45
  options.command = command;
45
- while (args.length > 0) {
46
- const token = args.shift();
47
- if (token === "--help" || token === "-h") {
46
+ const { tokens } = parseArgs({
47
+ args,
48
+ options: {
49
+ help: { type: "boolean", short: "h" },
50
+ repo: { type: "string" },
51
+ pr: { type: "string" },
52
+ "run-id": { type: "string" },
53
+ "require-existing": { type: "boolean" },
54
+ },
55
+ allowPositionals: true,
56
+ strict: false,
57
+ tokens: true,
58
+ });
59
+ for (const token of tokens) {
60
+ if (token.kind === "positional") {
61
+ throw parseError(`Unknown argument: ${token.value}`);
62
+ }
63
+ if (token.kind !== "option") {
64
+ continue;
65
+ }
66
+ if (token.name === "help") {
48
67
  options.help = true;
49
68
  return options;
50
69
  }
51
- if (token === "--repo") {
52
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
70
+ if (token.name === "repo") {
71
+ options.repo = requireTokenValue(token, parseError).trim();
53
72
  continue;
54
73
  }
55
- if (token === "--pr") {
56
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
74
+ if (token.name === "pr") {
75
+ options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
57
76
  continue;
58
77
  }
59
- if (token === "--run-id") {
60
- options.runId = requireOptionValue(args, "--run-id", parseError).trim();
78
+ if (token.name === "run-id") {
79
+ options.runId = requireTokenValue(token, parseError).trim();
61
80
  continue;
62
81
  }
63
- if (token === "--require-existing") {
82
+ if (token.name === "require-existing") {
64
83
  options.requireExisting = true;
65
84
  continue;
66
85
  }
67
- throw parseError(`Unknown argument: ${token}`);
86
+ throw parseError(`Unknown argument: ${token.rawName}`);
68
87
  }
69
88
  const validCommands = new Set(["status", "claim", "takeover", "assert", "release"]);
70
89
  if (!validCommands.has(options.command)) {
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
3
- import { requireOptionValue, runCommand } from "../_cli-primitives.mjs";
3
+ import { requireTokenValue, runCommand } from "../_cli-primitives.mjs";
4
+ import { parseArgs } from "node:util";
4
5
  import {
5
6
  isUnderWorktreePath, parseMainWorktreePath, isMainCheckout,
6
7
  } from "@dev-loops/core/loop/worktree-guard";
@@ -13,15 +14,31 @@ Verify the current git branch identity and/or worktree isolation before local co
13
14
  const parseError = buildParseError(USAGE);
14
15
 
15
16
  export function parseBranchGuardCliArgs(argv) {
16
- const args = [...argv];
17
17
  const options = { help: false, expectedBranch: undefined, requireWorktree: false, blockMainCheckout: false };
18
- while (args.length > 0) {
19
- const token = args.shift();
20
- if (token === "--help" || token === "-h") { options.help = true; return options; }
21
- if (token === "--expected-branch") { options.expectedBranch = requireOptionValue(args, "--expected-branch", parseError, { flagPattern: /^-/u }); continue; }
22
- if (token === "--require-worktree") { options.requireWorktree = true; continue; }
23
- if (token === "--block-main-checkout") { options.blockMainCheckout = true; continue; }
24
- throw parseError(`Unknown argument: ${token}`);
18
+ const { tokens } = parseArgs({
19
+ args: [...argv],
20
+ options: {
21
+ help: { type: "boolean", short: "h" },
22
+ "expected-branch": { type: "string" },
23
+ "require-worktree": { type: "boolean" },
24
+ "block-main-checkout": { type: "boolean" },
25
+ },
26
+ allowPositionals: true,
27
+ strict: false,
28
+ tokens: true,
29
+ });
30
+ for (const token of tokens) {
31
+ if (token.kind === "positional") {
32
+ throw parseError(`Unknown argument: ${token.value}`);
33
+ }
34
+ if (token.kind !== "option") {
35
+ continue;
36
+ }
37
+ if (token.name === "help") { options.help = true; return options; }
38
+ if (token.name === "expected-branch") { options.expectedBranch = requireTokenValue(token, parseError, { flagPattern: /^-/u }); continue; }
39
+ if (token.name === "require-worktree") { options.requireWorktree = true; continue; }
40
+ if (token.name === "block-main-checkout") { options.blockMainCheckout = true; continue; }
41
+ throw parseError(`Unknown argument: ${token.rawName}`);
25
42
  }
26
43
  if (options.expectedBranch === undefined) { throw parseError("--expected-branch <name> is required"); }
27
44
  return options;