Add branching-strategy skill with next and create actions
Version-aware branch naming (X.Y.Z.Q/{TASK_CODE}-{FEATURE}-{SEQ}),
branch creation from origin/test with origin/main fallback, and the
PR-into-test merge rule. Ships a TypeScript CLI (next, create) plus
shell modules (loggers, base, cli) run via Taskfile.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* branch-tools CLI dispatcher
|
||||
*
|
||||
* Usage:
|
||||
* branch-tools --action <action-name> [options]
|
||||
*
|
||||
* Actions:
|
||||
* next — compute the next branch name (version from git tags only)
|
||||
* create — compute and create the branch locally
|
||||
*
|
||||
* Output is always YAML to stdout. Exit 0 on success, 1 on error.
|
||||
*/
|
||||
|
||||
import { parseArgs } from "node:util";
|
||||
import * as yaml from "js-yaml";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Action registry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type ActionModule = {
|
||||
run: (options: Record<string, string | undefined>) => Record<string, unknown>;
|
||||
};
|
||||
|
||||
const ACTIONS: Record<string, () => Promise<ActionModule>> = {
|
||||
next: () => import("../actions/next/action.js"),
|
||||
create: () => import("../actions/create/action.js"),
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CLI
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function main(): Promise<void> {
|
||||
let values: {
|
||||
action?: string;
|
||||
"task-key"?: string;
|
||||
topic?: string;
|
||||
base?: string;
|
||||
"repo-dir"?: string;
|
||||
help?: boolean;
|
||||
};
|
||||
|
||||
try {
|
||||
({ values } = parseArgs({
|
||||
args: process.argv.slice(2),
|
||||
options: {
|
||||
action: { type: "string", short: "a" },
|
||||
"task-key": { type: "string" },
|
||||
topic: { type: "string" },
|
||||
base: { type: "string" },
|
||||
"repo-dir": { type: "string" },
|
||||
help: { type: "boolean", short: "h" },
|
||||
},
|
||||
strict: true,
|
||||
}));
|
||||
} catch (err) {
|
||||
printError(String(err));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (values.help || !values.action) {
|
||||
console.log(`branch-tools --action <action> [options]
|
||||
|
||||
Branch convention: X.Y.Z.Q/{TASK_MANAGEMENT_CODE}-{FEATURE_X}-{SEQ}
|
||||
Version (X.Y.Z.Q) is read from git tags ONLY. Branches are cut from origin/test (fallback origin/main).
|
||||
|
||||
Actions:
|
||||
next Compute the next branch name
|
||||
create Compute and create the branch locally (never pushes)
|
||||
|
||||
Options (both actions):
|
||||
--task-key <key> Task identifier, e.g. IIAA-123 (required)
|
||||
--topic <topic> Kebab-case focus topic, e.g. api-cleanup (required)
|
||||
--base <branch> Base branch for create (default: origin/test, then origin/main)
|
||||
--repo-dir <path> Target repository (default: current directory)
|
||||
|
||||
Examples:
|
||||
branch-tools --action next --task-key IIAA-123 --topic api-cleanup
|
||||
branch-tools --action create --task-key IIAA-123 --topic api-cleanup --repo-dir /path/to/repo
|
||||
`);
|
||||
process.exit(values.help ? 0 : 1);
|
||||
}
|
||||
|
||||
const actionName = values.action;
|
||||
|
||||
if (!ACTIONS[actionName]) {
|
||||
printError(
|
||||
`Unknown action '${actionName}'. Available: ${Object.keys(ACTIONS).join(", ")}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const mod = await ACTIONS[actionName]();
|
||||
const result = mod.run({
|
||||
"task-key": values["task-key"],
|
||||
topic: values.topic,
|
||||
base: values.base,
|
||||
"repo-dir": values["repo-dir"],
|
||||
});
|
||||
process.stdout.write(
|
||||
yaml.dump(result, { noRefs: true, sortKeys: false, lineWidth: 120 })
|
||||
);
|
||||
const success = (result["success"] as boolean | undefined) ?? true;
|
||||
process.exit(success ? 0 : 1);
|
||||
} catch (err) {
|
||||
const errorOutput = {
|
||||
success: false,
|
||||
action: actionName,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
process.stdout.write(yaml.dump(errorOutput, { noRefs: true, sortKeys: false }));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function printError(msg: string): void {
|
||||
process.stderr.write(`Error: ${msg}\n`);
|
||||
process.stderr.write(
|
||||
`Usage: branch-tools --action <${Object.keys(ACTIONS).join("|")}> [options]\n`
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user