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:
2026-07-24 13:48:13 +03:00
co-authored by Claude Fable 5
parent 823baf8302
commit c32c55cb4d
46 changed files with 1426 additions and 0 deletions
+126
View File
@@ -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();