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,76 @@
1
+ export function validateUiDesignerReviewInput(input = {}) {
2
+ if (input.workType !== 'ui' || input.uiReviewRequested !== true) {
3
+ return {
4
+ ok: true,
5
+ status: 'skip_non_ui',
6
+ reason: 'non_ui_or_not_requested',
7
+ missing: [],
8
+ };
9
+ }
10
+ const uiReviewMode = typeof input.uiReviewMode === 'string' ? input.uiReviewMode.trim() : '';
11
+ const reviewMode = uiReviewMode.length === 0 ? 'designer' : uiReviewMode;
12
+ if (reviewMode !== 'designer' && reviewMode !== 'vision') {
13
+ return {
14
+ ok: false,
15
+ status: 'blocked_unsupported_review_mode',
16
+ reason: 'unsupported_review_mode',
17
+ missing: ['uiReviewMode'],
18
+ };
19
+ }
20
+ const missing = [];
21
+ const acceptanceCriteria = Array.isArray(input.acceptanceCriteria) ? input.acceptanceCriteria.filter((entry) => typeof entry === 'string' && entry.trim().length > 0) : [];
22
+ if (acceptanceCriteria.length === 0) {
23
+ missing.push('acceptanceCriteria');
24
+ }
25
+ if (typeof input.reviewBrief !== 'string' || input.reviewBrief.trim().length === 0) {
26
+ missing.push('reviewBrief');
27
+ }
28
+ const artifactBundle = input.artifactBundle ?? {};
29
+ if (typeof artifactBundle.sliceId !== 'string' || artifactBundle.sliceId.trim().length === 0) {
30
+ missing.push('artifactBundle.sliceId');
31
+ }
32
+ const namedStates = Array.isArray(artifactBundle.namedStates) ? artifactBundle.namedStates : [];
33
+ if (namedStates.length === 0) {
34
+ missing.push('artifactBundle.namedStates');
35
+ }
36
+ if (missing.length > 0) {
37
+ return {
38
+ ok: false,
39
+ status: 'blocked_missing_required_inputs',
40
+ reason: 'required_inputs_missing',
41
+ missing,
42
+ };
43
+ }
44
+ const incompleteArtifacts = [];
45
+ namedStates.forEach((state, index) => {
46
+ if (typeof state.stateName !== 'string' || state.stateName.trim().length === 0) {
47
+ incompleteArtifacts.push(`artifactBundle.namedStates[${index}].stateName`);
48
+ }
49
+ if (typeof state.screenshotPath !== 'string' || state.screenshotPath.trim().length === 0) {
50
+ incompleteArtifacts.push(`artifactBundle.namedStates[${index}].screenshotPath`);
51
+ }
52
+ if (typeof state.statePath !== 'string' || state.statePath.trim().length === 0) {
53
+ incompleteArtifacts.push(`artifactBundle.namedStates[${index}].statePath`);
54
+ }
55
+ if (reviewMode === 'vision' && typeof state.screenshotPath === 'string' && state.screenshotPath.trim().length > 0 && !state.screenshotPath.trim().endsWith('screenshot.png')) {
56
+ incompleteArtifacts.push(`artifactBundle.namedStates[${index}].screenshotPath`);
57
+ }
58
+ if (reviewMode === 'vision' && typeof state.statePath === 'string' && state.statePath.trim().length > 0 && !state.statePath.trim().endsWith('state.json')) {
59
+ incompleteArtifacts.push(`artifactBundle.namedStates[${index}].statePath`);
60
+ }
61
+ });
62
+ if (incompleteArtifacts.length > 0) {
63
+ return {
64
+ ok: false,
65
+ status: 'blocked_incomplete_artifact_bundle',
66
+ reason: 'artifact_bundle_incomplete',
67
+ missing: incompleteArtifacts,
68
+ };
69
+ }
70
+ return {
71
+ ok: true,
72
+ status: reviewMode === 'vision' ? 'ready_for_vision_review' : 'ready_for_designer_review',
73
+ reason: 'artifact_bundle_complete',
74
+ missing: [],
75
+ };
76
+ }
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+ import { setTimeout as delay } from "node:timers/promises";
3
+ import { spawn } from "node:child_process";
4
+ import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
5
+ import { parseIssueNumber, requireOptionValue } from "../_cli-primitives.mjs";
6
+ import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
7
+ import { detectInitialCopilotPrState, LINKED_PR_STATE } from "./detect-initial-copilot-pr-state.mjs";
8
+ import { enforcePersistentInternalWaitTimeout } from "@dev-loops/core/loop/timeout-policy";
9
+ import {
10
+ DEFAULT_POLL_INTERVAL_MS,
11
+ COPILOT_FIRST_DURABLE_WAIT_TIMEOUT_MS,
12
+ } from "@dev-loops/core/loop/policy-constants";
13
+ const REMOVED_FLAGS = new Set([
14
+ "--poll-interval-ms",
15
+ "--timeout-ms",
16
+ ]);
17
+ const USAGE = `Usage: watch-initial-copilot-pr.mjs --repo <owner/name> --issue <number>
18
+ Wait for the Copilot-first bootstrap-only draft PR to become substantive.
19
+ Polls detect-initial-copilot-pr-state in a durable loop until the linked PR
20
+ leaves the bootstrap-only draft shape or the watch budget is exhausted.
21
+ Both waiting_for_initial_copilot_implementation, copilot_session_active, and
22
+ no_linked_pr are healthy non-terminal wait states for this seam. Quiet poll
23
+ cycles do not surface as errors. The 1-hour default watch budget matches the
24
+ Copilot-first durable-wait seam from the public dev-loop contract.
25
+ prior_linked_pr_closed_unmerged is a terminal non-wait state: the loop exits
26
+ immediately so callers can surface a reconcile/block reason.
27
+ Required:
28
+ --repo <owner/name> Repository slug (e.g. owner/repo)
29
+ --issue <number> Issue number
30
+ Output (stdout, JSON):
31
+ { "ok": true, "status": "ready_for_followup"|"timed_out"|"prior_linked_pr_closed_unmerged",
32
+ "repo": "...", "issue": N, "prNumber": N|null, "prUrl": "..."|null,
33
+ "attempts": N, "elapsedMs": N }
34
+ Status values:
35
+ ready_for_followup Linked PR has left the bootstrap-only draft shape;
36
+ proceed with PR follow-up using the returned prNumber
37
+ timed_out Watch budget exhausted while linked PR was still
38
+ bootstrap-only; this is an explicit still-waiting timeout
39
+ outcome, not an implementation failure
40
+ prior_linked_pr_closed_unmerged A prior linked PR was closed without merging; the issue
41
+ needs human reconciliation before the loop can continue
42
+ Error output (stderr, JSON):
43
+ Argument/usage errors:
44
+ { "ok": false, "error": "...", "usage": "..." }
45
+ gh/runtime failures:
46
+ { "ok": false, "error": "..." }
47
+ Exit codes:
48
+ 0 Success (ready_for_followup, timed_out, and prior_linked_pr_closed_unmerged are all ok:true)
49
+ 1 Argument error or gh failure`.trim();
50
+ const parseError = buildParseError(USAGE);
51
+ function rejectRemovedFlag(token) {
52
+ throw parseError(
53
+ `${token} has been removed. Poll interval and timeout are centralized policy constants. Omit the flag.`,
54
+ );
55
+ }
56
+ export async function watchCopilotRunUntilComplete(
57
+ { repo, runId, timeoutMs = null },
58
+ { env = process.env, ghCommand = "gh" } = {},
59
+ ) {
60
+ return new Promise((resolve, reject) => {
61
+ const child = spawn(
62
+ ghCommand,
63
+ ["run", "watch", String(runId), "--repo", repo],
64
+ { env, stdio: ["ignore", "ignore", "pipe"] },
65
+ );
66
+ let stderr = "";
67
+ let timedOut = false;
68
+ let timeoutId = null;
69
+ child.stderr.on("data", (chunk) => {
70
+ stderr += String(chunk);
71
+ });
72
+ if (Number.isInteger(timeoutMs) && timeoutMs >= 0) {
73
+ timeoutId = setTimeout(() => {
74
+ timedOut = true;
75
+ child.kill("SIGTERM");
76
+ }, timeoutMs);
77
+ }
78
+ child.on("error", reject);
79
+ child.on("close", (code) => {
80
+ if (timeoutId !== null) {
81
+ clearTimeout(timeoutId);
82
+ }
83
+ if (timedOut) {
84
+ resolve({ status: "timed_out" });
85
+ return;
86
+ }
87
+ if (code !== 0) {
88
+ const detail = stderr.trim() || `exit code ${code}`;
89
+ reject(new Error(`gh command failed: ${detail}`));
90
+ return;
91
+ }
92
+ resolve({ status: "completed" });
93
+ });
94
+ });
95
+ }
96
+ export function parseWatchInitialCopilotPrCliArgs(argv) {
97
+ const args = [...argv];
98
+ const options = {
99
+ help: false,
100
+ repo: undefined,
101
+ issue: undefined,
102
+ pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,
103
+ timeoutMs: COPILOT_FIRST_DURABLE_WAIT_TIMEOUT_MS,
104
+ };
105
+ while (args.length > 0) {
106
+ const token = args.shift();
107
+ if (token === "--help" || token === "-h") {
108
+ options.help = true;
109
+ return options;
110
+ }
111
+ if (REMOVED_FLAGS.has(token)) {
112
+ rejectRemovedFlag(token);
113
+ }
114
+ if (token === "--repo") {
115
+ options.repo = requireOptionValue(args, "--repo", parseError).trim();
116
+ continue;
117
+ }
118
+ if (token === "--issue") {
119
+ options.issue = parseIssueNumber(requireOptionValue(args, "--issue", parseError), parseError);
120
+ continue;
121
+ }
122
+ throw parseError(`Unknown argument: ${token}`);
123
+ }
124
+ if (options.repo === undefined || options.issue === undefined) {
125
+ throw parseError("watch-initial-copilot-pr requires both --repo <owner/name> and --issue <number>");
126
+ }
127
+ try {
128
+ parseRepoSlug(options.repo);
129
+ } catch (error) {
130
+ throw parseError(error instanceof Error ? error.message : String(error));
131
+ }
132
+ try {
133
+ options.timeoutMs = options.timeoutMs === 0
134
+ ? 0
135
+ : enforcePersistentInternalWaitTimeout({
136
+ timeoutMs: COPILOT_FIRST_DURABLE_WAIT_TIMEOUT_MS,
137
+ contextLabel: "watch-initial-copilot-pr persistent wait",
138
+ });
139
+ } catch (error) {
140
+ throw parseError(error instanceof Error ? error.message : String(error));
141
+ }
142
+ return options;
143
+ }
144
+ export async function watchInitialCopilotPr(
145
+ options,
146
+ {
147
+ env = process.env,
148
+ ghCommand = "gh",
149
+ delayImpl = delay,
150
+ nowMs = () => Date.now(),
151
+ detectInitialCopilotPrStateImpl = detectInitialCopilotPrState,
152
+ watchCopilotRunUntilCompleteImpl = watchCopilotRunUntilComplete,
153
+ } = {},
154
+ ) {
155
+ const effectiveTimeoutMs = options.timeoutMs === 0
156
+ ? 0
157
+ : enforcePersistentInternalWaitTimeout({
158
+ timeoutMs: options.timeoutMs,
159
+ contextLabel: "watch-initial-copilot-pr persistent wait",
160
+ });
161
+ const { repo, issue, pollIntervalMs } = options;
162
+ const startMs = nowMs();
163
+ let attempts = 0;
164
+ while (true) {
165
+ attempts += 1;
166
+ const detection = await detectInitialCopilotPrStateImpl({ repo, issue }, { env, ghCommand });
167
+ const elapsedMs = nowMs() - startMs;
168
+ if (detection.state === LINKED_PR_STATE.LINKED_PR_READY_FOR_FOLLOWUP) {
169
+ return {
170
+ ok: true,
171
+ status: "ready_for_followup",
172
+ repo,
173
+ issue,
174
+ prNumber: detection.prNumber,
175
+ prUrl: detection.prUrl,
176
+ attempts,
177
+ elapsedMs,
178
+ };
179
+ }
180
+ if (detection.state === LINKED_PR_STATE.PRIOR_LINKED_PR_CLOSED_UNMERGED) {
181
+ return {
182
+ ok: true,
183
+ status: "prior_linked_pr_closed_unmerged",
184
+ repo,
185
+ issue,
186
+ prNumber: detection.prNumber,
187
+ prUrl: detection.prUrl,
188
+ attempts,
189
+ elapsedMs,
190
+ };
191
+ }
192
+ if (effectiveTimeoutMs === 0 || elapsedMs >= effectiveTimeoutMs) {
193
+ return {
194
+ ok: true,
195
+ status: "timed_out",
196
+ repo,
197
+ issue,
198
+ prNumber: detection.prNumber,
199
+ prUrl: detection.prUrl,
200
+ attempts,
201
+ elapsedMs,
202
+ };
203
+ }
204
+ if (
205
+ detection.state === LINKED_PR_STATE.COPILOT_SESSION_ACTIVE
206
+ && Number.isInteger(detection.sessionRunId)
207
+ ) {
208
+ const watchResult = await watchCopilotRunUntilCompleteImpl(
209
+ {
210
+ repo,
211
+ runId: detection.sessionRunId,
212
+ timeoutMs: effectiveTimeoutMs - elapsedMs,
213
+ },
214
+ { env, ghCommand },
215
+ );
216
+ if (watchResult?.status === "timed_out") {
217
+ return {
218
+ ok: true,
219
+ status: "timed_out",
220
+ repo,
221
+ issue,
222
+ prNumber: detection.prNumber,
223
+ prUrl: detection.prUrl,
224
+ attempts,
225
+ elapsedMs: nowMs() - startMs,
226
+ };
227
+ }
228
+ continue;
229
+ }
230
+ const remaining = effectiveTimeoutMs - (nowMs() - startMs);
231
+ if (remaining > 0) {
232
+ await delayImpl(Math.min(pollIntervalMs, remaining));
233
+ }
234
+ }
235
+ }
236
+ export async function runCli(
237
+ argv = process.argv.slice(2),
238
+ { stdout = process.stdout, env = process.env, ghCommand = "gh" } = {},
239
+ ) {
240
+ const options = parseWatchInitialCopilotPrCliArgs(argv);
241
+ if (options.help) {
242
+ stdout.write(`${USAGE}\n`);
243
+ return;
244
+ }
245
+ const result = await watchInitialCopilotPr(options, { env, ghCommand });
246
+ stdout.write(`${JSON.stringify(result)}\n`);
247
+ }
248
+ if (isDirectCliRun(import.meta.url)) {
249
+ runCli().catch((error) => {
250
+ process.stderr.write(`${formatCliError(error)}\n`);
251
+ process.exitCode = 1;
252
+ });
253
+ }