feat(workflow): add preflight, cleanup, artifact-scan, version/tag checks
Adds six sanity-check layers to the release workflow: - preflight: orphaned worktrees, baseline green, plan-state grep, branch collision - cleanup: removes merged worktrees + branches (git branch -d, never -D) - debug artifact scan: dbg!/ console.log / TODO / unwrap() in diff (advisory) - checkbox hygiene: unticked plan tasks before verify (advisory) - pre-release version consistency across Cargo.toml workspace - pre-release tag collision check CLAUDE.md: discipline rules 5 (preflight before develop) and 6 (cleanup after lift). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ export const meta = {
|
|||||||
{ title: 'Verify' },
|
{ title: 'Verify' },
|
||||||
{ title: 'Generate' },
|
{ title: 'Generate' },
|
||||||
{ title: 'Finalize' },
|
{ title: 'Finalize' },
|
||||||
|
{ title: 'Cleanup' },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +88,94 @@ const VERIFY_RESULT_SCHEMA = {
|
|||||||
required: ['allPass', 'failures', 'summary'],
|
required: ['allPass', 'failures', 'summary'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WORKTREE_STATUS_SCHEMA = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
stale: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
path: { type: 'string' },
|
||||||
|
branch: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['path', 'branch'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
path: { type: 'string' },
|
||||||
|
branch: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['path', 'branch'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['stale', 'active'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLAN_STATE_SCHEMA = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
tickedTasks: { type: 'number' },
|
||||||
|
totalTasks: { type: 'number' },
|
||||||
|
gitEvidence: { type: 'array', items: { type: 'string' } },
|
||||||
|
},
|
||||||
|
required: ['tickedTasks', 'totalTasks', 'gitEvidence'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const BRANCH_CHECK_SCHEMA = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
collisions: { type: 'array', items: { type: 'string' } },
|
||||||
|
},
|
||||||
|
required: ['collisions'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const VERSION_CHECK_SCHEMA = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
consistent: { type: 'boolean' },
|
||||||
|
versions: { type: 'array', items: { type: 'string' } },
|
||||||
|
conflicts: { type: 'array', items: { type: 'string' } },
|
||||||
|
tagExists: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
required: ['consistent', 'versions', 'conflicts', 'tagExists'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLEANUP_RESULT_SCHEMA = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
removed: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
path: { type: 'string' },
|
||||||
|
branch: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['path', 'branch'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kept: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
path: { type: 'string' },
|
||||||
|
branch: { type: 'string' },
|
||||||
|
reason: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['path', 'branch', 'reason'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['removed', 'kept'],
|
||||||
|
}
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const REPO = '/home/alee/Sources/relicario'
|
const REPO = '/home/alee/Sources/relicario'
|
||||||
@@ -103,6 +192,94 @@ const mode = (args && args.mode) || 'single'
|
|||||||
const release = args && args.release
|
const release = args && args.release
|
||||||
const context = args && args.context
|
const context = args && args.context
|
||||||
|
|
||||||
|
// ── ACTION: preflight ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if (action === 'preflight') {
|
||||||
|
if (!release) throw new Error('args.release is required for action=preflight')
|
||||||
|
|
||||||
|
const [worktrees, baseline, planState, branches] = await parallel([
|
||||||
|
|
||||||
|
() => agent(
|
||||||
|
`Run: git -C ${REPO} worktree list\n` +
|
||||||
|
`Parse the output. For each worktree listed, extract its path and branch.\n` +
|
||||||
|
`Skip the main checkout at ${REPO} itself.\n` +
|
||||||
|
`Then run: git -C ${REPO} branch --merged main\n` +
|
||||||
|
`A worktree is stale if its branch appears in the merged list. Otherwise it is active.\n` +
|
||||||
|
`Return stale (merged worktrees) and active (unmerged worktrees), each as an array of {path, branch}.`,
|
||||||
|
{ schema: WORKTREE_STATUS_SCHEMA, label: 'worktree-scan', phase: 'Discover' }
|
||||||
|
),
|
||||||
|
|
||||||
|
() => agent(
|
||||||
|
`cd ${REPO} and run each of these commands, capturing the last 5 lines of output:\n` +
|
||||||
|
` cargo test --quiet 2>&1 | tail -5\n` +
|
||||||
|
` pnpm --filter extension test --run 2>&1 | tail -5\n` +
|
||||||
|
`Report allPass=true only if both commands exit with code 0. ` +
|
||||||
|
`List any failures with their error messages. Provide a one-line summary.`,
|
||||||
|
{ schema: VERIFY_RESULT_SCHEMA, label: 'baseline-green', phase: 'Discover' }
|
||||||
|
),
|
||||||
|
|
||||||
|
() => agent(
|
||||||
|
`Run: git -C ${REPO} log --oneline --all --grep="${release}" | head -20\n` +
|
||||||
|
`Capture the output as gitEvidence.\n` +
|
||||||
|
`Then scan ${REPO}/docs/superpowers/plans/ for any files whose filename contains "${release}".\n` +
|
||||||
|
`For each matching file, count lines matching "- \\[x\\]" (ticked) and "- \\[ \\]" (unticked).\n` +
|
||||||
|
`Sum across all matching files. Return tickedTasks, totalTasks, and gitEvidence (the git log lines).`,
|
||||||
|
{ schema: PLAN_STATE_SCHEMA, label: 'plan-state', phase: 'Discover' }
|
||||||
|
),
|
||||||
|
|
||||||
|
() => agent(
|
||||||
|
`Run: git -C ${REPO} branch --all\n` +
|
||||||
|
`Return any branch names (local or remote) that contain the string "${release}" as collisions.`,
|
||||||
|
{ schema: BRANCH_CHECK_SCHEMA, label: 'branch-collision', phase: 'Discover' }
|
||||||
|
),
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
const issues = []
|
||||||
|
|
||||||
|
if (worktrees.stale.length > 0) {
|
||||||
|
issues.push('orphaned-worktrees')
|
||||||
|
log(`WARN [worktree-scan]: ${worktrees.stale.length} stale worktree(s) found — run action=cleanup to remove them`)
|
||||||
|
for (const w of worktrees.stale) {
|
||||||
|
log(` stale: ${w.path} (${w.branch})`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log(`[worktree-scan]: clean`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!baseline.allPass) {
|
||||||
|
issues.push('baseline-failing')
|
||||||
|
log(`FAIL [baseline-green]: ${baseline.failures.length} failure(s): ${baseline.failures.join(' | ')}`)
|
||||||
|
} else {
|
||||||
|
log(`[baseline-green]: green`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (planState.tickedTasks > 0) {
|
||||||
|
issues.push('plan-partially-done')
|
||||||
|
log(`WARN [plan-state]: ${planState.tickedTasks}/${planState.totalTasks} tasks already ticked`)
|
||||||
|
for (const e of planState.gitEvidence) {
|
||||||
|
log(` evidence: ${e}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log(`[plan-state]: clean slate (0 ticked tasks)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branches.collisions.length > 0) {
|
||||||
|
issues.push('branch-collision')
|
||||||
|
log(`WARN [branch-collision]: branches already exist for release label "${release}": ${branches.collisions.join(', ')}`)
|
||||||
|
} else {
|
||||||
|
log(`[branch-collision]: no collisions`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issues.length === 0) {
|
||||||
|
log(`Preflight PASS`)
|
||||||
|
} else {
|
||||||
|
log(`Preflight has ${issues.length} warning(s): ${issues.join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: issues.length === 0 ? 'pass' : 'warn', issues, worktrees, baseline, planState, branches }
|
||||||
|
}
|
||||||
|
|
||||||
// ── ACTION: develop ───────────────────────────────────────────────────────────
|
// ── ACTION: develop ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if (action === 'develop') {
|
if (action === 'develop') {
|
||||||
@@ -156,6 +333,16 @@ if (action === 'develop') {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ── Advisory: checkbox hygiene ───────────────────────────────────────────
|
||||||
|
|
||||||
|
await agent(
|
||||||
|
`Read each of these plan files from ${REPO}:\n${manifest.plans.map(p => ' ' + p).join('\n')}\n\n` +
|
||||||
|
`Count any lines still matching "- [ ]" (unticked checkboxes). ` +
|
||||||
|
`Log each unticked item with its file and line text. ` +
|
||||||
|
`This is advisory only — report findings but do not block or fail.`,
|
||||||
|
{ label: 'checkbox-check', phase: 'Verify' }
|
||||||
|
)
|
||||||
|
|
||||||
phase('Verify')
|
phase('Verify')
|
||||||
const verifyResult = await agent(
|
const verifyResult = await agent(
|
||||||
`Run the full Relicario test suite from ${REPO}. IMPORTANT: cd ${REPO} first.\n` +
|
`Run the full Relicario test suite from ${REPO}. IMPORTANT: cd ${REPO} first.\n` +
|
||||||
@@ -173,6 +360,21 @@ if (action === 'develop') {
|
|||||||
return { status: 'verify-failed', failures: verifyResult.failures, summary: verifyResult.summary }
|
return { status: 'verify-failed', failures: verifyResult.failures, summary: verifyResult.summary }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Advisory: debug artifact scan ────────────────────────────────────────
|
||||||
|
|
||||||
|
await agent(
|
||||||
|
`Run the following command from ${REPO}:\n` +
|
||||||
|
` git -C ${REPO} diff $(git -C ${REPO} describe --tags --abbrev=0)..HEAD\n\n` +
|
||||||
|
`Examine lines beginning with "+" (additions) in the diff output.\n` +
|
||||||
|
`Report any occurrences of:\n` +
|
||||||
|
` - dbg!( in Rust files (warn)\n` +
|
||||||
|
` - console.log( in TypeScript files (warn)\n` +
|
||||||
|
` - TODO or FIXME anywhere (warn)\n` +
|
||||||
|
` - .unwrap() in Rust files (advisory note only, not a hard warn)\n` +
|
||||||
|
`Log each finding with its file and line. This is advisory only — do not block.`,
|
||||||
|
{ label: 'debug-artifact-scan', phase: 'Finalize' }
|
||||||
|
)
|
||||||
|
|
||||||
phase('Finalize')
|
phase('Finalize')
|
||||||
await agent(
|
await agent(
|
||||||
`Update ${REPO}/STATUS.md to reflect the ${release} work that just completed.\n` +
|
`Update ${REPO}/STATUS.md to reflect the ${release} work that just completed.\n` +
|
||||||
@@ -257,15 +459,45 @@ if (action === 'develop') {
|
|||||||
|
|
||||||
])
|
])
|
||||||
|
|
||||||
log(`Generated ${assignment.devCount + 1} prompt files in ${COORD_DIR}/`)
|
// Check relay, start if needed
|
||||||
log(``)
|
await agent(
|
||||||
log(`Next steps:`)
|
`Check if the relay server is running on localhost:7331 by running: ` +
|
||||||
log(` 1. cd ${REPO}/tools/relay && ./start.sh`)
|
`curl -sf http://127.0.0.1:7331/sse --max-time 2 > /dev/null 2>&1 && echo running || echo stopped\n\n` +
|
||||||
log(` 2. Open ${assignment.devCount + 1} terminal windows`)
|
`If the output is "stopped", start it: ` +
|
||||||
log(` 3. PM terminal → paste ${COORD_DIR}/${release}-pm-prompt.md`)
|
`nohup npx tsx ${REPO}/tools/relay/server.ts > /tmp/relay-${release}.log 2>&1 &\n` +
|
||||||
assignment.devs.forEach(dev => {
|
`Then poll curl -sf http://127.0.0.1:7331/sse --max-time 1 once per second for up to 10s. ` +
|
||||||
log(` Dev-${dev.letter} terminal → paste ${COORD_DIR}/${release}-dev-${dev.letter.toLowerCase()}-prompt.md`)
|
`Report "relay ready" or "relay failed to start (check /tmp/relay-${release}.log)".`,
|
||||||
})
|
{ label: 'relay-check', phase: 'Generate' }
|
||||||
|
)
|
||||||
|
|
||||||
|
await agent(
|
||||||
|
`Write a bash launch script to ${REPO}/${COORD_DIR}/${release}-launch.sh.\n\n` +
|
||||||
|
`Header comment: # Auto-generated by release workflow — ${release}\n` +
|
||||||
|
`set -e\n\n` +
|
||||||
|
`Section 1 — Relay health check and auto-start:\n` +
|
||||||
|
` if curl -sf http://127.0.0.1:7331/sse --max-time 2 > /dev/null 2>&1; then\n` +
|
||||||
|
` echo "[relay] already running"\n` +
|
||||||
|
` else\n` +
|
||||||
|
` echo "[relay] starting..." && nohup npx tsx ${REPO}/tools/relay/server.ts > /tmp/relay-${release}.log 2>&1 &\n` +
|
||||||
|
` for i in $(seq 1 10); do sleep 1; curl -sf http://127.0.0.1:7331/sse --max-time 1 > /dev/null 2>&1 && echo "[relay] ready" && break || true; [ $i -eq 10 ] && echo "[relay] ERROR — check /tmp/relay-${release}.log" && exit 1; done\n` +
|
||||||
|
` fi\n\n` +
|
||||||
|
`Section 2 — tmux session. Session name is the release label.\n` +
|
||||||
|
` If tmux session already exists, attach and exit.\n` +
|
||||||
|
` Otherwise create a new session, then for each role (pm + each dev letter) create a named window\n` +
|
||||||
|
` that runs: claude\n` +
|
||||||
|
` After creating windows, print a prompt-paste cheatsheet showing which file to paste in each window.\n` +
|
||||||
|
` Then attach to the session.\n\n` +
|
||||||
|
`Devs: ${assignment.devs.map(d => 'Dev-' + d.letter).join(', ')}\n` +
|
||||||
|
`Prompt files in ${COORD_DIR}:\n` +
|
||||||
|
` PM: ${release}-pm-prompt.md\n` +
|
||||||
|
assignment.devs.map(d => ` Dev-${d.letter}: ${release}-dev-${d.letter.toLowerCase()}-prompt.md`).join('\\n') + '\\n\\n' +
|
||||||
|
`After writing the file, run: chmod +x ${REPO}/${COORD_DIR}/${release}-launch.sh`,
|
||||||
|
{ label: 'gen-launch-script', phase: 'Generate' }
|
||||||
|
)
|
||||||
|
|
||||||
|
log(`Prompts + launch script ready in ${COORD_DIR}/`)
|
||||||
|
log(`Run: ${REPO}/${COORD_DIR}/${release}-launch.sh`)
|
||||||
|
log(`(starts relay if needed, opens tmux session, prompts you which file to paste in each window)`)
|
||||||
|
|
||||||
return { status: 'prompts-ready', devCount: assignment.devCount, coordDir: COORD_DIR }
|
return { status: 'prompts-ready', devCount: assignment.devCount, coordDir: COORD_DIR }
|
||||||
}
|
}
|
||||||
@@ -353,6 +585,46 @@ if (action === 'release') {
|
|||||||
return { status: 'blocked', failures: verifyResult.failures }
|
return { status: 'blocked', failures: verifyResult.failures }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Version + tag checks ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const versionCheck = await agent(
|
||||||
|
`Read ${REPO}/Cargo.toml and all files matching ${REPO}/crates/*/Cargo.toml.\n` +
|
||||||
|
`For each file, extract the version field from the [package] section.\n` +
|
||||||
|
`Check whether all extracted versions are identical.\n` +
|
||||||
|
`Then run: git -C ${REPO} tag -l "${release}"\n` +
|
||||||
|
`Set tagExists=true if the output is non-empty (the tag already exists), false otherwise.\n` +
|
||||||
|
`Return consistent (true if all versions match), versions (list of all extracted version strings), ` +
|
||||||
|
`conflicts (list of "file: version" strings for any that differ from the majority), and tagExists.`,
|
||||||
|
{ schema: VERSION_CHECK_SCHEMA, label: 'version-tag-check', phase: 'Finalize' }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!versionCheck.consistent) {
|
||||||
|
log(`FAIL [version-tag-check]: version mismatch across crates — ${versionCheck.conflicts.join(' | ')}`)
|
||||||
|
return { status: 'blocked', reason: 'version-mismatch' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versionCheck.tagExists) {
|
||||||
|
log(`FAIL [version-tag-check]: tag "${release}" already exists — cannot re-tag`)
|
||||||
|
return { status: 'blocked', reason: 'tag-exists' }
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`[version-tag-check]: Versions consistent (${versionCheck.versions[0]}), tag available`)
|
||||||
|
|
||||||
|
// ── Advisory: debug artifact scan ──────────────────────────────────────────
|
||||||
|
|
||||||
|
await agent(
|
||||||
|
`Run the following command from ${REPO}:\n` +
|
||||||
|
` git -C ${REPO} diff $(git -C ${REPO} describe --tags --abbrev=0)..HEAD\n\n` +
|
||||||
|
`Examine lines beginning with "+" (additions) in the diff output.\n` +
|
||||||
|
`Report any occurrences of:\n` +
|
||||||
|
` - dbg!( in Rust files (warn)\n` +
|
||||||
|
` - console.log( in TypeScript files (warn)\n` +
|
||||||
|
` - TODO or FIXME anywhere (warn)\n` +
|
||||||
|
` - .unwrap() in Rust files (advisory note only, not a hard warn)\n` +
|
||||||
|
`Log each finding with its file and line. This is advisory only — do not block.`,
|
||||||
|
{ label: 'debug-artifact-scan', phase: 'Finalize' }
|
||||||
|
)
|
||||||
|
|
||||||
phase('Finalize')
|
phase('Finalize')
|
||||||
await agent(
|
await agent(
|
||||||
`Cut the ${release} release for Relicario at ${REPO}. IMPORTANT: cd ${REPO} first.\n\n` +
|
`Cut the ${release} release for Relicario at ${REPO}. IMPORTANT: cd ${REPO} first.\n\n` +
|
||||||
@@ -371,5 +643,36 @@ if (action === 'release') {
|
|||||||
return { status: 'tagged', release, note: 'Confirm and push manually.' }
|
return { status: 'tagged', release, note: 'Confirm and push manually.' }
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`Unknown action: "${action}". Valid: develop, debug, verify, release`)
|
// ── ACTION: cleanup ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if (action === 'cleanup') {
|
||||||
|
phase('Cleanup')
|
||||||
|
|
||||||
|
const result = await agent(
|
||||||
|
`Run: git -C ${REPO} worktree list\n` +
|
||||||
|
`Run: git -C ${REPO} branch --merged main\n\n` +
|
||||||
|
`For each worktree listed (skip the main checkout at ${REPO} itself):\n` +
|
||||||
|
` - If its branch appears in the merged list:\n` +
|
||||||
|
` Run: git -C ${REPO} worktree remove --force <path>\n` +
|
||||||
|
` Run: git -C ${REPO} branch -d <branch> (lowercase -d only, never -D)\n` +
|
||||||
|
` Add to removed: [{path, branch}]\n` +
|
||||||
|
` - If its branch does NOT appear in the merged list:\n` +
|
||||||
|
` Add to kept: [{path, branch, reason: "unmerged"}]\n\n` +
|
||||||
|
`Return removed (worktrees that were deleted) and kept (worktrees that were left in place).`,
|
||||||
|
{ schema: CLEANUP_RESULT_SCHEMA, label: 'cleanup', phase: 'Cleanup' }
|
||||||
|
)
|
||||||
|
|
||||||
|
log(`Cleanup removed ${result.removed.length} worktree(s):`)
|
||||||
|
for (const w of result.removed) {
|
||||||
|
log(` removed: ${w.path} (${w.branch})`)
|
||||||
|
}
|
||||||
|
log(`Cleanup kept ${result.kept.length} worktree(s):`)
|
||||||
|
for (const w of result.kept) {
|
||||||
|
log(` kept: ${w.path} (${w.branch}) — ${w.reason}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'done', removed: result.removed.length, kept: result.kept.length }
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`Unknown action: "${action}". Valid: develop, debug, verify, release, preflight, cleanup`)
|
||||||
return { status: 'error', action }
|
return { status: 'error', action }
|
||||||
|
|||||||
@@ -154,3 +154,7 @@ Four rules to prevent the kind of drift the 2026-05-30 audits found:
|
|||||||
4. **Plan-state hygiene.** Plan checkboxes and `STATUS.md`/`ROADMAP.md` must reflect what's actually shipped. Two halves:
|
4. **Plan-state hygiene.** Plan checkboxes and `STATUS.md`/`ROADMAP.md` must reflect what's actually shipped. Two halves:
|
||||||
- **Ship side:** when a commit lands work that maps to a plan task, tick that plan's checkboxes in the same commit (or the immediately-following docs commit). Same for `STATUS.md` — the "Up next" list does not get to lag the actual state of `main` by weeks.
|
- **Ship side:** when a commit lands work that maps to a plan task, tick that plan's checkboxes in the same commit (or the immediately-following docs commit). Same for `STATUS.md` — the "Up next" list does not get to lag the actual state of `main` by weeks.
|
||||||
- **Execute side:** before starting execution of a plan whose checkboxes are all unchecked, spot-check git log (`git log --oneline --all --grep <distinctive-name>`) or grep for a distinctive symbol/file the plan would create. A plan whose work already merged is the worst kind of plan to re-execute. The 2026-05-30 status-audit found Phase 2B, v0.5.1 Streams A/B/C, and 1C-γ all stealth-shipped two-to-three weeks earlier because nobody ran this check.
|
- **Execute side:** before starting execution of a plan whose checkboxes are all unchecked, spot-check git log (`git log --oneline --all --grep <distinctive-name>`) or grep for a distinctive symbol/file the plan would create. A plan whose work already merged is the worst kind of plan to re-execute. The 2026-05-30 status-audit found Phase 2B, v0.5.1 Streams A/B/C, and 1C-γ all stealth-shipped two-to-three weeks earlier because nobody ran this check.
|
||||||
|
|
||||||
|
5. **Pre-flight before develop.** Before running `action:"develop"` on any release, run `action:"preflight"` first. If preflight reports FAIL (baseline not green or version mismatch), fix the failure before proceeding. WARN results (orphaned worktrees, partially-done plan) require a judgement call — acknowledge them explicitly before proceeding.
|
||||||
|
|
||||||
|
6. **Cleanup after every lift.** Once all PRs for a release are merged into main, run `Workflow({name:"release", args:{action:"cleanup"}})` to remove the lift's worktrees and feature branches. Stale worktrees accumulate silently and create confusion for the next lift's branch-collision check.
|
||||||
|
|||||||
Reference in New Issue
Block a user