dev-loops 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/agents/dev-loop.md +1 -1
- package/.claude/agents/refiner.md +1 -0
- package/.claude/commands/auto.md +7 -0
- package/.claude/commands/continue.md +15 -0
- package/.claude/commands/info.md +7 -0
- package/.claude/commands/start-spike.md +16 -0
- package/.claude/commands/start.md +7 -0
- package/.claude/commands/status.md +6 -0
- package/.claude/hooks/_run-context.mjs +11 -4
- package/.claude/skills/dev-loop/SKILL.md +21 -6
- package/.claude/skills/dev-loop/templates/slides-story-review.md +54 -0
- package/.claude/skills/docs/artifact-authority-contract.md +86 -31
- package/.claude/skills/docs/local-planning-flow.md +63 -0
- package/.claude/skills/docs/local-planning-worked-example.md +139 -0
- package/.claude/skills/docs/merge-preconditions.md +35 -0
- package/.claude/skills/docs/plan-file-contract.md +37 -0
- package/.claude/skills/docs/release-runbook.md +45 -0
- package/.claude/skills/docs/retrospective-checkpoint-contract.md +55 -7
- package/.claude/skills/docs/spike-mode-contract.md +237 -0
- package/.claude/skills/docs/ui-e2e-scoping-step.md +102 -0
- package/.claude/skills/local-implementation/SKILL.md +1 -1
- package/CHANGELOG.md +73 -0
- package/README.md +21 -1
- package/agents/dev-loop.agent.md +8 -1
- package/agents/refiner.agent.md +1 -0
- package/cli/index.mjs +2 -0
- package/extension/index.ts +10 -1
- package/extension/presentation.ts +15 -0
- package/lib/dev-loops-core.mjs +141 -0
- package/package.json +8 -3
- package/scripts/claude/generate-claude-assets.mjs +15 -1
- package/scripts/github/capture-review-threads.mjs +20 -2
- package/scripts/github/comment-issue.mjs +181 -0
- package/scripts/github/fetch-ci-logs.mjs +215 -0
- package/scripts/github/list-issues.mjs +191 -0
- package/scripts/github/probe-copilot-review.mjs +69 -3
- package/scripts/github/upsert-checkpoint-verdict.mjs +18 -3
- package/scripts/lib/jq-output.mjs +297 -0
- package/scripts/loop/_handoff-contract.mjs +1 -0
- package/scripts/loop/check-retro-tooling.mjs +246 -0
- package/scripts/loop/copilot-pr-handoff.mjs +21 -3
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +65 -2
- package/scripts/loop/docs-grill-contract.mjs +70 -0
- package/scripts/loop/info.mjs +21 -2
- package/scripts/loop/pr-runner-coordination.mjs +20 -4
- package/scripts/loop/resolve-dev-loop-startup.mjs +176 -5
- package/scripts/loop/resolve-pr-conflicts.mjs +357 -0
- package/scripts/loop/run-conductor-cycle.mjs +5 -0
- package/scripts/loop/run-watch-cycle.mjs +77 -3
- package/scripts/loop/slides-story-review-contract.mjs +123 -0
- package/scripts/pages/build-site.mjs +136 -0
- package/scripts/projects/add-queue-item.mjs +12 -2
- package/scripts/projects/list-queue-items.mjs +12 -2
- package/scripts/projects/move-queue-item.mjs +12 -2
- package/scripts/projects/resolve-active-board-item.mjs +193 -0
- package/scripts/refine/_refine-helpers.mjs +20 -0
- package/scripts/refine/exit-spike.mjs +186 -0
- package/scripts/refine/promote-plan.mjs +387 -0
- package/scripts/refine/refine-plan-file.mjs +165 -0
- package/scripts/refine/scaffold-spike-file.mjs +183 -0
- package/scripts/refine/validate-plan-file.mjs +64 -0
- package/scripts/refine/validate-spike-file.mjs +87 -0
- package/scripts/release/extract-changelog-section.mjs +111 -0
- package/skills/dev-loop/SKILL.md +24 -2
- package/skills/dev-loop/templates/slides-story-review.md +54 -0
- package/skills/docs/artifact-authority-contract.md +86 -31
- package/skills/docs/local-planning-flow.md +63 -0
- package/skills/docs/local-planning-worked-example.md +139 -0
- package/skills/docs/merge-preconditions.md +35 -0
- package/skills/docs/plan-file-contract.md +37 -0
- package/skills/docs/release-runbook.md +45 -0
- package/skills/docs/retrospective-checkpoint-contract.md +55 -7
- package/skills/docs/spike-mode-contract.md +237 -0
- package/skills/docs/ui-e2e-scoping-step.md +102 -0
- package/skills/local-implementation/SKILL.md +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Local-planning worked example
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
The repo runs local-planning by default (`strategy.default: local-first`), and the plan lives under `localPlanning.plansDir` (`docs/phases/`).
|
|
6
|
+
|
|
7
|
+
## Stage 1 — Authored
|
|
8
|
+
|
|
9
|
+
The operator writes the four base sections. The file at this point:
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
# phase-42 plan
|
|
13
|
+
|
|
14
|
+
## Status
|
|
15
|
+
|
|
16
|
+
draft
|
|
17
|
+
|
|
18
|
+
## Objective
|
|
19
|
+
|
|
20
|
+
Add a `--dry-run` flag to the queue dispatcher so an operator can preview the
|
|
21
|
+
dispatch order without mutating the board.
|
|
22
|
+
|
|
23
|
+
## In scope
|
|
24
|
+
|
|
25
|
+
- Parse `--dry-run` in the queue dispatcher CLI.
|
|
26
|
+
- Print the resolved dispatch order and exit 0 without any board mutation.
|
|
27
|
+
|
|
28
|
+
## Explicit non-goals
|
|
29
|
+
|
|
30
|
+
- Changing the dispatch ordering algorithm.
|
|
31
|
+
- Any change to non-dry-run dispatch behavior.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Validation passes:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
$ node scripts/refine/validate-plan-file.mjs --input docs/phases/phase-42.md --json
|
|
38
|
+
{ "checker": "validate-plan-file", "ok": true, "errors": [] }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Stage 2 — Started (intake state)
|
|
42
|
+
|
|
43
|
+
Startup reads the plan and reports the intake state. With the four base sections present and no refinement sections yet, the state is `new_plan_needs_refinement`:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
$ node scripts/loop/resolve-dev-loop-startup.mjs --plan-file docs/phases/phase-42.md
|
|
47
|
+
# ... startup output carries planFileIntakeState: "new_plan_needs_refinement"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Stage 3 — Refined in place
|
|
51
|
+
|
|
52
|
+
`refine-plan-file.mjs` appends the refinement sections, the coverage matrix, and the recorded docs-grill findings to the same file, then stops at the `local_human_review` checkpoint. The file now reads (added sections shown):
|
|
53
|
+
|
|
54
|
+
```markdown
|
|
55
|
+
# phase-42 plan
|
|
56
|
+
|
|
57
|
+
## Status
|
|
58
|
+
|
|
59
|
+
draft
|
|
60
|
+
|
|
61
|
+
## Objective
|
|
62
|
+
|
|
63
|
+
Add a `--dry-run` flag to the queue dispatcher so an operator can preview the
|
|
64
|
+
dispatch order without mutating the board.
|
|
65
|
+
|
|
66
|
+
## In scope
|
|
67
|
+
|
|
68
|
+
- Parse `--dry-run` in the queue dispatcher CLI.
|
|
69
|
+
- Print the resolved dispatch order and exit 0 without any board mutation.
|
|
70
|
+
|
|
71
|
+
## Explicit non-goals
|
|
72
|
+
|
|
73
|
+
- Changing the dispatch ordering algorithm.
|
|
74
|
+
- Any change to non-dry-run dispatch behavior.
|
|
75
|
+
|
|
76
|
+
## Acceptance criteria
|
|
77
|
+
|
|
78
|
+
- `--dry-run` prints the resolved dispatch order to stdout.
|
|
79
|
+
- `--dry-run` performs zero board mutation (no Projects V2 writes).
|
|
80
|
+
- Exit code is 0 on a successful dry-run preview.
|
|
81
|
+
|
|
82
|
+
## Definition of done
|
|
83
|
+
|
|
84
|
+
- CLI parses `--dry-run`; unit test covers the parse path.
|
|
85
|
+
- A test asserts no board-mutation call fires under `--dry-run`.
|
|
86
|
+
- The queue docs note the flag.
|
|
87
|
+
|
|
88
|
+
## Coverage matrix
|
|
89
|
+
|
|
90
|
+
| Item | Type | Status | Evidence | Notes |
|
|
91
|
+
|---|---|---|---|---|
|
|
92
|
+
| `--dry-run` prints dispatch order | AC | Unverified | pending implementation | |
|
|
93
|
+
| `--dry-run` performs zero mutation | AC | Unverified | pending implementation | |
|
|
94
|
+
| Exit code 0 on preview | AC | Unverified | pending implementation | |
|
|
95
|
+
| No-mutation test | DoD | Unverified | pending test | |
|
|
96
|
+
| Dispatch algorithm unchanged | Non-goal | Unverified | scope boundary | |
|
|
97
|
+
|
|
98
|
+
## Docs-grill findings
|
|
99
|
+
|
|
100
|
+
- [fix_in_place] (stale_reference) Queue docs example omits the new flag; add it on this branch.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The intake state for this file is now `plan_refined_ready_for_promotion`. The loop stops here for the operator to review the refined plan before promotion.
|
|
104
|
+
|
|
105
|
+
## Stage 4 — Local human-review checkpoint
|
|
106
|
+
|
|
107
|
+
The operator reads the refined plan, confirms the acceptance criteria and coverage matrix, and dispositions the docs-grill findings. No GitHub mutation has happened so far; the plan file is the only artifact. When the operator approves, the flow moves to promotion.
|
|
108
|
+
|
|
109
|
+
## Stage 5 — Promoted to a draft PR
|
|
110
|
+
|
|
111
|
+
`promote-plan.mjs` commits the plan doc, opens one draft PR, and writes the PR number back into the plan's front-matter. The file gains a leading front-matter block (the body is unchanged):
|
|
112
|
+
|
|
113
|
+
```markdown
|
|
114
|
+
---
|
|
115
|
+
prNumber: 1234
|
|
116
|
+
---
|
|
117
|
+
# phase-42 plan
|
|
118
|
+
|
|
119
|
+
## Status
|
|
120
|
+
|
|
121
|
+
draft
|
|
122
|
+
|
|
123
|
+
## Objective
|
|
124
|
+
|
|
125
|
+
Add a `--dry-run` flag to the queue dispatcher so an operator can preview the
|
|
126
|
+
dispatch order without mutating the board.
|
|
127
|
+
|
|
128
|
+
# ... base, refinement, coverage-matrix, and docs-grill sections unchanged ...
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The PR body references `docs/phases/phase-42.md` as the spec-of-record, completing the bidirectional plan↔PR link. No GitHub issue was minted. Re-running `promote-plan.mjs` on this file now resolves to `already_promoted` and opens nothing, because the front-matter already carries `prNumber`.
|
|
132
|
+
|
|
133
|
+
## Relationship to other docs
|
|
134
|
+
|
|
135
|
+
| Doc | Relationship |
|
|
136
|
+
|---|---|
|
|
137
|
+
| [Local-Planning Flow](local-planning-flow.md) | The operator sequence this example traces |
|
|
138
|
+
| [Artifact Authority Contract](artifact-authority-contract.md) | Canonical model and per-phase contract details |
|
|
139
|
+
| [Plan-file Contract](plan-file-contract.md) | Plan-file format, base sections, and the `prNumber` front-matter |
|
|
@@ -2,8 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
Canonical owner for merge preconditions across all workflow families.
|
|
4
4
|
|
|
5
|
+
## Conflict-free (mergeable) is a precondition at every gate
|
|
6
|
+
|
|
7
|
+
A PR that conflicts with its base gets **no `pull_request` CI run** — GitHub can't
|
|
8
|
+
compute the merge ref — so the gate silently stalls (green-less with no obvious
|
|
9
|
+
cause). Conflict-free is therefore a required gate precondition, checked at **two
|
|
10
|
+
seams**: before requesting CI/Copilot, and again before merge.
|
|
11
|
+
|
|
12
|
+
- `gh pr view --json mergeable,mergeStateStatus` drives it. A `CONFLICTING` /
|
|
13
|
+
`DIRTY` / `BEHIND` PR does **not** pass any gate (`gateBoundary:
|
|
14
|
+
conflict_resolution`, `nextAction: resolve_merge_conflicts`).
|
|
15
|
+
- `mergeable` is computed asynchronously, so a freshly-pushed head briefly reads
|
|
16
|
+
`UNKNOWN`. The detect layer **re-polls a bounded number of times**; if it never
|
|
17
|
+
settles, the gate **fails closed to a recheck** (`nextAction: wait_for_ci`) —
|
|
18
|
+
an unsettled merge state is never treated as clean.
|
|
19
|
+
- `loop info` surfaces a **Mergeable:** line (mergeStateStatus included) so a
|
|
20
|
+
conflict is diagnosed immediately, never mistaken for missing CI.
|
|
21
|
+
|
|
22
|
+
### Deterministic auto-resolve (additive CHANGELOG only)
|
|
23
|
+
|
|
24
|
+
When a PR is behind/`CONFLICTING`, run the conservative resolver before resuming
|
|
25
|
+
the gate path:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
node scripts/loop/resolve-pr-conflicts.mjs [--base <branch>] [--push]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
It merges `origin/<base>` into the PR branch and resolves **only the safe additive
|
|
32
|
+
case** — a `CHANGELOG.md` conflict where both sides only ADD list/section entries
|
|
33
|
+
(keep BOTH sides, in order) — then runs `npm run test:docs` and (with `--push`)
|
|
34
|
+
pushes. **Any other conflicted path, or a non-additive CHANGELOG edit, FAILS
|
|
35
|
+
CLOSED** naming the conflicted paths (no silent stall, no guessing — resolve those
|
|
36
|
+
by hand). This encodes the exact additive-CHANGELOG resolution the loop has been
|
|
37
|
+
doing manually; it is not a general conflict-resolution engine.
|
|
38
|
+
|
|
5
39
|
## Required before merge
|
|
6
40
|
|
|
41
|
+
1. ✅ Conflict-free with base (`mergeable: MERGEABLE`; not `CONFLICTING`/`DIRTY`/`BEHIND`/`UNKNOWN`)
|
|
7
42
|
1. ✅ CI green on current head (or crediblyGreen via `--local-validation-head-sha`)
|
|
8
43
|
2. ✅ Draft gate satisfied (clean verdict)
|
|
9
44
|
3. ✅ Pre-approval gate satisfied (clean verdict, current head)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Plan-file contract
|
|
2
|
+
|
|
3
|
+
A plan file is the persisted markdown artifact that drives work in local-planning mode. It reuses the existing phase-doc format under `docs/phases/` (for example, `docs/phases/phase-<n>.md`). The artifact authority model that selects local-planning mode lives in the [Artifact Authority Contract](artifact-authority-contract.md); this document defines the file format and its required base sections.
|
|
4
|
+
|
|
5
|
+
## Format
|
|
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/`. The directory that holds plan files is `localPlanning.plansDir`, which defaults to `docs/phases/`.
|
|
8
|
+
|
|
9
|
+
## Base authoring sections
|
|
10
|
+
|
|
11
|
+
A plan file carries these base sections when it is authored, before refinement adds acceptance criteria and a definition of done:
|
|
12
|
+
|
|
13
|
+
| Heading | Purpose |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `## Status` | The current lifecycle state of the plan (for example, in progress, deferred). |
|
|
16
|
+
| `## Objective` | What the plan sets out to achieve. |
|
|
17
|
+
| `## In scope` | The bounded work this plan covers. |
|
|
18
|
+
| `## Explicit non-goals` | What this plan leaves out. |
|
|
19
|
+
|
|
20
|
+
Each base section has a non-empty body. Refinement is a later phase that adds acceptance criteria and a definition of done; those are outside the base authoring contract this document defines.
|
|
21
|
+
|
|
22
|
+
## Validator
|
|
23
|
+
|
|
24
|
+
`scripts/refine/validate-plan-file.mjs` checks a plan file against the base sections above. It exports a pure function `validatePlanFile(markdownText)` that returns `{ checker: "validate-plan-file", ok, errors }`, where each absent or empty-body base section contributes one entry with a distinct `missing_*` code. `ok` is `true` when every base section is present with a non-empty body.
|
|
25
|
+
|
|
26
|
+
The thin CLI accepts `--input <path>`, `--json`, and `--help`. The validation verdict is reported in the JSON payload; the process exits non-zero for argument or path errors (house style — see the [Validation Policy](validation-policy.md)).
|
|
27
|
+
|
|
28
|
+
## Optional front-matter (plan↔PR link)
|
|
29
|
+
|
|
30
|
+
A plan file may carry an optional leading YAML front-matter block — a `---` line, `key: value` lines, a closing `---` line — before its first heading. It is additive: a plan without front-matter is unchanged and fully valid. The only key the tooling reads or writes today is `prNumber:`, the number of the draft PR a plan was promoted into (PR-FIRST promotion never mints an issue; the committed plan doc is the spec-of-record and the PR body links it). The link is recorded bidirectionally — the PR body references the committed plan-doc path; the plan's `prNumber` front-matter references the PR. Promotion (`scripts/refine/promote-plan.mjs`) writes `prNumber` and is idempotent on a plan that already carries one. The parser/serializer lives in the pure `@dev-loops/core/loop/plan-file-promote-contract` module.
|
|
31
|
+
|
|
32
|
+
## Relationship to other docs
|
|
33
|
+
|
|
34
|
+
| Doc | Relationship |
|
|
35
|
+
|---|---|
|
|
36
|
+
| [Artifact Authority Contract](artifact-authority-contract.md) | Owns the artifact-selection model; selects local-planning mode and points here for the plan-file format. |
|
|
37
|
+
| [Validation Policy](validation-policy.md) | Owns the shared CLI verdict-versus-exit-code convention this validator follows. |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Release runbook
|
|
2
|
+
|
|
3
|
+
Releasing is fully automated from a tag. **Pushing a `v<version>` tag is the only
|
|
4
|
+
manual step.** Everything after the tag is hands-off.
|
|
5
|
+
|
|
6
|
+
## Procedure
|
|
7
|
+
|
|
8
|
+
1. On `main` (merged, green), bump the version and add the matching
|
|
9
|
+
`## <version> - <date>` section to `CHANGELOG.md` (the empty `## Unreleased`
|
|
10
|
+
heading stays above the latest version).
|
|
11
|
+
2. Tag the release commit and push the tag:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git tag v<version>
|
|
15
|
+
git push origin v<version>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That is the whole manual flow. Do **not** create the GitHub Release by hand —
|
|
19
|
+
the workflow does it (and is idempotent if you already created one).
|
|
20
|
+
|
|
21
|
+
## What happens automatically
|
|
22
|
+
|
|
23
|
+
- **`.github/workflows/release.yml`** fires on the `v*` tag push. It verifies the
|
|
24
|
+
tagged commit is on `origin/main`, extracts the `## <version>` block from
|
|
25
|
+
`CHANGELOG.md` via `scripts/release/extract-changelog-section.mjs`, and creates
|
|
26
|
+
the GitHub Release (`--latest`, notes = that CHANGELOG section). It is
|
|
27
|
+
**idempotent** (no-op if a Release for the tag already exists) and **fails
|
|
28
|
+
closed** if the version has no CHANGELOG section — an undocumented version never
|
|
29
|
+
gets an empty release.
|
|
30
|
+
- **`.github/workflows/npm-publish.yml`** fires on `release: published` (created by
|
|
31
|
+
the step above) and publishes the packages to npm.
|
|
32
|
+
|
|
33
|
+
## Failure modes
|
|
34
|
+
|
|
35
|
+
- Tag not on `main`: the release workflow fails the on-main guard — re-tag the
|
|
36
|
+
correct commit.
|
|
37
|
+
- Missing CHANGELOG section: the extraction step exits non-zero and no Release is
|
|
38
|
+
created. The workflow checks out the tagged commit, so editing `CHANGELOG.md`
|
|
39
|
+
alone is not enough — commit the `## <version>` section, then move the tag to
|
|
40
|
+
the new commit and re-push:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git tag -f v<version>
|
|
44
|
+
git push --force origin v<version>
|
|
45
|
+
```
|
|
@@ -93,17 +93,48 @@ When `workflow.requireRetrospectiveGate` is enabled in `.devloops` at repo root,
|
|
|
93
93
|
- has `state: "complete"`
|
|
94
94
|
- includes a `behavioralReview` with `mergeApproved: true`, `followedWorkingAgreement` (boolean), `gateQualityAcceptable` (boolean), and `drifts` (array)
|
|
95
95
|
- includes `mergeRecommendation` (non-empty string)
|
|
96
|
+
- **(developer mode only)** includes `behavioralReview.internalToolingOnly: true` (boolean) and `behavioralReview.rawCallViolations: []` (empty array) — see **Internal-tooling-only rule** below
|
|
96
97
|
|
|
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
|
+
The enforcement function is `evaluateRetrospectiveMergeApproval(checkpoint, { developerMode })` in `packages/core/src/loop/pr-gate-coordination.mjs`, called from `evaluatePrGateCoordination` at each merge-ready boundary.
|
|
99
|
+
|
|
100
|
+
### Internal-tooling-only rule (issue #982) — DEVELOPER MODE ONLY, opt-in
|
|
101
|
+
|
|
102
|
+
This is a **developer-mode** retro step: it enforces the dev-loops maintainers' own dogfooding discipline and is **opt-in** via `workflow.requireRetrospectiveInternalTooling` (**default OFF**). **Consumers of the dev-loops extension are never blocked by it** — a consumer may legitimately use raw `gh`/`python`/`node -e` in their own workflow, so when the flag is OFF (the consumer default) the `internalToolingOnly`/`rawCallViolations` fields are neither required nor enforced, and a complete, merge-approved checkpoint passes exactly as it would without these fields. The check only requires and enforces them when `workflow.requireRetrospectiveInternalTooling: true` (the dev-loops repo opts in via its own repo-root `.devloops`).
|
|
103
|
+
|
|
104
|
+
When developer mode is ON, the loop's own execution must use internal dev-loops tooling only. **Agent-level top-level raw calls are all the same breach and all block the merge gate:**
|
|
105
|
+
|
|
106
|
+
- `gh ...` (including `gh api`, `gh ... --jq`) — use `gate capture-threads`, `gate reply-resolve`, `dev-loops loop info`, `queue …`, or a `node scripts/...` wrapper instead
|
|
107
|
+
- `python` / `python3` — parse JSON with the tool's `--jq`/built-in output, not an inline Python one-liner
|
|
108
|
+
- `node -e` / `node --eval` — inline eval to read/parse tool output is a violation; `node scripts/foo.mjs` is **not**
|
|
109
|
+
|
|
110
|
+
**Allowed (NOT violations):** dev-loops subcommands, and `node scripts/*.mjs` invocations — those scripts legitimately call `gh`/GraphQL internally; that is the tooling. The rule targets the agent's own top-level shell calls, not a script's internals.
|
|
111
|
+
|
|
112
|
+
The retrospective author records the result in the checkpoint:
|
|
113
|
+
- `behavioralReview.internalToolingOnly` (boolean) — `true` only when the run used internal tooling throughout
|
|
114
|
+
- `behavioralReview.rawCallViolations` (array of strings) — one entry per agent-level raw call; empty when clean
|
|
115
|
+
|
|
116
|
+
**Fail-closed (developer mode only):** when `requireRetrospectiveInternalTooling` is ON, the merge gate blocks (`retrospective_gate_pending`) if `internalToolingOnly` is not `true` **OR** `rawCallViolations` is missing/non-empty. `state: "complete"` alone is **not** sufficient — a clean tooling record is also required. When the flag is OFF (consumer default) this check is inert: it never blocks, even for a checkpoint that omits the fields or records a violation.
|
|
117
|
+
|
|
118
|
+
**Back-compat:** an OLD checkpoint that omits `internalToolingOnly`/`rawCallViolations` only fails closed in developer mode; with the flag OFF (consumer default) it passes unchanged. Re-record the retrospective with the new fields to clear a developer-mode block.
|
|
119
|
+
|
|
120
|
+
**Write-op allowlist (verifier only):** `gh pr merge`, `gh pr ready`, `gh issue create`, `gh issue edit` have no internal wrapper today. The deterministic verifier (below) records these as `allowedWriteOps` rather than violations so the gate is not blocked forever on an unavoidable gap. Close the gap with a wrapper to remove an entry. The gate itself still fails on any non-empty `rawCallViolations` the author records.
|
|
121
|
+
|
|
122
|
+
### Deterministic verifier
|
|
123
|
+
|
|
124
|
+
`node scripts/loop/check-retro-tooling.mjs [--transcript <path>] [--json]` reads a newline-delimited transcript of the shell commands the agent ran (one top-level command per line, via `--transcript` or stdin) and reports agent-level raw `gh`/`python`/`python3`/`node -e`/`node --eval` calls. Use it to derive the `rawCallViolations` array the retrospective author records. Exit code `1` when violations are found, `0` when clean. The pure `analyzeTranscript(transcript)` export returns `{ violations, allowedWriteOps, internalToolingOnly }`.
|
|
125
|
+
|
|
126
|
+
Matching rules: a tool name at the start of a command segment (start of line, or after `&&`/`||`/`|`/`;`); `node` is a violation only with `-e`/`--eval`. Before classifying, the verifier normalizes the segment head — it strips leading `NAME=value` env-assignment prefixes (`GH_TOKEN=x gh api`), strips a leading wrapper binary from `{sudo, env, xargs, time, nice, command}` (`sudo gh api`, `xargs gh api`), and reduces a path-prefixed binary to its basename (`./node_modules/.bin/gh`, `/usr/bin/python3`) — so the common prefixed/wrapped raw-call forms are caught. Known limitation: it does NOT fully parse shell quoting/substitution. A separator inside a quoted argument can over-report; deeply obfuscated calls (command substitution `$(...)`, aliases, `eval`) may evade it — prefer single-line, single-purpose commands in transcripts.
|
|
98
127
|
|
|
99
128
|
### Merge gate states
|
|
100
129
|
|
|
101
130
|
| Retrospective state | Merge gate result |
|
|
102
131
|
|---|---|
|
|
103
132
|
| No checkpoint file | Blocked: `retrospective_gate_pending` |
|
|
104
|
-
| `state: "complete"` with `mergeApproved: true` and valid fields | Allowed: proceeds to `FINAL_APPROVAL_READY` |
|
|
133
|
+
| `state: "complete"` with `mergeApproved: true` and valid base fields (consumer default; internal-tooling check OFF) | Allowed: proceeds to `FINAL_APPROVAL_READY` |
|
|
105
134
|
| `state: "complete"` without `mergeApproved: true` | Blocked |
|
|
106
|
-
|
|
|
135
|
+
| **Developer mode ON** + `internalToolingOnly` not `true`, or `rawCallViolations` missing/non-empty | Blocked |
|
|
136
|
+
| **Developer mode OFF** (consumer default) + missing `internalToolingOnly`/`rawCallViolations` or a recorded violation | Allowed (check is inert) |
|
|
137
|
+
| Missing base required fields (`followedWorkingAgreement`, `gateQualityAcceptable`, `drifts`, `mergeRecommendation`) | Blocked |
|
|
107
138
|
| `state: "skipped"` or `state: "required"` | Blocked |
|
|
108
139
|
|
|
109
140
|
### Configuration
|
|
@@ -111,8 +142,9 @@ The enforcement function is `evaluateRetrospectiveMergeApproval(checkpoint)` in
|
|
|
111
142
|
```yaml
|
|
112
143
|
# .devloops at repo root
|
|
113
144
|
workflow:
|
|
114
|
-
requireRetrospective: true
|
|
115
|
-
requireRetrospectiveGate: true
|
|
145
|
+
requireRetrospective: true # startup/resume gate
|
|
146
|
+
requireRetrospectiveGate: true # merge gate after pre_approval_gate
|
|
147
|
+
requireRetrospectiveInternalTooling: true # developer-mode internal-tooling-only check (default OFF; consumers leave unset)
|
|
116
148
|
```
|
|
117
149
|
|
|
118
150
|
## Durable artifact format
|
|
@@ -130,11 +162,25 @@ The checkpoint file is written by `.pi/extensions/dev-loop-behavioral-review.ts`
|
|
|
130
162
|
|
|
131
163
|
### After retrospective is done (written by operator or skill)
|
|
132
164
|
|
|
165
|
+
A minimal completion clears the startup gate. To also clear the **merge gate**
|
|
166
|
+
(`requireRetrospectiveGate`), include the `behavioralReview`, `gateQuality`, and
|
|
167
|
+
`mergeRecommendation` fields:
|
|
168
|
+
|
|
133
169
|
```json
|
|
134
170
|
{
|
|
135
171
|
"state": "complete",
|
|
136
172
|
"completedAt": "2026-05-29T16:30:00.000Z",
|
|
137
|
-
"notes": "Loop followed working agreement; minor drift on thread resolution."
|
|
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
|
+
}
|
|
138
184
|
}
|
|
139
185
|
```
|
|
140
186
|
|
|
@@ -153,7 +199,9 @@ The checkpoint file is written by `.pi/extensions/dev-loop-behavioral-review.ts`
|
|
|
153
199
|
| Artifact | Location |
|
|
154
200
|
|---|---|
|
|
155
201
|
| Checkpoint state machine | `packages/core/src/loop/retrospective-checkpoint.mjs` (internal core module; not part of the public package exports surface) |
|
|
156
|
-
|
|
|
202
|
+
| Merge gate evaluator | `packages/core/src/loop/pr-gate-coordination.mjs` — `evaluateRetrospectiveMergeApproval` |
|
|
203
|
+
| Internal-tooling verifier | `scripts/loop/check-retro-tooling.mjs` (+ `test/loop/check-retro-tooling.test.mjs`) |
|
|
204
|
+
| Tests | `packages/core/test/retrospective-checkpoint.test.mjs`, `packages/core/test/pr-gate-coordination.test.mjs` |
|
|
157
205
|
| Extension (writes required marker, fires review prompt) | `.pi/extensions/dev-loop-behavioral-review.ts` |
|
|
158
206
|
| Checkpoint file | `.pi/dev-loop-retrospective-checkpoint.json` |
|
|
159
207
|
| AGENTS.md repo contract | [Agent Instructions](../../AGENTS.md) — concise repo contract and working rules |
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Spike-mode contract
|
|
2
|
+
|
|
3
|
+
A spike is a time-boxed, exploratory dev-loop run started from a local question with no GitHub issue. Its deliverable is a findings document. When the exploration reaches a recommendation, the operator concludes it with one of two exits: **discard** (drop it with zero tracker artifacts) or **graduate** (emit a #947-style local-first plan file that enters the existing plan→PR promotion path).
|
|
4
|
+
|
|
5
|
+
This doc is the canonical operator sequence for spike mode and carries a worked example showing both exits. The spike artifact lives outside the tracker; it is exempt from the production-gate ceremony at entry and runs under a relaxed gate profile (`gates.spike`). The [Artifact Authority Contract](artifact-authority-contract.md) owns the local-planning model a graduated spike feeds into.
|
|
6
|
+
|
|
7
|
+
The spike track shipped across three phases: P1 (#964) the `--spike` intake state machine, P2 (#965) the relaxed gate profile and the discard/graduate exits, P3 (#966) this doc.
|
|
8
|
+
|
|
9
|
+
## Shipped surfaces
|
|
10
|
+
|
|
11
|
+
| Stage | Helper script | Pure logic |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Validate | `scripts/refine/validate-spike-file.mjs` | `validateSpikeFile` / `validateSpikeExplorationSections` (in the helper script) |
|
|
14
|
+
| Start | `scripts/loop/resolve-dev-loop-startup.mjs --spike <path>` | `buildSpikeInput` (in the script) + `evaluateSpikeIntakeState` (`@dev-loops/core/loop/spike-intake-contract`) |
|
|
15
|
+
| Exit | `scripts/refine/exit-spike.mjs` | `evaluateSpikeExit` / `buildGraduatedPlanBody` (`@dev-loops/core/loop/spike-exit-contract`) |
|
|
16
|
+
|
|
17
|
+
The spike findings artifact carries four authoring sections (`scripts/refine/validate-spike-file.mjs`, `SPIKE_FILE_BASE_SECTIONS`):
|
|
18
|
+
|
|
19
|
+
- `## Question` — what the spike investigates
|
|
20
|
+
- `## Approach` — how the exploration was carried out
|
|
21
|
+
- `## Findings` — the evidence gathered
|
|
22
|
+
- `## Recommendation` — the exit marker
|
|
23
|
+
|
|
24
|
+
The first three (`SPIKE_FILE_EXPLORATION_SECTIONS`) are the exploration scaffold required to **start** a spike. `Recommendation` (`SPIKE_FILE_EXIT_MARKER_SECTION`) is filled in during the spike; once it is present and non-empty, the intake state flips to ready-for-exit.
|
|
25
|
+
|
|
26
|
+
## Intake states
|
|
27
|
+
|
|
28
|
+
`evaluateSpikeIntakeState` (`packages/core/src/loop/spike-intake-contract.mjs`) classifies the artifact into one of three `SPIKE_INTAKE_STATE` values from two facts — whether the exploration scaffold is valid, and whether a Recommendation is present:
|
|
29
|
+
|
|
30
|
+
| State | Meaning |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `spike_in_progress` | Scaffold (Question/Approach/Findings) valid; no Recommendation yet |
|
|
33
|
+
| `spike_ready_for_exit` | Scaffold valid and a non-empty Recommendation is present; an exit decision can be made |
|
|
34
|
+
| `ambiguous_fail_closed` | Scaffold invalid (a missing or empty base section); fail closed, no routing |
|
|
35
|
+
|
|
36
|
+
## Operator sequence
|
|
37
|
+
|
|
38
|
+
### 1. Author the exploration scaffold
|
|
39
|
+
|
|
40
|
+
Write a findings artifact carrying `## Question`, `## Approach`, and `## Findings` with non-empty bodies. `## Recommendation` is left for the spike to fill in. Check the full set of base sections with the validator:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
node scripts/refine/validate-spike-file.mjs --input <spike.md> --json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The payload is `{ checker: "validate-spike-file", ok, errors }`, with a distinct `missing_*` code per absent or empty section (`missing_question`, `missing_approach`, `missing_findings`, `missing_recommendation`). An in-progress spike reports `missing_recommendation` here and is still startable, because startup gates on the exploration scaffold (`validateSpikeExplorationSections`), which omits the Recommendation.
|
|
47
|
+
|
|
48
|
+
### 2. Start the spike
|
|
49
|
+
|
|
50
|
+
Hand the artifact to startup with `--spike` (mutually exclusive with `--issue`, `--pr`, `--input`, `--plan-file`):
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
node scripts/loop/resolve-dev-loop-startup.mjs --spike <spike.md>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`buildSpikeInput` (`scripts/loop/resolve-dev-loop-startup.mjs`) reads the file read-only — no tracker mutation, no GitHub call, no issue or PR number. It requires the exploration scaffold; a missing/unreadable file, or one failing `validateSpikeExplorationSections`, throws and startup fails closed (exit 1, no readiness bundle). On success it builds a `local_phase` startup input with the resolved spike path as the target `phase` and threads `spikeIntakeState` onto the output. A spike with no Recommendation reports `spike_in_progress`. The spike path is exempt from the worktree-isolation guard because there is no issue to key a worktree on (`planFileExempt: true`).
|
|
57
|
+
|
|
58
|
+
### 3. Run under the relaxed gate profile
|
|
59
|
+
|
|
60
|
+
A spike runs under `gates.spike`, a lighter angle set than the production draft → pre-approval gates. See [Relaxed gate profile](#relaxed-gate-profile) below.
|
|
61
|
+
|
|
62
|
+
### 4. Reach a Recommendation, then exit
|
|
63
|
+
|
|
64
|
+
When the exploration concludes, fill in `## Recommendation`. The intake state is now `spike_ready_for_exit`, and `exit-spike.mjs` can conclude the spike. It recomputes the intake facts (`validateSpikeExplorationSections` for the scaffold, `extractSection` for the Recommendation), runs `evaluateSpikeExit`, and routes to one of two dispositions.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
node scripts/refine/exit-spike.mjs --spike-file <spike.md> --disposition <discard|graduate> [--plan-file <path>] [--json]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`evaluateSpikeExit` (`packages/core/src/loop/spike-exit-contract.mjs`) is eligible only from `spike_ready_for_exit`. From any other state it fails closed with `not_ready_for_exit`; an unrecognized disposition fails closed with `unknown_disposition`. On a fail-closed path the CLI makes zero tracker mutation, writes no plan file, and exits 1.
|
|
71
|
+
|
|
72
|
+
## Exit dispositions
|
|
73
|
+
|
|
74
|
+
The two dispositions are `SPIKE_EXIT_DISPOSITION.DISCARD` (`"discard"`) and `SPIKE_EXIT_DISPOSITION.GRADUATE` (`"graduate"`).
|
|
75
|
+
|
|
76
|
+
### discard
|
|
77
|
+
|
|
78
|
+
The recommendation is "don't pursue". The spike is dropped with zero tracker artifacts; the findings document on disk is the whole record. The CLI writes nothing and creates no GitHub artifact. `--plan-file` is irrelevant for a discard.
|
|
79
|
+
|
|
80
|
+
### graduate
|
|
81
|
+
|
|
82
|
+
The recommendation is "pursue this". `buildGraduatedPlanBody` builds a #947-consumable plan-file body from the spike's `Question`, `Approach`, `Findings`, and `Recommendation`, and `exit-spike.mjs` writes it to the required `--plan-file` path. The mapping:
|
|
83
|
+
|
|
84
|
+
- `Question` + `Approach` become the Objective's context.
|
|
85
|
+
- `Recommendation` becomes the In-scope work.
|
|
86
|
+
- `Findings` are recorded as supporting evidence.
|
|
87
|
+
- A fixed Explicit non-goals block keeps the plan from re-opening the concluded exploration.
|
|
88
|
+
|
|
89
|
+
The emitted body carries the four plan-file base sections (`## Status`, `## Objective`, `## In scope`, `## Explicit non-goals` — see [Plan-file Contract](plan-file-contract.md)), so it passes `validatePlanFile` and enters the local-first plan→PR promotion path (#952) unchanged: refine it, hold the local human-review checkpoint, then `promote-plan.mjs`. Graduation is idempotent — `buildGraduatedPlanBody` is pure, so re-running reproduces the same plan file. `buildGraduatedPlanBody` fails closed (throws) on an empty required section, so a graduate exit cannot emit a plan the validator would reject. `--plan-file` is required for graduate (the CLI never guesses an output path).
|
|
90
|
+
|
|
91
|
+
## Relaxed gate profile
|
|
92
|
+
|
|
93
|
+
A spike's deliverable is a findings doc, so it does not carry the full production draft → pre-approval → Copilot gate set. `gates.spike` is a relaxed profile, shipped in `packages/core/src/config/extension-defaults.yaml`:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
gates:
|
|
97
|
+
spike:
|
|
98
|
+
angles:
|
|
99
|
+
- scope
|
|
100
|
+
- docs
|
|
101
|
+
excludeAngles: []
|
|
102
|
+
required: false
|
|
103
|
+
requireCi: false
|
|
104
|
+
mandatoryAngles: []
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
| Knob | Spike value | Production draft/pre-approval | Why for a spike |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `angles` | `scope`, `docs` | the full production angle set | A findings doc needs a scope check and a docs check, so the angle set is small and docs-first |
|
|
110
|
+
| `required` | `false` | `true` | The spike's record is the findings doc, so the gate is advisory and the loop proceeds without a passing verdict |
|
|
111
|
+
| `requireCi` | `false` | `true` (pre-approval always requires CI) | A spike produces no production code to run CI against |
|
|
112
|
+
|
|
113
|
+
`gates.spike` resolves through the same config-merge layering and the same `resolveGateConfig(config, "spike")` path as `draft` and `preApproval` (`packages/core/src/config/config.mjs`) — no new strategy→knob resolver. It is `optional()` in the schema (`packages/core/src/config/config.mjs`, `schemas/dev-loop-config.schema.json`) and absent for non-spike work, so production gates are unaffected. A repo `.devloops` can override any of these knobs.
|
|
114
|
+
|
|
115
|
+
## Worked example
|
|
116
|
+
|
|
117
|
+
One spike, `spike-cache.md`, from question to a Recommendation, shown through both exits.
|
|
118
|
+
|
|
119
|
+
### Authored scaffold
|
|
120
|
+
|
|
121
|
+
```markdown
|
|
122
|
+
# Spike: response cache
|
|
123
|
+
|
|
124
|
+
## Question
|
|
125
|
+
|
|
126
|
+
Would an in-process LRU cache on the metadata fetch measurably cut p95 latency?
|
|
127
|
+
|
|
128
|
+
## Approach
|
|
129
|
+
|
|
130
|
+
Add a throwaway `@lru_cache` wrapper behind a flag, replay a captured request
|
|
131
|
+
trace, and compare p95 with and without the cache.
|
|
132
|
+
|
|
133
|
+
## Findings
|
|
134
|
+
|
|
135
|
+
The trace replay shows p95 dropping from 240ms to 90ms with the cache, with a
|
|
136
|
+
~3MB steady-state memory cost and no correctness regressions on the replay set.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Validation reports the spike is still in progress — the Recommendation is not yet written, so the full-section validator flags it while the scaffold itself is complete:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
$ node scripts/refine/validate-spike-file.mjs --input spike-cache.md --json
|
|
143
|
+
{ "checker": "validate-spike-file", "ok": false, "errors": [ { "code": "missing_recommendation", "message": "..." } ] }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Startup accepts it anyway, because the entry gate is the exploration scaffold:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
$ node scripts/loop/resolve-dev-loop-startup.mjs --spike spike-cache.md
|
|
150
|
+
# ... startup output carries spikeIntakeState: "spike_in_progress"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Recommendation reached
|
|
154
|
+
|
|
155
|
+
The operator fills in the exit marker:
|
|
156
|
+
|
|
157
|
+
```markdown
|
|
158
|
+
## Recommendation
|
|
159
|
+
|
|
160
|
+
Adopt the cache behind a default-on flag; the latency win is large and the
|
|
161
|
+
memory cost is small. Ship it as a follow-up plan.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The intake state is now `spike_ready_for_exit`, so the spike is exitable.
|
|
165
|
+
|
|
166
|
+
### Exit A — discard (leaves nothing behind)
|
|
167
|
+
|
|
168
|
+
Had the findings come out the other way — say the latency win was within noise — the recommendation would be "don't pursue", and the operator discards:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
$ node scripts/refine/exit-spike.mjs --spike-file spike-cache.md --disposition discard --json
|
|
172
|
+
{ "ok": true, "action": "discard", "spikeFile": "/abs/spike-cache.md" }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
No plan file is written, no GitHub artifact is created, and no tracker entry is made. The `spike-cache.md` findings doc on disk is the entire record of the exploration.
|
|
176
|
+
|
|
177
|
+
### Exit B — graduate (emits a plan file)
|
|
178
|
+
|
|
179
|
+
With the "adopt it" recommendation above, the operator graduates the spike into a plan file:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
$ node scripts/refine/exit-spike.mjs --spike-file spike-cache.md --disposition graduate --plan-file docs/phases/phase-43.md --json
|
|
183
|
+
{ "ok": true, "action": "graduate", "spikeFile": "/abs/spike-cache.md", "planFile": "/abs/docs/phases/phase-43.md" }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`docs/phases/phase-43.md` is written with the spike's sections mapped onto the plan-file base sections:
|
|
187
|
+
|
|
188
|
+
```markdown
|
|
189
|
+
# Graduated spike plan
|
|
190
|
+
|
|
191
|
+
## Status
|
|
192
|
+
|
|
193
|
+
Draft (graduated from a spike). Needs refinement before promotion.
|
|
194
|
+
|
|
195
|
+
## Objective
|
|
196
|
+
|
|
197
|
+
Act on the spike's recommendation. The spike asked: Would an in-process LRU
|
|
198
|
+
cache on the metadata fetch measurably cut p95 latency?
|
|
199
|
+
|
|
200
|
+
Approach explored:
|
|
201
|
+
|
|
202
|
+
Add a throwaway `@lru_cache` wrapper behind a flag, replay a captured request
|
|
203
|
+
trace, and compare p95 with and without the cache.
|
|
204
|
+
|
|
205
|
+
## In scope
|
|
206
|
+
|
|
207
|
+
Adopt the cache behind a default-on flag; the latency win is large and the
|
|
208
|
+
memory cost is small. Ship it as a follow-up plan.
|
|
209
|
+
|
|
210
|
+
Supporting findings from the spike:
|
|
211
|
+
|
|
212
|
+
The trace replay shows p95 dropping from 240ms to 90ms with the cache, with a
|
|
213
|
+
~3MB steady-state memory cost and no correctness regressions on the replay set.
|
|
214
|
+
|
|
215
|
+
## Explicit non-goals
|
|
216
|
+
|
|
217
|
+
- Re-running the spike's exploration; that question is concluded.
|
|
218
|
+
- Work beyond the recommendation above.
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This plan file passes `validatePlanFile`, so it enters the local-planning flow as a new plan needing refinement: refine it, hold the local human-review checkpoint, then `promote-plan.mjs` opens the draft PR (see the [Local-Planning Worked Example](local-planning-worked-example.md)). Re-running the graduate exit reproduces the same plan body, because `buildGraduatedPlanBody` is pure.
|
|
222
|
+
|
|
223
|
+
## Relationship to other docs
|
|
224
|
+
|
|
225
|
+
| Doc | Relationship |
|
|
226
|
+
|---|---|
|
|
227
|
+
| [Artifact Authority Contract](artifact-authority-contract.md) | Canonical artifact model; a graduated spike feeds the local-planning tier |
|
|
228
|
+
| [Local-Planning Flow](local-planning-flow.md) | The operator sequence a graduated plan file then follows |
|
|
229
|
+
| [Local-Planning Worked Example](local-planning-worked-example.md) | One plan file through promotion, where a graduated spike lands |
|
|
230
|
+
| [Plan-file Contract](plan-file-contract.md) | Plan-file format and base sections the graduated body satisfies |
|
|
231
|
+
| [Public Dev Loop Contract](public-dev-loop-contract.md) | Canonical routing contract; `--spike` is one startup input |
|
|
232
|
+
|
|
233
|
+
## Non-goals
|
|
234
|
+
|
|
235
|
+
- Changing spike intake, exit, or gate behavior (this is a docs-only surface).
|
|
236
|
+
- Defining a tracker-backed spike — a spike is local by construction.
|
|
237
|
+
- A discard that records a tracker artifact — discard is zero-mutation by contract.
|