dev-loops 0.6.0 → 0.7.2
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 +8 -7
- package/.claude/agents/fixer.md +1 -0
- package/.claude/agents/refiner.md +2 -2
- package/.claude/agents/review.md +4 -3
- package/.claude/commands/{auto.md → loop-auto.md} +1 -1
- 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/{info.md → loop-info.md} +2 -2
- package/.claude/commands/loop-queue-status.md +24 -0
- package/.claude/commands/{start-spike.md → loop-start-spike.md} +2 -2
- package/.claude/commands/{start.md → loop-start.md} +1 -1
- package/.claude/commands/{status.md → loop-status.md} +1 -1
- package/.claude/hooks/_bash-command-classify.mjs +333 -29
- package/.claude/hooks/_hook-decisions.mjs +138 -15
- package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
- package/.claude/skills/copilot-pr-followup/SKILL.md +60 -34
- package/.claude/skills/dev-loop/SKILL.md +16 -12
- package/.claude/skills/docs/acceptance-criteria-verification.md +8 -3
- package/.claude/skills/docs/anti-patterns.md +5 -3
- package/.claude/skills/docs/artifact-authority-contract.md +30 -11
- package/.claude/skills/docs/confirmation-rules.md +1 -0
- package/.claude/skills/docs/contract-style-guide.md +37 -0
- package/.claude/skills/docs/copilot-loop-operations.md +19 -15
- package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
- package/.claude/skills/docs/debt-remediation-contract.md +1 -1
- package/.claude/skills/docs/epic-tree-refinement-procedure.md +25 -22
- package/.claude/skills/docs/issue-intake-procedure.md +26 -19
- 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 +30 -12
- package/.claude/skills/docs/plan-file-contract.md +1 -1
- package/.claude/skills/docs/pr-lifecycle-contract.md +44 -35
- package/.claude/skills/docs/public-dev-loop-contract.md +60 -44
- package/.claude/skills/docs/retrospective-checkpoint-contract.md +106 -79
- package/.claude/skills/docs/spike-mode-contract.md +14 -7
- package/.claude/skills/docs/stop-conditions.md +30 -15
- package/.claude/skills/docs/tracker-first-loop-state.md +13 -8
- package/.claude/skills/docs/ui-e2e-scoping-step.md +32 -0
- package/.claude/skills/docs/validation-policy.md +4 -5
- package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
- package/.claude/skills/local-implementation/SKILL.md +62 -145
- package/.claude/skills/loop-grill/SKILL.md +163 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +101 -0
- package/README.md +9 -9
- package/agents/dev-loop.agent.md +11 -10
- package/agents/developer.agent.md +1 -1
- package/agents/docs.agent.md +1 -1
- package/agents/fixer.agent.md +2 -1
- package/agents/quality.agent.md +1 -1
- package/agents/refiner.agent.md +3 -3
- package/agents/review.agent.md +5 -4
- package/cli/index.mjs +35 -42
- package/extension/README.md +4 -4
- package/extension/checks.ts +1 -5
- package/extension/harness-types.ts +1 -0
- package/extension/index.ts +6 -0
- package/extension/pi-extension-adapter.ts +2 -0
- package/extension/presentation.ts +7 -19
- package/package.json +12 -12
- package/scripts/_core-helpers.mjs +2 -0
- package/scripts/claude/headless-dev-loop.mjs +1 -1
- package/scripts/docs/validate-rule-ownership.mjs +442 -0
- package/scripts/docs/validate-state-machine-conformance.mjs +1092 -0
- package/scripts/github/_review-thread-mutations.mjs +5 -2
- package/scripts/github/capture-review-threads.mjs +2 -9
- package/scripts/github/comment-issue.mjs +2 -9
- package/scripts/github/create-label.mjs +133 -0
- package/scripts/github/create-pr.mjs +119 -10
- package/scripts/github/detect-checkpoint-evidence.mjs +259 -18
- 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 +2 -9
- package/scripts/github/list-issues.mjs +2 -9
- package/scripts/github/list-review-threads.mjs +277 -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 +20 -5
- 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 +98 -26
- 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 +105 -15
- package/scripts/github/verify-briefing-prefixes.mjs +191 -0
- package/scripts/github/verify-fresh-review-context.mjs +226 -32
- package/scripts/github/view-pr.mjs +150 -0
- package/scripts/github/wait-pr-checks.mjs +171 -0
- package/scripts/github/write-gate-context.mjs +458 -65
- package/scripts/github/write-gate-findings-log.mjs +128 -2
- package/scripts/lib/jq-output.mjs +18 -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 +162 -14
- package/scripts/loop/debt-remediate.mjs +24 -12
- package/scripts/loop/detect-change-scope.mjs +38 -9
- package/scripts/loop/detect-copilot-loop-state.mjs +34 -8
- 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 +184 -69
- 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 +216 -10
- 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 +8 -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 +106 -0
- package/scripts/loop/steer-loop.mjs +29 -16
- package/scripts/loop/validate-pr-body-spec.mjs +223 -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 +443 -0
- package/scripts/projects/_resolve-project.mjs +1 -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 +59 -382
- package/scripts/projects/move-queue-item.mjs +21 -449
- package/scripts/projects/reconcile-queue.mjs +253 -0
- package/scripts/projects/reorder-queue-item.mjs +43 -68
- package/scripts/projects/resolve-active-board-item.mjs +108 -46
- 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 +4 -8
- 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 +16 -2
- package/skills/copilot-pr-followup/SKILL.md +60 -34
- package/skills/dev-loop/SKILL.md +11 -7
- package/skills/docs/acceptance-criteria-verification.md +8 -3
- package/skills/docs/anti-patterns.md +5 -3
- package/skills/docs/artifact-authority-contract.md +30 -11
- package/skills/docs/confirmation-rules.md +1 -0
- package/skills/docs/contract-style-guide.md +37 -0
- package/skills/docs/copilot-loop-operations.md +19 -15
- package/skills/docs/cross-harness-regression-contract.md +60 -0
- package/skills/docs/debt-remediation-contract.md +1 -1
- package/skills/docs/epic-tree-refinement-procedure.md +25 -22
- package/skills/docs/issue-intake-procedure.md +26 -19
- 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 +30 -12
- package/skills/docs/plan-file-contract.md +1 -1
- package/skills/docs/pr-lifecycle-contract.md +44 -35
- package/skills/docs/public-dev-loop-contract.md +60 -44
- package/skills/docs/required-rules.json +149 -0
- package/skills/docs/retrospective-checkpoint-contract.md +106 -79
- package/skills/docs/spike-mode-contract.md +14 -7
- package/skills/docs/stop-conditions.md +30 -15
- package/skills/docs/tracker-first-loop-state.md +13 -8
- package/skills/docs/ui-e2e-scoping-step.md +32 -0
- package/skills/docs/validation-policy.md +4 -5
- package/skills/docs/workflow-handoff-contract.md +39 -0
- package/skills/local-implementation/SKILL.md +62 -145
- package/skills/loop-grill/SKILL.md +165 -0
- package/.claude/commands/continue.md +0 -15
- package/scripts/docs/validate-no-duplicate-rules.mjs +0 -250
- 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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
5
6
|
|
|
6
7
|
const USAGE = `Usage: detect-change-scope.mjs [--base <ref>] [--head <ref>]
|
|
7
8
|
Detect change scope from git diff for light-mode eligibility.
|
|
@@ -9,9 +10,13 @@ Options:
|
|
|
9
10
|
--base <ref> Override base ref (default: HEAD~1)
|
|
10
11
|
--head <ref> Override head ref; ignored unless --base is also set
|
|
11
12
|
--help, -h Show this help
|
|
13
|
+
|
|
14
|
+
${JQ_OUTPUT_USAGE}
|
|
15
|
+
|
|
12
16
|
Exit codes:
|
|
13
17
|
0 Success
|
|
14
18
|
1 Error
|
|
19
|
+
2 Invalid --jq filter
|
|
15
20
|
`;
|
|
16
21
|
|
|
17
22
|
function parseCliArgs(argv) {
|
|
@@ -21,6 +26,7 @@ function parseCliArgs(argv) {
|
|
|
21
26
|
base: { type: "string" },
|
|
22
27
|
head: { type: "string" },
|
|
23
28
|
help: { type: "boolean", short: "h" },
|
|
29
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
24
30
|
},
|
|
25
31
|
allowPositionals: true,
|
|
26
32
|
strict: false,
|
|
@@ -43,7 +49,9 @@ function parseCliArgs(argv) {
|
|
|
43
49
|
}
|
|
44
50
|
if (token.name === "head") {
|
|
45
51
|
opts.head = token.value ?? null;
|
|
52
|
+
continue;
|
|
46
53
|
}
|
|
54
|
+
if (matchJqOutputToken(token, opts)) continue;
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
57
|
return opts;
|
|
@@ -67,7 +75,7 @@ export function parseGitDiffStat(output) {
|
|
|
67
75
|
}
|
|
68
76
|
return { filesChanged: fileCount, linesChanged: insertions + deletions };
|
|
69
77
|
}
|
|
70
|
-
function detectScope({ base, head } = {}) {
|
|
78
|
+
function detectScope({ base, head, cwd } = {}) {
|
|
71
79
|
let diffArgs = ["diff", "--stat"];
|
|
72
80
|
if (base && head) {
|
|
73
81
|
diffArgs.push(`${base}..${head}`);
|
|
@@ -78,7 +86,7 @@ function detectScope({ base, head } = {}) {
|
|
|
78
86
|
}
|
|
79
87
|
let output;
|
|
80
88
|
try {
|
|
81
|
-
output = execFileSync("git", diffArgs, { encoding: "utf8", maxBuffer: 1_000_000 });
|
|
89
|
+
output = execFileSync("git", diffArgs, { encoding: "utf8", maxBuffer: 1_000_000, cwd: cwd || undefined });
|
|
82
90
|
} catch (err) {
|
|
83
91
|
return { ok: false, filesChanged: 0, linesChanged: 0, error: err instanceof Error ? err.message : String(err) };
|
|
84
92
|
}
|
|
@@ -88,6 +96,27 @@ function detectScope({ base, head } = {}) {
|
|
|
88
96
|
function isEligibleForLightMode(scope, threshold) {
|
|
89
97
|
return scope.filesChanged <= threshold.maxFiles && scope.linesChanged <= threshold.maxLines;
|
|
90
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Detect change scope for the merge-base diff between `base` and `head` (the
|
|
101
|
+
* three-dot `base...head` diff git resolves against merge-base(base, head)).
|
|
102
|
+
*
|
|
103
|
+
* Fails CLOSED: a missing base/head, or any git failure, returns `{ ok: false }`
|
|
104
|
+
* so callers that gate on scope (e.g. the light-mode pre-merge acceptance) reject
|
|
105
|
+
* rather than silently treating an unmeasurable diff as under threshold. Reuses
|
|
106
|
+
* the same `parseGitDiffStat` scope resolution as `detectScope`.
|
|
107
|
+
*/
|
|
108
|
+
function detectMergeBaseScope({ base, head, cwd } = {}) {
|
|
109
|
+
if (!base || !head) {
|
|
110
|
+
return { ok: false, filesChanged: 0, linesChanged: 0, error: "base and head are required for merge-base scope detection" };
|
|
111
|
+
}
|
|
112
|
+
let output;
|
|
113
|
+
try {
|
|
114
|
+
output = execFileSync("git", ["diff", "--stat", `${base}...${head}`], { encoding: "utf8", maxBuffer: 1_000_000, cwd: cwd || undefined });
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return { ok: false, filesChanged: 0, linesChanged: 0, error: err instanceof Error ? err.message : String(err) };
|
|
117
|
+
}
|
|
118
|
+
return { ok: true, ...parseGitDiffStat(output) };
|
|
119
|
+
}
|
|
91
120
|
async function main() {
|
|
92
121
|
const opts = parseCliArgs(process.argv.slice(2));
|
|
93
122
|
const scope = detectScope(opts);
|
|
@@ -108,12 +137,12 @@ async function main() {
|
|
|
108
137
|
}
|
|
109
138
|
} catch {
|
|
110
139
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
// This tool always exits 0 on a parsed run (scope.ok:false only reflects a git
|
|
141
|
+
// diff failure inside the payload, never the process outcome) — force ok:true
|
|
142
|
+
// as emitResult's default so --jq/--silent compose without changing that.
|
|
143
|
+
process.exitCode = emitResult(
|
|
144
|
+
{ ...scope, eligibleForLightMode: eligible, threshold },
|
|
145
|
+
{ jq: opts.jq, silent: opts.silent, ok: true },
|
|
117
146
|
);
|
|
118
147
|
}
|
|
119
148
|
const isDirectRun =
|
|
@@ -124,4 +153,4 @@ if (isDirectRun) {
|
|
|
124
153
|
process.exitCode = 1;
|
|
125
154
|
});
|
|
126
155
|
}
|
|
127
|
-
export { detectScope, isEligibleForLightMode };
|
|
156
|
+
export { detectScope, detectMergeBaseScope, isEligibleForLightMode };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
3
|
import { parseArgs } from "node:util";
|
|
5
4
|
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
6
5
|
import {
|
|
@@ -15,7 +14,7 @@ import {
|
|
|
15
14
|
} from "../_core-helpers.mjs";
|
|
16
15
|
import { fetchGithubReviewThreadsPayload } from "../github/capture-review-threads.mjs";
|
|
17
16
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
18
|
-
import { loadDevLoopConfig, resolveRefinement } from "@dev-loops/core/config";
|
|
17
|
+
import { loadDevLoopConfig, resolveEffectiveCopilotRoundCap, resolveRefinement } from "@dev-loops/core/config";
|
|
19
18
|
import {
|
|
20
19
|
buildSnapshotFromPrFacts,
|
|
21
20
|
interpretLoopState,
|
|
@@ -28,6 +27,8 @@ import {
|
|
|
28
27
|
normalizeHeadScopedCommitStatus,
|
|
29
28
|
normalizeHeadScopedCiContract,
|
|
30
29
|
} from "@dev-loops/core/loop/copilot-ci-status";
|
|
30
|
+
import { resolveRepoRoot } from "./_repo-root-resolver.mjs";
|
|
31
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
31
32
|
const USAGE = `Usage:
|
|
32
33
|
detect-copilot-loop-state.mjs --repo <owner/name> --pr <number>
|
|
33
34
|
detect-copilot-loop-state.mjs --input <path>
|
|
@@ -42,8 +43,13 @@ Required (auto-detect mode):
|
|
|
42
43
|
--pr <number> Pull request number
|
|
43
44
|
Required (snapshot mode):
|
|
44
45
|
--input <path> Path to snapshot JSON file
|
|
45
|
-
Optional (
|
|
46
|
-
|
|
46
|
+
Optional (either mode):
|
|
47
|
+
--lightweight This PR is light-dispatched (#1210): compose the Copilot
|
|
48
|
+
round cap with localImplementation.lightMode.maxCopilotRounds
|
|
49
|
+
(default 1) via min(lightMode.maxCopilotRounds,
|
|
50
|
+
refinement.maxCopilotRounds) instead of using
|
|
51
|
+
refinement.maxCopilotRounds alone. refinement.maxCopilotRounds:
|
|
52
|
+
0 still disables Copilot rounds even with --lightweight.
|
|
47
53
|
Output (stdout, JSON):
|
|
48
54
|
{ "ok": true, "snapshot": {..., "copilotReviewRoundCount": N}, "state": "...", "allowedTransitions": [...], "nextAction": "...",
|
|
49
55
|
"autoRerequestEligible": true|false, "sameHeadCleanConverged": true|false,
|
|
@@ -53,9 +59,11 @@ Error output (stderr, JSON):
|
|
|
53
59
|
{ "ok": false, "error": "...", "usage": "..." }
|
|
54
60
|
gh/runtime failures:
|
|
55
61
|
{ "ok": false, "error": "..." }
|
|
62
|
+
${JQ_OUTPUT_USAGE}
|
|
56
63
|
Exit codes:
|
|
57
64
|
0 Success
|
|
58
|
-
1 Argument error, gh failure, or indeterminate state
|
|
65
|
+
1 Argument error, gh failure, or indeterminate state
|
|
66
|
+
2 Invalid --jq filter`.trim();
|
|
59
67
|
const VALID_OVERRIDE_STATUSES = new Set(["requested", "already-requested", "unavailable", "none", "failed"]);
|
|
60
68
|
const parseError = buildParseError(USAGE);
|
|
61
69
|
export function parseDetectCliArgs(argv) {
|
|
@@ -64,6 +72,7 @@ export function parseDetectCliArgs(argv) {
|
|
|
64
72
|
inputPath: undefined,
|
|
65
73
|
repo: undefined,
|
|
66
74
|
pr: undefined,
|
|
75
|
+
lightweight: false,
|
|
67
76
|
};
|
|
68
77
|
const { tokens } = parseArgs({
|
|
69
78
|
args: [...argv],
|
|
@@ -72,6 +81,8 @@ export function parseDetectCliArgs(argv) {
|
|
|
72
81
|
input: { type: "string" },
|
|
73
82
|
repo: { type: "string" },
|
|
74
83
|
pr: { type: "string" },
|
|
84
|
+
lightweight: { type: "boolean" },
|
|
85
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
75
86
|
},
|
|
76
87
|
allowPositionals: true,
|
|
77
88
|
strict: false,
|
|
@@ -100,6 +111,11 @@ export function parseDetectCliArgs(argv) {
|
|
|
100
111
|
options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
|
|
101
112
|
continue;
|
|
102
113
|
}
|
|
114
|
+
if (token.name === "lightweight") {
|
|
115
|
+
options.lightweight = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
103
119
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
104
120
|
}
|
|
105
121
|
if (options.inputPath !== undefined) {
|
|
@@ -419,6 +435,7 @@ export async function runCli(
|
|
|
419
435
|
argv = process.argv.slice(2),
|
|
420
436
|
{
|
|
421
437
|
stdout = process.stdout,
|
|
438
|
+
stderr = process.stderr,
|
|
422
439
|
env = process.env,
|
|
423
440
|
ghCommand = "gh",
|
|
424
441
|
} = {},
|
|
@@ -445,13 +462,22 @@ export async function runCli(
|
|
|
445
462
|
interpretationInput = snapshot;
|
|
446
463
|
}
|
|
447
464
|
let interpretation;
|
|
448
|
-
const config = await loadDevLoopConfig({ repoRoot:
|
|
465
|
+
const config = await loadDevLoopConfig({ repoRoot: resolveRepoRoot(process.cwd()) });
|
|
449
466
|
const refinementConfig = config.errors.length > 0
|
|
450
467
|
? resolveRefinement({ version: 1 })
|
|
451
468
|
: resolveRefinement(config.config);
|
|
469
|
+
if (options.lightweight) {
|
|
470
|
+
// Compose (not replace) the round cap for light-dispatched PRs (#1210):
|
|
471
|
+
// min(lightMode.maxCopilotRounds ?? 1, refinement.maxCopilotRounds), so
|
|
472
|
+
// maxCopilotRounds: 0 still disables Copilot rounds everywhere.
|
|
473
|
+
refinementConfig.maxCopilotRounds = resolveEffectiveCopilotRoundCap(
|
|
474
|
+
config.errors.length > 0 ? { version: 1 } : config.config,
|
|
475
|
+
{ lightweight: true },
|
|
476
|
+
);
|
|
477
|
+
}
|
|
452
478
|
interpretation = interpretLoopState(interpretationInput, refinementConfig);
|
|
453
479
|
const interpretationSummary = summarizeLoopInterpretation(interpretation);
|
|
454
|
-
|
|
480
|
+
process.exitCode = emitResult({
|
|
455
481
|
ok: true,
|
|
456
482
|
snapshot,
|
|
457
483
|
state: interpretation.state,
|
|
@@ -461,7 +487,7 @@ export async function runCli(
|
|
|
461
487
|
sameHeadCleanConverged: interpretation.sameHeadCleanConverged,
|
|
462
488
|
loopDisposition: interpretationSummary.loopDisposition,
|
|
463
489
|
terminal: interpretationSummary.terminal,
|
|
464
|
-
}
|
|
490
|
+
}, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
465
491
|
}
|
|
466
492
|
if (isDirectCliRun(import.meta.url)) {
|
|
467
493
|
runCli().catch((error) => {
|
|
@@ -3,6 +3,7 @@ import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "
|
|
|
3
3
|
import { parsePositiveInteger, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
4
4
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
6
7
|
const USAGE = `Usage: detect-copilot-session-activity.mjs --repo <owner/name> --branch <name> [--limit <number>]
|
|
7
8
|
Detect Copilot GitHub Actions session activity on a branch.
|
|
8
9
|
Required:
|
|
@@ -32,7 +33,8 @@ Error output (stderr, JSON):
|
|
|
32
33
|
Argument/usage errors:
|
|
33
34
|
{ "ok": false, "error": "...", "usage": "..." }
|
|
34
35
|
gh/runtime failures:
|
|
35
|
-
{ "ok": false, "error": "..." }
|
|
36
|
+
{ "ok": false, "error": "..." }
|
|
37
|
+
${JQ_OUTPUT_USAGE}`.trim();
|
|
36
38
|
const DEFAULT_LIMIT = 20;
|
|
37
39
|
const ACTIVE_RUN_STATUSES = new Set(["queued", "in_progress", "pending", "requested", "waiting"]);
|
|
38
40
|
const COPILOT_RUN_NAME_PATTERNS = Object.freeze([
|
|
@@ -55,6 +57,7 @@ export function parseDetectCopilotSessionActivityCliArgs(argv) {
|
|
|
55
57
|
repo: { type: "string" },
|
|
56
58
|
branch: { type: "string" },
|
|
57
59
|
limit: { type: "string" },
|
|
60
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
58
61
|
},
|
|
59
62
|
allowPositionals: true,
|
|
60
63
|
strict: false,
|
|
@@ -83,6 +86,7 @@ export function parseDetectCopilotSessionActivityCliArgs(argv) {
|
|
|
83
86
|
options.limit = parsePositiveInteger(requireTokenValue(token, parseError), "--limit", parseError);
|
|
84
87
|
continue;
|
|
85
88
|
}
|
|
89
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
86
90
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
87
91
|
}
|
|
88
92
|
if (options.repo === undefined || options.branch === undefined || options.branch.length === 0) {
|
|
@@ -185,7 +189,7 @@ export async function detectCopilotSessionActivity({ repo, branch, limit = DEFAU
|
|
|
185
189
|
}
|
|
186
190
|
export async function runCli(
|
|
187
191
|
argv = process.argv.slice(2),
|
|
188
|
-
{ stdout = process.stdout, env = process.env, ghCommand = "gh" } = {},
|
|
192
|
+
{ stdout = process.stdout, stderr = process.stderr, env = process.env, ghCommand = "gh" } = {},
|
|
189
193
|
) {
|
|
190
194
|
const options = parseDetectCopilotSessionActivityCliArgs(argv);
|
|
191
195
|
if (options.help) {
|
|
@@ -193,7 +197,7 @@ export async function runCli(
|
|
|
193
197
|
return;
|
|
194
198
|
}
|
|
195
199
|
const result = await detectCopilotSessionActivity(options, { env, ghCommand });
|
|
196
|
-
|
|
200
|
+
process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
197
201
|
}
|
|
198
202
|
if (isDirectCliRun(import.meta.url)) {
|
|
199
203
|
runCli().catch((error) => {
|
|
@@ -5,6 +5,7 @@ import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
|
5
5
|
import { detectLinkedIssuePr } from "../github/detect-linked-issue-pr.mjs";
|
|
6
6
|
import { detectCopilotSessionActivity } from "./detect-copilot-session-activity.mjs";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
8
9
|
const USAGE = `Usage: detect-initial-copilot-pr-state.mjs --repo <owner/name> --issue <number>
|
|
9
10
|
Detect whether an assigned issue is still on the bootstrap-only Copilot draft PR
|
|
10
11
|
or has moved into normal linked-PR follow-up.
|
|
@@ -43,7 +44,8 @@ Error output (stderr, JSON):
|
|
|
43
44
|
Argument/usage errors:
|
|
44
45
|
{ "ok": false, "error": "...", "usage": "..." }
|
|
45
46
|
gh/runtime failures:
|
|
46
|
-
{ "ok": false, "error": "..." }
|
|
47
|
+
{ "ok": false, "error": "..." }
|
|
48
|
+
${JQ_OUTPUT_USAGE}`.trim();
|
|
47
49
|
export const LINKED_PR_STATE = Object.freeze({
|
|
48
50
|
NO_LINKED_PR: "no_linked_pr",
|
|
49
51
|
PRIOR_LINKED_PR_CLOSED_UNMERGED: "prior_linked_pr_closed_unmerged",
|
|
@@ -91,6 +93,7 @@ export function parseDetectInitialCopilotPrStateCliArgs(argv) {
|
|
|
91
93
|
help: { type: "boolean", short: "h" },
|
|
92
94
|
repo: { type: "string" },
|
|
93
95
|
issue: { type: "string" },
|
|
96
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
94
97
|
},
|
|
95
98
|
allowPositionals: true,
|
|
96
99
|
strict: false,
|
|
@@ -115,6 +118,7 @@ export function parseDetectInitialCopilotPrStateCliArgs(argv) {
|
|
|
115
118
|
options.issue = parseIssueNumber(requireTokenValue(token, parseError), parseError);
|
|
116
119
|
continue;
|
|
117
120
|
}
|
|
121
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
118
122
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
119
123
|
}
|
|
120
124
|
if (options.repo === undefined || options.issue === undefined) {
|
|
@@ -313,7 +317,7 @@ export async function detectInitialCopilotPrState({ repo, issue }, { env = proce
|
|
|
313
317
|
}
|
|
314
318
|
export async function runCli(
|
|
315
319
|
argv = process.argv.slice(2),
|
|
316
|
-
{ stdout = process.stdout, env = process.env, ghCommand = "gh" } = {},
|
|
320
|
+
{ stdout = process.stdout, stderr = process.stderr, env = process.env, ghCommand = "gh" } = {},
|
|
317
321
|
) {
|
|
318
322
|
const options = parseDetectInitialCopilotPrStateCliArgs(argv);
|
|
319
323
|
if (options.help) {
|
|
@@ -324,7 +328,7 @@ export async function runCli(
|
|
|
324
328
|
{ repo: options.repo, issue: options.issue },
|
|
325
329
|
{ env, ghCommand },
|
|
326
330
|
);
|
|
327
|
-
|
|
331
|
+
process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
328
332
|
}
|
|
329
333
|
if (isDirectCliRun(import.meta.url)) {
|
|
330
334
|
runCli().catch((error) => {
|
|
@@ -6,6 +6,7 @@ import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helper
|
|
|
6
6
|
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
7
7
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
9
10
|
|
|
10
11
|
const USAGE = `Usage: detect-internal-only-pr.mjs --repo <owner/name> --pr <number> [--config <path>]
|
|
11
12
|
Detect whether a PR only touches internal tooling files (scripts, docs, tests, config)
|
|
@@ -20,9 +21,11 @@ Optional:
|
|
|
20
21
|
Output (stdout, JSON):
|
|
21
22
|
{ "ok": true, "internalOnly": true|false, "files": ["path1", "path2", ...],
|
|
22
23
|
"reason": "...", "repo": "...", "pr": N }
|
|
24
|
+
${JQ_OUTPUT_USAGE}
|
|
23
25
|
Exit codes:
|
|
24
26
|
0 Success
|
|
25
|
-
1 Argument error or gh failure
|
|
27
|
+
1 Argument error or gh failure
|
|
28
|
+
2 Invalid --jq filter`.trim();
|
|
26
29
|
|
|
27
30
|
const parseError = buildParseError(USAGE);
|
|
28
31
|
|
|
@@ -134,6 +137,7 @@ export function parseCliArgs(argv) {
|
|
|
134
137
|
pr: { type: "string" },
|
|
135
138
|
config: { type: "string" },
|
|
136
139
|
"label-check": { type: "boolean" },
|
|
140
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
137
141
|
},
|
|
138
142
|
allowPositionals: true,
|
|
139
143
|
strict: false,
|
|
@@ -166,6 +170,7 @@ export function parseCliArgs(argv) {
|
|
|
166
170
|
options.labelCheck = true;
|
|
167
171
|
continue;
|
|
168
172
|
}
|
|
173
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
169
174
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
170
175
|
}
|
|
171
176
|
if (options.repo === undefined || options.pr === undefined) {
|
|
@@ -265,6 +270,7 @@ export async function runCli(
|
|
|
265
270
|
argv = process.argv.slice(2),
|
|
266
271
|
{
|
|
267
272
|
stdout = process.stdout,
|
|
273
|
+
stderr = process.stderr,
|
|
268
274
|
env = process.env,
|
|
269
275
|
ghCommand = "gh",
|
|
270
276
|
} = {},
|
|
@@ -275,7 +281,7 @@ export async function runCli(
|
|
|
275
281
|
return;
|
|
276
282
|
}
|
|
277
283
|
const result = await detectInternalOnly(options, { env, ghCommand });
|
|
278
|
-
|
|
284
|
+
process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
279
285
|
}
|
|
280
286
|
|
|
281
287
|
if (isDirectCliRun(import.meta.url)) {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
detectIssueRefinementArtifact,
|
|
9
9
|
REFINEMENT_SOURCE,
|
|
10
10
|
} from "@dev-loops/core/loop/issue-refinement-artifact";
|
|
11
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
11
12
|
const USAGE = `Usage:
|
|
12
13
|
detect-issue-refinement-artifact.mjs --repo <owner/name> --issue <number>
|
|
13
14
|
detect-issue-refinement-artifact.mjs --input <path>
|
|
@@ -31,7 +32,8 @@ Success output (stdout, JSON):
|
|
|
31
32
|
"reason": "..."
|
|
32
33
|
}
|
|
33
34
|
Error output (stderr, JSON):
|
|
34
|
-
{ "ok": false, "error": "...", "usage": "..." }
|
|
35
|
+
{ "ok": false, "error": "...", "usage": "..." }
|
|
36
|
+
${JQ_OUTPUT_USAGE}`.trim();
|
|
35
37
|
const parseError = buildParseError(USAGE);
|
|
36
38
|
export function parseDetectIssueRefinementArtifactCliArgs(argv) {
|
|
37
39
|
const options = {
|
|
@@ -47,6 +49,7 @@ export function parseDetectIssueRefinementArtifactCliArgs(argv) {
|
|
|
47
49
|
repo: { type: "string" },
|
|
48
50
|
issue: { type: "string" },
|
|
49
51
|
input: { type: "string" },
|
|
52
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
50
53
|
},
|
|
51
54
|
allowPositionals: true,
|
|
52
55
|
strict: false,
|
|
@@ -79,6 +82,7 @@ export function parseDetectIssueRefinementArtifactCliArgs(argv) {
|
|
|
79
82
|
options.input = requireTokenValue(token, parseError).trim();
|
|
80
83
|
continue;
|
|
81
84
|
}
|
|
85
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
82
86
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
83
87
|
}
|
|
84
88
|
const hasInput = typeof options.input === "string" && options.input.length > 0;
|
|
@@ -165,8 +169,7 @@ export async function runCli(
|
|
|
165
169
|
}
|
|
166
170
|
try {
|
|
167
171
|
const result = await detectIssueRefinementArtifactFromOptions(options, { env, ghCommand });
|
|
168
|
-
|
|
169
|
-
return 0;
|
|
172
|
+
return emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
170
173
|
} catch (error) {
|
|
171
174
|
stderr.write(`${formatCliError(error)}\n`);
|
|
172
175
|
return 1;
|