dev-loops 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/dev-loop/defaults.yaml +477 -0
- package/AGENTS.md +25 -0
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/agents/dev-loop.agent.md +82 -0
- package/agents/developer.agent.md +37 -0
- package/agents/docs.agent.md +33 -0
- package/agents/fixer.agent.md +53 -0
- package/agents/quality.agent.md +28 -0
- package/agents/refiner.agent.md +87 -0
- package/agents/review.agent.md +64 -0
- package/cli/index.mjs +424 -0
- package/extension/README.md +233 -0
- package/extension/checks.ts +94 -0
- package/extension/index.ts +131 -0
- package/extension/post-merge-update.ts +512 -0
- package/extension/presentation.ts +107 -0
- package/lib/dev-loops-core.mjs +284 -0
- package/package.json +103 -0
- package/scripts/README.md +1007 -0
- package/scripts/_cli-primitives.mjs +10 -0
- package/scripts/_core-helpers.mjs +30 -0
- package/scripts/docs/validate-links.mjs +567 -0
- package/scripts/docs/validate-no-duplicate-rules.mjs +250 -0
- package/scripts/github/_review-thread-mutations.mjs +214 -0
- package/scripts/github/capture-review-threads.mjs +180 -0
- package/scripts/github/create-draft-pr.mjs +108 -0
- package/scripts/github/detect-checkpoint-evidence.mjs +393 -0
- package/scripts/github/detect-linked-issue-pr.mjs +331 -0
- package/scripts/github/manage-sub-issues.mjs +394 -0
- package/scripts/github/probe-copilot-review.mjs +323 -0
- package/scripts/github/ready-for-review.mjs +93 -0
- package/scripts/github/reconcile-draft-gate.mjs +328 -0
- package/scripts/github/reply-resolve-review-thread.mjs +42 -0
- package/scripts/github/reply-resolve-review-threads.mjs +329 -0
- package/scripts/github/request-copilot-review.mjs +551 -0
- package/scripts/github/resolve-tracker-local-spec.mjs +205 -0
- package/scripts/github/stage-reviewer-draft.mjs +191 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +694 -0
- package/scripts/github/verify-fresh-review-context.mjs +125 -0
- package/scripts/github/write-gate-findings-log.mjs +212 -0
- package/scripts/loop/_checkpoint-io.mjs +55 -0
- package/scripts/loop/_checkpoint-paths.mjs +28 -0
- package/scripts/loop/_handoff-contract.mjs +230 -0
- package/scripts/loop/_inspect-run-viewer-adapter.mjs +345 -0
- package/scripts/loop/_loop-evidence.mjs +32 -0
- package/scripts/loop/_pr-runner-coordination.mjs +611 -0
- package/scripts/loop/_stale-runner-detection.mjs +145 -0
- package/scripts/loop/_steering-state-file.mjs +134 -0
- package/scripts/loop/build-handoff-envelope.mjs +181 -0
- package/scripts/loop/checkpoint-contract.mjs +49 -0
- package/scripts/loop/conductor-monitor.mjs +1850 -0
- package/scripts/loop/conductor.mjs +214 -0
- package/scripts/loop/copilot-pr-handoff.mjs +493 -0
- package/scripts/loop/debt-remediate.mjs +304 -0
- package/scripts/loop/detect-change-scope.mjs +102 -0
- package/scripts/loop/detect-copilot-loop-state.mjs +454 -0
- package/scripts/loop/detect-copilot-session-activity.mjs +186 -0
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +318 -0
- package/scripts/loop/detect-internal-only-pr.mjs +270 -0
- package/scripts/loop/detect-issue-refinement-artifact.mjs +163 -0
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +509 -0
- package/scripts/loop/detect-reviewer-loop-state.mjs +231 -0
- package/scripts/loop/detect-stale-runner.mjs +250 -0
- package/scripts/loop/detect-tracker-first-loop-state.mjs +76 -0
- package/scripts/loop/detect-tracker-pr-state.mjs +102 -0
- package/scripts/loop/info.mjs +267 -0
- package/scripts/loop/inspect-run-viewer/cli.mjs +117 -0
- package/scripts/loop/inspect-run-viewer/constants.mjs +80 -0
- package/scripts/loop/inspect-run-viewer/graph.mjs +757 -0
- package/scripts/loop/inspect-run-viewer/handoff-envelope-renderer.mjs +398 -0
- package/scripts/loop/inspect-run-viewer/inbox.mjs +308 -0
- package/scripts/loop/inspect-run-viewer/managed-instance.mjs +750 -0
- package/scripts/loop/inspect-run-viewer/rendering.mjs +411 -0
- package/scripts/loop/inspect-run-viewer/server.mjs +638 -0
- package/scripts/loop/inspect-run-viewer/shared.mjs +103 -0
- package/scripts/loop/inspect-run-viewer/status.mjs +715 -0
- package/scripts/loop/inspect-run-viewer-ci-changes.mjs +77 -0
- package/scripts/loop/inspect-run-viewer.mjs +82 -0
- package/scripts/loop/inspect-run.mjs +382 -0
- package/scripts/loop/outer-loop.mjs +419 -0
- package/scripts/loop/pr-runner-coordination.mjs +143 -0
- package/scripts/loop/pre-commit-branch-guard.mjs +68 -0
- package/scripts/loop/pre-flight-gate.mjs +236 -0
- package/scripts/loop/pre-pr-ready-gate.mjs +183 -0
- package/scripts/loop/pre-push-main-guard.mjs +103 -0
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +32 -0
- package/scripts/loop/print-gates.mjs +42 -0
- package/scripts/loop/resolve-dev-loop-startup.mjs +533 -0
- package/scripts/loop/run-conductor-cycle.mjs +322 -0
- package/scripts/loop/run-queue.mjs +124 -0
- package/scripts/loop/run-refinement-audit.mjs +513 -0
- package/scripts/loop/run-watch-cycle.mjs +358 -0
- package/scripts/loop/steer-loop.mjs +841 -0
- package/scripts/loop/ui-designer-review-contract.mjs +76 -0
- package/scripts/loop/watch-initial-copilot-pr.mjs +253 -0
- package/scripts/projects/add-queue-item.mjs +528 -0
- package/scripts/projects/ensure-queue-board.mjs +837 -0
- package/scripts/projects/list-queue-items.mjs +489 -0
- package/scripts/projects/move-queue-item.mjs +549 -0
- package/scripts/projects/reorder-queue-item.mjs +518 -0
- package/scripts/refine/_refine-helpers.mjs +258 -0
- package/scripts/refine/prose-linkage-detector.mjs +92 -0
- package/scripts/refine/refinement-completeness-checker.mjs +88 -0
- package/scripts/refine/scope-boundary-cross-checker.mjs +163 -0
- package/scripts/refine/tree-integrity-validator.mjs +211 -0
- package/scripts/refine/verify.mjs +178 -0
- package/scripts/repo-wiki-local.mjs +156 -0
- package/scripts/repo-wiki.mjs +119 -0
- package/skills/copilot-pr-followup/SKILL.md +380 -0
- package/skills/dev-loop/SKILL.md +141 -0
- package/skills/dev-loop/scripts/dev-mode-context.mjs +152 -0
- package/skills/dev-loop/scripts/dev-mode-context.test.mjs +80 -0
- package/skills/dev-loop/scripts/init-phase.mjs +71 -0
- package/skills/dev-loop/scripts/log-bash-exit-1.mjs +25 -0
- package/skills/dev-loop/scripts/phase-files.mjs +29 -0
- package/skills/dev-loop/scripts/post-gate-verdict-fallback.mjs +480 -0
- package/skills/dev-loop/scripts/post-gate-verdict-fallback.test.mjs +732 -0
- package/skills/dev-loop/scripts/render-template.mjs +82 -0
- package/skills/dev-loop/scripts/render-template.test.mjs +63 -0
- package/skills/dev-loop/templates/bootstrap-agents.md +26 -0
- package/skills/dev-loop/templates/bootstrap-implementation-state.md +31 -0
- package/skills/dev-loop/templates/bootstrap-implementation-workflow.md +17 -0
- package/skills/dev-loop/templates/dev-mode-retrospective.md +15 -0
- package/skills/dev-loop/templates/dev-mode-review.md +17 -0
- package/skills/dev-loop/templates/dev-mode-skill-changes.md +11 -0
- package/skills/dev-loop/templates/merged-phase-plan.md +19 -0
- package/skills/dev-loop/templates/phase-doc.md +27 -0
- package/skills/dev-loop/templates/phase-summary.md +13 -0
- package/skills/dev-loop/templates/phase-variant.md +15 -0
- package/skills/dev-loop/templates/retrospective.md +11 -0
- package/skills/dev-loop/templates/review.md +32 -0
- package/skills/dev-loop/templates/ui-vision-review.md +55 -0
- package/skills/docs/acceptance-criteria-verification.md +21 -0
- package/skills/docs/anti-patterns.md +21 -0
- package/skills/docs/artifact-authority-contract.md +119 -0
- package/skills/docs/confirmation-rules.md +28 -0
- package/skills/docs/copilot-ci-status-contract.md +52 -0
- package/skills/docs/copilot-loop-operations.md +233 -0
- package/skills/docs/debt-remediation-contract.md +107 -0
- package/skills/docs/entrypoint-strategies.md +115 -0
- package/skills/docs/epic-tree-refinement-procedure.md +234 -0
- package/skills/docs/issue-intake-procedure.md +235 -0
- package/skills/docs/main-agent-contract.md +72 -0
- package/skills/docs/merge-preconditions.md +29 -0
- package/skills/docs/pr-lifecycle-contract.md +209 -0
- package/skills/docs/public-dev-loop-contract.md +497 -0
- package/skills/docs/retrospective-checkpoint-contract.md +159 -0
- package/skills/docs/stop-conditions.md +29 -0
- package/skills/docs/structural-quality.md +42 -0
- package/skills/docs/tracker-first-loop-state.md +281 -0
- package/skills/docs/validation-policy.md +27 -0
- package/skills/docs/workflow-handoff-contract.md +135 -0
- package/skills/final-approval/SKILL.md +19 -0
- package/skills/local-implementation/SKILL.md +640 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# Public dev-loop contract
|
|
2
|
+
|
|
3
|
+
This document is the canonical authority for the public `dev-loop` entrypoint, its routed semantics, accepted shorthand, and the rule that internal strategy names stay behind that public façade.
|
|
4
|
+
|
|
5
|
+
This canonical owner lives in the shipped `skills/docs/` surface because installed skill/runtime consumers reliably own the skills subtree. In installed layouts, read the same contract via [Public Dev Loop Contract](../docs/public-dev-loop-contract.md) from the installed skill directory.
|
|
6
|
+
|
|
7
|
+
Other repo docs may summarize or link this contract, but they should not redefine it.
|
|
8
|
+
|
|
9
|
+
## Public surface
|
|
10
|
+
|
|
11
|
+
The single public entrypoint is:
|
|
12
|
+
|
|
13
|
+
- `dev-loop`
|
|
14
|
+
|
|
15
|
+
It should be callable from the user-facing workflow surfaces, including:
|
|
16
|
+
|
|
17
|
+
- `subagent dev-loop`
|
|
18
|
+
- `/skill:dev-loop`
|
|
19
|
+
|
|
20
|
+
Day-one user-intent forms:
|
|
21
|
+
|
|
22
|
+
- start dev loop on issue `<n>`
|
|
23
|
+
- continue dev loop on PR `<n>`
|
|
24
|
+
- start issue `<n>` locally
|
|
25
|
+
- start issue `<n>` locally, then continue the loop
|
|
26
|
+
- continue the current dev loop
|
|
27
|
+
- auto dev loop (durable auto ownership over the detected routed loop)
|
|
28
|
+
- auto dev loop on issue `<n>`
|
|
29
|
+
- what state is the dev loop in?
|
|
30
|
+
|
|
31
|
+
Users should not have to choose `dev-loop` vs internal seam names up front.
|
|
32
|
+
|
|
33
|
+
## Issue-based shorthand auto trigger contract
|
|
34
|
+
|
|
35
|
+
This shorthand form is explicitly accepted and resolves to the same bounded public `dev-loop` intent:
|
|
36
|
+
|
|
37
|
+
- `auto dev loop on issue 112`
|
|
38
|
+
|
|
39
|
+
Canonical mapping:
|
|
40
|
+
|
|
41
|
+
| Shorthand phrase | Canonical public intent |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `auto dev loop on issue 112` | `dev-loop --intent auto_continue_current` with authoritative current state targeting issue 112 |
|
|
44
|
+
|
|
45
|
+
Stop-boundary contract for this shorthand:
|
|
46
|
+
|
|
47
|
+
1. continue through the normal GitHub/Copilot loop (assignment, PR watch, draft review/fix, Copilot review/fix, and final pre-approval work) unless a genuine stop condition is reached
|
|
48
|
+
2. stop at the final human approval decision by default
|
|
49
|
+
3. after formal approval, stop again in `waiting_for_merge_authorization` unless merge authorization is explicitly granted for the active issue/PR scope
|
|
50
|
+
4. merge only after explicit merge authorization for the active issue/PR scope
|
|
51
|
+
|
|
52
|
+
## Surfaced-UX deprecation readiness bar
|
|
53
|
+
|
|
54
|
+
Do not remove surfaced internal loop names until all of the following are true:
|
|
55
|
+
|
|
56
|
+
1. authoritative routing is explicit and test-backed
|
|
57
|
+
2. fresh-session startup/resume/status can route from the bounded authoritative startup bundle
|
|
58
|
+
3. former name-shaped variation pressure has a bounded `dev-loop` parameter/settings home
|
|
59
|
+
4. surfaced help/discovery/readiness paths already point users to `dev-loop` and supported routed/parameterized forms
|
|
60
|
+
|
|
61
|
+
Once that bar is met:
|
|
62
|
+
|
|
63
|
+
- `dev-loop` remains the only intended visible workflow entrypoint in surfaced UX
|
|
64
|
+
- internal seam names are removed from surfaced workflow-choice phrasing
|
|
65
|
+
- any remaining seam use stays explicitly internal/runtime-only
|
|
66
|
+
|
|
67
|
+
## Workflow-surface taxonomy and guardrails
|
|
68
|
+
|
|
69
|
+
Use this taxonomy consistently across docs, discovery surfaces, and tests:
|
|
70
|
+
|
|
71
|
+
| Surface class | Entrypoints | Guardrail |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Public workflow entrypoint | `dev-loop` | treat as the default and converging public workflow surface |
|
|
74
|
+
| Internal routed strategy modules | `issue-intake`, `copilot-pr-followup`, `local-implementation`, `final-approval` | keep internal-only behind `dev-loop`; do not expose as executable peer workflow entrypoints |
|
|
75
|
+
| Reusable role agents | `developer`, `docs`, `review`, `fixer`, `quality`, `refiner` | keep framed as reusable building blocks, not peer public workflow entrypoints |
|
|
76
|
+
|
|
77
|
+
Any remaining specialized Copilot behavior stays internal-only behind `dev-loop`.
|
|
78
|
+
|
|
79
|
+
Regression tests must fail if this taxonomy drifts in wording or surfaced entrypoint assets.
|
|
80
|
+
|
|
81
|
+
## Canonical current state
|
|
82
|
+
|
|
83
|
+
The public router consumes one canonical current state with these top-level dimensions:
|
|
84
|
+
|
|
85
|
+
| Field | Meaning |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `target` | active artifact: `issue` \| `pr` \| `local_branch` \| `local_phase`; issue targets may include `linkedPr` when an existing PR is authoritative |
|
|
88
|
+
| `ownership` | durable owner or strategy family currently responsible for the artifact: `local` \| `copilot` \| `external_human` \| `reviewer` \| `maintainer` \| `user` |
|
|
89
|
+
| `nextActor` | immediate actor expected to take the next step; it may differ from `ownership` during review, approval, or handoff states |
|
|
90
|
+
| `status` | `active` \| `waiting` \| `blocked` \| `approval_ready` \| `merge_ready` \| `done` |
|
|
91
|
+
| `authorization` | `authorized` \| `needs_confirmation` \| `not_authorized` |
|
|
92
|
+
|
|
93
|
+
The authoritative first-slice evaluator is:
|
|
94
|
+
|
|
95
|
+
- `packages/core/src/loop/public-dev-loop-routing.mjs`
|
|
96
|
+
|
|
97
|
+
Authoritative status-report helper:
|
|
98
|
+
|
|
99
|
+
- `resolveAuthoritativeDevLoopStatus()` in `packages/core/src/loop/public-dev-loop-routing.mjs`
|
|
100
|
+
|
|
101
|
+
Authoritative startup/resume bundle helper:
|
|
102
|
+
|
|
103
|
+
- `resolveAuthoritativeStartupResumeBundle()` in `packages/core/src/loop/public-dev-loop-routing.mjs`
|
|
104
|
+
|
|
105
|
+
Its tests are:
|
|
106
|
+
|
|
107
|
+
- `packages/core/test/public-dev-loop-routing.test.mjs`
|
|
108
|
+
|
|
109
|
+
## Authoritative-state-first status reporting contract
|
|
110
|
+
|
|
111
|
+
Before answering status/progress/readiness/merge-state/next-step questions, consumers must:
|
|
112
|
+
|
|
113
|
+
1. resolve the authoritative active artifact identity (issue/PR/branch/phase as applicable)
|
|
114
|
+
- for issue targets, this includes authoritative issue↔PR linkage resolution (for example via timeline linkage detection such as `scripts/github/detect-linked-issue-pr.mjs`)
|
|
115
|
+
2. resolve artifact state (`open` \| `closed` \| `merged` \| `not_applicable`)
|
|
116
|
+
3. resolve current loop state
|
|
117
|
+
4. resolve the next action from routed canonical state
|
|
118
|
+
|
|
119
|
+
Prior chat context is only a hint, never state authority.
|
|
120
|
+
|
|
121
|
+
If authoritative identity/state (including issue↔PR linkage when relevant) cannot be resolved confidently, fail closed to reconcile/unknown instead of guessing.
|
|
122
|
+
For async/durable-auto flows, do not claim that `dev-loop` has started or is running unless a visible Pi-managed async run id has also been resolved.
|
|
123
|
+
|
|
124
|
+
When the routed next step requires confirmation for a mutation, the status/startup next action should name that concrete pending mutation (for example issue assignment to `copilot-swe-agent`) instead of generic "approval gate" wording.
|
|
125
|
+
|
|
126
|
+
## Authoritative startup/resume bundle contract
|
|
127
|
+
|
|
128
|
+
Fresh-session `continue`, `inspect`, and status-style paths should compose one bounded authoritative startup/resume bundle from the existing routing/status contract fields.
|
|
129
|
+
|
|
130
|
+
An optional public `intent` may be supplied when the caller needs the bundle to preserve `inspect_state` semantics without re-deriving them in a separate layer.
|
|
131
|
+
|
|
132
|
+
Required authoritative inputs:
|
|
133
|
+
|
|
134
|
+
- `currentState` (`target`, `ownership`, `nextActor`, `status`, `authorization`)
|
|
135
|
+
- optional `intent`
|
|
136
|
+
- when present, it must be a valid public `dev-loop` intent
|
|
137
|
+
- `inspect_state` preserves the bundle's `inspect` route kind and inspect-style next action
|
|
138
|
+
- optional `mode` (`bounded_handoff` \| `durable_auto`)
|
|
139
|
+
- same bounded variation-mode semantics as `evaluatePublicDevLoopRouting`
|
|
140
|
+
- `auto_continue_current` always resolves to `durable_auto`
|
|
141
|
+
- `issueLinkageResolution` (`resolved_linked_pr` \| `resolved_no_open_pr` \| `not_applicable`)
|
|
142
|
+
- required when `currentState.target.kind === issue`
|
|
143
|
+
- `issueReadiness` (`ready` \| `needs_clarification` \| `not_applicable`)
|
|
144
|
+
- required for Copilot-first issue targets with `issueLinkageResolution=resolved_no_open_pr`
|
|
145
|
+
- `issueAssignmentState` (`unassigned` \| `assigned_to_copilot` \| `not_applicable`)
|
|
146
|
+
- required for Copilot-first issue targets with `issueLinkageResolution=resolved_no_open_pr`
|
|
147
|
+
- `artifactState` (`open` \| `closed` \| `merged` \| `not_applicable`)
|
|
148
|
+
- explicit resolved `loopState` (`unknown` is not authoritative input)
|
|
149
|
+
- required for async/durable-auto startup or status paths: `asyncRun`
|
|
150
|
+
- shape: `{ "kind": "pi_managed_run" | "detached_process", "runId": "<visible-run-id>" | null, "processId": 12345 | null, "visible": true|false, "inspectionState"?: "visible" | "hidden" | "stale" | "uninspectable" | "missing" }`
|
|
151
|
+
- durable-auto success requires `kind=pi_managed_run`, a non-empty visible `runId`, and `visible=true`
|
|
152
|
+
- when `inspectionState` is provided as `hidden`, `stale`, or `uninspectable`, durable-auto must fail closed with that state surfaced in diagnostics
|
|
153
|
+
- detached local processes are diagnostic-only evidence and must fail closed instead of being treated as a successful async start
|
|
154
|
+
- when refreshed loop state is `linked_pr_ready_for_followup` for an issue target with a resolved linked PR, startup/resume and status resolution must promote stale bootstrap waiting to the linked PR follow-up path (or fail closed if the linked-PR facts are incomplete/contradictory) instead of preserving the old bootstrap wait route
|
|
155
|
+
- when refreshed loop state is `prior_linked_pr_closed_unmerged` for a Copilot-owned issue target with `issueLinkageResolution=resolved_no_open_pr`, startup/resume and status resolution must fail closed to reconcile instead of treating the issue as a healthy bootstrap wait or fresh issue-intake path
|
|
156
|
+
|
|
157
|
+
Resolved bundle output shape:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"bundleKind": "resolved | needs_reconcile",
|
|
162
|
+
"activeArtifact": {
|
|
163
|
+
"kind": "issue | pr | local_branch | local_phase",
|
|
164
|
+
"issue": 111,
|
|
165
|
+
"pr": null,
|
|
166
|
+
"branch": null,
|
|
167
|
+
"phase": null
|
|
168
|
+
},
|
|
169
|
+
"artifactState": "open | closed | merged | not_applicable",
|
|
170
|
+
"issueLinkageResolution": "resolved_linked_pr | resolved_no_open_pr | not_applicable",
|
|
171
|
+
"issueAssignmentSeam": "needs_refinement | ready_needs_assignment_confirmation | ready_assign_now | assigned_to_copilot | not_applicable",
|
|
172
|
+
"canonicalState": {
|
|
173
|
+
"target": { "kind": "..." },
|
|
174
|
+
"ownership": "...",
|
|
175
|
+
"nextActor": "...",
|
|
176
|
+
"status": "...",
|
|
177
|
+
"authorization": "..."
|
|
178
|
+
},
|
|
179
|
+
"loopState": "...",
|
|
180
|
+
"routeKind": "route | wait | stop | inspect | needs_reconcile",
|
|
181
|
+
"selectedGate": "...",
|
|
182
|
+
"selectedStrategy": "...",
|
|
183
|
+
"executionMode": "bounded_handoff | durable_auto",
|
|
184
|
+
"waitSemantics": "default | auto_healthy_wait",
|
|
185
|
+
"asyncRun": {
|
|
186
|
+
"kind": "pi_managed_run",
|
|
187
|
+
"runId": "run-186",
|
|
188
|
+
"processId": null,
|
|
189
|
+
"visible": true
|
|
190
|
+
},
|
|
191
|
+
"nextAction": "...",
|
|
192
|
+
"reason": "...",
|
|
193
|
+
"contractTrace": {
|
|
194
|
+
"decision": {
|
|
195
|
+
"selectedGate": "...",
|
|
196
|
+
"routeKind": "...",
|
|
197
|
+
"selectedStrategy": "...",
|
|
198
|
+
"executionMode": "...",
|
|
199
|
+
"watchRequested": true,
|
|
200
|
+
"contractClassification": "routed_followup | healthy_wait | terminal | blocked | authorization_gated | reconcile | inspect",
|
|
201
|
+
"contractJustification": "..."
|
|
202
|
+
},
|
|
203
|
+
"waitStrategy": {
|
|
204
|
+
"waitMode": "persistent_watch | not_applicable",
|
|
205
|
+
"timeoutPolicyClassification": "... | null",
|
|
206
|
+
"effectiveTimeoutMs": 3600000,
|
|
207
|
+
"effectivePollIntervalMs": null
|
|
208
|
+
},
|
|
209
|
+
"stopReason": {
|
|
210
|
+
"classification": "routed_followup | healthy_wait | terminal | blocked | authorization_gated | reconcile | inspect",
|
|
211
|
+
"terminal": false,
|
|
212
|
+
"reason": "..."
|
|
213
|
+
},
|
|
214
|
+
"stateRefresh": {
|
|
215
|
+
"boundaryKind": "post_watch_or_probe | startup_resume_refresh | authoritative_status_refresh",
|
|
216
|
+
"refreshRequired": true,
|
|
217
|
+
"refreshReason": "..."
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Dev-mode observability requirement:
|
|
224
|
+
|
|
225
|
+
- saved artifacts must preserve the `contractTrace` decision, wait strategy, state-refresh boundary, and stop classification so a fresh session can explain why a healthy wait re-attached, stopped, or failed closed without replaying the whole run
|
|
226
|
+
- wait/watch artifacts must record the effective timeout budget in force and whether the seam ran as `persistent_watch` or `one_shot_probe`; when a surface does not own a poll interval directly it may record `effectivePollIntervalMs=null` rather than inventing a value
|
|
227
|
+
|
|
228
|
+
Fail-closed semantics:
|
|
229
|
+
|
|
230
|
+
- incomplete/invalid/conflicting startup inputs return:
|
|
231
|
+
- `bundleKind = needs_reconcile`
|
|
232
|
+
- `routeKind = needs_reconcile`
|
|
233
|
+
- `selectedStrategy = none`
|
|
234
|
+
- `loopState = unknown`
|
|
235
|
+
- `nextAction` must instruct reconciliation before routing/status answers
|
|
236
|
+
- `executionMode=durable_auto` must fail closed unless a visible Pi-managed async run is already registered
|
|
237
|
+
- a detached watcher/background pid is never acceptable evidence of async `dev-loop` startup success
|
|
238
|
+
- invalid explicit `intent` also fails closed
|
|
239
|
+
- do not introduce additional public degraded states for this slice
|
|
240
|
+
|
|
241
|
+
Expected answer shape (field names may vary by surface, but semantics must match):
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
Active issue: <owner/repo>#<n> (when applicable)
|
|
245
|
+
Active PR: <owner/repo>#<n> (when applicable)
|
|
246
|
+
Artifact state: open|closed|merged|not_applicable
|
|
247
|
+
Loop state: <resolved loop state>
|
|
248
|
+
Async run: <visible Pi-managed run id>|unknown
|
|
249
|
+
Next action: <resolved next action>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Internal strategy families
|
|
253
|
+
|
|
254
|
+
The public router currently maps to these deterministic internal strategies:
|
|
255
|
+
|
|
256
|
+
| Strategy | Used for | Public workflow entrypoint exposure |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| `local_implementation` | local branch/phase work and explicit local starts | `dev-loop` |
|
|
259
|
+
| `issue_intake` | issue-first normalization/intake before PR follow-up | none (internal-only via `dev-loop` routing; implemented by [Copilot PR Follow-up](../copilot-pr-followup/SKILL.md) + [Copilot Loop Operations](./copilot-loop-operations.md) + [Issue Intake Procedure](./issue-intake-procedure.md)) |
|
|
260
|
+
| `copilot_pr_followup` | Copilot-owned PR follow-up | none (internal-only via `dev-loop` routing) |
|
|
261
|
+
| `external_pr_followup` | external-human contributor PR follow-up | none |
|
|
262
|
+
| `reviewer_fixer` | reviewer/fixer passes on the current PR | none |
|
|
263
|
+
| `wait_watch` | waiting/watch states | `dev-loop` |
|
|
264
|
+
| `final_approval` | approval-ready gate, or merge-ready with explicit merge authorization | none (canonical procedure lives in [Copilot PR Follow-up](../copilot-pr-followup/SKILL.md) + [Copilot Loop Operations](./copilot-loop-operations.md) Human approval checkpoint; [Final Approval](../final-approval/SKILL.md) is a thin redirect) |
|
|
265
|
+
|
|
266
|
+
`waiting_for_merge_authorization` is part of the gate contract below as a stop gate rather than an internal strategy.
|
|
267
|
+
|
|
268
|
+
Internal strategy naming is implementation detail; normal orchestration always starts from `dev-loop`.
|
|
269
|
+
|
|
270
|
+
## Tracker-backed local implementation input-source contract
|
|
271
|
+
|
|
272
|
+
Tracker-backed local implementation is an input-source addition to the existing `local_implementation` strategy. It does **not** create a new routing mode, strategy family, or public workflow entrypoint.
|
|
273
|
+
|
|
274
|
+
For tracker-backed local sessions, the tracker issue is canonical. `docs/phases/phase-<n>.md` must not exist for that same session, and local execution must not maintain a second durable phase-doc copy of the same spec.
|
|
275
|
+
|
|
276
|
+
Deterministic GitHub-backed spec resolution:
|
|
277
|
+
|
|
278
|
+
1. accept either a full GitHub issue URL or an explicit `<owner/name>` + `<number>` tracker reference
|
|
279
|
+
2. parse that reference into one deterministic repo slug + issue number pair
|
|
280
|
+
3. resolve the issue through the bounded GitHub helper path (`scripts/github/resolve-tracker-local-spec.mjs`) or the equivalent `gh issue view <number> --repo <owner/name> --json number,title,body,url,state` call
|
|
281
|
+
4. treat the returned issue `title`, `body`, `url`, and `state` as the usable local spec bundle
|
|
282
|
+
5. if the tracker reference cannot be resolved into one valid issue payload, fail closed instead of guessing or falling back to a duplicate phase doc
|
|
283
|
+
|
|
284
|
+
State-sync expectations for this slice:
|
|
285
|
+
|
|
286
|
+
- local branch state and `tmp/` artifacts remain local execution state
|
|
287
|
+
- durable scope / acceptance / status changes discovered during local execution should sync back to the tracker issue, because the tracker issue remains the canonical spec source for tracker-backed sessions
|
|
288
|
+
- this slice does **not** introduce full bidirectional tracker sync or tracker-provider adapters beyond the bounded GitHub-backed helper path above
|
|
289
|
+
|
|
290
|
+
Non-duplication rule:
|
|
291
|
+
|
|
292
|
+
- do not create, read, or update `docs/phases/phase-<n>.md` for the same tracker-backed session
|
|
293
|
+
- if a duplicate local phase doc already exists for the same tracker-backed session, reconcile that conflict explicitly before continuing; do not silently keep two durable spec surfaces alive
|
|
294
|
+
|
|
295
|
+
## Copilot-first issue-assignment seam (unassigned issues)
|
|
296
|
+
|
|
297
|
+
For Copilot-first issue flows (`currentState.target.kind=issue`, `ownership=copilot`, and no linked PR), orchestration must resolve this seam from authoritative issue facts before follow-up routing:
|
|
298
|
+
|
|
299
|
+
1. `issueReadiness=needs_clarification` → ask clarification questions and stop before assignment (`issueAssignmentSeam=needs_refinement`)
|
|
300
|
+
2. `issueReadiness=ready` + `issueAssignmentState=unassigned` + `authorization=needs_confirmation` → ask for explicit assignment confirmation (`issueAssignmentSeam=ready_needs_assignment_confirmation`)
|
|
301
|
+
3. `issueReadiness=ready` + `issueAssignmentState=unassigned` + `authorization=authorized` → assign `copilot-swe-agent` now before PR/bootstrap/watch follow-up (`issueAssignmentSeam=ready_assign_now`)
|
|
302
|
+
4. `issueReadiness=ready` + `issueAssignmentState=assigned_to_copilot` → assignment seam satisfied; proceed to follow-up (`issueAssignmentSeam=assigned_to_copilot`)
|
|
303
|
+
|
|
304
|
+
Fail closed if those readiness/assignment facts are missing or invalid.
|
|
305
|
+
|
|
306
|
+
## Authoritative gate contract
|
|
307
|
+
|
|
308
|
+
Authoritative route selection is a two-step boundary for this slice:
|
|
309
|
+
|
|
310
|
+
1. resolve one authoritative canonical current state
|
|
311
|
+
2. map that state to one explicit gate, then to the corresponding route/strategy outcome
|
|
312
|
+
|
|
313
|
+
The shared machine-checkable gate contract is exported from `packages/core/src/loop/public-dev-loop-routing.mjs` as `DEV_LOOP_GATE` and `PUBLIC_DEV_LOOP_GATE_CONTRACT`.
|
|
314
|
+
|
|
315
|
+
| Gate | Route kind | Strategy | Meaning |
|
|
316
|
+
|---|---|---|---|
|
|
317
|
+
| `stop_blocked_or_not_authorized` | `stop` | none | blocked or not-authorized canonical state stops for a human decision |
|
|
318
|
+
| `stop_done_terminal` | `stop` | none | done canonical state stops as terminal work |
|
|
319
|
+
| `final_approval` | `route` | `final_approval` | approval-ready canonical state routes to the human approval checkpoint; merge-ready routes here only when merge authorization is explicit; requires explicit current-head `pre_approval_gate` checkpoint verdict evidence — CI green + resolved threads + clean rereview are not sufficient substitutes |
|
|
320
|
+
| `waiting_for_merge_authorization` | `stop` | none | merge-ready canonical state without explicit merge authorization stops and waits for explicit merge authorization |
|
|
321
|
+
| `wait_watch` | `wait` | `wait_watch` | waiting canonical state routes to the shared wait/watch strategy |
|
|
322
|
+
| `local_implementation` | `route` | `local_implementation` | local branch or local phase canonical state stays on local implementation |
|
|
323
|
+
| `issue_intake` | `route` | `issue_intake` | issue canonical state without a linked PR routes to issue intake |
|
|
324
|
+
| `external_pr_followup` | `route` | `external_pr_followup` | external-human PR ownership routes to external PR follow-up |
|
|
325
|
+
| `reviewer_fixer` | `route` | `reviewer_fixer` | reviewer-owned or reviewer-next PR state routes to reviewer/fixer |
|
|
326
|
+
| `copilot_pr_followup` | `route` | `copilot_pr_followup` | Copilot-owned PR state routes to Copilot PR follow-up |
|
|
327
|
+
| `fail_closed_reconcile` | `needs_reconcile` | none | ambiguous, conflicting, or unsupported canonical state fails closed to reconcile |
|
|
328
|
+
|
|
329
|
+
For issue targets, authoritative issue↔PR linkage resolution remains part of state resolution before claiming there is no open linked PR:
|
|
330
|
+
|
|
331
|
+
- when canonical issue state includes `linkedPr`, route selection first uses that linked PR as the authoritative routable artifact
|
|
332
|
+
- when canonical issue state does **not** include `linkedPr`, status/reporting consumers must still require explicit authoritative linkage resolution before asserting there is no open linked PR
|
|
333
|
+
- when authoritative linkage resolves an already-open linked PR, that PR is the only canonical active artifact for the issue during follow-up; startup/status/follow-up must reuse it and fail closed against opening another PR until the prior state is explicitly reconciled
|
|
334
|
+
|
|
335
|
+
## Deterministic routing order
|
|
336
|
+
|
|
337
|
+
First-match-wins routing posture:
|
|
338
|
+
|
|
339
|
+
1. blocked or not-authorized state -> stop and ask for a human decision
|
|
340
|
+
2. done -> terminal stop
|
|
341
|
+
3. merge-ready + `authorization=needs_confirmation` -> `waiting_for_merge_authorization`
|
|
342
|
+
4. approval-ready with explicit current-head `pre_approval_gate` evidence, or merge-ready + `authorization=authorized` with the same evidence -> `final_approval`
|
|
343
|
+
5. waiting -> `wait_watch`
|
|
344
|
+
6. local branch / local phase -> `local_implementation`
|
|
345
|
+
7. issue target with `linkedPr` -> route as the linked PR with the same ownership/actor state
|
|
346
|
+
8. issue target without `linkedPr` -> `issue_intake`
|
|
347
|
+
9. PR owned by external human -> `external_pr_followup`
|
|
348
|
+
10. PR owned by reviewer or next actor reviewer -> `reviewer_fixer`
|
|
349
|
+
11. PR owned by Copilot -> `copilot_pr_followup`
|
|
350
|
+
12. anything else -> fail closed to `needs_reconcile`
|
|
351
|
+
|
|
352
|
+
## Conflict reconciliation path (`CONFLICTING` / `DIRTY`)
|
|
353
|
+
|
|
354
|
+
When an open linked PR reports merge conflict against `main`, treat this as an explicit bounded local-agent reconciliation path, not as a blind merge/update step:
|
|
355
|
+
|
|
356
|
+
1. keep the route at `needs_reconcile` / `fail_closed_reconcile` until reconciliation is complete
|
|
357
|
+
2. before any conflict edit, retrieve authoritative context at minimum:
|
|
358
|
+
- latest `origin/main`
|
|
359
|
+
- current PR head SHA and effective PR diff
|
|
360
|
+
- issue/PR scope and acceptance criteria
|
|
361
|
+
- current-head gate evidence and relevant unresolved review feedback
|
|
362
|
+
- local validation surface for the touched conflict slice
|
|
363
|
+
3. if required authoritative context is missing, stale for the current head, or contradictory, fail closed to reconcile
|
|
364
|
+
4. only when that context is complete for one current head, resolve the conflict locally on the PR branch
|
|
365
|
+
5. after conflict resolution, rerun required local validation, gate checks, and required CI checks for the new head before approval/merge evaluation
|
|
366
|
+
|
|
367
|
+
## `auto dev loop` durable auto contract
|
|
368
|
+
|
|
369
|
+
When the public intent is `auto dev loop`, the router must:
|
|
370
|
+
|
|
371
|
+
1. require canonical current state resolution first
|
|
372
|
+
2. route to the same detected internal strategy as normal state-based routing
|
|
373
|
+
3. mark execution mode as durable auto ownership (`durable_auto`)
|
|
374
|
+
4. keep waiting/watch states in healthy-wait semantics (`auto_healthy_wait`)
|
|
375
|
+
|
|
376
|
+
In healthy waiting states, quiet watcher observations (for example `timeout` or `idle`) are observational only and must not be surfaced as attention by themselves. Escalation is still expected for true blocked/authorization/reconcile states.
|
|
377
|
+
|
|
378
|
+
For the Copilot-first bootstrap seam (`waiting_for_initial_copilot_implementation`), durable-auto ownership must route to the dedicated `watch-initial-copilot-pr.mjs` watcher with its default 1-hour watch budget. Quiet/no-activity observations alone do not eject durable ownership while refreshed authoritative state still resolves `waiting_for_initial_copilot_implementation`; inspect/status intents may still summarize that state and exit normally. This seam has a bootstrap-only exception to the general blocked escalation rule: when the linked PR is still bootstrap-only, approval-gated Actions/Copilot runs in `action_required` (GitHub Actions run conclusion, not a lifecycle state term) are treated as non-blocking observational signals (surfacing as concluded session activity) and do not by themselves force stop/escalation.
|
|
379
|
+
When refreshed authoritative bootstrap state instead resolves `prior_linked_pr_closed_unmerged`, that is not a healthy wait seam: the routed outcome must fail closed to reconcile so status/startup answers surface the prior closed-unmerged PR decision rather than implying normal watch continuity.
|
|
380
|
+
|
|
381
|
+
When that refreshed seam state advances to `linked_pr_ready_for_followup`, durable-auto continuation must re-enter the same linked PR follow-up path. If the follow-up handoff carries `conductorRouting.handoffEnvelope.requiresLocalIsolation=true`, orchestration should continue through an isolated checkout/worktree transition instead of treating that boundary as final completion.
|
|
382
|
+
|
|
383
|
+
Main conductor orchestration must treat non-terminal follow-up/wait states (for example `waiting_for_copilot_review`) as continuation boundaries rather than clean completion. If an async child exits before the requested stop boundary and continuation is feasible, re-dispatch via the main session driver (the subagent exits on external wait; the main session re-dispatches); otherwise surface the concrete blocker.
|
|
384
|
+
|
|
385
|
+
## Internal / external model
|
|
386
|
+
|
|
387
|
+
```mermaid
|
|
388
|
+
flowchart TD
|
|
389
|
+
U[User intent / public dev-loop entrypoint] --> C[Unified dev-loop conductor]
|
|
390
|
+
C --> S[Canonical current state]
|
|
391
|
+
S --> R[Deterministic router]
|
|
392
|
+
|
|
393
|
+
R --> L[Local implementation]
|
|
394
|
+
R --> I[Issue intake / normalization]
|
|
395
|
+
R --> CP[Copilot PR follow-up]
|
|
396
|
+
R --> HP[External-human PR follow-up]
|
|
397
|
+
R --> RF[Reviewer / fixer]
|
|
398
|
+
R --> W[Wait / watch]
|
|
399
|
+
R --> A[Human approval checkpoint]
|
|
400
|
+
R --> M[Wait for merge authorization]
|
|
401
|
+
|
|
402
|
+
L --> S
|
|
403
|
+
I --> S
|
|
404
|
+
CP --> S
|
|
405
|
+
HP --> S
|
|
406
|
+
RF --> S
|
|
407
|
+
W --> S
|
|
408
|
+
A --> S
|
|
409
|
+
M --> S
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Single-entrypoint convergence posture
|
|
413
|
+
|
|
414
|
+
- `dev-loop` is the only intended public workflow entrypoint.
|
|
415
|
+
- any remaining specialized Copilot behavior is internal-only and non-user-invocable behind the canonical internal route-pack surface (`issue-intake`, `copilot-pr-followup`, `local-implementation`, `final-approval`).
|
|
416
|
+
- Documentation and examples should lead with `dev-loop` and explain routed behavior.
|
|
417
|
+
- Almost all workflow branching should converge into deterministic state-machine/tooling surfaces behind `dev-loop`.
|
|
418
|
+
- User-visible variation should be expressed through the external `dev-loop` API / bounded parameters or settings, not by preserving multiple public workflow names or legacy compatibility seams.
|
|
419
|
+
|
|
420
|
+
## Bounded variation parameter contract
|
|
421
|
+
|
|
422
|
+
Supported workflow variations are expressed as `dev-loop` API parameters or settings rather than as new public workflow names.
|
|
423
|
+
Parameters may **steer** `dev-loop`, but must not replace authoritative routing.
|
|
424
|
+
|
|
425
|
+
### Precedence order (highest → lowest)
|
|
426
|
+
|
|
427
|
+
1. **Authoritative current state** — primary source of truth for what artifact/state the loop is actually in
|
|
428
|
+
2. **Explicit user intent / API parameters** — may choose among supported variation modes for the same public entrypoint
|
|
429
|
+
3. **Settings / preferences** — provide defaults only when explicit intent/parameters have not decided
|
|
430
|
+
|
|
431
|
+
Any conflict that would materially change artifact identity, ownership truth, or gate classification **fails closed** rather than being silently resolved by a parameter or preference.
|
|
432
|
+
|
|
433
|
+
### First-slice allowed parameters
|
|
434
|
+
|
|
435
|
+
| Parameter | Allowed values | Behavior |
|
|
436
|
+
|---|---|---|
|
|
437
|
+
| `mode` | `bounded_handoff` (default) \| `durable_auto` | Steers execution mode; `durable_auto` uses the same durable-auto execution-mode semantics as `auto_continue_current`, without replacing the selected intent |
|
|
438
|
+
| `watch` | boolean | Explicitly request wait/watch semantics; fails closed for otherwise-successful non-wait routed results, while preserving authoritative `stop` and `needs_reconcile` outcomes |
|
|
439
|
+
| `intent` | any existing public `dev-loop` intent | Disambiguates the supported public intent; maps to existing contract values |
|
|
440
|
+
| `targetPreference` | `prefer_github_first` (default) \| `prefer_local` | Steers routing preference; must not override authoritative linked-PR or active-artifact truth |
|
|
441
|
+
|
|
442
|
+
The bounded allow-list is exported from `packages/core/src/loop/public-dev-loop-routing.mjs` as `DEV_LOOP_VARIATION_PARAMETER_CONTRACT`.
|
|
443
|
+
|
|
444
|
+
`issueReadiness` and `issueAssignmentState` are **not** part of that bounded variation-parameter allow-list. They are authoritative issue-state facts used only for the Copilot-first unassigned-issue seam during startup/status/routing resolution.
|
|
445
|
+
|
|
446
|
+
### Explicit non-parameters for this slice
|
|
447
|
+
|
|
448
|
+
These must **not** become public variation knobs:
|
|
449
|
+
- arbitrary ownership override for an already-resolved canonical state
|
|
450
|
+
- arbitrary strategy override (e.g. "force copilot-pr-followup")
|
|
451
|
+
- arbitrary gate override (e.g. "skip approval gate")
|
|
452
|
+
- issue↔PR linkage bypass
|
|
453
|
+
- free-form "expert mode" flags that bypass deterministic routing
|
|
454
|
+
|
|
455
|
+
### Fail-closed rules
|
|
456
|
+
|
|
457
|
+
The following parameter/state combinations fail closed to `needs_reconcile` instead of silently coercing:
|
|
458
|
+
|
|
459
|
+
| Conflict | Reason |
|
|
460
|
+
|---|---|
|
|
461
|
+
| `mode=bounded_handoff` + `intent=auto_continue_current` | `auto_continue_current` always requires durable auto execution mode |
|
|
462
|
+
| Unrecognized `mode` value | Value not on the bounded allow-list |
|
|
463
|
+
| Unrecognized `targetPreference` value | Value not on the bounded allow-list |
|
|
464
|
+
| `watch=true` when an otherwise-successful routed result is not wait/watch-eligible | Watch semantics require a routed wait result (`routeKind=wait`), not just `selectedGate=wait_watch`; existing `stop` and `needs_reconcile` outcomes stay authoritative |
|
|
465
|
+
| Non-boolean `watch` value | Value is outside the bounded boolean allow-list and must fail closed |
|
|
466
|
+
| `targetPreference=prefer_local` when authoritative state has a linked PR or active PR artifact | Preference must not override authoritative PR/linked-PR active artifact truth |
|
|
467
|
+
| `mode=durable_auto` without authoritative current state | Durable auto requires authoritative current state to route from |
|
|
468
|
+
|
|
469
|
+
### Representative translations: name-shaped intent → parameterized `dev-loop` form
|
|
470
|
+
|
|
471
|
+
| Formerly name-shaped or prose-shaped | Parameterized single-entrypoint form |
|
|
472
|
+
|---|---|
|
|
473
|
+
| "auto dev loop" | `dev-loop --intent continue_current --mode durable_auto` |
|
|
474
|
+
| "run dev loop on PR 88 and stay on it" | `dev-loop --intent continue_on_pr --target pr:88 --watch` |
|
|
475
|
+
| "prefer the local path for issue 42" | `dev-loop --intent start_on_issue --target issue:42 --target-preference prefer_local` |
|
|
476
|
+
| "just inspect current state" | `dev-loop --intent inspect_state` |
|
|
477
|
+
|
|
478
|
+
These are parameterized uses of `dev-loop`, not new workflow-facing entrypoints.
|
|
479
|
+
|
|
480
|
+
## Non-goals for this slice
|
|
481
|
+
|
|
482
|
+
- broad deletion of lower-level helper logic that `dev-loop` still routes to internally
|
|
483
|
+
- flattening actor/ownership differences between local, Copilot, reviewer, maintainer, and external-human paths
|
|
484
|
+
- replacing existing lower-level state machines with prompt-only branching
|
|
485
|
+
- wiring every runtime helper through this façade in one change
|
|
486
|
+
- broad UI work outside the public workflow/API unification
|
|
487
|
+
|
|
488
|
+
## Example mappings
|
|
489
|
+
|
|
490
|
+
| User intent | Canonical state / route |
|
|
491
|
+
|---|---|
|
|
492
|
+
| start dev loop on issue `86` with no linked PR | synthesize issue target -> `issue_intake` internal strategy (routed behind `dev-loop`) |
|
|
493
|
+
| start dev loop on issue `86` with linked PR `88` and Copilot ownership | issue target + `linkedPr=88` -> route as PR `88` -> `copilot_pr_followup` internal strategy (routed behind `dev-loop`) |
|
|
494
|
+
| continue dev loop on PR `88` with Copilot ownership | PR target + `ownership=copilot` -> `copilot_pr_followup` internal strategy (routed behind `dev-loop`) |
|
|
495
|
+
| start issue `86` locally, then continue the loop | local phase slice for issue `86` -> `local_implementation`, then resume via public `dev-loop` against the updated state |
|
|
496
|
+
| continue the current dev loop while waiting | same target + `status=waiting` -> `wait_watch` |
|
|
497
|
+
| what state is the dev loop in? | inspect the canonical state and report the routed internal strategy without switching public entrypoints |
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Retrospective checkpoint contract
|
|
2
|
+
|
|
3
|
+
This document defines the enforcement seam for the post-run behavioral retrospective checkpoint after qualifying async `dev-loop` completions in this repository. Whether a missing checkpoint blocks the next qualifying start/resume is controlled by `.devloops` at repo root `workflow.requireRetrospective`; shipped defaults remain permissive and this repo opts in.
|
|
4
|
+
|
|
5
|
+
## Relationship to formal dev mode
|
|
6
|
+
|
|
7
|
+
Formal local dev mode and the required post-run behavioral retrospective are related but distinct:
|
|
8
|
+
|
|
9
|
+
| Requirement | Scope |
|
|
10
|
+
|---|---|
|
|
11
|
+
| **Formal local dev mode** | Local implementation/self-improvement work; explicitly scoped in [Dev Loop Skill](../dev-loop/SKILL.md) |
|
|
12
|
+
| **Required post-run behavioral retrospective** | Every qualifying async GitHub-first `dev-loop` completion in this repo |
|
|
13
|
+
|
|
14
|
+
Routed GitHub-first async `dev-loop` runs do **not** need to be in full formal local dev mode. When `workflow.requireRetrospective` is enabled, they **do** require the retrospective checkpoint before the next qualifying start/resume.
|
|
15
|
+
|
|
16
|
+
## Qualifying completions
|
|
17
|
+
|
|
18
|
+
A qualifying async `dev-loop` completion is one that:
|
|
19
|
+
- routes through a GitHub-first Copilot-owned strategy gate, and
|
|
20
|
+
- has `routeKind === "route"` (inspect/status-only results do not qualify).
|
|
21
|
+
|
|
22
|
+
Qualifying gates:
|
|
23
|
+
|
|
24
|
+
| Gate | Strategy | Description |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `copilot_pr_followup` | Copilot PR follow-up | Primary routed GitHub-first async path |
|
|
27
|
+
| `issue_intake` | Issue intake | Copilot-first issue assignment path |
|
|
28
|
+
|
|
29
|
+
The authoritative classification function is `isQualifyingAsyncCompletion(routingResult)` in `packages/core/src/loop/retrospective-checkpoint.mjs`.
|
|
30
|
+
|
|
31
|
+
## Checkpoint states
|
|
32
|
+
|
|
33
|
+
A fresh session can determine the status of the required retrospective by reading `.pi/dev-loop-retrospective-checkpoint.json`:
|
|
34
|
+
|
|
35
|
+
| File state | Mapped checkpoint state | Meaning |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| File absent | `RETROSPECTIVE_CHECKPOINT_STATE.NONE` | No qualifying completion has occurred; no requirement |
|
|
38
|
+
| `{ "state": "required" }` | `RETROSPECTIVE_CHECKPOINT_STATE.MISSING` | Qualifying completion detected; retrospective pending |
|
|
39
|
+
| `{ "state": "complete" }` | `RETROSPECTIVE_CHECKPOINT_STATE.COMPLETE` | Retrospective recorded; requirement satisfied |
|
|
40
|
+
| `{ "state": "skipped" }` | `RETROSPECTIVE_CHECKPOINT_STATE.SKIPPED` | Explicitly skipped with reason; requirement satisfied |
|
|
41
|
+
|
|
42
|
+
## Enforcement gate
|
|
43
|
+
|
|
44
|
+
The enforcement seam is the pure function `evaluateRetrospectiveGate` in `packages/core/src/loop/retrospective-checkpoint.mjs`. The checkpoint artifact may still exist even when enforcement is disabled; callers must first consult `workflow.requireRetrospective` to decide whether the checkpoint should block the next qualifying routed start/resume or remain advisory-only.
|
|
45
|
+
|
|
46
|
+
For convenience, the public routing helpers in `packages/core/src/loop/public-dev-loop-routing.mjs` also accept an optional `retrospectiveCheckpointState` input and apply the same gate internally before returning routed start/resume/status results. Callers should only pass that input when `workflow.requireRetrospective` is enabled for the active repo/workflow posture.
|
|
47
|
+
|
|
48
|
+
### Inputs
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
evaluateRetrospectiveGate({
|
|
52
|
+
checkpointState, // one of RETROSPECTIVE_CHECKPOINT_STATE
|
|
53
|
+
proposedRouting, // result from evaluatePublicDevLoopRouting()
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Outputs
|
|
58
|
+
|
|
59
|
+
- **Pass-through** (proposed routing returned unchanged) when:
|
|
60
|
+
- `checkpointState` is `none`, `complete`, or `skipped`
|
|
61
|
+
- `proposedRouting` is already `stop`, `needs_reconcile`, or `inspect`
|
|
62
|
+
- **Fail-closed** (`needs_reconcile` result) when:
|
|
63
|
+
- `checkpointState` is `missing`
|
|
64
|
+
- `checkpointState` is unrecognized
|
|
65
|
+
|
|
66
|
+
### Caller contract
|
|
67
|
+
|
|
68
|
+
Callers have two supported integration options:
|
|
69
|
+
|
|
70
|
+
#### Option A — direct public-routing helper integration (preferred)
|
|
71
|
+
|
|
72
|
+
1. Read `.pi/dev-loop-retrospective-checkpoint.json` (if it exists).
|
|
73
|
+
2. Map the file contents to a `RETROSPECTIVE_CHECKPOINT_STATE` value.
|
|
74
|
+
3. Pass that value as `retrospectiveCheckpointState` to one of:
|
|
75
|
+
- `evaluatePublicDevLoopRouting(...)`
|
|
76
|
+
- `resolveAuthoritativeStartupResumeBundle(...)`
|
|
77
|
+
- `resolveAuthoritativeDevLoopStatus(...)`
|
|
78
|
+
4. Use the returned result directly. When enforcement is enabled and the checkpoint is missing, these helpers fail closed to `needs_reconcile`.
|
|
79
|
+
|
|
80
|
+
#### Option B — explicit manual gate composition
|
|
81
|
+
|
|
82
|
+
1. Read `.pi/dev-loop-retrospective-checkpoint.json` (if it exists).
|
|
83
|
+
2. Map the file contents to a `RETROSPECTIVE_CHECKPOINT_STATE` value.
|
|
84
|
+
3. Call `evaluatePublicDevLoopRouting(...)` to get the proposed routing.
|
|
85
|
+
4. Call `evaluateRetrospectiveGate({ checkpointState, proposedRouting })`.
|
|
86
|
+
5. Use the gate result (not the raw routing result) as the effective routing decision when enforcement is enabled; otherwise keep the raw routing result and treat the checkpoint artifact as advisory context only.
|
|
87
|
+
|
|
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
|
+
|
|
90
|
+
## Merge gate (`requireRetrospectiveGate`)
|
|
91
|
+
|
|
92
|
+
When `workflow.requireRetrospectiveGate` is enabled in `.devloops` at repo root, merge-ready progression is blocked after `pre_approval_gate` unless the retrospective checkpoint:
|
|
93
|
+
- has `state: "complete"`
|
|
94
|
+
- includes a `behavioralReview` with `mergeApproved: true`, `followedWorkingAgreement` (boolean), `gateQualityAcceptable` (boolean), and `drifts` (array)
|
|
95
|
+
- includes `mergeRecommendation` (non-empty string)
|
|
96
|
+
|
|
97
|
+
The enforcement function is `evaluateRetrospectiveMergeApproval(checkpoint)` in `packages/core/src/loop/pr-gate-coordination.mjs`, called from `evaluatePrGateCoordination` at each merge-ready boundary.
|
|
98
|
+
|
|
99
|
+
### Merge gate states
|
|
100
|
+
|
|
101
|
+
| Retrospective state | Merge gate result |
|
|
102
|
+
|---|---|
|
|
103
|
+
| No checkpoint file | Blocked: `retrospective_gate_pending` |
|
|
104
|
+
| `state: "complete"` with `mergeApproved: true` and valid fields | Allowed: proceeds to `FINAL_APPROVAL_READY` |
|
|
105
|
+
| `state: "complete"` without `mergeApproved: true` | Blocked |
|
|
106
|
+
| Missing required fields (`followedWorkingAgreement`, `gateQualityAcceptable`, `drifts`, `mergeRecommendation`) | Blocked |
|
|
107
|
+
| `state: "skipped"` or `state: "required"` | Blocked |
|
|
108
|
+
|
|
109
|
+
### Configuration
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
# .devloops at repo root
|
|
113
|
+
workflow:
|
|
114
|
+
requireRetrospective: true # startup/resume gate
|
|
115
|
+
requireRetrospectiveGate: true # merge gate after pre_approval_gate
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Durable artifact format
|
|
119
|
+
|
|
120
|
+
The checkpoint file is written by `.pi/extensions/dev-loop-behavioral-review.ts` when it observes the standard async `dev-loop` completion message. The extension trigger is message-based; qualifying-path policy is enforced by the checkpoint gate and repo contract, not by deep route inspection in the extension itself:
|
|
121
|
+
|
|
122
|
+
### On observed async dev-loop completion message (written automatically by extension)
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"state": "required",
|
|
127
|
+
"triggeredAt": "2026-05-29T16:00:00.000Z"
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### After retrospective is done (written by operator or skill)
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"state": "complete",
|
|
136
|
+
"completedAt": "2026-05-29T16:30:00.000Z",
|
|
137
|
+
"notes": "Loop followed working agreement; minor drift on thread resolution."
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Explicit skip with reason
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"state": "skipped",
|
|
146
|
+
"skippedAt": "2026-05-29T16:30:00.000Z",
|
|
147
|
+
"reason": "Trivial documentation-only change; no post-run audit needed."
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Authoritative source locations
|
|
152
|
+
|
|
153
|
+
| Artifact | Location |
|
|
154
|
+
|---|---|
|
|
155
|
+
| Checkpoint state machine | `packages/core/src/loop/retrospective-checkpoint.mjs` (internal core module; not part of the public package exports surface) |
|
|
156
|
+
| Tests | `packages/core/test/retrospective-checkpoint.test.mjs` |
|
|
157
|
+
| Extension (writes required marker, fires review prompt) | `.pi/extensions/dev-loop-behavioral-review.ts` |
|
|
158
|
+
| Checkpoint file | `.pi/dev-loop-retrospective-checkpoint.json` |
|
|
159
|
+
| AGENTS.md repo contract | [Agent Instructions](../../AGENTS.md) — concise repo contract and working rules |
|