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.
- package/.pi/dev-loop/defaults.yaml +477 -0
- package/AGENTS.md +25 -0
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/agents/dev-loop.agent.md +82 -0
- package/agents/developer.agent.md +37 -0
- package/agents/docs.agent.md +33 -0
- package/agents/fixer.agent.md +53 -0
- package/agents/quality.agent.md +28 -0
- package/agents/refiner.agent.md +87 -0
- package/agents/review.agent.md +64 -0
- package/cli/index.mjs +424 -0
- package/extension/README.md +233 -0
- package/extension/checks.ts +94 -0
- package/extension/index.ts +131 -0
- package/extension/post-merge-update.ts +512 -0
- package/extension/presentation.ts +107 -0
- package/lib/dev-loops-core.mjs +284 -0
- package/package.json +103 -0
- package/scripts/README.md +1007 -0
- package/scripts/_cli-primitives.mjs +10 -0
- package/scripts/_core-helpers.mjs +30 -0
- package/scripts/docs/validate-links.mjs +567 -0
- package/scripts/docs/validate-no-duplicate-rules.mjs +250 -0
- package/scripts/github/_review-thread-mutations.mjs +214 -0
- package/scripts/github/capture-review-threads.mjs +180 -0
- package/scripts/github/create-draft-pr.mjs +108 -0
- package/scripts/github/detect-checkpoint-evidence.mjs +393 -0
- package/scripts/github/detect-linked-issue-pr.mjs +331 -0
- package/scripts/github/manage-sub-issues.mjs +394 -0
- package/scripts/github/probe-copilot-review.mjs +323 -0
- package/scripts/github/ready-for-review.mjs +93 -0
- package/scripts/github/reconcile-draft-gate.mjs +328 -0
- package/scripts/github/reply-resolve-review-thread.mjs +42 -0
- package/scripts/github/reply-resolve-review-threads.mjs +329 -0
- package/scripts/github/request-copilot-review.mjs +551 -0
- package/scripts/github/resolve-tracker-local-spec.mjs +205 -0
- package/scripts/github/stage-reviewer-draft.mjs +191 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +694 -0
- package/scripts/github/verify-fresh-review-context.mjs +125 -0
- package/scripts/github/write-gate-findings-log.mjs +212 -0
- package/scripts/loop/_checkpoint-io.mjs +55 -0
- package/scripts/loop/_checkpoint-paths.mjs +28 -0
- package/scripts/loop/_handoff-contract.mjs +230 -0
- package/scripts/loop/_inspect-run-viewer-adapter.mjs +345 -0
- package/scripts/loop/_loop-evidence.mjs +32 -0
- package/scripts/loop/_pr-runner-coordination.mjs +611 -0
- package/scripts/loop/_stale-runner-detection.mjs +145 -0
- package/scripts/loop/_steering-state-file.mjs +134 -0
- package/scripts/loop/build-handoff-envelope.mjs +181 -0
- package/scripts/loop/checkpoint-contract.mjs +49 -0
- package/scripts/loop/conductor-monitor.mjs +1850 -0
- package/scripts/loop/conductor.mjs +214 -0
- package/scripts/loop/copilot-pr-handoff.mjs +493 -0
- package/scripts/loop/debt-remediate.mjs +304 -0
- package/scripts/loop/detect-change-scope.mjs +102 -0
- package/scripts/loop/detect-copilot-loop-state.mjs +454 -0
- package/scripts/loop/detect-copilot-session-activity.mjs +186 -0
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +318 -0
- package/scripts/loop/detect-internal-only-pr.mjs +270 -0
- package/scripts/loop/detect-issue-refinement-artifact.mjs +163 -0
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +509 -0
- package/scripts/loop/detect-reviewer-loop-state.mjs +231 -0
- package/scripts/loop/detect-stale-runner.mjs +250 -0
- package/scripts/loop/detect-tracker-first-loop-state.mjs +76 -0
- package/scripts/loop/detect-tracker-pr-state.mjs +102 -0
- package/scripts/loop/info.mjs +267 -0
- package/scripts/loop/inspect-run-viewer/cli.mjs +117 -0
- package/scripts/loop/inspect-run-viewer/constants.mjs +80 -0
- package/scripts/loop/inspect-run-viewer/graph.mjs +757 -0
- package/scripts/loop/inspect-run-viewer/handoff-envelope-renderer.mjs +398 -0
- package/scripts/loop/inspect-run-viewer/inbox.mjs +308 -0
- package/scripts/loop/inspect-run-viewer/managed-instance.mjs +750 -0
- package/scripts/loop/inspect-run-viewer/rendering.mjs +411 -0
- package/scripts/loop/inspect-run-viewer/server.mjs +638 -0
- package/scripts/loop/inspect-run-viewer/shared.mjs +103 -0
- package/scripts/loop/inspect-run-viewer/status.mjs +715 -0
- package/scripts/loop/inspect-run-viewer-ci-changes.mjs +77 -0
- package/scripts/loop/inspect-run-viewer.mjs +82 -0
- package/scripts/loop/inspect-run.mjs +382 -0
- package/scripts/loop/outer-loop.mjs +419 -0
- package/scripts/loop/pr-runner-coordination.mjs +143 -0
- package/scripts/loop/pre-commit-branch-guard.mjs +68 -0
- package/scripts/loop/pre-flight-gate.mjs +236 -0
- package/scripts/loop/pre-pr-ready-gate.mjs +183 -0
- package/scripts/loop/pre-push-main-guard.mjs +103 -0
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +32 -0
- package/scripts/loop/print-gates.mjs +42 -0
- package/scripts/loop/resolve-dev-loop-startup.mjs +533 -0
- package/scripts/loop/run-conductor-cycle.mjs +322 -0
- package/scripts/loop/run-queue.mjs +124 -0
- package/scripts/loop/run-refinement-audit.mjs +513 -0
- package/scripts/loop/run-watch-cycle.mjs +358 -0
- package/scripts/loop/steer-loop.mjs +841 -0
- package/scripts/loop/ui-designer-review-contract.mjs +76 -0
- package/scripts/loop/watch-initial-copilot-pr.mjs +253 -0
- package/scripts/projects/add-queue-item.mjs +528 -0
- package/scripts/projects/ensure-queue-board.mjs +837 -0
- package/scripts/projects/list-queue-items.mjs +489 -0
- package/scripts/projects/move-queue-item.mjs +549 -0
- package/scripts/projects/reorder-queue-item.mjs +518 -0
- package/scripts/refine/_refine-helpers.mjs +258 -0
- package/scripts/refine/prose-linkage-detector.mjs +92 -0
- package/scripts/refine/refinement-completeness-checker.mjs +88 -0
- package/scripts/refine/scope-boundary-cross-checker.mjs +163 -0
- package/scripts/refine/tree-integrity-validator.mjs +211 -0
- package/scripts/refine/verify.mjs +178 -0
- package/scripts/repo-wiki-local.mjs +156 -0
- package/scripts/repo-wiki.mjs +119 -0
- package/skills/copilot-pr-followup/SKILL.md +380 -0
- package/skills/dev-loop/SKILL.md +141 -0
- package/skills/dev-loop/scripts/dev-mode-context.mjs +152 -0
- package/skills/dev-loop/scripts/dev-mode-context.test.mjs +80 -0
- package/skills/dev-loop/scripts/init-phase.mjs +71 -0
- package/skills/dev-loop/scripts/log-bash-exit-1.mjs +25 -0
- package/skills/dev-loop/scripts/phase-files.mjs +29 -0
- package/skills/dev-loop/scripts/post-gate-verdict-fallback.mjs +480 -0
- package/skills/dev-loop/scripts/post-gate-verdict-fallback.test.mjs +732 -0
- package/skills/dev-loop/scripts/render-template.mjs +82 -0
- package/skills/dev-loop/scripts/render-template.test.mjs +63 -0
- package/skills/dev-loop/templates/bootstrap-agents.md +26 -0
- package/skills/dev-loop/templates/bootstrap-implementation-state.md +31 -0
- package/skills/dev-loop/templates/bootstrap-implementation-workflow.md +17 -0
- package/skills/dev-loop/templates/dev-mode-retrospective.md +15 -0
- package/skills/dev-loop/templates/dev-mode-review.md +17 -0
- package/skills/dev-loop/templates/dev-mode-skill-changes.md +11 -0
- package/skills/dev-loop/templates/merged-phase-plan.md +19 -0
- package/skills/dev-loop/templates/phase-doc.md +27 -0
- package/skills/dev-loop/templates/phase-summary.md +13 -0
- package/skills/dev-loop/templates/phase-variant.md +15 -0
- package/skills/dev-loop/templates/retrospective.md +11 -0
- package/skills/dev-loop/templates/review.md +32 -0
- package/skills/dev-loop/templates/ui-vision-review.md +55 -0
- package/skills/docs/acceptance-criteria-verification.md +21 -0
- package/skills/docs/anti-patterns.md +21 -0
- package/skills/docs/artifact-authority-contract.md +119 -0
- package/skills/docs/confirmation-rules.md +28 -0
- package/skills/docs/copilot-ci-status-contract.md +52 -0
- package/skills/docs/copilot-loop-operations.md +233 -0
- package/skills/docs/debt-remediation-contract.md +107 -0
- package/skills/docs/entrypoint-strategies.md +115 -0
- package/skills/docs/epic-tree-refinement-procedure.md +234 -0
- package/skills/docs/issue-intake-procedure.md +235 -0
- package/skills/docs/main-agent-contract.md +72 -0
- package/skills/docs/merge-preconditions.md +29 -0
- package/skills/docs/pr-lifecycle-contract.md +209 -0
- package/skills/docs/public-dev-loop-contract.md +497 -0
- package/skills/docs/retrospective-checkpoint-contract.md +159 -0
- package/skills/docs/stop-conditions.md +29 -0
- package/skills/docs/structural-quality.md +42 -0
- package/skills/docs/tracker-first-loop-state.md +281 -0
- package/skills/docs/validation-policy.md +27 -0
- package/skills/docs/workflow-handoff-contract.md +135 -0
- package/skills/final-approval/SKILL.md +19 -0
- package/skills/local-implementation/SKILL.md +640 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { parsePrNumber, requireOptionValue, runChild } from "../_cli-primitives.mjs";
|
|
4
|
+
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
5
|
+
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
6
|
+
import { DEV_LOOP_CONTRACT_TRACE_CLASSIFICATION } from "@dev-loops/core/loop/public-dev-loop-routing";
|
|
7
|
+
import { watchCopilotReview } from "../github/probe-copilot-review.mjs";
|
|
8
|
+
import { runHandoff } from "./copilot-pr-handoff.mjs";
|
|
9
|
+
import { detectCopilotSessionActivity } from "./detect-copilot-session-activity.mjs";
|
|
10
|
+
import {
|
|
11
|
+
EXTERNAL_HEALTHY_WAIT_TIMEOUT_POLICY,
|
|
12
|
+
enforceExternalHealthyWaitTimeout,
|
|
13
|
+
} from "@dev-loops/core/loop/timeout-policy";
|
|
14
|
+
const REMOVED_FLAGS = new Set([
|
|
15
|
+
"--force-rerequest-review",
|
|
16
|
+
"--probe-only",
|
|
17
|
+
]);
|
|
18
|
+
const USAGE = `Usage: run-watch-cycle.mjs --repo <owner/name> --pr <number>
|
|
19
|
+
Run one deterministic Copilot wait-cycle boundary.
|
|
20
|
+
Required:
|
|
21
|
+
--repo <owner/name> Repository slug (e.g. owner/repo)
|
|
22
|
+
--pr <number> Pull request number
|
|
23
|
+
Output (stdout, JSON):
|
|
24
|
+
{ "ok": true, "handoffAction": "watch"|"fix"|"stop", "state": "...",
|
|
25
|
+
"allowedTransitions": [...], "nextAction": "...", "snapshot": {...},
|
|
26
|
+
"requestWatchContract"?: { ... },
|
|
27
|
+
"reviewRequestStatus"?: "...", "watchArgs"?: { ... },
|
|
28
|
+
"watchTimeoutPolicy"?: { "classification": "...", "minimumTimeoutMs": N, "defaultTimeoutMs": N },
|
|
29
|
+
"contractTrace"?: { ... },
|
|
30
|
+
"sessionActivity"?: { ... },
|
|
31
|
+
"watchStatus"?: "changed"|"timeout"|"idle", "watch"?: { ... },
|
|
32
|
+
"loopDisposition": "pending"|"unresolved_feedback"|"clean_converged"|"blocked"|"action_required"|"done",
|
|
33
|
+
"cycleDisposition": "pending"|"needs_followup"|"terminal",
|
|
34
|
+
"roundCapCleanEligible": true|false,
|
|
35
|
+
"terminal": true|false }
|
|
36
|
+
Cycle disposition:
|
|
37
|
+
pending Watch state persists; keep waiting or re-enter later
|
|
38
|
+
needs_followup Fresh review activity or fix-state follow-up needs action
|
|
39
|
+
terminal No automatic next step remains
|
|
40
|
+
Error output (stderr, JSON):
|
|
41
|
+
Argument/usage errors:
|
|
42
|
+
{ "ok": false, "error": "...", "usage": "..." }
|
|
43
|
+
runtime failures:
|
|
44
|
+
{ "ok": false, "error": "..." }
|
|
45
|
+
Exit codes:
|
|
46
|
+
0 Success
|
|
47
|
+
1 Argument error or runtime failure`.trim();
|
|
48
|
+
const parseError = buildParseError(USAGE);
|
|
49
|
+
function rejectRemovedFlag(token) {
|
|
50
|
+
throw parseError(
|
|
51
|
+
`${token} has been removed. Copilot re-requests and probe-only mode are managed internally. Omit the flag.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async function fetchPrHeadBranch({ repo, pr }, { env, ghCommand }) {
|
|
55
|
+
const result = await runChild(
|
|
56
|
+
ghCommand,
|
|
57
|
+
["pr", "view", String(pr), "--repo", repo, "--json", "headRefName"],
|
|
58
|
+
env,
|
|
59
|
+
);
|
|
60
|
+
if (result.code !== 0) {
|
|
61
|
+
const detail = result.stderr.trim() || `exit code ${result.code}`;
|
|
62
|
+
throw new Error(`gh command failed: ${detail}`);
|
|
63
|
+
}
|
|
64
|
+
let payload;
|
|
65
|
+
try {
|
|
66
|
+
payload = JSON.parse(result.stdout);
|
|
67
|
+
} catch {
|
|
68
|
+
throw new Error(`Invalid JSON from gh: ${result.stdout.trim() || "<empty>"}`);
|
|
69
|
+
}
|
|
70
|
+
if (typeof payload.headRefName !== "string" || payload.headRefName.trim().length === 0) {
|
|
71
|
+
throw new Error("Missing required PR facts: headRefName");
|
|
72
|
+
}
|
|
73
|
+
return payload.headRefName.trim();
|
|
74
|
+
}
|
|
75
|
+
async function watchWorkflowRun({ repo, runId, timeoutMs = null }, { env, ghCommand }) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const child = spawn(
|
|
78
|
+
ghCommand,
|
|
79
|
+
["run", "watch", String(runId), "--repo", repo],
|
|
80
|
+
{ env, stdio: ["ignore", "ignore", "pipe"] },
|
|
81
|
+
);
|
|
82
|
+
let stderr = "";
|
|
83
|
+
let timedOut = false;
|
|
84
|
+
let timeoutId = null;
|
|
85
|
+
child.stderr.on("data", (chunk) => {
|
|
86
|
+
stderr += String(chunk);
|
|
87
|
+
});
|
|
88
|
+
if (Number.isInteger(timeoutMs) && timeoutMs >= 0) {
|
|
89
|
+
timeoutId = setTimeout(() => {
|
|
90
|
+
timedOut = true;
|
|
91
|
+
child.kill("SIGTERM");
|
|
92
|
+
}, timeoutMs);
|
|
93
|
+
}
|
|
94
|
+
child.on("error", reject);
|
|
95
|
+
child.on("close", (code) => {
|
|
96
|
+
if (timeoutId !== null) {
|
|
97
|
+
clearTimeout(timeoutId);
|
|
98
|
+
}
|
|
99
|
+
if (timedOut) {
|
|
100
|
+
resolve({ status: "timed_out" });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (code !== 0) {
|
|
104
|
+
const detail = stderr.trim() || `exit code ${code}`;
|
|
105
|
+
reject(new Error(`gh command failed: ${detail}`));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
resolve({ status: "completed" });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function determineWatchTimeout(defaultTimeoutMs) {
|
|
113
|
+
return enforceExternalHealthyWaitTimeout({
|
|
114
|
+
timeoutMs: defaultTimeoutMs,
|
|
115
|
+
contextLabel: "Copilot review wait",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function buildWatchCycleContractTrace({
|
|
119
|
+
handoff,
|
|
120
|
+
watchArgs = null,
|
|
121
|
+
watchTimeoutPolicy = null,
|
|
122
|
+
watchStatus,
|
|
123
|
+
cycleDisposition,
|
|
124
|
+
sessionActivity = null,
|
|
125
|
+
workflowRunWatch = null,
|
|
126
|
+
}) {
|
|
127
|
+
const boundaryClassification = handoff.action !== "watch"
|
|
128
|
+
? (handoff.loopDisposition === "blocked"
|
|
129
|
+
? DEV_LOOP_CONTRACT_TRACE_CLASSIFICATION.BLOCKED
|
|
130
|
+
: handoff.terminal
|
|
131
|
+
? DEV_LOOP_CONTRACT_TRACE_CLASSIFICATION.TERMINAL
|
|
132
|
+
: DEV_LOOP_CONTRACT_TRACE_CLASSIFICATION.ROUTED_FOLLOWUP)
|
|
133
|
+
: watchStatus === "changed"
|
|
134
|
+
? DEV_LOOP_CONTRACT_TRACE_CLASSIFICATION.ROUTED_FOLLOWUP
|
|
135
|
+
: DEV_LOOP_CONTRACT_TRACE_CLASSIFICATION.HEALTHY_WAIT;
|
|
136
|
+
return {
|
|
137
|
+
handoff: {
|
|
138
|
+
action: handoff.action,
|
|
139
|
+
state: handoff.state,
|
|
140
|
+
loopDisposition: handoff.loopDisposition,
|
|
141
|
+
terminal: Boolean(handoff.terminal),
|
|
142
|
+
},
|
|
143
|
+
waitStrategy: {
|
|
144
|
+
helper: handoff.action === "watch" ? "scripts/github/probe-copilot-review.mjs" : null,
|
|
145
|
+
mode: handoff.action === "watch"
|
|
146
|
+
? "persistent_watch"
|
|
147
|
+
: "not_applicable",
|
|
148
|
+
effectiveTimeoutMs: watchArgs?.timeoutMs ?? null,
|
|
149
|
+
effectivePollIntervalMs: watchArgs?.pollIntervalMs ?? null,
|
|
150
|
+
timeoutPolicyClassification: watchTimeoutPolicy?.classification ?? null,
|
|
151
|
+
},
|
|
152
|
+
orchestration: {
|
|
153
|
+
emittedWatchArgs: handoff.watchArgs ?? null,
|
|
154
|
+
effectiveWatchArgs: watchArgs,
|
|
155
|
+
sessionActivity,
|
|
156
|
+
workflowRunWatch,
|
|
157
|
+
},
|
|
158
|
+
stateRefresh: handoff.action === "watch"
|
|
159
|
+
? {
|
|
160
|
+
boundaryKind: "post_watch_or_probe",
|
|
161
|
+
observedStatus: watchStatus,
|
|
162
|
+
refreshRequired: true,
|
|
163
|
+
refreshReason: watchStatus === "changed"
|
|
164
|
+
? "Watch boundaries with fresh activity require an authoritative state refresh before routing the follow-up path."
|
|
165
|
+
: "Healthy watch boundaries are observational only; refresh authoritative state before treating timeout/idle as stop or completion.",
|
|
166
|
+
}
|
|
167
|
+
: null,
|
|
168
|
+
stopReason: {
|
|
169
|
+
classification: boundaryClassification,
|
|
170
|
+
terminal: Boolean(handoff.terminal),
|
|
171
|
+
cycleDisposition,
|
|
172
|
+
reason: handoff.action === "watch"
|
|
173
|
+
? (watchStatus === "changed"
|
|
174
|
+
? "Fresh watcher activity requires follow-up instead of staying in a healthy wait boundary."
|
|
175
|
+
: "Quiet watcher boundaries remain healthy waits and must not be treated as terminal completion by themselves.")
|
|
176
|
+
: handoff.nextAction,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export function parseWatchCycleCliArgs(argv) {
|
|
181
|
+
const args = [...argv];
|
|
182
|
+
const options = {
|
|
183
|
+
help: false,
|
|
184
|
+
repo: undefined,
|
|
185
|
+
pr: undefined,
|
|
186
|
+
};
|
|
187
|
+
while (args.length > 0) {
|
|
188
|
+
const token = args.shift();
|
|
189
|
+
if (token === "--help" || token === "-h") {
|
|
190
|
+
options.help = true;
|
|
191
|
+
return options;
|
|
192
|
+
}
|
|
193
|
+
if (REMOVED_FLAGS.has(token)) {
|
|
194
|
+
rejectRemovedFlag(token);
|
|
195
|
+
}
|
|
196
|
+
if (token === "--repo") {
|
|
197
|
+
options.repo = requireOptionValue(args, "--repo", parseError).trim();
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (token === "--pr") {
|
|
201
|
+
options.pr = parsePrNumber(requireOptionValue(args, "--pr", parseError), parseError);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
throw parseError(`Unknown argument: ${token}`);
|
|
205
|
+
}
|
|
206
|
+
if (options.repo === undefined || options.pr === undefined) {
|
|
207
|
+
throw parseError("run-watch-cycle requires both --repo <owner/name> and --pr <number>");
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
parseRepoSlug(options.repo);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
throw parseError(error instanceof Error ? error.message : String(error));
|
|
213
|
+
}
|
|
214
|
+
return options;
|
|
215
|
+
}
|
|
216
|
+
export async function runWatchCycle(
|
|
217
|
+
options,
|
|
218
|
+
{
|
|
219
|
+
env = process.env,
|
|
220
|
+
ghCommand = "gh",
|
|
221
|
+
runHandoffImpl = runHandoff,
|
|
222
|
+
watchCopilotReviewImpl = watchCopilotReview,
|
|
223
|
+
detectCopilotSessionActivityImpl = detectCopilotSessionActivity,
|
|
224
|
+
fetchPrHeadBranchImpl = fetchPrHeadBranch,
|
|
225
|
+
watchWorkflowRunImpl = watchWorkflowRun,
|
|
226
|
+
detectSessionActivity = false,
|
|
227
|
+
} = {},
|
|
228
|
+
) {
|
|
229
|
+
const handoff = await runHandoffImpl(options, { env, ghCommand });
|
|
230
|
+
const result = {
|
|
231
|
+
ok: true,
|
|
232
|
+
handoffAction: handoff.action,
|
|
233
|
+
state: handoff.state,
|
|
234
|
+
allowedTransitions: handoff.allowedTransitions,
|
|
235
|
+
nextAction: handoff.nextAction,
|
|
236
|
+
snapshot: handoff.snapshot,
|
|
237
|
+
roundCapCleanEligible: handoff.roundCapCleanEligible ?? false,
|
|
238
|
+
loopDisposition: handoff.loopDisposition,
|
|
239
|
+
cycleDisposition: handoff.action === "stop" ? "terminal" : "needs_followup",
|
|
240
|
+
terminal: Boolean(handoff.terminal),
|
|
241
|
+
};
|
|
242
|
+
if (handoff.requestWatchContract !== undefined) {
|
|
243
|
+
result.requestWatchContract = handoff.requestWatchContract;
|
|
244
|
+
}
|
|
245
|
+
if (handoff.reviewRequestStatus !== undefined) {
|
|
246
|
+
result.reviewRequestStatus = handoff.reviewRequestStatus;
|
|
247
|
+
}
|
|
248
|
+
if (handoff.watchArgs !== undefined) {
|
|
249
|
+
result.watchArgs = handoff.watchArgs;
|
|
250
|
+
}
|
|
251
|
+
if (handoff.watchTimeoutPolicy !== undefined) {
|
|
252
|
+
result.watchTimeoutPolicy = handoff.watchTimeoutPolicy;
|
|
253
|
+
}
|
|
254
|
+
if (handoff.action !== "watch") {
|
|
255
|
+
result.contractTrace = buildWatchCycleContractTrace({
|
|
256
|
+
handoff,
|
|
257
|
+
watchArgs: result.watchArgs ?? null,
|
|
258
|
+
watchTimeoutPolicy: result.watchTimeoutPolicy ?? null,
|
|
259
|
+
watchStatus: result.watchStatus,
|
|
260
|
+
cycleDisposition: result.cycleDisposition,
|
|
261
|
+
});
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
if (result.watchTimeoutPolicy === undefined) {
|
|
265
|
+
result.watchTimeoutPolicy = EXTERNAL_HEALTHY_WAIT_TIMEOUT_POLICY;
|
|
266
|
+
}
|
|
267
|
+
const persistentWatchTimeoutMs = determineWatchTimeout(
|
|
268
|
+
handoff.watchArgs.timeoutMs,
|
|
269
|
+
);
|
|
270
|
+
let workflowRunWatch = null;
|
|
271
|
+
if (detectSessionActivity) {
|
|
272
|
+
const headBranch = await fetchPrHeadBranchImpl({ repo: options.repo, pr: options.pr }, { env, ghCommand });
|
|
273
|
+
const session = await detectCopilotSessionActivityImpl(
|
|
274
|
+
{
|
|
275
|
+
repo: options.repo,
|
|
276
|
+
branch: headBranch,
|
|
277
|
+
},
|
|
278
|
+
{ env, ghCommand },
|
|
279
|
+
);
|
|
280
|
+
result.sessionActivity = session;
|
|
281
|
+
if (
|
|
282
|
+
session.activity === "active"
|
|
283
|
+
&& Number.isInteger(session.runId)
|
|
284
|
+
) {
|
|
285
|
+
const workflowWatchResult = await watchWorkflowRunImpl(
|
|
286
|
+
{
|
|
287
|
+
repo: options.repo,
|
|
288
|
+
runId: session.runId,
|
|
289
|
+
timeoutMs: persistentWatchTimeoutMs,
|
|
290
|
+
},
|
|
291
|
+
{ env, ghCommand },
|
|
292
|
+
);
|
|
293
|
+
workflowRunWatch = {
|
|
294
|
+
attempted: true,
|
|
295
|
+
timeoutMs: persistentWatchTimeoutMs,
|
|
296
|
+
runId: session.runId,
|
|
297
|
+
status: workflowWatchResult?.status ?? "unknown",
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const watchOptions = {
|
|
302
|
+
...handoff.watchArgs,
|
|
303
|
+
timeoutMs: persistentWatchTimeoutMs,
|
|
304
|
+
};
|
|
305
|
+
const watch = await watchCopilotReviewImpl(watchOptions, { env, ghCommand });
|
|
306
|
+
result.watchArgs = watchOptions;
|
|
307
|
+
result.watchStatus = watch.status;
|
|
308
|
+
result.watch = watch;
|
|
309
|
+
result.cycleDisposition = watch.status === "changed" ? "needs_followup" : "pending";
|
|
310
|
+
result.terminal = false;
|
|
311
|
+
result.contractTrace = buildWatchCycleContractTrace({
|
|
312
|
+
handoff,
|
|
313
|
+
watchArgs: watchOptions,
|
|
314
|
+
watchTimeoutPolicy: result.watchTimeoutPolicy,
|
|
315
|
+
watchStatus: watch.status,
|
|
316
|
+
cycleDisposition: result.cycleDisposition,
|
|
317
|
+
sessionActivity: result.sessionActivity ?? null,
|
|
318
|
+
workflowRunWatch: detectSessionActivity
|
|
319
|
+
? (workflowRunWatch ?? {
|
|
320
|
+
attempted: false,
|
|
321
|
+
timeoutMs: persistentWatchTimeoutMs,
|
|
322
|
+
runId: result.sessionActivity?.runId ?? null,
|
|
323
|
+
status: null,
|
|
324
|
+
})
|
|
325
|
+
: null,
|
|
326
|
+
});
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
export async function runCli(
|
|
330
|
+
argv = process.argv.slice(2),
|
|
331
|
+
{
|
|
332
|
+
stdout = process.stdout,
|
|
333
|
+
env = process.env,
|
|
334
|
+
ghCommand = "gh",
|
|
335
|
+
runHandoffImpl = runHandoff,
|
|
336
|
+
watchCopilotReviewImpl = watchCopilotReview,
|
|
337
|
+
} = {},
|
|
338
|
+
) {
|
|
339
|
+
const options = parseWatchCycleCliArgs(argv);
|
|
340
|
+
if (options.help) {
|
|
341
|
+
stdout.write(`${USAGE}\n`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const result = await runWatchCycle(options, {
|
|
345
|
+
env,
|
|
346
|
+
ghCommand,
|
|
347
|
+
runHandoffImpl,
|
|
348
|
+
watchCopilotReviewImpl,
|
|
349
|
+
detectSessionActivity: true,
|
|
350
|
+
});
|
|
351
|
+
stdout.write(`${JSON.stringify(result)}\n`);
|
|
352
|
+
}
|
|
353
|
+
if (isDirectCliRun(import.meta.url)) {
|
|
354
|
+
runCli().catch((error) => {
|
|
355
|
+
process.stderr.write(`${formatCliError(error)}\n`);
|
|
356
|
+
process.exitCode = 1;
|
|
357
|
+
});
|
|
358
|
+
}
|