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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { parsePrNumber, requireTokenValue } from "../_cli-primitives.mjs";
|
|
5
6
|
import { formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
6
7
|
const USAGE = `Usage: write-gate-findings-log.mjs --repo <owner/name> --pr <number> --gate <draft_gate|pre_approval_gate> --head-sha <sha> --verdict <clean|findings_present|blocked> --findings <json> [--tmp-root <path>]
|
|
7
8
|
Write a durable <gate>-<headSha>.json log under deterministic tmp/ paths.
|
|
@@ -89,7 +90,22 @@ function parseFindingsJson(raw) {
|
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
92
|
export function parseWriteGateFindingsLogCliArgs(argv) {
|
|
92
|
-
const
|
|
93
|
+
const { tokens } = parseArgs({
|
|
94
|
+
args: [...argv],
|
|
95
|
+
options: {
|
|
96
|
+
help: { type: "boolean", short: "h" },
|
|
97
|
+
repo: { type: "string" },
|
|
98
|
+
pr: { type: "string" },
|
|
99
|
+
gate: { type: "string" },
|
|
100
|
+
"head-sha": { type: "string" },
|
|
101
|
+
verdict: { type: "string" },
|
|
102
|
+
findings: { type: "string" },
|
|
103
|
+
"tmp-root": { type: "string" },
|
|
104
|
+
},
|
|
105
|
+
allowPositionals: true,
|
|
106
|
+
strict: false,
|
|
107
|
+
tokens: true,
|
|
108
|
+
});
|
|
93
109
|
const options = {
|
|
94
110
|
repo: undefined,
|
|
95
111
|
pr: undefined,
|
|
@@ -99,46 +115,51 @@ export function parseWriteGateFindingsLogCliArgs(argv) {
|
|
|
99
115
|
findings: undefined,
|
|
100
116
|
tmpRoot: "tmp",
|
|
101
117
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
118
|
+
for (const token of tokens) {
|
|
119
|
+
if (token.kind === "positional") {
|
|
120
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
121
|
+
}
|
|
122
|
+
if (token.kind !== "option") {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (token.name === "help") {
|
|
105
126
|
return { help: true };
|
|
106
127
|
}
|
|
107
|
-
if (token === "
|
|
108
|
-
options.repo =
|
|
128
|
+
if (token.name === "repo") {
|
|
129
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
109
130
|
continue;
|
|
110
131
|
}
|
|
111
|
-
if (token === "
|
|
112
|
-
options.pr = parsePrNumber(
|
|
132
|
+
if (token.name === "pr") {
|
|
133
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
113
134
|
continue;
|
|
114
135
|
}
|
|
115
|
-
if (token === "
|
|
116
|
-
const gate = normalizeGate(
|
|
136
|
+
if (token.name === "gate") {
|
|
137
|
+
const gate = normalizeGate(requireTokenValue(token, parseError));
|
|
117
138
|
if (!gate) throw parseError("--gate must be draft_gate or pre_approval_gate");
|
|
118
139
|
options.gate = gate;
|
|
119
140
|
continue;
|
|
120
141
|
}
|
|
121
|
-
if (token === "
|
|
122
|
-
const sha = normalizeHeadSha(
|
|
142
|
+
if (token.name === "head-sha") {
|
|
143
|
+
const sha = normalizeHeadSha(requireTokenValue(token, parseError));
|
|
123
144
|
if (!sha) throw parseError("--head-sha must be a 7-64 character hex SHA");
|
|
124
145
|
options.headSha = sha;
|
|
125
146
|
continue;
|
|
126
147
|
}
|
|
127
|
-
if (token === "
|
|
128
|
-
const verdict = normalizeVerdict(
|
|
148
|
+
if (token.name === "verdict") {
|
|
149
|
+
const verdict = normalizeVerdict(requireTokenValue(token, parseError));
|
|
129
150
|
if (!verdict) throw parseError("--verdict must be clean, findings_present, or blocked");
|
|
130
151
|
options.verdict = verdict;
|
|
131
152
|
continue;
|
|
132
153
|
}
|
|
133
|
-
if (token === "
|
|
134
|
-
options.findings =
|
|
154
|
+
if (token.name === "findings") {
|
|
155
|
+
options.findings = requireTokenValue(token, parseError);
|
|
135
156
|
continue;
|
|
136
157
|
}
|
|
137
|
-
if (token === "
|
|
138
|
-
options.tmpRoot =
|
|
158
|
+
if (token.name === "tmp-root") {
|
|
159
|
+
options.tmpRoot = requireTokenValue(token, parseError).trim();
|
|
139
160
|
continue;
|
|
140
161
|
}
|
|
141
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
162
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
142
163
|
}
|
|
143
164
|
const missing = ["repo", "pr", "gate", "headSha", "verdict", "findings"]
|
|
144
165
|
.filter(k => options[k] === undefined);
|
|
@@ -147,7 +168,7 @@ export function parseWriteGateFindingsLogCliArgs(argv) {
|
|
|
147
168
|
}
|
|
148
169
|
return options;
|
|
149
170
|
}
|
|
150
|
-
function buildLogPath({ repo, pr, gate, headSha, tmpRoot }) {
|
|
171
|
+
export function buildLogPath({ repo, pr, gate, headSha, tmpRoot }) {
|
|
151
172
|
const parts = repo.split("/");
|
|
152
173
|
if (parts.length !== 2 || parts.some(p => p.length === 0)) {
|
|
153
174
|
throw new Error(`--repo must be in owner/name format, got: ${JSON.stringify(repo)}`);
|
|
@@ -18,10 +18,11 @@ import { readFile } from "node:fs/promises";
|
|
|
18
18
|
import { detectRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
21
|
-
import {
|
|
21
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
22
22
|
import { buildDevLoopHandoffEnvelope } from "@dev-loops/core/loop/handoff-envelope";
|
|
23
23
|
import { loadDevLoopConfig } from "@dev-loops/core/config";
|
|
24
24
|
import { createPiAdapter } from "@dev-loops/core/harness";
|
|
25
|
+
import { parseArgs } from "node:util";
|
|
25
26
|
|
|
26
27
|
const USAGE = `Usage: build-handoff-envelope.mjs --input <path>
|
|
27
28
|
Build a deterministic handoff envelope from startup resolver output and settings.
|
|
@@ -56,7 +57,6 @@ function parseFlagJson(raw, flagName, parseErrorFn) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export function parseBuildHandoffEnvelopeCliArgs(argv) {
|
|
59
|
-
const args = [...argv];
|
|
60
60
|
const options = {
|
|
61
61
|
help: false,
|
|
62
62
|
inputPath: undefined,
|
|
@@ -65,29 +65,47 @@ export function parseBuildHandoffEnvelopeCliArgs(argv) {
|
|
|
65
65
|
repo: undefined,
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const { tokens } = parseArgs({
|
|
69
|
+
args: [...argv],
|
|
70
|
+
options: {
|
|
71
|
+
help: { type: "boolean", short: "h" },
|
|
72
|
+
input: { type: "string" },
|
|
73
|
+
"gate-state": { type: "string" },
|
|
74
|
+
overrides: { type: "string" },
|
|
75
|
+
repo: { 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 parseError(`Unknown argument: ${token.value}`);
|
|
84
|
+
}
|
|
85
|
+
if (token.kind !== "option") {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (token.name === "help") {
|
|
71
89
|
options.help = true;
|
|
72
90
|
return options;
|
|
73
91
|
}
|
|
74
|
-
if (token === "
|
|
75
|
-
options.inputPath =
|
|
92
|
+
if (token.name === "input") {
|
|
93
|
+
options.inputPath = requireTokenValue(token, parseError);
|
|
76
94
|
continue;
|
|
77
95
|
}
|
|
78
|
-
if (token === "
|
|
79
|
-
options.gateState =
|
|
96
|
+
if (token.name === "gate-state") {
|
|
97
|
+
options.gateState = requireTokenValue(token, parseError);
|
|
80
98
|
continue;
|
|
81
99
|
}
|
|
82
|
-
if (token === "
|
|
83
|
-
options.overrides =
|
|
100
|
+
if (token.name === "overrides") {
|
|
101
|
+
options.overrides = requireTokenValue(token, parseError);
|
|
84
102
|
continue;
|
|
85
103
|
}
|
|
86
|
-
if (token === "
|
|
87
|
-
options.repo =
|
|
104
|
+
if (token.name === "repo") {
|
|
105
|
+
options.repo = requireTokenValue(token, parseError);
|
|
88
106
|
continue;
|
|
89
107
|
}
|
|
90
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
108
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
91
109
|
}
|
|
92
110
|
|
|
93
111
|
if (!options.inputPath) {
|
|
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { access, open, readFile, readdir } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
7
7
|
import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "../_core-helpers.mjs";
|
|
8
8
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
9
9
|
import { autoDetectSnapshot } from "./detect-copilot-loop-state.mjs";
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "./_handoff-contract.mjs";
|
|
15
15
|
import { interpretLoopState, summarizeLoopInterpretation } from "@dev-loops/core/loop/copilot-loop-state";
|
|
16
16
|
import { listOpenPrs } from "./_loop-pr-aggregation.mjs";
|
|
17
|
+
import { parseArgs } from "node:util";
|
|
17
18
|
const USAGE = `Usage: conductor-monitor.mjs --repo <owner/name> [--auto-resume]
|
|
18
19
|
Aggregate Copilot-loop status across all open PRs in one repo.
|
|
19
20
|
Required:
|
|
@@ -113,27 +114,42 @@ const MANUAL_REASON = {
|
|
|
113
114
|
HANDOFF_CONTRACT_MISMATCH: "handoff_contract_mismatch",
|
|
114
115
|
};
|
|
115
116
|
function parseCliArgs(argv) {
|
|
116
|
-
const args = [...argv];
|
|
117
117
|
const options = {
|
|
118
118
|
help: false,
|
|
119
119
|
repo: undefined,
|
|
120
120
|
autoResume: false,
|
|
121
121
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
const { tokens } = parseArgs({
|
|
123
|
+
args: [...argv],
|
|
124
|
+
options: {
|
|
125
|
+
help: { type: "boolean", short: "h" },
|
|
126
|
+
repo: { type: "string" },
|
|
127
|
+
"auto-resume": { type: "boolean" },
|
|
128
|
+
},
|
|
129
|
+
allowPositionals: true,
|
|
130
|
+
strict: false,
|
|
131
|
+
tokens: true,
|
|
132
|
+
});
|
|
133
|
+
for (const token of tokens) {
|
|
134
|
+
if (token.kind === "positional") {
|
|
135
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
136
|
+
}
|
|
137
|
+
if (token.kind !== "option") {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (token.name === "help") {
|
|
125
141
|
options.help = true;
|
|
126
142
|
return options;
|
|
127
143
|
}
|
|
128
|
-
if (token === "
|
|
129
|
-
options.repo =
|
|
144
|
+
if (token.name === "repo") {
|
|
145
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
130
146
|
continue;
|
|
131
147
|
}
|
|
132
|
-
if (token === "
|
|
148
|
+
if (token.name === "auto-resume") {
|
|
133
149
|
options.autoResume = true;
|
|
134
150
|
continue;
|
|
135
151
|
}
|
|
136
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
152
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
137
153
|
}
|
|
138
154
|
if (options.repo === undefined) {
|
|
139
155
|
throw parseError("conductor-monitor requires --repo <owner/name>");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runConductorCycle } from "./run-conductor-cycle.mjs";
|
|
3
3
|
import { runConductorMonitor } from "./conductor-monitor.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
5
5
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
6
6
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
7
7
|
import {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@dev-loops/core/config";
|
|
13
13
|
import { readFileSync } from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
|
+
import { parseArgs } from "node:util";
|
|
15
16
|
const USAGE = `Usage: conductor.mjs --repo <owner/name> [--auto-resume] [--cycle-only] [--monitor-only] [--require-retrospective]
|
|
16
17
|
Unified conductor entrypoint for dev-loop lifecycle orchestration.`.trim();
|
|
17
18
|
const parseError = buildParseError(USAGE);
|
|
@@ -43,7 +44,6 @@ function checkRetrospectiveGate(cwd, requireRetrospective) {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
function parseCliArgs(argv) {
|
|
46
|
-
const args = [...argv];
|
|
47
47
|
const options = {
|
|
48
48
|
help: false,
|
|
49
49
|
repo: undefined,
|
|
@@ -52,33 +52,52 @@ function parseCliArgs(argv) {
|
|
|
52
52
|
monitorOnly: false,
|
|
53
53
|
requireRetrospective: false,
|
|
54
54
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
const { tokens } = parseArgs({
|
|
56
|
+
args: [...argv],
|
|
57
|
+
options: {
|
|
58
|
+
help: { type: "boolean", short: "h" },
|
|
59
|
+
repo: { type: "string" },
|
|
60
|
+
"auto-resume": { type: "boolean" },
|
|
61
|
+
"cycle-only": { type: "boolean" },
|
|
62
|
+
"monitor-only": { type: "boolean" },
|
|
63
|
+
"require-retrospective": { type: "boolean" },
|
|
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") {
|
|
58
77
|
options.help = true;
|
|
59
78
|
return options;
|
|
60
79
|
}
|
|
61
|
-
if (token === "
|
|
62
|
-
options.repo =
|
|
80
|
+
if (token.name === "repo") {
|
|
81
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
63
82
|
continue;
|
|
64
83
|
}
|
|
65
|
-
if (token === "
|
|
84
|
+
if (token.name === "auto-resume") {
|
|
66
85
|
options.autoResume = true;
|
|
67
86
|
continue;
|
|
68
87
|
}
|
|
69
|
-
if (token === "
|
|
88
|
+
if (token.name === "cycle-only") {
|
|
70
89
|
options.cycleOnly = true;
|
|
71
90
|
continue;
|
|
72
91
|
}
|
|
73
|
-
if (token === "
|
|
92
|
+
if (token.name === "monitor-only") {
|
|
74
93
|
options.monitorOnly = true;
|
|
75
94
|
continue;
|
|
76
95
|
}
|
|
77
|
-
if (token === "
|
|
96
|
+
if (token.name === "require-retrospective") {
|
|
78
97
|
options.requireRetrospective = true;
|
|
79
98
|
continue;
|
|
80
99
|
}
|
|
81
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
100
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
82
101
|
}
|
|
83
102
|
if (options.repo === undefined) {
|
|
84
103
|
throw parseError("conductor requires --repo <owner/name>");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { buildParseError, formatCliError, isCopilotLogin, isDirectCliRun, normalizeTimestamp } from "../_core-helpers.mjs";
|
|
3
|
-
import { parsePrNumber,
|
|
3
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
4
|
import { detectRepoSlug, parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
5
5
|
import { resolveRunId } from "@dev-loops/core/loop/run-context";
|
|
6
6
|
import path from "node:path";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
EXTERNAL_HEALTHY_WAIT_TIMEOUT_POLICY,
|
|
17
17
|
enforceExternalHealthyWaitTimeout,
|
|
18
18
|
} from "@dev-loops/core/loop/timeout-policy";
|
|
19
|
+
import { parseArgs } from "node:util";
|
|
19
20
|
import {
|
|
20
21
|
DEFAULT_POLL_INTERVAL_MS,
|
|
21
22
|
COPILOT_REVIEW_WAIT_TIMEOUT_MS,
|
|
@@ -128,39 +129,55 @@ function rejectRemovedFlag(token) {
|
|
|
128
129
|
);
|
|
129
130
|
}
|
|
130
131
|
export function parseHandoffCliArgs(argv, { cwd = process.cwd() } = {}) {
|
|
131
|
-
const args = [...argv];
|
|
132
132
|
const options = {
|
|
133
133
|
help: false,
|
|
134
134
|
repo: undefined,
|
|
135
135
|
pr: undefined,
|
|
136
136
|
watchStatus: undefined,
|
|
137
137
|
};
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
const { tokens } = parseArgs({
|
|
139
|
+
args: [...argv],
|
|
140
|
+
options: {
|
|
141
|
+
help: { type: "boolean", short: "h" },
|
|
142
|
+
repo: { type: "string" },
|
|
143
|
+
pr: { type: "string" },
|
|
144
|
+
"watch-status": { type: "string" },
|
|
145
|
+
},
|
|
146
|
+
allowPositionals: true,
|
|
147
|
+
strict: false,
|
|
148
|
+
tokens: true,
|
|
149
|
+
});
|
|
150
|
+
for (const token of tokens) {
|
|
151
|
+
if (token.kind === "positional") {
|
|
152
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
153
|
+
}
|
|
154
|
+
if (token.kind !== "option") {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (token.name === "help") {
|
|
141
158
|
options.help = true;
|
|
142
159
|
return options;
|
|
143
160
|
}
|
|
144
|
-
if (REMOVED_FLAGS.has(token)) {
|
|
145
|
-
rejectRemovedFlag(token);
|
|
161
|
+
if (REMOVED_FLAGS.has(token.rawName)) {
|
|
162
|
+
rejectRemovedFlag(token.rawName);
|
|
146
163
|
}
|
|
147
|
-
if (token === "
|
|
148
|
-
options.repo =
|
|
164
|
+
if (token.name === "repo") {
|
|
165
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
149
166
|
continue;
|
|
150
167
|
}
|
|
151
|
-
if (token === "
|
|
152
|
-
options.pr = parsePrNumber(
|
|
168
|
+
if (token.name === "pr") {
|
|
169
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
153
170
|
continue;
|
|
154
171
|
}
|
|
155
|
-
if (token === "
|
|
156
|
-
const watchStatus =
|
|
172
|
+
if (token.name === "watch-status") {
|
|
173
|
+
const watchStatus = requireTokenValue(token, parseError).trim().toLowerCase();
|
|
157
174
|
if (!VALID_WATCH_STATUSES.has(watchStatus)) {
|
|
158
175
|
throw parseError(`--watch-status must be one of: ${[...VALID_WATCH_STATUSES].join(", ")}`);
|
|
159
176
|
}
|
|
160
177
|
options.watchStatus = watchStatus;
|
|
161
178
|
continue;
|
|
162
179
|
}
|
|
163
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
180
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
164
181
|
}
|
|
165
182
|
if (options.pr === undefined) {
|
|
166
183
|
throw parseError("copilot-pr-handoff requires --pr <number>");
|
|
@@ -15,9 +15,10 @@ import { readFile } from "node:fs/promises";
|
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { parseArgs } from "node:util";
|
|
18
19
|
|
|
19
20
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
20
|
-
import {
|
|
21
|
+
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
21
22
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
22
23
|
import { DebtSignalSchema } from "@dev-loops/core/debt/signal";
|
|
23
24
|
import { clusterSignalsEnriched } from "@dev-loops/core/debt/cluster";
|
|
@@ -150,28 +151,44 @@ function buildReport(signalsCount, findingsCount, results) {
|
|
|
150
151
|
// ============================================================================
|
|
151
152
|
|
|
152
153
|
export async function runCli(argv) {
|
|
153
|
-
const args = [...argv];
|
|
154
154
|
const options = { input: undefined, repo: undefined, dryRun: false, help: false };
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
const { tokens } = parseArgs({
|
|
157
|
+
args: [...argv],
|
|
158
|
+
options: {
|
|
159
|
+
help: { type: "boolean", short: "h" },
|
|
160
|
+
input: { type: "string" },
|
|
161
|
+
repo: { type: "string" },
|
|
162
|
+
"dry-run": { type: "boolean" },
|
|
163
|
+
},
|
|
164
|
+
allowPositionals: true,
|
|
165
|
+
strict: false,
|
|
166
|
+
tokens: true,
|
|
167
|
+
});
|
|
168
|
+
for (const token of tokens) {
|
|
169
|
+
if (token.kind === "positional") {
|
|
170
|
+
throw parseError(`Unknown flag: ${token.value}`);
|
|
171
|
+
}
|
|
172
|
+
if (token.kind !== "option") {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (token.name === "help") {
|
|
159
176
|
options.help = true;
|
|
160
177
|
break;
|
|
161
178
|
}
|
|
162
|
-
if (token === "
|
|
163
|
-
options.input =
|
|
179
|
+
if (token.name === "input") {
|
|
180
|
+
options.input = requireTokenValue(token, parseError);
|
|
164
181
|
continue;
|
|
165
182
|
}
|
|
166
|
-
if (token === "
|
|
167
|
-
options.repo =
|
|
183
|
+
if (token.name === "repo") {
|
|
184
|
+
options.repo = requireTokenValue(token, parseError);
|
|
168
185
|
continue;
|
|
169
186
|
}
|
|
170
|
-
if (token === "
|
|
187
|
+
if (token.name === "dry-run") {
|
|
171
188
|
options.dryRun = true;
|
|
172
189
|
continue;
|
|
173
190
|
}
|
|
174
|
-
throw parseError(`Unknown flag: ${token}`);
|
|
191
|
+
throw parseError(`Unknown flag: ${token.rawName}`);
|
|
175
192
|
}
|
|
176
193
|
|
|
177
194
|
if (options.help) {
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
for (let i = 0; i < args.length; i++) {
|
|
8
|
-
if (args[i] === "--help" || args[i] === "-h") {
|
|
9
|
-
process.stdout.write(`Usage: detect-change-scope.mjs [--base <ref>] [--head <ref>]
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
|
|
6
|
+
const USAGE = `Usage: detect-change-scope.mjs [--base <ref>] [--head <ref>]
|
|
10
7
|
Detect change scope from git diff for light-mode eligibility.
|
|
11
8
|
Options:
|
|
12
9
|
--base <ref> Override base ref (default: HEAD~1)
|
|
@@ -15,11 +12,39 @@ Options:
|
|
|
15
12
|
Exit codes:
|
|
16
13
|
0 Success
|
|
17
14
|
1 Error
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
function parseCliArgs(argv) {
|
|
18
|
+
const { tokens } = parseArgs({
|
|
19
|
+
args: [...argv],
|
|
20
|
+
options: {
|
|
21
|
+
base: { type: "string" },
|
|
22
|
+
head: { type: "string" },
|
|
23
|
+
help: { type: "boolean", short: "h" },
|
|
24
|
+
},
|
|
25
|
+
allowPositionals: true,
|
|
26
|
+
strict: false,
|
|
27
|
+
tokens: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const opts = { base: null, head: null };
|
|
31
|
+
for (const token of tokens) {
|
|
32
|
+
if (token.kind === "option") {
|
|
33
|
+
if (token.name === "help") {
|
|
34
|
+
if (token.value !== undefined) {
|
|
35
|
+
throw new Error(`unknown argument: ${token.rawName}=${token.value}`);
|
|
36
|
+
}
|
|
37
|
+
process.stdout.write(USAGE);
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
if (token.name === "base") {
|
|
41
|
+
opts.base = token.value ?? null;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (token.name === "head") {
|
|
45
|
+
opts.head = token.value ?? null;
|
|
46
|
+
}
|
|
20
47
|
}
|
|
21
|
-
if (args[i] === "--base" && i + 1 < args.length) opts.base = args[++i];
|
|
22
|
-
else if (args[i] === "--head" && i + 1 < args.length) opts.head = args[++i];
|
|
23
48
|
}
|
|
24
49
|
return opts;
|
|
25
50
|
}
|
|
@@ -64,7 +89,7 @@ function isEligibleForLightMode(scope, threshold) {
|
|
|
64
89
|
return scope.filesChanged <= threshold.maxFiles && scope.linesChanged <= threshold.maxLines;
|
|
65
90
|
}
|
|
66
91
|
async function main() {
|
|
67
|
-
const opts =
|
|
92
|
+
const opts = parseCliArgs(process.argv.slice(2));
|
|
68
93
|
const scope = detectScope(opts);
|
|
69
94
|
let threshold = { maxFiles: 3, maxLines: 200 };
|
|
70
95
|
let eligible = false;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
5
6
|
import {
|
|
6
7
|
buildParseError,
|
|
7
8
|
formatCliError,
|
|
@@ -58,32 +59,48 @@ Exit codes:
|
|
|
58
59
|
const VALID_OVERRIDE_STATUSES = new Set(["requested", "already-requested", "unavailable", "none", "failed"]);
|
|
59
60
|
const parseError = buildParseError(USAGE);
|
|
60
61
|
export function parseDetectCliArgs(argv) {
|
|
61
|
-
const args = [...argv];
|
|
62
62
|
const options = {
|
|
63
63
|
help: false,
|
|
64
64
|
inputPath: undefined,
|
|
65
65
|
repo: undefined,
|
|
66
66
|
pr: undefined,
|
|
67
67
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const { tokens } = parseArgs({
|
|
69
|
+
args: [...argv],
|
|
70
|
+
options: {
|
|
71
|
+
help: { type: "boolean", short: "h" },
|
|
72
|
+
input: { type: "string" },
|
|
73
|
+
repo: { type: "string" },
|
|
74
|
+
pr: { type: "string" },
|
|
75
|
+
},
|
|
76
|
+
allowPositionals: true,
|
|
77
|
+
strict: false,
|
|
78
|
+
tokens: true,
|
|
79
|
+
});
|
|
80
|
+
for (const token of tokens) {
|
|
81
|
+
if (token.kind === "positional") {
|
|
82
|
+
throw parseError(`Unknown argument: ${token.value}`);
|
|
83
|
+
}
|
|
84
|
+
if (token.kind !== "option") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (token.name === "help") {
|
|
71
88
|
options.help = true;
|
|
72
89
|
return options;
|
|
73
90
|
}
|
|
74
|
-
if (token === "
|
|
75
|
-
options.inputPath =
|
|
91
|
+
if (token.name === "input") {
|
|
92
|
+
options.inputPath = requireTokenValue(token, parseError);
|
|
76
93
|
continue;
|
|
77
94
|
}
|
|
78
|
-
if (token === "
|
|
79
|
-
options.repo =
|
|
95
|
+
if (token.name === "repo") {
|
|
96
|
+
options.repo = requireTokenValue(token, parseError).trim();
|
|
80
97
|
continue;
|
|
81
98
|
}
|
|
82
|
-
if (token === "
|
|
83
|
-
options.pr = parsePrNumber(
|
|
99
|
+
if (token.name === "pr") {
|
|
100
|
+
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
84
101
|
continue;
|
|
85
102
|
}
|
|
86
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
103
|
+
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
87
104
|
}
|
|
88
105
|
if (options.inputPath !== undefined) {
|
|
89
106
|
if (options.repo !== undefined || options.pr !== undefined) {
|