dev-loops 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/agents/dev-loop.md +2 -1
- package/.claude/agents/developer.md +1 -0
- package/.claude/agents/fixer.md +1 -0
- package/.claude/agents/review.md +30 -0
- package/.claude/skills/copilot-pr-followup/SKILL.md +57 -3
- package/.claude/skills/dev-loop/SKILL.md +5 -5
- package/.claude/skills/docs/anti-patterns.md +2 -0
- package/.claude/skills/docs/copilot-loop-operations.md +2 -2
- package/.claude/skills/local-implementation/SKILL.md +17 -3
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +60 -0
- package/agents/dev-loop.agent.md +5 -1
- package/agents/developer.agent.md +1 -0
- package/agents/fixer.agent.md +1 -0
- package/agents/review.agent.md +30 -0
- package/cli/index.mjs +42 -7
- package/package.json +2 -2
- package/scripts/README.md +6 -5
- package/scripts/_cli-primitives.mjs +2 -0
- package/scripts/_core-helpers.mjs +1 -0
- package/scripts/claude/generate-claude-assets.mjs +12 -2
- package/scripts/claude/headless-dev-loop.mjs +53 -13
- package/scripts/claude/headless-info-smoke.mjs +45 -11
- package/scripts/docs/validate-links.mjs +20 -11
- package/scripts/github/build-adjacent-bundle.mjs +448 -0
- package/scripts/github/capture-review-threads.mjs +32 -14
- package/scripts/github/{create-draft-pr.mjs → create-pr.mjs} +28 -12
- package/scripts/github/detect-checkpoint-evidence.mjs +123 -15
- package/scripts/github/detect-linked-issue-pr.mjs +22 -10
- package/scripts/github/manage-sub-issues.mjs +37 -16
- package/scripts/github/post-gate-findings.mjs +392 -0
- package/scripts/github/probe-copilot-review.mjs +24 -12
- package/scripts/github/ready-for-review.mjs +17 -8
- package/scripts/github/reconcile-draft-gate.mjs +24 -12
- package/scripts/github/reply-resolve-review-threads.mjs +34 -15
- package/scripts/github/request-copilot-review.mjs +97 -19
- package/scripts/github/resolve-tracker-local-spec.mjs +29 -12
- package/scripts/github/stage-reviewer-draft.mjs +32 -14
- package/scripts/github/upsert-checkpoint-verdict.mjs +646 -41
- package/scripts/github/verify-fresh-review-context.mjs +12 -1
- package/scripts/github/write-gate-context.mjs +634 -0
- package/scripts/github/write-gate-findings-log.mjs +42 -21
- package/scripts/loop/build-handoff-envelope.mjs +32 -14
- package/scripts/loop/conductor-monitor.mjs +25 -9
- package/scripts/loop/conductor.mjs +31 -12
- package/scripts/loop/copilot-pr-handoff.mjs +31 -14
- package/scripts/loop/debt-remediate.mjs +28 -11
- package/scripts/loop/detect-change-scope.mjs +36 -11
- package/scripts/loop/detect-copilot-loop-state.mjs +29 -12
- package/scripts/loop/detect-copilot-session-activity.mjs +29 -12
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +26 -10
- package/scripts/loop/detect-internal-only-pr.mjs +31 -13
- package/scripts/loop/detect-issue-refinement-artifact.mjs +29 -12
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +56 -28
- package/scripts/loop/detect-reviewer-loop-state.mjs +35 -16
- package/scripts/loop/detect-stale-runner.mjs +32 -14
- package/scripts/loop/detect-tracker-first-loop-state.mjs +38 -11
- package/scripts/loop/detect-tracker-pr-state.mjs +23 -8
- package/scripts/loop/info.mjs +28 -10
- package/scripts/loop/inspect-run-viewer/cli.mjs +44 -21
- package/scripts/loop/inspect-run.mjs +35 -16
- package/scripts/loop/outer-loop.mjs +35 -16
- package/scripts/loop/pr-runner-coordination.mjs +31 -12
- package/scripts/loop/pre-commit-branch-guard.mjs +26 -9
- package/scripts/loop/pre-flight-gate.mjs +25 -9
- package/scripts/loop/pre-pr-ready-gate.mjs +24 -8
- package/scripts/loop/pre-push-main-guard.mjs +19 -5
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +23 -7
- package/scripts/loop/resolve-dev-loop-startup.mjs +29 -12
- package/scripts/loop/run-conductor-cycle.mjs +23 -8
- package/scripts/loop/run-queue.mjs +87 -15
- package/scripts/loop/run-refinement-audit.mjs +44 -22
- package/scripts/loop/run-watch-cycle.mjs +28 -12
- package/scripts/loop/steer-loop.mjs +122 -62
- package/scripts/loop/watch-initial-copilot-pr.mjs +28 -12
- package/scripts/projects/add-queue-item.mjs +60 -43
- package/scripts/projects/archive-done-items.mjs +506 -0
- package/scripts/projects/ensure-queue-board.mjs +65 -64
- package/scripts/projects/list-queue-items.mjs +57 -56
- package/scripts/projects/move-queue-item.mjs +123 -124
- package/scripts/projects/reorder-queue-item.mjs +384 -108
- package/scripts/projects/sync-item-status.mjs +198 -0
- package/scripts/refine/_refine-helpers.mjs +21 -9
- package/scripts/refine/verify.mjs +31 -13
- package/skills/copilot-pr-followup/SKILL.md +57 -3
- package/skills/dev-loop/SKILL.md +9 -5
- package/skills/dev-loop/scripts/log-bash-exit-1.mjs +2 -2
- package/skills/dev-loop/scripts/phase-files.mjs +2 -2
- package/skills/docs/anti-patterns.md +2 -0
- package/skills/docs/copilot-loop-operations.md +2 -2
- package/skills/local-implementation/SKILL.md +17 -3
package/cli/index.mjs
CHANGED
|
@@ -22,6 +22,7 @@ const SUBCOMMAND_ROUTES = {
|
|
|
22
22
|
"upsert-verdict": "scripts/github/upsert-checkpoint-verdict.mjs",
|
|
23
23
|
"detect-evidence": "scripts/github/detect-checkpoint-evidence.mjs",
|
|
24
24
|
"write-findings-log": "scripts/github/write-gate-findings-log.mjs",
|
|
25
|
+
"post-findings": "scripts/github/post-gate-findings.mjs",
|
|
25
26
|
"request-copilot": "scripts/github/request-copilot-review.mjs",
|
|
26
27
|
"probe-copilot": "scripts/github/probe-copilot-review.mjs",
|
|
27
28
|
"capture-threads": "scripts/github/capture-review-threads.mjs",
|
|
@@ -43,7 +44,7 @@ const SUBCOMMAND_ROUTES = {
|
|
|
43
44
|
"debt-remediate": "scripts/loop/debt-remediate.mjs",
|
|
44
45
|
},
|
|
45
46
|
pr: {
|
|
46
|
-
|
|
47
|
+
create: "scripts/github/create-pr.mjs",
|
|
47
48
|
"ready-for-review": "scripts/github/ready-for-review.mjs",
|
|
48
49
|
"reconcile-draft": "scripts/github/reconcile-draft-gate.mjs",
|
|
49
50
|
},
|
|
@@ -52,6 +53,8 @@ const SUBCOMMAND_ROUTES = {
|
|
|
52
53
|
add: "scripts/projects/add-queue-item.mjs",
|
|
53
54
|
move: "scripts/projects/move-queue-item.mjs",
|
|
54
55
|
reorder: "scripts/projects/reorder-queue-item.mjs",
|
|
56
|
+
"archive-done": "scripts/projects/archive-done-items.mjs",
|
|
57
|
+
"sync-status": "scripts/projects/sync-item-status.mjs",
|
|
55
58
|
ensure: "scripts/projects/ensure-queue-board.mjs",
|
|
56
59
|
},
|
|
57
60
|
queue: {
|
|
@@ -66,6 +69,22 @@ const SUBCOMMAND_ROUTES = {
|
|
|
66
69
|
},
|
|
67
70
|
};
|
|
68
71
|
|
|
72
|
+
// Back-compat subcommand aliases: { category: { oldName: { canonical, notice } } }.
|
|
73
|
+
// Aliases keep existing callers working while emitting a one-line deprecation
|
|
74
|
+
// notice to stderr so they migrate to the canonical subcommand.
|
|
75
|
+
const SUBCOMMAND_ALIASES = {
|
|
76
|
+
pr: {
|
|
77
|
+
"create-draft": {
|
|
78
|
+
canonical: "create",
|
|
79
|
+
notice: "[dev-loops] `pr create-draft` is deprecated; use `pr create` (always draft, self-assigned by default).",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
function resolveSubcommandAlias(category, subcommand) {
|
|
85
|
+
return SUBCOMMAND_ALIASES[category]?.[subcommand] ?? null;
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
const TOP_LEVEL_COMMANDS = new Set(["help", "status", "doctor", "gates", "hide"]);
|
|
70
89
|
|
|
71
90
|
const HELP_CATEGORY_LABELS = {
|
|
@@ -84,6 +103,7 @@ const SUBCOMMAND_DESCRIPTIONS = {
|
|
|
84
103
|
"upsert-verdict": "Post/update gate review comment",
|
|
85
104
|
"detect-evidence": "Check merge preconditions",
|
|
86
105
|
"write-findings-log": "Write disposition ledger",
|
|
106
|
+
"post-findings": "Post gate fan-out findings comment",
|
|
87
107
|
"request-copilot": "Request Copilot review",
|
|
88
108
|
"probe-copilot": "Poll for Copilot review activity",
|
|
89
109
|
"capture-threads": "Capture review threads",
|
|
@@ -105,7 +125,7 @@ const SUBCOMMAND_DESCRIPTIONS = {
|
|
|
105
125
|
"debt-remediate": "File debt remediation issues",
|
|
106
126
|
},
|
|
107
127
|
pr: {
|
|
108
|
-
|
|
128
|
+
create: "Create PR (always draft, self-assigned by default)",
|
|
109
129
|
"ready-for-review": "Mark PR ready for review",
|
|
110
130
|
"reconcile-draft": "Reconcile non-draft PR",
|
|
111
131
|
},
|
|
@@ -113,7 +133,9 @@ const SUBCOMMAND_DESCRIPTIONS = {
|
|
|
113
133
|
list: "List queue board items",
|
|
114
134
|
add: "Add issue/PR to queue board",
|
|
115
135
|
move: "Move queue item between Status columns",
|
|
116
|
-
reorder: "Reorder
|
|
136
|
+
reorder: "Reorder items (move-to-top/move-after/order, --dry-run)",
|
|
137
|
+
"archive-done": "Archive closed Done items older than a duration",
|
|
138
|
+
"sync-status": "Sync a queued issue/PR's board Status column (best-effort)",
|
|
117
139
|
ensure: "Create/repair queue board bootstrap surface",
|
|
118
140
|
},
|
|
119
141
|
queue: {
|
|
@@ -273,16 +295,19 @@ function resolveSubcommandRoute(args) {
|
|
|
273
295
|
return { error: `Missing subcommand for '${category}'. Available: ${subs}` };
|
|
274
296
|
}
|
|
275
297
|
|
|
276
|
-
const
|
|
298
|
+
const requestedSubcommand = args[1];
|
|
299
|
+
const alias = resolveSubcommandAlias(category, requestedSubcommand);
|
|
300
|
+
const subcommand = alias ? alias.canonical : requestedSubcommand;
|
|
277
301
|
const scriptPath = routes[subcommand];
|
|
278
302
|
if (!scriptPath) {
|
|
279
303
|
const subs = Object.keys(routes).join(", ");
|
|
280
|
-
return { error: `Unknown subcommand '${
|
|
304
|
+
return { error: `Unknown subcommand '${requestedSubcommand}' for '${category}'. Available: ${subs}` };
|
|
281
305
|
}
|
|
282
306
|
|
|
283
307
|
return {
|
|
284
308
|
scriptPath: path.resolve(REPO_ROOT, scriptPath),
|
|
285
309
|
forwardedArgs: args.slice(2),
|
|
310
|
+
...(alias ? { deprecationNotice: alias.notice } : {}),
|
|
286
311
|
};
|
|
287
312
|
}
|
|
288
313
|
|
|
@@ -311,9 +336,17 @@ function parseTopLevelCommand(argv) {
|
|
|
311
336
|
}
|
|
312
337
|
// Check if any remaining arg is --help — delegate to script
|
|
313
338
|
if (args.slice(1).some((a) => a === "--help" || a === "-h")) {
|
|
314
|
-
const
|
|
339
|
+
const alias = resolveSubcommandAlias(cmd, sub);
|
|
340
|
+
const resolvedSub = alias ? alias.canonical : sub;
|
|
341
|
+
const scriptPath = routes[resolvedSub];
|
|
315
342
|
if (!scriptPath) return { kind: "category_help", category: cmd };
|
|
316
|
-
return {
|
|
343
|
+
return {
|
|
344
|
+
kind: "subcommand_help",
|
|
345
|
+
scriptPath: path.resolve(REPO_ROOT, scriptPath),
|
|
346
|
+
// Surface the deprecation notice on the --help fast-path too, so a
|
|
347
|
+
// deprecated alias signals migration in help mode (not only on dispatch).
|
|
348
|
+
...(alias ? { deprecationNotice: alias.notice } : {}),
|
|
349
|
+
};
|
|
317
350
|
}
|
|
318
351
|
const route = resolveSubcommandRoute(args);
|
|
319
352
|
if (route) return { kind: "subcommand", ...route };
|
|
@@ -343,6 +376,7 @@ export async function runCli({
|
|
|
343
376
|
return 0;
|
|
344
377
|
}
|
|
345
378
|
case "subcommand_help": {
|
|
379
|
+
if (fromTop.deprecationNotice) { writeLines(stderr, [fromTop.deprecationNotice]); }
|
|
346
380
|
const result = spawnSync("node", [fromTop.scriptPath, "--help"], {
|
|
347
381
|
cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
|
|
348
382
|
});
|
|
@@ -381,6 +415,7 @@ export async function runCli({
|
|
|
381
415
|
}
|
|
382
416
|
case "subcommand": {
|
|
383
417
|
if (fromTop.error) { writeLines(stderr, [fromTop.error]); return 1; }
|
|
418
|
+
if (fromTop.deprecationNotice) { writeLines(stderr, [fromTop.deprecationNotice]); }
|
|
384
419
|
const scriptArgs = fromTop.forwardedArgs || [];
|
|
385
420
|
const result = spawnSync("node", [fromTop.scriptPath, ...scriptArgs], {
|
|
386
421
|
cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
|
package/package.json
CHANGED
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"mermaid": "11.15.0",
|
|
71
|
-
"@dev-loops/core": "^0.
|
|
71
|
+
"@dev-loops/core": "^0.3.0"
|
|
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.
|
|
81
|
+
"version": "0.3.0",
|
|
82
82
|
"files": [
|
|
83
83
|
"cli/",
|
|
84
84
|
"lib/",
|
package/scripts/README.md
CHANGED
|
@@ -68,16 +68,17 @@ Failure behavior:
|
|
|
68
68
|
- malformed arguments, invalid JSON, and `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
69
69
|
- live capture is only allowed when both `--repo` and `--pr` are present
|
|
70
70
|
|
|
71
|
-
### `scripts/github/create-
|
|
71
|
+
### `scripts/github/create-pr.mjs`
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Canonical wrapper around `gh pr create`. Every PR opened through this tool is ALWAYS a draft and is always assigned — self-assigned by default (`--assignee @me` when none is given), while still honoring an explicit `--assignee <login>`. Never call raw `gh pr create` to open a PR. (`dev-loops pr create` dispatches here; the legacy `dev-loops pr create-draft` subcommand still works as a deprecated alias.)
|
|
74
74
|
|
|
75
75
|
Usage:
|
|
76
|
-
- `node scripts/github/create-
|
|
77
|
-
- `node <resolved-skill-scripts>/github/create-
|
|
76
|
+
- `node scripts/github/create-pr.mjs [gh pr create args...]`
|
|
77
|
+
- `node <resolved-skill-scripts>/github/create-pr.mjs [gh pr create args...]`
|
|
78
78
|
|
|
79
79
|
Contract:
|
|
80
|
-
- injects exactly one `--draft` when the caller did not already supply it
|
|
80
|
+
- injects exactly one `--draft` when the caller did not already supply it (draft is the only mode)
|
|
81
|
+
- self-assigns by default (defaults `--assignee @me` when no `--assignee` is provided); honors an explicit `--assignee <login>` when one is given
|
|
81
82
|
- rejects `--ready` before invoking `gh`; use `gh pr ready` later after draft-gate approval
|
|
82
83
|
- forwards every other argument to `gh pr create` unchanged and in order
|
|
83
84
|
- preserves the underlying `gh pr create` stdout, stderr, and exit code without wrapping success output
|
|
@@ -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
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { spawnSync } from "node:child_process";
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { parseArgs } from "node:util";
|
|
23
24
|
|
|
24
25
|
import { ensureRunId } from "@dev-loops/core/loop/run-context";
|
|
25
26
|
import {
|
|
@@ -28,22 +29,61 @@ import {
|
|
|
28
29
|
DEFAULT_CLAUDE_BIN,
|
|
29
30
|
} from "@dev-loops/core/claude/headless-entry";
|
|
30
31
|
|
|
31
|
-
function
|
|
32
|
+
function parseCliArgs(argv) {
|
|
32
33
|
const opts = { dryRun: false, claudeBin: DEFAULT_CLAUDE_BIN };
|
|
33
|
-
const requireValue = (name,
|
|
34
|
-
const v =
|
|
35
|
-
if (v ===
|
|
34
|
+
const requireValue = (name, token) => {
|
|
35
|
+
const v = token.value;
|
|
36
|
+
if (typeof v !== "string" || v.length === 0 || v.startsWith("--")) {
|
|
37
|
+
throw new Error(`${name} requires a value`);
|
|
38
|
+
}
|
|
36
39
|
return v;
|
|
37
40
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
|
|
42
|
+
const { tokens } = parseArgs({
|
|
43
|
+
args: [...argv],
|
|
44
|
+
options: {
|
|
45
|
+
"dry-run": { type: "boolean" },
|
|
46
|
+
issue: { type: "string" },
|
|
47
|
+
pr: { type: "string" },
|
|
48
|
+
"claude-bin": { type: "string" },
|
|
49
|
+
prompt: { type: "string" },
|
|
50
|
+
},
|
|
51
|
+
allowPositionals: true,
|
|
52
|
+
strict: false,
|
|
53
|
+
tokens: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
for (const token of tokens) {
|
|
57
|
+
if (token.kind === "positional") {
|
|
58
|
+
throw new Error(`unknown argument: ${token.value}`);
|
|
59
|
+
}
|
|
60
|
+
if (token.kind !== "option") {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
switch (token.name) {
|
|
64
|
+
case "dry-run":
|
|
65
|
+
if (token.value !== undefined) {
|
|
66
|
+
throw new Error(`unknown argument: ${token.rawName}=${token.value}`);
|
|
67
|
+
}
|
|
68
|
+
opts.dryRun = true;
|
|
69
|
+
break;
|
|
70
|
+
case "issue":
|
|
71
|
+
opts.issue = requireValue("--issue", token);
|
|
72
|
+
break;
|
|
73
|
+
case "pr":
|
|
74
|
+
opts.pr = requireValue("--pr", token);
|
|
75
|
+
break;
|
|
76
|
+
case "claude-bin":
|
|
77
|
+
opts.claudeBin = requireValue("--claude-bin", token);
|
|
78
|
+
break;
|
|
79
|
+
case "prompt":
|
|
80
|
+
opts.prompt = requireValue("--prompt", token);
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(`unknown argument: ${token.rawName}`);
|
|
84
|
+
}
|
|
46
85
|
}
|
|
86
|
+
|
|
47
87
|
if (opts.issue != null && opts.pr != null) {
|
|
48
88
|
throw new Error("--issue and --pr are mutually exclusive");
|
|
49
89
|
}
|
|
@@ -53,7 +93,7 @@ function parseArgs(argv) {
|
|
|
53
93
|
function main(argv) {
|
|
54
94
|
let opts;
|
|
55
95
|
try {
|
|
56
|
-
opts =
|
|
96
|
+
opts = parseCliArgs(argv);
|
|
57
97
|
} catch (error) {
|
|
58
98
|
process.stderr.write(JSON.stringify({ ok: false, error: error.message }) + "\n");
|
|
59
99
|
return 1;
|
|
@@ -18,21 +18,55 @@
|
|
|
18
18
|
import { spawnSync } from "node:child_process";
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { parseArgs } from "node:util";
|
|
21
22
|
|
|
22
|
-
function
|
|
23
|
+
function parseCliArgs(argv) {
|
|
23
24
|
const opts = { loopInfo: false };
|
|
24
|
-
const requireValue = (name,
|
|
25
|
-
const v =
|
|
26
|
-
if (v ===
|
|
25
|
+
const requireValue = (name, token) => {
|
|
26
|
+
const v = token.value;
|
|
27
|
+
if (typeof v !== "string" || v.length === 0 || v.startsWith("--")) {
|
|
28
|
+
throw new Error(`${name} requires a value`);
|
|
29
|
+
}
|
|
27
30
|
return v;
|
|
28
31
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
|
|
33
|
+
const { tokens } = parseArgs({
|
|
34
|
+
args: [...argv],
|
|
35
|
+
options: {
|
|
36
|
+
"loop-info": { type: "boolean" },
|
|
37
|
+
issue: { type: "string" },
|
|
38
|
+
pr: { type: "string" },
|
|
39
|
+
},
|
|
40
|
+
allowPositionals: true,
|
|
41
|
+
strict: false,
|
|
42
|
+
tokens: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
for (const token of tokens) {
|
|
46
|
+
if (token.kind === "positional") {
|
|
47
|
+
throw new Error(`unknown argument: ${token.value}`);
|
|
48
|
+
}
|
|
49
|
+
if (token.kind !== "option") {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
switch (token.name) {
|
|
53
|
+
case "loop-info":
|
|
54
|
+
if (token.value !== undefined) {
|
|
55
|
+
throw new Error(`unknown argument: ${token.rawName}=${token.value}`);
|
|
56
|
+
}
|
|
57
|
+
opts.loopInfo = true;
|
|
58
|
+
break;
|
|
59
|
+
case "issue":
|
|
60
|
+
opts.issue = requireValue("--issue", token);
|
|
61
|
+
break;
|
|
62
|
+
case "pr":
|
|
63
|
+
opts.pr = requireValue("--pr", token);
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`unknown argument: ${token.rawName}`);
|
|
67
|
+
}
|
|
35
68
|
}
|
|
69
|
+
|
|
36
70
|
if (opts.issue != null && opts.pr != null) {
|
|
37
71
|
throw new Error("--issue and --pr are mutually exclusive");
|
|
38
72
|
}
|
|
@@ -46,7 +80,7 @@ function runCli(cliEntry, repoRoot, args) {
|
|
|
46
80
|
function main(argv) {
|
|
47
81
|
let opts;
|
|
48
82
|
try {
|
|
49
|
-
opts =
|
|
83
|
+
opts = parseCliArgs(argv);
|
|
50
84
|
} catch (error) {
|
|
51
85
|
process.stderr.write(JSON.stringify({ ok: false, error: error.message }) + "\n");
|
|
52
86
|
return 1;
|
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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;
|