Files
affaan-m--everything-claude…/scripts/worktree-lifecycle.js
T
wehub-resource-sync d48cda4081
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:55:55 +08:00

165 lines
4.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
'use strict';
const { buildLifecycleReport, planCleanup } = require('./lib/worktree-lifecycle/lifecycle');
function parseArgs(argv = process.argv) {
const args = argv.slice(2);
const options = {
json: false,
conflictsOnly: false,
staleOnly: false,
cleanupPlan: false,
help: false,
baseBranch: 'main',
staleDays: 7,
repoRoot: process.cwd()
};
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === '--json') {
options.json = true;
} else if (arg === '--conflicts') {
options.conflictsOnly = true;
} else if (arg === '--stale') {
options.staleOnly = true;
} else if (arg === '--cleanup-plan') {
options.cleanupPlan = true;
} else if (arg === '--help' || arg === '-h') {
options.help = true;
} else if (arg === '--base') {
options.baseBranch = args[i + 1] || options.baseBranch;
i += 1;
} else if (arg === '--stale-days') {
const value = Number(args[i + 1]);
if (Number.isFinite(value) && value >= 0) {
options.staleDays = value;
}
i += 1;
} else if (arg === '--repo') {
options.repoRoot = args[i + 1] || options.repoRoot;
i += 1;
}
}
return options;
}
function usage() {
return [
'Usage: worktree-lifecycle [options]',
'',
'Analyze every git worktree in a repo and classify its lifecycle state',
'(dirty, merge-ready, conflict, merged, stale, idle). Predicts merge',
'conflicts without touching the working tree (git merge-tree) and proposes',
'a safe cleanup plan that never removes dirty or unmerged worktrees.',
'',
'Options:',
' --json Print the full ecc.worktree-lifecycle.v1 report as JSON',
' --conflicts Only show worktrees that would conflict on merge',
' --stale Only show stale (clean, inactive) worktrees',
' --cleanup-plan Show which worktrees are safe to remove and why',
' --base <branch> Base branch to compare against (default: main)',
' --stale-days <n> Days of inactivity before a clean worktree is stale (default: 7)',
' --repo <path> Repository root (default: cwd)',
' -h, --help Show this help'
].join('\n');
}
function formatReport(report, options = {}) {
const lines = [];
const a = report.aggregates;
lines.push(`Worktree Lifecycle (${report.baseBranch})`);
lines.push(
` ${a.worktreeCount} worktrees | ${a.mergeReadyCount} merge-ready, `
+ `${a.conflictCount} conflict, ${a.staleCount} stale`
);
lines.push('');
if (options.conflictsOnly) {
if (report.conflictQueue.length === 0) {
lines.push('No worktrees would conflict on merge.');
} else {
lines.push('Conflict queue:');
for (const w of report.conflictQueue) {
const files = w.conflictFiles.length > 0 ? ` [${w.conflictFiles.slice(0, 5).join(', ')}]` : '';
lines.push(` ${w.branch} (${w.ahead} ahead)${files}`);
}
}
return lines.join('\n');
}
if (options.staleOnly) {
if (report.staleQueue.length === 0) {
lines.push('No stale worktrees.');
} else {
lines.push('Stale queue:');
for (const w of report.staleQueue) {
lines.push(` ${w.branch} (${Math.round((w.ageMs || 0) / 86400000)}d old) ${w.path}`);
}
}
return lines.join('\n');
}
if (options.cleanupPlan) {
const plan = planCleanup(report);
lines.push(`Safe to remove (${plan.remove.length}):`);
for (const item of plan.remove) {
lines.push(` ${item.branch || '(detached)'} - ${item.reason}`);
}
lines.push('');
lines.push(`Salvage first, then remove (${plan.salvage.length}):`);
for (const item of plan.salvage) {
lines.push(` ${item.branch || '(detached)'} - ${item.reason}`);
}
lines.push('');
lines.push(`Kept (${plan.keep.length}):`);
for (const item of plan.keep) {
lines.push(` ${item.branch || '(detached)'} - ${item.reason}`);
}
return lines.join('\n');
}
lines.push('Worktrees:');
for (const w of report.worktrees) {
const counts = w.ahead !== null ? ` +${w.ahead}/-${w.behind}` : '';
lines.push(` ${(w.branch || '(detached)').padEnd(28)} ${w.state}${counts}`);
}
return lines.join('\n');
}
function main(argv = process.argv) {
const options = parseArgs(argv);
if (options.help) {
console.log(usage());
return;
}
const report = buildLifecycleReport(options.repoRoot, {
baseBranch: options.baseBranch,
staleThresholdMs: options.staleDays * 24 * 60 * 60 * 1000
});
if (options.json) {
console.log(JSON.stringify(report, null, 2));
return;
}
console.log(formatReport(report, options));
}
if (require.main === module) {
main();
}
module.exports = {
parseArgs,
usage,
formatReport,
main
};