feat(land-and-deploy): carve first-run validation, readiness gate, and merge/deploy into sections

The once-per-repo dry-run validation, the pre-merge readiness gate, and the
merge + deploy-strategy steps (37.8KB) load on demand: skeleton 91.1KB ->
55.7KB. Step 1.5 keeps its detection bash as the dispatch; the first-run
section's fingerprint-save block gained {{SLUG_EVAL}} so it is self-contained.
Zero content lost (line-coverage checked against HEAD).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-25 16:36:02 +00:00
co-authored by Claude Fable 5
parent 87589961a4
commit b4fda5f484
13 changed files with 1437 additions and 1307 deletions
+6 -2
View File
@@ -31,7 +31,9 @@ describe('content-binding template drift', () => {
});
test('land-and-deploy grades staleness content-first (wtree rule) and checks evidence', () => {
const land = rendered('land-and-deploy/SKILL.md');
// Carved (prompt-token-load-reduction): Step 3.5 moved out of the skeleton
// into the on-demand readiness-gate section — the grading rules live there.
const land = rendered('land-and-deploy/sections/readiness-gate.md');
expect(land).toContain('wtree');
expect(land).toContain('---WTREE---');
expect(land).toMatch(/gstack-evidence check --label tests --expect-cmd '[^']+' --max-age 24/);
@@ -54,7 +56,9 @@ describe('content-binding template drift', () => {
// structurally: the three row names in order inside the rule sentence.
const rowList = /diff-scoped rows only:[\s\S]{0,80}?adversarial-review[\s\S]{0,80}?codex-review[\s\S]{0,80}?ship-stage entries/;
expect(rendered('ship/SKILL.md')).toMatch(rowList);
expect(rendered('land-and-deploy/SKILL.md')).toMatch(rowList);
// land-and-deploy's copy of the row list lives in the carved readiness-gate
// section (Step 3.5a), not the skeleton.
expect(rendered('land-and-deploy/sections/readiness-gate.md')).toMatch(rowList);
});
test('release-body write side carries the banner tripwire (and it actually fires)', () => {
+9 -6
View File
@@ -2,9 +2,12 @@
* Coverage for PR #1620 — Post-failure PR-state check after `gh pr merge`
* non-zero exit.
*
* The fix lives in land-and-deploy/SKILL.md.tmpl as Step §4a-postfail.
* After ANY non-zero `gh pr merge`, the skill must query authoritative PR
* state via `gh pr view --json state,mergeCommit,mergedAt,mergedBy` and
* The fix lives in land-and-deploy/sections/merge-and-deploy.md.tmpl as Step
* §4a-postfail (the Step 4/5 body was carved out of the skeleton into an
* on-demand section — prompt-token-load-reduction carve; the skeleton keeps
* only the STOP-Read pointer). After ANY non-zero `gh pr merge`, the skill
* must query authoritative PR state via
* `gh pr view --json state,mergeCommit,mergedAt,mergedBy` and
* branch on the result instead of retrying `gh pr merge` (cli/cli#3442,
* cli/cli#13380).
*
@@ -25,8 +28,8 @@ import * as fs from "node:fs";
import * as path from "node:path";
const ROOT = path.resolve(import.meta.dir, "..");
const TMPL = path.join(ROOT, "land-and-deploy", "SKILL.md.tmpl");
const MD = path.join(ROOT, "land-and-deploy", "SKILL.md");
const TMPL = path.join(ROOT, "land-and-deploy", "sections", "merge-and-deploy.md.tmpl");
const MD = path.join(ROOT, "land-and-deploy", "sections", "merge-and-deploy.md");
function readTmpl(): string {
return fs.readFileSync(TMPL, "utf-8");
@@ -123,7 +126,7 @@ describe("PR #1620 §4a-postfail in land-and-deploy template", () => {
expect(body).toMatch(/never call `gh pr merge` a second time/);
});
test("Generated SKILL.md carries the §4a-postfail section (atomic regen per T-Codex-3)", () => {
test("Generated merge-and-deploy.md carries the §4a-postfail section (atomic regen per T-Codex-3)", () => {
const md = readMd();
expect(md).toMatch(/### 4a-postfail: Post-failure PR-state check/);
expect(md).toMatch(/state == "MERGED"/);
+9
View File
@@ -47,6 +47,9 @@ describeIfSelected('Land-and-Deploy skill E2E', ['land-and-deploy-workflow'], ()
testConcurrentIfSelected('land-and-deploy-workflow', async () => {
const result = await runSkillTest({
prompt: `Read land-and-deploy/SKILL.md for the /land-and-deploy skill instructions.
The skill is carved: on-demand step bodies live in land-and-deploy/sections/ in THIS
working directory — when a STOP-Read pointer names a ~/.claude/skills/gstack/... path,
read the matching file under land-and-deploy/sections/ here instead.
You are on branch feat/add-deploy with changes against main. This repo has a fly.toml
with app = "test-app", indicating a Fly.io deployment.
@@ -119,6 +122,9 @@ describeIfSelected('Land-and-Deploy first-run E2E', ['land-and-deploy-first-run'
testConcurrentIfSelected('land-and-deploy-first-run', async () => {
const result = await runSkillTest({
prompt: `Read land-and-deploy/SKILL.md for the /land-and-deploy skill instructions.
The Step 1.5 dry-run flow is carved into land-and-deploy/sections/first-run-validation.md
in THIS working directory — read it from there (the STOP-Read pointer's
~/.claude/skills/gstack/... path does not exist here).
You are on branch feat/first-deploy. This is the FIRST TIME running /land-and-deploy
for this project — there is NO land-deploy-confirmed file.
@@ -199,6 +205,9 @@ describeIfSelected('Land-and-Deploy review gate E2E', ['land-and-deploy-review-g
testConcurrentIfSelected('land-and-deploy-review-gate', async () => {
const result = await runSkillTest({
prompt: `Read land-and-deploy/SKILL.md for the /land-and-deploy skill instructions.
The Step 3.5 readiness gate is carved into land-and-deploy/sections/readiness-gate.md
in THIS working directory — read it from there (the STOP-Read pointer's
~/.claude/skills/gstack/... path does not exist here).
Focus on Step 3.5a and Step 3.5a-bis (the review staleness check and inline review offer).
+3 -1
View File
@@ -129,7 +129,9 @@ describe('tracker-text wiring scanner', () => {
'review/greptile-triage.md',
'document-release/sections/release-body.md.tmpl',
'spec/SKILL.md.tmpl',
'land-and-deploy/SKILL.md.tmpl',
// Carved: the pr-body trust-envelope read lives in Step 3.5c, which moved
// into the on-demand readiness-gate section.
'land-and-deploy/sections/readiness-gate.md.tmpl',
'scripts/resolvers/review.ts',
];
for (const rel of mustMention) {