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,1007 @@
|
|
|
1
|
+
# Shared script area
|
|
2
|
+
|
|
3
|
+
This directory is reserved for deterministic workflow entrypoints.
|
|
4
|
+
|
|
5
|
+
Scripts here should prefer:
|
|
6
|
+
|
|
7
|
+
1. native `gh ... watch` support when it matches the exact wait condition,
|
|
8
|
+
2. shared package helpers for pure parsing and state logic,
|
|
9
|
+
3. stable machine-readable JSON output for skills and async workflows.
|
|
10
|
+
|
|
11
|
+
In this source-loaded workspace repo, root scripts may consume shared package helpers through a thin local adapter rather than a published package import path so the checkout remains runnable without an install step.
|
|
12
|
+
|
|
13
|
+
## Authority note
|
|
14
|
+
|
|
15
|
+
For the script surfaces documented here:
|
|
16
|
+
- code, tests, and the helper entrypoints themselves are authoritative for shipped runtime behavior
|
|
17
|
+
- this README summarizes those contracts for operators and maintainers; if behavior changes, update the code/tests and then sync this document
|
|
18
|
+
- use the more specific state-graph and contract docs under `docs/` when a helper family has a narrower machine-readable contract that this README is summarizing
|
|
19
|
+
|
|
20
|
+
## Scripts
|
|
21
|
+
|
|
22
|
+
### `scripts/docs/validate-links.mjs`
|
|
23
|
+
|
|
24
|
+
Validate repo-owned markdown relative links for the shipped docs / skills / agent surface.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
- `node scripts/docs/validate-links.mjs`
|
|
28
|
+
|
|
29
|
+
Optional:
|
|
30
|
+
- `--root <path>` — override the repo root to scan (used by deterministic tests and local dry-runs against another checkout)
|
|
31
|
+
|
|
32
|
+
Contract:
|
|
33
|
+
- scans these markdown sources only: `README.md`, `PLAN.md`, `AGENTS.md`, `scripts/README.md`, `extension/README.md`, `docs/**/*.md` (excluding `docs/archive/**`), `skills/**/*.md`, and `agents/**/*.md`
|
|
34
|
+
- validates inline relative markdown links after resolving them from the containing file
|
|
35
|
+
- strips any `#fragment` before checking the filesystem
|
|
36
|
+
- treats existing files and directories as valid targets
|
|
37
|
+
- ignores external URLs, `mailto:`, fragment-only links, image links, and links inside fenced code blocks
|
|
38
|
+
- supports a checked-in narrow ignore list through repo-root `.linkcheckignore` for intentional symbolic placeholder targets (for example `docs/phases/phase-x.md`)
|
|
39
|
+
- prints actionable broken-link output with source file, line, raw target, resolved path, and a suggestion only when one clear candidate exists
|
|
40
|
+
|
|
41
|
+
Failure behavior:
|
|
42
|
+
- exits `0` when all validated links resolve
|
|
43
|
+
- exits `1` when one or more broken links are found
|
|
44
|
+
- exits non-zero (other than `1`) for usage/runtime failures
|
|
45
|
+
|
|
46
|
+
### `scripts/github/capture-review-threads.mjs`
|
|
47
|
+
|
|
48
|
+
Capture and normalize PR review-thread JSON.
|
|
49
|
+
|
|
50
|
+
Supported inputs:
|
|
51
|
+
- `--input <path>`
|
|
52
|
+
- stdin JSON
|
|
53
|
+
- live GitHub capture with `--repo <owner/name> --pr <number>`
|
|
54
|
+
|
|
55
|
+
Optional:
|
|
56
|
+
- `--output <path>` writes the same success JSON emitted on stdout
|
|
57
|
+
|
|
58
|
+
Success output shape:
|
|
59
|
+
- `{ "ok": true, "source": { ... }, "summary": { ... }, "threads": [...], "comments": [...] }`
|
|
60
|
+
- normalized `comments[]` preserve both the GraphQL comment node id (`id`) and the REST-safe numeric review-comment id (`databaseId`) when available
|
|
61
|
+
- normalized `threads[]` include `commentDatabaseIds` and `actionableCommentDatabaseIds` so follow-up helpers can pair `--comment-id` and `--thread-id` from the same fresh snapshot
|
|
62
|
+
- when `--output` is used, success output also includes `"outputPath"`
|
|
63
|
+
|
|
64
|
+
Failure behavior:
|
|
65
|
+
- malformed arguments, invalid JSON, and `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
66
|
+
- live capture is only allowed when both `--repo` and `--pr` are present
|
|
67
|
+
|
|
68
|
+
### `scripts/github/create-draft-pr.mjs`
|
|
69
|
+
|
|
70
|
+
Thin wrapper around `gh pr create` for draft-first PR creation.
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
- `node scripts/github/create-draft-pr.mjs [gh pr create args...]`
|
|
74
|
+
- `node <resolved-skill-scripts>/github/create-draft-pr.mjs [gh pr create args...]`
|
|
75
|
+
|
|
76
|
+
Contract:
|
|
77
|
+
- injects exactly one `--draft` when the caller did not already supply it
|
|
78
|
+
- rejects `--ready` before invoking `gh`; use `gh pr ready` later after draft-gate approval
|
|
79
|
+
- forwards every other argument to `gh pr create` unchanged and in order
|
|
80
|
+
- preserves the underlying `gh pr create` stdout, stderr, and exit code without wrapping success output
|
|
81
|
+
- stays intentionally narrow: this is the prevention layer for draft-first creation, while `scripts/github/reconcile-draft-gate.mjs` remains the separate recovery path for already-open non-draft PRs
|
|
82
|
+
|
|
83
|
+
Failure behavior:
|
|
84
|
+
- wrapper-owned validation failures emit `{ "ok": false, "error": "...", "usage": "..." }` on stderr and exit non-zero
|
|
85
|
+
- when `gh pr create` itself exits non-zero, this wrapper preserves the original stdout/stderr and propagates the same exit code
|
|
86
|
+
|
|
87
|
+
### `scripts/github/request-copilot-review.mjs`
|
|
88
|
+
|
|
89
|
+
Request Copilot review on a PR and verify the request deterministically.
|
|
90
|
+
|
|
91
|
+
Required:
|
|
92
|
+
- `--repo <owner/name>`
|
|
93
|
+
- `--pr <number>`
|
|
94
|
+
|
|
95
|
+
Optional:
|
|
96
|
+
- `--force-rerequest-review` — bypass the round cap when new commits exist since the last Copilot review. Refused with `no_changes_since_last_review` when the PR head has not changed since the last review. No-op when the round cap has not been reached. Normal requests now automatically re-open at round cap when the head changed, all prior feedback is resolved, and current GitHub CI is green.
|
|
97
|
+
|
|
98
|
+
Contract:
|
|
99
|
+
- checks `requested_reviewers` first so an existing Copilot request is detected without mutating PR state again
|
|
100
|
+
- requests Copilot via `gh pr edit <pr> --repo <owner/name> --add-reviewer @copilot`
|
|
101
|
+
- is suitable both for the first request after ready-for-review and for later explicit re-requests after follow-up fix commits land on the PR head
|
|
102
|
+
- enforces a round cap (default: 5, configurable via `maxCopilotRounds` in settings); clean round-cap PRs with new commits, resolved threads, and green current GitHub CI automatically re-open for a normal re-request, while `--force-rerequest-review` remains the operator override for other new-commit cases
|
|
103
|
+
- should be paired with a fresh unresolved-thread check after Copilot posts again; requesting review alone does not complete the loop
|
|
104
|
+
- verifies the result through `gh api repos/<owner>/<name>/pulls/<pr>/requested_reviewers`
|
|
105
|
+
- does **not** rely on `gh pr view --json reviewRequests`, which can be incomplete for Copilot reviewer state
|
|
106
|
+
- normalizes known repository/tooling limitations into a machine-readable `unavailable` result instead of forcing callers to parse ad hoc stderr
|
|
107
|
+
|
|
108
|
+
Success output shape:
|
|
109
|
+
- `{ "ok": true, "status": "requested"|"already-requested"|"unavailable"|"suppressed_same_head_clean"|"blocked_by_copilot_comment"|"round_cap_reached"|"no_changes_since_last_review"|"suppressed_draft", "repo": "owner/name", "pr": 17, "reviewer": "Copilot", ... }`
|
|
110
|
+
- `unavailable` also includes a `detail` string with the normalized GitHub/CLI limitation
|
|
111
|
+
- `round_cap_reached` includes `completedRounds` and `maxRounds` fields
|
|
112
|
+
- `no_changes_since_last_review` is returned by `--force-rerequest-review` when the PR head SHA has not changed since the last Copilot review
|
|
113
|
+
|
|
114
|
+
Failure behavior:
|
|
115
|
+
- malformed arguments and unexpected `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
116
|
+
|
|
117
|
+
### `scripts/github/detect-linked-issue-pr.mjs`
|
|
118
|
+
|
|
119
|
+
Detect whether an issue already has an open linked PR in the same repository.
|
|
120
|
+
|
|
121
|
+
Required:
|
|
122
|
+
- `--repo <owner/name>`
|
|
123
|
+
- `--issue <number>`
|
|
124
|
+
|
|
125
|
+
Contract:
|
|
126
|
+
- queries issue timeline linked-PR events (`CONNECTED_EVENT`, `CROSS_REFERENCED_EVENT`)
|
|
127
|
+
- pages through timeline items until `hasNextPage=false`
|
|
128
|
+
- keeps only open linked PRs in the same repository (`repository.nameWithOwner === <repo>`)
|
|
129
|
+
- also tracks closed-unmerged (state=`CLOSED`) same-repo linked PRs separately
|
|
130
|
+
- chooses deterministically when multiple candidates remain:
|
|
131
|
+
1. prefer `CONNECTED_EVENT` candidates over `CROSS_REFERENCED_EVENT`
|
|
132
|
+
2. then choose newest linked-event `createdAt`
|
|
133
|
+
3. then stable fallback by PR number/url
|
|
134
|
+
- returns a machine-readable selection payload for skills/workflows; callers should not re-implement query/pagination/tie-break logic in markdown policy text
|
|
135
|
+
|
|
136
|
+
Success output shape:
|
|
137
|
+
- when `hasOpenLinkedPr: true`: `{ "ok": true, "repo": "owner/name", "issue": 85, "hasOpenLinkedPr": true, "prNumber": 90, "prUrl": "...", "selection": { "eventType": "...", "eventCreatedAt": "..." } }`
|
|
138
|
+
- when `hasOpenLinkedPr: false`: `{ "ok": true, "repo": "owner/name", "issue": 85, "hasOpenLinkedPr": false, "prNumber": null, "prUrl": null, "hasPriorClosedUnmergedPr": true|false, "priorClosedUnmergedPrNumber": 149|null, "priorClosedUnmergedPrUrl": "..."|null }`
|
|
139
|
+
|
|
140
|
+
Failure behavior:
|
|
141
|
+
- malformed arguments and unexpected `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
142
|
+
|
|
143
|
+
### `scripts/github/resolve-tracker-local-spec.mjs`
|
|
144
|
+
|
|
145
|
+
Resolve the canonical spec bundle for tracker-backed local implementation from one
|
|
146
|
+
GitHub issue reference. This is the bounded GitHub-backed path for tracker-backed
|
|
147
|
+
local spec resolution; it does not create or read `docs/phases/phase-<n>.md`.
|
|
148
|
+
|
|
149
|
+
Allowed inputs:
|
|
150
|
+
- `--repo <owner/name>` with `--issue <number>`
|
|
151
|
+
- `--issue-url <github issue url>`
|
|
152
|
+
|
|
153
|
+
Contract:
|
|
154
|
+
- deterministically resolves exactly one repo slug + issue number pair
|
|
155
|
+
- reads the GitHub issue via `gh issue view <number> --repo <owner/name> --json number,title,body,url,state`
|
|
156
|
+
- treats the issue as canonical for tracker-backed local sessions
|
|
157
|
+
- always reports `localPhaseDocAllowed: false` so callers do not silently maintain a duplicate local phase doc for the same session
|
|
158
|
+
- leaves full tracker-sync policy to higher-level callers; this helper's bounded responsibility is spec resolution only
|
|
159
|
+
|
|
160
|
+
Success output shape:
|
|
161
|
+
- `{ "ok": true, "repo": "owner/name", "issue": 85, "issueUrl": "...", "state": "OPEN"|"CLOSED", "title": "...", "body": "...", "canonicalSpecSource": "tracker_issue", "localImplementationMode": "tracker_backed", "localPhaseDocAllowed": false, "stateSync": "tracker_issue_is_canonical" }`
|
|
162
|
+
|
|
163
|
+
Failure behavior:
|
|
164
|
+
- malformed arguments emit `{ "ok": false, "error": "...", "usage": "..." }` on stderr and exit non-zero
|
|
165
|
+
- unexpected `gh` failures and malformed `gh` JSON emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
166
|
+
|
|
167
|
+
### `scripts/github/manage-sub-issues.mjs`
|
|
168
|
+
|
|
169
|
+
Deterministic helper for reading, linking, ordering, and verifying GitHub sub-issue trees.
|
|
170
|
+
Use this for epic/umbrella issue decomposition. See [Sub-Issue Tree Contract](../docs/sub-issue-tree-contract.md) for the full workflow.
|
|
171
|
+
|
|
172
|
+
Commands:
|
|
173
|
+
- `list` — list sub-issues of a parent issue in tree order
|
|
174
|
+
- `add` — attach a child issue to a parent as a real GitHub sub-issue
|
|
175
|
+
- `reorder` — set the execution order of sub-issues (highest priority first)
|
|
176
|
+
- `verify` — verify that the current sub-issue tree matches an expected set (and optionally order)
|
|
177
|
+
|
|
178
|
+
Required for all commands:
|
|
179
|
+
- `--repo <owner/name>`
|
|
180
|
+
- `--issue <number>` (parent issue)
|
|
181
|
+
|
|
182
|
+
`add` adds: `--child <number>`
|
|
183
|
+
`reorder` adds: `--order <n1,n2,...>` (comma-separated issue numbers in desired execution order)
|
|
184
|
+
`verify` adds: `--expected <n1,n2,...>` and optional `--ordered` flag
|
|
185
|
+
|
|
186
|
+
Contract:
|
|
187
|
+
- `add` resolves the child issue's internal GitHub id before calling the sub-issues REST endpoint
|
|
188
|
+
- `reorder` first lists current sub-issues to validate all specified numbers are already in the tree, then issues sequential priority-update API calls
|
|
189
|
+
- `verify` is read-only and exits 0 for mismatch-only results; `"verified": false` is a machine-readable signal, not a process failure. Argument errors and unexpected `gh`/runtime failures still exit non-zero
|
|
190
|
+
- do not re-implement sub-issue management ad hoc or bypass this helper
|
|
191
|
+
|
|
192
|
+
Success output shapes:
|
|
193
|
+
- `list`: `{ "ok": true, "repo": "owner/name", "issue": N, "command": "list", "subIssues": [{ "number": M, "title": "...", "state": "...", "id": ID }, ...] }`
|
|
194
|
+
- `add`: `{ "ok": true, "repo": "owner/name", "issue": N, "command": "add", "child": M }`
|
|
195
|
+
- `reorder`: `{ "ok": true, "repo": "owner/name", "issue": N, "command": "reorder", "order": [n1, n2, ...] }`
|
|
196
|
+
- `verify`: `{ "ok": true, ..., "command": "verify", "verified": true|false, "expected": [...], "actual": [...], "missing": [...], "unexpected": [...] }` (plus `"orderMismatch": true` when `--ordered` and only the order differs)
|
|
197
|
+
|
|
198
|
+
Failure behavior:
|
|
199
|
+
- malformed arguments and unexpected `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
200
|
+
- argument errors also include `"usage"` in the error payload
|
|
201
|
+
|
|
202
|
+
### `scripts/refine/verify.mjs`
|
|
203
|
+
|
|
204
|
+
Verify epic-tree refinement structure and contracts in one pass.
|
|
205
|
+
|
|
206
|
+
Usage:
|
|
207
|
+
- `node scripts/refine/verify.mjs --issue <number> [--repo <owner/name>] [--json]`
|
|
208
|
+
- `node scripts/refine/verify.mjs --input <path> [--json]`
|
|
209
|
+
|
|
210
|
+
Contract:
|
|
211
|
+
- runs four deterministic checkers:
|
|
212
|
+
- `prose-linkage-detector` (forbidden prose parent/child/dependency markers + missing API links)
|
|
213
|
+
- `scope-boundary-cross-checker` (mutual-exclusion gaps + duplicate sibling ownership)
|
|
214
|
+
- `refinement-completeness-checker` (AC/DoD/non-goals/matrix presence)
|
|
215
|
+
- `tree-integrity-validator` (parent links, orphan detection, cycles, max depth 3)
|
|
216
|
+
- online mode (`--issue`) walks GitHub sub-issues API from the root issue
|
|
217
|
+
- offline mode (`--input`) validates a local refinement tree JSON snapshot
|
|
218
|
+
- default output is human-readable text; `--json` emits machine-readable output for CI
|
|
219
|
+
|
|
220
|
+
Failure behavior:
|
|
221
|
+
- checker findings are process-failing (`exit 1`) because verification did not pass
|
|
222
|
+
- malformed arguments and runtime errors emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
223
|
+
|
|
224
|
+
### `scripts/github/stage-reviewer-draft.mjs`
|
|
225
|
+
|
|
226
|
+
Stage a pending reviewer-side draft review from a merged deterministic review package.
|
|
227
|
+
|
|
228
|
+
Required:
|
|
229
|
+
- `--repo <owner/name>`
|
|
230
|
+
- `--pr <number>`
|
|
231
|
+
- `--review-file <path>`
|
|
232
|
+
|
|
233
|
+
Optional:
|
|
234
|
+
- `--local-state-output <path>` writes/merges deterministic draft-review metadata for later
|
|
235
|
+
`detect-reviewer-loop-state.mjs --local-state` use
|
|
236
|
+
|
|
237
|
+
Contract:
|
|
238
|
+
- reads a merged reviewer result JSON file (for example output derived from `mergeReviewerResults`)
|
|
239
|
+
- builds a deterministic pending-review payload pinned to the review package `headSha`
|
|
240
|
+
- posts the pending review to `repos/<owner>/<name>/pulls/<pr>/reviews` without an `event` field so GitHub keeps it pending
|
|
241
|
+
- returns the draft review id/url/commit sha
|
|
242
|
+
- optionally writes bounded local reviewer-loop metadata including `draftReviewPosted`, `draftReviewId`, `draftReviewUrl`, `draftReviewCommitSha`, and `draftReviewNotificationStatus`
|
|
243
|
+
|
|
244
|
+
Success output shape:
|
|
245
|
+
- `{ "ok": true, "repo": "owner/name", "pr": 17, "reviewId": 456, "reviewUrl": "...", "reviewState": "PENDING", "commitSha": "abc123", "localStatePath": "..."|null }`
|
|
246
|
+
|
|
247
|
+
Failure behavior:
|
|
248
|
+
- malformed arguments, invalid review JSON, missing `headSha`, unexpected `gh` failures, and malformed review-create responses emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
249
|
+
|
|
250
|
+
### `scripts/github/reply-resolve-review-thread.mjs`
|
|
251
|
+
|
|
252
|
+
Reply to a PR review comment and resolve the associated review thread deterministically.
|
|
253
|
+
|
|
254
|
+
Required:
|
|
255
|
+
- `--repo <owner/name>`
|
|
256
|
+
- `--pr <number>`
|
|
257
|
+
- `--comment-id <number>`
|
|
258
|
+
- `--thread-id <node-id>`
|
|
259
|
+
- `--body-file <path>`
|
|
260
|
+
|
|
261
|
+
Contract:
|
|
262
|
+
- reads the reply body from a file so shell quoting does not become part of the workflow logic
|
|
263
|
+
- validates the live PR thread snapshot before mutating GitHub so `--comment-id` and `--thread-id` must refer to the same thread on the target PR
|
|
264
|
+
- posts the reply to `repos/<owner>/<name>/pulls/<pr>/comments/<comment-id>/replies`
|
|
265
|
+
- resolves the thread with the GraphQL `resolveReviewThread` mutation
|
|
266
|
+
- fails if the thread does not report resolved after the mutation
|
|
267
|
+
|
|
268
|
+
Success output shape:
|
|
269
|
+
- `{ "ok": true, "repo": "owner/name", "pr": 17, "commentId": 123, "threadId": "...", "replyId": 456, "replyUrl": "...", "resolved": true }`
|
|
270
|
+
|
|
271
|
+
Failure behavior:
|
|
272
|
+
- malformed arguments, empty body files, missing threads, missing comments, comment/thread mismatches, unexpected `gh` failures, and unsuccessful resolve responses emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
273
|
+
|
|
274
|
+
### `scripts/github/reply-resolve-review-threads.mjs`
|
|
275
|
+
|
|
276
|
+
Reply to all matching unresolved review threads on one PR and optionally resolve them with one bounded note.
|
|
277
|
+
|
|
278
|
+
Required:
|
|
279
|
+
- `--repo <owner/name>`
|
|
280
|
+
- `--pr <number>`
|
|
281
|
+
|
|
282
|
+
Optional:
|
|
283
|
+
- `--author <login>` (default `Copilot`)
|
|
284
|
+
- exactly one message source: `--message <text>` or stdin
|
|
285
|
+
- `--resolve`
|
|
286
|
+
|
|
287
|
+
Contract:
|
|
288
|
+
- captures one authoritative review-thread snapshot via `capture-review-threads.mjs`
|
|
289
|
+
- filters to unresolved threads containing at least one comment by the selected author
|
|
290
|
+
- chooses the newest matching author-authored comment in each matched thread as the REST reply target
|
|
291
|
+
- processes matched threads sequentially in deterministic snapshot order
|
|
292
|
+
- reuses the shared single-thread reply/resolve primitives instead of duplicating GitHub mutation logic
|
|
293
|
+
- with `--resolve`, re-captures the review-thread snapshot at the end and fails closed if any targeted thread remains unresolved
|
|
294
|
+
- zero-match runs are deterministic no-ops with success JSON
|
|
295
|
+
|
|
296
|
+
Success output shape:
|
|
297
|
+
- `{ "ok": true, "repo": "owner/name", "pr": 17, "author": "Copilot", "resolve": true|false, "matchedThreadCount": 2, "repliedThreadCount": 2, "resolvedThreadCount": 2, "skippedThreadCount": 1, "results": [{ "threadId": "...", "commentId": 123, "replyId": 456, "replyUrl": "...", "resolved": true }] }`
|
|
298
|
+
|
|
299
|
+
Failure behavior:
|
|
300
|
+
- malformed arguments, empty/conflicting message input, malformed thread snapshots, unexpected `gh` failures, reply failures, resolve failures, and failed post-resolve verification emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
301
|
+
- when partial progress exists, stderr JSON also includes `partialProgress`
|
|
302
|
+
|
|
303
|
+
For new GitHub mutation helpers in this repo, do not stop at fixture-only confidence when a real PR is available and mutation is authorized. Run a bounded real-PR smoke check before depending on the helper inside a longer async review/fix loop.
|
|
304
|
+
|
|
305
|
+
### `scripts/github/probe-copilot-review.mjs`
|
|
306
|
+
|
|
307
|
+
Watch for fresh Copilot-authored review activity on a PR.
|
|
308
|
+
|
|
309
|
+
Required:
|
|
310
|
+
- `--repo <owner/name>`
|
|
311
|
+
- `--pr <number>`
|
|
312
|
+
|
|
313
|
+
Optional:
|
|
314
|
+
- `--poll-interval-ms <positive-integer>` (default `60000`, i.e. 1 minute)
|
|
315
|
+
- `--timeout-ms <non-negative-integer>` (default `1800000`, i.e. 30 minutes)
|
|
316
|
+
|
|
317
|
+
Contract:
|
|
318
|
+
- captures a baseline snapshot, then performs a bounded number of follow-up polls
|
|
319
|
+
- returns `changed` for any fresh Copilot-authored review-thread comments, PR review summaries, or PR issue comments that were not present in the baseline snapshot
|
|
320
|
+
- ignores fresh non-Copilot review activity across those same surfaces
|
|
321
|
+
- `--timeout-ms 0` performs a single immediate recheck and returns `idle` if unchanged
|
|
322
|
+
|
|
323
|
+
Success output shape:
|
|
324
|
+
- `{ "ok": true, "status": "changed"|"timeout"|"idle", "repo": "owner/name", "pr": 17, "attempts": 1, "newComments": [...], "newReviews": [...], "newIssueComments": [...] }`
|
|
325
|
+
|
|
326
|
+
Failure behavior:
|
|
327
|
+
- malformed arguments and `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
328
|
+
|
|
329
|
+
### `scripts/loop/detect-initial-copilot-pr-state.mjs`
|
|
330
|
+
|
|
331
|
+
Detect the post-assignment issue-to-linked-PR seam for Copilot handoff.
|
|
332
|
+
|
|
333
|
+
Required:
|
|
334
|
+
- `--repo <owner/name>`
|
|
335
|
+
- `--issue <number>`
|
|
336
|
+
|
|
337
|
+
Contract:
|
|
338
|
+
- uses `scripts/github/detect-linked-issue-pr.mjs` as the authoritative linked-PR selector
|
|
339
|
+
- returns exactly one deterministic state:
|
|
340
|
+
- `no_linked_pr`
|
|
341
|
+
- `prior_linked_pr_closed_unmerged`
|
|
342
|
+
- `copilot_session_active`
|
|
343
|
+
- `waiting_for_initial_copilot_implementation`
|
|
344
|
+
- `linked_pr_ready_for_followup`
|
|
345
|
+
- uses `scripts/loop/detect-copilot-session-activity.mjs` on the linked PR head branch for Copilot-authored draft PRs
|
|
346
|
+
- while `activity=active`, emits `copilot_session_active` regardless of commit/file-count heuristics
|
|
347
|
+
- approval-gated `action_required` Copilot/Actions runs are treated as observational (non-active) for this bootstrap seam
|
|
348
|
+
- for non-bootstrap linked PRs, falls back to the existing substantive PR heuristics when session activity is `idle` or `concluded`
|
|
349
|
+
- if the session-activity check itself fails, the helper fails closed instead of pretending session state was unavailable
|
|
350
|
+
- classifies `prior_linked_pr_closed_unmerged` when there is no open linked PR but a same-repo linked PR was previously closed without merging; this is a terminal non-wait state requiring human reconciliation
|
|
351
|
+
- classifies `waiting_for_initial_copilot_implementation` only for the bounded bootstrap-only draft shape:
|
|
352
|
+
- open same-repo linked PR
|
|
353
|
+
- draft
|
|
354
|
+
- Copilot-authored (`Copilot`, `copilot-swe-agent`, `app/copilot-swe-agent`, or `copilot-swe-agent[bot]`)
|
|
355
|
+
- exactly 1 commit
|
|
356
|
+
- sole commit headline exactly `Initial plan`
|
|
357
|
+
- exactly 0 changed files
|
|
358
|
+
- fails closed with explicit error output when required PR facts cannot be fetched
|
|
359
|
+
|
|
360
|
+
Success output shape:
|
|
361
|
+
- `{ "ok": true, "repo": "owner/name", "issue": 59, "state": "...", "prNumber": 79|null, "prUrl": "..."|null, "headBranch": "..."|null, "authorLogin": "Copilot"|null, "isDraft": true|false|null, "changedFiles": 0|null, "commitCount": 1|null, "soleCommitHeadline": "Initial plan"|null, "sessionActivity": "active"|"concluded"|"idle"|null, "sessionRunId": 123|null, "sessionRunName": "..."|null, "sessionRunStatus": "..."|null, "sessionRunConclusion": string|null, "sessionRunCreatedAt": "..."|null, "sessionConfidence": "high"|null }`
|
|
362
|
+
|
|
363
|
+
### `scripts/loop/run-conductor-cycle.mjs`
|
|
364
|
+
|
|
365
|
+
Poll all open PRs, detect gate state, and emit an ordered action queue.
|
|
366
|
+
|
|
367
|
+
Output `actions[]` entries include:
|
|
368
|
+
- `requiresSubagent`
|
|
369
|
+
- `requiresApproval`
|
|
370
|
+
- `handoffContract` with:
|
|
371
|
+
- `ownership`
|
|
372
|
+
- `stopBoundary`
|
|
373
|
+
- `resumePolicy`
|
|
374
|
+
|
|
375
|
+
The handoff contract is recorded on each action so parent, child, and resume
|
|
376
|
+
actions can share one explicit stop/resume boundary.
|
|
377
|
+
|
|
378
|
+
Contract values:
|
|
379
|
+
- `ownership`: `subagent`, `parent`, `human`, or `terminal`
|
|
380
|
+
- `stopBoundary`: `subagent_exit`, `watch_boundary`, `approval_boundary`,
|
|
381
|
+
`merge_boundary`, `conflict_boundary`, or `terminal_boundary`
|
|
382
|
+
- `resumePolicy`: `resume_after_subagent_exit`,
|
|
383
|
+
`resume_after_state_refresh`, `resume_after_human_approval`,
|
|
384
|
+
`resume_after_merge_authorization`, `manual_attention`, or `none`
|
|
385
|
+
|
|
386
|
+
`run-conductor-cycle.mjs` emits the contract-boundary values above. The parser
|
|
387
|
+
also accepts `PR_CHECKPOINT` values when reading recorded artifacts so older
|
|
388
|
+
recorded handoff notes can still be validated fail-closed.
|
|
389
|
+
|
|
390
|
+
### `scripts/loop/conductor-monitor.mjs`
|
|
391
|
+
|
|
392
|
+
Aggregate the current Copilot-loop status for every open PR in one repository.
|
|
393
|
+
|
|
394
|
+
Required:
|
|
395
|
+
- `--repo <owner/name>`
|
|
396
|
+
|
|
397
|
+
Optional:
|
|
398
|
+
- `--auto-resume` — inspect documented pi-subagents async/session artifacts on disk, detect orphaned open PR follow-up runs, and emit deterministic resume plans without executing `subagent({ action: "resume" })`
|
|
399
|
+
|
|
400
|
+
Contract:
|
|
401
|
+
- lists all open PRs via `gh pr list --state open --limit 1000` to avoid GitHub CLI default truncation
|
|
402
|
+
- reuses `detect-copilot-loop-state.mjs` logic for each PR instead of inventing a second state classifier
|
|
403
|
+
- reports one queue-level summary with per-PR loop state, next action, and whether human follow-up is needed now
|
|
404
|
+
- reports `queue_complete` when the open-PR queue is empty
|
|
405
|
+
- keeps the current implementation human-in-the-loop; fully autonomous monitor ownership is a follow-up slice
|
|
406
|
+
- when `--auto-resume` is present, scans documented async evidence sources only:
|
|
407
|
+
- pi-subagents async run dirs under `<tmpdir>/pi-subagents-*/async-subagent-runs/<runId>/`
|
|
408
|
+
(`status.json`, `events.jsonl`, `output-<n>.log`)
|
|
409
|
+
- pi-subagents async result artifacts under
|
|
410
|
+
`<tmpdir>/pi-subagents-*/async-subagent-results/`
|
|
411
|
+
- persisted session/subagent artifacts under the Pi sessions tree (for example
|
|
412
|
+
`~/.pi/agent/sessions/.../subagent-artifacts/*_dev-loop_*_meta.json` and
|
|
413
|
+
`~/.pi/agent/sessions/.../subagent-artifacts/*_dev-loop_*_output.md`
|
|
414
|
+
plus matching `run-<n>/session.jsonl` files)
|
|
415
|
+
- ignores non-`dev-loop` agents, other repositories, merged PRs, and any exited run superseded by a newer matching `running` or `queued` run
|
|
416
|
+
- matches exited runs to open PRs only by PR number parsed from artifact text; branch names, issue numbers, or worktree paths alone are never sufficient identity
|
|
417
|
+
- fail-closes to `needsManualAttention` when PR identity, artifact state, or resume inputs are missing, contradictory, or ambiguous
|
|
418
|
+
- `--auto-resume` remains single-shot only; it does not poll, sleep, watch, or execute the resume itself
|
|
419
|
+
|
|
420
|
+
Success output shape:
|
|
421
|
+
- default:
|
|
422
|
+
- `{ "ok": true, "repo": "owner/name", "checkedAt": "...", "prCount": 2, "queueStatus": "queue_complete"|"monitoring"|"attention_needed", "needsAttentionCount": 0, "summary": { "waiting": 0, "needsAttention": 0, "blocked": 0, "done": 0 }, "prs": [...] }`
|
|
423
|
+
- with `--auto-resume`:
|
|
424
|
+
- adds
|
|
425
|
+
`{ "autoResumeRequested": true, "orphanedPrCount": 1, "resumePlanCount": 1, "manualAttentionCount": 0, "resumePlans": [...], "needsManualAttention": [...] }`
|
|
426
|
+
|
|
427
|
+
`resumePlans[]` fields:
|
|
428
|
+
- `pr`
|
|
429
|
+
- `runId`
|
|
430
|
+
- `runState` (`completed` | `failed` | `paused`)
|
|
431
|
+
- `artifactPath`
|
|
432
|
+
- `sessionPath` when known
|
|
433
|
+
- `parsedArtifactState`
|
|
434
|
+
- `parsedLoopState`
|
|
435
|
+
- `livePrState`
|
|
436
|
+
- `resumeAction`
|
|
437
|
+
- `handoffContract`
|
|
438
|
+
- `recordedHandoffContract` when the artifact explicitly records one
|
|
439
|
+
- `resumeMessage`
|
|
440
|
+
- `resumeCommandPreview`
|
|
441
|
+
- `staleWorktree`
|
|
442
|
+
|
|
443
|
+
If a recorded handoff contract is present, it must include explicit
|
|
444
|
+
`Handoff ownership`, `Stop boundary`, and `Resume policy` lines. The monitor
|
|
445
|
+
fails closed when those fields are partial, invalid, or conflict with the
|
|
446
|
+
live-derived resume plan.
|
|
447
|
+
|
|
448
|
+
`needsManualAttention[]` fields:
|
|
449
|
+
- `pr` when known
|
|
450
|
+
- `runId` when known
|
|
451
|
+
- `reason`
|
|
452
|
+
- `evidence`
|
|
453
|
+
- `suggestedNextStep`
|
|
454
|
+
|
|
455
|
+
Failure behavior:
|
|
456
|
+
- malformed arguments and unexpected `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
457
|
+
|
|
458
|
+
### `scripts/loop/detect-copilot-session-activity.mjs`
|
|
459
|
+
|
|
460
|
+
Detect deterministic Copilot workflow session activity on a branch.
|
|
461
|
+
|
|
462
|
+
Required:
|
|
463
|
+
- `--repo <owner/name>`
|
|
464
|
+
- `--branch <name>`
|
|
465
|
+
|
|
466
|
+
Optional:
|
|
467
|
+
- `--limit <positive-integer>` (default `20`)
|
|
468
|
+
|
|
469
|
+
Contract:
|
|
470
|
+
- uses `gh run list --branch <branch>` as the primary signal
|
|
471
|
+
- pattern-matches known Copilot run names (`Copilot coding for issue`, `Addressing comment on PR`, `Addressing review on PR`)
|
|
472
|
+
- classifies activity as:
|
|
473
|
+
- `active` when a matching run is currently in progress
|
|
474
|
+
- `concluded` when the most recent matching run is completed
|
|
475
|
+
- `concluded` (non-blocking observational) when a matching run is approval-gated in `action_required`; the payload still preserves the raw `runStatus` / `runConclusion` strings for debugging
|
|
476
|
+
- `idle` when no matching runs are found
|
|
477
|
+
|
|
478
|
+
Failure behavior:
|
|
479
|
+
- malformed arguments and unexpected `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
480
|
+
|
|
481
|
+
### `scripts/loop/copilot-pr-handoff.mjs`
|
|
482
|
+
|
|
483
|
+
Thin high-level helper for the common Copilot PR follow-up handoff path.
|
|
484
|
+
|
|
485
|
+
Required:
|
|
486
|
+
- `--repo <owner/name>`
|
|
487
|
+
- `--pr <number>`
|
|
488
|
+
|
|
489
|
+
Optional:
|
|
490
|
+
- `--watch-status <changed|timeout|idle>` — refresh deterministic state after a prior watcher observation; this readback mode never requests review again
|
|
491
|
+
|
|
492
|
+
Contract:
|
|
493
|
+
- this helper is the source of truth for normal request/re-request/watch routing on Copilot PR follow-up
|
|
494
|
+
- emits deterministic `action` + `nextAction` + `reviewRequestStatus` and a helper-owned `requestWatchContract` envelope for status interpretation
|
|
495
|
+
- enters watch only when request state is confirmed (`requested` or `already-requested`) and emits exact `watchArgs` + `watchTimeoutPolicy`
|
|
496
|
+
- watch refresh (`--watch-status`) is observational-only; rely on refreshed `loopDisposition` + `terminal` to decide whether to continue or stop
|
|
497
|
+
- explicit stop/blocked routing is machine-readable via `action: "stop"` plus `requestWatchContract.stopState`
|
|
498
|
+
- when `PI_SUBAGENT_RUN_ID` is set to a non-empty value, the helper enforces one-runner-per-PR ownership and returns `runnerOwnership` details when a conflicting or displaced run must stop
|
|
499
|
+
|
|
500
|
+
Success output shape:
|
|
501
|
+
- `{ "ok": true, "action": "watch"|"fix"|"stop", "state": "...", "allowedTransitions": [...], "nextAction": "...", "snapshot": {...}, "reviewRequestStatus"?: "...", "watchStatus"?: "...", "autoRerequestEligible": true|false, "sameHeadCleanConverged": true|false, "loopDisposition": "...", "terminal": true|false, "requestWatchContract": { "action": "...", "nextAction": "...", "requestStatus": "requested"|"already-requested"|"unavailable"|"failed"|"none", "routingState": "copilot_request_confirmed_waiting"|"ready_state_needs_copilot_request"|"draft_reset_requires_ready_state_reentry"|"non_ready_state", "watchEntryConfirmed": true|false, "watchArgs": { ... }|null, "stopState"?: "unavailable"|"blocked"|"draft_requires_ready_state_reentry"|"no_automatic_next_step" }, "watchTimeoutPolicy"?: { "classification": "external_healthy_wait", "minimumTimeoutMs": 1800000, "defaultTimeoutMs": 1800000 }, "watchArgs"?: { ... } }`
|
|
502
|
+
|
|
503
|
+
Failure behavior:
|
|
504
|
+
- malformed arguments and unexpected `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
505
|
+
|
|
506
|
+
### `scripts/loop/pr-runner-coordination.mjs`
|
|
507
|
+
|
|
508
|
+
Durable one-runner-per-PR coordination helper.
|
|
509
|
+
|
|
510
|
+
Subcommands:
|
|
511
|
+
- `status` — inspect current ownership record for one PR
|
|
512
|
+
- `claim` — claim ownership when no active runner exists; otherwise fail closed with machine-readable conflict details
|
|
513
|
+
- `takeover` — explicit replacement path that records prior/new run ids
|
|
514
|
+
- `assert` — verify the current run still owns the PR; `--require-existing` fails closed when async pre-merge ownership proof is missing
|
|
515
|
+
- `release` — clear ownership when the active run is done
|
|
516
|
+
|
|
517
|
+
State path:
|
|
518
|
+
- `.pi/runner-coordination/<owner>/<repo>/pr-<number>.json`
|
|
519
|
+
- this is local coordination state under `.pi/`; keep it uncommitted like other repo-local runtime artifacts
|
|
520
|
+
|
|
521
|
+
Integration notes:
|
|
522
|
+
- `copilot-pr-handoff.mjs` uses this surface for async run ownership checks
|
|
523
|
+
- `detect-checkpoint-evidence.mjs` uses strict ownership assertion before async merge-time gate evaluation
|
|
524
|
+
|
|
525
|
+
### `scripts/loop/run-watch-cycle.mjs`
|
|
526
|
+
|
|
527
|
+
Deterministic handoff → watch helper for one Copilot wait-cycle boundary.
|
|
528
|
+
|
|
529
|
+
Required:
|
|
530
|
+
- `--repo <owner/name>`
|
|
531
|
+
- `--pr <number>`
|
|
532
|
+
|
|
533
|
+
Optional:
|
|
534
|
+
- `--probe-only` — use a single immediate recheck (`timeoutMs: 0`) for explicit status probes only
|
|
535
|
+
|
|
536
|
+
Contract:
|
|
537
|
+
- runs `copilot-pr-handoff.mjs` first and preserves its current state / next action / watch args
|
|
538
|
+
- when handoff stays in watch mode, checks Copilot session activity on the PR head branch via `detect-copilot-session-activity.mjs`
|
|
539
|
+
- when activity is `active`, blocks on `gh run watch <run-id>` and then continues with the same emitted persistent watch budget instead of silently degrading to a zero-timeout probe
|
|
540
|
+
- when handoff returns `action: "watch"`, runs `probe-copilot-review.mjs` with the emitted `watchArgs`; zero-timeout probes are reserved for explicit `--probe-only` status checks
|
|
541
|
+
- treats `waiting_for_copilot_review` as a persistence boundary, not a completion boundary
|
|
542
|
+
- for explicit async loop entry/continuation, `cycleDisposition: "pending"` with `terminal: false` means stay attached and run another watch boundary rather than exiting as clean success
|
|
543
|
+
- after a follow-up fix / reply-resolve / re-request path returns to `waiting_for_copilot_review`, resume this helper again instead of treating the re-request handoff as completion
|
|
544
|
+
- handoff-only behavior must be explicitly requested; do not silently reinterpret async loop entry as one-step transition behavior
|
|
545
|
+
- preserves the shared Copilot-loop `loopDisposition` contract from the handoff/state-machine output (`pending`, `unresolved_feedback`, `clean_converged`, `blocked`, `action_required`, `done`)
|
|
546
|
+
- exposes the helper's coarser wait-cycle summary separately as `cycleDisposition`
|
|
547
|
+
- reports `cycleDisposition: "pending"` for quiet watch results (`timeout` or explicit probe `idle`) instead of pretending the loop concluded cleanly
|
|
548
|
+
- reserves zero-timeout `idle` probes for explicit status/reattach checks; normal async waiting should use the emitted non-zero watch timeout
|
|
549
|
+
- returns `cycleDisposition: "needs_followup"` when fresh Copilot activity appears or handoff already routed directly to `fix`
|
|
550
|
+
- returns `cycleDisposition: "terminal"` only when handoff routed to `stop`
|
|
551
|
+
|
|
552
|
+
Success output shape:
|
|
553
|
+
- `{ "ok": true, "handoffAction": "watch"|"fix"|"stop", "state": "...", "allowedTransitions": [...], "nextAction": "...", "snapshot": {...}, "reviewRequestStatus"?: "...", "watchArgs"?: { ... }, "watchTimeoutPolicy"?: { "classification": "external_healthy_wait", "minimumTimeoutMs": 1800000, "defaultTimeoutMs": 1800000 }, "watchStatus"?: "changed"|"timeout"|"idle", "watch"?: { ... }, "loopDisposition": "pending"|"unresolved_feedback"|"clean_converged"|"blocked"|"action_required"|"done", "cycleDisposition": "pending"|"needs_followup"|"terminal", "terminal": true|false }`
|
|
554
|
+
|
|
555
|
+
Failure behavior:
|
|
556
|
+
- malformed arguments and unexpected `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
557
|
+
|
|
558
|
+
### `scripts/loop/detect-copilot-loop-state.mjs`
|
|
559
|
+
|
|
560
|
+
Deterministic Copilot-loop state detector. Captures current loop state from observable PR/GitHub
|
|
561
|
+
facts and interprets the snapshot into one explicit current state, allowed next transitions, and
|
|
562
|
+
a recommended next action. This script is the orchestration authority for the async Copilot
|
|
563
|
+
review/fix loop; see [Copilot Loop State Graph](../docs/copilot-loop-state-graph.md) for the full state-graph design.
|
|
564
|
+
|
|
565
|
+
Two modes:
|
|
566
|
+
|
|
567
|
+
- **Auto-detect**: `--repo <owner/name> --pr <number>`
|
|
568
|
+
Fetches PR state, Copilot review request status, review threads, and CI checks from GitHub,
|
|
569
|
+
builds a snapshot, and interprets it. PR CI/check normalization is owned by
|
|
570
|
+
[Copilot CI Status Contract](../skills/docs/copilot-ci-status-contract.md).
|
|
571
|
+
|
|
572
|
+
- **Snapshot interpretation**: `--input <path>`
|
|
573
|
+
Reads a pre-built snapshot JSON and interprets it without any `gh` calls. Use this mode when
|
|
574
|
+
the caller has already gathered facts — for example, to incorporate the `status` field from a
|
|
575
|
+
prior `scripts/github/request-copilot-review.mjs` run, which can report `unavailable` or
|
|
576
|
+
`failed` statuses that are not observable from static GitHub state alone.
|
|
577
|
+
|
|
578
|
+
Optional (auto-detect mode only):
|
|
579
|
+
- `--steering-state-file <path>`
|
|
580
|
+
Overlay the detected state with the current persisted steering contract state.
|
|
581
|
+
The detector stays read-only: it does not promote queued steering or write the
|
|
582
|
+
steering file. This is available only in `--repo/--pr` mode; snapshot `--input`
|
|
583
|
+
mode does not accept steering files because repo/pr target identity cannot be
|
|
584
|
+
proven from the snapshot alone.
|
|
585
|
+
- `--review-request-status <requested|already-requested|unavailable|none|failed>`
|
|
586
|
+
Override the Copilot review-request status with a known prior result. Skips the
|
|
587
|
+
`requested_reviewers` API call and injects the provided value directly into the snapshot.
|
|
588
|
+
Use when the caller already ran `request-copilot-review.mjs` and wants to inject its output
|
|
589
|
+
status without re-probing the reviewers endpoint.
|
|
590
|
+
|
|
591
|
+
Snapshot schema (`--input` mode or `snapshot` field in success output):
|
|
592
|
+
- `prExists` {boolean} — whether a PR was found
|
|
593
|
+
- `prNumber` {number|null} — PR number if prExists, otherwise null
|
|
594
|
+
- `prDraft` {boolean} — whether the PR is in draft state
|
|
595
|
+
- `prMerged` {boolean} — whether the PR has been merged
|
|
596
|
+
- `prClosed` {boolean} — whether the PR was closed without merge; merged PRs set `prMerged=true` instead of reusing `prClosed`
|
|
597
|
+
- `copilotReviewRequestStatus` {"requested"|"already-requested"|"unavailable"|"none"|"failed"} — current known Copilot review-request state
|
|
598
|
+
- `copilotReviewPresent` {boolean} — whether at least one Copilot review exists on the PR
|
|
599
|
+
- `copilotReviewOnCurrentHead` {boolean} — whether a submitted (non-PENDING) Copilot review exists for the current head commit
|
|
600
|
+
- `unresolvedThreadCount` {number} — total unresolved review-thread count
|
|
601
|
+
- `actionableThreadCount` {number} — unresolved threads with non-bot actionable comments
|
|
602
|
+
- `copilotReviewRoundCount` {number} — completed Copilot review rounds observed on the PR
|
|
603
|
+
- `ciStatus` {"success"|"failure"|"pending"|"none"} — contract-owned current-head CI/check rollup
|
|
604
|
+
from [Copilot CI Status Contract](../skills/docs/copilot-ci-status-contract.md); `none` means no usable readiness signal yet
|
|
605
|
+
- `agentFixStatus` {"applied"|null} — agent-provided: "applied" when code has been fixed
|
|
606
|
+
|
|
607
|
+
Success output shape:
|
|
608
|
+
- `{ "ok": true, "snapshot": { ... }, "state": "...", "allowedTransitions": [...], "nextAction": "...", "autoRerequestEligible": true|false, "sameHeadCleanConverged": true|false, "loopDisposition": "...", "terminal": true|false }`
|
|
609
|
+
- `state` is one of the stable state names defined in [Copilot Loop State Graph](../docs/copilot-loop-state-graph.md)
|
|
610
|
+
- `allowedTransitions` is the list of states reachable from `state`
|
|
611
|
+
- `nextAction` is a human-readable recommended next step
|
|
612
|
+
- `autoRerequestEligible` is `true` only when a meaningful remediation event has made automatic re-request valid again
|
|
613
|
+
- `sameHeadCleanConverged` is `true` when the current head has clean convergence (a submitted Copilot review, zero unresolved and actionable threads, and CI not blocked), suppressing automatic same-head re-request
|
|
614
|
+
- `loopDisposition` is the high-level refreshed classification: `pending`, `unresolved_feedback`, `clean_converged`, `blocked`, `action_required`, or `done`
|
|
615
|
+
- `terminal` is `true` only for clean-converged, blocked, or done states; watcher timeout/idle must be treated as non-terminal until a refreshed detector output proves `terminal=true`
|
|
616
|
+
|
|
617
|
+
Failure behavior:
|
|
618
|
+
- Malformed arguments, unexpected `gh` failures, and review-thread detection failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
619
|
+
|
|
620
|
+
Key behavioral guarantees:
|
|
621
|
+
- When `unresolvedThreadCount > 0`, the state is always in the fix/reply-resolve family — never `waiting_for_copilot_review` or any wait state
|
|
622
|
+
- When `copilotReviewRequestStatus` is `unavailable` or `failed`, the state is a terminal stop/report state with no allowed transitions
|
|
623
|
+
- When `agentFixStatus` is `"applied"` and unresolved threads exist, the state is `already_fixed_needs_reply_resolve`, and `allowedTransitions` includes only `ready_to_rerequest_review`
|
|
624
|
+
- When the current head has clean convergence (a submitted Copilot review, zero unresolved and actionable threads, and CI not blocked), automatic same-head re-request is suppressed until a meaningful remediation event occurs
|
|
625
|
+
- If review-thread state cannot be determined during auto-detect, the script fails closed instead of assuming zero unresolved threads
|
|
626
|
+
- When `--steering-state-file` is provided, steering is surfaced as a read-only overlay;
|
|
627
|
+
queued steering promotion/persistence is owned explicitly by `steer-loop.mjs promote`
|
|
628
|
+
|
|
629
|
+
### `scripts/github/upsert-checkpoint-verdict.mjs`
|
|
630
|
+
|
|
631
|
+
Creates or updates the visible gate-review PR comment for one `gate + headSha` pair.
|
|
632
|
+
Use this at the `draft_gate` / `pre_approval_gate` boundaries so same-head reruns
|
|
633
|
+
remain idempotent: the helper updates an existing same-head marker in place when
|
|
634
|
+
correction is needed and suppresses duplicate reposts when the visible comment
|
|
635
|
+
already matches the requested contract fields. The rendered visible comment uses compact readable labels (`Gate review`, `Reviewed head SHA`, `Verdict`, `Findings summary`, `Next action`). When a gate pass needed corrective changes before reaching `clean`, pass a truthful `--findings-summary` that briefly states the gap, the change, and why the current head is now acceptable instead of defaulting to `no issues found`. Verbose multiline `--findings-summary` input is compacted before posting so visible gate comments keep validation reporting bounded: raw passing output is omitted, command/count/CI signals are preferred, and any failure excerpt uses a deterministic retained-prefix limit plus a short truncation marker suffix when needed.
|
|
636
|
+
|
|
637
|
+
Required:
|
|
638
|
+
- `--repo <owner/name>`
|
|
639
|
+
- `--pr <number>`
|
|
640
|
+
- `--gate <draft_gate|pre_approval_gate>`
|
|
641
|
+
- `--head-sha <sha>` — full current head SHA or a hexadecimal prefix of it; the helper canonicalizes to the full current head before comparing/updating visible markers
|
|
642
|
+
- `--verdict <clean|findings_present|blocked>`
|
|
643
|
+
- `--findings-summary <text>`
|
|
644
|
+
- `--next-action <text>`
|
|
645
|
+
|
|
646
|
+
Optional:
|
|
647
|
+
- `--local-validation-head-sha <sha>` — reuse the bounded `crediblyGreen` exception when GitHub created zero current-head suites/statuses and local verification already passed for that exact head
|
|
648
|
+
- `--force --force-reason <text>` — narrow operator-authorized CI override for the helper-local gate-entry refusal only; `--force-reason` is required with `--force`, `--force-reason` without `--force` is rejected, and the reason text is whitespace-normalized for machine-readable output
|
|
649
|
+
|
|
650
|
+
Success output shape:
|
|
651
|
+
- standard path: `{ "ok": true, "action": "created"|"updated"|"noop", "repo": "owner/repo", "pr": 17, "gate": "draft_gate", "headSha": "abc1234", "currentHeadSha": "abc1234", "commentId": 101, "commentUrl": "https://github.com/owner/repo/pull/17#issuecomment-101" }`
|
|
652
|
+
- forced CI-override path: the same shape plus `{ "forced": true, "forceReason": "CI failed due to transient infrastructure", "forceBypass": "ci_blocked_needs_user_decision" }`
|
|
653
|
+
|
|
654
|
+
Failure behavior:
|
|
655
|
+
- malformed arguments emit `{ "ok": false, "error": "...", "usage": "..." }` on stderr and exit non-zero
|
|
656
|
+
- contradictory head-SHA requests or unexpected `gh` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
657
|
+
- gate upserts still fail closed when `detect-pr-gate-coordination-state.mjs` reports that the requested gate action is illegal for the current head
|
|
658
|
+
- when that refusal is specifically `lifecycleState=blocked_needs_user_decision` plus current-head `ciStatus="failure"`, the unforced error now points operators to `--force --force-reason` as the explicit CI-only escape hatch
|
|
659
|
+
- `--force` does **not** bypass stale-head protection, non-draft `draft_gate` refusal, unresolved-feedback / unsettled-review `pre_approval_gate` refusal, merge-conflict handling, or closed/merged PR protection
|
|
660
|
+
|
|
661
|
+
Use `--force` only after the user explicitly authorizes ignoring the current-head CI failure for this one gate-comment upsert. Prefer the normal paths first: green current-head CI, `--local-validation-head-sha` for the bounded `crediblyGreen` case, or the draft-gate policy knob from issue #351 when the desired behavior is a durable draft-gate policy rather than a one-off override.
|
|
662
|
+
|
|
663
|
+
### `scripts/loop/run-refinement-audit.mjs`
|
|
664
|
+
|
|
665
|
+
Runs one deterministic bounded refinement audit over explicit repo paths and emits one machine-readable planning artifact.
|
|
666
|
+
Use this before refiner fan-out when a local phase or GitHub issue would benefit from scoped slop-risk discovery.
|
|
667
|
+
|
|
668
|
+
Required:
|
|
669
|
+
- exactly one of:
|
|
670
|
+
- `--paths <comma-separated path list>`
|
|
671
|
+
- `--paths-file <file>`
|
|
672
|
+
|
|
673
|
+
Optional:
|
|
674
|
+
- `--root <path>` — override the repo root (defaults to `git rev-parse --show-toplevel`)
|
|
675
|
+
- `--max-lines <n>` — oversized-file threshold (default `1000`)
|
|
676
|
+
- `--duplicate-window-lines <n>` — duplicate-block window size (default `4`)
|
|
677
|
+
- `--branch-threshold <n>` — branching-hotspot threshold (default `25` control-flow tokens)
|
|
678
|
+
- `--thin-wrapper-max-lines <n>` — thin-wrapper threshold (default `40`)
|
|
679
|
+
- `--output <path>` — writes the same success JSON emitted on stdout
|
|
680
|
+
|
|
681
|
+
Success output shape:
|
|
682
|
+
- `{ "ok": true, "repoRoot": "/repo", "paths": ["AGENTS.md"], "auditedFiles": [...], "findings": [...], "highestValueFollowUpCandidates": [...], "scopeBoundary": { "mode": "bounded_paths_only", "fullRepoScan": false } }`
|
|
683
|
+
- `auditedFiles[]` reports per-file line count, branch-token count, duplicate-block match count, thin-wrapper classification, and deterministic skip notes for binary/unreadable files
|
|
684
|
+
- `findings[]` uses bounded heuristic ids: `oversized_file`, `duplicate_block_candidate`, `branching_hotspot`, `thin_wrapper_candidate`
|
|
685
|
+
- `highestValueFollowUpCandidates[]` is the concise planning handoff surface for refiner fan-out/fan-in; findings are planning inputs, not auto-authorized rewrites
|
|
686
|
+
|
|
687
|
+
Failure behavior:
|
|
688
|
+
- malformed arguments, blank paths, invalid thresholds, unreadable `--paths-file` input, or zero auditable files after tracked-file expansion emit `{ "ok": false, "error": "...", ... }` on stderr and exit non-zero
|
|
689
|
+
- findings do **not** fail the process; findings return exit `0`
|
|
690
|
+
- `--paths*` is required; the helper never silently scans the whole repo
|
|
691
|
+
|
|
692
|
+
### `scripts/loop/detect-pr-gate-coordination-state.mjs`
|
|
693
|
+
|
|
694
|
+
Fetches the live PR facts needed to answer which gate/transition is legal next for a pull request. It combines the shared Copilot loop-state machine with visible `draft_gate` / `pre_approval_gate` evidence, GitHub `mergeStateStatus`, and local `git -c core.quotepath=false status --porcelain=v1 -z --untracked-files=no` conflict detection, then emits one explicit gate boundary, allowed/forbidden next actions, and a single recommended next step. Use this before entering `pre_approval_gate` and when deciding whether a ready PR should request Copilot review, keep waiting, stay in feedback resolution, or stop for conflict resolution.
|
|
695
|
+
|
|
696
|
+
Required:
|
|
697
|
+
- `--repo <owner/name>`
|
|
698
|
+
- `--pr <number>`
|
|
699
|
+
|
|
700
|
+
Success output shape:
|
|
701
|
+
- `{ "ok": true, "repo": "owner/repo", "pr": 266, "currentHeadSha": "...", "mergeStateStatus": string|null, "conflictFiles": ["path"]|[], "lifecycleState": string, "loopDisposition": string, "gateBoundary": string, "draftGate": { ... }, "preApprovalGate": { ... }, "draftGateAlreadySatisfied": true, "allowedNextActions": [ ... ], "forbiddenActions": [ ... ], "nextAction": string, "reason": "..." }`
|
|
702
|
+
- `draftGate` / `preApprovalGate` report both latest visible evidence (`visible`, `headSha`, `verdict`, `findingsSummary`, `nextAction`) and whether the evidence is current-head + contract-complete (`currentHead`, `contractComplete`, `currentHeadClean`)
|
|
703
|
+
- `mergeStateStatus` preserves the current GitHub `gh pr view` signal in helper output even when the PR is not in the conflict boundary; `DIRTY` and explicit `CONFLICTING` inputs are treated as conflict-required states
|
|
704
|
+
- `conflictFiles` lists unmerged local paths from `git -c core.quotepath=false status --porcelain=v1 -z --untracked-files=no` when local conflict reconciliation is already in progress
|
|
705
|
+
- when `mergeStateStatus` is conflicted or `conflictFiles` is non-empty, the evaluator emits `gateBoundary=conflict_resolution`, `nextAction=resolve_merge_conflicts`, and forbids gate/approval/merge progression until reconciliation completes
|
|
706
|
+
- `draftGateAlreadySatisfied` — true when the draft→ready transition was already recorded (non-draft + clean evidence exists); callers must skip draft gate when this is true
|
|
707
|
+
- `forbiddenActions` includes `run_pre_approval_gate` whenever the post-draft review cycle has not yet settled for the current head, and conflicted PRs keep it forbidden until reconciliation is complete
|
|
708
|
+
- non-draft PRs do not need visible `draft_gate` evidence to progress through post-draft review or `pre_approval_gate`; `draftGateAlreadySatisfied` is informational only, and downstream legality comes from `gateBoundary`, `allowedNextActions`, and `forbiddenActions`
|
|
709
|
+
- if the PR head changes while gate/conflict facts are loading, the helper still fails closed rather than evaluating mixed-head evidence
|
|
710
|
+
|
|
711
|
+
Failure behavior:
|
|
712
|
+
- malformed arguments emit `{ "ok": false, "error": "...", "usage": "..." }` on stderr and exit non-zero
|
|
713
|
+
- `gh` failures and malformed `gh` JSON emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
714
|
+
|
|
715
|
+
### `scripts/github/detect-checkpoint-evidence.mjs`
|
|
716
|
+
|
|
717
|
+
Fetches the live PR head SHA plus visible PR issue comments, then summarizes the
|
|
718
|
+
latest valid `draft_gate` and `pre_approval_gate` checkpoint verdict comments.
|
|
719
|
+
Use this when a fresh session needs authoritative visible gate evidence for the
|
|
720
|
+
current head before running `gh pr ready` or declaring final-approval readiness.
|
|
721
|
+
When `PI_SUBAGENT_RUN_ID` is set to a non-empty value, it also enforces async runner ownership before reading GitHub facts, so stale/displaced runs fail closed before merge.
|
|
722
|
+
|
|
723
|
+
Required:
|
|
724
|
+
- `--repo <owner/name>`
|
|
725
|
+
- `--pr <number>`
|
|
726
|
+
|
|
727
|
+
Success output shape:
|
|
728
|
+
- `{ "ok": true, "repo": "owner/repo", "pr": 17, "currentHeadSha": "abc1234", "draftGate": { ... }, "preApprovalGate": { ... }, "draftGateMarker": { ... }, "preApprovalGateMarker": { ... } }`
|
|
729
|
+
- each gate summary includes `visible`, `headSha`, `verdict`, `findingsSummary`, `nextAction`, `commentId`, `commentUrl`, and `updatedAt`
|
|
730
|
+
- when no valid visible comment exists for a gate, its summary is emitted with `visible=false` and the other fields set to `null`
|
|
731
|
+
- each marker summary includes `visible`, `headSha`, `verdict`, `findingsSummary`, `nextAction`, `contractComplete`, `commentId`, `commentUrl`, and `updatedAt`
|
|
732
|
+
- marker summaries track the newest visible marker for the current head (`gate + currentHeadSha`) even if contract fields are partial, enabling same-head rerun idempotency without posting duplicate visible markers
|
|
733
|
+
|
|
734
|
+
Failure behavior:
|
|
735
|
+
- malformed arguments emit `{ "ok": false, "error": "...", "usage": "..." }` on stderr and exit non-zero
|
|
736
|
+
- `gh` failures and malformed `gh` JSON emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
737
|
+
|
|
738
|
+
### `scripts/loop/detect-tracker-pr-state.mjs`
|
|
739
|
+
|
|
740
|
+
Deterministic tracker-first story-to-PR state detector. Interprets a pre-built
|
|
741
|
+
tracker/PR lifecycle snapshot into one explicit current state, allowed next
|
|
742
|
+
transitions, a recommended next action, and the canonical reverse-sync action.
|
|
743
|
+
This helper is intentionally snapshot-only: tracker-adapter lookups and live
|
|
744
|
+
GitHub discovery remain outside this CLI.
|
|
745
|
+
|
|
746
|
+
Required:
|
|
747
|
+
- `--input <path>`
|
|
748
|
+
|
|
749
|
+
Snapshot schema (`--input` JSON):
|
|
750
|
+
- `trackerItemExists` {boolean} — whether a tracker work item was found
|
|
751
|
+
- `trackerItemId` {string|null} — tracker item identifier if present
|
|
752
|
+
- `prExists` {boolean} — whether a PR exists for the tracker item
|
|
753
|
+
- `prNumber` {number|null} — PR number if known; `prNumber` with `prExists=false` is contradictory and blocked
|
|
754
|
+
- `prDraft` {boolean} — whether the PR is still draft
|
|
755
|
+
- `prMerged` {boolean} — whether the PR has been merged
|
|
756
|
+
- `prClosed` {boolean} — whether the PR is closed on GitHub (merged PRs are also closed); `pr_closed_unmerged` is derived from `prClosed && !prMerged`
|
|
757
|
+
|
|
758
|
+
Unlike the Copilot/reviewer loop snapshots, this tracker snapshot uses `prClosed` for the raw GitHub closed state. Merged PRs therefore set both `prMerged=true` and `prClosed=true`, while `pr_closed_unmerged` is derived from `prClosed && !prMerged`.
|
|
759
|
+
|
|
760
|
+
This snapshot surface is intentionally limited to tracker identity plus PR lifecycle facts. It does not encode tracker-native workflow readiness/blocking/done state; higher-level callers must combine tracker-owned state separately when deciding whether opening a PR is appropriate.
|
|
761
|
+
|
|
762
|
+
Success output shape:
|
|
763
|
+
- `{ "ok": true, "snapshot": { ... }, "state": "...", "allowedTransitions": [...], "nextAction": "...", "reverseSyncAction": "..." }`
|
|
764
|
+
|
|
765
|
+
Failure behavior:
|
|
766
|
+
- malformed arguments emit `{ "ok": false, "error": "...", "usage": "..." }`
|
|
767
|
+
on stderr and exit non-zero
|
|
768
|
+
- unreadable input files, invalid JSON, and invalid snapshot objects emit
|
|
769
|
+
`{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
770
|
+
|
|
771
|
+
### `scripts/loop/detect-reviewer-loop-state.mjs`
|
|
772
|
+
|
|
773
|
+
Deterministic reviewer-loop state detector. Captures reviewer-side PR loop state from observable
|
|
774
|
+
GitHub facts plus optional local reviewer-loop metadata and interprets that snapshot into one
|
|
775
|
+
explicit current state, allowed next transitions, and a recommended next action. See
|
|
776
|
+
[Reviewer Loop State Graph](../docs/reviewer-loop-state-graph.md) for the full reviewer-loop state graph and contracts.
|
|
777
|
+
|
|
778
|
+
Two modes:
|
|
779
|
+
|
|
780
|
+
- **Auto-detect**: `--repo <owner/name> --pr <number>`
|
|
781
|
+
Fetches PR/open-head state, review-request status, and pending/submitted review surfaces from
|
|
782
|
+
GitHub and interprets them into deterministic reviewer-loop state. When `--reviewer-login` is
|
|
783
|
+
omitted, this uses aggregate all-reviewer scope for the PR.
|
|
784
|
+
|
|
785
|
+
- **Snapshot interpretation**: `--input <path>`
|
|
786
|
+
Reads a pre-built snapshot JSON and interprets it without any `gh` calls.
|
|
787
|
+
|
|
788
|
+
Optional (auto-detect mode only):
|
|
789
|
+
- `--reviewer-login <login>`
|
|
790
|
+
Scope review-request and review-surface detection to a single reviewer identity. Success output
|
|
791
|
+
snapshots include `reviewerScope` (`"all_reviewers"` or `"single_reviewer"`) plus
|
|
792
|
+
`reviewerLogin` (`string|null`) so callers can tell whether the detector used aggregate or
|
|
793
|
+
single-reviewer scope.
|
|
794
|
+
- `--review-requested <true|false>`
|
|
795
|
+
Override review-request detection with a known prior result.
|
|
796
|
+
- `--local-state <path>`
|
|
797
|
+
Inject local reviewer-loop metadata (planning/run/merge/draft-notification status) used for
|
|
798
|
+
deterministic planning/running/merge-ready and draft lifecycle transitions.
|
|
799
|
+
|
|
800
|
+
Success output shape:
|
|
801
|
+
- `{ "ok": true, "snapshot": { ... }, "state": "...", "allowedTransitions": [...], "nextAction": "..." }`
|
|
802
|
+
- reviewer snapshots preserve the latest submitted review metadata via `submittedReviewPresent`, `submittedReviewCommitSha`, and `submittedReviewState`
|
|
803
|
+
- together those fields let read-only inspection UIs distinguish submitted-verdict handoff boundaries from active reviewer-pass states
|
|
804
|
+
|
|
805
|
+
Failure behavior:
|
|
806
|
+
- malformed arguments, unexpected `gh` failures, and invalid input/local-state JSON emit
|
|
807
|
+
`{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
808
|
+
|
|
809
|
+
### `scripts/loop/outer-loop.mjs`
|
|
810
|
+
|
|
811
|
+
Thin deterministic outer-loop wrapper for the Copilot PR remediation path. It combines
|
|
812
|
+
the existing Copilot and reviewer inner-loop detectors into one machine-readable outer
|
|
813
|
+
action so bounded external waits remain owned by the same remediation family instead of
|
|
814
|
+
looking like terminal run endpoints.
|
|
815
|
+
|
|
816
|
+
Required:
|
|
817
|
+
- `--repo <owner/name>`
|
|
818
|
+
- `--pr <number>`
|
|
819
|
+
|
|
820
|
+
Optional:
|
|
821
|
+
- `--reviewer-login <login>`
|
|
822
|
+
- `--checkpoint-dir <path>`
|
|
823
|
+
- `--copilot-input <path>`
|
|
824
|
+
- `--reviewer-input <path>`
|
|
825
|
+
|
|
826
|
+
Reviewer-scope contract:
|
|
827
|
+
- omitting `--reviewer-login` means aggregate all-reviewer scope for the PR
|
|
828
|
+
- providing `--reviewer-login` means single-reviewer scope for that login
|
|
829
|
+
- `--reviewer-input` cannot be combined with `--reviewer-login`
|
|
830
|
+
|
|
831
|
+
Contract:
|
|
832
|
+
- auto-detect mode calls both inner detectors, interprets their current states, and emits one
|
|
833
|
+
outer action: `continue_wait`, `reenter_copilot_loop`, `reenter_reviewer_loop`, `stop`, or `done`
|
|
834
|
+
- treats draft PRs as a re-entry point into owned draft-stage follow-up rather than a terminal stop
|
|
835
|
+
- treats `waiting_for_copilot_review`, `waiting_for_ci`, and reviewer `submitted_review`
|
|
836
|
+
as outer-loop-owned `continue_wait` states at explicit external/handoff boundaries
|
|
837
|
+
- preserves compatibility for reviewer `waiting_for_author_followup` and `waiting_for_re_request`
|
|
838
|
+
as legacy named external-wait boundaries
|
|
839
|
+
- when the next step needs local execution or mutation and the checkout is dirty or detached, preserves the loop-family handoff and marks `conductorRouting.handoffEnvelope.requiresLocalIsolation=true` so callers can continue from an isolated checkout/worktree instead of treating the boundary as terminal
|
|
840
|
+
- for PR-local re-entry actions, verifies local branch/HEAD identity against the active PR head;
|
|
841
|
+
when an isolation-managed handoff is already in effect, it enriches the handoff with `headRefName` / `headRefOid` for the target PR head instead of failing the handoff on the parent checkout's expected mismatch
|
|
842
|
+
- otherwise stops with `unsafe_local_branch_mismatch_requires_reconcile` or
|
|
843
|
+
`unsafe_local_head_mismatch_requires_reconcile` when checkout identity is not aligned
|
|
844
|
+
- when that PR-local identity gate trips, the emitted `conductorRouting` result is also fail-closed
|
|
845
|
+
to a stop outcome with no handoff entrypoint, so consumers cannot keep following a stale handoff envelope
|
|
846
|
+
- persists bounded checkpoint state to `tmp/copilot-loop/<owner>/<repo>/pr-<n>/outer-loop-state.json` for
|
|
847
|
+
async continuation and false-positive wakeup detection
|
|
848
|
+
- emits an additive `conductorRouting` field with the conductor-owned routing outcome, derived
|
|
849
|
+
outer action, stop reason when relevant, and any machine-readable handoff envelope
|
|
850
|
+
- supports snapshot-input mode for deterministic gh-free testing
|
|
851
|
+
|
|
852
|
+
Success output shape:
|
|
853
|
+
- `{ "ok": true, "outerAction": "...", "copilotState": "...", "reviewerState": "...", "reviewerScope": { "mode": "all_reviewers"|"single_reviewer", "reviewerLogin": "..."|null }, "reason"?: "...", "branchIdentity"?: { ... }, "conductorRouting": { "routingOutcome": "...", "outerAction": "...", "stopReason": null|"...", "handoffEnvelope": { ... } }, "checkpoint": { ... } }`
|
|
854
|
+
|
|
855
|
+
Failure behavior:
|
|
856
|
+
- malformed arguments emit `{ "ok": false, "error": "...", "usage": "..." }` on stderr and exit non-zero
|
|
857
|
+
- unexpected `gh` or `git` failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
858
|
+
|
|
859
|
+
### `scripts/loop/inspect-run.mjs`
|
|
860
|
+
|
|
861
|
+
Read-only inspection entrypoint for one explicit Copilot PR outer-loop target.
|
|
862
|
+
It composes current inner-loop facts into one JSON snapshot without attaching to
|
|
863
|
+
an active worker or mutating local/runtime state.
|
|
864
|
+
|
|
865
|
+
Required:
|
|
866
|
+
- `--repo <owner/name>`
|
|
867
|
+
- `--pr <number>`
|
|
868
|
+
|
|
869
|
+
Optional:
|
|
870
|
+
- `--steering-state-file <path>`
|
|
871
|
+
- `--reviewer-login <login>` — narrows live reviewer detection to one reviewer identity; when omitted, inspection uses aggregate all-reviewer scope for the PR
|
|
872
|
+
- `--copilot-input <path>`
|
|
873
|
+
- `--reviewer-input <path>` — cannot be combined with `--reviewer-login`
|
|
874
|
+
|
|
875
|
+
Contract:
|
|
876
|
+
- is strictly read-only: it does not write checkpoints, mutate GitHub state, or create local artifacts
|
|
877
|
+
- returns a stable top-level inspection shape with target identity, derived `runId`, authoritative `outerState`, conditional top-level `allowedTransitions`, compatibility `outerAction`, active family state,
|
|
878
|
+
status class, trust/source semantics, evidence, markers, and best-effort drill-down layers
|
|
879
|
+
- only derives top-level `outerState` / `allowedTransitions` / `outerAction` / `activeFamilyState` / `statusClass` when inspection has a complete current inner-loop picture, whether from live detectors and/or caller-supplied snapshot inputs
|
|
880
|
+
- when inspection falls back to checkpoint-only data or mixed live + checkpoint evidence, checkpoint-backed drill-down layers and checkpoint evidence paths remain available as advisory context while the top-level state stays `"unknown"`
|
|
881
|
+
- reports not-found or unavailable targets as structured success output with `statusClass: "unknown"`
|
|
882
|
+
rather than by throwing a synthetic blocked-run error
|
|
883
|
+
- looks for checkpoints at the repo-qualified default path `tmp/copilot-loop/<owner>/<repo>/pr-<n>/outer-loop-state.json`
|
|
884
|
+
- during transition, may read the legacy default path `tmp/copilot-loop/pr-<n>/outer-loop-state.json` only when the checkpoint file's embedded `repo` and `pr` match the explicit target
|
|
885
|
+
- surfaces steering as a best-effort drill-down layer when `--steering-state-file` is provided,
|
|
886
|
+
including latest acknowledgement plus queued/effective stop summaries for the current run,
|
|
887
|
+
without exposing full steering history/detail or raw steering-file locator paths
|
|
888
|
+
- when live GitHub PR facts are available, surfaces a deterministic `loopIterations` summary for the
|
|
889
|
+
remote Copilot review/fix loop (completed rounds, pending round indicator, Copilot review comments,
|
|
890
|
+
current resolved/unresolved review-thread counts, and fix commits after feedback)
|
|
891
|
+
- keeps `loopIterations` unavailable in snapshot-only / non-live inspection paths instead of
|
|
892
|
+
inventing local phase-loop iteration semantics
|
|
893
|
+
- rejects mismatched steering-state files from the targeted repo/pr instead of projecting their state onto the inspected run
|
|
894
|
+
|
|
895
|
+
Success output shape:
|
|
896
|
+
- `{ "ok": true, "schemaVersion": 1, "target": { "repo": "...", "pr": 17 }, "runId": "pr-17", "inspectedAt": "...", "activeStateFamily": "copilot-pr-outer-loop", "outerState": "...", "allowedTransitions"?: [...], "outerAction": "...", "activeFamilyState": "...", "statusClass": "...", "needsAttention": false, "sourceMode": "...", "trust": "...", "evidence": { ... }, "markers": { ... }, "loopIterations": { "available": true|false, ... }, "layers": { "reviewer": { "currentState": "...", "scope": { "mode": "all_reviewers"|"single_reviewer", "reviewerLogin": "..."|null }, ... }, ... } }`
|
|
897
|
+
|
|
898
|
+
Failure behavior:
|
|
899
|
+
- malformed arguments emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
900
|
+
- unexpected runtime failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|
|
901
|
+
|
|
902
|
+
### `scripts/loop/inspect-run-viewer.mjs`
|
|
903
|
+
|
|
904
|
+
Owned read-only local/operator inspection dashboard layered on `inspect-run`.
|
|
905
|
+
`inspect-run` remains authoritative for inspection/status state; the viewer owns local inbox discovery and read-only presentation/prioritization.
|
|
906
|
+
|
|
907
|
+
Primary local lifecycle UX now lives in the Pi extension under:
|
|
908
|
+
- `/dev-loops inspect open [--repo <owner/name>]`
|
|
909
|
+
- `/dev-loops inspect resume [--repo <owner/name>]`
|
|
910
|
+
- `/dev-loops inspect status [--repo <owner/name>]`
|
|
911
|
+
- `/dev-loops inspect stop [--repo <owner/name>]`
|
|
912
|
+
- `/dev-loops inspect restart [--repo <owner/name>]`
|
|
913
|
+
|
|
914
|
+
The extension-managed seam stores one narrow repo-local managed-instance record at:
|
|
915
|
+
- `.pi/ui-servers/inspect-run-viewer.json`
|
|
916
|
+
|
|
917
|
+
Ownership split for this slice:
|
|
918
|
+
- extension owns lifecycle UX, URL discovery, liveness checks, reattach logic, stop/restart, and best-effort browser opening
|
|
919
|
+
- when a repo-scoped command reuses an inbox-first managed viewer, the surfaced URL may include `?scope=<owner/name>` to pre-scope the inbox without replacing the managed instance
|
|
920
|
+
- viewer script still owns HTTP server behavior, rendering, inbox/query behavior, and snapshot loading
|
|
921
|
+
|
|
922
|
+
Optional:
|
|
923
|
+
- `--repo <owner/name>` (repo-scope the inbox; otherwise the viewer starts in inbox-first mode)
|
|
924
|
+
- `--host <host>` (default: `127.0.0.1`; non-loopback binds require `--allow-non-localhost`)
|
|
925
|
+
- `--port <port>` (default: `4311`)
|
|
926
|
+
- `--allow-non-localhost` (explicit opt-in for non-loopback binds such as `0.0.0.0` or LAN IPs)
|
|
927
|
+
- `--restart` (manual/debug convenience only; requires `lsof` / POSIX support and sends `SIGTERM` to every listener already bound to that port)
|
|
928
|
+
- `--steering-state-file <path>` (pass-through to `inspect-run`)
|
|
929
|
+
- `--reviewer-login <login>` (pass-through to `inspect-run`)
|
|
930
|
+
- `--copilot-input <path>` (pass-through to `inspect-run`)
|
|
931
|
+
- `--reviewer-input <path>` (pass-through to `inspect-run`; cannot be combined with `--reviewer-login`)
|
|
932
|
+
|
|
933
|
+
Contract:
|
|
934
|
+
- current-slice posture: kept/promoted as an explicitly owned local/operator inspection dashboard (not a second public workflow entrypoint)
|
|
935
|
+
- read-only: no GitHub mutations, no checkpoint writes, no steering writes, no worker attachment
|
|
936
|
+
- ownership boundary: `inspect-run` owns authoritative inspection/status state; viewer owns local inbox discovery plus read-only operator presentation/prioritization
|
|
937
|
+
- extension-managed lifecycle remains loopback-first and local-only; no remote/public hosting support
|
|
938
|
+
- the script fallback still requires explicit `--allow-non-localhost` opt-in for non-loopback binds; do not expose inspection state on the network by default
|
|
939
|
+
- GitHub-first launch boundary: repo scope is optional and PR selection happens through the viewer URL/query state, not a CLI `--pr` flag
|
|
940
|
+
- uses one adapter module (`scripts/loop/_inspect-run-viewer-adapter.mjs`) to load the normalized inspection snapshot
|
|
941
|
+
- adapter is the only viewer integration seam that calls the existing `inspect-run` contract in this source-loaded workspace
|
|
942
|
+
- serves two explicit read-only endpoints:
|
|
943
|
+
- `/` → operator-facing HTML with an assigned-PR inbox shell and, when a PR is selected via URL or sidebar, the Mermaid-first graph plus current-PR-state banner and supporting textual summary/evidence
|
|
944
|
+
- `/snapshot.json` → the full authoritative inspection snapshot JSON for the currently selected PR/query target
|
|
945
|
+
- HTML includes a visible link to `/snapshot.json` so machine-readable state no longer depends on an inline full-snapshot dump in the page itself
|
|
946
|
+
- `/snapshot.json` returns `application/json; charset=utf-8` on success and deterministic JSON error output with non-2xx status when snapshot loading throws or yields no snapshot
|
|
947
|
+
- unsupported paths return deterministic `404` without loading a snapshot (even for unsupported methods on unknown paths); `/favicon.ico` returns deterministic `204`; unsupported methods on supported routes return `405 Allow: GET`
|
|
948
|
+
- both primary endpoints send `Cache-Control: no-store` to match the manual-reload workflow
|
|
949
|
+
- the script-local `--restart` flag remains a manual/debug fallback only; the extension-managed path must not depend on killing unknown listeners
|
|
950
|
+
- manual reload only (`window.location.reload()`); no polling/watch/timeout/control semantics
|
|
951
|
+
|
|
952
|
+
Local manual verification path:
|
|
953
|
+
1. Preferred extension-managed path:
|
|
954
|
+
- `/dev-loops inspect open`
|
|
955
|
+
- `/dev-loops inspect status`
|
|
956
|
+
- `/dev-loops inspect resume`
|
|
957
|
+
- `/dev-loops inspect stop`
|
|
958
|
+
2. Script fallback for manual/debug verification:
|
|
959
|
+
- `node scripts/loop/inspect-run-viewer.mjs`
|
|
960
|
+
- `node scripts/loop/inspect-run-viewer.mjs --repo <owner/name>`
|
|
961
|
+
3. Open the printed/resolved URL in a local browser and verify the human-oriented `/` page
|
|
962
|
+
4. Select a PR via the sidebar or by adding `?repo=<owner/name>&pr=<number>` to the viewer URL
|
|
963
|
+
5. Open `/snapshot.json` for that selected/query-targeted PR and verify it returns the matching full inspection snapshot JSON
|
|
964
|
+
6. Use browser refresh or the reload button for point-in-time re-inspection
|
|
965
|
+
|
|
966
|
+
Local WebKit/Playwright smoke path:
|
|
967
|
+
1. Install the Safari/WebKit browser runtime once:
|
|
968
|
+
- `npx playwright install webkit`
|
|
969
|
+
2. Run the viewer smoke suite:
|
|
970
|
+
- `npm run test:playwright:viewer`
|
|
971
|
+
3. Review screenshots/traces under `test-results/` and the HTML report under `playwright-report/ui-smoke/inspect-run-viewer/`
|
|
972
|
+
4. Optionally hit `/favicon.ico` or an unsupported path to confirm those paths stay deterministic and do not perform snapshot rendering
|
|
973
|
+
5. For deterministic/local test mode, pass `--copilot-input` and `--reviewer-input` fixtures to viewer; these are forwarded to `inspect-run`
|
|
974
|
+
|
|
975
|
+
### `scripts/loop/steer-loop.mjs`
|
|
976
|
+
|
|
977
|
+
Mid-flight operator steering CLI for active dev loops.
|
|
978
|
+
|
|
979
|
+
Subcommands:
|
|
980
|
+
- `submit` — submit a steering directive to a specific run
|
|
981
|
+
- `promote` — explicitly promote queued steering for a specific run at a known loop state
|
|
982
|
+
- `status` — inspect the current steering state for a run
|
|
983
|
+
|
|
984
|
+
Contract:
|
|
985
|
+
- persists steering state to a JSON file (default: `.pi/steering/<owner>/<repo>/pr-<n>.json` for operator-facing `--repo/--pr` mode; `.pi/steering/<run-id>.json` for low-level `--run-id` mode)
|
|
986
|
+
- operator-facing `submit` resolves one explicit `repo` + `pr` target through the read-only
|
|
987
|
+
inspection surface and derives `runId: pr-<number>` from that target while persisting repo-qualified target metadata alongside the steering state
|
|
988
|
+
- explicit queued-steering promotion/persistence belongs to `promote`; detector-shaped helpers stay read-only
|
|
989
|
+
- operator-facing `submit` is intentionally limited to `stop_at_next_safe_gate`; other directive
|
|
990
|
+
kinds remain low-level/internal and are rejected on the external submit path
|
|
991
|
+
- operator-facing `submit` fails closed when inspection is partial, checkpoint-only, unavailable,
|
|
992
|
+
stale, or conflicting
|
|
993
|
+
- low-level/testing mode may still accept injected loop-state inputs for deterministic tests
|
|
994
|
+
- returns deterministic acknowledgement/result payloads for `submit` and deterministic state
|
|
995
|
+
readback for `status`
|
|
996
|
+
- rejected operator-facing submits leave any trusted durable steering file unchanged; when the
|
|
997
|
+
persisted file is malformed or target-mismatched, the response may include a fresh synthetic
|
|
998
|
+
target-scoped `steeringState` for deterministic readback without trusting broken persisted data
|
|
999
|
+
|
|
1000
|
+
Success output shape:
|
|
1001
|
+
- `submit`: `{ "ok": true, "acknowledgement": { ... }, "result": { ... }, "steeringState": { ... } }`
|
|
1002
|
+
- `promote`: `{ "ok": true, "promotedCount": <n>, "promoted": [ ... ], "steeringState": { ... } }`
|
|
1003
|
+
- `status`: `{ "ok": true, "status": { ... } }`
|
|
1004
|
+
|
|
1005
|
+
Failure behavior:
|
|
1006
|
+
- argument/usage errors emit `{ "ok": false, "error": "...", "usage": "..." }` on stderr and exit non-zero
|
|
1007
|
+
- runtime failures emit `{ "ok": false, "error": "..." }` on stderr and exit non-zero
|