dev-loops 0.5.0 → 0.7.1
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 +2 -1
- package/.claude/agents/review.md +2 -1
- package/.claude/commands/loop-auto.md +7 -0
- package/.claude/commands/loop-continue.md +15 -0
- package/.claude/commands/loop-enqueue.md +22 -0
- package/.claude/commands/loop-grill.md +17 -0
- package/.claude/commands/loop-info.md +7 -0
- package/.claude/commands/loop-queue-status.md +24 -0
- package/.claude/commands/loop-start-spike.md +16 -0
- package/.claude/commands/loop-start.md +7 -0
- package/.claude/commands/loop-status.md +6 -0
- package/.claude/hooks/_bash-command-classify.mjs +333 -29
- package/.claude/hooks/_hook-decisions.mjs +138 -15
- package/.claude/hooks/_run-context.mjs +11 -4
- package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
- package/.claude/skills/copilot-pr-followup/SKILL.md +24 -12
- package/.claude/skills/dev-loop/SKILL.md +19 -10
- package/.claude/skills/docs/acceptance-criteria-verification.md +6 -2
- package/.claude/skills/docs/anti-patterns.md +2 -1
- package/.claude/skills/docs/artifact-authority-contract.md +22 -4
- package/.claude/skills/docs/copilot-loop-operations.md +1 -1
- package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
- package/.claude/skills/docs/issue-intake-procedure.md +1 -0
- package/.claude/skills/docs/local-planning-flow.md +1 -1
- package/.claude/skills/docs/local-planning-worked-example.md +1 -1
- package/.claude/skills/docs/merge-preconditions.md +17 -1
- package/.claude/skills/docs/plan-file-contract.md +1 -1
- package/.claude/skills/docs/public-dev-loop-contract.md +1 -1
- package/.claude/skills/docs/release-runbook.md +45 -0
- package/.claude/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/.claude/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
- package/.claude/skills/local-implementation/SKILL.md +26 -15
- package/.claude/skills/loop-grill/SKILL.md +163 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +125 -0
- package/README.md +29 -10
- package/agents/dev-loop.agent.md +11 -3
- package/agents/developer.agent.md +1 -1
- package/agents/docs.agent.md +1 -1
- package/agents/fixer.agent.md +1 -1
- package/agents/quality.agent.md +1 -1
- package/agents/refiner.agent.md +1 -1
- package/agents/review.agent.md +3 -2
- package/cli/index.mjs +35 -40
- package/extension/README.md +4 -4
- package/extension/checks.ts +1 -5
- package/extension/harness-types.ts +1 -0
- package/extension/index.ts +16 -1
- package/extension/pi-extension-adapter.ts +2 -0
- package/extension/presentation.ts +16 -13
- package/lib/dev-loops-core.mjs +141 -0
- package/package.json +10 -8
- package/scripts/claude/generate-claude-assets.mjs +15 -1
- package/scripts/github/capture-review-threads.mjs +2 -9
- package/scripts/github/comment-issue.mjs +174 -0
- package/scripts/github/create-label.mjs +133 -0
- package/scripts/github/detect-checkpoint-evidence.mjs +170 -14
- package/scripts/github/detect-linked-issue-pr.mjs +22 -6
- package/scripts/github/edit-pr.mjs +259 -0
- package/scripts/github/fetch-ci-logs.mjs +208 -0
- package/scripts/github/list-issues.mjs +184 -0
- package/scripts/github/manage-sub-issues.mjs +46 -10
- package/scripts/github/offer-human-handoff.mjs +8 -5
- package/scripts/github/post-gate-findings.mjs +14 -2
- package/scripts/github/probe-ci-status.mjs +8 -2
- package/scripts/github/probe-copilot-review.mjs +21 -14
- package/scripts/github/ready-for-review.mjs +26 -8
- package/scripts/github/reconcile-draft-gate.mjs +7 -3
- package/scripts/github/reply-resolve-review-thread.mjs +16 -5
- package/scripts/github/reply-resolve-review-threads.mjs +69 -7
- package/scripts/github/request-copilot-review.mjs +8 -2
- package/scripts/github/resolve-handoff-candidates.mjs +7 -3
- package/scripts/github/resolve-tracker-local-spec.mjs +9 -3
- package/scripts/github/stage-reviewer-draft.mjs +10 -2
- package/scripts/github/tick-verified-checkboxes.mjs +202 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +31 -10
- package/scripts/github/verify-fresh-review-context.mjs +150 -31
- package/scripts/github/view-pr.mjs +150 -0
- package/scripts/github/write-gate-context.mjs +248 -61
- package/scripts/github/write-gate-findings-log.mjs +75 -1
- package/scripts/lib/jq-output.mjs +18 -0
- package/scripts/loop/_handoff-contract.mjs +1 -0
- package/scripts/loop/_post-convergence-change.mjs +211 -0
- package/scripts/loop/_pr-runner-coordination.mjs +70 -0
- package/scripts/loop/_repo-root-resolver.mjs +47 -0
- package/scripts/loop/build-handoff-envelope.mjs +14 -4
- package/scripts/loop/check-retro-tooling.mjs +14 -3
- package/scripts/loop/checkpoint-contract.mjs +7 -4
- package/scripts/loop/cleanup-worktree.mjs +12 -3
- package/scripts/loop/conductor-monitor.mjs +12 -18
- package/scripts/loop/copilot-pr-handoff.mjs +135 -14
- package/scripts/loop/debt-remediate.mjs +24 -12
- package/scripts/loop/detect-change-scope.mjs +36 -7
- package/scripts/loop/detect-copilot-loop-state.mjs +11 -5
- package/scripts/loop/detect-copilot-session-activity.mjs +7 -3
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +7 -3
- package/scripts/loop/detect-internal-only-pr.mjs +8 -2
- package/scripts/loop/detect-issue-refinement-artifact.mjs +6 -3
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +179 -66
- package/scripts/loop/detect-reviewer-loop-state.mjs +12 -2
- package/scripts/loop/detect-tracker-first-loop-state.mjs +9 -1
- package/scripts/loop/detect-tracker-pr-state.mjs +10 -3
- package/scripts/loop/ensure-worktree.mjs +8 -3
- package/scripts/loop/info.mjs +12 -4
- package/scripts/loop/inspect-run-viewer/constants.mjs +4 -18
- package/scripts/loop/inspect-run-viewer/vendor/mermaid.min.js +3405 -0
- package/scripts/loop/inspect-run-viewer-ci-changes.mjs +18 -6
- package/scripts/loop/inspect-run-viewer.mjs +67 -4
- package/scripts/loop/inspect-run.mjs +8 -2
- package/scripts/loop/outer-loop.mjs +8 -4
- package/scripts/loop/pr-runner-coordination.mjs +2 -9
- package/scripts/loop/pre-commit-branch-guard.mjs +16 -10
- package/scripts/loop/pre-flight-gate.mjs +9 -9
- package/scripts/loop/pre-pr-ready-gate.mjs +8 -4
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +13 -4
- package/scripts/loop/provision-worktree.mjs +74 -3
- package/scripts/loop/resolve-dev-loop-startup.mjs +76 -7
- package/scripts/loop/resolve-gate-dispatch.mjs +134 -0
- package/scripts/loop/resolve-pr-conflicts.mjs +14 -7
- package/scripts/loop/run-conductor-cycle.mjs +13 -2
- package/scripts/loop/run-queue.mjs +18 -9
- package/scripts/loop/run-refinement-audit.mjs +8 -9
- package/scripts/loop/run-watch-cycle.mjs +2 -9
- package/scripts/loop/sanctioned-commands.mjs +104 -0
- package/scripts/loop/steer-loop.mjs +29 -16
- package/scripts/loop/validate-pr-body-spec.mjs +207 -0
- package/scripts/loop/watch-initial-copilot-pr.mjs +8 -3
- package/scripts/pages/build-site.mjs +59 -8
- package/scripts/pages/build-state-atlas.mjs +441 -0
- package/scripts/projects/_resolve-project.mjs +148 -0
- package/scripts/projects/add-queue-item.mjs +60 -54
- package/scripts/projects/archive-done-items.mjs +49 -84
- package/scripts/projects/ensure-queue-board.mjs +10 -36
- package/scripts/projects/list-queue-items.mjs +116 -65
- package/scripts/projects/move-queue-item.mjs +28 -49
- package/scripts/projects/reconcile-queue.mjs +253 -0
- package/scripts/projects/reorder-queue-item.mjs +41 -47
- package/scripts/projects/resolve-active-board-item.mjs +255 -0
- package/scripts/projects/sync-item-status.mjs +15 -10
- package/scripts/refine/_refine-helpers.mjs +21 -5
- package/scripts/refine/exit-spike.mjs +18 -8
- package/scripts/refine/promote-plan.mjs +22 -16
- package/scripts/refine/prose-linkage-detector.mjs +1 -1
- package/scripts/refine/refine-plan-file.mjs +13 -4
- package/scripts/refine/refinement-completeness-checker.mjs +1 -1
- package/scripts/refine/scaffold-spike-file.mjs +179 -0
- package/scripts/refine/scope-boundary-cross-checker.mjs +1 -1
- package/scripts/refine/tree-integrity-validator.mjs +1 -1
- package/scripts/refine/validate-plan-file.mjs +1 -1
- package/scripts/refine/validate-spike-file.mjs +1 -1
- package/scripts/refine/verify.mjs +11 -6
- package/scripts/release/assert-core-dependency-version.mjs +123 -0
- package/scripts/release/extract-changelog-section.mjs +125 -0
- package/skills/copilot-pr-followup/SKILL.md +24 -12
- package/skills/dev-loop/SKILL.md +22 -6
- package/skills/docs/acceptance-criteria-verification.md +6 -2
- package/skills/docs/anti-patterns.md +2 -1
- package/skills/docs/artifact-authority-contract.md +22 -4
- package/skills/docs/copilot-loop-operations.md +1 -1
- package/skills/docs/cross-harness-regression-contract.md +60 -0
- package/skills/docs/issue-intake-procedure.md +1 -0
- package/skills/docs/local-planning-flow.md +1 -1
- package/skills/docs/local-planning-worked-example.md +1 -1
- package/skills/docs/merge-preconditions.md +17 -1
- package/skills/docs/plan-file-contract.md +1 -1
- package/skills/docs/public-dev-loop-contract.md +1 -1
- package/skills/docs/release-runbook.md +45 -0
- package/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/skills/docs/workflow-handoff-contract.md +39 -0
- package/skills/local-implementation/SKILL.md +26 -15
- package/skills/loop-grill/SKILL.md +165 -0
- package/scripts/loop/conductor.mjs +0 -233
- package/scripts/loop/detect-stale-runner.mjs +0 -265
- package/scripts/loop/pre-push-main-guard.mjs +0 -117
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Release runbook
|
|
2
|
+
|
|
3
|
+
Releasing is fully automated from a tag. **Pushing a `v<version>` tag is the only
|
|
4
|
+
manual step.** Everything after the tag is hands-off.
|
|
5
|
+
|
|
6
|
+
## Procedure
|
|
7
|
+
|
|
8
|
+
1. On `main` (merged, green), bump the version and add the matching
|
|
9
|
+
`## <version> - <date>` section to `CHANGELOG.md` (the empty `## Unreleased`
|
|
10
|
+
heading stays above the latest version).
|
|
11
|
+
2. Tag the release commit and push the tag:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git tag v<version>
|
|
15
|
+
git push origin v<version>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That is the whole manual flow. Do **not** create the GitHub Release by hand —
|
|
19
|
+
the workflow does it (and is idempotent if you already created one).
|
|
20
|
+
|
|
21
|
+
## What happens automatically
|
|
22
|
+
|
|
23
|
+
- **`.github/workflows/release.yml`** fires on the `v*` tag push. It verifies the
|
|
24
|
+
tagged commit is on `origin/main`, extracts the `## <version>` block from
|
|
25
|
+
`CHANGELOG.md` via `scripts/release/extract-changelog-section.mjs`, and creates
|
|
26
|
+
the GitHub Release (`--latest`, notes = that CHANGELOG section). It is
|
|
27
|
+
**idempotent** (no-op if a Release for the tag already exists) and **fails
|
|
28
|
+
closed** if the version has no CHANGELOG section — an undocumented version never
|
|
29
|
+
gets an empty release.
|
|
30
|
+
- **`.github/workflows/npm-publish.yml`** fires on `release: published` (created by
|
|
31
|
+
the step above) and publishes the packages to npm.
|
|
32
|
+
|
|
33
|
+
## Failure modes
|
|
34
|
+
|
|
35
|
+
- Tag not on `main`: the release workflow fails the on-main guard — re-tag the
|
|
36
|
+
correct commit.
|
|
37
|
+
- Missing CHANGELOG section: the extraction step exits non-zero and no Release is
|
|
38
|
+
created. The workflow checks out the tagged commit, so editing `CHANGELOG.md`
|
|
39
|
+
alone is not enough — commit the `## <version>` section, then move the tag to
|
|
40
|
+
the new commit and re-push:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git tag -f v<version>
|
|
44
|
+
git push --force origin v<version>
|
|
45
|
+
```
|
|
@@ -87,65 +87,87 @@ Callers have two supported integration options:
|
|
|
87
87
|
|
|
88
88
|
If the gate result is `needs_reconcile`, the caller must not proceed with the proposed routing. The `nextAction` field instructs the operator to complete or explicitly skip the retrospective.
|
|
89
89
|
|
|
90
|
-
##
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
###
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
90
|
+
## Advisory findings — never a merge gate (issue #1077, Reading B)
|
|
91
|
+
|
|
92
|
+
The retrospective is **advisory**: it runs, records flagged raw-call / discipline
|
|
93
|
+
observations honestly, and passes them back to the conductor (main agent) to
|
|
94
|
+
**decide** what to do with them — but it **NEVER blocks a merge or any lifecycle
|
|
95
|
+
transition**. The pre-merge retrospective gate (`evaluateRetrospectiveMergeApproval`
|
|
96
|
+
and the `requireRetrospectiveGate` / `requireRetrospectiveInternalTooling` config
|
|
97
|
+
keys) has been **removed**. There is no `retrospective_gate_pending` / `blocked`
|
|
98
|
+
disposition on account of the internal-tooling raw-call record.
|
|
99
|
+
|
|
100
|
+
### How findings travel (Reading B)
|
|
101
|
+
|
|
102
|
+
1. **Deterministic return contract.** The loop subagent's handoff envelope carries
|
|
103
|
+
the retrospective findings as a structured `retrospectiveFindings` field — the
|
|
104
|
+
`check-retro-tooling.mjs` JSON output (`{ internalToolingOnly, rawCallViolations,
|
|
105
|
+
allowedWriteOps }`), not prose. The conductor reads that field. This is a hard
|
|
106
|
+
contract; see [Workflow Handoff Contract](./workflow-handoff-contract.md).
|
|
107
|
+
2. **Durability — advisory PR comment.** The conductor posts a single advisory PR
|
|
108
|
+
comment carrying the findings (`rawCallViolations`, `internalToolingOnly`,
|
|
109
|
+
`allowedWriteOps`). Durable and on-GitHub, but **not a gate**. No disk artifact
|
|
110
|
+
is written for retrospective *findings*.
|
|
111
|
+
3. **No config.** There is nothing to configure: the retrospective always runs and
|
|
112
|
+
always returns findings. `requireRetrospectiveGate` and
|
|
113
|
+
`requireRetrospectiveInternalTooling` no longer exist.
|
|
114
|
+
|
|
115
|
+
A PR that is otherwise green becomes merge-ready with the violations **recorded**,
|
|
116
|
+
not blocked. The conductor may note them, open a follow-up, coach — or ignore.
|
|
117
|
+
|
|
118
|
+
### Internal-tooling-only rule (issue #982) — now advisory
|
|
119
|
+
|
|
120
|
+
This rule records the dev-loops maintainers' own dogfooding discipline: the loop's
|
|
121
|
+
own execution should use internal dev-loops tooling, not agent-level raw
|
|
122
|
+
`gh`/`python`/`node -e` escape hatches. **It no longer blocks.** The flagged calls
|
|
123
|
+
are reported as advisory findings via the envelope + PR comment.
|
|
124
|
+
|
|
125
|
+
**Flagged as raw-call violations:** `gh ...` (including `gh api`, `gh ... --jq`),
|
|
126
|
+
`python` / `python3`, `node -e` / `node --eval` (inline eval). **Allowed (NOT
|
|
127
|
+
violations):** dev-loops subcommands and `node scripts/*.mjs` invocations — those
|
|
128
|
+
scripts legitimately call `gh`/GraphQL internally; that is the tooling. The rule
|
|
129
|
+
targets the agent's own top-level shell calls, not a script's internals.
|
|
130
|
+
|
|
131
|
+
**Write-op allowlist (verifier only):** `gh pr merge`, `gh pr ready`,
|
|
132
|
+
`gh issue create`, `gh issue edit` have no internal wrapper today. The deterministic
|
|
133
|
+
verifier records these as `allowedWriteOps` rather than violations so they are
|
|
134
|
+
surfaced distinctly, not as breaches. Close the gap with a wrapper to remove an
|
|
135
|
+
entry. None of these block anything.
|
|
136
|
+
|
|
137
|
+
### Deterministic verifier (findings-producer)
|
|
138
|
+
|
|
139
|
+
`node scripts/loop/check-retro-tooling.mjs [--transcript <path>] [--json]` reads a
|
|
140
|
+
newline-delimited transcript of the shell commands the agent ran (one top-level
|
|
141
|
+
command per line, via `--transcript` or stdin) and reports agent-level raw
|
|
142
|
+
`gh`/`python`/`python3`/`node -e`/`node --eval` calls. It is a **findings-producer**:
|
|
143
|
+
its JSON output (`{ ok, internalToolingOnly, rawCallViolations, allowedWriteOps }`)
|
|
144
|
+
is returned to the conductor via the envelope's `retrospectiveFindings` field (the
|
|
145
|
+
envelope carries the normalized shape `{ internalToolingOnly, rawCallViolations,
|
|
146
|
+
allowedWriteOps }` — the redundant `ok` flag is dropped by normalization) — it
|
|
147
|
+
is **not** written to a checkpoint and **not** a gate. Exit code `1` when violations
|
|
148
|
+
are found, `0` when clean. The pure `analyzeTranscript(transcript)` export returns
|
|
149
|
+
`{ violations, allowedWriteOps, internalToolingOnly }`.
|
|
150
|
+
|
|
151
|
+
Matching rules: a tool name at the start of a command segment (start of line, or
|
|
152
|
+
after `&&`/`||`/`|`/`;`); `node` is a violation only with `-e`/`--eval`. Before
|
|
153
|
+
classifying, the verifier normalizes the segment head — it strips leading
|
|
154
|
+
`NAME=value` env-assignment prefixes (`GH_TOKEN=x gh api`), strips a leading
|
|
155
|
+
wrapper binary from `{sudo, env, xargs, time, nice, command}` (`sudo gh api`,
|
|
156
|
+
`xargs gh api`), and reduces a path-prefixed binary to its basename
|
|
157
|
+
(`./node_modules/.bin/gh`, `/usr/bin/python3`) — so the common prefixed/wrapped
|
|
158
|
+
raw-call forms are caught. Known limitation: it does NOT fully parse shell
|
|
159
|
+
quoting/substitution. A separator inside a quoted argument can over-report; deeply
|
|
160
|
+
obfuscated calls (command substitution `$(...)`, aliases, `eval`) may evade it —
|
|
161
|
+
prefer single-line, single-purpose commands in transcripts.
|
|
162
|
+
|
|
163
|
+
### Lifecycle reconciliation
|
|
164
|
+
|
|
165
|
+
The retrospective is described consistently as a **post-merge / advisory
|
|
166
|
+
reflection**, never a pre-merge blocker. The former contradiction —
|
|
167
|
+
`lifecycle-state.mjs` documenting the retro as a post-merge write while
|
|
168
|
+
`pr-gate-coordination.mjs` enforced it pre-merge — is resolved by removing the
|
|
169
|
+
pre-merge gate: the merge lifecycle step proceeds, and the retrospective is an
|
|
170
|
+
advisory reflection whose findings reach the conductor via the envelope.
|
|
149
171
|
|
|
150
172
|
## Durable artifact format
|
|
151
173
|
|
|
@@ -162,25 +184,17 @@ The checkpoint file is written by `.pi/extensions/dev-loop-behavioral-review.ts`
|
|
|
162
184
|
|
|
163
185
|
### After retrospective is done (written by operator or skill)
|
|
164
186
|
|
|
165
|
-
A minimal completion clears the startup gate.
|
|
166
|
-
|
|
167
|
-
`
|
|
187
|
+
A minimal completion clears the startup/resume completion gate. The checkpoint
|
|
188
|
+
file carries **only completion state** — retrospective *findings*
|
|
189
|
+
(`behavioralReview`, `rawCallViolations`, `internalToolingOnly`) no longer live on
|
|
190
|
+
disk; they travel in the handoff envelope's `retrospectiveFindings` field and an
|
|
191
|
+
advisory PR comment (issue #1077, Reading B):
|
|
168
192
|
|
|
169
193
|
```json
|
|
170
194
|
{
|
|
171
195
|
"state": "complete",
|
|
172
196
|
"completedAt": "2026-05-29T16:30:00.000Z",
|
|
173
|
-
"notes": "Loop followed working agreement; minor drift on thread resolution."
|
|
174
|
-
"gateQuality": "Real gates with concrete findings and follow-through.",
|
|
175
|
-
"mergeRecommendation": "Merge approved — all gates passed clean.",
|
|
176
|
-
"behavioralReview": {
|
|
177
|
-
"mergeApproved": true,
|
|
178
|
-
"followedWorkingAgreement": true,
|
|
179
|
-
"gateQualityAcceptable": true,
|
|
180
|
-
"drifts": [],
|
|
181
|
-
"internalToolingOnly": true,
|
|
182
|
-
"rawCallViolations": []
|
|
183
|
-
}
|
|
197
|
+
"notes": "Loop followed working agreement; minor drift on thread resolution."
|
|
184
198
|
}
|
|
185
199
|
```
|
|
186
200
|
|
|
@@ -199,9 +213,9 @@ A minimal completion clears the startup gate. To also clear the **merge gate**
|
|
|
199
213
|
| Artifact | Location |
|
|
200
214
|
|---|---|
|
|
201
215
|
| Checkpoint state machine | `packages/core/src/loop/retrospective-checkpoint.mjs` (internal core module; not part of the public package exports surface) |
|
|
202
|
-
|
|
|
203
|
-
|
|
|
204
|
-
| Tests | `packages/core/test/retrospective-checkpoint.test.mjs`, `packages/core/test/pr-gate-coordination.test.mjs` |
|
|
216
|
+
| Internal-tooling verifier (findings-producer) | `scripts/loop/check-retro-tooling.mjs` (+ `test/loop/check-retro-tooling.test.mjs`) |
|
|
217
|
+
| Advisory findings envelope field | `packages/core/src/loop/handoff-envelope.mjs` — `retrospectiveFindings` |
|
|
218
|
+
| Tests | `packages/core/test/retrospective-checkpoint.test.mjs`, `packages/core/test/pr-gate-coordination.test.mjs`, `packages/core/test/handoff-envelope.test.mjs` |
|
|
205
219
|
| Extension (writes required marker, fires review prompt) | `.pi/extensions/dev-loop-behavioral-review.ts` |
|
|
206
220
|
| Checkpoint file | `.pi/dev-loop-retrospective-checkpoint.json` |
|
|
207
221
|
| AGENTS.md repo contract | [Agent Instructions](../../AGENTS.md) — concise repo contract and working rules |
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# UI e2e scoping step
|
|
2
|
+
|
|
3
|
+
Canonical owner for **when the shared UI/mobile e2e loop is required**. Inclusion
|
|
4
|
+
is **path-triggered and deterministic** — it does not depend on a human annotating
|
|
5
|
+
the PR or a phase doc.
|
|
6
|
+
|
|
7
|
+
## Trigger: a rendered-artifact change requires UI e2e coverage
|
|
8
|
+
|
|
9
|
+
A PR that **adds or modifies a rendered HTML artifact** MUST run the shared UI e2e
|
|
10
|
+
assertions (mobile + desktop) AND register that artifact in the e2e suite. A
|
|
11
|
+
"rendered artifact" is anything that renders to a served page or component:
|
|
12
|
+
|
|
13
|
+
- a presentation deck — `docs/presentations/*.html` (registered in `DECK_REGISTRY`)
|
|
14
|
+
- an article page — `docs/articles/*.html` (registered in `ARTICLE_REGISTRY`; the
|
|
15
|
+
intro article is the published landing page)
|
|
16
|
+
- the inspect-run viewer's served page/component — `scripts/loop/inspect-run-viewer.mjs`
|
|
17
|
+
|
|
18
|
+
The trigger is the PR's **changed-file set** matched against these explicit globs.
|
|
19
|
+
It is conservative on purpose (issue #976 scope): only artifacts that render to a
|
|
20
|
+
page/component, matched by exact directory + single-segment `*.html` globs (no
|
|
21
|
+
recursion) and the viewer source path.
|
|
22
|
+
|
|
23
|
+
Registration is keyed on the **full repo-relative path**, not the basename, so
|
|
24
|
+
`docs/articles/X.html` and `docs/presentations/X.html` (which share basenames,
|
|
25
|
+
e.g. `introducing-dev-loops.html`) are **distinct** artifacts and can never alias
|
|
26
|
+
onto each other — editing the article never counts as deck coverage and vice versa.
|
|
27
|
+
|
|
28
|
+
### Examples — required
|
|
29
|
+
|
|
30
|
+
- `docs/presentations/introducing-dev-loops.html` edited → UI e2e **required**
|
|
31
|
+
(deck-smoke CI job runs the deck fit specs).
|
|
32
|
+
- `docs/articles/introducing-dev-loops.html` (the landing page) edited → UI e2e
|
|
33
|
+
**required** (article-smoke CI job runs the article fit specs); it is its own
|
|
34
|
+
registration, NOT covered by the same-named deck.
|
|
35
|
+
- A new `docs/articles/new-page.html` added → UI e2e **required**, and because the
|
|
36
|
+
new page is not yet in `ARTICLE_REGISTRY` the gate **fails closed** until it is
|
|
37
|
+
registered (add an entry + a thin spec calling `defineArticleSuite`).
|
|
38
|
+
- `scripts/loop/inspect-run-viewer.mjs` edited → UI e2e **required** (the viewer is
|
|
39
|
+
registered in `VIEWER_REGISTRY`).
|
|
40
|
+
|
|
41
|
+
### Examples — not required
|
|
42
|
+
|
|
43
|
+
- `packages/core/src/loop/copilot-loop-state.mjs`, `README.md`, `*.test.mjs`,
|
|
44
|
+
`docs/articles/foo.md` (not `.html`), or a nested `docs/articles/sub/x.html`
|
|
45
|
+
(the glob is single-segment) → UI e2e **not required**; the gate passes through.
|
|
46
|
+
|
|
47
|
+
## Register the artifact in the suite
|
|
48
|
+
|
|
49
|
+
"Registered" means the artifact appears in the shared registries:
|
|
50
|
+
|
|
51
|
+
- decks → `DECK_REGISTRY` in `test/playwright/harness/deck-fit-harness.mjs`
|
|
52
|
+
(served from `docs/presentations/<deck>`) with a thin spec calling `defineDeckSuite`.
|
|
53
|
+
- articles → `ARTICLE_REGISTRY` in `test/playwright/harness/deck-fit-harness.mjs`
|
|
54
|
+
(served from `docs/articles/<file>`) with a thin spec calling `defineArticleSuite`
|
|
55
|
+
(shared fit/CSP/no-horizontal-scroll assertions, minus the deck's per-section
|
|
56
|
+
named captures).
|
|
57
|
+
- the viewer → `VIEWER_REGISTRY` in
|
|
58
|
+
`test/playwright/harness/inspect-run-viewer-harness.mjs`.
|
|
59
|
+
|
|
60
|
+
The deterministic membership list the gate checks against lives in
|
|
61
|
+
`packages/core/src/loop/ui-e2e-scoping.mjs` (`REGISTERED_ARTIFACT_PATHS`, keyed by
|
|
62
|
+
full repo-relative path; `VIEWER_ARTIFACT_ID`). A sync test keeps
|
|
63
|
+
`REGISTERED_ARTIFACT_PATHS` from drifting from `DECK_REGISTRY` + `ARTICLE_REGISTRY`;
|
|
64
|
+
the single-valued `VIEWER_ARTIFACT_ID` is not import-checked against `VIEWER_REGISTRY`
|
|
65
|
+
(that harness pulls `@playwright/test` into core), so keep the two in sync by hand.
|
|
66
|
+
|
|
67
|
+
## Fail-closed semantics
|
|
68
|
+
|
|
69
|
+
The check is wired as a gate precondition in `evaluatePrGateCoordination`
|
|
70
|
+
(`packages/core/src/loop/pr-gate-coordination.mjs`), at a seam distinct from the
|
|
71
|
+
mergeability (#980) and retrospective (#982) preconditions. It fails closed:
|
|
72
|
+
|
|
73
|
+
- **Unregistered rendered artifact touched** → gate blocks with
|
|
74
|
+
`nextAction: run_ui_e2e_suite`, reason naming the artifact and that it needs
|
|
75
|
+
registration in the suite.
|
|
76
|
+
- **Registered, but UI e2e suite did not pass for this head** (`uiE2ePassed` is
|
|
77
|
+
`false`/unknown) → gate blocks with the same action, reason that the suite must
|
|
78
|
+
run and pass first.
|
|
79
|
+
- **Non-UI change** → `required: false`, gate passes through untouched.
|
|
80
|
+
|
|
81
|
+
The detect layer reads the changed-file set from `gh pr view --json files` and
|
|
82
|
+
derives `uiE2ePassed` from the `statusCheckRollup` UI e2e check(s)
|
|
83
|
+
(`UI_E2E_CHECK_NAMES`): present UI e2e checks must all be `SUCCESS`; if none is
|
|
84
|
+
present the value is unknown and the gate fails closed.
|
|
85
|
+
|
|
86
|
+
Each rendered-artifact family has a stable, satisfiable CI job in
|
|
87
|
+
`.github/workflows/ci.yml`, path/diff-conditioned in the `changes` job and named
|
|
88
|
+
to match `UI_E2E_CHECK_NAMES` so the gate can read a real signal:
|
|
89
|
+
|
|
90
|
+
- `viewer-smoke` → inspect-run viewer spec (triggered by the viewer source set).
|
|
91
|
+
- `deck-smoke` → both presentation deck fit specs (triggered by
|
|
92
|
+
`docs/presentations/**` and the deck specs/harness/configs).
|
|
93
|
+
- `article-smoke` → both article fit specs (triggered by `docs/articles/**` and the
|
|
94
|
+
article specs/harness/configs).
|
|
95
|
+
|
|
96
|
+
So a deck- or article-only PR has a CI check that can actually satisfy the gate;
|
|
97
|
+
without these jobs such a PR would fail closed with no satisfiable signal.
|
|
98
|
+
|
|
99
|
+
## Verifying a UI change: render and look, but don't persist ad-hoc checks
|
|
100
|
+
|
|
101
|
+
When you change a rendered artifact's layout, **verify it visually before merging** —
|
|
102
|
+
render the built page and inspect real geometry (`getBoundingClientRect` for
|
|
103
|
+
edges/widths, a full-page screenshot to actually look at it) at desktop and mobile
|
|
104
|
+
widths. This intermediary e2e pass is a required *verification strategy*: a layout
|
|
105
|
+
claim ("the columns align now") is only credible once measured against the rendered
|
|
106
|
+
page, not the source CSS.
|
|
107
|
+
|
|
108
|
+
It is **not** something to persist into the codebase. The registered, always-on
|
|
109
|
+
coverage is the shared fit/CSP suite (`defineArticleSuite`/`defineDeckSuite`); do
|
|
110
|
+
not grow it with one-off pixel/alignment assertions written to confirm a single
|
|
111
|
+
fix. Use a throwaway script to render, measure, and screenshot while iterating,
|
|
112
|
+
then drop it. Adding a bespoke geometry assertion per fix bloats the suite and
|
|
113
|
+
couples it to incidental layout numbers.
|
|
114
|
+
|
|
115
|
+
## An assertion must be able to fail on the defect
|
|
116
|
+
|
|
117
|
+
A UI assertion that is **true for the broken state validates the bug**. Real
|
|
118
|
+
example (the layout regression this rule comes from): a check asserted
|
|
119
|
+
`margin-left > 1px` on the prose to prove it was "centered" — but the visible bug
|
|
120
|
+
*was* the prose floating in a narrow centered measure, misaligned from the
|
|
121
|
+
full-width boxes beside it. `margin-left` was large in exactly the broken state, so
|
|
122
|
+
the check passed green on every gate while the page rendered visibly broken.
|
|
123
|
+
|
|
124
|
+
Before trusting any UI assertion, confirm it **fails on the defect**: run it against
|
|
125
|
+
the broken layout, or inject the defect (e.g. shove one block's left edge) and check
|
|
126
|
+
that the assertion goes red. An assertion that cannot fail on the bug it names is
|
|
127
|
+
not coverage — it is false confidence. Prefer asserting the invariant that actually
|
|
128
|
+
defines "correct" (e.g. all content blocks share one left edge within tolerance)
|
|
129
|
+
over a proxy that happens to correlate with it.
|
|
130
|
+
|
|
131
|
+
## Non-goals
|
|
132
|
+
|
|
133
|
+
Not always-on screenshot testing; not mandatory multi-browser. The criterion is
|
|
134
|
+
only the conservative path-glob + registry-membership + passing-coverage check.
|
|
@@ -17,6 +17,35 @@ template.
|
|
|
17
17
|
| Gate state (detectors) + strategy defaults | `currentGate`, `worktreeRequired` |
|
|
18
18
|
| Settings (`.devloops` at repo root + `defaults.yaml`) | `gateConfig`, `stopRules`, `asyncStartMode`, `requireDraftFirst`, `maxCopilotRounds` |
|
|
19
19
|
| Gate state (detectors) | `currentHeadSha`, `ciStatus`, `unresolvedThreadCount`, `copilotRoundCount` |
|
|
20
|
+
| Canonical sanctioned-command map (`scripts/loop/sanctioned-commands.mjs`) | `sanctionedCommands` |
|
|
21
|
+
|
|
22
|
+
## Sanctioned commands (MANDATORY DEFAULT — issue #1081)
|
|
23
|
+
|
|
24
|
+
`sanctionedCommands` is a **mandatory default** element of every handoff
|
|
25
|
+
envelope. It is the operation → wrapper command map (which wrapper performs
|
|
26
|
+
which GitHub/loop operation), plus the forbidden and orchestrator-owned lists.
|
|
27
|
+
The `loop build-envelope` CLI injects it into every emitted envelope, so a
|
|
28
|
+
spawned dev-loop subagent receives it verbatim without the briefer adding it —
|
|
29
|
+
no re-deriving which wrapper does `gh pr ready` etc.
|
|
30
|
+
|
|
31
|
+
The **single source of truth** is `scripts/loop/sanctioned-commands.mjs` (a
|
|
32
|
+
frozen data module). `@dev-loops/core` stays consumer-agnostic: it defines the
|
|
33
|
+
envelope SHAPE and carries whatever `sanctionedCommands` object the consumer
|
|
34
|
+
supplies; the repo-specific `scripts/...` paths live only in the consumer
|
|
35
|
+
module. Do not duplicate the map into prose — reference the module.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
sanctionedCommands?: {
|
|
39
|
+
reads: Record<string, string>; // operation → wrapper path (some also accept `loop info`)
|
|
40
|
+
edits: Record<string, string>;
|
|
41
|
+
lifecycle: Record<string, string>;
|
|
42
|
+
forbidden: string[]; // raw `gh pr view/checks/edit`, `node -e`, `python -c`, transcript tailing, sleep-poll loops
|
|
43
|
+
orchestratorOwned: string[]; // `gh pr merge`, board status transitions — never done by a subagent
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A contract test (`test/contracts/sanctioned-commands-exist.test.mjs`) asserts
|
|
48
|
+
every mapped wrapper exists on disk and fails closed if one is renamed/removed.
|
|
20
49
|
|
|
21
50
|
## Acceptance templates
|
|
22
51
|
|
|
@@ -111,6 +140,15 @@ interface HandoffEnvelope {
|
|
|
111
140
|
scopeConstraint?: string;
|
|
112
141
|
customStopAt?: string;
|
|
113
142
|
};
|
|
143
|
+
|
|
144
|
+
// Mandatory default (issue #1081). Source: scripts/loop/sanctioned-commands.mjs
|
|
145
|
+
sanctionedCommands?: {
|
|
146
|
+
reads: Record<string, string>;
|
|
147
|
+
edits: Record<string, string>;
|
|
148
|
+
lifecycle: Record<string, string>;
|
|
149
|
+
forbidden: string[];
|
|
150
|
+
orchestratorOwned: string[];
|
|
151
|
+
};
|
|
114
152
|
}
|
|
115
153
|
```
|
|
116
154
|
|
|
@@ -121,6 +159,7 @@ interface HandoffEnvelope {
|
|
|
121
159
|
3. Execute `nextAction`.
|
|
122
160
|
4. Respect `stopRules` — do not proceed past a gated stop point without authorization.
|
|
123
161
|
5. Use `acceptance` to self-validate before declaring completion.
|
|
162
|
+
6. Use `sanctionedCommands` as the authoritative operation → wrapper map: never call a raw `gh`/`node -e`/`python -c` for an operation the map covers, and never perform an `orchestratorOwned` action.
|
|
124
163
|
|
|
125
164
|
## Backward compatibility
|
|
126
165
|
|
|
@@ -43,11 +43,17 @@ Treat missing optional files as normal bootstrap conditions, not errors.
|
|
|
43
43
|
|
|
44
44
|
### Tracker-backed local implementation
|
|
45
45
|
|
|
46
|
-
Local implementation supports two durable spec
|
|
46
|
+
Local implementation supports these spec-of-record inputs. The first two are durable/committed artifacts; the third is a spec-of-record that is NOT a durable committed artifact:
|
|
47
|
+
|
|
48
|
+
Durable/committed spec artifacts:
|
|
47
49
|
|
|
48
50
|
- phase-doc-backed local sessions ([Phase Plan](../../docs/phases/phase-x.md) is canonical)
|
|
49
51
|
- tracker-backed local sessions (the tracker issue is canonical)
|
|
50
52
|
|
|
53
|
+
Non-durable spec-of-record (no committed plan artifact):
|
|
54
|
+
|
|
55
|
+
- lightweight PR-body-as-spec sessions (`--lightweight`; the PR description is the canonical spec-of-record but NOT a durable committed artifact — the resolver output carries `canonicalSpecSource: pr_body` and the derived handoff envelope carries `specSource: pr_body`; no phase/plan doc minted or committed). Same gate sequence as the phase-doc path; only the backing artifact differs. See [Artifact Authority Contract](../docs/artifact-authority-contract.md) "Lightweight (PR-body-as-spec)".
|
|
56
|
+
|
|
51
57
|
Tracker-backed local implementation stays inside the existing `local_implementation` path. For sub-issue tree decomposition, see [Sub-Issue Tree Contract](../../docs/sub-issue-tree-contract.md) (this is a source-repo reference; it is not part of the bundled `../docs/` runtime contract surface for installed skill copies). It does not introduce a new routing mode.
|
|
52
58
|
|
|
53
59
|
When the local spec already lives in a tracker issue:
|
|
@@ -130,33 +136,33 @@ Follow [Anti-patterns](../docs/anti-patterns.md) for the general tooling-interna
|
|
|
130
136
|
|
|
131
137
|
Apply [Structural Quality](../docs/structural-quality.md) from the `deep` review angle.
|
|
132
138
|
|
|
133
|
-
## Light mode (small changes)
|
|
139
|
+
## Light mode (small changes)
|
|
140
|
+
|
|
141
|
+
Light mode is wired into gate dispatch. A PR that is **under threshold** (≤ `maxLines` lines changed AND ≤ `maxFiles` files, per `.devloops` field `localImplementation.lightMode` — this repo sets `maxLines: 20`, `maxFiles: 2`; the shipped built-in default has light mode disabled (`enabled: false`); if enabled without explicit values it falls back to `maxFiles: 3` / `maxLines: 200`) AND carries no `gate:full` label collapses BOTH `draft_gate` and `pre_approval_gate` to a single inline single-agent correctness + no-op check. The Copilot review request and its polling are skipped.
|
|
134
142
|
|
|
135
|
-
|
|
143
|
+
**Escalation:**
|
|
144
|
+
1. Auto-escalate — if the inline check surfaces any finding whose severity is in the gate's `blockCleanOnFindingSeverities` (i.e. ≥ `worth-fixing-now`), dispatch escalates to full fan-out.
|
|
145
|
+
2. Label override — the `gate:full` label forces full fan-out regardless of PR size.
|
|
136
146
|
|
|
137
|
-
|
|
147
|
+
The `draft_gate` boundary (draft → ready) is still recorded because `requireDraftFirst` is honored: light mode only changes HOW the gate runs (inline vs fan-out), not WHETHER the draft boundary exists.
|
|
138
148
|
|
|
139
|
-
|
|
149
|
+
Dispatch is resolved deterministically via `resolveGateDispatchMode(config, gate, { scope, hasFullLabel, inlineFindingSeverities })` from `@dev-loops/core/config`. `scripts/loop/resolve-gate-dispatch.mjs` is the CLI wrapper the orchestrator calls, combining `detect-change-scope.mjs` output with the `gate:full` label fact.
|
|
150
|
+
|
|
151
|
+
Use `scripts/loop/detect-change-scope.mjs` to determine scope:
|
|
140
152
|
```sh
|
|
141
153
|
node scripts/loop/detect-change-scope.mjs
|
|
142
154
|
```
|
|
143
155
|
|
|
144
|
-
**Planned light mode path (not yet wired):**
|
|
145
|
-
1. Validation (`npm run verify`)
|
|
146
|
-
2. Single review pass (not multi-angle fan-out)
|
|
147
|
-
3. Pre-approval gate
|
|
148
|
-
4. Finalization
|
|
149
|
-
|
|
150
156
|
**Override threshold:**
|
|
151
157
|
```yaml
|
|
152
158
|
localImplementation:
|
|
153
159
|
lightMode:
|
|
154
160
|
enabled: true
|
|
155
|
-
maxFiles:
|
|
156
|
-
maxLines:
|
|
161
|
+
maxFiles: 2
|
|
162
|
+
maxLines: 20
|
|
157
163
|
```
|
|
158
164
|
|
|
159
|
-
|
|
165
|
+
Scope above threshold falls back to the full fan-out/fan-in path.
|
|
160
166
|
|
|
161
167
|
## Deterministic logging structure
|
|
162
168
|
|
|
@@ -264,6 +270,10 @@ For the **current phase only**, run this loop before implementation.
|
|
|
264
270
|
|
|
265
271
|
### 1. Create or update the durable phase doc and tmp scaffold
|
|
266
272
|
|
|
273
|
+
**Lightweight (PR-body-as-spec) exception:** when the session is lightweight — the resolver output carries `canonicalSpecSource: pr_body` and the derived handoff envelope carries `specSource: pr_body` (started via `resolve-dev-loop-startup.mjs --issue <n> --lightweight`) — SKIP the durable phase-doc mint entirely. The PR description is the spec-of-record; do NOT create or commit any `docs/phases/*.md` for this session. The ephemeral `tmp/phases/` scaffold may still be used for local execution state. All other steps (read prior learning, plan, implement) proceed unchanged, and the gate sequence (draft → pre-approval fanout → detect-evidence → human merge) is identical. See [Artifact Authority Contract](../docs/artifact-authority-contract.md) "Lightweight (PR-body-as-spec)".
|
|
274
|
+
|
|
275
|
+
Otherwise (default phase-doc path), create or update the durable phase doc.
|
|
276
|
+
|
|
267
277
|
Use paths like:
|
|
268
278
|
- `docs/phases/phase-0.md`
|
|
269
279
|
- `tmp/phases/phase-0/`
|
|
@@ -512,7 +522,7 @@ After the phase plan passes review:
|
|
|
512
522
|
3. Run local validation:
|
|
513
523
|
- `npm run verify`
|
|
514
524
|
- when a narrower local check is more honest for the touched slice, say so explicitly and run the narrowest justified subset instead of pretending the full verify path was unnecessary
|
|
515
|
-
- for
|
|
525
|
+
- for a *rendered* HTML artifact (a deck `docs/presentations/*.html`, an article `docs/articles/*.html`, or the inspect-run viewer source) UI e2e coverage is **required and auto-scoped** — not opt-in. Touching one obliges you to register it in the shared harness and have a passing UI e2e suite, or the gate fails closed (see [UI e2e scoping step](../docs/ui-e2e-scoping-step.md)); run the matching shared suite locally, e.g. `npm run test:playwright:intro-deck` / `:intro-article` / `:viewer`. For a bespoke local UI surface that is not a registered rendered artifact, the WebKit smoke seam (fixture-backed Playwright WebKit plus screenshot capture) remains available without being mandatory
|
|
516
526
|
- **Commit immediately after validation passes.** Once `npm run verify` (or the narrowest justified validation subset) is green, stage and commit the current slice of work with an atomic commit message. For tracker-backed sessions, also push the commit. In non-interactive subagent sessions, commit authorization is implicit — the subagent was dispatched to implement and must commit before exiting. In interactive sessions, wait for coordination agent authorization. Do not defer commits — uncommitted changes when the session terminates are a workflow defect.
|
|
517
527
|
4. Review the implementation against the merged phase plan.
|
|
518
528
|
5. Run the default pre-approval gate as a full review / fix loop on the branch before calling it review-complete, approval-ready, merge-ready, or ready for final handoff:
|
|
@@ -643,6 +653,7 @@ See [Stop Conditions](../docs/stop-conditions.md). Local-specific stops: phase c
|
|
|
643
653
|
- PR creation always goes through `dev-loops pr create`, which is ALWAYS draft and always assigned — self-assigned by default (`--assignee @me` when none is given; honors an explicit `--assignee <login>` / `-a <login>`), never via raw `gh pr create`, and the PR body must contain `Closes #N` (or `Fixes #N`) for the linked issue so GitHub auto-closes it on merge. When `.devloops` sets `workflow.requireDraftFirst` to true, use `dev-loops pr create --assignee @me ...`. Do not create a fresh PR directly in ready-for-review state unless the user explicitly overrides that policy for the current PR scope. The draft gate inspection is a real workflow boundary, so a new PR must exist in draft before `gh pr ready` is eligible.
|
|
644
654
|
- When authorization is pending, record the phase as `awaiting-finalization` and describe the exact missing step.
|
|
645
655
|
- For phase-doc-backed sessions, merge the fully reviewed, locally validated branch back into local `main` when authorized.
|
|
656
|
+
- Behind-branch integration before merge (for example a sibling PR merged first and both touch shared files): prefer a merge commit (`git merge origin/main`) over rebase + force-push. Since dev-loop PRs are squash-merged, intermediate branch history is discarded at merge time, so a merge commit lands an identical result on `main` while avoiding a non-fast-forward push to the remote. Force-push (`--force-with-lease` only, never bare `--force`) remains acceptable only for a branch the loop solely owns and where no integration alternative exists (rare); document that carve-out rather than leaving it implicit. After any integration that changes the head SHA, re-verify gate evidence at the new head (existing behavior — see [Merge Preconditions](../docs/merge-preconditions.md#required-before-merge)).
|
|
646
657
|
|
|
647
658
|
## Commit policy
|
|
648
659
|
|