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.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/agents/dev-loop.md +2 -1
- package/.claude/skills/dev-loop/SKILL.md +5 -5
- package/CHANGELOG.md +17 -0
- package/agents/dev-loop.agent.md +5 -1
- package/cli/index.mjs +3 -1
- package/package.json +2 -2
- package/scripts/_cli-primitives.mjs +2 -0
- package/scripts/claude/generate-claude-assets.mjs +12 -2
- package/scripts/docs/validate-links.mjs +20 -11
- package/scripts/github/capture-review-threads.mjs +32 -14
- package/scripts/github/detect-checkpoint-evidence.mjs +28 -11
- package/scripts/github/detect-linked-issue-pr.mjs +22 -10
- package/scripts/github/manage-sub-issues.mjs +37 -16
- 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 +22 -10
- package/scripts/github/reply-resolve-review-threads.mjs +34 -15
- package/scripts/github/request-copilot-review.mjs +28 -11
- 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 +49 -26
- package/scripts/github/write-gate-findings-log.mjs +41 -20
- 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-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 +26 -10
- package/scripts/loop/detect-reviewer-loop-state.mjs +35 -16
- package/scripts/loop/detect-stale-runner.mjs +32 -14
- 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-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/archive-done-items.mjs +410 -0
- package/scripts/projects/reorder-queue-item.mjs +334 -76
- package/scripts/refine/_refine-helpers.mjs +21 -9
- package/scripts/refine/verify.mjs +31 -13
- package/skills/dev-loop/SKILL.md +9 -5
|
@@ -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
|
-
|
|
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
|
package/agents/dev-loop.agent.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
81
|
+
"version": "0.2.7",
|
|
82
82
|
"files": [
|
|
83
83
|
"cli/",
|
|
84
84
|
"lib/",
|
|
@@ -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 {
|
|
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;
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
parseReviewThreads,
|
|
9
9
|
readInput,
|
|
10
10
|
} from "../_core-helpers.mjs";
|
|
11
|
-
import {
|
|
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
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 === "
|
|
67
|
-
options.inputPath =
|
|
84
|
+
if (token.name === "input") {
|
|
85
|
+
options.inputPath = requireTokenValue(token);
|
|
68
86
|
continue;
|
|
69
87
|
}
|
|
70
|
-
if (token === "
|
|
71
|
-
options.outputPath =
|
|
88
|
+
if (token.name === "output") {
|
|
89
|
+
options.outputPath = requireTokenValue(token);
|
|
72
90
|
continue;
|
|
73
91
|
}
|
|
74
|
-
if (token === "
|
|
75
|
-
options.repo =
|
|
92
|
+
if (token.name === "repo") {
|
|
93
|
+
options.repo = requireTokenValue(token);
|
|
76
94
|
continue;
|
|
77
95
|
}
|
|
78
|
-
if (token === "
|
|
79
|
-
options.pr = parsePrNumber(
|
|
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,
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 === "
|
|
100
|
-
options.repo =
|
|
116
|
+
if (token.name === "repo") {
|
|
117
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
101
118
|
continue;
|
|
102
119
|
}
|
|
103
|
-
if (token === "
|
|
104
|
-
options.pr = parsePrNumber(
|
|
120
|
+
if (token.name === "pr") {
|
|
121
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
105
122
|
continue;
|
|
106
123
|
}
|
|
107
|
-
if (token === "
|
|
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,
|
|
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
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 === "
|
|
91
|
-
options.repo =
|
|
102
|
+
if (token.name === "repo") {
|
|
103
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
92
104
|
continue;
|
|
93
105
|
}
|
|
94
|
-
if (token === "
|
|
95
|
-
options.issue = parseIssueNumber(
|
|
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,
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 === "
|
|
86
|
-
options.repo =
|
|
106
|
+
if (token.name === "repo") {
|
|
107
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
87
108
|
continue;
|
|
88
109
|
}
|
|
89
|
-
if (token === "
|
|
90
|
-
options.issue = parsePositiveInteger(
|
|
110
|
+
if (token.name === "issue") {
|
|
111
|
+
options.issue = parsePositiveInteger(requireTokenValue(token, parseError), "Issue number", parseError);
|
|
91
112
|
continue;
|
|
92
113
|
}
|
|
93
|
-
if (token === "
|
|
94
|
-
options.child = parsePositiveInteger(
|
|
114
|
+
if (token.name === "child") {
|
|
115
|
+
options.child = parsePositiveInteger(requireTokenValue(token, parseError), "Issue number", parseError);
|
|
95
116
|
continue;
|
|
96
117
|
}
|
|
97
|
-
if (token === "
|
|
98
|
-
options.order = parseIssueList(
|
|
118
|
+
if (token.name === "order") {
|
|
119
|
+
options.order = parseIssueList(requireTokenValue(token, parseError));
|
|
99
120
|
continue;
|
|
100
121
|
}
|
|
101
|
-
if (token === "
|
|
102
|
-
options.expected = parseIssueList(
|
|
122
|
+
if (token.name === "expected") {
|
|
123
|
+
options.expected = parseIssueList(requireTokenValue(token, parseError));
|
|
103
124
|
continue;
|
|
104
125
|
}
|
|
105
|
-
if (token === "
|
|
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 {
|
|
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
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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 === "
|
|
109
|
-
options.repo =
|
|
120
|
+
if (token.name === "repo") {
|
|
121
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
110
122
|
continue;
|
|
111
123
|
}
|
|
112
|
-
if (token === "
|
|
113
|
-
options.pr = parsePositiveInteger(
|
|
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,
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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);
|