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,6 @@
1
1
  {
2
2
  "name": "dev-loops",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Agent-harness-agnostic dev-loop: agents, skills, and hooks for GitHub/Copilot-driven development loops.",
5
5
  "author": {
6
6
  "name": "Manuel Fittko",
@@ -21,7 +21,8 @@ The envelope is the primary handoff artifact — it is derived from resolver out
21
21
  - `acceptance` — self-validation criteria for declaring completion
22
22
 
23
23
  **Construction sequence:**
24
- 1. Run the deterministic startup resolver to produce the authoritative state bundle: `npx dev-loops loop startup --issue <n>` for issues, or `npx dev-loops loop startup --pr <n>` for PRs.
24
+
25
+ 1. Run the deterministic startup resolver to produce the authoritative state bundle: `npx dev-loops@0.2.7 loop startup --issue <n>` for issues, or `npx dev-loops@0.2.7 loop startup --pr <n>` for PRs.
25
26
  2. Pass the resolver output, resolved settings (merged from `.devloops` and `.pi/dev-loop/defaults.yaml`), and current gate state to `buildDevLoopHandoffEnvelope()`.
26
27
  3. **Validate the envelope** with `validateHandoffEnvelope()` before consuming any field. If validation returns `ok: false`, reject the handoff with the structured error — do not load requiredReads, do not execute nextAction, do not delegate.
27
28
  4. Read the envelope as the first artifact.
@@ -25,7 +25,7 @@ Required installed runtime contract docs are shared bundled copies under `../doc
25
25
 
26
26
  > Under the Claude Code harness the dev-loop runs as a single agent: run these steps directly — no read-only boundary and no separate async-subagent dispatch. See [Main Agent Contract](../docs/main-agent-contract.md).
27
27
 
28
- Resolve authoritative state via the startup resolver (`npx dev-loops loop startup --issue <n>` for issues, `npx dev-loops loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `npx dev-loops loop build-envelope --input <resolver-output.json>`. The envelope determines `requiredReads`, `nextAction`, `stopRules`, and `acceptance` — load only those files, execute only that bounded task. It is the first handoff artifact consumed before loading any route pack. See [Workflow Handoff Contract](../docs/workflow-handoff-contract.md) for the derivation contract.
28
+ Resolve authoritative state via the startup resolver (`npx dev-loops@0.2.7 loop startup --issue <n>` for issues, `npx dev-loops@0.2.7 loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `npx dev-loops@0.2.7 loop build-envelope --input <resolver-output.json>`. The envelope determines `requiredReads`, `nextAction`, `stopRules`, and `acceptance` — load only those files, execute only that bounded task. It is the first handoff artifact consumed before loading any route pack. See [Workflow Handoff Contract](../docs/workflow-handoff-contract.md) for the derivation contract.
29
29
 
30
30
  **Retrospective checkpoint gate:** the resolver reads `.pi/dev-loop-retrospective-checkpoint.json` and injects the state. When the checkpoint is `missing` and the repo config `workflow.requireRetrospective` (set via `.devloops` at repo root) is `true`, the resolver returns `needs_reconcile`. Complete or explicitly skip the retrospective before starting.
31
31
 
@@ -99,10 +99,10 @@ When `@dev-loops/core` is available again, switch back to the full helper. The f
99
99
 
100
100
  ## Read-only info shortcut
101
101
 
102
- Info/handoff requests can be served directly via `npx dev-loops loop info` (read-only; no full dev-loop run required):
103
- - `npx dev-loops loop info --issue <n>` — human-readable issue state summary (strategy, route, linked PR, next action)
104
- - `npx dev-loops loop info --pr <n>` — human-readable PR state summary (branch, CI, threads, rounds, action)
105
- - `npx dev-loops loop info --issue <n> --json` — machine-readable JSON output
102
+ Info/handoff requests can be served directly via `npx dev-loops@0.2.7 loop info` (read-only; no full dev-loop run required):
103
+ - `npx dev-loops@0.2.7 loop info --issue <n>` — human-readable issue state summary (strategy, route, linked PR, next action)
104
+ - `npx dev-loops@0.2.7 loop info --pr <n>` — human-readable PR state summary (branch, CI, threads, rounds, action)
105
+ - `npx dev-loops@0.2.7 loop info --issue <n> --json` — machine-readable JSON output
106
106
 
107
107
  ## Guard rules
108
108
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.2.7
6
+
7
+ ### Fixed
8
+
9
+ - **Deterministic, harness-aware dev-loops CLI invocation** (#801, #833). Pi runtime skills/agents now invoke the package-local `node <dev-loops-package-root>/cli/index.mjs`; the generated Claude tree pins `npx dev-loops@<version>` (version injected at generation time) so the plugin and CLI no longer drift.
10
+ - **Round-cap Copilot-gate deadlock resolved** (#848). At the round cap with clean threads + green CI, the loop routes to a clean fallback instead of dead-ending at `waiting_for_copilot_review` when a lingering reviewer assignment / post-cap push leaves the head unreviewed. The pre-approval gate still reviews any post-cap head.
11
+ - **Draft-gate ordering after external un-draft** (#836). Verified + regression-guarded: a non-draft PR without clean `draft_gate` evidence is routed to `reconcile_draft_gate` and cannot merge; the relayed-authorization deadlock is moot under the single-agent Claude harness.
12
+
13
+ ### Added
14
+
15
+ - **Projects board reorder + Done-cleanup CLI** (#789). `project reorder move-to-top|move-after|order` (with `--dry-run`, diff-friendly output, cross-column fail-closed) and `project archive-done [--older-than]`.
16
+ - **Loop-state-driven board status sync** (#793). Board Status column is derived from the loop state via a pure, config-driven mapping (`queue.statusColumns` / `queue.stateColumnMap`), opt-in, fail-open, reverse-safe.
17
+
18
+ ### Changed
19
+
20
+ - **Arg parsing migrated to `node:util.parseArgs`** (#808). All hand-rolled `while/shift` parsers (49 scripts/modules + 3 core files) now use `parseArgs` via shared adapters, with CLI contracts preserved. (Index-based parsers tracked in #857.)
21
+
5
22
  ## 0.2.6
6
23
 
7
24
  ### Fixed
@@ -25,7 +25,11 @@ The envelope is the primary handoff artifact — it is derived from resolver out
25
25
  - `acceptance` — self-validation criteria for declaring completion
26
26
 
27
27
  **Construction sequence:**
28
- 1. Run the deterministic startup resolver to produce the authoritative state bundle: `npx dev-loops loop startup --issue <n>` for issues, or `npx dev-loops loop startup --pr <n>` for PRs.
28
+ <!-- pi-only -->
29
+ **CLI invocation (`<dev-loops-package-root>`):** dev-loop CLI commands are invoked as `node <dev-loops-package-root>/cli/index.mjs <verb...>` using the package-local CLI rather than `npx`, so they resolve unambiguously from the installed package without a global install. Resolve `<dev-loops-package-root>` from this agent's own installed path: this agent is installed at `<package-root>/.pi/agents/dev-loop.agent.md`, so the package root is `../..` from this agent's directory (`agents` → `.pi` → package root). (The `dev-loop` skill resolves it analogously from its own installed path.)
30
+ <!-- /pi-only -->
31
+
32
+ 1. Run the deterministic startup resolver to produce the authoritative state bundle: `node <dev-loops-package-root>/cli/index.mjs loop startup --issue <n>` for issues, or `node <dev-loops-package-root>/cli/index.mjs loop startup --pr <n>` for PRs.
29
33
  2. Pass the resolver output, resolved settings (merged from `.devloops` and `.pi/dev-loop/defaults.yaml`), and current gate state to `buildDevLoopHandoffEnvelope()`.
30
34
  3. **Validate the envelope** with `validateHandoffEnvelope()` before consuming any field. If validation returns `ok: false`, reject the handoff with the structured error — do not load requiredReads, do not execute nextAction, do not delegate.
31
35
  4. Read the envelope as the first artifact.
package/cli/index.mjs CHANGED
@@ -52,6 +52,7 @@ const SUBCOMMAND_ROUTES = {
52
52
  add: "scripts/projects/add-queue-item.mjs",
53
53
  move: "scripts/projects/move-queue-item.mjs",
54
54
  reorder: "scripts/projects/reorder-queue-item.mjs",
55
+ "archive-done": "scripts/projects/archive-done-items.mjs",
55
56
  ensure: "scripts/projects/ensure-queue-board.mjs",
56
57
  },
57
58
  queue: {
@@ -113,7 +114,8 @@ const SUBCOMMAND_DESCRIPTIONS = {
113
114
  list: "List queue board items",
114
115
  add: "Add issue/PR to queue board",
115
116
  move: "Move queue item between Status columns",
116
- reorder: "Reorder queue board items",
117
+ reorder: "Reorder items (move-to-top/move-after/order, --dry-run)",
118
+ "archive-done": "Archive closed Done items older than a duration",
117
119
  ensure: "Create/repair queue board bootstrap surface",
118
120
  },
119
121
  queue: {
package/package.json CHANGED
@@ -68,7 +68,7 @@
68
68
  },
69
69
  "dependencies": {
70
70
  "mermaid": "11.15.0",
71
- "@dev-loops/core": "^0.2.6"
71
+ "@dev-loops/core": "^0.2.7"
72
72
  },
73
73
  "repository": {
74
74
  "type": "git",
@@ -78,7 +78,7 @@
78
78
  "url": "https://github.com/mfittko/dev-loops/issues"
79
79
  },
80
80
  "homepage": "https://github.com/mfittko/dev-loops#readme",
81
- "version": "0.2.6",
81
+ "version": "0.2.7",
82
82
  "files": [
83
83
  "cli/",
84
84
  "lib/",
@@ -1,6 +1,8 @@
1
1
  // Re-export from shared library (Phase 2, issue #548)
2
2
  export {
3
+ parseCliTokens,
3
4
  requireOptionValue,
5
+ requireTokenValue,
4
6
  parsePositiveInteger,
5
7
  parseNonNegativeInteger,
6
8
  parsePrNumber,
@@ -24,6 +24,16 @@ import { transformAgent, transformSkill, stripPiOnlyBlocks } from "@dev-loops/co
24
24
  export function collectGeneratedAssets({ repoRoot = process.cwd() } = {}) {
25
25
  const assets = [];
26
26
 
27
+ // The dev-loops package version pins the Claude `npx dev-loops@<version>` CLI invocation so the
28
+ // generated plugin tree cannot drift against the published version (#833). Read it once here.
29
+ // Falls back to `latest` when no repo-root package.json is present (e.g. a consumer/fixture tree
30
+ // that mirrors only agents/skills).
31
+ let version = "latest";
32
+ const pkgPath = path.join(repoRoot, "package.json");
33
+ if (fs.existsSync(pkgPath)) {
34
+ version = JSON.parse(fs.readFileSync(pkgPath, "utf8")).version ?? "latest";
35
+ }
36
+
27
37
  const agentsDir = path.join(repoRoot, "agents");
28
38
  if (fs.existsSync(agentsDir)) {
29
39
  for (const entry of fs.readdirSync(agentsDir).sort()) {
@@ -31,7 +41,7 @@ export function collectGeneratedAssets({ repoRoot = process.cwd() } = {}) {
31
41
  const source = `agents/${entry}`;
32
42
  const raw = fs.readFileSync(path.join(repoRoot, source), "utf8");
33
43
  const base = entry.slice(0, -".agent.md".length);
34
- assets.push({ target: `.claude/agents/${base}.md`, content: transformAgent({ source, raw }) });
44
+ assets.push({ target: `.claude/agents/${base}.md`, content: transformAgent({ source, raw, version }) });
35
45
  }
36
46
  }
37
47
 
@@ -43,7 +53,7 @@ export function collectGeneratedAssets({ repoRoot = process.cwd() } = {}) {
43
53
  const abs = path.join(repoRoot, source);
44
54
  if (!fs.existsSync(abs)) continue; // e.g. skills/docs/ holds shared docs, not a SKILL.md
45
55
  const raw = fs.readFileSync(abs, "utf8");
46
- assets.push({ target: `.claude/skills/${entry.name}/SKILL.md`, content: transformSkill({ source, raw }) });
56
+ assets.push({ target: `.claude/skills/${entry.name}/SKILL.md`, content: transformSkill({ source, raw, version }) });
47
57
  }
48
58
  }
49
59
 
@@ -2,9 +2,10 @@
2
2
  import { lstat, readdir, readFile, stat } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { parseArgs } from "node:util";
5
6
 
6
7
  import { isDirectCliRun } from "../_core-helpers.mjs";
7
- import { requireOptionValue } from "../_cli-primitives.mjs";
8
+ import { requireTokenValue } from "../_cli-primitives.mjs";
8
9
 
9
10
  const DEFAULT_SCAN_PATHS = Object.freeze([
10
11
  "README.md",
@@ -52,26 +53,34 @@ function parseError(message) {
52
53
  }
53
54
 
54
55
  export function parseValidateLinksCliArgs(argv) {
55
- const args = [...argv];
56
+ const { tokens } = parseArgs({
57
+ args: [...argv],
58
+ options: { help: { type: "boolean", short: "h" }, root: { type: "string" } },
59
+ allowPositionals: true,
60
+ strict: false,
61
+ tokens: true,
62
+ });
56
63
  const options = {
57
64
  help: false,
58
65
  repoRoot: resolveDefaultRepoRoot(),
59
66
  };
60
67
 
61
- while (args.length > 0) {
62
- const token = args.shift();
63
-
64
- if (token === "--help" || token === "-h") {
68
+ for (const token of tokens) {
69
+ if (token.kind === "positional") {
70
+ throw parseError(`Unknown argument: ${token.value}`);
71
+ }
72
+ if (token.kind !== "option") {
73
+ continue;
74
+ }
75
+ if (token.name === "help") {
65
76
  options.help = true;
66
77
  return options;
67
78
  }
68
-
69
- if (token === "--root") {
70
- options.repoRoot = path.resolve(requireOptionValue(args, "--root"));
79
+ if (token.name === "root") {
80
+ options.repoRoot = path.resolve(requireTokenValue(token));
71
81
  continue;
72
82
  }
73
-
74
- throw parseError(`Unknown argument: ${token}`);
83
+ throw parseError(`Unknown argument: ${token.rawName}`);
75
84
  }
76
85
 
77
86
  return options;
@@ -8,7 +8,8 @@ import {
8
8
  parseReviewThreads,
9
9
  readInput,
10
10
  } from "../_core-helpers.mjs";
11
- import { parsePrNumber, requireOptionValue, runChild } from "../_cli-primitives.mjs";
11
+ import { parseArgs } from "node:util";
12
+ import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
12
13
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
13
14
  export const REVIEW_THREADS_QUERY = [
14
15
  "query($owner: String!, $name: String!, $pr: Int!) {",
@@ -49,7 +50,19 @@ Exit codes:
49
50
  1 Error
50
51
  `;
51
52
  export function parseCaptureCliArgs(argv) {
52
- const args = [...argv];
53
+ const { tokens } = parseArgs({
54
+ args: [...argv],
55
+ options: {
56
+ help: { type: "boolean", short: "h" },
57
+ input: { type: "string" },
58
+ output: { type: "string" },
59
+ repo: { type: "string" },
60
+ pr: { type: "string" },
61
+ },
62
+ allowPositionals: true,
63
+ strict: false,
64
+ tokens: true,
65
+ });
53
66
  const options = {
54
67
  inputPath: undefined,
55
68
  outputPath: undefined,
@@ -57,29 +70,34 @@ export function parseCaptureCliArgs(argv) {
57
70
  pr: undefined,
58
71
  help: false,
59
72
  };
60
- while (args.length > 0) {
61
- const token = args.shift();
62
- if (token === "--help" || token === "-h") {
73
+ for (const token of tokens) {
74
+ if (token.kind === "positional") {
75
+ throw new Error(`Unknown argument: ${token.value}`);
76
+ }
77
+ if (token.kind !== "option") {
78
+ continue;
79
+ }
80
+ if (token.name === "help") {
63
81
  options.help = true;
64
82
  return options;
65
83
  }
66
- if (token === "--input") {
67
- options.inputPath = requireOptionValue(args, "--input");
84
+ if (token.name === "input") {
85
+ options.inputPath = requireTokenValue(token);
68
86
  continue;
69
87
  }
70
- if (token === "--output") {
71
- options.outputPath = requireOptionValue(args, "--output");
88
+ if (token.name === "output") {
89
+ options.outputPath = requireTokenValue(token);
72
90
  continue;
73
91
  }
74
- if (token === "--repo") {
75
- options.repo = requireOptionValue(args, "--repo");
92
+ if (token.name === "repo") {
93
+ options.repo = requireTokenValue(token);
76
94
  continue;
77
95
  }
78
- if (token === "--pr") {
79
- options.pr = parsePrNumber(requireOptionValue(args, "--pr"));
96
+ if (token.name === "pr") {
97
+ options.pr = parsePrNumber(requireTokenValue(token));
80
98
  continue;
81
99
  }
82
- throw new Error(`Unknown argument: ${token}`);
100
+ throw new Error(`Unknown argument: ${token.rawName}`);
83
101
  }
84
102
  const hasLiveArgs = options.repo !== undefined || options.pr !== undefined;
85
103
  const hasCompleteLiveArgs = options.repo !== undefined && options.pr !== undefined;
@@ -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,
@@ -8,7 +9,7 @@ import {
8
9
  summarizeGateReviewCommentMarkers,
9
10
  summarizeGateReviewComments,
10
11
  } from "../_core-helpers.mjs";
11
- import { parsePrNumber, requireOptionValue, runChild } from "../_cli-primitives.mjs";
12
+ import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
12
13
  import { fetchGithubReviewThreadsPayload } from "./capture-review-threads.mjs";
13
14
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
14
15
  import { ensureAsyncRunnerOwnership } from "../loop/_pr-runner-coordination.mjs";
@@ -84,30 +85,46 @@ Exit codes:
84
85
  1 Argument error, gh failure, malformed gh JSON, or missing required pre-merge gate evidence.`.trim();
85
86
  const parseError = buildParseError(USAGE);
86
87
  export function parseDetectCheckpointEvidenceCliArgs(argv) {
87
- const args = [...argv];
88
+ const { tokens } = parseArgs({
89
+ args: [...argv],
90
+ options: {
91
+ help: { type: "boolean", short: "h" },
92
+ repo: { type: "string" },
93
+ pr: { type: "string" },
94
+ "require-before-merge": { type: "boolean" },
95
+ },
96
+ allowPositionals: true,
97
+ strict: false,
98
+ tokens: true,
99
+ });
88
100
  const options = {
89
101
  help: false,
90
102
  repo: undefined,
91
103
  pr: undefined,
92
104
  };
93
- while (args.length > 0) {
94
- const token = args.shift();
95
- if (token === "--help" || token === "-h") {
105
+ for (const token of tokens) {
106
+ if (token.kind === "positional") {
107
+ throw parseError(`Unknown argument: ${token.value}`);
108
+ }
109
+ if (token.kind !== "option") {
110
+ continue;
111
+ }
112
+ if (token.name === "help") {
96
113
  options.help = true;
97
114
  return options;
98
115
  }
99
- if (token === "--repo") {
100
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
116
+ if (token.name === "repo") {
117
+ options.repo = requireTokenValue(token, parseError).trim();
101
118
  continue;
102
119
  }
103
- if (token === "--pr") {
104
- options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
120
+ if (token.name === "pr") {
121
+ options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
105
122
  continue;
106
123
  }
107
- if (token === "--require-before-merge") {
124
+ if (token.name === "require-before-merge") {
108
125
  throw parseError(`--require-before-merge has been removed: gate evidence enforcement is now always-on by default. Omit the flag.`);
109
126
  }
110
- throw parseError(`Unknown argument: ${token}`);
127
+ throw parseError(`Unknown argument: ${token.rawName}`);
111
128
  }
112
129
  if (options.repo === undefined || options.pr === undefined) {
113
130
  throw parseError("detect-checkpoint-evidence 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
  export const LINKED_ISSUE_PR_QUERY = [
6
7
  "query($owner:String!, $name:String!, $issue:Int!, $after:String) {",
@@ -75,27 +76,38 @@ Error output (stderr, JSON):
75
76
  { "ok": false, "error": "..." }`.trim();
76
77
  const parseError = buildParseError(USAGE);
77
78
  export function parseDetectLinkedIssuePrCliArgs(argv) {
78
- const args = [...argv];
79
+ const { tokens } = parseArgs({
80
+ args: [...argv],
81
+ options: { help: { type: "boolean", short: "h" }, repo: { type: "string" }, issue: { type: "string" } },
82
+ allowPositionals: true,
83
+ strict: false,
84
+ tokens: true,
85
+ });
79
86
  const options = {
80
87
  help: false,
81
88
  repo: undefined,
82
89
  issue: undefined,
83
90
  };
84
- while (args.length > 0) {
85
- const token = args.shift();
86
- if (token === "--help" || token === "-h") {
91
+ for (const token of tokens) {
92
+ if (token.kind === "positional") {
93
+ throw parseError(`Unknown argument: ${token.value}`);
94
+ }
95
+ if (token.kind !== "option") {
96
+ continue;
97
+ }
98
+ if (token.name === "help") {
87
99
  options.help = true;
88
100
  return options;
89
101
  }
90
- if (token === "--repo") {
91
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
102
+ if (token.name === "repo") {
103
+ options.repo = requireTokenValue(token, parseError).trim();
92
104
  continue;
93
105
  }
94
- if (token === "--issue") {
95
- options.issue = parseIssueNumber(requireOptionValue(args, "--issue", parseError), parseError);
106
+ if (token.name === "issue") {
107
+ options.issue = parseIssueNumber(requireTokenValue(token, parseError), parseError);
96
108
  continue;
97
109
  }
98
- throw parseError(`Unknown argument: ${token}`);
110
+ throw parseError(`Unknown argument: ${token.rawName}`);
99
111
  }
100
112
  if (options.repo === undefined || options.issue === undefined) {
101
113
  throw parseError("Linked PR detection requires both --repo <owner/name> and --issue <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 { parsePositiveInteger, requireOptionValue, runChild } from "../_cli-primitives.mjs";
4
+ import { parsePositiveInteger, requireTokenValue, runChild } from "../_cli-primitives.mjs";
4
5
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
5
6
  const USAGE = `Usage: manage-sub-issues.mjs <command> --repo <owner/name> --issue <number> [options]
6
7
  Deterministic helper for reading, linking, ordering, and verifying GitHub sub-issue trees.
@@ -76,37 +77,57 @@ export function parseManageSubIssuesCliArgs(argv) {
76
77
  expected: undefined,
77
78
  ordered: false,
78
79
  };
79
- while (args.length > 0) {
80
- const token = args.shift();
81
- if (token === "--help" || token === "-h") {
80
+ const { tokens } = parseArgs({
81
+ args,
82
+ options: {
83
+ help: { type: "boolean", short: "h" },
84
+ repo: { type: "string" },
85
+ issue: { type: "string" },
86
+ child: { type: "string" },
87
+ order: { type: "string" },
88
+ expected: { type: "string" },
89
+ ordered: { type: "boolean" },
90
+ },
91
+ allowPositionals: true,
92
+ strict: false,
93
+ tokens: true,
94
+ });
95
+ for (const token of tokens) {
96
+ if (token.kind === "positional") {
97
+ throw parseError(`Unknown argument: ${token.value}`);
98
+ }
99
+ if (token.kind !== "option") {
100
+ continue;
101
+ }
102
+ if (token.name === "help") {
82
103
  options.help = true;
83
104
  return options;
84
105
  }
85
- if (token === "--repo") {
86
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
106
+ if (token.name === "repo") {
107
+ options.repo = requireTokenValue(token, parseError).trim();
87
108
  continue;
88
109
  }
89
- if (token === "--issue") {
90
- options.issue = parsePositiveInteger(requireOptionValue(args, "--issue", parseError), "Issue number", parseError);
110
+ if (token.name === "issue") {
111
+ options.issue = parsePositiveInteger(requireTokenValue(token, parseError), "Issue number", parseError);
91
112
  continue;
92
113
  }
93
- if (token === "--child") {
94
- options.child = parsePositiveInteger(requireOptionValue(args, "--child", parseError), "Issue number", parseError);
114
+ if (token.name === "child") {
115
+ options.child = parsePositiveInteger(requireTokenValue(token, parseError), "Issue number", parseError);
95
116
  continue;
96
117
  }
97
- if (token === "--order") {
98
- options.order = parseIssueList(requireOptionValue(args, "--order", parseError));
118
+ if (token.name === "order") {
119
+ options.order = parseIssueList(requireTokenValue(token, parseError));
99
120
  continue;
100
121
  }
101
- if (token === "--expected") {
102
- options.expected = parseIssueList(requireOptionValue(args, "--expected", parseError));
122
+ if (token.name === "expected") {
123
+ options.expected = parseIssueList(requireTokenValue(token, parseError));
103
124
  continue;
104
125
  }
105
- if (token === "--ordered") {
126
+ if (token.name === "ordered") {
106
127
  options.ordered = true;
107
128
  continue;
108
129
  }
109
- throw parseError(`Unknown argument: ${token}`);
130
+ throw parseError(`Unknown argument: ${token.rawName}`);
110
131
  }
111
132
  if (options.repo === undefined || options.issue === undefined) {
112
133
  throw parseError("Both --repo <owner/name> and --issue <number> are required");
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { setTimeout as delay } from "node:timers/promises";
3
3
  import { buildParseError, formatCliError, isCopilotLogin, isDirectCliRun, parseJsonText, parseReviewThreads } from "../_core-helpers.mjs";
4
- import { parsePositiveInteger, requireOptionValue, runChild } from "../_cli-primitives.mjs";
4
+ import { parseArgs } from "node:util";
5
+ import { parsePositiveInteger, requireTokenValue, runChild } from "../_cli-primitives.mjs";
5
6
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
6
7
  import {
7
8
  DEFAULT_POLL_INTERVAL_MS,
@@ -88,7 +89,13 @@ function rejectRemovedFlag(token) {
88
89
  );
89
90
  }
90
91
  export function parseWatchCliArgs(argv) {
91
- const args = [...argv];
92
+ const { tokens } = parseArgs({
93
+ args: [...argv],
94
+ options: { help: { type: "boolean", short: "h" }, repo: { type: "string" }, pr: { type: "string" } },
95
+ allowPositionals: true,
96
+ strict: false,
97
+ tokens: true,
98
+ });
92
99
  const options = {
93
100
  help: false,
94
101
  repo: undefined,
@@ -96,24 +103,29 @@ export function parseWatchCliArgs(argv) {
96
103
  pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,
97
104
  timeoutMs: COPILOT_REVIEW_WAIT_TIMEOUT_MS,
98
105
  };
99
- while (args.length > 0) {
100
- const token = args.shift();
101
- if (token === "--help" || token === "-h") {
106
+ for (const token of tokens) {
107
+ if (token.kind === "positional") {
108
+ throw parseError(`Unknown argument: ${token.value}`);
109
+ }
110
+ if (token.kind !== "option") {
111
+ continue;
112
+ }
113
+ if (token.name === "help") {
102
114
  options.help = true;
103
115
  return options;
104
116
  }
105
- if (REMOVED_FLAGS.has(token)) {
106
- rejectRemovedFlag(token);
117
+ if (REMOVED_FLAGS.has(token.rawName)) {
118
+ rejectRemovedFlag(token.rawName);
107
119
  }
108
- if (token === "--repo") {
109
- options.repo = requireOptionValue(args, "--repo", parseError).trim();
120
+ if (token.name === "repo") {
121
+ options.repo = requireTokenValue(token, parseError).trim();
110
122
  continue;
111
123
  }
112
- if (token === "--pr") {
113
- options.pr = parsePositiveInteger(requireOptionValue(args, "--pr", parseError), "--pr", parseError);
124
+ if (token.name === "pr") {
125
+ options.pr = parsePositiveInteger(requireTokenValue(token, parseError), "--pr", parseError);
114
126
  continue;
115
127
  }
116
- throw parseError(`Unknown argument: ${token}`);
128
+ throw parseError(`Unknown argument: ${token.rawName}`);
117
129
  }
118
130
  if (options.repo === undefined || options.pr === undefined) {
119
131
  throw parseError("Watching 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, summarizeGateReviewComments, summarizeGateReviewCommentMarkers } 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 { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
5
6
  import { loadDevLoopConfig, resolveGateConfig } from "@dev-loops/core/config";
6
7
  import { findBlockingTitleMarkers } from "@dev-loops/core/loop/pr-title-markers";
@@ -10,13 +11,21 @@ const parseError = buildParseError(USAGE);
10
11
  const PR_VIEW_QUERY = `query($owner:String!, $name:String!, $number:Int!) { repository(owner:$owner, name:$name) { pullRequest(number:$number) { id, isDraft, headRefOid, state, mergeStateStatus, title } } }`;
11
12
 
12
13
  export function parseReadyForReviewCliArgs(argv) {
13
- const args = [...argv], opts = { help: false, repo: undefined, pr: undefined };
14
- while (args.length > 0) {
15
- const token = args.shift();
16
- if (token === "--help" || token === "-h") { opts.help = true; return opts; }
17
- if (token === "--repo") { opts.repo = requireOptionValue(args, "--repo", parseError).trim(); continue; }
18
- if (token === "--pr") { opts.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError); continue; }
19
- throw parseError(`Unknown argument: ${token}`);
14
+ const { tokens } = parseArgs({
15
+ args: [...argv],
16
+ options: { help: { type: "boolean", short: "h" }, repo: { type: "string" }, pr: { type: "string" } },
17
+ allowPositionals: true,
18
+ strict: false,
19
+ tokens: true,
20
+ });
21
+ const opts = { help: false, repo: undefined, pr: undefined };
22
+ for (const token of tokens) {
23
+ if (token.kind === "positional") throw parseError(`Unknown argument: ${token.value}`);
24
+ if (token.kind !== "option") continue;
25
+ if (token.name === "help") { opts.help = true; return opts; }
26
+ if (token.name === "repo") { opts.repo = requireTokenValue(token, parseError).trim(); continue; }
27
+ if (token.name === "pr") { opts.pr = parsePrNumber(requireTokenValue(token, parseError), parseError); continue; }
28
+ throw parseError(`Unknown argument: ${token.rawName}`);
20
29
  }
21
30
  if (!opts.repo || opts.pr === undefined) throw parseError("ready-for-review requires --repo and --pr");
22
31
  parseRepoSlug(opts.repo);