dev-loops 0.5.0 → 0.7.1
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/review.md +2 -1
- package/.claude/commands/loop-auto.md +7 -0
- package/.claude/commands/loop-continue.md +15 -0
- package/.claude/commands/loop-enqueue.md +22 -0
- package/.claude/commands/loop-grill.md +17 -0
- package/.claude/commands/loop-info.md +7 -0
- package/.claude/commands/loop-queue-status.md +24 -0
- package/.claude/commands/loop-start-spike.md +16 -0
- package/.claude/commands/loop-start.md +7 -0
- package/.claude/commands/loop-status.md +6 -0
- package/.claude/hooks/_bash-command-classify.mjs +333 -29
- package/.claude/hooks/_hook-decisions.mjs +138 -15
- package/.claude/hooks/_run-context.mjs +11 -4
- package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
- package/.claude/skills/copilot-pr-followup/SKILL.md +24 -12
- package/.claude/skills/dev-loop/SKILL.md +19 -10
- package/.claude/skills/docs/acceptance-criteria-verification.md +6 -2
- package/.claude/skills/docs/anti-patterns.md +2 -1
- package/.claude/skills/docs/artifact-authority-contract.md +22 -4
- package/.claude/skills/docs/copilot-loop-operations.md +1 -1
- package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
- package/.claude/skills/docs/issue-intake-procedure.md +1 -0
- package/.claude/skills/docs/local-planning-flow.md +1 -1
- package/.claude/skills/docs/local-planning-worked-example.md +1 -1
- package/.claude/skills/docs/merge-preconditions.md +17 -1
- package/.claude/skills/docs/plan-file-contract.md +1 -1
- package/.claude/skills/docs/public-dev-loop-contract.md +1 -1
- package/.claude/skills/docs/release-runbook.md +45 -0
- package/.claude/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/.claude/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
- package/.claude/skills/local-implementation/SKILL.md +26 -15
- package/.claude/skills/loop-grill/SKILL.md +163 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +125 -0
- package/README.md +29 -10
- package/agents/dev-loop.agent.md +11 -3
- package/agents/developer.agent.md +1 -1
- package/agents/docs.agent.md +1 -1
- package/agents/fixer.agent.md +1 -1
- package/agents/quality.agent.md +1 -1
- package/agents/refiner.agent.md +1 -1
- package/agents/review.agent.md +3 -2
- package/cli/index.mjs +35 -40
- package/extension/README.md +4 -4
- package/extension/checks.ts +1 -5
- package/extension/harness-types.ts +1 -0
- package/extension/index.ts +16 -1
- package/extension/pi-extension-adapter.ts +2 -0
- package/extension/presentation.ts +16 -13
- package/lib/dev-loops-core.mjs +141 -0
- package/package.json +10 -8
- package/scripts/claude/generate-claude-assets.mjs +15 -1
- package/scripts/github/capture-review-threads.mjs +2 -9
- package/scripts/github/comment-issue.mjs +174 -0
- package/scripts/github/create-label.mjs +133 -0
- package/scripts/github/detect-checkpoint-evidence.mjs +170 -14
- package/scripts/github/detect-linked-issue-pr.mjs +22 -6
- package/scripts/github/edit-pr.mjs +259 -0
- package/scripts/github/fetch-ci-logs.mjs +208 -0
- package/scripts/github/list-issues.mjs +184 -0
- package/scripts/github/manage-sub-issues.mjs +46 -10
- package/scripts/github/offer-human-handoff.mjs +8 -5
- package/scripts/github/post-gate-findings.mjs +14 -2
- package/scripts/github/probe-ci-status.mjs +8 -2
- package/scripts/github/probe-copilot-review.mjs +21 -14
- package/scripts/github/ready-for-review.mjs +26 -8
- package/scripts/github/reconcile-draft-gate.mjs +7 -3
- package/scripts/github/reply-resolve-review-thread.mjs +16 -5
- package/scripts/github/reply-resolve-review-threads.mjs +69 -7
- package/scripts/github/request-copilot-review.mjs +8 -2
- package/scripts/github/resolve-handoff-candidates.mjs +7 -3
- package/scripts/github/resolve-tracker-local-spec.mjs +9 -3
- package/scripts/github/stage-reviewer-draft.mjs +10 -2
- package/scripts/github/tick-verified-checkboxes.mjs +202 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +31 -10
- package/scripts/github/verify-fresh-review-context.mjs +150 -31
- package/scripts/github/view-pr.mjs +150 -0
- package/scripts/github/write-gate-context.mjs +248 -61
- package/scripts/github/write-gate-findings-log.mjs +75 -1
- package/scripts/lib/jq-output.mjs +18 -0
- package/scripts/loop/_handoff-contract.mjs +1 -0
- package/scripts/loop/_post-convergence-change.mjs +211 -0
- package/scripts/loop/_pr-runner-coordination.mjs +70 -0
- package/scripts/loop/_repo-root-resolver.mjs +47 -0
- package/scripts/loop/build-handoff-envelope.mjs +14 -4
- package/scripts/loop/check-retro-tooling.mjs +14 -3
- package/scripts/loop/checkpoint-contract.mjs +7 -4
- package/scripts/loop/cleanup-worktree.mjs +12 -3
- package/scripts/loop/conductor-monitor.mjs +12 -18
- package/scripts/loop/copilot-pr-handoff.mjs +135 -14
- package/scripts/loop/debt-remediate.mjs +24 -12
- package/scripts/loop/detect-change-scope.mjs +36 -7
- package/scripts/loop/detect-copilot-loop-state.mjs +11 -5
- package/scripts/loop/detect-copilot-session-activity.mjs +7 -3
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +7 -3
- package/scripts/loop/detect-internal-only-pr.mjs +8 -2
- package/scripts/loop/detect-issue-refinement-artifact.mjs +6 -3
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +179 -66
- package/scripts/loop/detect-reviewer-loop-state.mjs +12 -2
- package/scripts/loop/detect-tracker-first-loop-state.mjs +9 -1
- package/scripts/loop/detect-tracker-pr-state.mjs +10 -3
- package/scripts/loop/ensure-worktree.mjs +8 -3
- package/scripts/loop/info.mjs +12 -4
- package/scripts/loop/inspect-run-viewer/constants.mjs +4 -18
- package/scripts/loop/inspect-run-viewer/vendor/mermaid.min.js +3405 -0
- package/scripts/loop/inspect-run-viewer-ci-changes.mjs +18 -6
- package/scripts/loop/inspect-run-viewer.mjs +67 -4
- package/scripts/loop/inspect-run.mjs +8 -2
- package/scripts/loop/outer-loop.mjs +8 -4
- package/scripts/loop/pr-runner-coordination.mjs +2 -9
- package/scripts/loop/pre-commit-branch-guard.mjs +16 -10
- package/scripts/loop/pre-flight-gate.mjs +9 -9
- package/scripts/loop/pre-pr-ready-gate.mjs +8 -4
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +13 -4
- package/scripts/loop/provision-worktree.mjs +74 -3
- package/scripts/loop/resolve-dev-loop-startup.mjs +76 -7
- package/scripts/loop/resolve-gate-dispatch.mjs +134 -0
- package/scripts/loop/resolve-pr-conflicts.mjs +14 -7
- package/scripts/loop/run-conductor-cycle.mjs +13 -2
- package/scripts/loop/run-queue.mjs +18 -9
- package/scripts/loop/run-refinement-audit.mjs +8 -9
- package/scripts/loop/run-watch-cycle.mjs +2 -9
- package/scripts/loop/sanctioned-commands.mjs +104 -0
- package/scripts/loop/steer-loop.mjs +29 -16
- package/scripts/loop/validate-pr-body-spec.mjs +207 -0
- package/scripts/loop/watch-initial-copilot-pr.mjs +8 -3
- package/scripts/pages/build-site.mjs +59 -8
- package/scripts/pages/build-state-atlas.mjs +441 -0
- package/scripts/projects/_resolve-project.mjs +148 -0
- package/scripts/projects/add-queue-item.mjs +60 -54
- package/scripts/projects/archive-done-items.mjs +49 -84
- package/scripts/projects/ensure-queue-board.mjs +10 -36
- package/scripts/projects/list-queue-items.mjs +116 -65
- package/scripts/projects/move-queue-item.mjs +28 -49
- package/scripts/projects/reconcile-queue.mjs +253 -0
- package/scripts/projects/reorder-queue-item.mjs +41 -47
- package/scripts/projects/resolve-active-board-item.mjs +255 -0
- package/scripts/projects/sync-item-status.mjs +15 -10
- package/scripts/refine/_refine-helpers.mjs +21 -5
- package/scripts/refine/exit-spike.mjs +18 -8
- package/scripts/refine/promote-plan.mjs +22 -16
- package/scripts/refine/prose-linkage-detector.mjs +1 -1
- package/scripts/refine/refine-plan-file.mjs +13 -4
- package/scripts/refine/refinement-completeness-checker.mjs +1 -1
- package/scripts/refine/scaffold-spike-file.mjs +179 -0
- package/scripts/refine/scope-boundary-cross-checker.mjs +1 -1
- package/scripts/refine/tree-integrity-validator.mjs +1 -1
- package/scripts/refine/validate-plan-file.mjs +1 -1
- package/scripts/refine/validate-spike-file.mjs +1 -1
- package/scripts/refine/verify.mjs +11 -6
- package/scripts/release/assert-core-dependency-version.mjs +123 -0
- package/scripts/release/extract-changelog-section.mjs +125 -0
- package/skills/copilot-pr-followup/SKILL.md +24 -12
- package/skills/dev-loop/SKILL.md +22 -6
- package/skills/docs/acceptance-criteria-verification.md +6 -2
- package/skills/docs/anti-patterns.md +2 -1
- package/skills/docs/artifact-authority-contract.md +22 -4
- package/skills/docs/copilot-loop-operations.md +1 -1
- package/skills/docs/cross-harness-regression-contract.md +60 -0
- package/skills/docs/issue-intake-procedure.md +1 -0
- package/skills/docs/local-planning-flow.md +1 -1
- package/skills/docs/local-planning-worked-example.md +1 -1
- package/skills/docs/merge-preconditions.md +17 -1
- package/skills/docs/plan-file-contract.md +1 -1
- package/skills/docs/public-dev-loop-contract.md +1 -1
- package/skills/docs/release-runbook.md +45 -0
- package/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/skills/docs/workflow-handoff-contract.md +39 -0
- package/skills/local-implementation/SKILL.md +26 -15
- package/skills/loop-grill/SKILL.md +165 -0
- package/scripts/loop/conductor.mjs +0 -233
- package/scripts/loop/detect-stale-runner.mjs +0 -265
- package/scripts/loop/pre-push-main-guard.mjs +0 -117
|
@@ -34,7 +34,10 @@ import {
|
|
|
34
34
|
PLAN_FILE_REFINEMENT_SECTIONS,
|
|
35
35
|
} from "@dev-loops/core/loop/plan-file-intake-contract";
|
|
36
36
|
import { evaluateSpikeIntakeState } from "@dev-loops/core/loop/spike-intake-contract";
|
|
37
|
+
import { loadBoardConfig } from "@dev-loops/core/loop/queue-board-sync";
|
|
38
|
+
import { main as reconcileQueue } from "../projects/reconcile-queue.mjs";
|
|
37
39
|
import { parseArgs } from "node:util";
|
|
40
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
38
41
|
const USAGE = `Usage:
|
|
39
42
|
resolve-dev-loop-startup.mjs --issue <number>
|
|
40
43
|
resolve-dev-loop-startup.mjs --pr <number>
|
|
@@ -54,9 +57,23 @@ Required (exactly one):
|
|
|
54
57
|
--input <path> Path to a JSON file with canonical-state payload
|
|
55
58
|
--plan-file <path> Path to a phase-doc-format plan to start locally
|
|
56
59
|
--spike <path> Path to a spike artifact to start a spike loop locally
|
|
60
|
+
Optional modifier:
|
|
61
|
+
--lightweight With --issue: use the PR body as the spec-of-record
|
|
62
|
+
(canonicalSpecSource: pr_body) — no phase/plan doc minted or
|
|
63
|
+
committed. Same gate sequence; only the backing artifact
|
|
64
|
+
differs. Rejected with --plan-file (its opposite). The secondary
|
|
65
|
+
heuristic (chore/fix commit type + no --plan-file + small change)
|
|
66
|
+
is a documented manual signal; --lightweight is the explicit,
|
|
67
|
+
deterministic trigger.
|
|
68
|
+
${JQ_OUTPUT_USAGE}
|
|
69
|
+
|
|
57
70
|
Exit codes:
|
|
58
71
|
0 Success
|
|
59
|
-
1 Argument error, runtime failure, or async-start contract rejection
|
|
72
|
+
1 Argument error, runtime failure, or async-start contract rejection
|
|
73
|
+
2 Invalid --jq filter`.trim();
|
|
74
|
+
// Upper bound on the awaited best-effort startup reconcile so a slow or hung gh
|
|
75
|
+
// can never delay startup completion.
|
|
76
|
+
const STARTUP_RECONCILE_BUDGET_MS = 20000;
|
|
60
77
|
const SHARED_PUBLIC_CONTRACT = "skills/docs/public-dev-loop-contract.md";
|
|
61
78
|
const SHARED_RETROSPECTIVE_CONTRACT = "skills/docs/retrospective-checkpoint-contract.md";
|
|
62
79
|
const STRATEGY_REQUIRED_READS = {
|
|
@@ -123,6 +140,7 @@ export function parseResolveDevLoopStartupCliArgs(argv) {
|
|
|
123
140
|
pr: undefined,
|
|
124
141
|
planFile: undefined,
|
|
125
142
|
spike: undefined,
|
|
143
|
+
lightweight: false,
|
|
126
144
|
};
|
|
127
145
|
const { tokens } = parseArgs({
|
|
128
146
|
args: [...argv],
|
|
@@ -133,6 +151,8 @@ export function parseResolveDevLoopStartupCliArgs(argv) {
|
|
|
133
151
|
pr: { type: "string" },
|
|
134
152
|
"plan-file": { type: "string" },
|
|
135
153
|
spike: { type: "string" },
|
|
154
|
+
lightweight: { type: "boolean" },
|
|
155
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
136
156
|
},
|
|
137
157
|
allowPositionals: true,
|
|
138
158
|
strict: false,
|
|
@@ -169,6 +189,11 @@ export function parseResolveDevLoopStartupCliArgs(argv) {
|
|
|
169
189
|
options.spike = requireTokenValue(token, parseError);
|
|
170
190
|
continue;
|
|
171
191
|
}
|
|
192
|
+
if (token.name === "lightweight") {
|
|
193
|
+
options.lightweight = true;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
172
197
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
173
198
|
}
|
|
174
199
|
const modeCount = [options.inputPath, options.issue, options.pr, options.planFile, options.spike].filter(v => v !== undefined).length;
|
|
@@ -178,6 +203,15 @@ export function parseResolveDevLoopStartupCliArgs(argv) {
|
|
|
178
203
|
if (modeCount === 0) {
|
|
179
204
|
throw parseError("--input <path>, --issue <n>, --pr <n>, --plan-file <path>, or --spike <path> is required");
|
|
180
205
|
}
|
|
206
|
+
// --lightweight is a MODIFIER (not a 6th mode): it makes the PR body the
|
|
207
|
+
// spec-of-record for the --issue local path. It is the opposite of --plan-file
|
|
208
|
+
// (which commits a durable plan doc as the spec) and only composes with --issue.
|
|
209
|
+
if (options.lightweight && options.planFile !== undefined) {
|
|
210
|
+
throw parseError("--lightweight and --plan-file are opposites: --plan-file commits a durable plan doc as the spec-of-record, --lightweight makes the PR body the spec. Provide only one.");
|
|
211
|
+
}
|
|
212
|
+
if (options.lightweight && options.issue === undefined) {
|
|
213
|
+
throw parseError("--lightweight is a modifier for the --issue path (the PR body becomes the spec-of-record). Combine it with --issue <n>.");
|
|
214
|
+
}
|
|
181
215
|
return options;
|
|
182
216
|
}
|
|
183
217
|
function ghJson(args, cwd) {
|
|
@@ -264,7 +298,7 @@ function resolveTargetPreference(cwd) {
|
|
|
264
298
|
} catch {
|
|
265
299
|
}
|
|
266
300
|
}
|
|
267
|
-
return "
|
|
301
|
+
return "prefer_local";
|
|
268
302
|
}
|
|
269
303
|
function normalizeConfigInputSource(value) {
|
|
270
304
|
if (value === "phase-docs") return "phase-docs";
|
|
@@ -541,7 +575,10 @@ export function buildResolveDevLoopStartupResult(input, { adapter = createPiAdap
|
|
|
541
575
|
// evaluator does not model. Strip them before evaluation and re-apply them to
|
|
542
576
|
// the result; `planFileExempt` waives the worktree-isolation guard because a
|
|
543
577
|
// pre-promotion plan has no issue to key a worktree on.
|
|
544
|
-
|
|
578
|
+
// `canonicalSpecSource` (issue #1025) is a resolver-only field the pure routing
|
|
579
|
+
// evaluator does not model — strip it before evaluation and re-attach to the
|
|
580
|
+
// result, mirroring planFileIntakeState/spikeIntakeState.
|
|
581
|
+
const { planFileExempt = false, planFileIntakeState = null, spikeIntakeState = null, canonicalSpecSource = null, ...routingInput } = input;
|
|
545
582
|
input = routingInput;
|
|
546
583
|
try {
|
|
547
584
|
const checkpointText = readFileSync(
|
|
@@ -652,6 +689,7 @@ export function buildResolveDevLoopStartupResult(input, { adapter = createPiAdap
|
|
|
652
689
|
canonicalStateSummary: summarizeCanonicalState(bundle),
|
|
653
690
|
...(planFileIntakeState !== null ? { planFileIntakeState } : {}),
|
|
654
691
|
...(spikeIntakeState !== null ? { spikeIntakeState } : {}),
|
|
692
|
+
...(canonicalSpecSource !== null ? { canonicalSpecSource } : {}),
|
|
655
693
|
bundle,
|
|
656
694
|
};
|
|
657
695
|
}
|
|
@@ -672,7 +710,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
672
710
|
? devLoopConfig?.strategy?.default === "local-first"
|
|
673
711
|
? "prefer_local"
|
|
674
712
|
: "prefer_github_first"
|
|
675
|
-
: "
|
|
713
|
+
: "prefer_local";
|
|
676
714
|
const inputSource = configErrors.length === 0
|
|
677
715
|
? normalizeConfigInputSource(devLoopConfig?.inputSource?.default)
|
|
678
716
|
: "tracker";
|
|
@@ -693,6 +731,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
693
731
|
delete parsed.planFileExempt;
|
|
694
732
|
delete parsed.planFileIntakeState;
|
|
695
733
|
delete parsed.spikeIntakeState;
|
|
734
|
+
delete parsed.canonicalSpecSource;
|
|
696
735
|
}
|
|
697
736
|
input = parsed;
|
|
698
737
|
} else if (options.issue !== undefined) {
|
|
@@ -702,6 +741,11 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
702
741
|
targetPreference,
|
|
703
742
|
inputSource,
|
|
704
743
|
});
|
|
744
|
+
// --lightweight modifier (issue #1025): the PR body becomes the
|
|
745
|
+
// spec-of-record for this local session — no phase/plan doc minted.
|
|
746
|
+
if (options.lightweight) {
|
|
747
|
+
input = { ...input, canonicalSpecSource: "pr_body" };
|
|
748
|
+
}
|
|
705
749
|
} else {
|
|
706
750
|
input = buildAutoResolvedInput({
|
|
707
751
|
pr: options.pr,
|
|
@@ -711,11 +755,36 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
711
755
|
}
|
|
712
756
|
const result = buildResolveDevLoopStartupResult(input, { asyncStartMode, adapter });
|
|
713
757
|
if (result.ok === false) {
|
|
714
|
-
|
|
715
|
-
process.exitCode = 1;
|
|
758
|
+
process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout: stderr, stderr });
|
|
716
759
|
return;
|
|
717
760
|
}
|
|
718
|
-
|
|
761
|
+
// Emit the deterministic bundle FIRST, before the best-effort self-heal below,
|
|
762
|
+
// so a slow or hung gh reconcile can never delay the startup result.
|
|
763
|
+
process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
764
|
+
// #1069: best-effort startup self-heal — converge the board from live GitHub
|
|
765
|
+
// state so merged→Done / ready→In Progress land deterministically. Gated on a
|
|
766
|
+
// configured board so it never shells out to gh in the no-.devloops unit tests;
|
|
767
|
+
// never writes stdout, never changes exit code, never throws. Skips
|
|
768
|
+
// --input/--plan-file/--spike modes.
|
|
769
|
+
if (options.issue !== undefined || options.pr !== undefined) {
|
|
770
|
+
let reconcileRoot = sessionCwd;
|
|
771
|
+
try {
|
|
772
|
+
reconcileRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
773
|
+
cwd: sessionCwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
|
|
774
|
+
}).trim();
|
|
775
|
+
} catch { /* fall back to sessionCwd */ }
|
|
776
|
+
if (loadBoardConfig(reconcileRoot).enabled === true) {
|
|
777
|
+
try {
|
|
778
|
+
// The budget bounds startup latency; reconcile is best-effort and the
|
|
779
|
+
// board self-heals on the next entry if it doesn't finish. The timer is
|
|
780
|
+
// unref'd so it never keeps the event loop alive on its own.
|
|
781
|
+
await Promise.race([
|
|
782
|
+
reconcileQueue({ repo: detectRepoSlug(reconcileRoot) }, { env: adapter.getEnv(), cwd: reconcileRoot, skipTerminalColumn: true }),
|
|
783
|
+
new Promise((resolve) => { const t = setTimeout(resolve, STARTUP_RECONCILE_BUDGET_MS); t.unref?.(); }),
|
|
784
|
+
]);
|
|
785
|
+
} catch { /* best-effort */ }
|
|
786
|
+
}
|
|
787
|
+
}
|
|
719
788
|
}
|
|
720
789
|
if (isDirectCliRun(import.meta.url)) {
|
|
721
790
|
runCli().catch((error) => {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import {
|
|
5
|
+
loadDevLoopConfig,
|
|
6
|
+
resolveGateDispatchMode,
|
|
7
|
+
GATE_FULL_LABEL,
|
|
8
|
+
} from "@dev-loops/core/config";
|
|
9
|
+
import { detectScope } from "./detect-change-scope.mjs";
|
|
10
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult } from "../lib/jq-output.mjs";
|
|
11
|
+
|
|
12
|
+
const USAGE = `Usage: resolve-gate-dispatch.mjs --gate <draft|preApproval> [--base <ref>] [--head <ref>] [--full-label] [--inline-severities <csv>]
|
|
13
|
+
Decide inline vs full fan-out for a gate from lightMode config + PR facts.
|
|
14
|
+
Options:
|
|
15
|
+
--gate <draft|preApproval> Gate to resolve dispatch for (required)
|
|
16
|
+
--base <ref> Base ref for scope detection (default: HEAD~1)
|
|
17
|
+
--head <ref> Head ref; ignored unless --base is also set
|
|
18
|
+
--full-label PR has the ${GATE_FULL_LABEL} label (forces full fan-out)
|
|
19
|
+
--inline-severities <csv> Comma-separated severities from the inline pass (escalation phase)
|
|
20
|
+
--help, -h Show this help
|
|
21
|
+
Output (stdout, JSON):
|
|
22
|
+
{ "ok": true, "gate": "draft", "scope": { "ok": true, "filesChanged": 1, "linesChanged": 5 }, "mode": "inline", "reason": "under_threshold", "threshold": { "maxFiles": 2, "maxLines": 20 } }
|
|
23
|
+
{ "ok": true, "gate": "draft", "scope": { "ok": true, "filesChanged": 9, "linesChanged": 300 }, "mode": "full_fanout", "reason": "over_threshold", "threshold": { "maxFiles": 2, "maxLines": 20 } }
|
|
24
|
+
{ "ok": true, "gate": "draft", "scope": { "ok": false, ... }, "mode": "full_fanout", "reason": "scope_detection_failed", "threshold": null }
|
|
25
|
+
Error output (stderr, JSON):
|
|
26
|
+
{ "ok": false, "error": "...", "usage"?: "..." }
|
|
27
|
+
|
|
28
|
+
${JQ_OUTPUT_USAGE}
|
|
29
|
+
|
|
30
|
+
Exit codes:
|
|
31
|
+
0 Success
|
|
32
|
+
1 Error
|
|
33
|
+
2 Invalid --jq filter
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const VALID_GATES = new Set(["draft", "preApproval"]);
|
|
37
|
+
|
|
38
|
+
/** Parse a comma-separated severity list. Returns `undefined` when the flag is absent, otherwise a trimmed array (which may be empty `[]` for empty/whitespace-only input). */
|
|
39
|
+
export function parseSeverities(csv) {
|
|
40
|
+
if (csv == null) return undefined;
|
|
41
|
+
const list = String(csv)
|
|
42
|
+
.split(",")
|
|
43
|
+
.map((s) => s.trim())
|
|
44
|
+
.filter((s) => s.length > 0);
|
|
45
|
+
return list.length > 0 ? list : [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseCliArgs(argv) {
|
|
49
|
+
const { values } = parseArgs({
|
|
50
|
+
args: [...argv],
|
|
51
|
+
options: {
|
|
52
|
+
gate: { type: "string" },
|
|
53
|
+
base: { type: "string" },
|
|
54
|
+
head: { type: "string" },
|
|
55
|
+
"full-label": { type: "boolean", default: false },
|
|
56
|
+
"inline-severities": { type: "string" },
|
|
57
|
+
help: { type: "boolean", short: "h" },
|
|
58
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
59
|
+
},
|
|
60
|
+
allowPositionals: true,
|
|
61
|
+
strict: true,
|
|
62
|
+
});
|
|
63
|
+
if (values.help) {
|
|
64
|
+
process.stdout.write(USAGE);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
if (!values.gate || !VALID_GATES.has(values.gate)) {
|
|
68
|
+
throw new Error("--gate must be one of: draft, preApproval");
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
gate: values.gate,
|
|
72
|
+
base: values.base ?? null,
|
|
73
|
+
head: values.head ?? null,
|
|
74
|
+
hasFullLabel: Boolean(values["full-label"]),
|
|
75
|
+
inlineFindingSeverities: parseSeverities(values["inline-severities"]),
|
|
76
|
+
jq: values.jq,
|
|
77
|
+
silent: values.silent === true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function run(argv) {
|
|
82
|
+
let opts;
|
|
83
|
+
try {
|
|
84
|
+
opts = parseCliArgs(argv);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
process.stderr.write(
|
|
87
|
+
JSON.stringify({ ok: false, error: err.message, usage: USAGE.trim() }) + "\n"
|
|
88
|
+
);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const { config } = await loadDevLoopConfig({ repoRoot: process.cwd() });
|
|
94
|
+
const scope = detectScope({ base: opts.base, head: opts.head });
|
|
95
|
+
// Fail CLOSED on unmeasurable scope: a broken/failed diff must route to the
|
|
96
|
+
// full gate, never silently collapse to inline (which would bypass review).
|
|
97
|
+
if (scope.ok === false) {
|
|
98
|
+
process.exitCode = emitResult({
|
|
99
|
+
ok: true,
|
|
100
|
+
gate: opts.gate,
|
|
101
|
+
scope,
|
|
102
|
+
mode: "full_fanout",
|
|
103
|
+
reason: "scope_detection_failed",
|
|
104
|
+
threshold: null,
|
|
105
|
+
}, { jq: opts.jq, silent: opts.silent });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const decision = resolveGateDispatchMode(config, opts.gate, {
|
|
109
|
+
scope,
|
|
110
|
+
hasFullLabel: opts.hasFullLabel,
|
|
111
|
+
inlineFindingSeverities: opts.inlineFindingSeverities,
|
|
112
|
+
});
|
|
113
|
+
process.exitCode = emitResult(
|
|
114
|
+
{ ok: true, gate: opts.gate, scope, ...decision },
|
|
115
|
+
{ jq: opts.jq, silent: opts.silent },
|
|
116
|
+
);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
process.stderr.write(
|
|
119
|
+
JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }) + "\n"
|
|
120
|
+
);
|
|
121
|
+
process.exitCode = 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const isDirectRun =
|
|
126
|
+
process.argv[1] && process.argv[1].includes("resolve-gate-dispatch.mjs");
|
|
127
|
+
if (isDirectRun) {
|
|
128
|
+
run(process.argv.slice(2)).catch((err) => {
|
|
129
|
+
process.stderr.write(
|
|
130
|
+
JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }) + "\n"
|
|
131
|
+
);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -6,6 +6,7 @@ import { parseArgs } from "node:util";
|
|
|
6
6
|
|
|
7
7
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
8
8
|
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
9
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
9
10
|
|
|
10
11
|
const USAGE = `Usage: resolve-pr-conflicts.mjs [--base <branch>] [--repo-root <dir>] [--no-verify] [--push] [--json]
|
|
11
12
|
|
|
@@ -36,9 +37,14 @@ Output (stdout, JSON with --json):
|
|
|
36
37
|
Error output:
|
|
37
38
|
{ "ok": false, "error": "...", "conflictFiles": ["..."] }
|
|
38
39
|
|
|
40
|
+
${JQ_OUTPUT_USAGE}
|
|
41
|
+
(--jq/--silent apply to the JSON error output always, and to the success output
|
|
42
|
+
with --json; the default non-JSON success summary is unaffected.)
|
|
43
|
+
|
|
39
44
|
Exit codes:
|
|
40
45
|
0 Clean merge (incl. already up to date) or auto-resolved CHANGELOG
|
|
41
|
-
1 Argument error, git failure, or UNRESOLVABLE conflict (fail closed)
|
|
46
|
+
1 Argument error, git failure, or UNRESOLVABLE conflict (fail closed)
|
|
47
|
+
2 Invalid --jq filter`.trim();
|
|
42
48
|
|
|
43
49
|
const parseError = buildParseError(USAGE);
|
|
44
50
|
|
|
@@ -70,6 +76,7 @@ export function parseResolvePrConflictsCliArgs(argv) {
|
|
|
70
76
|
"no-verify": { type: "boolean" },
|
|
71
77
|
push: { type: "boolean" },
|
|
72
78
|
json: { type: "boolean" },
|
|
79
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
73
80
|
},
|
|
74
81
|
allowPositionals: true,
|
|
75
82
|
strict: false,
|
|
@@ -101,8 +108,10 @@ export function parseResolvePrConflictsCliArgs(argv) {
|
|
|
101
108
|
case "json":
|
|
102
109
|
options.json = true;
|
|
103
110
|
break;
|
|
104
|
-
default:
|
|
111
|
+
default: {
|
|
112
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) break;
|
|
105
113
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
114
|
+
}
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
return options;
|
|
@@ -337,18 +346,16 @@ export async function runCli(argv, { stdout = process.stdout, stderr = process.s
|
|
|
337
346
|
try {
|
|
338
347
|
const result = await resolvePrConflicts(options, { env });
|
|
339
348
|
if (options.json) {
|
|
340
|
-
|
|
341
|
-
} else {
|
|
342
|
-
stdout.write(`${result.action} (base ${result.base}${result.resolvedFiles.length ? `, resolved ${result.resolvedFiles.join(", ")}` : ""}${result.pushed ? ", pushed" : ""})\n`);
|
|
349
|
+
return emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
343
350
|
}
|
|
351
|
+
stdout.write(`${result.action} (base ${result.base}${result.resolvedFiles.length ? `, resolved ${result.resolvedFiles.join(", ")}` : ""}${result.pushed ? ", pushed" : ""})\n`);
|
|
344
352
|
return 0;
|
|
345
353
|
} catch (error) {
|
|
346
354
|
const payload = { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
347
355
|
if (Array.isArray(error?.conflictFiles)) {
|
|
348
356
|
payload.conflictFiles = error.conflictFiles;
|
|
349
357
|
}
|
|
350
|
-
|
|
351
|
-
return 1;
|
|
358
|
+
return emitResult(payload, { jq: options.jq, silent: options.silent, stdout: stderr, stderr });
|
|
352
359
|
}
|
|
353
360
|
}
|
|
354
361
|
|
|
@@ -15,9 +15,12 @@ import {
|
|
|
15
15
|
} from "./_handoff-contract.mjs";
|
|
16
16
|
import { listOpenPrs } from "./_loop-pr-aggregation.mjs";
|
|
17
17
|
import { parseArgs } from "node:util";
|
|
18
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
18
19
|
export { listOpenPrs };
|
|
19
20
|
const USAGE = `Usage: run-conductor-cycle.mjs --repo <owner/name>
|
|
20
|
-
Poll all open PRs, detect state, and output an ordered action queue
|
|
21
|
+
Poll all open PRs, detect state, and output an ordered action queue.
|
|
22
|
+
|
|
23
|
+
${JQ_OUTPUT_USAGE}`.trim();
|
|
21
24
|
export const CHECKPOINT_ACTION_TO_CONDUCTOR_ACTION = Object.freeze({
|
|
22
25
|
[PR_CHECKPOINT_ACTION.ADDRESS_REVIEW_FEEDBACK]: "fix_threads",
|
|
23
26
|
[PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS]: "fix_threads",
|
|
@@ -32,11 +35,16 @@ export const CHECKPOINT_ACTION_TO_CONDUCTOR_ACTION = Object.freeze({
|
|
|
32
35
|
[PR_CHECKPOINT_ACTION.DECLARE_MERGE_READY]: "merge",
|
|
33
36
|
[PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL]: "await_approval",
|
|
34
37
|
[PR_CHECKPOINT_ACTION.RESOLVE_MERGE_CONFLICTS]: "resolve_conflicts",
|
|
38
|
+
[PR_CHECKPOINT_ACTION.RUN_UI_E2E_SUITE]: "run_ui_e2e",
|
|
35
39
|
[PR_CHECKPOINT_ACTION.REPORT_BLOCKED]: "blocked",
|
|
36
40
|
[PR_CHECKPOINT_ACTION.REPORT_DONE]: "done",
|
|
37
41
|
});
|
|
38
42
|
export const ACTION_PRIORITY = Object.freeze({
|
|
39
43
|
merge: 100,
|
|
44
|
+
// UI e2e auto-scoping fix work (#976): a path-triggered, fail-closed
|
|
45
|
+
// precondition — surface it ahead of feedback/draft work so the rendered
|
|
46
|
+
// artifact gets registered + covered before the gate proceeds.
|
|
47
|
+
run_ui_e2e: 95,
|
|
40
48
|
fix_threads: 90,
|
|
41
49
|
run_pre_approval: 80,
|
|
42
50
|
draft_gate: 70,
|
|
@@ -73,6 +81,7 @@ export function parseCliArgs(argv) {
|
|
|
73
81
|
options: {
|
|
74
82
|
help: { type: "boolean", short: "h" },
|
|
75
83
|
repo: { type: "string" },
|
|
84
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
76
85
|
},
|
|
77
86
|
allowPositionals: true,
|
|
78
87
|
strict: false,
|
|
@@ -93,6 +102,7 @@ export function parseCliArgs(argv) {
|
|
|
93
102
|
options.repo = requireTokenValue(token, parseError).trim();
|
|
94
103
|
continue;
|
|
95
104
|
}
|
|
105
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
96
106
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
97
107
|
}
|
|
98
108
|
if (options.repo === undefined) {
|
|
@@ -275,6 +285,7 @@ export async function runCli(
|
|
|
275
285
|
argv = process.argv.slice(2),
|
|
276
286
|
{
|
|
277
287
|
stdout = process.stdout,
|
|
288
|
+
stderr = process.stderr,
|
|
278
289
|
env = process.env,
|
|
279
290
|
ghCommand = "gh",
|
|
280
291
|
cwd = process.cwd(),
|
|
@@ -290,7 +301,7 @@ export async function runCli(
|
|
|
290
301
|
ghCommand,
|
|
291
302
|
repoRoot: cwd,
|
|
292
303
|
});
|
|
293
|
-
|
|
304
|
+
process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
294
305
|
}
|
|
295
306
|
if (isDirectCliRun(import.meta.url)) {
|
|
296
307
|
runCli().catch((error) => {
|
|
@@ -21,6 +21,11 @@ import { readQueue } from "@dev-loops/core/loop/queue-state";
|
|
|
21
21
|
import { reconcileBoardMembership } from "@dev-loops/core/loop/queue-membership";
|
|
22
22
|
import { parsePositiveInteger } from "@dev-loops/core/cli/primitives";
|
|
23
23
|
import { loadDevLoopConfig, resolveEffectiveMergeAuthorizedFromLoad } from "@dev-loops/core/config";
|
|
24
|
+
import {
|
|
25
|
+
REASON_NEXT_UP_EMPTY,
|
|
26
|
+
REASON_BOARD_QUERY_ERROR,
|
|
27
|
+
EMPTY_NEXT_UP_MESSAGE,
|
|
28
|
+
} from "@dev-loops/core/loop/queue-board-ordering";
|
|
24
29
|
|
|
25
30
|
const REPO_ROOT = fileURLToPath(new URL("../..", import.meta.url));
|
|
26
31
|
|
|
@@ -129,24 +134,28 @@ async function main() {
|
|
|
129
134
|
// resolution failure (which falls through to the local queue below).
|
|
130
135
|
console.log(JSON.stringify({
|
|
131
136
|
ok: true,
|
|
132
|
-
|
|
137
|
+
// Canonical empty-Next-Up outcome — matches queue-driver.mjs so operators
|
|
138
|
+
// see one message regardless of which layer detects it.
|
|
139
|
+
message: EMPTY_NEXT_UP_MESSAGE,
|
|
133
140
|
boardConfigured: true,
|
|
134
|
-
reason:
|
|
141
|
+
reason: REASON_NEXT_UP_EMPTY,
|
|
135
142
|
results: [],
|
|
136
143
|
}));
|
|
137
144
|
return;
|
|
138
145
|
}
|
|
139
146
|
|
|
140
147
|
if (membership.emptiness === "board_unavailable") {
|
|
141
|
-
// The board IS configured but Next Up resolution failed
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
148
|
+
// The board IS configured but Next Up resolution failed and the local queue
|
|
149
|
+
// had nothing to fall back to. Align with the driver's canonical fail-closed
|
|
150
|
+
// board-query-error outcome (reason + framing) so operators see one shape
|
|
151
|
+
// regardless of which layer detects the outage — an outage halts the run
|
|
152
|
+
// rather than draining Backlog/local order.
|
|
145
153
|
console.log(JSON.stringify({
|
|
146
|
-
ok:
|
|
147
|
-
|
|
154
|
+
ok: false,
|
|
155
|
+
stopped: true,
|
|
156
|
+
reason: REASON_BOARD_QUERY_ERROR,
|
|
157
|
+
message: `Next Up query failed (${membership.reason}); refusing to fall back to Backlog/local order — nothing to run`,
|
|
148
158
|
boardConfigured: true,
|
|
149
|
-
reason: membership.reason ?? null,
|
|
150
159
|
results: [],
|
|
151
160
|
}));
|
|
152
161
|
return;
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
5
5
|
import { parsePositiveInteger, requireTokenValue, runCommand } from "../_cli-primitives.mjs";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
7
8
|
const DEFAULT_MAX_LINES = 1000;
|
|
8
9
|
const DEFAULT_DUPLICATE_WINDOW_LINES = 4;
|
|
9
10
|
const DEFAULT_BRANCH_THRESHOLD = 25;
|
|
@@ -35,7 +36,9 @@ Success output (stdout, JSON):
|
|
|
35
36
|
}
|
|
36
37
|
Failure behavior (stderr, JSON, exit 1):
|
|
37
38
|
- malformed arguments, blank paths, invalid thresholds, and zero auditable files fail closed
|
|
38
|
-
- findings are not a process failure; findings still return exit 0
|
|
39
|
+
- findings are not a process failure; findings still return exit 0
|
|
40
|
+
|
|
41
|
+
${JQ_OUTPUT_USAGE}`.trim();
|
|
39
42
|
const parseError = buildParseError(USAGE);
|
|
40
43
|
const BRANCH_TOKEN_PATTERN = /\b(?:if|else|switch|case|for|while|catch|finally|break|continue)\b|&&|\|\||\?(?![?.])/gu;
|
|
41
44
|
const PRIORITY_ORDER = new Map([
|
|
@@ -67,6 +70,7 @@ export function parseRefinementAuditCliArgs(argv) {
|
|
|
67
70
|
"branch-threshold": { type: "string" },
|
|
68
71
|
"thin-wrapper-max-lines": { type: "string" },
|
|
69
72
|
output: { type: "string" },
|
|
73
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
70
74
|
},
|
|
71
75
|
allowPositionals: true,
|
|
72
76
|
strict: false,
|
|
@@ -127,6 +131,7 @@ export function parseRefinementAuditCliArgs(argv) {
|
|
|
127
131
|
options.output = requireTokenValue(token, parseError, { flagPattern: /^-/u });
|
|
128
132
|
continue;
|
|
129
133
|
}
|
|
134
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
130
135
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
131
136
|
}
|
|
132
137
|
if (options.paths !== undefined && options.pathsFile !== undefined) {
|
|
@@ -512,22 +517,16 @@ export async function runCli(
|
|
|
512
517
|
fullRepoScan: false,
|
|
513
518
|
},
|
|
514
519
|
};
|
|
515
|
-
const serializedPayload = `${JSON.stringify(payload)}\n`;
|
|
516
520
|
if (typeof options.output === "string") {
|
|
517
521
|
const outputPath = path.resolve(cwd, options.output);
|
|
518
522
|
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
519
|
-
await writeFile(outputPath,
|
|
523
|
+
await writeFile(outputPath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
520
524
|
}
|
|
521
|
-
|
|
525
|
+
process.exitCode = emitResult(payload, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
522
526
|
return payload;
|
|
523
527
|
}
|
|
524
528
|
if (isDirectCliRun(import.meta.url)) {
|
|
525
529
|
runCli()
|
|
526
|
-
.then((result) => {
|
|
527
|
-
if (result?.ok === false) {
|
|
528
|
-
process.exitCode = 1;
|
|
529
|
-
}
|
|
530
|
-
})
|
|
531
530
|
.catch((error) => {
|
|
532
531
|
process.stderr.write(`${formatCliError(error)}\n`);
|
|
533
532
|
process.exitCode = 1;
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
EXTERNAL_HEALTHY_WAIT_TIMEOUT_POLICY,
|
|
16
16
|
enforceExternalHealthyWaitTimeout,
|
|
17
17
|
} from "@dev-loops/core/loop/timeout-policy";
|
|
18
|
-
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult } from "../lib/jq-output.mjs";
|
|
18
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
19
19
|
const REMOVED_FLAGS = new Set([
|
|
20
20
|
"--force-rerequest-review",
|
|
21
21
|
"--probe-only",
|
|
@@ -254,14 +254,7 @@ export function parseWatchCycleCliArgs(argv) {
|
|
|
254
254
|
options.concise = true;
|
|
255
255
|
continue;
|
|
256
256
|
}
|
|
257
|
-
if (token
|
|
258
|
-
options.jq = requireTokenValue(token, parseError);
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
if (token.name === "silent") {
|
|
262
|
-
options.silent = true;
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
257
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
265
258
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
266
259
|
}
|
|
267
260
|
if (options.repo === undefined || options.pr === undefined) {
|