hamster-wheel-cli 0.2.0-beta.1 → 0.2.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  本项目遵循语义化版本,变更日志以时间倒序记录。
4
4
 
5
+ ## [0.2.0-beta.2] - 2025-12-24
6
+ ### Added
7
+ - 新增 `alias run` 子命令,支持执行 alias 时追加参数并覆盖同名选项。
8
+
5
9
  ## [0.2.0] - 2025-12-24
6
10
  ### Added
7
11
  - 支持多任务执行与 multi-task 模式,提供 relay(接力)、serial(串行)、serial-continue(串行继续)、parallel(并行)四种执行方式。
package/README.md CHANGED
@@ -82,6 +82,13 @@ weekly = "run --task \"补充测试\" --run-tests"
82
82
  wheel-ai set alias daily --task "补充文档" --run-tests
83
83
  ```
84
84
 
85
+ 也可以直接执行 alias,并在末尾追加命令:
86
+ ```bash
87
+ wheel-ai alias run daily --run-e2e --task "补充 e2e"
88
+ ```
89
+ - `<addition...>` 支持多个命令/参数组合,等价于将追加内容放在 alias 命令末尾。
90
+ - 当追加的命令/参数与 alias 内已有内容重复时,以追加的为准。
91
+
85
92
  ## 持久化记忆
86
93
  - `docs/ai-workflow.md`:AI 执行前的工作流基线,需作为提示前置输入。
87
94
  - `memory/plan.md`:分阶段计划(可被 AI 重写保持最新)。
package/dist/cli.js CHANGED
@@ -3802,6 +3802,44 @@ async function tailLogFile(options) {
3802
3802
 
3803
3803
  // src/cli.ts
3804
3804
  var FOREGROUND_CHILD_ENV = "WHEEL_AI_FOREGROUND_CHILD";
3805
+ var RUN_OPTION_SPECS = [
3806
+ { name: "task", flags: ["-t", "--task"], valueMode: "single" },
3807
+ { name: "iterations", flags: ["-i", "--iterations"], valueMode: "single" },
3808
+ { name: "ai-cli", flags: ["--ai-cli"], valueMode: "single" },
3809
+ { name: "ai-args", flags: ["--ai-args"], valueMode: "variadic" },
3810
+ { name: "ai-prompt-arg", flags: ["--ai-prompt-arg"], valueMode: "single" },
3811
+ { name: "notes-file", flags: ["--notes-file"], valueMode: "single" },
3812
+ { name: "plan-file", flags: ["--plan-file"], valueMode: "single" },
3813
+ { name: "workflow-doc", flags: ["--workflow-doc"], valueMode: "single" },
3814
+ { name: "worktree", flags: ["--worktree"], valueMode: "none" },
3815
+ { name: "branch", flags: ["--branch"], valueMode: "single" },
3816
+ { name: "worktree-path", flags: ["--worktree-path"], valueMode: "single" },
3817
+ { name: "base-branch", flags: ["--base-branch"], valueMode: "single" },
3818
+ { name: "skip-install", flags: ["--skip-install"], valueMode: "none" },
3819
+ { name: "run-tests", flags: ["--run-tests"], valueMode: "none" },
3820
+ { name: "run-e2e", flags: ["--run-e2e"], valueMode: "none" },
3821
+ { name: "unit-command", flags: ["--unit-command"], valueMode: "single" },
3822
+ { name: "e2e-command", flags: ["--e2e-command"], valueMode: "single" },
3823
+ { name: "auto-commit", flags: ["--auto-commit"], valueMode: "none" },
3824
+ { name: "auto-push", flags: ["--auto-push"], valueMode: "none" },
3825
+ { name: "pr", flags: ["--pr"], valueMode: "none" },
3826
+ { name: "pr-title", flags: ["--pr-title"], valueMode: "single" },
3827
+ { name: "pr-body", flags: ["--pr-body"], valueMode: "single" },
3828
+ { name: "draft", flags: ["--draft"], valueMode: "none" },
3829
+ { name: "reviewer", flags: ["--reviewer"], valueMode: "variadic" },
3830
+ { name: "auto-merge", flags: ["--auto-merge"], valueMode: "none" },
3831
+ { name: "webhook", flags: ["--webhook"], valueMode: "single" },
3832
+ { name: "webhook-timeout", flags: ["--webhook-timeout"], valueMode: "single" },
3833
+ { name: "multi-task-mode", flags: ["--multi-task-mode"], valueMode: "single" },
3834
+ { name: "stop-signal", flags: ["--stop-signal"], valueMode: "single" },
3835
+ { name: "log-file", flags: ["--log-file"], valueMode: "single" },
3836
+ { name: "background", flags: ["--background"], valueMode: "none" },
3837
+ { name: "verbose", flags: ["-v", "--verbose"], valueMode: "none" },
3838
+ { name: "skip-quality", flags: ["--skip-quality"], valueMode: "none" }
3839
+ ];
3840
+ var RUN_OPTION_FLAG_MAP = new Map(
3841
+ RUN_OPTION_SPECS.flatMap((spec) => spec.flags.map((flag) => [flag, spec]))
3842
+ );
3805
3843
  function parseInteger(value, defaultValue) {
3806
3844
  const parsed = Number.parseInt(value, 10);
3807
3845
  if (Number.isNaN(parsed)) return defaultValue;
@@ -3837,6 +3875,102 @@ function extractAliasCommandArgs(argv, name) {
3837
3875
  if (rest[0] === "--") return rest.slice(1);
3838
3876
  return rest;
3839
3877
  }
3878
+ function isAliasCommandToken(token) {
3879
+ return token === "alias" || token === "aliases";
3880
+ }
3881
+ function extractAliasRunArgs(argv, name) {
3882
+ const args = argv.slice(2);
3883
+ const start = args.findIndex(
3884
+ (arg, index) => isAliasCommandToken(arg) && args[index + 1] === "run" && args[index + 2] === name
3885
+ );
3886
+ if (start < 0) return [];
3887
+ const rest = args.slice(start + 3);
3888
+ if (rest[0] === "--") return rest.slice(1);
3889
+ return rest;
3890
+ }
3891
+ function normalizeAliasCommandArgs(args) {
3892
+ let start = 0;
3893
+ if (args[start] === "wheel-ai") {
3894
+ start += 1;
3895
+ }
3896
+ if (args[start] === "run") {
3897
+ start += 1;
3898
+ }
3899
+ return args.slice(start);
3900
+ }
3901
+ function resolveRunOptionSpec(token) {
3902
+ const equalIndex = token.indexOf("=");
3903
+ const flag = equalIndex > 0 ? token.slice(0, equalIndex) : token;
3904
+ const spec = RUN_OPTION_FLAG_MAP.get(flag);
3905
+ if (!spec) return null;
3906
+ if (equalIndex > 0) {
3907
+ return { spec, inlineValue: token.slice(equalIndex + 1) };
3908
+ }
3909
+ return { spec };
3910
+ }
3911
+ function parseArgSegments(tokens) {
3912
+ const segments = [];
3913
+ let index = 0;
3914
+ while (index < tokens.length) {
3915
+ const token = tokens[index];
3916
+ if (token === "--") {
3917
+ segments.push({ tokens: tokens.slice(index) });
3918
+ break;
3919
+ }
3920
+ const match = resolveRunOptionSpec(token);
3921
+ if (!match) {
3922
+ segments.push({ tokens: [token] });
3923
+ index += 1;
3924
+ continue;
3925
+ }
3926
+ if (match.inlineValue !== void 0) {
3927
+ segments.push({ name: match.spec.name, tokens: [token] });
3928
+ index += 1;
3929
+ continue;
3930
+ }
3931
+ if (match.spec.valueMode === "none") {
3932
+ segments.push({ name: match.spec.name, tokens: [token] });
3933
+ index += 1;
3934
+ continue;
3935
+ }
3936
+ if (match.spec.valueMode === "single") {
3937
+ const next = tokens[index + 1];
3938
+ if (next !== void 0) {
3939
+ segments.push({ name: match.spec.name, tokens: [token, next] });
3940
+ index += 2;
3941
+ } else {
3942
+ segments.push({ name: match.spec.name, tokens: [token] });
3943
+ index += 1;
3944
+ }
3945
+ continue;
3946
+ }
3947
+ const values = [];
3948
+ let cursor = index + 1;
3949
+ while (cursor < tokens.length) {
3950
+ const next = tokens[cursor];
3951
+ if (next === "--") break;
3952
+ const nextMatch = resolveRunOptionSpec(next);
3953
+ if (nextMatch) break;
3954
+ values.push(next);
3955
+ cursor += 1;
3956
+ }
3957
+ segments.push({ name: match.spec.name, tokens: [token, ...values] });
3958
+ index = cursor;
3959
+ }
3960
+ return segments;
3961
+ }
3962
+ function mergeAliasCommandArgs(aliasTokens, additionTokens) {
3963
+ const aliasSegments = parseArgSegments(aliasTokens);
3964
+ const additionSegments = parseArgSegments(additionTokens);
3965
+ const overrideNames = new Set(
3966
+ additionSegments.flatMap((segment) => segment.name ? [segment.name] : [])
3967
+ );
3968
+ const merged = [
3969
+ ...aliasSegments.filter((segment) => !segment.name || !overrideNames.has(segment.name)),
3970
+ ...additionSegments
3971
+ ];
3972
+ return merged.flatMap((segment) => segment.tokens);
3973
+ }
3840
3974
  async function runForegroundWithDetach(options) {
3841
3975
  const args = buildBackgroundArgs(options.argv, options.logFile, options.branchName, options.injectBranch);
3842
3976
  const child = (0, import_node_child_process.spawn)(process.execPath, [...process.execArgv, ...args], {
@@ -3921,6 +4055,10 @@ async function runCli(argv) {
3921
4055
  const effectiveArgv = applyShortcutArgv(argv, globalConfig);
3922
4056
  const program = new import_commander.Command();
3923
4057
  program.name("wheel-ai").description("\u57FA\u4E8E AI CLI \u7684\u6301\u7EED\u8FED\u4EE3\u5F00\u53D1\u5DE5\u5177").version("1.0.0");
4058
+ program.addHelpText(
4059
+ "after",
4060
+ "\n\u522B\u540D\u6267\u884C\uFF1A\n wheel-ai alias run <alias> <addition...>\n \u8FFD\u52A0\u547D\u4EE4\u4E0E alias \u91CD\u53E0\u65F6\uFF0C\u4EE5\u8FFD\u52A0\u4E3A\u51C6\u3002\n"
4061
+ );
3924
4062
  program.command("run").option("-t, --task <task>", "\u9700\u8981\u5B8C\u6210\u7684\u4EFB\u52A1\u63CF\u8FF0\uFF08\u53EF\u91CD\u590D\u4F20\u5165\uFF0C\u72EC\u7ACB\u5904\u7406\uFF09", collect, []).option("-i, --iterations <number>", "\u6700\u5927\u8FED\u4EE3\u6B21\u6570", (value) => parseInteger(value, 5), 5).option("--ai-cli <command>", "AI CLI \u547D\u4EE4", "claude").option("--ai-args <args...>", "AI CLI \u53C2\u6570", []).option("--ai-prompt-arg <flag>", "\u7528\u4E8E\u4F20\u5165 prompt \u7684\u53C2\u6570\uFF08\u4E3A\u7A7A\u5219\u4F7F\u7528 stdin\uFF09").option("--notes-file <path>", "\u6301\u4E45\u5316\u8BB0\u5FC6\u6587\u4EF6", defaultNotesPath()).option("--plan-file <path>", "\u8BA1\u5212\u6587\u4EF6", defaultPlanPath()).option("--workflow-doc <path>", "AI \u5DE5\u4F5C\u6D41\u7A0B\u8BF4\u660E\u6587\u4EF6", defaultWorkflowDoc()).option("--worktree", "\u5728\u72EC\u7ACB worktree \u4E0A\u6267\u884C", false).option("--branch <name>", "worktree \u5206\u652F\u540D\uFF08\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210\u6216\u5F53\u524D\u5206\u652F\uFF09").option("--worktree-path <path>", "worktree \u8DEF\u5F84\uFF0C\u9ED8\u8BA4 ../worktrees/<branch>").option("--base-branch <name>", "\u521B\u5EFA\u5206\u652F\u7684\u57FA\u7EBF\u5206\u652F", "main").option("--skip-install", "\u8DF3\u8FC7\u5F00\u59CB\u4EFB\u52A1\u524D\u7684\u4F9D\u8D56\u68C0\u67E5", false).option("--run-tests", "\u8FD0\u884C\u5355\u5143\u6D4B\u8BD5\u547D\u4EE4", false).option("--run-e2e", "\u8FD0\u884C e2e \u6D4B\u8BD5\u547D\u4EE4", false).option("--unit-command <cmd>", "\u5355\u5143\u6D4B\u8BD5\u547D\u4EE4", "yarn test").option("--e2e-command <cmd>", "e2e \u6D4B\u8BD5\u547D\u4EE4", "yarn e2e").option("--auto-commit", "\u81EA\u52A8 git commit", false).option("--auto-push", "\u81EA\u52A8 git push", false).option("--pr", "\u4F7F\u7528 gh \u521B\u5EFA PR", false).option("--pr-title <title>", "PR \u6807\u9898").option("--pr-body <path>", "PR \u63CF\u8FF0\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u7559\u7A7A\u81EA\u52A8\u751F\u6210\uFF09").option("--draft", "\u4EE5\u8349\u7A3F\u5F62\u5F0F\u521B\u5EFA PR", false).option("--reviewer <user...>", "PR reviewers", collect, []).option("--auto-merge", "PR \u68C0\u67E5\u901A\u8FC7\u540E\u81EA\u52A8\u5408\u5E76", false).option("--webhook <url>", "webhook \u901A\u77E5 URL\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("--webhook-timeout <ms>", "webhook \u8BF7\u6C42\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09", (value) => parseInteger(value, 8e3)).option("--multi-task-mode <mode>", "\u591A\u4EFB\u52A1\u6267\u884C\u6A21\u5F0F\uFF08relay/serial/serial-continue/parallel\uFF0C\u6216\u4E2D\u6587\u63CF\u8FF0\uFF09", "relay").option("--stop-signal <token>", "AI \u8F93\u51FA\u4E2D\u7684\u505C\u6B62\u6807\u8BB0", "<<DONE>>").option("--log-file <path>", "\u65E5\u5FD7\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84").option("--background", "\u5207\u5165\u540E\u53F0\u8FD0\u884C", false).option("-v, --verbose", "\u8F93\u51FA\u8C03\u8BD5\u65E5\u5FD7", false).option("--skip-quality", "\u8DF3\u8FC7\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5", false).action(async (options) => {
3925
4063
  const tasks = normalizeTaskList(options.task);
3926
4064
  if (tasks.length === 0) {
@@ -4097,7 +4235,39 @@ async function runCli(argv) {
4097
4235
  await upsertAliasEntry(normalized, commandLine);
4098
4236
  console.log(`\u5DF2\u5199\u5165 alias\uFF1A${normalized}`);
4099
4237
  });
4100
- program.command("alias").alias("aliases").description("\u6D4F\u89C8\u5168\u5C40 alias \u914D\u7F6E").action(async () => {
4238
+ const aliasCommand = program.command("alias").alias("aliases").description("\u6D4F\u89C8\u5168\u5C40 alias \u914D\u7F6E\uFF08alias run \u53EF\u6267\u884C\u5E76\u8FFD\u52A0\u547D\u4EE4\uFF09");
4239
+ aliasCommand.command("run <name> [addition...]").description("\u6267\u884C alias \u5E76\u8FFD\u52A0\u547D\u4EE4").allowUnknownOption(true).allowExcessArguments(true).action(async (name) => {
4240
+ const normalized = normalizeAliasName(name);
4241
+ if (!normalized) {
4242
+ throw new Error("alias \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
4243
+ }
4244
+ const filePath = getGlobalConfigPath();
4245
+ const exists = await import_fs_extra12.default.pathExists(filePath);
4246
+ if (!exists) {
4247
+ throw new Error(`\u672A\u627E\u5230 alias \u914D\u7F6E\u6587\u4EF6\uFF1A${filePath}`);
4248
+ }
4249
+ const content = await import_fs_extra12.default.readFile(filePath, "utf8");
4250
+ const entries = parseAliasEntries(content);
4251
+ const entry = entries.find((item) => item.name === normalized);
4252
+ if (!entry) {
4253
+ throw new Error(`\u672A\u627E\u5230 alias\uFF1A${normalized}`);
4254
+ }
4255
+ const aliasTokens = normalizeAliasCommandArgs(splitCommandArgs(entry.command));
4256
+ const additionTokens = extractAliasRunArgs(effectiveArgv, normalized);
4257
+ const mergedTokens = mergeAliasCommandArgs(aliasTokens, additionTokens);
4258
+ if (mergedTokens.length === 0) {
4259
+ throw new Error("alias \u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A");
4260
+ }
4261
+ const nextArgv = [process.argv[0], process.argv[1], "run", ...mergedTokens];
4262
+ const originalArgv = process.argv;
4263
+ process.argv = nextArgv;
4264
+ try {
4265
+ await runCli(nextArgv);
4266
+ } finally {
4267
+ process.argv = originalArgv;
4268
+ }
4269
+ });
4270
+ aliasCommand.action(async () => {
4101
4271
  await runAliasViewer();
4102
4272
  });
4103
4273
  await program.parseAsync(effectiveArgv);