dev-loops 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/agents/dev-loop.md +1 -1
- package/.claude/hooks/_run-context.mjs +9 -16
- package/.claude/skills/copilot-pr-followup/SKILL.md +9 -7
- package/.claude/skills/dev-loop/SKILL.md +6 -6
- package/.claude/skills/docs/anti-patterns.md +1 -1
- package/.claude/skills/docs/copilot-loop-operations.md +1 -1
- package/.claude/skills/docs/issue-intake-procedure.md +4 -0
- package/.claude/skills/docs/merge-preconditions.md +65 -1
- package/.claude/skills/docs/stop-conditions.md +1 -0
- package/.claude/skills/docs/tracker-first-loop-state.md +6 -6
- package/.claude/skills/local-implementation/SKILL.md +8 -2
- package/CHANGELOG.md +26 -0
- package/README.md +8 -2
- package/cli/index.mjs +21 -2
- package/extension/README.md +1 -1
- package/package.json +3 -3
- package/scripts/README.md +2 -2
- package/scripts/github/offer-human-handoff.mjs +147 -0
- package/scripts/github/probe-ci-status.mjs +468 -0
- package/scripts/github/request-copilot-review.mjs +3 -3
- package/scripts/github/resolve-handoff-candidates.mjs +412 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +2 -2
- package/scripts/loop/_stale-runner-detection.mjs +1 -1
- package/scripts/loop/_worktree-path.mjs +27 -0
- package/scripts/loop/cleanup-worktree.mjs +175 -0
- package/scripts/loop/copilot-pr-handoff.mjs +1 -1
- package/scripts/loop/detect-stale-runner.mjs +3 -4
- package/scripts/loop/ensure-worktree.mjs +219 -0
- package/scripts/loop/outer-loop.mjs +1 -1
- package/scripts/loop/pr-runner-coordination.mjs +1 -1
- package/scripts/loop/pre-flight-gate.mjs +10 -7
- package/scripts/loop/pre-push-main-guard.mjs +4 -4
- package/scripts/loop/provision-worktree.mjs +243 -0
- package/scripts/loop/resolve-dev-loop-startup.mjs +5 -5
- package/scripts/loop/run-queue.mjs +25 -1
- package/scripts/loop/run-watch-cycle.mjs +75 -22
- package/scripts/projects/add-queue-item.mjs +20 -5
- package/scripts/projects/archive-done-items.mjs +2 -1
- package/scripts/projects/ensure-queue-board.mjs +2 -1
- package/scripts/projects/list-queue-items.mjs +2 -1
- package/scripts/projects/move-queue-item.mjs +2 -1
- package/scripts/projects/reorder-queue-item.mjs +5 -4
- package/scripts/projects/sync-item-status.mjs +2 -1
- package/skills/copilot-pr-followup/SKILL.md +9 -7
- package/skills/dev-loop/SKILL.md +2 -2
- package/skills/docs/anti-patterns.md +1 -1
- package/skills/docs/copilot-loop-operations.md +1 -1
- package/skills/docs/issue-intake-procedure.md +4 -0
- package/skills/docs/merge-preconditions.md +65 -1
- package/skills/docs/stop-conditions.md +1 -0
- package/skills/docs/tracker-first-loop-state.md +6 -6
- package/skills/local-implementation/SKILL.md +8 -2
|
@@ -22,7 +22,7 @@ The envelope is the primary handoff artifact — it is derived from resolver out
|
|
|
22
22
|
|
|
23
23
|
**Construction sequence:**
|
|
24
24
|
|
|
25
|
-
1. Run the deterministic startup resolver to produce the authoritative state bundle: `npx dev-loops@0.
|
|
25
|
+
1. Run the deterministic startup resolver to produce the authoritative state bundle: `npx dev-loops@0.4.0 loop startup --issue <n>` for issues, or `npx dev-loops@0.4.0 loop startup --pr <n>` for PRs.
|
|
26
26
|
2. Pass the resolver output, resolved settings (merged from `.devloops` and `.pi/dev-loop/defaults.yaml`), and current gate state to `buildDevLoopHandoffEnvelope()`.
|
|
27
27
|
3. **Validate the envelope** with `validateHandoffEnvelope()` before consuming any field. If validation returns `ok: false`, reject the handoff with the structured error — do not load requiredReads, do not execute nextAction, do not delegate.
|
|
28
28
|
4. Read the envelope as the first artifact.
|
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Neutral run-id / async-context contract.
|
|
4
4
|
*
|
|
5
|
-
* The dev-loop async path
|
|
5
|
+
* The dev-loop async path keys off the harness-neutral `DEVLOOPS_RUN_ID` env var to
|
|
6
6
|
* identify an inspectable per-subagent run (runner ownership, async-start enforcement,
|
|
7
|
-
* human-comment gating)
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* native per-subagent run id.
|
|
11
|
-
*
|
|
12
|
-
* Marker precedence is neutral-first: a present `DEVLOOPS_RUN_ID` wins; otherwise the Pi
|
|
13
|
-
* alias is honored. Existing Pi runs that set only `PI_SUBAGENT_RUN_ID` behave identically.
|
|
7
|
+
* human-comment gating), and provides a mint-and-propagate path for harnesses (e.g. Claude
|
|
8
|
+
* Code) that inject no native per-subagent run id. The harness sets `DEVLOOPS_RUN_ID` when
|
|
9
|
+
* dispatching an async subagent.
|
|
14
10
|
*
|
|
15
11
|
* This module is pure except for the explicit file/IO helpers (writeRunContext/readRunContext),
|
|
16
12
|
* which take an injectable `fs` and `root` for testability.
|
|
@@ -22,16 +18,13 @@ import path from "node:path";
|
|
|
22
18
|
|
|
23
19
|
/**
|
|
24
20
|
* Env var names that carry the async-context run id, in resolution precedence order.
|
|
25
|
-
*
|
|
21
|
+
* The neutral `DEVLOOPS_RUN_ID` is the sole marker.
|
|
26
22
|
*/
|
|
27
|
-
export const RUN_ID_MARKERS = Object.freeze(["DEVLOOPS_RUN_ID"
|
|
23
|
+
export const RUN_ID_MARKERS = Object.freeze(["DEVLOOPS_RUN_ID"]);
|
|
28
24
|
|
|
29
25
|
/** Neutral env var name used when minting/propagating a run id. */
|
|
30
26
|
export const NEUTRAL_RUN_ID_VAR = "DEVLOOPS_RUN_ID";
|
|
31
27
|
|
|
32
|
-
/** Pi-compatibility alias env var name. */
|
|
33
|
-
export const PI_RUN_ID_ALIAS_VAR = "PI_SUBAGENT_RUN_ID";
|
|
34
|
-
|
|
35
28
|
/** State-file name (under `.pi/`, consistent with existing dev-loop checkpoint files). */
|
|
36
29
|
export const RUN_CONTEXT_FILENAME = "dev-loop-run-context.json";
|
|
37
30
|
|
|
@@ -57,7 +50,7 @@ export function isClaudeHarness(env = process.env) {
|
|
|
57
50
|
}
|
|
58
51
|
|
|
59
52
|
/**
|
|
60
|
-
* Resolve the active run id from the environment
|
|
53
|
+
* Resolve the active run id from the environment.
|
|
61
54
|
*
|
|
62
55
|
* @param {Record<string, string|undefined>} [env]
|
|
63
56
|
* @returns {string|null} The trimmed run id, or null when none is set.
|
|
@@ -155,8 +148,8 @@ export function readRunContext({ root, fs = fsDefault }) {
|
|
|
155
148
|
* Resolve the active run id, or mint one and persist a run-context state file.
|
|
156
149
|
*
|
|
157
150
|
* This is the "mint at startup and propagate" primitive a Claude dev-loop agent (or a
|
|
158
|
-
* headless entry) calls before dispatching child work. When the env already carries a
|
|
159
|
-
*
|
|
151
|
+
* headless entry) calls before dispatching child work. When the env already carries a
|
|
152
|
+
* `DEVLOOPS_RUN_ID`, it is reused and no new id is minted.
|
|
160
153
|
*
|
|
161
154
|
* @param {object} [params]
|
|
162
155
|
* @param {Record<string, string|undefined>} [params.env]
|
|
@@ -156,9 +156,9 @@ The outer-loop checkpoint is the canonical re-attachment artifact.
|
|
|
156
156
|
|
|
157
157
|
Start every wait seam with a detector refresh: `detect-copilot-loop-state.mjs --repo <owner/name> --pr <number>`.
|
|
158
158
|
|
|
159
|
-
Allowed wait tools: `detect-copilot-loop-state.mjs` (one-shot), `dev-loops loop watch-cycle` (persistent), `copilot-pr-handoff.mjs --watch-status` (refresh after timeout/idle), `gh run watch <run-id> --repo <owner/name>` (
|
|
159
|
+
Allowed wait tools: `detect-copilot-loop-state.mjs` (one-shot), `dev-loops loop watch-cycle` (persistent), `copilot-pr-handoff.mjs --watch-status` (refresh after timeout/idle), `dev-loops loop watch-ci --repo <owner/name> --pr <number>` (provider-agnostic CI: CircleCI / Actions / external commit-status), `gh run watch <run-id> --repo <owner/name>` (Actions-only fallback when the run id is known). Otherwise exit and resume later from fresh detector call.
|
|
160
160
|
|
|
161
|
-
Practical rules: do not poll manually. `waiting_for_copilot_review` → `run-watch-cycle.mjs` or report-and-resume. `waiting_for_ci` with pending/none CI → `
|
|
161
|
+
Practical rules: do not poll manually. `waiting_for_copilot_review` → `run-watch-cycle.mjs` or report-and-resume. `waiting_for_ci` with pending/none CI → `dev-loops loop watch-ci --repo <owner/name> --pr <number>` (provider-agnostic; covers CircleCI / Actions / external commit-status) or report-and-resume; `gh run watch <run-id>` is an Actions-only fallback. `dev-loops loop watch-cycle` also auto-routes a `waiting_for_ci` boundary to this CI watcher. Bounded CI exception: zero current-head suites + previous-head green + local `npm run verify` passed → rerun detector with `--local-validation-head-sha` for `crediblyGreen` promotion. `ciStatus=failure` → stop/fix, never wait.
|
|
162
162
|
|
|
163
163
|
Preferred approach:
|
|
164
164
|
- route decisions through `copilot-pr-handoff.mjs` output; enter watcher only on `action: "watch"` with `watchEntryConfirmed=true`; prefer `dev-loops loop watch-cycle` for deterministic handoff → watch
|
|
@@ -238,7 +238,7 @@ When unresolved feedback exists, use a narrow follow-up loop:
|
|
|
238
238
|
- if that local validation is still known red, continue remediation instead of re-requesting Copilot
|
|
239
239
|
- after a fix push advances the PR head SHA, re-run `detect-copilot-loop-state.mjs` for the new head and apply the [Copilot CI Status Contract](../docs/copilot-ci-status-contract.md). Previous-head CI is stale; only current-head results unblock CI-dependent steps. if GitHub CI/checks for the updated head are known red for a fixable issue, continue remediation instead of re-requesting Copilot. only once the updated head is green or credibly green, explicitly re-request Copilot review for the new head. Always use `request-copilot-review.mjs` — never `gh api POST repos/.../requested_reviewers` directly.
|
|
240
240
|
- only enter a wait/watch loop if the request result is confirmed as `requested` or `already-requested`
|
|
241
|
-
- for `requested` / `already-requested`, immediately re-baseline with `detect-copilot-loop-state.mjs`; if the returned state is `waiting_for_copilot_review`, use `dev-loops loop watch-cycle` or stop/resume later, and if the returned state is `waiting_for_ci`, use `gh run watch`
|
|
241
|
+
- for `requested` / `already-requested`, immediately re-baseline with `detect-copilot-loop-state.mjs`; if the returned state is `waiting_for_copilot_review`, use `dev-loops loop watch-cycle` or stop/resume later, and if the returned state is `waiting_for_ci`, use `dev-loops loop watch-ci` (provider-agnostic CI wait; `gh run watch` is an Actions-only fallback) or stop/resume later after that single detector refresh
|
|
242
242
|
- if the request result is `unavailable`, report that limitation and stop unless the user explicitly wants passive waiting anyway
|
|
243
243
|
- if the request command fails unexpectedly, stop and report the error rather than sleeping and hoping for a new review
|
|
244
244
|
13. after a confirmed re-requested Copilot pass, refresh PR thread state again before reporting completion; if fresh Copilot threads exist, return to this follow-up loop rather than stopping at `review requested`
|
|
@@ -309,7 +309,7 @@ The canonical checkpoint verdict comment contract is [Gate Review Comment Contra
|
|
|
309
309
|
- **CI prerequisite:** resolve the draft gate config first (`resolveGateConfig(config, "draft")`). When `requireCi=true` (default), wait for green current-head CI before entering `draft_gate`. When `requireCi=false`, the draft gate may proceed without green CI. This draft-only override does **not** relax `pre_approval_gate`; final approval and merge readiness still require green current-head CI.
|
|
310
310
|
- **Pass criteria:** all configured draft gate angles pass; all findings at severities in `blockCleanOnFindingSeverities` are addressed; validation passes; no unrelated files are included.
|
|
311
311
|
- **Next step after passing:** mark the PR ready for review.
|
|
312
|
-
- **Board status sync (best-effort, after ready-for-review):** once the PR is created/marked ready for review, sync the linked issue's board item to "In Progress" so the queue board reflects active work without a manual `
|
|
312
|
+
- **Board status sync (best-effort, after ready-for-review):** once the PR is created/marked ready for review, sync the linked issue's board item to "In Progress" so the queue board reflects active work without a manual `dev-loops queue sync-status --to-column <col>`:
|
|
313
313
|
```sh
|
|
314
314
|
node <resolved-skill-scripts>/projects/sync-item-status.mjs --repo <owner/name> --item <linked-issue> --to-column "In Progress"
|
|
315
315
|
```
|
|
@@ -357,6 +357,8 @@ See [Merge Preconditions](../docs/merge-preconditions.md). Verify: zero unresolv
|
|
|
357
357
|
After merge-ready preconditions pass, verify [Merge Preconditions](../docs/merge-preconditions.md) authoritatively before reporting merge-ready. Stop at the human approval checkpoint by default. Cross-check via `dev-loops gate capture-threads` (not prose assertion).
|
|
358
358
|
Follow [Merge Preconditions](../docs/merge-preconditions.md): stop at `waiting_for_merge_authorization` after approval unless merge explicitly authorized. Run pre-merge gate evidence check before any `gh pr merge`.
|
|
359
359
|
|
|
360
|
+
When `approval.humanHandoff.enabled` is set, don't just park silently at this stop: run `dev-loops gate offer-human-handoff --repo <owner/name> --pr <number>` to surface candidate reviewers/assignees, then **offer** them to the operator. Only on operator confirmation, route the PR with `--assign <login>` / `--request-review <login>`. This is OFFER-only — never auto-assign. See the `approval.humanHandoff` section in [Merge Preconditions](../docs/merge-preconditions.md); it pairs with `autonomy.humanMergeOnly`.
|
|
361
|
+
|
|
360
362
|
### Mechanical pre-merge gate evidence check
|
|
361
363
|
|
|
362
364
|
Immediately before any `gh pr merge`, run:
|
|
@@ -387,15 +389,15 @@ Do not report completion or advance to the next PR queue item until `.pi/dev-loo
|
|
|
387
389
|
|
|
388
390
|
### Post-merge board archive (best-effort)
|
|
389
391
|
|
|
390
|
-
After the retrospective checkpoint write,
|
|
392
|
+
After the retrospective checkpoint write, run the post-merge board archive as a standard step of the post-merge hook (see [Merge Preconditions](../docs/merge-preconditions.md) "Post-merge"):
|
|
391
393
|
|
|
392
394
|
```sh
|
|
393
|
-
node <resolved-skill-scripts>/projects/archive-done-items.mjs --repo <owner/name>
|
|
395
|
+
node <resolved-skill-scripts>/projects/archive-done-items.mjs --repo <owner/name> || true
|
|
394
396
|
```
|
|
395
397
|
|
|
396
398
|
Board and threshold resolve from `.devloops` (`queue.projectNumber`/`queue.boardTitle`, `queue.archiveOlderThanDays`, default 7d), using local `gh` auth — no CI, cron, or PAT. This step is best-effort and NON-FATAL: ignore any failure and never let it block the merge or the retrospective.
|
|
397
399
|
|
|
398
|
-
Alongside the archive step, sync the linked issue's board item to "Done" so the queue board reflects the merge without a manual `
|
|
400
|
+
Alongside the archive step, sync the linked issue's board item to "Done" so the queue board reflects the merge without a manual `dev-loops queue sync-status --to-column <col>`:
|
|
399
401
|
|
|
400
402
|
```sh
|
|
401
403
|
node <resolved-skill-scripts>/projects/sync-item-status.mjs --repo <owner/name> --item <linked-issue> --to-column "Done"
|
|
@@ -25,7 +25,7 @@ Required installed runtime contract docs are shared bundled copies under `../doc
|
|
|
25
25
|
|
|
26
26
|
> Under the Claude Code harness the dev-loop runs as a single agent: run these steps directly — no read-only boundary and no separate async-subagent dispatch. See [Main Agent Contract](../docs/main-agent-contract.md).
|
|
27
27
|
|
|
28
|
-
Resolve authoritative state via the startup resolver (`npx dev-loops@0.
|
|
28
|
+
Resolve authoritative state via the startup resolver (`npx dev-loops@0.4.0 loop startup --issue <n>` for issues, `npx dev-loops@0.4.0 loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `npx dev-loops@0.4.0 loop build-envelope --input <resolver-output.json>`. The envelope determines `requiredReads`, `nextAction`, `stopRules`, and `acceptance` — load only those files, execute only that bounded task. It is the first handoff artifact consumed before loading any route pack. See [Workflow Handoff Contract](../docs/workflow-handoff-contract.md) for the derivation contract.
|
|
29
29
|
|
|
30
30
|
**Retrospective checkpoint gate:** the resolver reads `.pi/dev-loop-retrospective-checkpoint.json` and injects the state. When the checkpoint is `missing` and the repo config `workflow.requireRetrospective` (set via `.devloops` at repo root) is `true`, the resolver returns `needs_reconcile`. Complete or explicitly skip the retrospective before starting.
|
|
31
31
|
|
|
@@ -81,7 +81,7 @@ Do not preload route packs before the resolver selects the strategy.
|
|
|
81
81
|
|
|
82
82
|
## Async dispatch
|
|
83
83
|
|
|
84
|
-
**Async dispatch rule (enforced):** the resolver fails closed for GitHub-first strategies when `canonicalStateSummary.requiresAsyncDispatch` is `true` (default `required` mode) — inline invocation without an async run-id marker (`DEVLOOPS_RUN_ID
|
|
84
|
+
**Async dispatch rule (enforced):** the resolver fails closed for GitHub-first strategies when `canonicalStateSummary.requiresAsyncDispatch` is `true` (default `required` mode) — inline invocation without an async run-id marker (`DEVLOOPS_RUN_ID`) is rejected for those routes. Under the Claude Code harness this requirement is relaxed automatically. See [Startup procedure](#startup-procedure).
|
|
85
85
|
|
|
86
86
|
## Fallback gate-comment poster
|
|
87
87
|
|
|
@@ -99,10 +99,10 @@ When `@dev-loops/core` is available again, switch back to the full helper. The f
|
|
|
99
99
|
|
|
100
100
|
## Read-only info shortcut
|
|
101
101
|
|
|
102
|
-
Info/handoff requests can be served directly via `npx dev-loops@0.
|
|
103
|
-
- `npx dev-loops@0.
|
|
104
|
-
- `npx dev-loops@0.
|
|
105
|
-
- `npx dev-loops@0.
|
|
102
|
+
Info/handoff requests can be served directly via `npx dev-loops@0.4.0 loop info` (read-only; no full dev-loop run required):
|
|
103
|
+
- `npx dev-loops@0.4.0 loop info --issue <n>` — human-readable issue state summary (strategy, route, linked PR, next action)
|
|
104
|
+
- `npx dev-loops@0.4.0 loop info --pr <n>` — human-readable PR state summary (branch, CI, threads, rounds, action)
|
|
105
|
+
- `npx dev-loops@0.4.0 loop info --issue <n> --json` — machine-readable JSON output
|
|
106
106
|
|
|
107
107
|
## Guard rules
|
|
108
108
|
|
|
@@ -11,7 +11,7 @@ Canonical owner for anti-pattern guidance across all workflow families.
|
|
|
11
11
|
5. **Duplicate worktree paths**: Check `git worktree list` before creating new worktrees; reuse existing matching worktrees.
|
|
12
12
|
6. **Main-checkout mutation**: Reserve main checkout for inspection/control; use `tmp/worktrees/` paths for mutation work.
|
|
13
13
|
7. **Spelunking tooling internals instead of using the public surface**: Do not read installed package internals, scan tooling source, or run ad-hoc scripts to understand a tool's behavior. Use the CLI, its `--help` subcommands, and `skills/docs/`. Read tool source only when the task is to inspect or change that tool, or when a concrete failure path is inside it and no public CLI/docs path exists. Once a failure is concrete, search changed files for the exact pattern — don't run duplicate broad searches.
|
|
14
|
-
8. **Hand-editing the queue file when a board is configured**: When a GitHub Projects board is configured (`queue.projectNumber`/`queue.boardTitle` in `.devloops`), the board is the authoritative queue MEMBERSHIP and ordering source — not just status. Add work via the board (`dev-loops
|
|
14
|
+
8. **Hand-editing the queue file when a board is configured**: When a GitHub Projects board is configured (`queue.projectNumber`/`queue.boardTitle` in `.devloops`), the board is the authoritative queue MEMBERSHIP and ordering source — not just status. Add work via the board (`dev-loops queue add ... --column "Next Up"`), not by hand-editing `.pi/dev-loop-queue.json`; the queue runner reconciles board `Next Up` items into queue entries before each run. See the operator guides under `docs/` (queue board usage/setup) for the workflow.
|
|
15
15
|
|
|
16
16
|
## Light mode exception
|
|
17
17
|
|
|
@@ -51,7 +51,7 @@ For tracker-first MVP `story -> PR -> tracker sync` work, the source-repo refere
|
|
|
51
51
|
|
|
52
52
|
4. Branch on the detector output instead of inventing a polling loop:
|
|
53
53
|
- `state=waiting_for_copilot_review` with `snapshot.copilotReviewOnCurrentHead=false`: do **not** poll manually; either run `node <resolved-skill-scripts>/loop/run-watch-cycle.mjs --repo <owner/name> --pr <number>` for persistent async waiting or report the wait state and resume later after the single detector call
|
|
54
|
-
- `state=waiting_for_ci` with `snapshot.ciStatus` in `{ "pending", "none" }`: do **not** poll manually by default; use `
|
|
54
|
+
- `state=waiting_for_ci` with `snapshot.ciStatus` in `{ "pending", "none" }`: do **not** poll manually by default; use `dev-loops loop watch-ci --repo <owner/name> --pr <number>` to block-wait on the combined check/status state (provider-agnostic — CircleCI, GitHub Actions, and any external commit-status / check-run). `gh run watch <run-id>` is an Actions-only fallback when the current-head run id is already known and you know the gating checks are GitHub Actions. Either way, on `timeout`/`changed` report pending CI and resume later after the single detector refresh. Bounded exception: if GitHub created zero current-head check suites/statuses, the previous head rollup was green, and local `npm run verify` already passed for the same current head, rerun `detect-copilot-loop-state.mjs` with `--local-validation-head-sha <current-head-sha>` so the detector can promote that exact zero-suite case to `snapshot.ciStatus="crediblyGreen"` instead of waiting forever on raw `none`.
|
|
55
55
|
- `snapshot.ciStatus="failure"` remains a stop/fix state, never a wait loop
|
|
56
56
|
|
|
57
57
|
5. For reviewer-side draft-review work, run `node <resolved-skill-scripts>/loop/detect-reviewer-loop-state.mjs --repo <owner/name> --pr <number> [--reviewer-login <login>] [--local-state <path>]`.
|
|
@@ -224,6 +224,10 @@ gh pr edit <pr-number> --repo <resolved-repo> --title "..." --body-file <body-fi
|
|
|
224
224
|
gh pr ready <pr-number> --repo <resolved-repo>
|
|
225
225
|
gh pr review <pr-number> --repo <resolved-repo> --approve --body "..."
|
|
226
226
|
node <resolved-skill-scripts>/github/detect-checkpoint-evidence.mjs --repo <resolved-repo> --pr <pr-number>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The merge itself is gated, not implied by the lines above. Before any `gh pr merge`, follow [Merge Preconditions](./merge-preconditions.md): all preconditions (green CI, clean `draft_gate` + current-head `pre_approval_gate`, zero unresolved threads, explicit merge authorization) must hold. Critically, when `autonomy.humanMergeOnly: true` is set, merge is a fixed human-only action — `resolveEffectiveMergeAuthorized` fails closed, the agent **never** runs `gh pr merge`, and instead reports merge-ready + gate evidence and hands off to a human. Only when **not** `humanMergeOnly` and merge is explicitly authorized may the agent run:
|
|
230
|
+
```sh
|
|
227
231
|
gh pr merge <pr-number> --repo <resolved-repo> --squash --delete-branch
|
|
228
232
|
```
|
|
229
233
|
|
|
@@ -32,9 +32,73 @@ A marker is allowed only while the PR is still in draft; it must be removed befo
|
|
|
32
32
|
- `"Merge authorized if gates green"` is valid explicit authorization
|
|
33
33
|
- Implied approval from prior turns is not sufficient
|
|
34
34
|
|
|
35
|
+
### `autonomy.humanMergeOnly` — fixed human-only merge (non-overridable)
|
|
36
|
+
|
|
37
|
+
When a repo sets `autonomy.humanMergeOnly: true` in `.devloops`, merge is a fixed
|
|
38
|
+
human action and this authorization step is **non-overridable**:
|
|
39
|
+
|
|
40
|
+
- `resolveAutonomyStopAt` always includes `merge`, even if `stopAt` is set to `[]`.
|
|
41
|
+
- The effective merge authorization fails closed: `resolveEffectiveMergeAuthorized`
|
|
42
|
+
returns `false` regardless of the `mergeAuthorized` envelope flag or an explicit
|
|
43
|
+
"merge" instruction. The lifecycle resolver therefore never advances to the merge
|
|
44
|
+
state and parks at the `pre_approval_gate` human-merge handoff.
|
|
45
|
+
- The agent still runs the full mechanical pre-merge evidence check and reports
|
|
46
|
+
merge-ready + gate evidence, then hands off to a human to perform `gh pr merge`.
|
|
47
|
+
The agent **never** runs `gh pr merge` itself.
|
|
48
|
+
|
|
49
|
+
This makes human-gated merge an enforced repo invariant, not a per-run default an
|
|
50
|
+
explicit instruction can unlock.
|
|
51
|
+
|
|
52
|
+
### `approval.humanHandoff` — offer to assign a human at the handoff (opt-in)
|
|
53
|
+
|
|
54
|
+
When a repo sets `approval.humanHandoff.enabled: true` in `.devloops`, the loop
|
|
55
|
+
does not just park silently at the human-merge stop — at the
|
|
56
|
+
`pre_approval_gate` / `waiting_for_merge_authorization` boundary it **offers**
|
|
57
|
+
to route the PR to a named human (pairs with `autonomy.humanMergeOnly`: when
|
|
58
|
+
human-merge is enforced, the handoff names who should take it). Disabled by
|
|
59
|
+
default; no candidate sourcing when disabled.
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
approval:
|
|
63
|
+
humanHandoff:
|
|
64
|
+
enabled: true
|
|
65
|
+
candidatesFrom: [codeowners, recent-committers]
|
|
66
|
+
assignees: [alice, bob]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
At the handoff boundary, resolve and surface candidates:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
dev-loops gate offer-human-handoff --repo <owner/name> --pr <number>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This prints the deduped, ordered candidate list (priority:
|
|
76
|
+
`assignees` > `codeowners` for the touched paths (last-match-wins) >
|
|
77
|
+
`recent-committers` to those paths, PR author/bots excluded). It assigns no one.
|
|
78
|
+
|
|
79
|
+
**OFFER-only — the operator confirms the assignee** (auto-assigning without
|
|
80
|
+
confirmation is a non-goal). On confirmation, perform the action:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
dev-loops gate offer-human-handoff --repo <owner/name> --pr <number> \
|
|
84
|
+
--assign <login> --request-review <login>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
which runs `gh pr edit --add-assignee` / `--add-reviewer` for the confirmed
|
|
88
|
+
human(s).
|
|
89
|
+
|
|
35
90
|
## Post-merge
|
|
36
91
|
|
|
37
|
-
- Remove merged worktree: `
|
|
92
|
+
- Remove merged worktree (canonical): `node scripts/loop/cleanup-worktree.mjs --repo-root <main> (--issue <n> | --pr <n>)`.
|
|
93
|
+
It resolves the namespaced path, runs `git worktree remove --force <path> && git worktree prune` (the underlying
|
|
94
|
+
mechanism) from the main checkout, and refuses any path not under `tmp/worktrees/dev-loops/`. See
|
|
95
|
+
[worktree guidance](../../docs/worktree-guidance.md).
|
|
96
|
+
- Archive long-done queue items (operator-induced, NOT a cron): `node scripts/projects/archive-done-items.mjs --repo <owner/name> || true`.
|
|
97
|
+
Runs as part of this post-merge hook. It applies the configured `queue.archiveOlderThanDays` (default `7d`) and archives
|
|
98
|
+
board items whose issue/PR has been closed at least that long. Best-effort: run it as a standard post-merge step but ignore
|
|
99
|
+
any non-zero exit (a successful run that finds nothing to archive exits 0; a board/config-resolution/API error exits
|
|
100
|
+
non-zero, which the `|| true` masks) — a failure here must never block merge completion. See
|
|
101
|
+
[projects queue usage](../../docs/projects-queue-usage.md).
|
|
38
102
|
- Clean up stale branches
|
|
39
103
|
|
|
40
104
|
## Cross-references
|
|
@@ -10,6 +10,7 @@ Canonical owner for agent stop / wait / block conditions across all workflow fam
|
|
|
10
10
|
| `done` lifecycle state | all | Terminal stop |
|
|
11
11
|
| `approval_ready` without explicit merge auth | `final_approval` | Stop at approval gate |
|
|
12
12
|
| `merge_ready` without explicit merge auth | all | Stop at `waiting_for_merge_authorization` |
|
|
13
|
+
| `autonomy.humanMergeOnly: true` | all | Always stop at merge for human action, even with explicit merge auth — agent never runs `gh pr merge` (see [Merge preconditions](merge-preconditions.md)) |
|
|
13
14
|
| Ambiguous / contradictory state | all | Fail closed to `needs_reconcile` |
|
|
14
15
|
| Missing authoritative startup inputs | `dev-loop` | Fail closed |
|
|
15
16
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Tracker-First Story-to-PR Contract
|
|
2
2
|
|
|
3
3
|
This document defines the adapter-agnostic MVP contract for the tracker-first
|
|
4
|
-
story-to-PR workflow in `
|
|
4
|
+
story-to-PR workflow in `dev-loops`.
|
|
5
5
|
|
|
6
6
|
**MVP invariant: one tracker work item → one GitHub PR.**
|
|
7
7
|
|
|
@@ -43,9 +43,9 @@ are out of scope for this slice.
|
|
|
43
43
|
| PR lifecycle facts | GitHub | Draft / ready-for-review, open / merged / closed, branch, head SHA |
|
|
44
44
|
| PR review and CI facts | GitHub | Reviewer assignments, review states, check-run results, merge status |
|
|
45
45
|
| Decision content | ADR / RFC artifacts (when linked) | Architecture decisions and RFCs referenced from PR body |
|
|
46
|
-
| PR projection and reverse-sync logic | `
|
|
46
|
+
| PR projection and reverse-sync logic | `dev-loops` | Title/body/label generation rules, state mapping, sync triggers |
|
|
47
47
|
|
|
48
|
-
`
|
|
48
|
+
`dev-loops` **does not** become the canonical owner of any business fields.
|
|
49
49
|
It provides projection and sync logic only.
|
|
50
50
|
|
|
51
51
|
## 3. PR Projection Contract
|
|
@@ -234,9 +234,9 @@ This contract is intentionally narrower than the parent epics:
|
|
|
234
234
|
|
|
235
235
|
## 7. Related
|
|
236
236
|
|
|
237
|
-
- Parent workflow-family epic: [mfittko/
|
|
238
|
-
- Umbrella artifact model epic: [mfittko/
|
|
239
|
-
- This contract (first implementable slice): [mfittko/
|
|
237
|
+
- Parent workflow-family epic: [mfittko/dev-loops#17](https://github.com/mfittko/dev-loops/issues/17)
|
|
238
|
+
- Umbrella artifact model epic: [mfittko/dev-loops#19](https://github.com/mfittko/dev-loops/issues/19)
|
|
239
|
+
- This contract (first implementable slice): [mfittko/dev-loops#21](https://github.com/mfittko/dev-loops/issues/21)
|
|
240
240
|
- Copilot loop state graph: [Copilot Loop State Graph](../../docs/copilot-loop-state-graph.md)
|
|
241
241
|
- Reviewer loop state graph: [Reviewer Loop State Graph](../../docs/reviewer-loop-state-graph.md)
|
|
242
242
|
|
|
@@ -68,7 +68,13 @@ For the `local_implementation` strategy, before any planning or implementation m
|
|
|
68
68
|
node scripts/loop/pre-flight-gate.mjs --expected-branch <working-branch> --check-subagents
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
Before creating or reusing a worktree for local implementation,
|
|
71
|
+
Before creating or reusing a worktree for local implementation, use the canonical lifecycle entrypoint, which fetches the base remote, then creates-or-reuses the worktree at its namespaced path and provisions it in one step:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
node scripts/loop/ensure-worktree.mjs --repo-root <main> --issue <n>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This creates (or reuses) the worktree at `tmp/worktrees/dev-loops/<kind>-<n>` from a freshly fetched `origin/main` (the authoritative base for all worktree creation) and provisions its gitignored runtime files. Raw `git worktree add -b <branch> tmp/worktrees/dev-loops/<kind>-<n> origin/main` is the underlying mechanism `ensure-worktree.mjs` runs for you. See [worktree guidance](../../docs/worktree-guidance.md).
|
|
72
78
|
|
|
73
79
|
This validates:
|
|
74
80
|
- Worktree isolation (current directory is under `tmp/worktrees/`)
|
|
@@ -81,7 +87,7 @@ Note: `--check-subagents` is advisory — it reports availability but does not b
|
|
|
81
87
|
|
|
82
88
|
This gate does **not** apply to other routed strategies (`copilot_pr_followup`, `external_pr_followup`, `reviewer_fixer`, `wait_watch`, `final_approval`, `issue_intake`). Those strategies have their own execution rules and may edit code from any checkout as needed.
|
|
83
89
|
|
|
84
|
-
Development-only bypass (`
|
|
90
|
+
Development-only bypass (`DEVLOOPS_PREFLIGHT_BYPASS=1`) exists for testing the gate itself but must not be used in production workflow runs. The bypass variable is a testing convenience, not an operational escape hatch.
|
|
85
91
|
|
|
86
92
|
## Narrow failure-triage fast path
|
|
87
93
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 0.4.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Opt-in human reviewer/assignee handoff at the pre-approval gate** (#920, pairs with #910). A new `approval.humanHandoff` config (`{ enabled (default false), candidatesFrom: ["codeowners"|"recent-committers"], assignees: [...] }`) plus `scripts/github/resolve-handoff-candidates.mjs` resolve a deduped, priority-ordered candidate list (configured `assignees` → CODEOWNERS last-match-wins for the PR's changed paths → recent committers via `git log`, excluding the PR author/bots; team handles flagged). `dev-loops gate offer-human-handoff --repo <o/n> --pr <n> [--assign <login>] [--request-review <login>]` prints the offer and, only on an explicit `--assign`/`--request-review` flag, runs `gh pr edit --add-assignee/--add-reviewer` — OFFER-only, never auto-assigns. Surfaced at the human-merge handoff so `autonomy.humanMergeOnly` routes the PR to a named human instead of parking silently. Disabled by default; fail-soft per source.
|
|
12
|
+
- **Provider-agnostic CI watcher `dev-loops loop watch-ci`** (#917). A block-waiting watcher (`scripts/github/probe-ci-status.mjs`) that polls a PR's combined check-run + commit-status state for the current head SHA until terminal or timeout — covering GitHub Actions, CircleCI, and any external commit-status/check-run, unlike Actions-only `gh run watch`. It short-circuits to `changed` when the head SHA advances mid-wait so the loop re-baselines. The no-checks path is race-safe: a fresh push where a provider (CircleCI/Actions) hasn't posted its first status yet is NOT settled green on the first poll — the watcher awaits a 2-consecutive-zero-check-poll grace before settling `none`→`success`, treats PR `statusCheckRollup`-expected-but-unreported checks as pending, and never fabricates green from a transient `gh api`/parse failure (an errored fetch forces pending; a persistent error settles `timeout`, never success).
|
|
13
|
+
- **`autonomy.humanMergeOnly` — fixed, non-overridable human-merge rule** (#910). Repos with a hard "a human must perform the merge" rule can now set `autonomy.humanMergeOnly: true` in `.devloops`, making merge an enforced repo invariant rather than a per-run default an explicit instruction can unlock. When set: `resolveAutonomyStopAt` always includes `merge` (even if `stopAt` is `[]`); the new authoritative gate `resolveEffectiveMergeAuthorized(mergeAuthorized, config)` fails closed — it returns `false` regardless of the `mergeAuthorized` envelope flag / explicit "merge" instruction — and the lifecycle resolver (`resolveLifecycleState`) therefore never advances to the terminal merge state, parking instead at the `pre_approval_gate` human-merge handoff. The agent still runs the full mechanical pre-merge evidence check and reports merge-ready, but never runs `gh pr merge` itself. The `queue run` path routes its `--merge-authorized` flag through the same gate. New resolvers `resolveHumanMergeOnly` / `resolveEffectiveMergeAuthorized` in `@dev-loops/core/config`; see [skills/docs/merge-preconditions.md](skills/docs/merge-preconditions.md).
|
|
14
|
+
- **Managed worktree lifecycle** (#909). dev-loops now owns the full worktree lifecycle through one shared canonical-path resolver. (1) **Namespaced naming:** loop-owned worktrees live at `tmp/worktrees/dev-loops/<kind>-<number>` (e.g. `issue-909`, `pr-908`) with no branch suffix, so the path is recomputable from the issue/PR number alone — `resolveWorktreePath({ repoRoot, kind, number })` (in `@dev-loops/core/loop/handoff-envelope`) is the single source of truth for create/provision/cleanup. (1a) **Lifecycle entrypoint:** `scripts/loop/ensure-worktree.mjs --repo-root <p> (--issue <n> | --pr <n>)` is the canonical create+provision command — it fetches the base remote, creates the worktree at the canonical path (or reuses one that already exists there, reporting a conflict instead of clobbering a different branch), then invokes the provisioning core in the same step, printing `{ ok, path, created|reused, provision }`. (2) **Auto-provisioning:** a new `.devloops` `worktree` section (`copyOnInit` / `linkOnInit`, both opt-in arrays of repo-relative literal paths or glob patterns) drives `scripts/loop/provision-worktree.mjs`, which copies (`fs.cp`) or absolute-symlinks the configured gitignored files/dirs from the main checkout into a fresh worktree — directories recurse, sources outside the main checkout are rejected, missing sources / empty globs fail soft, and reuse is idempotent. It does not run `npm install` and is not a `node_modules` mirror. (3) **Namespace-scoped cleanup:** `scripts/loop/cleanup-worktree.mjs` resolves the canonical path and runs `git worktree remove --force` + `git worktree prune` from the main checkout, refusing any path not under `tmp/worktrees/dev-loops/` and failing soft on git errors. See [docs/worktree-guidance.md](docs/worktree-guidance.md).
|
|
15
|
+
- **Consumer migration guide** (#769): [`docs/migrating-to-dev-loops.md`](docs/migrating-to-dev-loops.md) walks existing `pi-dev-loops` consumers through every breaking change — package name (`pi-dev-loops`→`dev-loops`, `@pi-dev-loops/core`→`@dev-loops/core`), repo slug, the full `PI_*`→`DEVLOOPS_*` env-var mapping (a deliberate clean break with no aliases), and the `.devloops` config location. Linked from the README. The env vars are not shimmed by design (`0.x`, YAGNI); the legacy `.pi/dev-loop/settings.yaml` config path still loads with a deprecation warning.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Post-merge board archive is now a standard step of the post-merge hook** (#918). `archive-done-items.mjs` (applying `queue.archiveOlderThanDays`, default 7d) is wired into the canonical `merge-preconditions.md` "Post-merge" surface alongside worktree cleanup, and the copilot-pr-followup post-merge step is no longer framed as merely optional. Operator-induced (runs after merge); best-effort — the hook ignores a non-zero exit so a failed archive never blocks merge completion. NOT a cron/scheduled job.
|
|
20
|
+
- **Queue management surfaced under `dev-loops queue`** (#912). The queue board management commands (`add`, `list`, `reorder`, `move`, `sync-status`, `archive-done`, `ensure`) are now discoverable and runnable under `dev-loops queue <sub>` alongside the existing `queue run` — `dev-loops queue --help` lists them all with one-line descriptions. They delegate to the same `scripts/projects/*.mjs` implementations; `dev-loops project <sub>` is retained as a back-compat alias group (lowest-churn: the routing table is data-driven, so `queue` reuses the existing script mappings). Flag consistency: `queue add` now accepts `--column <name>` for the Status column (matching `queue list`), with `--status <name>` kept as a back-compat alias. `move`/`sync-status` keep their distinct `--to-column`. Removes the only reason to hand-write `gh api graphql` for queue work.
|
|
21
|
+
- **BREAKING: Node floor raised `>=20` → `>=24`** (#911). `engines.node` is now `>=24` in both `dev-loops` and `@dev-loops/core` (the latter previously declared no floor). CI already runs Node 24; this makes the supported floor explicit and unlocks Node 24 stdlib (e.g. native `fsp.glob`/`path.matchesGlob`). Consumers on Node < 24 must upgrade.
|
|
22
|
+
- **BREAKING: all `PI_*` environment variables renamed to `DEVLOOPS_*` — no aliases, no fallback** (#905). Completing the env-var neutralization left half-done by the rebrand (#763, surfaced in #769), every dev-loops-owned operational env var is now `DEVLOOPS_*`-only; the previous neutral-first alias pattern (which honored `PI_SUBAGENT_RUN_ID` / `PI_SUBAGENT_AVAILABLE` as fallbacks) is removed. This is a deliberate `0.x` breaking change — consumers must rename their env vars (migration covered by #769). Mapping: `PI_SUBAGENT_RUN_ID`→`DEVLOOPS_RUN_ID`, `PI_SUBAGENT_AVAILABLE`→`DEVLOOPS_SUBAGENT_AVAILABLE`, `PI_PREFLIGHT_BYPASS`→`DEVLOOPS_PREFLIGHT_BYPASS`, `PI_PREPUSH_BYPASS`→`DEVLOOPS_PREPUSH_BYPASS`, `PI_WORKTREE_BYPASS`→`DEVLOOPS_WORKTREE_BYPASS`, `PI_DEV_LOOPS_DEBUG`→`DEVLOOPS_DEBUG`, `PI_DEV_LOOP_STALE_RUNNER_MAX_AGE_MS`→`DEVLOOPS_STALE_RUNNER_MAX_AGE_MS`, `PI_DEV_LOOP_DETACHED`→`DEVLOOPS_DETACHED`. The Pi-runtime-injected vars dev-loops reads only to *integrate* with the Pi harness (`PI_SESSION`, `PI_INTERACTIVE`, `PI_AGENT_SESSIONS_DIR`, `PI_SUBAGENT_SESSIONS_DIR`, `PI_SUBAGENT_ASYNC_RUNS_DIR`, `PI_SUBAGENT_ASYNC_RESULTS_DIR`) are external Pi-platform contract vars and intentionally unchanged. A `cli-harness-agnostic` guard now asserts the code is harness-agnostic: no dev-loops-owned `PI_*` env var survives, and the Pi-runtime-injected vars may only be read at the harness-adapter boundary (`pi-adapter.mjs`, `conductor-monitor.mjs`) — a `PI_*` read anywhere else in code fails.
|
|
23
|
+
|
|
24
|
+
- **Remaining `pi-dev-loops` → `dev-loops` identity references aligned** (#906, closes #768). Residual stale-slug strings in a contract doc (and its generated `.claude` mirror) were corrected, guarded by the `docs-identity-contract` test so user-facing identity surfaces stay consistent.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **`queue run` no longer fabricates `done` for undispatched items** (#913, data-integrity). The queue driver is a deterministic adapter over the board, not the orchestration harness — but its missing-orchestrator path fell back to a per-entry `{ ok: true, pr: null }`, which silently marked every `Next Up` item `done` and moved it to **Done** with `pr: null`/`runId: null` in ~1s without any work happening (a single resolve pass would "complete" an entire backlog untouched). The driver now requires a verifiable terminal signal (an orchestrator-supplied result, e.g. a merged PR) before reflecting an item to Done; with no orchestrator wired (`runEntry`) in the current harness, `dev-loops queue run` is a no-op that leaves every board column unchanged and reports `reason: "no-orchestrator"`. The legit reflect path (real merged PR → Done) is preserved.
|
|
29
|
+
- **Queue board `Next Up` membership now resolves from a title-only `.devloops` config** (#904, closes #901). `resolveNextUpOrder` passed the project number as a raw number, which `list-queue-items`' string-only `--project` guard rejected — so a board configured by `queue.boardTitle` alone reported "Board configured but unavailable; nothing to run" and never read `Next Up`. The project ref is now passed as a string.
|
|
30
|
+
|
|
5
31
|
## 0.3.0
|
|
6
32
|
|
|
7
33
|
### Added
|
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ pi install git:github.com/mfittko/dev-loops # global
|
|
|
89
89
|
pi install -l git:github.com/mfittko/dev-loops # project-local
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
The CLI requires Node `>=
|
|
92
|
+
The CLI requires Node `>=24` and a GitHub-authenticated `gh` CLI for repository workflows. See [Requirements](#requirements).
|
|
93
93
|
|
|
94
94
|
## Docker
|
|
95
95
|
|
|
@@ -199,6 +199,12 @@ Key surfaces:
|
|
|
199
199
|
|
|
200
200
|
Full details: [Extension Documentation](./extension/README.md) and `.pi/dev-loop/defaults.yaml`.
|
|
201
201
|
|
|
202
|
+
### Migrating from an earlier release
|
|
203
|
+
|
|
204
|
+
Upgrading an install from before the rename to `dev-loops`? The package name, repo slug,
|
|
205
|
+
and all `PI_*` environment variables changed (the env vars are a clean break — no aliases).
|
|
206
|
+
See the [migration guide](./docs/migrating-to-dev-loops.md) for the full change list.
|
|
207
|
+
|
|
202
208
|
## Package surface
|
|
203
209
|
|
|
204
210
|
The `dev-loops` package ships both a standalone CLI and a Pi extension. Consumer repos should prefer pinned Pi package installs; global npm installs are optional, not part of the Pi runtime contract.
|
|
@@ -247,7 +253,7 @@ See [Extension Documentation](./extension/README.md) for the full command and pa
|
|
|
247
253
|
|
|
248
254
|
## Requirements
|
|
249
255
|
|
|
250
|
-
- Node `>=
|
|
256
|
+
- Node `>=24`
|
|
251
257
|
- `gh` installed and authenticated for GitHub/Copilot workflows
|
|
252
258
|
- `pi-subagents` for async workflow assumptions
|
|
253
259
|
- A Pi host that satisfies peer dependencies on `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui`
|
package/cli/index.mjs
CHANGED
|
@@ -27,12 +27,14 @@ const SUBCOMMAND_ROUTES = {
|
|
|
27
27
|
"probe-copilot": "scripts/github/probe-copilot-review.mjs",
|
|
28
28
|
"capture-threads": "scripts/github/capture-review-threads.mjs",
|
|
29
29
|
"reply-resolve": "scripts/github/reply-resolve-review-threads.mjs",
|
|
30
|
+
"offer-human-handoff": "scripts/github/offer-human-handoff.mjs",
|
|
30
31
|
},
|
|
31
32
|
loop: {
|
|
32
33
|
startup: "scripts/loop/resolve-dev-loop-startup.mjs",
|
|
33
34
|
"build-envelope": "scripts/loop/build-handoff-envelope.mjs",
|
|
34
35
|
outer: "scripts/loop/outer-loop.mjs",
|
|
35
36
|
"watch-cycle": "scripts/loop/run-watch-cycle.mjs",
|
|
37
|
+
"watch-ci": "scripts/github/probe-ci-status.mjs",
|
|
36
38
|
handoff: "scripts/loop/copilot-pr-handoff.mjs",
|
|
37
39
|
"watch-initial": "scripts/loop/watch-initial-copilot-pr.mjs",
|
|
38
40
|
"loop-state": "scripts/loop/detect-copilot-loop-state.mjs",
|
|
@@ -59,6 +61,13 @@ const SUBCOMMAND_ROUTES = {
|
|
|
59
61
|
},
|
|
60
62
|
queue: {
|
|
61
63
|
run: "scripts/loop/run-queue.mjs",
|
|
64
|
+
list: "scripts/projects/list-queue-items.mjs",
|
|
65
|
+
add: "scripts/projects/add-queue-item.mjs",
|
|
66
|
+
move: "scripts/projects/move-queue-item.mjs",
|
|
67
|
+
reorder: "scripts/projects/reorder-queue-item.mjs",
|
|
68
|
+
"archive-done": "scripts/projects/archive-done-items.mjs",
|
|
69
|
+
"sync-status": "scripts/projects/sync-item-status.mjs",
|
|
70
|
+
ensure: "scripts/projects/ensure-queue-board.mjs",
|
|
62
71
|
},
|
|
63
72
|
inspect: {
|
|
64
73
|
run: "scripts/loop/inspect-run.mjs",
|
|
@@ -91,12 +100,13 @@ const HELP_CATEGORY_LABELS = {
|
|
|
91
100
|
gate: "Gate verdicts, evidence, and review operations",
|
|
92
101
|
loop: "Loop lifecycle",
|
|
93
102
|
pr: "PR helpers",
|
|
94
|
-
|
|
103
|
+
queue: "Queue board: run + management (add/list/reorder/move/sync-status/archive)",
|
|
104
|
+
project: "Alias for queue (GitHub Projects queue helpers)",
|
|
95
105
|
inspect: "Inspection (Pi extension only)",
|
|
96
106
|
refine: "Epic tree refinement verification",
|
|
97
107
|
};
|
|
98
108
|
|
|
99
|
-
const TOP_LEVEL_HELP_CATEGORY_ORDER = ["gate", "loop", "pr", "project", "inspect", "refine"];
|
|
109
|
+
const TOP_LEVEL_HELP_CATEGORY_ORDER = ["gate", "loop", "pr", "queue", "project", "inspect", "refine"];
|
|
100
110
|
|
|
101
111
|
const SUBCOMMAND_DESCRIPTIONS = {
|
|
102
112
|
gate: {
|
|
@@ -108,12 +118,14 @@ const SUBCOMMAND_DESCRIPTIONS = {
|
|
|
108
118
|
"probe-copilot": "Poll for Copilot review activity",
|
|
109
119
|
"capture-threads": "Capture review threads",
|
|
110
120
|
"reply-resolve": "Reply and resolve review threads",
|
|
121
|
+
"offer-human-handoff": "Offer to assign PR to a human reviewer/assignee",
|
|
111
122
|
},
|
|
112
123
|
loop: {
|
|
113
124
|
startup: "Resolve dev-loop startup bundle",
|
|
114
125
|
"build-envelope": "Build handoff envelope from startup output",
|
|
115
126
|
outer: "Run outer-loop detection",
|
|
116
127
|
"watch-cycle": "Run Copilot wait cycle",
|
|
128
|
+
"watch-ci": "Block-wait on provider-agnostic CI (CircleCI/Actions/external)",
|
|
117
129
|
handoff: "Copilot PR handoff",
|
|
118
130
|
"watch-initial": "Watch initial Copilot PR",
|
|
119
131
|
"loop-state": "Detect Copilot loop state",
|
|
@@ -140,6 +152,13 @@ const SUBCOMMAND_DESCRIPTIONS = {
|
|
|
140
152
|
},
|
|
141
153
|
queue: {
|
|
142
154
|
run: "Run queue driver",
|
|
155
|
+
list: "List queue board items",
|
|
156
|
+
add: "Add issue/PR to queue board",
|
|
157
|
+
move: "Move queue item between Status columns",
|
|
158
|
+
reorder: "Reorder items (move-to-top/move-after/order, --dry-run)",
|
|
159
|
+
"archive-done": "Archive closed Done items older than a duration",
|
|
160
|
+
"sync-status": "Sync a queued issue/PR's board Status column (best-effort)",
|
|
161
|
+
ensure: "Create/repair queue board bootstrap surface",
|
|
143
162
|
},
|
|
144
163
|
inspect: {
|
|
145
164
|
run: "Inspect run state",
|
package/extension/README.md
CHANGED
|
@@ -210,7 +210,7 @@ Config is validated at runtime by Zod schemas (`packages/core/src/config/config.
|
|
|
210
210
|
## Runtime / build / test contract
|
|
211
211
|
|
|
212
212
|
Current Phase 3+ contract:
|
|
213
|
-
- Node runtime floor: `>=
|
|
213
|
+
- Node runtime floor: `>=24` (from `package.json`)
|
|
214
214
|
- Pi host expectations are documented from current peer dependencies rather than a tested pinned Pi version range
|
|
215
215
|
- the extension is source-loaded from `./extension/index.ts` through `package.json` `pi.extensions`
|
|
216
216
|
- the package exposes `skills` through `package.json` `pi.skills` for install-based global skill loading
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"pi-package"
|
|
16
16
|
],
|
|
17
17
|
"engines": {
|
|
18
|
-
"node": ">=
|
|
18
|
+
"node": ">=24"
|
|
19
19
|
},
|
|
20
20
|
"workspaces": [
|
|
21
21
|
"packages/*"
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"mermaid": "11.15.0",
|
|
71
|
-
"@dev-loops/core": "^0.
|
|
71
|
+
"@dev-loops/core": "^0.4.0"
|
|
72
72
|
},
|
|
73
73
|
"repository": {
|
|
74
74
|
"type": "git",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"url": "https://github.com/mfittko/dev-loops/issues"
|
|
79
79
|
},
|
|
80
80
|
"homepage": "https://github.com/mfittko/dev-loops#readme",
|
|
81
|
-
"version": "0.
|
|
81
|
+
"version": "0.4.0",
|
|
82
82
|
"files": [
|
|
83
83
|
"cli/",
|
|
84
84
|
"lib/",
|
package/scripts/README.md
CHANGED
|
@@ -499,7 +499,7 @@ Contract:
|
|
|
499
499
|
- enters watch only when request state is confirmed (`requested` or `already-requested`) and emits exact `watchArgs` + `watchTimeoutPolicy`
|
|
500
500
|
- watch refresh (`--watch-status`) is observational-only; rely on refreshed `loopDisposition` + `terminal` to decide whether to continue or stop
|
|
501
501
|
- explicit stop/blocked routing is machine-readable via `action: "stop"` plus `requestWatchContract.stopState`
|
|
502
|
-
- when `
|
|
502
|
+
- when `DEVLOOPS_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
|
|
503
503
|
|
|
504
504
|
Success output shape:
|
|
505
505
|
- `{ "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"?: { ... } }`
|
|
@@ -722,7 +722,7 @@ Fetches the live PR head SHA plus visible PR issue comments, then summarizes the
|
|
|
722
722
|
latest valid `draft_gate` and `pre_approval_gate` checkpoint verdict comments.
|
|
723
723
|
Use this when a fresh session needs authoritative visible gate evidence for the
|
|
724
724
|
current head before running `gh pr ready` or declaring final-approval readiness.
|
|
725
|
-
When `
|
|
725
|
+
When `DEVLOOPS_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.
|
|
726
726
|
|
|
727
727
|
Required:
|
|
728
728
|
- `--repo <owner/name>`
|