dev-loops 0.2.7 → 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 +1 -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 +43 -0
- 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 +39 -6
- package/package.json +2 -2
- package/scripts/README.md +6 -5
- package/scripts/_core-helpers.mjs +1 -0
- package/scripts/claude/headless-dev-loop.mjs +53 -13
- package/scripts/claude/headless-info-smoke.mjs +45 -11
- package/scripts/github/build-adjacent-bundle.mjs +448 -0
- package/scripts/github/{create-draft-pr.mjs → create-pr.mjs} +28 -12
- package/scripts/github/detect-checkpoint-evidence.mjs +95 -4
- package/scripts/github/post-gate-findings.mjs +392 -0
- package/scripts/github/reconcile-draft-gate.mjs +2 -2
- package/scripts/github/request-copilot-review.mjs +69 -8
- package/scripts/github/upsert-checkpoint-verdict.mjs +597 -15
- 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 +1 -1
- package/scripts/loop/detect-change-scope.mjs +36 -11
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +30 -18
- package/scripts/loop/detect-tracker-first-loop-state.mjs +38 -11
- package/scripts/loop/run-queue.mjs +87 -15
- package/scripts/projects/add-queue-item.mjs +60 -43
- package/scripts/projects/archive-done-items.mjs +135 -39
- 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 +62 -44
- package/scripts/projects/sync-item-status.mjs +198 -0
- package/skills/copilot-pr-followup/SKILL.md +57 -3
- 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
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
isCopilotLogin,
|
|
7
7
|
isDirectCliRun,
|
|
8
8
|
parseReviewThreads,
|
|
9
|
+
resolveDraftGateRoundResetMs,
|
|
9
10
|
summarizeCopilotReviews,
|
|
11
|
+
summarizeGateReviewComments,
|
|
10
12
|
} from "../_core-helpers.mjs";
|
|
11
13
|
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
12
14
|
import { fetchGithubReviewThreadsPayload } from "./capture-review-threads.mjs";
|
|
@@ -121,7 +123,7 @@ function parseRequestedReviewersPayload(text) {
|
|
|
121
123
|
requested: users.some((user) => isCopilotLogin(user?.login)),
|
|
122
124
|
};
|
|
123
125
|
}
|
|
124
|
-
function parseReviewsPayload(text) {
|
|
126
|
+
function parseReviewsPayload(text, { draftGateResetAtMs = null } = {}) {
|
|
125
127
|
let payload;
|
|
126
128
|
try {
|
|
127
129
|
payload = JSON.parse(text);
|
|
@@ -131,7 +133,10 @@ function parseReviewsPayload(text) {
|
|
|
131
133
|
const headSha = typeof payload?.headRefOid === "string" && payload.headRefOid.trim().length > 0
|
|
132
134
|
? payload.headRefOid.trim()
|
|
133
135
|
: null;
|
|
134
|
-
|
|
136
|
+
// Apply the draft-gate round reset so the completed round count matches what
|
|
137
|
+
// detect-pr-gate-coordination-state computes (#896): when the draft gate has
|
|
138
|
+
// re-passed clean on an earlier head, only reviews after that re-pass count.
|
|
139
|
+
const reviewSummary = summarizeCopilotReviews(payload?.reviews, { headSha, draftGateResetAtMs });
|
|
135
140
|
return {
|
|
136
141
|
prData: payload,
|
|
137
142
|
headSha,
|
|
@@ -166,6 +171,51 @@ async function fetchCopilotReviewIds({ repo, pr }, { env = process.env, ghComman
|
|
|
166
171
|
}
|
|
167
172
|
return parseReviewsPayload(result.stdout);
|
|
168
173
|
}
|
|
174
|
+
|
|
175
|
+
// Re-derive the completed Copilot round count with the draft-gate round reset
|
|
176
|
+
// applied, mirroring detect-pr-gate-coordination-state so both scripts agree on the
|
|
177
|
+
// completed count and therefore on round-cap-reached (#896). A clean draft_gate
|
|
178
|
+
// re-pass on an earlier head resets the count, so post-reset reviews must not be
|
|
179
|
+
// counted toward the cap.
|
|
180
|
+
//
|
|
181
|
+
// Queried lazily — only when the raw (un-reset) count has already hit the cap — so
|
|
182
|
+
// the common (under-cap) request path keeps its existing gh-call contract and adds
|
|
183
|
+
// no API round-trip. Uses a single issue-comments fetch (the same source the gate
|
|
184
|
+
// detector uses for the latest clean draft_gate marker), not the full checkpoint-
|
|
185
|
+
// evidence pipeline, to keep the added surface minimal. Best-effort: a fetch failure
|
|
186
|
+
// falls back to the raw count, so the cap is never silently disabled.
|
|
187
|
+
async function resolveDraftGateAdjustedRounds(options, { env = process.env, ghCommand = "gh" } = {}, before) {
|
|
188
|
+
try {
|
|
189
|
+
const currentHeadSha = typeof before?.prData?.headRefOid === "string" && before.prData.headRefOid.trim().length > 0
|
|
190
|
+
? before.prData.headRefOid.trim()
|
|
191
|
+
: null;
|
|
192
|
+
const result = await runChild(
|
|
193
|
+
ghCommand,
|
|
194
|
+
["api", "--paginate", "--slurp", `repos/${options.repo}/issues/${options.pr}/comments?per_page=100`],
|
|
195
|
+
env,
|
|
196
|
+
);
|
|
197
|
+
if (result.code !== 0) {
|
|
198
|
+
return before.completedCopilotReviewRounds ?? 0;
|
|
199
|
+
}
|
|
200
|
+
let comments;
|
|
201
|
+
try {
|
|
202
|
+
const payload = JSON.parse(result.stdout);
|
|
203
|
+
comments = Array.isArray(payload) ? payload.flat() : [];
|
|
204
|
+
} catch {
|
|
205
|
+
return before.completedCopilotReviewRounds ?? 0;
|
|
206
|
+
}
|
|
207
|
+
const gateSummary = summarizeGateReviewComments(comments);
|
|
208
|
+
const draftGateResetAtMs = resolveDraftGateRoundResetMs({ draftGate: gateSummary?.draft_gate, currentHeadSha });
|
|
209
|
+
if (draftGateResetAtMs == null) {
|
|
210
|
+
return before.completedCopilotReviewRounds ?? 0;
|
|
211
|
+
}
|
|
212
|
+
const adjusted = parseReviewsPayload(JSON.stringify(before.prData ?? {}), { draftGateResetAtMs });
|
|
213
|
+
return adjusted.completedCopilotReviewRounds ?? 0;
|
|
214
|
+
} catch {
|
|
215
|
+
return before.completedCopilotReviewRounds ?? 0;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
169
219
|
async function fetchCopilotReviewState(options, runtime) {
|
|
170
220
|
const requestedReviewers = await fetchRequestedReviewers(options, runtime);
|
|
171
221
|
const reviews = await fetchCopilotReviewIds(options, runtime);
|
|
@@ -434,7 +484,18 @@ export async function performCopilotReviewRequest(options, { env = process.env,
|
|
|
434
484
|
}
|
|
435
485
|
} catch {
|
|
436
486
|
}
|
|
437
|
-
|
|
487
|
+
// Reconcile the completed-round count with detect-pr-gate-coordination-state (#896):
|
|
488
|
+
// when the raw count has reached the cap, re-derive it with the draft-gate round
|
|
489
|
+
// reset applied. A clean draft_gate re-pass on an earlier head resets the count, so
|
|
490
|
+
// a post-reset PR that detect reports as under-cap must NOT be refused here as
|
|
491
|
+
// cap-reached. Only query checkpoint evidence on this (at/over-cap) path.
|
|
492
|
+
let completedRounds = before.completedCopilotReviewRounds ?? 0;
|
|
493
|
+
if (completedRounds >= maxRounds
|
|
494
|
+
&& !before.requested
|
|
495
|
+
&& !before.hasPendingReviewOnCurrentHead) {
|
|
496
|
+
completedRounds = await resolveDraftGateAdjustedRounds(options, { env, ghCommand }, before);
|
|
497
|
+
}
|
|
498
|
+
if (completedRounds >= maxRounds
|
|
438
499
|
&& !before.requested
|
|
439
500
|
&& !before.hasPendingReviewOnCurrentHead) {
|
|
440
501
|
if (!options.forceRerequestReview) {
|
|
@@ -451,9 +512,9 @@ export async function performCopilotReviewRequest(options, { env = process.env,
|
|
|
451
512
|
repo: options.repo,
|
|
452
513
|
pr: options.pr,
|
|
453
514
|
reviewer: "Copilot",
|
|
454
|
-
completedRounds
|
|
515
|
+
completedRounds,
|
|
455
516
|
maxRounds,
|
|
456
|
-
detail: `Round cap of ${maxRounds} reached with ${
|
|
517
|
+
detail: `Round cap of ${maxRounds} reached with ${completedRounds} completed rounds. No further re-requests will be made.`,
|
|
457
518
|
};
|
|
458
519
|
}
|
|
459
520
|
}
|
|
@@ -471,8 +532,8 @@ export async function performCopilotReviewRequest(options, { env = process.env,
|
|
|
471
532
|
repo: options.repo,
|
|
472
533
|
pr: options.pr,
|
|
473
534
|
reviewer: "Copilot",
|
|
474
|
-
detail: `Round cap of ${maxRounds} reached with ${
|
|
475
|
-
completedRounds
|
|
535
|
+
detail: `Round cap of ${maxRounds} reached with ${completedRounds} completed rounds. --force-rerequest-review was supplied but commit SHA data is unavailable, so change-since-last-review could not be evaluated.`,
|
|
536
|
+
completedRounds,
|
|
476
537
|
maxRounds,
|
|
477
538
|
};
|
|
478
539
|
}
|
|
@@ -484,7 +545,7 @@ export async function performCopilotReviewRequest(options, { env = process.env,
|
|
|
484
545
|
pr: options.pr,
|
|
485
546
|
reviewer: "Copilot",
|
|
486
547
|
detail: "No changes since last Copilot review. --force-rerequest-review requires new commits on the PR head.",
|
|
487
|
-
completedRounds
|
|
548
|
+
completedRounds,
|
|
488
549
|
maxRounds,
|
|
489
550
|
};
|
|
490
551
|
}
|