dev-loops 0.5.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/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 +11 -6
- package/.claude/skills/docs/release-runbook.md +45 -0
- package/.claude/skills/docs/ui-e2e-scoping-step.md +102 -0
- package/.claude/skills/local-implementation/SKILL.md +1 -1
- package/CHANGELOG.md +30 -0
- package/README.md +20 -1
- package/agents/dev-loop.agent.md +8 -1
- 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 +5 -2
- package/scripts/claude/generate-claude-assets.mjs +15 -1
- 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/loop/_handoff-contract.mjs +1 -0
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +31 -1
- package/scripts/loop/run-conductor-cycle.mjs +5 -0
- package/scripts/projects/resolve-active-board-item.mjs +193 -0
- package/scripts/refine/scaffold-spike-file.mjs +183 -0
- package/scripts/release/extract-changelog-section.mjs +111 -0
- package/skills/dev-loop/SKILL.md +14 -2
- package/skills/docs/release-runbook.md +45 -0
- package/skills/docs/ui-e2e-scoping-step.md +102 -0
- package/skills/local-implementation/SKILL.md +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import { isDirectCliRun } from "../_core-helpers.mjs";
|
|
4
|
+
|
|
5
|
+
const USAGE = `Usage: extract-changelog-section.mjs --version <v> [--changelog <path>]
|
|
6
|
+
|
|
7
|
+
Prints the CHANGELOG.md section for <version> (the block from "## <version>"
|
|
8
|
+
up to the next "## " heading). Exits 1 if no such section exists or the section
|
|
9
|
+
is empty, so a release is never created for an undocumented version. Exits 2 on
|
|
10
|
+
usage/argument errors (missing --version, missing flag value, unreadable file).`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract the changelog block for a single version.
|
|
14
|
+
*
|
|
15
|
+
* Matches a heading line of the form "## <version>" optionally followed by
|
|
16
|
+
* " - <date>" (or any trailing text), and returns everything up to but not
|
|
17
|
+
* including the next "## " heading. Returns null when no matching section
|
|
18
|
+
* exists (fail closed — caller must not synthesize notes).
|
|
19
|
+
*
|
|
20
|
+
* @param {string} changelog - Full CHANGELOG.md contents.
|
|
21
|
+
* @param {string} version - Version without a leading "v" (e.g. "0.5.0").
|
|
22
|
+
* @returns {string|null} Trimmed section body (without the heading), or null.
|
|
23
|
+
*/
|
|
24
|
+
export function extractChangelogSection(changelog, version) {
|
|
25
|
+
const lines = changelog.split("\n");
|
|
26
|
+
// A heading is "## " followed by the exact version token, then either end of
|
|
27
|
+
// line or a non-version-character (space / "-"). This avoids "0.5.0" matching
|
|
28
|
+
// "0.5.0-rc1" or "0.5.01".
|
|
29
|
+
const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30
|
+
const headingRe = new RegExp(`^##\\s+v?${escaped}(?=\\s|$)`);
|
|
31
|
+
|
|
32
|
+
let start = -1;
|
|
33
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
34
|
+
if (headingRe.test(lines[i])) {
|
|
35
|
+
start = i;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (start === -1) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const body = [];
|
|
45
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
46
|
+
if (/^##\s/.test(lines[i])) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
body.push(lines[i]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return body.join("\n").trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
56
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
57
|
+
process.stdout.write(`${USAGE}\n`);
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let version;
|
|
62
|
+
let changelogPath = "CHANGELOG.md";
|
|
63
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
64
|
+
if (argv[i] === "--version" || argv[i] === "--changelog") {
|
|
65
|
+
const flag = argv[i];
|
|
66
|
+
const value = argv[i + 1];
|
|
67
|
+
if (value === undefined) {
|
|
68
|
+
process.stderr.write(`error: ${flag} requires a value\n\n${USAGE}\n`);
|
|
69
|
+
return 2;
|
|
70
|
+
}
|
|
71
|
+
if (flag === "--version") {
|
|
72
|
+
version = value;
|
|
73
|
+
} else {
|
|
74
|
+
changelogPath = value;
|
|
75
|
+
}
|
|
76
|
+
i += 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!version) {
|
|
81
|
+
process.stderr.write(`error: --version is required\n\n${USAGE}\n`);
|
|
82
|
+
return 2;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const normalized = version.replace(/^v/, "");
|
|
86
|
+
|
|
87
|
+
let changelog;
|
|
88
|
+
try {
|
|
89
|
+
changelog = await readFile(changelogPath, "utf8");
|
|
90
|
+
} catch (error) {
|
|
91
|
+
process.stderr.write(`error: cannot read ${changelogPath}: ${error.message}\n`);
|
|
92
|
+
return 2;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const section = extractChangelogSection(changelog, normalized);
|
|
96
|
+
if (section === null || section === "") {
|
|
97
|
+
process.stderr.write(
|
|
98
|
+
`error: no section found for version ${normalized} in ${changelogPath}. ` +
|
|
99
|
+
`Refusing to create a release for an undocumented version.\n`,
|
|
100
|
+
);
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
process.stdout.write(`${section}\n`);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isDirectCliRun(import.meta.url)) {
|
|
109
|
+
const exitCode = await main();
|
|
110
|
+
process.exitCode = exitCode;
|
|
111
|
+
}
|
package/skills/dev-loop/SKILL.md
CHANGED
|
@@ -35,7 +35,14 @@ For async-required routes (config `workflow.asyncStartMode`, default `required`)
|
|
|
35
35
|
> Under the Claude Code harness the dev-loop runs as a single agent: run these steps directly — no read-only boundary and no separate async-subagent dispatch. See [Main Agent Contract](../docs/main-agent-contract.md).
|
|
36
36
|
|
|
37
37
|
<!-- pi-only -->
|
|
38
|
-
**CLI invocation (`<dev-loops-package-root>`):** dev-loop CLI commands below are invoked as `node <dev-loops-package-root>/cli/index.mjs <verb...>` using the package-local CLI rather than `npx`, so they resolve unambiguously from the installed package without a global install. Resolve `<dev-loops-package-root>`
|
|
38
|
+
**CLI invocation (`<dev-loops-package-root>`):** dev-loop CLI commands below are invoked as `node <dev-loops-package-root>/cli/index.mjs <verb...>` using the package-local CLI rather than `npx`, so they resolve unambiguously from the installed package without a global install. Resolve `<dev-loops-package-root>` via the first of these **bounded** candidates whose `cli/index.mjs` exists — never assume a single fixed layout (under a Pi user-level install the package lives at `~/.pi/agent/npm/node_modules/dev-loops/`, so the old `../../..` package-relative guess from `skills/dev-loop/SKILL.md` overshoots the package root):
|
|
39
|
+
|
|
40
|
+
1. **Node module resolution** (best-effort first try): `node -e "try{const p=require('node:path');console.log(p.resolve(p.dirname(require.resolve('dev-loops/cli/index.mjs')),'..'))}catch{process.exit(1)}"` — resolves the package root when `dev-loops` is reachable from Node's module search path (notably under `~/.pi/agent/npm`); this is cwd-dependent and commonly misses from a target-repo cwd, so the probe is wrapped in try/catch (no stack trace, exits non-zero on miss) — treat a non-zero exit as "probe missed, try the next candidate", not a hard failure.
|
|
41
|
+
2. **Pi user-agent npm root** (reliable for user-level installs): `~/.pi/agent/npm/node_modules/dev-loops`.
|
|
42
|
+
3. **Package-relative (legacy):** `../../..` from this skill's own directory (the original package-local install layout).
|
|
43
|
+
4. **Global npm root:** `$(npm root -g)/dev-loops`.
|
|
44
|
+
|
|
45
|
+
NEVER fall back to `find /` or any unbounded filesystem walk to locate the CLI — it stalls and trips the needs-attention timeout. If every bounded candidate fails, stop and ask the orchestrator/operator for the dev-loops package root rather than searching. (The `dev-loop` agent resolves it analogously.)
|
|
39
46
|
<!-- /pi-only -->
|
|
40
47
|
|
|
41
48
|
Resolve authoritative state via the startup resolver (`node <dev-loops-package-root>/cli/index.mjs loop startup --issue <n>` for issues, `node <dev-loops-package-root>/cli/index.mjs loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `node <dev-loops-package-root>/cli/index.mjs loop build-envelope --input <resolver-output.json>`. The envelope determines `requiredReads`, `nextAction`, `stopRules`, and `acceptance` — load only those files, execute only that bounded task. It is the first handoff artifact consumed before loading any route pack. See [Workflow Handoff Contract](../docs/workflow-handoff-contract.md) for the derivation contract.
|
|
@@ -125,7 +132,12 @@ When you need a fact from a dev-loops JSON-emitting script, climb this ladder an
|
|
|
125
132
|
1. **Prefer the dev-loops subcommand / concise mode.** Use `loop info` or a script's `--concise`/`--summary` mode (e.g. `run-watch-cycle.mjs --concise`, `probe-copilot-review.mjs --concise`) for a human-readable digest. The concise modes surface loop state, Copilot round count, unresolved/actionable thread counts, round-cap-clean eligibility, CI status, next action, and the current round's new Copilot comment bodies.
|
|
126
133
|
2. **`--silent` / `-s` for a yes/no check.** Reads ZERO output: `… --jq '<predicate>' --silent; echo $?` exits `0` for true / `1` for false. Without `--jq`, `--silent` maps the script's success (`ok:true`) to exit `0`, failure to `1`. Example: `probe-copilot-review.mjs --repo o/r --pr N --jq '.status=="idle"' -s`.
|
|
127
134
|
3. **`--jq <filter>` to extract a single field.** The `--jq`-wired scripts (`probe-copilot-review.mjs`, `capture-review-threads.mjs`, `upsert-checkpoint-verdict.mjs`, the `scripts/projects/*` queue scripts) accept a gh-style `--jq` filter (jq subset: field access, `.[]`/`.[N]`, `|`, `select(...)`, `==`/`!=`/`<`/`<=`/`>`/`>=`, `length`, `keys`). It prints only the filtered value. An invalid filter fails closed (stderr + exit `2`), distinct from a clean predicate-false (silent exit `1`).
|
|
128
|
-
4.
|
|
135
|
+
4. **Use a dev-loops wrapper for `gh` reads — never an agent-level raw `gh`.** With `workflow.requireRetrospectiveInternalTooling: true`, a raw `gh` call is a recorded retro violation (fail-closed). If no script covers the read you need, treat it as a tooling gap: file/build a thin wrapper (reuse `scripts/lib/jq-output.mjs`, like the three below), don't shell out. The reads that already have wrappers:
|
|
136
|
+
- CI run-log tail (a failing PR's job log) → `scripts/github/fetch-ci-logs.mjs --repo <o/r> --pr <n> [--failed-only] [--tail <n>]`, **never raw `gh run view --log`/`--log-failed`**. (`probe-ci-status.mjs` names the failed checks; this returns the LOG.)
|
|
137
|
+
- Issue list/filter → `scripts/github/list-issues.mjs --repo <o/r> [--state <open|closed|all>] [--label <l>] [--limit <n>]`, **never raw `gh issue list`**. (The queue tool lists the project board; this is for arbitrary issue queries.)
|
|
138
|
+
- Issue comment → `scripts/github/comment-issue.mjs --repo <o/r> --issue <n> (--body <text> | --body-file <path>)`, **never raw `gh issue comment`**. Returns `{ ok, commentUrl }`.
|
|
139
|
+
|
|
140
|
+
All three accept the same `--jq`/`--silent` output flags as the other JSON-emitting scripts.
|
|
129
141
|
5. **NEVER `| python3` or `node -e`** to parse tool JSON. If a field you need is missing from a script's output, add it to the script (or its concise mode), not an inline parser.
|
|
130
142
|
|
|
131
143
|
## Guard rules
|
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# UI e2e scoping step
|
|
2
|
+
|
|
3
|
+
Canonical owner for **when the shared UI/mobile e2e loop is required**. Inclusion
|
|
4
|
+
is **path-triggered and deterministic** — it does not depend on a human annotating
|
|
5
|
+
the PR or a phase doc.
|
|
6
|
+
|
|
7
|
+
## Trigger: a rendered-artifact change requires UI e2e coverage
|
|
8
|
+
|
|
9
|
+
A PR that **adds or modifies a rendered HTML artifact** MUST run the shared UI e2e
|
|
10
|
+
assertions (mobile + desktop) AND register that artifact in the e2e suite. A
|
|
11
|
+
"rendered artifact" is anything that renders to a served page or component:
|
|
12
|
+
|
|
13
|
+
- a presentation deck — `docs/presentations/*.html` (registered in `DECK_REGISTRY`)
|
|
14
|
+
- an article page — `docs/articles/*.html` (registered in `ARTICLE_REGISTRY`; the
|
|
15
|
+
intro article is the published landing page)
|
|
16
|
+
- the inspect-run viewer's served page/component — `scripts/loop/inspect-run-viewer.mjs`
|
|
17
|
+
|
|
18
|
+
The trigger is the PR's **changed-file set** matched against these explicit globs.
|
|
19
|
+
It is conservative on purpose (issue #976 scope): only artifacts that render to a
|
|
20
|
+
page/component, matched by exact directory + single-segment `*.html` globs (no
|
|
21
|
+
recursion) and the viewer source path.
|
|
22
|
+
|
|
23
|
+
Registration is keyed on the **full repo-relative path**, not the basename, so
|
|
24
|
+
`docs/articles/X.html` and `docs/presentations/X.html` (which share basenames,
|
|
25
|
+
e.g. `introducing-dev-loops.html`) are **distinct** artifacts and can never alias
|
|
26
|
+
onto each other — editing the article never counts as deck coverage and vice versa.
|
|
27
|
+
|
|
28
|
+
### Examples — required
|
|
29
|
+
|
|
30
|
+
- `docs/presentations/introducing-dev-loops.html` edited → UI e2e **required**
|
|
31
|
+
(deck-smoke CI job runs the deck fit specs).
|
|
32
|
+
- `docs/articles/introducing-dev-loops.html` (the landing page) edited → UI e2e
|
|
33
|
+
**required** (article-smoke CI job runs the article fit specs); it is its own
|
|
34
|
+
registration, NOT covered by the same-named deck.
|
|
35
|
+
- A new `docs/articles/new-page.html` added → UI e2e **required**, and because the
|
|
36
|
+
new page is not yet in `ARTICLE_REGISTRY` the gate **fails closed** until it is
|
|
37
|
+
registered (add an entry + a thin spec calling `defineArticleSuite`).
|
|
38
|
+
- `scripts/loop/inspect-run-viewer.mjs` edited → UI e2e **required** (the viewer is
|
|
39
|
+
registered in `VIEWER_REGISTRY`).
|
|
40
|
+
|
|
41
|
+
### Examples — not required
|
|
42
|
+
|
|
43
|
+
- `packages/core/src/loop/copilot-loop-state.mjs`, `README.md`, `*.test.mjs`,
|
|
44
|
+
`docs/articles/foo.md` (not `.html`), or a nested `docs/articles/sub/x.html`
|
|
45
|
+
(the glob is single-segment) → UI e2e **not required**; the gate passes through.
|
|
46
|
+
|
|
47
|
+
## Register the artifact in the suite
|
|
48
|
+
|
|
49
|
+
"Registered" means the artifact appears in the shared registries:
|
|
50
|
+
|
|
51
|
+
- decks → `DECK_REGISTRY` in `test/playwright/harness/deck-fit-harness.mjs`
|
|
52
|
+
(served from `docs/presentations/<deck>`) with a thin spec calling `defineDeckSuite`.
|
|
53
|
+
- articles → `ARTICLE_REGISTRY` in `test/playwright/harness/deck-fit-harness.mjs`
|
|
54
|
+
(served from `docs/articles/<file>`) with a thin spec calling `defineArticleSuite`
|
|
55
|
+
(shared fit/CSP/no-horizontal-scroll assertions, minus the deck's per-section
|
|
56
|
+
named captures).
|
|
57
|
+
- the viewer → `VIEWER_REGISTRY` in
|
|
58
|
+
`test/playwright/harness/inspect-run-viewer-harness.mjs`.
|
|
59
|
+
|
|
60
|
+
The deterministic membership list the gate checks against lives in
|
|
61
|
+
`packages/core/src/loop/ui-e2e-scoping.mjs` (`REGISTERED_ARTIFACT_PATHS`, keyed by
|
|
62
|
+
full repo-relative path; `VIEWER_ARTIFACT_ID`). A sync test keeps
|
|
63
|
+
`REGISTERED_ARTIFACT_PATHS` from drifting from `DECK_REGISTRY` + `ARTICLE_REGISTRY`;
|
|
64
|
+
the single-valued `VIEWER_ARTIFACT_ID` is not import-checked against `VIEWER_REGISTRY`
|
|
65
|
+
(that harness pulls `@playwright/test` into core), so keep the two in sync by hand.
|
|
66
|
+
|
|
67
|
+
## Fail-closed semantics
|
|
68
|
+
|
|
69
|
+
The check is wired as a gate precondition in `evaluatePrGateCoordination`
|
|
70
|
+
(`packages/core/src/loop/pr-gate-coordination.mjs`), at a seam distinct from the
|
|
71
|
+
mergeability (#980) and retrospective (#982) preconditions. It fails closed:
|
|
72
|
+
|
|
73
|
+
- **Unregistered rendered artifact touched** → gate blocks with
|
|
74
|
+
`nextAction: run_ui_e2e_suite`, reason naming the artifact and that it needs
|
|
75
|
+
registration in the suite.
|
|
76
|
+
- **Registered, but UI e2e suite did not pass for this head** (`uiE2ePassed` is
|
|
77
|
+
`false`/unknown) → gate blocks with the same action, reason that the suite must
|
|
78
|
+
run and pass first.
|
|
79
|
+
- **Non-UI change** → `required: false`, gate passes through untouched.
|
|
80
|
+
|
|
81
|
+
The detect layer reads the changed-file set from `gh pr view --json files` and
|
|
82
|
+
derives `uiE2ePassed` from the `statusCheckRollup` UI e2e check(s)
|
|
83
|
+
(`UI_E2E_CHECK_NAMES`): present UI e2e checks must all be `SUCCESS`; if none is
|
|
84
|
+
present the value is unknown and the gate fails closed.
|
|
85
|
+
|
|
86
|
+
Each rendered-artifact family has a stable, satisfiable CI job in
|
|
87
|
+
`.github/workflows/ci.yml`, path/diff-conditioned in the `changes` job and named
|
|
88
|
+
to match `UI_E2E_CHECK_NAMES` so the gate can read a real signal:
|
|
89
|
+
|
|
90
|
+
- `viewer-smoke` → inspect-run viewer spec (triggered by the viewer source set).
|
|
91
|
+
- `deck-smoke` → both presentation deck fit specs (triggered by
|
|
92
|
+
`docs/presentations/**` and the deck specs/harness/configs).
|
|
93
|
+
- `article-smoke` → both article fit specs (triggered by `docs/articles/**` and the
|
|
94
|
+
article specs/harness/configs).
|
|
95
|
+
|
|
96
|
+
So a deck- or article-only PR has a CI check that can actually satisfy the gate;
|
|
97
|
+
without these jobs such a PR would fail closed with no satisfiable signal.
|
|
98
|
+
|
|
99
|
+
## Non-goals
|
|
100
|
+
|
|
101
|
+
Not always-on screenshot testing; not mandatory multi-browser. The criterion is
|
|
102
|
+
only the conservative path-glob + registry-membership + passing-coverage check.
|
|
@@ -512,7 +512,7 @@ After the phase plan passes review:
|
|
|
512
512
|
3. Run local validation:
|
|
513
513
|
- `npm run verify`
|
|
514
514
|
- when a narrower local check is more honest for the touched slice, say so explicitly and run the narrowest justified subset instead of pretending the full verify path was unnecessary
|
|
515
|
-
- for
|
|
515
|
+
- for a *rendered* HTML artifact (a deck `docs/presentations/*.html`, an article `docs/articles/*.html`, or the inspect-run viewer source) UI e2e coverage is **required and auto-scoped** — not opt-in. Touching one obliges you to register it in the shared harness and have a passing UI e2e suite, or the gate fails closed (see [UI e2e scoping step](../docs/ui-e2e-scoping-step.md)); run the matching shared suite locally, e.g. `npm run test:playwright:intro-deck` / `:intro-article` / `:viewer`. For a bespoke local UI surface that is not a registered rendered artifact, the WebKit smoke seam (fixture-backed Playwright WebKit plus screenshot capture) remains available without being mandatory
|
|
516
516
|
- **Commit immediately after validation passes.** Once `npm run verify` (or the narrowest justified validation subset) is green, stage and commit the current slice of work with an atomic commit message. For tracker-backed sessions, also push the commit. In non-interactive subagent sessions, commit authorization is implicit — the subagent was dispatched to implement and must commit before exiting. In interactive sessions, wait for coordination agent authorization. Do not defer commits — uncommitted changes when the session terminates are a workflow defect.
|
|
517
517
|
4. Review the implementation against the merged phase plan.
|
|
518
518
|
5. Run the default pre-approval gate as a full review / fix loop on the branch before calling it review-complete, approval-ready, merge-ready, or ready for final handoff:
|