dev-loops 0.6.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/{auto.md → loop-auto.md} +1 -1
- 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/{info.md → loop-info.md} +2 -2
- package/.claude/commands/loop-queue-status.md +24 -0
- package/.claude/commands/{start-spike.md → loop-start-spike.md} +1 -1
- package/.claude/commands/{start.md → loop-start.md} +1 -1
- package/.claude/commands/{status.md → loop-status.md} +1 -1
- package/.claude/hooks/_bash-command-classify.mjs +333 -29
- package/.claude/hooks/_hook-decisions.mjs +138 -15
- 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 +15 -11
- 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/retrospective-checkpoint-contract.md +90 -76
- package/.claude/skills/docs/ui-e2e-scoping-step.md +32 -0
- package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
- package/.claude/skills/local-implementation/SKILL.md +25 -14
- package/.claude/skills/loop-grill/SKILL.md +163 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +95 -0
- package/README.md +9 -9
- package/agents/dev-loop.agent.md +3 -2
- 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 -42
- package/extension/README.md +4 -4
- package/extension/checks.ts +1 -5
- package/extension/harness-types.ts +1 -0
- package/extension/index.ts +6 -0
- package/extension/pi-extension-adapter.ts +2 -0
- package/extension/presentation.ts +7 -19
- package/package.json +9 -10
- package/scripts/github/capture-review-threads.mjs +2 -9
- package/scripts/github/comment-issue.mjs +2 -9
- 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 +2 -9
- package/scripts/github/list-issues.mjs +2 -9
- 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/_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 +149 -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 +8 -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 +108 -46
- 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 +4 -8
- 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 +16 -2
- package/skills/copilot-pr-followup/SKILL.md +24 -12
- package/skills/dev-loop/SKILL.md +10 -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/retrospective-checkpoint-contract.md +90 -76
- package/skills/docs/ui-e2e-scoping-step.md +32 -0
- package/skills/docs/workflow-handoff-contract.md +39 -0
- package/skills/local-implementation/SKILL.md +25 -14
- package/skills/loop-grill/SKILL.md +165 -0
- package/.claude/commands/continue.md +0 -15
- 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
|
@@ -10,7 +10,7 @@ Other repo docs may summarize or link this contract, but they should not redefin
|
|
|
10
10
|
|
|
11
11
|
dev-loops supports two mutually exclusive artifact authority modes. Every work item originates from exactly one authoritative artifact: a GitHub issue or a persisted markdown plan file. Work originates from a PR or a direct local change only when explicitly requested.
|
|
12
12
|
|
|
13
|
-
The shipped extension default selects local-planning; see [Shipped default posture](#shipped-default-posture) below. The mode names that follow describe the two tiers; "default" in their headings refers to the
|
|
13
|
+
The shipped extension default selects local-planning; see [Shipped default posture](#shipped-default-posture) below. The mode names that follow describe the two tiers; "default" in their headings refers to the local-first code-level default in `BUILT_IN_DEFAULTS`.
|
|
14
14
|
|
|
15
15
|
### Tracker-first
|
|
16
16
|
|
|
@@ -25,6 +25,7 @@ Key contract:
|
|
|
25
25
|
- GitHub issue state is authoritative — not local notes or chat context
|
|
26
26
|
- A linked PR is the single canonical follow-up artifact for the issue
|
|
27
27
|
- When an open linked PR exists, reuse it rather than opening another
|
|
28
|
+
- Follow-ups discovered while working a PR/loop are noted on the originating issue or PR body by default; a standalone issue is filed only when the follow-up is genuinely independent of the PR and outlives it (see [Sub-Issue Tree Contract](../../docs/sub-issue-tree-contract.md))
|
|
28
29
|
- Implementation may proceed through either the GitHub-first routed path or the local implementation strategy (see [Public Dev Loop Contract](public-dev-loop-contract.md) `targetPreference`)
|
|
29
30
|
|
|
30
31
|
### Local-planning
|
|
@@ -41,12 +42,29 @@ Key contract:
|
|
|
41
42
|
- GitHub issues may still be used for tracking or linking, but the plan file is authoritative for scope and acceptance criteria
|
|
42
43
|
- A tracker-backed local implementation session (GitHub issue as canonical spec) must not also maintain a duplicate `docs/phases/phase-<n>.md` — see [Public Dev Loop Contract](public-dev-loop-contract.md) "Tracker-backed local implementation input-source contract"
|
|
43
44
|
|
|
45
|
+
### Lightweight (PR-body-as-spec)
|
|
46
|
+
|
|
47
|
+
**The PR description itself is the authoritative artifact store — no committed plan artifact.** This is a lightweight modifier on the local `--issue` path (`resolve-dev-loop-startup.mjs --issue <n> --lightweight`, `canonicalSpecSource: pr_body`), not a settings-level mode. No phase/plan doc is minted or committed; the PR body carries the spec-of-record invariants directly. The gate sequence is identical to the phase-doc path (draft → pre-approval fanout → detect-evidence → human merge); only the backing artifact differs (PR body vs phase doc).
|
|
48
|
+
|
|
49
|
+
Artifacts:
|
|
50
|
+
- **Planning + execution artifact:** the GitHub PR — its description is the spec, its diff is the execution
|
|
51
|
+
- **No committed plan doc:** no `docs/phases/*.md` is created for the session
|
|
52
|
+
|
|
53
|
+
Key contract:
|
|
54
|
+
- The PR body must carry the same invariants a durable spec would: **Objective/why, in-scope + explicit non-goals, testable acceptance criteria, definition of done, open questions/risks**. `scripts/loop/validate-pr-body-spec.mjs` (reusing the generic markdown logic of `@dev-loops/core/loop/issue-refinement-artifact`, `validatePrBodySpec`) validates these and **fails closed** with a distinct `missing_*` reason per absent invariant.
|
|
55
|
+
- The PR body MUST also carry the `Closes #N` linkage (GitHub's other closing keywords count too); `validate-pr-body-spec` fails closed with `missing_closing_issue_reference` without it, and with `closes_wrong_issue` when an `--expected-issue` is given and doesn't match — the lightweight path's issue-tracking state must never silently diverge from PR state (issue #1181).
|
|
56
|
+
- This flips the promotion invariant below (P4, "the PR body carries the committed plan-doc **path**"): under lightweight there is no committed plan doc — the PR body **is** the spec, not a pointer to one.
|
|
57
|
+
- The explicit `--lightweight` flag is the primary, deterministic trigger. The secondary heuristic (chore/fix commit type + no `--plan-file` + small change) is a documented manual signal for when to reach for the flag; it is not an automatic selector.
|
|
58
|
+
- `--lightweight` is rejected when combined with `--plan-file` (they are opposites: `--plan-file` commits a durable plan doc as the spec, `--lightweight` makes the PR body the spec) and only composes with `--issue`.
|
|
59
|
+
- Pre-approval acceptance-criteria verification reads the AC/DoD/invariants directly from the PR body rather than a linked issue body; see [Acceptance Criteria Verification](acceptance-criteria-verification.md).
|
|
60
|
+
|
|
44
61
|
### Mode selection table
|
|
45
62
|
|
|
46
63
|
| Mode | Canonical artifact | GitHub issue required | Settings value |
|
|
47
64
|
|---|---|---|---|
|
|
48
65
|
| Tracker-first | GitHub issue | Yes | `strategy.default: github-first` |
|
|
49
66
|
| Local-planning (shipped default) | Markdown plan file | No | `strategy.default: local-first` |
|
|
67
|
+
| Lightweight (PR-body-as-spec) | GitHub PR description | Yes (`--issue`) | modifier: `--lightweight` (`canonicalSpecSource: pr_body`) |
|
|
50
68
|
|
|
51
69
|
`inputSource.default` further disambiguates local-first startup:
|
|
52
70
|
| inputSource | Meaning |
|
|
@@ -81,7 +99,7 @@ The `inputSource.default` key disambiguates local-first startup:
|
|
|
81
99
|
|
|
82
100
|
The effective default for a consumer comes from the config-merge layering in `packages/core/src/config/config.mjs`. Precedence, low to high:
|
|
83
101
|
|
|
84
|
-
1. `BUILT_IN_DEFAULTS` (frozen in `config.mjs`) — `strategy.default:
|
|
102
|
+
1. `BUILT_IN_DEFAULTS` (frozen in `config.mjs`) — `strategy.default: local-first`. This is the code-level fallback when no other layer sets the key.
|
|
85
103
|
2. Extension-packaged defaults (`packages/core/src/config/extension-defaults.yaml`, loaded as the `extensionDefaults` layer) — `strategy.default: local-first`. This is the opinion the package ships and the layer that wins over the built-in fallback.
|
|
86
104
|
3. Repo-local `.pi/dev-loop/defaults.*` (legacy) — applied when present.
|
|
87
105
|
4. Repo `.devloops` at repo root — the per-repo override, highest precedence. When `.devloops` is absent, the legacy `.pi/dev-loop/settings.*` / `overrides.*` apply at this position instead.
|
|
@@ -95,7 +113,7 @@ Two legacy repo-local layers also exist under `.pi/dev-loop/` (the package no lo
|
|
|
95
113
|
These are not valid artifact authority mode selectors:
|
|
96
114
|
- `strategy.default: copilot` — not a valid mode; the enum accepts only `github-first` or `local-first` (`packages/core/src/config/config.mjs`)
|
|
97
115
|
- Free-form string values — fail closed
|
|
98
|
-
- Omitting `strategy.default` from every layer — resolves to `
|
|
116
|
+
- Omitting `strategy.default` from every layer — resolves to `local-first` from `BUILT_IN_DEFAULTS`
|
|
99
117
|
|
|
100
118
|
## Local-first plan-file flow end to end
|
|
101
119
|
|
|
@@ -103,7 +121,7 @@ Under local-planning, one plan file moves through four stages. Each stage has a
|
|
|
103
121
|
|
|
104
122
|
### P1 — Plan-file artifact + config (#949)
|
|
105
123
|
|
|
106
|
-
The plan file is a phase-doc-format markdown document.
|
|
124
|
+
The plan file is a phase-doc-format markdown document. It lives under `docs/phases/`, the existing phase-docs directory. Its required base authoring sections — `## Status`, `## Objective`, `## In scope`, `## Explicit non-goals` — and the validator `scripts/refine/validate-plan-file.mjs` (`validatePlanFile`, distinct `missing_*` codes per absent or empty section) are defined in the [Plan-file Contract](plan-file-contract.md).
|
|
107
125
|
|
|
108
126
|
### P2 — Intake (#950)
|
|
109
127
|
|
|
@@ -188,7 +188,7 @@ Preferred defaults for this repo:
|
|
|
188
188
|
- parent/subagent no-activity threshold for watcher-style runs: at least **15 minutes**
|
|
189
189
|
- active-long-running notice threshold for watcher-style runs: about **30 minutes**
|
|
190
190
|
|
|
191
|
-
These are the defaults built into `probe-copilot-review.mjs`, `run-watch-cycle.mjs`, and the `watchArgs` emitted by `copilot-pr-handoff.mjs`. Do not pass removed CLI policy flags (`--poll-interval-ms`, `--
|
|
191
|
+
These are the defaults built into `probe-copilot-review.mjs`, `run-watch-cycle.mjs`, and the `watchArgs` emitted by `copilot-pr-handoff.mjs`. Do not pass removed CLI policy flags (`--poll-interval-ms`, `--probe-only`): the scripts reject them as usage errors, but do not rely on that as a guaranteed hard stop in the `dev-loops gate …` path — the CLI retry-wrapper (`buildCorrectedArgs`) may strip an unrecognized/removed flag and silently retry with defaults, so passing one can quietly fall back to default behavior rather than failing. `probe-copilot-review.mjs` does accept `--timeout-ms` (watch budget in ms; `0` = single immediate idle check, no watch); timeouts and intervals are otherwise derived from `packages/core/src/loop/policy-constants.mjs`.
|
|
192
192
|
|
|
193
193
|
### Outer-loop checkpoint: canonical re-attachment artifact
|
|
194
194
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Cross-Harness Regression Contract
|
|
2
|
+
|
|
3
|
+
Canonical owner for harness-agnostic non-regression across Pi and Claude Code.
|
|
4
|
+
|
|
5
|
+
## What counts as a harness-specific change
|
|
6
|
+
|
|
7
|
+
A change is harness-specific when it touches a seam that branches by runtime harness (Pi vs Claude Code), including:
|
|
8
|
+
|
|
9
|
+
- Runner coordination (`scripts/loop/pr-runner-coordination.mjs`, `scripts/loop/_pr-runner-coordination.mjs`)
|
|
10
|
+
- Run-id injection / async-start semantics (`packages/core/src/loop/async-start-contract.mjs`, `packages/core/src/loop/run-context.mjs`)
|
|
11
|
+
- Subagent / Task fan-out tool provisioning
|
|
12
|
+
- Harness-specific environment variables (`PI_SUBAGENT_RUN_ID`, `DEVLOOPS_RUN_ID`)
|
|
13
|
+
- Session/intercom wiring
|
|
14
|
+
- TUI behavior
|
|
15
|
+
- Any capability detection or `process.env` branch whose outcome differs per harness
|
|
16
|
+
|
|
17
|
+
## Required cross-harness test coverage
|
|
18
|
+
|
|
19
|
+
Every harness-specific change MUST add or keep a test that exercises the path of the harness it does not directly target. A pull request that alters behavior on one harness without any test evidence for the other harness fails this contract.
|
|
20
|
+
|
|
21
|
+
## The additive/no-op bar
|
|
22
|
+
|
|
23
|
+
State the bar symmetrically, in both directions:
|
|
24
|
+
|
|
25
|
+
- A change additive on Pi MUST be a no-op on Claude Code, or its effect on Claude Code MUST be explicitly validated by a test.
|
|
26
|
+
- A change additive on Claude Code MUST be a no-op on Pi, or its effect on Pi MUST be explicitly validated by a test.
|
|
27
|
+
|
|
28
|
+
Definitions:
|
|
29
|
+
|
|
30
|
+
- **No-op**: the existing test suite for the other harness passes unmodified — no assertion needed to change, no new test required, because the seam does not fire for that harness.
|
|
31
|
+
- **Explicitly validated**: a new or updated test directly asserts the other harness's resulting behavior (not merely "did not throw"), covering both the case where the seam is inert and the case where it participates.
|
|
32
|
+
|
|
33
|
+
## Claude-Code-path test inventory
|
|
34
|
+
|
|
35
|
+
When a change targets a Claude-Code-specific seam, the following suites are the baseline that MUST stay green, and are the first place to add coverage for a Pi-originated change that must not regress Claude Code:
|
|
36
|
+
|
|
37
|
+
- `npm run test:assets` — runs `test/contracts/*.test.mjs`, including `test/contracts/claude-assets-reproducible.test.mjs`, `test/contracts/claude-headless-smoke.test.mjs`, `test/contracts/claude-plugin-manifest.test.mjs`, `test/contracts/claude-plugin-marketplace.test.mjs`, `test/contracts/claude-hooks-settings.test.mjs`, and `test/contracts/cli-harness-agnostic.test.mjs`
|
|
38
|
+
- `npm run smoke:headless` — runs `scripts/claude/headless-info-smoke.mjs`; this is a manual/local-only check, not part of `npm test` or `npm run verify` and not run by CI — run it directly when a change touches Claude Code headless-info behavior
|
|
39
|
+
- `npm run test:core` — runs `packages/core/test/*.test.mjs`, including `packages/core/test/claude-headless-entry.test.mjs`, `packages/core/test/claude-hook-decisions.test.mjs`, `packages/core/test/run-context.test.mjs`, `packages/core/test/async-start-contract.test.mjs`
|
|
40
|
+
|
|
41
|
+
Symmetrically, for a change targeting the Claude-Code-specific seam that must not regress Pi, `npm run test:extension` (which runs `test/extension-*.test.mjs`, including `test/extension-pi-adapter.test.mjs`, the Pi-side adapter inventory) is the baseline suite to check and extend.
|
|
42
|
+
|
|
43
|
+
## How to add coverage
|
|
44
|
+
|
|
45
|
+
1. Identify the harness-specific seam the change touches (see the surface list above).
|
|
46
|
+
2. Locate the existing test file that already covers that seam for the harness being changed, and its counterpart for the other harness (from the inventory above, or the nearest analogous file).
|
|
47
|
+
3. Add or extend a test asserting the other harness's behavior directly — do not rely on an untested assumption that the change is inert there.
|
|
48
|
+
4. Run the relevant suite locally (`npm run test:assets`, `npm run test:core`, `npm run test:extension`, or `npm run smoke:headless` as applicable) before opening the pull request.
|
|
49
|
+
|
|
50
|
+
## Non-goals
|
|
51
|
+
|
|
52
|
+
- No new or expanded CI matrix: `npm run verify` (which CI runs on every pull request) already covers `test:assets`, `test:extension`, `test:scripts`, `test:core`, `test:docs`, and `test:dev-loop` — every suite named above except `npm run smoke:headless`, which is manual/local-only by design (see the test inventory above).
|
|
53
|
+
- No bespoke cross-harness enforcement script: naming the required suites here, combined with CI already running them on every pull request, satisfies the coverage bar.
|
|
54
|
+
- No rewriting of existing harness-specific code as part of adopting this contract.
|
|
55
|
+
|
|
56
|
+
## Cross-references
|
|
57
|
+
|
|
58
|
+
- [AGENTS.md](../../AGENTS.md)
|
|
59
|
+
- [Dev Loop Skill](../dev-loop/SKILL.md)
|
|
60
|
+
- [Public Dev Loop Contract](public-dev-loop-contract.md)
|
|
@@ -111,6 +111,7 @@ Preflight verdicts:
|
|
|
111
111
|
- do not rely only on PR title/body containing a literal issue number
|
|
112
112
|
- treat an open linked PR as the active implementation for this issue
|
|
113
113
|
- once an open linked PR exists, that PR is the only canonical follow-up artifact for the issue; attach follow-up work to it and do not open another PR unless the prior PR was explicitly superseded and reconciled first
|
|
114
|
+
- **follow-up-capture rule:** when a follow-up is discovered while working a PR/loop, note it on the originating issue (or the PR body); file a standalone issue only if the follow-up is genuinely independent of the PR **and** outlives it (a real separate bug/feature that would be lost as a note on a soon-closed issue). Prefer noting PR-scoped follow-ups on the originating artifact over spinning up standalone tracker items for work that is part of the same effort or will be resolved imminently. See [Sub-Issue Tree Contract](../../docs/sub-issue-tree-contract.md).
|
|
114
115
|
- if a PR already exists, classify bootstrap-wait versus follow-up:
|
|
115
116
|
`node <resolved-skill-scripts>/loop/detect-initial-copilot-pr-state.mjs --repo <resolved-repo> --issue <number>`
|
|
116
117
|
- `waiting_for_initial_copilot_implementation`: keep waiting; in durable-auto mode use:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The operator sequence for the local-first plan-file flow. The [Artifact Authority Contract](artifact-authority-contract.md) owns the model and the per-phase contract details; this doc names the shipped helper scripts in the order they run for one plan file. The [Local-Planning Worked Example](local-planning-worked-example.md) shows a single plan file evolving through these steps.
|
|
4
4
|
|
|
5
|
-
The flow applies under local-planning mode (`strategy.default: local-first`, the shipped default — see the contract's [Shipped default posture](artifact-authority-contract.md#shipped-default-posture)). The plan file lives under `
|
|
5
|
+
The flow applies under local-planning mode (`strategy.default: local-first`, the shipped default — see the contract's [Shipped default posture](artifact-authority-contract.md#shipped-default-posture)). The plan file lives under `docs/phases/`.
|
|
6
6
|
|
|
7
7
|
## Stages
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
One plan file, `docs/phases/phase-42.md`, carried through every stage of the [Local-Planning Flow](local-planning-flow.md): authored, validated, refined in place, held at the local human-review checkpoint, then promoted to a draft PR. Each stage below shows the same file's content as it evolves. The contract details live in the [Artifact Authority Contract](artifact-authority-contract.md); this is a concrete trace of one file.
|
|
4
4
|
|
|
5
|
-
The repo runs local-planning by default (`strategy.default: local-first`), and the plan lives under `
|
|
5
|
+
The repo runs local-planning by default (`strategy.default: local-first`), and the plan lives under `docs/phases/`.
|
|
6
6
|
|
|
7
7
|
## Stage 1 — Authored
|
|
8
8
|
|
|
@@ -47,6 +47,22 @@ doing manually; it is not a general conflict-resolution engine.
|
|
|
47
47
|
6. ✅ PR body contains `Closes #N` or `Fixes #N`
|
|
48
48
|
7. ✅ PR **title** free of merge-blocking markers — `WIP`, `[WIP]`, `DRAFT`, `DO NOT MERGE`, `🚧` (case-insensitive)
|
|
49
49
|
|
|
50
|
+
> Runner-coordination lock: the pre-merge evidence check fails closed on a stale/foreign runner claim for the PR. A completing run releases its claim best-effort at every terminal stop (including the human approval checkpoint), so a merge re-dispatch normally proceeds. If a lock held by a completed/dead run still blocks the merge, take it over explicitly with `node <resolved-skill-scripts>/loop/pr-runner-coordination.mjs takeover --repo <owner/name> --pr <number>`. Never take over a genuinely active (non-stale) run — that fail-closed block is intentional.
|
|
51
|
+
|
|
52
|
+
### Evidence writes and `gh pr merge` MUST be separate tool calls (#1172)
|
|
53
|
+
|
|
54
|
+
The PreToolUse Bash gate evaluates `gh pr merge` **before** the Bash tool call executes. A compound
|
|
55
|
+
command that both writes gate evidence (the findings-log ledger, `upsert-checkpoint-verdict`) and
|
|
56
|
+
merges in the same call is blocked at hook-evaluation time — the write never runs. This can look
|
|
57
|
+
like the ledger "vanished" between writing and merging; it was never written. The block message
|
|
58
|
+
names this when it detects an evidence-writing invocation in the same command string.
|
|
59
|
+
|
|
60
|
+
Documented pattern — **write, verify, then merge alone**:
|
|
61
|
+
|
|
62
|
+
1. Write the gate evidence (findings-log ledger / checkpoint verdict) in its own Bash call.
|
|
63
|
+
2. Verify it landed (e.g. `ls tmp/gate-findings/<slug>/pr-<n>/`) in a separate call.
|
|
64
|
+
3. Run `gh pr merge` alone, with no other command chained via `&&`/`;`/newline.
|
|
65
|
+
|
|
50
66
|
## Title markers
|
|
51
67
|
|
|
52
68
|
The PR title is a contract surface, so a merge-blocking marker in the title is enforced
|
|
@@ -89,7 +105,7 @@ explicit instruction can unlock.
|
|
|
89
105
|
When a repo sets `approval.humanHandoff.enabled: true` in `.devloops`, the loop
|
|
90
106
|
does not just park silently at the human-merge stop — at the
|
|
91
107
|
`pre_approval_gate` / `waiting_for_merge_authorization` boundary it **offers**
|
|
92
|
-
to route the PR to a
|
|
108
|
+
to route the PR to a contributor (pairs with `autonomy.humanMergeOnly`: when
|
|
93
109
|
human-merge is enforced, the handoff names who should take it). Disabled by
|
|
94
110
|
default; no candidate sourcing when disabled.
|
|
95
111
|
|
|
@@ -4,7 +4,7 @@ A plan file is the persisted markdown artifact that drives work in local-plannin
|
|
|
4
4
|
|
|
5
5
|
## Format
|
|
6
6
|
|
|
7
|
-
A plan file is a markdown document whose sections are level-2 (`##`) headings. It follows the same shape as the phase docs already committed under `docs/phases
|
|
7
|
+
A plan file is a markdown document whose sections are level-2 (`##`) headings. It follows the same shape as the phase docs already committed under `docs/phases/`, which is also the directory that holds plan files.
|
|
8
8
|
|
|
9
9
|
## Base authoring sections
|
|
10
10
|
|
|
@@ -437,7 +437,7 @@ Any conflict that would materially change artifact identity, ownership truth, or
|
|
|
437
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
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
439
|
| `intent` | any existing public `dev-loop` intent | Disambiguates the supported public intent; maps to existing contract values |
|
|
440
|
-
| `targetPreference` | `
|
|
440
|
+
| `targetPreference` | `prefer_local` (default) \| `prefer_github_first` | Steers routing preference; must not override authoritative linked-PR or active-artifact truth |
|
|
441
441
|
|
|
442
442
|
The bounded allow-list is exported from `packages/core/src/loop/public-dev-loop-routing.mjs` as `DEV_LOOP_VARIATION_PARAMETER_CONTRACT`.
|
|
443
443
|
|
|
@@ -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 |
|
|
@@ -96,6 +96,38 @@ to match `UI_E2E_CHECK_NAMES` so the gate can read a real signal:
|
|
|
96
96
|
So a deck- or article-only PR has a CI check that can actually satisfy the gate;
|
|
97
97
|
without these jobs such a PR would fail closed with no satisfiable signal.
|
|
98
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
|
+
|
|
99
131
|
## Non-goals
|
|
100
132
|
|
|
101
133
|
Not always-on screenshot testing; not mandatory multi-browser. The criterion is
|
|
@@ -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
|
|