dev-loops 0.1.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.
Files changed (156) hide show
  1. package/.pi/dev-loop/defaults.yaml +477 -0
  2. package/AGENTS.md +25 -0
  3. package/CHANGELOG.md +18 -0
  4. package/LICENSE +21 -0
  5. package/README.md +178 -0
  6. package/agents/dev-loop.agent.md +82 -0
  7. package/agents/developer.agent.md +37 -0
  8. package/agents/docs.agent.md +33 -0
  9. package/agents/fixer.agent.md +53 -0
  10. package/agents/quality.agent.md +28 -0
  11. package/agents/refiner.agent.md +87 -0
  12. package/agents/review.agent.md +64 -0
  13. package/cli/index.mjs +424 -0
  14. package/extension/README.md +233 -0
  15. package/extension/checks.ts +94 -0
  16. package/extension/index.ts +131 -0
  17. package/extension/post-merge-update.ts +512 -0
  18. package/extension/presentation.ts +107 -0
  19. package/lib/dev-loops-core.mjs +284 -0
  20. package/package.json +103 -0
  21. package/scripts/README.md +1007 -0
  22. package/scripts/_cli-primitives.mjs +10 -0
  23. package/scripts/_core-helpers.mjs +30 -0
  24. package/scripts/docs/validate-links.mjs +567 -0
  25. package/scripts/docs/validate-no-duplicate-rules.mjs +250 -0
  26. package/scripts/github/_review-thread-mutations.mjs +214 -0
  27. package/scripts/github/capture-review-threads.mjs +180 -0
  28. package/scripts/github/create-draft-pr.mjs +108 -0
  29. package/scripts/github/detect-checkpoint-evidence.mjs +393 -0
  30. package/scripts/github/detect-linked-issue-pr.mjs +331 -0
  31. package/scripts/github/manage-sub-issues.mjs +394 -0
  32. package/scripts/github/probe-copilot-review.mjs +323 -0
  33. package/scripts/github/ready-for-review.mjs +93 -0
  34. package/scripts/github/reconcile-draft-gate.mjs +328 -0
  35. package/scripts/github/reply-resolve-review-thread.mjs +42 -0
  36. package/scripts/github/reply-resolve-review-threads.mjs +329 -0
  37. package/scripts/github/request-copilot-review.mjs +551 -0
  38. package/scripts/github/resolve-tracker-local-spec.mjs +205 -0
  39. package/scripts/github/stage-reviewer-draft.mjs +191 -0
  40. package/scripts/github/upsert-checkpoint-verdict.mjs +694 -0
  41. package/scripts/github/verify-fresh-review-context.mjs +125 -0
  42. package/scripts/github/write-gate-findings-log.mjs +212 -0
  43. package/scripts/loop/_checkpoint-io.mjs +55 -0
  44. package/scripts/loop/_checkpoint-paths.mjs +28 -0
  45. package/scripts/loop/_handoff-contract.mjs +230 -0
  46. package/scripts/loop/_inspect-run-viewer-adapter.mjs +345 -0
  47. package/scripts/loop/_loop-evidence.mjs +32 -0
  48. package/scripts/loop/_pr-runner-coordination.mjs +611 -0
  49. package/scripts/loop/_stale-runner-detection.mjs +145 -0
  50. package/scripts/loop/_steering-state-file.mjs +134 -0
  51. package/scripts/loop/build-handoff-envelope.mjs +181 -0
  52. package/scripts/loop/checkpoint-contract.mjs +49 -0
  53. package/scripts/loop/conductor-monitor.mjs +1850 -0
  54. package/scripts/loop/conductor.mjs +214 -0
  55. package/scripts/loop/copilot-pr-handoff.mjs +493 -0
  56. package/scripts/loop/debt-remediate.mjs +304 -0
  57. package/scripts/loop/detect-change-scope.mjs +102 -0
  58. package/scripts/loop/detect-copilot-loop-state.mjs +454 -0
  59. package/scripts/loop/detect-copilot-session-activity.mjs +186 -0
  60. package/scripts/loop/detect-initial-copilot-pr-state.mjs +318 -0
  61. package/scripts/loop/detect-internal-only-pr.mjs +270 -0
  62. package/scripts/loop/detect-issue-refinement-artifact.mjs +163 -0
  63. package/scripts/loop/detect-pr-gate-coordination-state.mjs +509 -0
  64. package/scripts/loop/detect-reviewer-loop-state.mjs +231 -0
  65. package/scripts/loop/detect-stale-runner.mjs +250 -0
  66. package/scripts/loop/detect-tracker-first-loop-state.mjs +76 -0
  67. package/scripts/loop/detect-tracker-pr-state.mjs +102 -0
  68. package/scripts/loop/info.mjs +267 -0
  69. package/scripts/loop/inspect-run-viewer/cli.mjs +117 -0
  70. package/scripts/loop/inspect-run-viewer/constants.mjs +80 -0
  71. package/scripts/loop/inspect-run-viewer/graph.mjs +757 -0
  72. package/scripts/loop/inspect-run-viewer/handoff-envelope-renderer.mjs +398 -0
  73. package/scripts/loop/inspect-run-viewer/inbox.mjs +308 -0
  74. package/scripts/loop/inspect-run-viewer/managed-instance.mjs +750 -0
  75. package/scripts/loop/inspect-run-viewer/rendering.mjs +411 -0
  76. package/scripts/loop/inspect-run-viewer/server.mjs +638 -0
  77. package/scripts/loop/inspect-run-viewer/shared.mjs +103 -0
  78. package/scripts/loop/inspect-run-viewer/status.mjs +715 -0
  79. package/scripts/loop/inspect-run-viewer-ci-changes.mjs +77 -0
  80. package/scripts/loop/inspect-run-viewer.mjs +82 -0
  81. package/scripts/loop/inspect-run.mjs +382 -0
  82. package/scripts/loop/outer-loop.mjs +419 -0
  83. package/scripts/loop/pr-runner-coordination.mjs +143 -0
  84. package/scripts/loop/pre-commit-branch-guard.mjs +68 -0
  85. package/scripts/loop/pre-flight-gate.mjs +236 -0
  86. package/scripts/loop/pre-pr-ready-gate.mjs +183 -0
  87. package/scripts/loop/pre-push-main-guard.mjs +103 -0
  88. package/scripts/loop/pre-write-remote-freshness-guard.mjs +32 -0
  89. package/scripts/loop/print-gates.mjs +42 -0
  90. package/scripts/loop/resolve-dev-loop-startup.mjs +533 -0
  91. package/scripts/loop/run-conductor-cycle.mjs +322 -0
  92. package/scripts/loop/run-queue.mjs +124 -0
  93. package/scripts/loop/run-refinement-audit.mjs +513 -0
  94. package/scripts/loop/run-watch-cycle.mjs +358 -0
  95. package/scripts/loop/steer-loop.mjs +841 -0
  96. package/scripts/loop/ui-designer-review-contract.mjs +76 -0
  97. package/scripts/loop/watch-initial-copilot-pr.mjs +253 -0
  98. package/scripts/projects/add-queue-item.mjs +528 -0
  99. package/scripts/projects/ensure-queue-board.mjs +837 -0
  100. package/scripts/projects/list-queue-items.mjs +489 -0
  101. package/scripts/projects/move-queue-item.mjs +549 -0
  102. package/scripts/projects/reorder-queue-item.mjs +518 -0
  103. package/scripts/refine/_refine-helpers.mjs +258 -0
  104. package/scripts/refine/prose-linkage-detector.mjs +92 -0
  105. package/scripts/refine/refinement-completeness-checker.mjs +88 -0
  106. package/scripts/refine/scope-boundary-cross-checker.mjs +163 -0
  107. package/scripts/refine/tree-integrity-validator.mjs +211 -0
  108. package/scripts/refine/verify.mjs +178 -0
  109. package/scripts/repo-wiki-local.mjs +156 -0
  110. package/scripts/repo-wiki.mjs +119 -0
  111. package/skills/copilot-pr-followup/SKILL.md +380 -0
  112. package/skills/dev-loop/SKILL.md +141 -0
  113. package/skills/dev-loop/scripts/dev-mode-context.mjs +152 -0
  114. package/skills/dev-loop/scripts/dev-mode-context.test.mjs +80 -0
  115. package/skills/dev-loop/scripts/init-phase.mjs +71 -0
  116. package/skills/dev-loop/scripts/log-bash-exit-1.mjs +25 -0
  117. package/skills/dev-loop/scripts/phase-files.mjs +29 -0
  118. package/skills/dev-loop/scripts/post-gate-verdict-fallback.mjs +480 -0
  119. package/skills/dev-loop/scripts/post-gate-verdict-fallback.test.mjs +732 -0
  120. package/skills/dev-loop/scripts/render-template.mjs +82 -0
  121. package/skills/dev-loop/scripts/render-template.test.mjs +63 -0
  122. package/skills/dev-loop/templates/bootstrap-agents.md +26 -0
  123. package/skills/dev-loop/templates/bootstrap-implementation-state.md +31 -0
  124. package/skills/dev-loop/templates/bootstrap-implementation-workflow.md +17 -0
  125. package/skills/dev-loop/templates/dev-mode-retrospective.md +15 -0
  126. package/skills/dev-loop/templates/dev-mode-review.md +17 -0
  127. package/skills/dev-loop/templates/dev-mode-skill-changes.md +11 -0
  128. package/skills/dev-loop/templates/merged-phase-plan.md +19 -0
  129. package/skills/dev-loop/templates/phase-doc.md +27 -0
  130. package/skills/dev-loop/templates/phase-summary.md +13 -0
  131. package/skills/dev-loop/templates/phase-variant.md +15 -0
  132. package/skills/dev-loop/templates/retrospective.md +11 -0
  133. package/skills/dev-loop/templates/review.md +32 -0
  134. package/skills/dev-loop/templates/ui-vision-review.md +55 -0
  135. package/skills/docs/acceptance-criteria-verification.md +21 -0
  136. package/skills/docs/anti-patterns.md +21 -0
  137. package/skills/docs/artifact-authority-contract.md +119 -0
  138. package/skills/docs/confirmation-rules.md +28 -0
  139. package/skills/docs/copilot-ci-status-contract.md +52 -0
  140. package/skills/docs/copilot-loop-operations.md +233 -0
  141. package/skills/docs/debt-remediation-contract.md +107 -0
  142. package/skills/docs/entrypoint-strategies.md +115 -0
  143. package/skills/docs/epic-tree-refinement-procedure.md +234 -0
  144. package/skills/docs/issue-intake-procedure.md +235 -0
  145. package/skills/docs/main-agent-contract.md +72 -0
  146. package/skills/docs/merge-preconditions.md +29 -0
  147. package/skills/docs/pr-lifecycle-contract.md +209 -0
  148. package/skills/docs/public-dev-loop-contract.md +497 -0
  149. package/skills/docs/retrospective-checkpoint-contract.md +159 -0
  150. package/skills/docs/stop-conditions.md +29 -0
  151. package/skills/docs/structural-quality.md +42 -0
  152. package/skills/docs/tracker-first-loop-state.md +281 -0
  153. package/skills/docs/validation-policy.md +27 -0
  154. package/skills/docs/workflow-handoff-contract.md +135 -0
  155. package/skills/final-approval/SKILL.md +19 -0
  156. package/skills/local-implementation/SKILL.md +640 -0
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env node
2
+ import { runChild, requireOptionValue } from "../_cli-primitives.mjs";
3
+ import {
4
+ buildParseError,
5
+ formatCliError,
6
+ isDirectCliRun,
7
+ parseJsonText,
8
+ } from "../_core-helpers.mjs";
9
+ import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
10
+ import { detectPrGateCoordinationState } from "./detect-pr-gate-coordination-state.mjs";
11
+ import { autoDetectSnapshot } from "./detect-copilot-loop-state.mjs";
12
+ import { PR_CHECKPOINT_ACTION } from "@dev-loops/core/loop/pr-gate-coordination";
13
+ import {
14
+ SUBAGENT_ACTIONS as SHARED_SUBAGENT_ACTIONS,
15
+ buildHandoffContractForConductorAction,
16
+ } from "./_handoff-contract.mjs";
17
+ const USAGE = `Usage: run-conductor-cycle.mjs --repo <owner/name>
18
+ Poll all open PRs, detect state, and output an ordered action queue.`.trim();
19
+ const OPEN_PR_LIST_LIMIT = 1000;
20
+ export const CHECKPOINT_ACTION_TO_CONDUCTOR_ACTION = Object.freeze({
21
+ [PR_CHECKPOINT_ACTION.ADDRESS_REVIEW_FEEDBACK]: "fix_threads",
22
+ [PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS]: "fix_threads",
23
+ [PR_CHECKPOINT_ACTION.RUN_DRAFT_GATE]: "draft_gate",
24
+ [PR_CHECKPOINT_ACTION.RECONCILE_DRAFT_GATE]: "draft_gate",
25
+ [PR_CHECKPOINT_ACTION.RUN_PRE_APPROVAL_GATE]: "run_pre_approval",
26
+ [PR_CHECKPOINT_ACTION.MARK_READY_FOR_REVIEW]: "request_review",
27
+ [PR_CHECKPOINT_ACTION.REQUEST_COPILOT_REVIEW]: "request_review",
28
+ [PR_CHECKPOINT_ACTION.REREQUEST_COPILOT_REVIEW]: "rerequest_review",
29
+ [PR_CHECKPOINT_ACTION.WAIT_FOR_COPILOT_REVIEW]: "watch",
30
+ [PR_CHECKPOINT_ACTION.WAIT_FOR_CI]: "watch",
31
+ [PR_CHECKPOINT_ACTION.DECLARE_MERGE_READY]: "merge",
32
+ [PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL]: "await_approval",
33
+ [PR_CHECKPOINT_ACTION.RESOLVE_MERGE_CONFLICTS]: "resolve_conflicts",
34
+ [PR_CHECKPOINT_ACTION.REPORT_BLOCKED]: "blocked",
35
+ [PR_CHECKPOINT_ACTION.REPORT_DONE]: "done",
36
+ });
37
+ export const ACTION_PRIORITY = Object.freeze({
38
+ merge: 100,
39
+ fix_threads: 90,
40
+ run_pre_approval: 80,
41
+ draft_gate: 70,
42
+ request_review: 60,
43
+ rerequest_review: 50,
44
+ watch: 30,
45
+ await_approval: 20,
46
+ resolve_conflicts: 10,
47
+ blocked: 10,
48
+ done: 0,
49
+ error: -1,
50
+ });
51
+ export const SUBAGENT_ACTIONS = SHARED_SUBAGENT_ACTIONS;
52
+ export const AUTONOMY_GATE_ACTION_MAP = Object.freeze({
53
+ merge: ["merge"],
54
+ "pre-approval": ["run_pre_approval"],
55
+ "draft-pr": ["draft_gate", "request_review", "rerequest_review"],
56
+ refinement: [],
57
+ });
58
+ export function actionRequiresApproval(action, autonomyStopAt = ["merge"]) {
59
+ const stopSet = new Set(
60
+ autonomyStopAt.flatMap((gate) => AUTONOMY_GATE_ACTION_MAP[gate] ?? [])
61
+ );
62
+ return stopSet.has(action);
63
+ }
64
+ const parseError = buildParseError(USAGE);
65
+ export function parseCliArgs(argv) {
66
+ const args = [...argv];
67
+ const options = {
68
+ help: false,
69
+ repo: undefined,
70
+ };
71
+ while (args.length > 0) {
72
+ const token = args.shift();
73
+ if (token === "--help" || token === "-h") {
74
+ options.help = true;
75
+ return options;
76
+ }
77
+ if (token === "--repo") {
78
+ options.repo = requireOptionValue(args, "--repo", parseError).trim();
79
+ continue;
80
+ }
81
+ throw parseError(`Unknown argument: ${token}`);
82
+ }
83
+ if (options.repo === undefined) {
84
+ throw parseError("run-conductor-cycle requires --repo <owner/name>");
85
+ }
86
+ try {
87
+ parseRepoSlug(options.repo);
88
+ } catch (error) {
89
+ throw parseError(error instanceof Error ? error.message : String(error));
90
+ }
91
+ return options;
92
+ }
93
+ export async function listOpenPrs({ repo }, { env, ghCommand }) {
94
+ const result = await runChild(
95
+ ghCommand,
96
+ [
97
+ "pr",
98
+ "list",
99
+ "--repo",
100
+ repo,
101
+ "--state",
102
+ "open",
103
+ "--limit",
104
+ String(OPEN_PR_LIST_LIMIT),
105
+ "--json",
106
+ "number,title,url,isDraft,headRefName,author",
107
+ ],
108
+ env,
109
+ );
110
+ if (result.code !== 0) {
111
+ const detail = result.stderr.trim() || `exit code ${result.code}`;
112
+ throw new Error(`gh command failed: ${detail}`);
113
+ }
114
+ const payload = parseJsonText(result.stdout);
115
+ if (!Array.isArray(payload)) {
116
+ throw new Error("Invalid gh pr list payload: expected an array");
117
+ }
118
+ return payload
119
+ .map((pr) => ({
120
+ number: Number.isInteger(pr?.number) ? pr.number : null,
121
+ title: typeof pr?.title === "string" ? pr.title : "",
122
+ url: typeof pr?.url === "string" ? pr.url : null,
123
+ isDraft: Boolean(pr?.isDraft),
124
+ headRefName: typeof pr?.headRefName === "string" ? pr.headRefName : null,
125
+ authorLogin: typeof pr?.author?.login === "string" ? pr.author.login : null,
126
+ }))
127
+ .filter((pr) => pr.number !== null)
128
+ .sort((left, right) => left.number - right.number);
129
+ }
130
+ export async function detectPrState(
131
+ pr,
132
+ {
133
+ repo,
134
+ env,
135
+ ghCommand,
136
+ repoRoot,
137
+ detectGateImpl = detectPrGateCoordinationState,
138
+ detectSnapshotImpl = autoDetectSnapshot,
139
+ autonomyStopAt = ["merge"],
140
+ },
141
+ ) {
142
+ try {
143
+ const gateState = await detectGateImpl(
144
+ { repo, pr: pr.number },
145
+ { env, ghCommand, repoRoot },
146
+ );
147
+ let snapshot = null;
148
+ try {
149
+ snapshot = await detectSnapshotImpl(
150
+ { repo, pr: pr.number },
151
+ { env, ghCommand },
152
+ );
153
+ } catch {
154
+ }
155
+ const action = CHECKPOINT_ACTION_TO_CONDUCTOR_ACTION[gateState.nextAction] ?? "error";
156
+ const priority = ACTION_PRIORITY[action] ?? -1;
157
+ const requiresApproval = actionRequiresApproval(action, autonomyStopAt);
158
+ const handoffContract = buildHandoffContractForConductorAction({
159
+ action,
160
+ gateBoundary: gateState.gateBoundary,
161
+ requiresApproval,
162
+ });
163
+ return {
164
+ pr: pr.number,
165
+ title: pr.title,
166
+ url: pr.url,
167
+ isDraft: pr.isDraft,
168
+ headRefName: pr.headRefName,
169
+ action,
170
+ priority,
171
+ state: gateState.lifecycleState,
172
+ lifecycleState: gateState.lifecycleState,
173
+ loopDisposition: gateState.loopDisposition,
174
+ gateBoundary: gateState.gateBoundary,
175
+ reason: gateState.reason ?? null,
176
+ snapshot: snapshot ?? null,
177
+ gateState: {
178
+ allowedNextActions: gateState.allowedNextActions,
179
+ forbiddenActions: gateState.forbiddenActions,
180
+ draftGate: gateState.draftGate ?? null,
181
+ preApprovalGate: gateState.preApprovalGate ?? null,
182
+ mergeStateStatus: gateState.mergeStateStatus ?? null,
183
+ conflictFiles: gateState.conflictFiles ?? [],
184
+ currentHeadSha: gateState.currentHeadSha ?? null,
185
+ ciStatus: snapshot?.ciStatus ?? null,
186
+ },
187
+ requiresSubagent: SUBAGENT_ACTIONS.has(action),
188
+ requiresApproval,
189
+ handoffContract,
190
+ };
191
+ } catch (error) {
192
+ return {
193
+ pr: pr.number,
194
+ title: pr.title,
195
+ url: pr.url,
196
+ isDraft: pr.isDraft,
197
+ headRefName: pr.headRefName,
198
+ action: "error",
199
+ priority: ACTION_PRIORITY.error,
200
+ state: null,
201
+ lifecycleState: null,
202
+ loopDisposition: null,
203
+ gateBoundary: null,
204
+ reason: null,
205
+ snapshot: null,
206
+ gateState: null,
207
+ requiresSubagent: false,
208
+ requiresApproval: false,
209
+ handoffContract: buildHandoffContractForConductorAction({ action: "error" }),
210
+ error: error instanceof Error ? error.message : String(error),
211
+ };
212
+ }
213
+ }
214
+ export function buildActionQueue(detectionResults) {
215
+ return [...detectionResults].sort((left, right) => {
216
+ const priorityDiff = right.priority - left.priority;
217
+ if (priorityDiff !== 0) {
218
+ return priorityDiff;
219
+ }
220
+ return left.pr - right.pr;
221
+ });
222
+ }
223
+ export function buildSummary(actions) {
224
+ const summary = {
225
+ needsSubagent: 0,
226
+ readyToMerge: 0,
227
+ waiting: 0,
228
+ blocked: 0,
229
+ done: 0,
230
+ errors: 0,
231
+ };
232
+ for (const action of actions) {
233
+ switch (action.action) {
234
+ case "error":
235
+ summary.errors += 1;
236
+ break;
237
+ case "merge":
238
+ summary.readyToMerge += 1;
239
+ break;
240
+ case "watch":
241
+ summary.waiting += 1;
242
+ break;
243
+ case "done":
244
+ summary.done += 1;
245
+ break;
246
+ case "blocked":
247
+ case "resolve_conflicts":
248
+ case "await_approval":
249
+ summary.blocked += 1;
250
+ break;
251
+ default:
252
+ break;
253
+ }
254
+ if (action.requiresSubagent) {
255
+ summary.needsSubagent += 1;
256
+ }
257
+ }
258
+ return summary;
259
+ }
260
+ export async function runConductorCycle(
261
+ { repo, autonomyStopAt, gateConfig },
262
+ {
263
+ env = process.env,
264
+ ghCommand = "gh",
265
+ repoRoot = process.cwd(),
266
+ listPrsImpl = listOpenPrs,
267
+ detectPrStateImpl = detectPrState,
268
+ } = {},
269
+ ) {
270
+ const prs = await listPrsImpl({ repo }, { env, ghCommand });
271
+ const stopAt = autonomyStopAt ?? ["merge"];
272
+ const detectionResults = [];
273
+ for (const pr of prs) {
274
+ const result = await detectPrStateImpl(pr, {
275
+ repo,
276
+ env,
277
+ ghCommand,
278
+ repoRoot,
279
+ autonomyStopAt: stopAt,
280
+ });
281
+ detectionResults.push(result);
282
+ }
283
+ const actions = buildActionQueue(detectionResults);
284
+ const summary = buildSummary(actions);
285
+ return {
286
+ ok: true,
287
+ repo,
288
+ checkedAt: new Date().toISOString(),
289
+ prCount: prs.length,
290
+ actions,
291
+ summary,
292
+ ...(gateConfig ? { gateConfig } : {}),
293
+ autonomyStopAt: stopAt,
294
+ };
295
+ }
296
+ export async function runCli(
297
+ argv = process.argv.slice(2),
298
+ {
299
+ stdout = process.stdout,
300
+ env = process.env,
301
+ ghCommand = "gh",
302
+ cwd = process.cwd(),
303
+ } = {},
304
+ ) {
305
+ const options = parseCliArgs(argv);
306
+ if (options.help) {
307
+ stdout.write(`${USAGE}\n`);
308
+ return;
309
+ }
310
+ const result = await runConductorCycle(options, {
311
+ env,
312
+ ghCommand,
313
+ repoRoot: cwd,
314
+ });
315
+ stdout.write(`${JSON.stringify(result)}\n`);
316
+ }
317
+ if (isDirectCliRun(import.meta.url)) {
318
+ runCli().catch((error) => {
319
+ process.stderr.write(`${formatCliError(error)}\n`);
320
+ process.exitCode = 1;
321
+ });
322
+ }
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * run-queue.mjs — Queue runner for dev-loop queue mode.
4
+ *
5
+ * Usage:
6
+ * dev-loops queue run --repo <owner/name> [--merge-authorized] [--parallel] [--redispatch-max-retries <n>]
7
+ *
8
+ * Reads queue state from .pi/dev-loop-queue.json and drives entries
9
+ * through the sequential queue driver. Queue config (maxParallel etc.)
10
+ * lives in .devloops at repo root.
11
+ *
12
+ * For parallel execution, use --parallel (file-overlap detection is
13
+ * deferred to a future phase; currently falls back to sequential).
14
+ */
15
+
16
+ import { fileURLToPath } from "node:url";
17
+ import { runQueue, DEFAULT_QUEUE_DRIVER_OPTIONS } from "@dev-loops/core/loop/queue-driver";
18
+ import { computeParallelSchedule } from "@dev-loops/core/loop/queue-parallel";
19
+ import { readQueue } from "@dev-loops/core/loop/queue-state";
20
+ import { parsePositiveInteger } from "@dev-loops/core/cli/primitives";
21
+
22
+ const REPO_ROOT = fileURLToPath(new URL("../..", import.meta.url));
23
+
24
+ const USAGE = `Usage:
25
+ dev-loops queue run --repo <owner/name> [--merge-authorized] [--parallel] [--redispatch-max-retries <n>]
26
+
27
+ Run the dev-loop queue driver over entries in .pi/dev-loop-queue.json.
28
+ Exit codes: 0 success, 1 error`.trim();
29
+
30
+ function parseArgs(argv) {
31
+ const args = {
32
+ repo: null,
33
+ mergeAuthorized: false,
34
+ parallel: false,
35
+ reDispatchMaxRetries: 1,
36
+ maxParallel: 3,
37
+ help: false,
38
+ };
39
+
40
+ for (let i = 0; i < argv.length; i++) {
41
+ switch (argv[i]) {
42
+ case "--repo":
43
+ args.repo = argv[++i];
44
+ break;
45
+ case "--merge-authorized":
46
+ args.mergeAuthorized = true;
47
+ break;
48
+ case "--parallel":
49
+ args.parallel = true;
50
+ break;
51
+ case "--redispatch-max-retries":
52
+ args.reDispatchMaxRetries = parsePositiveInteger(argv[++i], "--redispatch-max-retries");
53
+ break;
54
+ case "--max-parallel":
55
+ args.maxParallel = parsePositiveInteger(argv[++i], "--max-parallel");
56
+ break;
57
+ case "--help":
58
+ case "-h":
59
+ args.help = true;
60
+ break;
61
+ }
62
+ }
63
+
64
+ return args;
65
+ }
66
+
67
+ async function main() {
68
+ const args = parseArgs(process.argv.slice(2));
69
+
70
+ if (args.help) {
71
+ console.log(USAGE);
72
+ process.exit(0);
73
+ }
74
+
75
+ if (!args.repo) {
76
+ console.error("Error: --repo <owner/name> is required");
77
+ process.exit(1);
78
+ }
79
+
80
+ const queue = await readQueue(REPO_ROOT);
81
+
82
+ if (queue.entries.length === 0) {
83
+ console.log(JSON.stringify({ ok: true, message: "Queue is empty", results: [] }));
84
+ return;
85
+ }
86
+
87
+ const pending = queue.entries.filter((e) => e.status !== "done" && e.status !== "blocked");
88
+ console.error(`Queue: ${queue.entries.length} entries, ${pending.length} pending`);
89
+
90
+ if (args.parallel && pending.length > 1) {
91
+ // Note: file lists are not resolved from issues yet; real overlap
92
+ // detection requires fetching issue bodies via gh CLI. For now,
93
+ // compute a schedule from entry metadata and fall back to sequential.
94
+ const schedule = computeParallelSchedule(
95
+ pending.map((e) => ({
96
+ target: e.target,
97
+ files: [],
98
+ dependsOn: e.dependsOn || [],
99
+ })),
100
+ args.maxParallel
101
+ );
102
+
103
+ console.error(`Parallel schedule: ${schedule.waves.length} waves`);
104
+ for (let wi = 0; wi < schedule.waves.length; wi++) {
105
+ const wave = schedule.waves[wi];
106
+ console.error(` Wave ${wi + 1}: ${wave.map((g) => `[${g.join(", ")}]`).join(" ")}`);
107
+ }
108
+
109
+ console.error("Parallel dispatch via async subagents not yet wired; falling back to sequential.");
110
+ }
111
+
112
+ const result = await runQueue(REPO_ROOT, args.repo, {
113
+ ...DEFAULT_QUEUE_DRIVER_OPTIONS,
114
+ mergeAuthorized: args.mergeAuthorized,
115
+ reDispatchMaxRetries: args.reDispatchMaxRetries,
116
+ });
117
+
118
+ console.log(JSON.stringify(result, null, 2));
119
+ }
120
+
121
+ main().catch((err) => {
122
+ console.error(JSON.stringify({ ok: false, error: err.message }));
123
+ process.exit(1);
124
+ });