agentplane 0.1.0 → 0.1.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/dist/run-cli.js CHANGED
@@ -4,7 +4,7 @@ import { chmod, cp, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, wri
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { promisify } from "node:util";
7
- import { defaultConfig, extractTaskSuffix, findGitRoot, getBaseBranch, getStagedFiles, getUnstagedFiles, lintTasksFile, loadConfig, renderTaskReadme, resolveProject, saveConfig, setByDottedKey, setPinnedBaseBranch, taskReadmePath, validateCommitSubject, validateTaskDocMetadata, } from "@agentplane/core";
7
+ import { defaultConfig, extractTaskSuffix, findGitRoot, getBaseBranch, getStagedFiles, getUnstagedFiles, lintTasksFile, loadConfig, renderTaskReadme, resolveProject, saveConfig, setByDottedKey, setPinnedBaseBranch, taskReadmePath, validateCommitSubject, validateTaskDocMetadata, } from "@agentplaneorg/core";
8
8
  import { renderHelp } from "./help.js";
9
9
  import { listRoles, renderQuickstart, renderRole } from "./command-guide.js";
10
10
  import { formatCommentBodyForCommit } from "./comment-format.js";
@@ -52,7 +52,11 @@ function parseGlobalArgs(argv) {
52
52
  if (arg === "--root") {
53
53
  const next = argv[i + 1];
54
54
  if (!next)
55
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Missing value for --root" });
55
+ throw new CliError({
56
+ exitCode: 2,
57
+ code: "E_USAGE",
58
+ message: "Missing value after --root (expected repository path)",
59
+ });
56
60
  root = next;
57
61
  i++;
58
62
  continue;
@@ -80,24 +84,82 @@ function writeError(err, jsonErrors) {
80
84
  }
81
85
  }
82
86
  function renderErrorHint(err) {
87
+ const command = typeof err.context?.command === "string" ? err.context.command : undefined;
88
+ const usage = command ? `agentplane ${command} --help` : "agentplane --help";
83
89
  switch (err.code) {
84
90
  case "E_USAGE": {
85
- return "Run `agentplane --help` for usage.";
91
+ return `See \`${usage}\` for usage.`;
86
92
  }
87
93
  case "E_GIT": {
88
- return "Run inside a git repository or pass --root <path>.";
94
+ if (command?.startsWith("branch")) {
95
+ return "Check git repo/branch; run `git branch` or pass --root <path>.";
96
+ }
97
+ if (command === "guard commit" || command === "commit") {
98
+ return "Check git status/index; stage changes and retry.";
99
+ }
100
+ return "Check git repo context; pass --root <path> if needed.";
89
101
  }
90
102
  case "E_NETWORK": {
91
- return "Check network connectivity and credentials.";
103
+ return "Check network access and credentials.";
92
104
  }
93
105
  case "E_BACKEND": {
94
- return "Check backend configuration in .agentplane/backends.";
106
+ if (command?.includes("sync")) {
107
+ return "Check backend config under .agentplane/backends and retry.";
108
+ }
109
+ return "Check backend config under .agentplane/backends.";
95
110
  }
96
111
  default: {
97
112
  return undefined;
98
113
  }
99
114
  }
100
115
  }
116
+ function missingValueMessage(flag) {
117
+ return `Missing value for ${flag} (expected value after flag)`;
118
+ }
119
+ function invalidValueMessage(label, value, expected) {
120
+ return `Invalid ${label}: ${value} (expected ${expected})`;
121
+ }
122
+ function invalidValueForFlag(flag, value, expected) {
123
+ return invalidValueMessage(`value for ${flag}`, value, expected);
124
+ }
125
+ function unknownEntityMessage(entity, value) {
126
+ return `Unknown ${entity}: ${value}`;
127
+ }
128
+ function emptyStateMessage(resource, hint) {
129
+ return `No ${resource} found.${hint ? ` ${hint}` : ""}`;
130
+ }
131
+ function requiredFieldMessage(field, source) {
132
+ return `Missing required field: ${field}${source ? ` (${source})` : ""}`;
133
+ }
134
+ function invalidFieldMessage(field, expected, source) {
135
+ return `Invalid field ${field}: expected ${expected}${source ? ` (${source})` : ""}`;
136
+ }
137
+ function invalidPathMessage(field, reason, source) {
138
+ return `Invalid ${field}: ${reason}${source ? ` (${source})` : ""}`;
139
+ }
140
+ function missingFileMessage(filename, rootHint) {
141
+ return `Missing ${filename}${rootHint ? ` at ${rootHint}` : ""}`;
142
+ }
143
+ function successMessage(action, target, details) {
144
+ const base = target ? `${action} ${target}` : action;
145
+ const suffix = details ? ` (${details})` : "";
146
+ return `✅ ${base}${suffix}`;
147
+ }
148
+ function infoMessage(message) {
149
+ return `ℹ️ ${message}`;
150
+ }
151
+ function warnMessage(message) {
152
+ return `⚠️ ${message}`;
153
+ }
154
+ function usageMessage(usage, example) {
155
+ return example ? `${usage}\nExample: ${example}` : usage;
156
+ }
157
+ function backendNotSupportedMessage(feature) {
158
+ return `Backend does not support ${feature}`;
159
+ }
160
+ function workflowModeMessage(actual, expected) {
161
+ return `Invalid workflow_mode: ${actual ?? "unknown"} (expected ${expected})`;
162
+ }
101
163
  function mapCoreError(err, context) {
102
164
  const message = err instanceof Error ? err.message : String(err);
103
165
  if (message.startsWith("Not a git repository")) {
@@ -254,30 +316,78 @@ const RECIPES_SCENARIOS_DIR_NAME = "scenarios";
254
316
  const RECIPES_SCENARIOS_INDEX_NAME = "scenarios.json";
255
317
  const RECIPES_REMOTE_INDEX_NAME = "recipes-index.json";
256
318
  const RECIPE_USAGE = "Usage: agentplane recipes <list|info|explain|install|remove|list-remote|cache> [args]";
319
+ const RECIPE_USAGE_EXAMPLE = "agentplane recipes list";
257
320
  const RECIPE_INFO_USAGE = "Usage: agentplane recipes info <id>";
321
+ const RECIPE_INFO_USAGE_EXAMPLE = "agentplane recipes info viewer";
258
322
  const RECIPE_EXPLAIN_USAGE = "Usage: agentplane recipes explain <id>";
323
+ const RECIPE_EXPLAIN_USAGE_EXAMPLE = "agentplane recipes explain viewer";
259
324
  const RECIPE_INSTALL_USAGE = "Usage: agentplane recipes install --name <id> [--index <path|url>] [--refresh] | --path <path> | --url <url>";
325
+ const RECIPE_INSTALL_USAGE_EXAMPLE = "agentplane recipes install --name viewer";
260
326
  const RECIPE_REMOVE_USAGE = "Usage: agentplane recipes remove <id>";
327
+ const RECIPE_REMOVE_USAGE_EXAMPLE = "agentplane recipes remove viewer";
261
328
  const RECIPE_CACHE_USAGE = "Usage: agentplane recipes cache <prune> [args]";
329
+ const RECIPE_CACHE_USAGE_EXAMPLE = "agentplane recipes cache prune --dry-run";
262
330
  const RECIPE_CACHE_PRUNE_USAGE = "Usage: agentplane recipes cache prune [--dry-run] [--all]";
331
+ const RECIPE_CACHE_PRUNE_USAGE_EXAMPLE = "agentplane recipes cache prune --dry-run";
263
332
  const RECIPE_LIST_REMOTE_USAGE = "Usage: agentplane recipes list-remote [--refresh] [--index <path|url>]";
333
+ const RECIPE_LIST_REMOTE_USAGE_EXAMPLE = "agentplane recipes list-remote --refresh";
264
334
  const DEFAULT_RECIPES_INDEX_URL = "https://raw.githubusercontent.com/basilisk-labs/agentplane-recipes/main/index.json";
265
335
  const RECIPE_CONFLICT_MODES = ["fail", "rename", "overwrite"];
266
336
  const AGENTPLANE_HOME_ENV = "AGENTPLANE_HOME";
267
337
  const GLOBAL_RECIPES_DIR_NAME = "recipes";
268
338
  const PROJECT_RECIPES_CACHE_DIR_NAME = "recipes-cache";
269
339
  const SCENARIO_USAGE = "Usage: agentplane scenario <list|info|run> [args]";
340
+ const SCENARIO_USAGE_EXAMPLE = "agentplane scenario list";
270
341
  const SCENARIO_INFO_USAGE = "Usage: agentplane scenario info <recipe:scenario>";
342
+ const SCENARIO_INFO_USAGE_EXAMPLE = "agentplane scenario info viewer:demo";
271
343
  const SCENARIO_RUN_USAGE = "Usage: agentplane scenario run <recipe:scenario>";
344
+ const SCENARIO_RUN_USAGE_EXAMPLE = "agentplane scenario run viewer:demo";
272
345
  const BACKEND_SYNC_USAGE = "Usage: agentplane backend sync <id> --direction <push|pull> [--conflict <diff|prefer-local|prefer-remote|fail>] [--yes] [--quiet]";
346
+ const BACKEND_SYNC_USAGE_EXAMPLE = "agentplane backend sync local --direction pull";
273
347
  const SYNC_USAGE = "Usage: agentplane sync [<id>] [--direction <push|pull>] [--conflict <diff|prefer-local|prefer-remote|fail>] [--yes] [--quiet]";
348
+ const SYNC_USAGE_EXAMPLE = "agentplane sync --direction push --yes";
274
349
  const READY_USAGE = "Usage: agentplane ready <task-id>";
350
+ const READY_USAGE_EXAMPLE = "agentplane ready 202602030608-F1Q8AB";
275
351
  const ROLE_USAGE = "Usage: agentplane role <role>";
352
+ const ROLE_USAGE_EXAMPLE = "agentplane role ORCHESTRATOR";
276
353
  const AGENTS_USAGE = "Usage: agentplane agents";
354
+ const AGENTS_USAGE_EXAMPLE = "agentplane agents";
277
355
  const BRANCH_BASE_USAGE = "Usage: agentplane branch base get|set <name>";
356
+ const BRANCH_BASE_USAGE_EXAMPLE = "agentplane branch base set main";
278
357
  const BRANCH_STATUS_USAGE = "Usage: agentplane branch status [--branch <name>] [--base <name>]";
358
+ const BRANCH_STATUS_USAGE_EXAMPLE = "agentplane branch status --base main";
279
359
  const BRANCH_REMOVE_USAGE = "Usage: agentplane branch remove [--branch <name>] [--worktree <path>] [--force] [--quiet]";
360
+ const BRANCH_REMOVE_USAGE_EXAMPLE = "agentplane branch remove --branch task/20260203-F1Q8AB --worktree .agentplane/worktrees/task";
280
361
  const UPGRADE_USAGE = "Usage: agentplane upgrade [--tag <tag>] [--dry-run] [--no-backup] [--source <repo-url>] [--bundle <path|url>] [--checksum <path|url>]";
362
+ const UPGRADE_USAGE_EXAMPLE = "agentplane upgrade --tag v0.1.2 --dry-run";
363
+ const INIT_USAGE = "Usage: agentplane init --ide <...> --workflow <...> --hooks <...> --require-plan-approval <...> --require-network-approval <...> [--recipes <...>] [--yes] [--force|--backup]";
364
+ const INIT_USAGE_EXAMPLE = "agentplane init --ide codex --workflow direct --hooks false --require-plan-approval true --require-network-approval true --yes";
365
+ const CONFIG_SET_USAGE = "Usage: agentplane config set <key> <value>";
366
+ const CONFIG_SET_USAGE_EXAMPLE = "agentplane config set workflow_mode branch_pr";
367
+ const MODE_SET_USAGE = "Usage: agentplane mode set <direct|branch_pr>";
368
+ const MODE_SET_USAGE_EXAMPLE = "agentplane mode set direct";
369
+ const QUICKSTART_USAGE = "Usage: agentplane quickstart";
370
+ const QUICKSTART_USAGE_EXAMPLE = "agentplane quickstart";
371
+ const TASK_UPDATE_USAGE = "Usage: agentplane task update <task-id> [flags]";
372
+ const TASK_UPDATE_USAGE_EXAMPLE = 'agentplane task update 202602030608-F1Q8AB --title "..." --owner CODER';
373
+ const TASK_SCAFFOLD_USAGE = "Usage: agentplane task scaffold <task-id> [--title <text>] [--overwrite] [--force]";
374
+ const TASK_SCAFFOLD_USAGE_EXAMPLE = "agentplane task scaffold 202602030608-F1Q8AB";
375
+ const TASK_SHOW_USAGE = "Usage: agentplane task show <task-id>";
376
+ const TASK_SHOW_USAGE_EXAMPLE = "agentplane task show 202602030608-F1Q8AB";
377
+ const TASK_SEARCH_USAGE = "Usage: agentplane task search <query> [flags]";
378
+ const TASK_SEARCH_USAGE_EXAMPLE = 'agentplane task search "cli"';
379
+ const TASK_COMMENT_USAGE = "Usage: agentplane task comment <task-id>";
380
+ const TASK_COMMENT_USAGE_EXAMPLE = 'agentplane task comment 202602030608-F1Q8AB --author CODER --body "..."';
381
+ const TASK_SET_STATUS_USAGE = "Usage: agentplane task set-status <task-id> <status> [flags]";
382
+ const TASK_SET_STATUS_USAGE_EXAMPLE = "agentplane task set-status 202602030608-F1Q8AB DONE";
383
+ const PR_GROUP_USAGE = "Usage: agentplane pr open|update|check|note <task-id>";
384
+ const PR_GROUP_USAGE_EXAMPLE = "agentplane pr open 202602030608-F1Q8AB --author CODER";
385
+ const GUARD_USAGE = "Usage: agentplane guard <subcommand>";
386
+ const GUARD_USAGE_EXAMPLE = 'agentplane guard commit 202602030608-F1Q8AB -m "✨ F1Q8AB update" --allow packages/agentplane';
387
+ const HOOKS_RUN_USAGE = "Usage: agentplane hooks run <hook>";
388
+ const HOOKS_RUN_USAGE_EXAMPLE = "agentplane hooks run pre-commit";
389
+ const HOOKS_INSTALL_USAGE = "Usage: agentplane hooks install|uninstall";
390
+ const HOOKS_INSTALL_USAGE_EXAMPLE = "agentplane hooks install";
281
391
  const DEFAULT_UPGRADE_ASSET = "agentplane-upgrade.tar.gz";
282
392
  const DEFAULT_UPGRADE_CHECKSUM_ASSET = `${DEFAULT_UPGRADE_ASSET}.sha256`;
283
393
  function parseBooleanFlag(value, flag) {
@@ -289,7 +399,7 @@ function parseBooleanFlag(value, flag) {
289
399
  throw new CliError({
290
400
  exitCode: 2,
291
401
  code: "E_USAGE",
292
- message: `Invalid value for ${flag}: ${value}`,
402
+ message: invalidValueForFlag(flag, value, "true|false"),
293
403
  });
294
404
  }
295
405
  function parseInitFlags(args) {
@@ -315,7 +425,7 @@ function parseInitFlags(args) {
315
425
  }
316
426
  const next = args[i + 1];
317
427
  if (!next) {
318
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
428
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
319
429
  }
320
430
  switch (arg) {
321
431
  case "--ide": {
@@ -324,7 +434,7 @@ function parseInitFlags(args) {
324
434
  throw new CliError({
325
435
  exitCode: 2,
326
436
  code: "E_USAGE",
327
- message: "Usage: --ide <codex|cursor|windsurf>",
437
+ message: invalidValueForFlag("--ide", next, "codex|cursor|windsurf"),
328
438
  });
329
439
  }
330
440
  out.ide = normalized;
@@ -335,7 +445,7 @@ function parseInitFlags(args) {
335
445
  throw new CliError({
336
446
  exitCode: 2,
337
447
  code: "E_USAGE",
338
- message: "Usage: --workflow <direct|branch_pr>",
448
+ message: invalidValueForFlag("--workflow", next, "direct|branch_pr"),
339
449
  });
340
450
  }
341
451
  out.workflow = next;
@@ -386,7 +496,11 @@ function parseUpgradeFlags(args) {
386
496
  if (!arg)
387
497
  continue;
388
498
  if (!arg.startsWith("--")) {
389
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: UPGRADE_USAGE });
499
+ throw new CliError({
500
+ exitCode: 2,
501
+ code: "E_USAGE",
502
+ message: usageMessage(UPGRADE_USAGE, UPGRADE_USAGE_EXAMPLE),
503
+ });
390
504
  }
391
505
  if (arg === "--dry-run") {
392
506
  out.dryRun = true;
@@ -398,7 +512,7 @@ function parseUpgradeFlags(args) {
398
512
  }
399
513
  const next = args[i + 1];
400
514
  if (!next) {
401
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
515
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
402
516
  }
403
517
  switch (arg) {
404
518
  case "--source": {
@@ -426,13 +540,21 @@ function parseUpgradeFlags(args) {
426
540
  break;
427
541
  }
428
542
  default: {
429
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: UPGRADE_USAGE });
543
+ throw new CliError({
544
+ exitCode: 2,
545
+ code: "E_USAGE",
546
+ message: usageMessage(UPGRADE_USAGE, UPGRADE_USAGE_EXAMPLE),
547
+ });
430
548
  }
431
549
  }
432
550
  i++;
433
551
  }
434
552
  if ((out.bundle && !out.checksum) || (!out.bundle && out.checksum)) {
435
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: UPGRADE_USAGE });
553
+ throw new CliError({
554
+ exitCode: 2,
555
+ code: "E_USAGE",
556
+ message: usageMessage(UPGRADE_USAGE, UPGRADE_USAGE_EXAMPLE),
557
+ });
436
558
  }
437
559
  return out;
438
560
  }
@@ -443,7 +565,11 @@ function parseRecipeListRemoteFlags(args) {
443
565
  if (!arg)
444
566
  continue;
445
567
  if (!arg.startsWith("--")) {
446
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_LIST_REMOTE_USAGE });
568
+ throw new CliError({
569
+ exitCode: 2,
570
+ code: "E_USAGE",
571
+ message: usageMessage(RECIPE_LIST_REMOTE_USAGE, RECIPE_LIST_REMOTE_USAGE_EXAMPLE),
572
+ });
447
573
  }
448
574
  if (arg === "--refresh") {
449
575
  out.refresh = true;
@@ -451,7 +577,7 @@ function parseRecipeListRemoteFlags(args) {
451
577
  }
452
578
  const next = args[i + 1];
453
579
  if (!next) {
454
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
580
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
455
581
  }
456
582
  switch (arg) {
457
583
  case "--index": {
@@ -459,7 +585,11 @@ function parseRecipeListRemoteFlags(args) {
459
585
  break;
460
586
  }
461
587
  default: {
462
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_LIST_REMOTE_USAGE });
588
+ throw new CliError({
589
+ exitCode: 2,
590
+ code: "E_USAGE",
591
+ message: usageMessage(RECIPE_LIST_REMOTE_USAGE, RECIPE_LIST_REMOTE_USAGE_EXAMPLE),
592
+ });
463
593
  }
464
594
  }
465
595
  i++;
@@ -469,36 +599,36 @@ function parseRecipeListRemoteFlags(args) {
469
599
  function normalizeRecipeId(value) {
470
600
  const trimmed = value.trim();
471
601
  if (!trimmed)
472
- throw new Error("manifest.id must be non-empty");
602
+ throw new Error(requiredFieldMessage("manifest.id"));
473
603
  if (trimmed.includes("/") || trimmed.includes("\\")) {
474
- throw new Error("manifest.id must not contain path separators");
604
+ throw new Error(invalidPathMessage("manifest.id", "must not contain path separators"));
475
605
  }
476
606
  if (trimmed === "." || trimmed === "..") {
477
- throw new Error("manifest.id must not be '.' or '..'");
607
+ throw new Error(invalidPathMessage("manifest.id", "must not be '.' or '..'"));
478
608
  }
479
609
  return trimmed;
480
610
  }
481
611
  function normalizeAgentId(value) {
482
612
  const trimmed = value.trim();
483
613
  if (!trimmed)
484
- throw new Error("agent.id must be non-empty");
614
+ throw new Error(requiredFieldMessage("agent.id"));
485
615
  if (trimmed.includes("/") || trimmed.includes("\\")) {
486
- throw new Error("agent.id must not contain path separators");
616
+ throw new Error(invalidPathMessage("agent.id", "must not contain path separators"));
487
617
  }
488
618
  if (trimmed === "." || trimmed === "..") {
489
- throw new Error("agent.id must not be '.' or '..'");
619
+ throw new Error(invalidPathMessage("agent.id", "must not be '.' or '..'"));
490
620
  }
491
621
  return trimmed;
492
622
  }
493
623
  function normalizeScenarioId(value) {
494
624
  const trimmed = value.trim();
495
625
  if (!trimmed)
496
- throw new Error("scenario.id must be non-empty");
626
+ throw new Error(requiredFieldMessage("scenario.id"));
497
627
  if (trimmed.includes("/") || trimmed.includes("\\")) {
498
- throw new Error("scenario.id must not contain path separators");
628
+ throw new Error(invalidPathMessage("scenario.id", "must not contain path separators"));
499
629
  }
500
630
  if (trimmed === "." || trimmed === "..") {
501
- throw new Error("scenario.id must not be '.' or '..'");
631
+ throw new Error(invalidPathMessage("scenario.id", "must not be '.' or '..'"));
502
632
  }
503
633
  return trimmed;
504
634
  }
@@ -506,33 +636,33 @@ function normalizeRecipeTags(value) {
506
636
  if (value === undefined)
507
637
  return [];
508
638
  if (!Array.isArray(value))
509
- throw new Error("manifest.tags must be an array of strings");
639
+ throw new Error(invalidFieldMessage("manifest.tags", "string[]"));
510
640
  const tags = value.map((tag) => {
511
641
  if (typeof tag !== "string")
512
- throw new Error("manifest.tags must be an array of strings");
642
+ throw new Error(invalidFieldMessage("manifest.tags", "string[]"));
513
643
  return tag.trim();
514
644
  });
515
645
  return dedupeStrings(tags);
516
646
  }
517
647
  function validateRecipeManifest(raw) {
518
648
  if (!isRecord(raw))
519
- throw new Error("manifest must be an object");
649
+ throw new Error(invalidFieldMessage("manifest", "object"));
520
650
  if (raw.schema_version !== "1")
521
- throw new Error("manifest.schema_version must be '1'");
651
+ throw new Error(invalidFieldMessage("manifest.schema_version", '"1"'));
522
652
  if (typeof raw.id !== "string")
523
- throw new Error("manifest.id must be string");
653
+ throw new Error(invalidFieldMessage("manifest.id", "string"));
524
654
  if (typeof raw.version !== "string")
525
- throw new Error("manifest.version must be string");
655
+ throw new Error(invalidFieldMessage("manifest.version", "string"));
526
656
  if (typeof raw.name !== "string")
527
- throw new Error("manifest.name must be string");
657
+ throw new Error(invalidFieldMessage("manifest.name", "string"));
528
658
  if (typeof raw.summary !== "string")
529
- throw new Error("manifest.summary must be string");
659
+ throw new Error(invalidFieldMessage("manifest.summary", "string"));
530
660
  if (typeof raw.description !== "string")
531
- throw new Error("manifest.description must be string");
661
+ throw new Error(invalidFieldMessage("manifest.description", "string"));
532
662
  const id = normalizeRecipeId(raw.id);
533
663
  const version = raw.version.trim();
534
664
  if (!version)
535
- throw new Error("manifest.version must be non-empty");
665
+ throw new Error(requiredFieldMessage("manifest.version"));
536
666
  const tags = normalizeRecipeTags(raw.tags);
537
667
  return {
538
668
  schema_version: "1",
@@ -551,11 +681,11 @@ function validateRecipeManifest(raw) {
551
681
  }
552
682
  function validateInstalledRecipesFile(raw) {
553
683
  if (!isRecord(raw))
554
- throw new Error("recipes.json must be an object");
684
+ throw new Error(invalidFieldMessage("recipes.json", "object"));
555
685
  if (raw.schema_version !== 1)
556
- throw new Error("recipes.json schema_version must be 1");
686
+ throw new Error(invalidFieldMessage("recipes.json.schema_version", "1"));
557
687
  if (!Array.isArray(raw.recipes))
558
- throw new Error("recipes.json recipes must be array");
688
+ throw new Error(invalidFieldMessage("recipes.json.recipes", "array"));
559
689
  const updatedAt = typeof raw.updated_at === "string" ? raw.updated_at : "";
560
690
  const recipes = raw.recipes
561
691
  .filter((entry) => isRecord(entry))
@@ -566,10 +696,10 @@ function validateInstalledRecipesFile(raw) {
566
696
  const source = typeof entry.source === "string" ? entry.source.trim() : "";
567
697
  const installedAt = typeof entry.installed_at === "string" ? entry.installed_at.trim() : "";
568
698
  if (!id || !version || !source || !installedAt) {
569
- throw new Error("recipes.json entries must include id, version, source, installed_at");
699
+ throw new Error(invalidFieldMessage("recipes.json.recipes[]", "id, version, source, installed_at"));
570
700
  }
571
701
  if (id !== manifest.id || version !== manifest.version) {
572
- throw new Error("recipes.json entry id/version must match manifest");
702
+ throw new Error(invalidFieldMessage("recipes.json.recipes[]", "id/version match manifest"));
573
703
  }
574
704
  const tags = normalizeRecipeTags(entry.tags ?? manifest.tags ?? []);
575
705
  return { id, version, source, installed_at: installedAt, tags, manifest };
@@ -582,11 +712,11 @@ function sortInstalledRecipes(file) {
582
712
  }
583
713
  function validateRecipesIndex(raw) {
584
714
  if (!isRecord(raw))
585
- throw new Error("recipes index must be an object");
715
+ throw new Error(invalidFieldMessage("recipes index", "object"));
586
716
  if (raw.schema_version !== 1)
587
- throw new Error("recipes index schema_version must be 1");
717
+ throw new Error(invalidFieldMessage("recipes index.schema_version", "1"));
588
718
  if (!Array.isArray(raw.recipes))
589
- throw new Error("recipes index recipes must be array");
719
+ throw new Error(invalidFieldMessage("recipes index.recipes", "array"));
590
720
  const recipes = raw.recipes
591
721
  .filter((entry) => isRecord(entry))
592
722
  .map((entry) => {
@@ -595,7 +725,7 @@ function validateRecipesIndex(raw) {
595
725
  const description = typeof entry.description === "string" ? entry.description : undefined;
596
726
  const versionsRaw = Array.isArray(entry.versions) ? entry.versions : [];
597
727
  if (!id || !summary || versionsRaw.length === 0) {
598
- throw new Error("recipes index entries must include id, summary, and versions");
728
+ throw new Error(invalidFieldMessage("recipes index.recipes[]", "id, summary, versions"));
599
729
  }
600
730
  const versions = versionsRaw
601
731
  .filter((version) => isRecord(version))
@@ -604,7 +734,7 @@ function validateRecipesIndex(raw) {
604
734
  const url = typeof version.url === "string" ? version.url : "";
605
735
  const sha256 = typeof version.sha256 === "string" ? version.sha256 : "";
606
736
  if (!versionId || !url || !sha256) {
607
- throw new Error("recipes index versions must include version, url, sha256");
737
+ throw new Error(invalidFieldMessage("recipes index.recipes[].versions[]", "version, url, sha256"));
608
738
  }
609
739
  return {
610
740
  version: versionId,
@@ -624,21 +754,21 @@ function validateRecipesIndex(raw) {
624
754
  }
625
755
  function validateScenarioDefinition(raw, sourcePath) {
626
756
  if (!isRecord(raw))
627
- throw new Error(`scenario must be an object (${sourcePath})`);
757
+ throw new Error(invalidFieldMessage("scenario", "object", sourcePath));
628
758
  if (raw.schema_version !== undefined && raw.schema_version !== "1") {
629
- throw new Error(`scenario.schema_version must be "1" (${sourcePath})`);
759
+ throw new Error(invalidFieldMessage("scenario.schema_version", '"1"', sourcePath));
630
760
  }
631
761
  const rawId = typeof raw.id === "string" ? raw.id : "";
632
762
  const id = normalizeScenarioId(rawId);
633
763
  const goal = typeof raw.goal === "string" ? raw.goal.trim() : "";
634
764
  if (!goal)
635
- throw new Error(`scenario.goal must be non-empty (${sourcePath})`);
765
+ throw new Error(requiredFieldMessage("scenario.goal", sourcePath));
636
766
  if (!("inputs" in raw))
637
- throw new Error(`scenario.inputs is required (${sourcePath})`);
767
+ throw new Error(requiredFieldMessage("scenario.inputs", sourcePath));
638
768
  if (!("outputs" in raw))
639
- throw new Error(`scenario.outputs is required (${sourcePath})`);
769
+ throw new Error(requiredFieldMessage("scenario.outputs", sourcePath));
640
770
  if (!Array.isArray(raw.steps)) {
641
- throw new Error(`scenario.steps must be an array (${sourcePath})`);
771
+ throw new Error(invalidFieldMessage("scenario.steps", "array", sourcePath));
642
772
  }
643
773
  return {
644
774
  schema_version: "1",
@@ -658,11 +788,11 @@ async function readScenarioDefinition(filePath) {
658
788
  async function readScenarioIndex(filePath) {
659
789
  const raw = JSON.parse(await readFile(filePath, "utf8"));
660
790
  if (!isRecord(raw))
661
- throw new Error("scenarios index must be an object");
791
+ throw new Error(invalidFieldMessage("scenarios index", "object"));
662
792
  if (raw.schema_version !== 1)
663
- throw new Error("scenarios index schema_version must be 1");
793
+ throw new Error(invalidFieldMessage("scenarios index.schema_version", "1"));
664
794
  if (!Array.isArray(raw.scenarios))
665
- throw new Error("scenarios index scenarios must be array");
795
+ throw new Error(invalidFieldMessage("scenarios index.scenarios", "array"));
666
796
  const scenarios = raw.scenarios
667
797
  .filter((entry) => isRecord(entry))
668
798
  .map((entry) => ({
@@ -728,24 +858,24 @@ async function collectRecipeScenarioDetails(recipeDir, manifest) {
728
858
  }
729
859
  function normalizeScenarioToolStep(raw, sourcePath) {
730
860
  if (!isRecord(raw)) {
731
- throw new Error(`scenario step must be an object (${sourcePath})`);
861
+ throw new Error(invalidFieldMessage("scenario step", "object", sourcePath));
732
862
  }
733
863
  const tool = typeof raw.tool === "string" ? raw.tool.trim() : "";
734
864
  if (!tool) {
735
- throw new Error(`scenario step is missing tool id (${sourcePath})`);
865
+ throw new Error(requiredFieldMessage("scenario step.tool", sourcePath));
736
866
  }
737
867
  const args = Array.isArray(raw.args) ? raw.args.filter((arg) => typeof arg === "string") : [];
738
868
  if (Array.isArray(raw.args) && args.length !== raw.args.length) {
739
- throw new Error(`scenario step args must be strings (${sourcePath})`);
869
+ throw new Error(invalidFieldMessage("scenario step.args", "string[]", sourcePath));
740
870
  }
741
871
  const env = {};
742
872
  if (raw.env !== undefined) {
743
873
  if (!isRecord(raw.env)) {
744
- throw new Error(`scenario step env must be an object (${sourcePath})`);
874
+ throw new Error(invalidFieldMessage("scenario step.env", "object", sourcePath));
745
875
  }
746
876
  for (const [key, value] of Object.entries(raw.env)) {
747
877
  if (typeof value !== "string") {
748
- throw new Error(`scenario step env values must be strings (${sourcePath})`);
878
+ throw new Error(invalidFieldMessage("scenario step.env", "string map", sourcePath));
749
879
  }
750
880
  env[key] = value;
751
881
  }
@@ -783,11 +913,11 @@ async function resolveRecipeRoot(extractedDir) {
783
913
  const entries = await readdir(extractedDir, { withFileTypes: true });
784
914
  const dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
785
915
  if (dirs.length !== 1) {
786
- throw new Error("manifest.json not found at archive root");
916
+ throw new Error(missingFileMessage("manifest.json", "archive root"));
787
917
  }
788
918
  const candidate = path.join(extractedDir, dirs[0]);
789
919
  if (!(await fileExists(path.join(candidate, "manifest.json")))) {
790
- throw new Error("manifest.json not found at archive root");
920
+ throw new Error(missingFileMessage("manifest.json", "archive root"));
791
921
  }
792
922
  return candidate;
793
923
  }
@@ -823,7 +953,11 @@ function resolveProjectRecipesCacheDir(resolved) {
823
953
  async function extractArchive(archivePath, destDir) {
824
954
  const archiveType = detectArchiveType(archivePath);
825
955
  if (!archiveType) {
826
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
956
+ throw new CliError({
957
+ exitCode: 2,
958
+ code: "E_USAGE",
959
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
960
+ });
827
961
  }
828
962
  if (archiveType === "tar") {
829
963
  await execFileAsync("tar", ["-xzf", archivePath, "-C", destDir]);
@@ -879,7 +1013,11 @@ function parseRecipeInstallArgs(args) {
879
1013
  if (arg === "--name") {
880
1014
  const next = args[i + 1];
881
1015
  if (!next)
882
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1016
+ throw new CliError({
1017
+ exitCode: 2,
1018
+ code: "E_USAGE",
1019
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1020
+ });
883
1021
  name = next;
884
1022
  i++;
885
1023
  continue;
@@ -887,7 +1025,11 @@ function parseRecipeInstallArgs(args) {
887
1025
  if (arg === "--path") {
888
1026
  const next = args[i + 1];
889
1027
  if (!next)
890
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1028
+ throw new CliError({
1029
+ exitCode: 2,
1030
+ code: "E_USAGE",
1031
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1032
+ });
891
1033
  localPath = next;
892
1034
  i++;
893
1035
  continue;
@@ -895,7 +1037,11 @@ function parseRecipeInstallArgs(args) {
895
1037
  if (arg === "--url") {
896
1038
  const next = args[i + 1];
897
1039
  if (!next)
898
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1040
+ throw new CliError({
1041
+ exitCode: 2,
1042
+ code: "E_USAGE",
1043
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1044
+ });
899
1045
  url = next;
900
1046
  i++;
901
1047
  continue;
@@ -903,7 +1049,11 @@ function parseRecipeInstallArgs(args) {
903
1049
  if (arg === "--index") {
904
1050
  const next = args[i + 1];
905
1051
  if (!next)
906
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1052
+ throw new CliError({
1053
+ exitCode: 2,
1054
+ code: "E_USAGE",
1055
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1056
+ });
907
1057
  index = next;
908
1058
  i++;
909
1059
  continue;
@@ -915,28 +1065,52 @@ function parseRecipeInstallArgs(args) {
915
1065
  if (arg === "--on-conflict") {
916
1066
  const next = args[i + 1];
917
1067
  if (!next)
918
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1068
+ throw new CliError({
1069
+ exitCode: 2,
1070
+ code: "E_USAGE",
1071
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1072
+ });
919
1073
  if (!RECIPE_CONFLICT_MODES.includes(next)) {
920
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1074
+ throw new CliError({
1075
+ exitCode: 2,
1076
+ code: "E_USAGE",
1077
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1078
+ });
921
1079
  }
922
1080
  onConflict = next;
923
1081
  i++;
924
1082
  continue;
925
1083
  }
926
1084
  if (arg.startsWith("--")) {
927
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1085
+ throw new CliError({
1086
+ exitCode: 2,
1087
+ code: "E_USAGE",
1088
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1089
+ });
928
1090
  }
929
1091
  positional.push(arg);
930
1092
  }
931
1093
  const explicitFlags = [name, localPath, url].filter(Boolean).length;
932
1094
  if (explicitFlags > 1) {
933
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1095
+ throw new CliError({
1096
+ exitCode: 2,
1097
+ code: "E_USAGE",
1098
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1099
+ });
934
1100
  }
935
1101
  if (positional.length > 1) {
936
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1102
+ throw new CliError({
1103
+ exitCode: 2,
1104
+ code: "E_USAGE",
1105
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1106
+ });
937
1107
  }
938
1108
  if (positional.length > 0 && explicitFlags > 0) {
939
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1109
+ throw new CliError({
1110
+ exitCode: 2,
1111
+ code: "E_USAGE",
1112
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1113
+ });
940
1114
  }
941
1115
  if (name)
942
1116
  return { source: { type: "name", value: name }, index, refresh, onConflict };
@@ -947,7 +1121,11 @@ function parseRecipeInstallArgs(args) {
947
1121
  if (positional.length === 1) {
948
1122
  return { source: { type: "auto", value: positional[0] }, index, refresh, onConflict };
949
1123
  }
950
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INSTALL_USAGE });
1124
+ throw new CliError({
1125
+ exitCode: 2,
1126
+ code: "E_USAGE",
1127
+ message: usageMessage(RECIPE_INSTALL_USAGE, RECIPE_INSTALL_USAGE_EXAMPLE),
1128
+ });
951
1129
  }
952
1130
  function parseRecipeListArgs(args) {
953
1131
  const flags = { full: false };
@@ -962,18 +1140,34 @@ function parseRecipeListArgs(args) {
962
1140
  if (arg === "--tag") {
963
1141
  const next = args[i + 1];
964
1142
  if (!next)
965
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_USAGE });
1143
+ throw new CliError({
1144
+ exitCode: 2,
1145
+ code: "E_USAGE",
1146
+ message: usageMessage(RECIPE_USAGE, RECIPE_USAGE_EXAMPLE),
1147
+ });
966
1148
  flags.tag = next.trim();
967
1149
  i++;
968
1150
  continue;
969
1151
  }
970
1152
  if (arg.startsWith("--")) {
971
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_USAGE });
1153
+ throw new CliError({
1154
+ exitCode: 2,
1155
+ code: "E_USAGE",
1156
+ message: usageMessage(RECIPE_USAGE, RECIPE_USAGE_EXAMPLE),
1157
+ });
972
1158
  }
973
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_USAGE });
1159
+ throw new CliError({
1160
+ exitCode: 2,
1161
+ code: "E_USAGE",
1162
+ message: usageMessage(RECIPE_USAGE, RECIPE_USAGE_EXAMPLE),
1163
+ });
974
1164
  }
975
1165
  if (flags.tag !== undefined && !flags.tag) {
976
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_USAGE });
1166
+ throw new CliError({
1167
+ exitCode: 2,
1168
+ code: "E_USAGE",
1169
+ message: usageMessage(RECIPE_USAGE, RECIPE_USAGE_EXAMPLE),
1170
+ });
977
1171
  }
978
1172
  return flags;
979
1173
  }
@@ -990,7 +1184,11 @@ function parseRecipeCachePruneArgs(args) {
990
1184
  flags.all = true;
991
1185
  continue;
992
1186
  }
993
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_CACHE_PRUNE_USAGE });
1187
+ throw new CliError({
1188
+ exitCode: 2,
1189
+ code: "E_USAGE",
1190
+ message: usageMessage(RECIPE_CACHE_PRUNE_USAGE, RECIPE_CACHE_PRUNE_USAGE_EXAMPLE),
1191
+ });
994
1192
  }
995
1193
  return flags;
996
1194
  }
@@ -1006,7 +1204,11 @@ function parseBackendSyncArgs(args) {
1006
1204
  continue;
1007
1205
  if (!arg.startsWith("--")) {
1008
1206
  if (backendId) {
1009
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BACKEND_SYNC_USAGE });
1207
+ throw new CliError({
1208
+ exitCode: 2,
1209
+ code: "E_USAGE",
1210
+ message: usageMessage(BACKEND_SYNC_USAGE, BACKEND_SYNC_USAGE_EXAMPLE),
1211
+ });
1010
1212
  }
1011
1213
  backendId = arg;
1012
1214
  continue;
@@ -1014,7 +1216,11 @@ function parseBackendSyncArgs(args) {
1014
1216
  if (arg === "--direction") {
1015
1217
  const next = args[i + 1];
1016
1218
  if (!next || (next !== "push" && next !== "pull")) {
1017
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BACKEND_SYNC_USAGE });
1219
+ throw new CliError({
1220
+ exitCode: 2,
1221
+ code: "E_USAGE",
1222
+ message: usageMessage(BACKEND_SYNC_USAGE, BACKEND_SYNC_USAGE_EXAMPLE),
1223
+ });
1018
1224
  }
1019
1225
  direction = next;
1020
1226
  i++;
@@ -1023,7 +1229,11 @@ function parseBackendSyncArgs(args) {
1023
1229
  if (arg === "--conflict") {
1024
1230
  const next = args[i + 1];
1025
1231
  if (!next || !["diff", "prefer-local", "prefer-remote", "fail"].includes(next)) {
1026
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BACKEND_SYNC_USAGE });
1232
+ throw new CliError({
1233
+ exitCode: 2,
1234
+ code: "E_USAGE",
1235
+ message: usageMessage(BACKEND_SYNC_USAGE, BACKEND_SYNC_USAGE_EXAMPLE),
1236
+ });
1027
1237
  }
1028
1238
  conflict = next;
1029
1239
  i++;
@@ -1037,10 +1247,18 @@ function parseBackendSyncArgs(args) {
1037
1247
  quiet = true;
1038
1248
  continue;
1039
1249
  }
1040
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BACKEND_SYNC_USAGE });
1250
+ throw new CliError({
1251
+ exitCode: 2,
1252
+ code: "E_USAGE",
1253
+ message: usageMessage(BACKEND_SYNC_USAGE, BACKEND_SYNC_USAGE_EXAMPLE),
1254
+ });
1041
1255
  }
1042
1256
  if (!backendId || !direction) {
1043
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BACKEND_SYNC_USAGE });
1257
+ throw new CliError({
1258
+ exitCode: 2,
1259
+ code: "E_USAGE",
1260
+ message: usageMessage(BACKEND_SYNC_USAGE, BACKEND_SYNC_USAGE_EXAMPLE),
1261
+ });
1044
1262
  }
1045
1263
  return { backendId, direction, conflict, confirm, quiet };
1046
1264
  }
@@ -1056,7 +1274,11 @@ function parseSyncArgs(args) {
1056
1274
  continue;
1057
1275
  if (!arg.startsWith("--")) {
1058
1276
  if (backendId) {
1059
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SYNC_USAGE });
1277
+ throw new CliError({
1278
+ exitCode: 2,
1279
+ code: "E_USAGE",
1280
+ message: usageMessage(SYNC_USAGE, SYNC_USAGE_EXAMPLE),
1281
+ });
1060
1282
  }
1061
1283
  backendId = arg;
1062
1284
  continue;
@@ -1064,7 +1286,11 @@ function parseSyncArgs(args) {
1064
1286
  if (arg === "--direction") {
1065
1287
  const next = args[i + 1];
1066
1288
  if (!next || (next !== "push" && next !== "pull")) {
1067
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SYNC_USAGE });
1289
+ throw new CliError({
1290
+ exitCode: 2,
1291
+ code: "E_USAGE",
1292
+ message: usageMessage(SYNC_USAGE, SYNC_USAGE_EXAMPLE),
1293
+ });
1068
1294
  }
1069
1295
  direction = next;
1070
1296
  i++;
@@ -1073,7 +1299,11 @@ function parseSyncArgs(args) {
1073
1299
  if (arg === "--conflict") {
1074
1300
  const next = args[i + 1];
1075
1301
  if (!next || !["diff", "prefer-local", "prefer-remote", "fail"].includes(next)) {
1076
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SYNC_USAGE });
1302
+ throw new CliError({
1303
+ exitCode: 2,
1304
+ code: "E_USAGE",
1305
+ message: usageMessage(SYNC_USAGE, SYNC_USAGE_EXAMPLE),
1306
+ });
1077
1307
  }
1078
1308
  conflict = next;
1079
1309
  i++;
@@ -1087,7 +1317,11 @@ function parseSyncArgs(args) {
1087
1317
  quiet = true;
1088
1318
  continue;
1089
1319
  }
1090
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SYNC_USAGE });
1320
+ throw new CliError({
1321
+ exitCode: 2,
1322
+ code: "E_USAGE",
1323
+ message: usageMessage(SYNC_USAGE, SYNC_USAGE_EXAMPLE),
1324
+ });
1091
1325
  }
1092
1326
  return { backendId, direction, conflict, confirm, quiet };
1093
1327
  }
@@ -1106,11 +1340,11 @@ async function applyRecipeAgents(opts) {
1106
1340
  const agentId = normalizeAgentId(rawId);
1107
1341
  const sourcePath = path.join(opts.recipeDir, rawFile);
1108
1342
  if (!(await fileExists(sourcePath))) {
1109
- throw new Error(`Recipe agent file not found: ${rawFile}`);
1343
+ throw new Error(missingFileMessage("recipe agent file", rawFile));
1110
1344
  }
1111
1345
  const rawAgent = JSON.parse(await readFile(sourcePath, "utf8"));
1112
1346
  if (!isRecord(rawAgent)) {
1113
- throw new Error(`Recipe agent file must be a JSON object: ${rawFile}`);
1347
+ throw new Error(invalidFieldMessage("recipe agent file", "JSON object", rawFile));
1114
1348
  }
1115
1349
  const baseId = `${opts.manifest.id}__${agentId}`;
1116
1350
  let targetId = baseId;
@@ -1191,19 +1425,19 @@ async function loadRecipesRemoteIndex(opts) {
1191
1425
  function parseGitHubRepo(source) {
1192
1426
  const trimmed = source.trim();
1193
1427
  if (!trimmed)
1194
- throw new Error("config.framework.source must be non-empty");
1428
+ throw new Error(requiredFieldMessage("config.framework.source"));
1195
1429
  if (!trimmed.includes("github.com")) {
1196
- throw new Error("upgrade supports GitHub sources only");
1430
+ throw new Error(invalidFieldMessage("config.framework.source", "GitHub URL"));
1197
1431
  }
1198
1432
  try {
1199
1433
  const url = new URL(trimmed);
1200
1434
  const parts = url.pathname.replaceAll(".git", "").split("/").filter(Boolean);
1201
1435
  if (parts.length < 2)
1202
- throw new Error("Invalid GitHub repo URL");
1436
+ throw new Error(invalidValueMessage("GitHub repo URL", trimmed, "owner/repo"));
1203
1437
  return { owner: parts[0], repo: parts[1] };
1204
1438
  }
1205
1439
  catch {
1206
- throw new Error("Invalid GitHub repo URL");
1440
+ throw new Error(invalidValueMessage("GitHub repo URL", trimmed, "owner/repo"));
1207
1441
  }
1208
1442
  }
1209
1443
  async function resolveUpgradeRoot(extractedDir) {
@@ -1260,7 +1494,7 @@ async function cmdInit(opts) {
1260
1494
  throw new CliError({
1261
1495
  exitCode: 2,
1262
1496
  code: "E_USAGE",
1263
- message: "Usage: agentplane init --ide <...> --workflow <...> --hooks <...> --require-plan-approval <...> --require-network-approval <...> [--recipes <...>] [--yes] [--force|--backup]",
1497
+ message: usageMessage(INIT_USAGE, INIT_USAGE_EXAMPLE),
1264
1498
  });
1265
1499
  }
1266
1500
  if (process.stdin.isTTY && !flags.yes) {
@@ -1413,10 +1647,10 @@ async function cmdInit(opts) {
1413
1647
  }
1414
1648
  if (recipes.length > 0) {
1415
1649
  if (listBundledRecipes().length === 0) {
1416
- process.stdout.write("Bundled recipes catalog is empty; skipping install.\n");
1650
+ process.stdout.write(`${infoMessage("bundled recipes are empty; nothing to install")}\n`);
1417
1651
  }
1418
1652
  else {
1419
- process.stdout.write("Recipes install is not implemented yet; skipping.\n");
1653
+ process.stdout.write(`${infoMessage("bundled recipe install is not implemented; skipping")}\n`);
1420
1654
  }
1421
1655
  }
1422
1656
  await ensureInitCommit({
@@ -1636,13 +1870,21 @@ function cmdRole(opts) {
1636
1870
  try {
1637
1871
  const roleRaw = opts.role.trim();
1638
1872
  if (!roleRaw) {
1639
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: ROLE_USAGE });
1873
+ throw new CliError({
1874
+ exitCode: 2,
1875
+ code: "E_USAGE",
1876
+ message: usageMessage(ROLE_USAGE, ROLE_USAGE_EXAMPLE),
1877
+ });
1640
1878
  }
1641
1879
  const guide = renderRole(roleRaw);
1642
1880
  if (!guide) {
1643
1881
  const roles = listRoles();
1644
1882
  const available = roles.length > 0 ? `\nAvailable roles: ${roles.join(", ")}` : "";
1645
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `${ROLE_USAGE}${available}` });
1883
+ throw new CliError({
1884
+ exitCode: 2,
1885
+ code: "E_USAGE",
1886
+ message: usageMessage(`${ROLE_USAGE}${available}`, ROLE_USAGE_EXAMPLE),
1887
+ });
1646
1888
  }
1647
1889
  process.stdout.write(`${guide}\n`);
1648
1890
  return 0;
@@ -1675,7 +1917,7 @@ async function cmdAgents(opts) {
1675
1917
  throw new CliError({
1676
1918
  exitCode: 2,
1677
1919
  code: "E_USAGE",
1678
- message: `Missing directory: ${agentsDir}`,
1920
+ message: `Agents directory not found: ${agentsDir} (run \`agentplane init\`)`,
1679
1921
  });
1680
1922
  }
1681
1923
  const entriesRaw = await readdir(agentsDir);
@@ -1684,7 +1926,7 @@ async function cmdAgents(opts) {
1684
1926
  throw new CliError({
1685
1927
  exitCode: 2,
1686
1928
  code: "E_USAGE",
1687
- message: `No agents found under ${agentsDir}`,
1929
+ message: `No agent definitions found under ${agentsDir} (expected *.json)`,
1688
1930
  });
1689
1931
  }
1690
1932
  const rows = [];
@@ -1739,10 +1981,10 @@ async function cmdRecipeList(opts) {
1739
1981
  }
1740
1982
  if (recipes.length === 0) {
1741
1983
  if (flags.tag) {
1742
- process.stdout.write(`No recipes matched tag: ${flags.tag}\n`);
1984
+ process.stdout.write(`${emptyStateMessage(`installed recipes for tag ${flags.tag}`)}\n`);
1743
1985
  return 0;
1744
1986
  }
1745
- process.stdout.write("No recipes installed.\n");
1987
+ process.stdout.write(`${emptyStateMessage("installed recipes", "Use `agentplane recipes list-remote` or `agentplane recipes install <id>`.")}\n`);
1746
1988
  return 0;
1747
1989
  }
1748
1990
  if (flags.full) {
@@ -1755,7 +1997,7 @@ async function cmdRecipeList(opts) {
1755
1997
  return 0;
1756
1998
  }
1757
1999
  for (const entry of recipes) {
1758
- process.stdout.write(`${entry.id}@${entry.version} - ${entry.manifest.summary || "No summary provided."}\n`);
2000
+ process.stdout.write(`${entry.id}@${entry.version} - ${entry.manifest.summary || "No summary"}\n`);
1759
2001
  }
1760
2002
  return 0;
1761
2003
  }
@@ -1818,7 +2060,7 @@ async function cmdScenarioList(opts) {
1818
2060
  }
1819
2061
  }
1820
2062
  if (entries.length === 0) {
1821
- process.stdout.write("No scenarios installed.\n");
2063
+ process.stdout.write(`${emptyStateMessage("scenarios", "Install a recipe to add scenarios.")}\n`);
1822
2064
  return 0;
1823
2065
  }
1824
2066
  const sorted = entries.toSorted((a, b) => {
@@ -1828,7 +2070,7 @@ async function cmdScenarioList(opts) {
1828
2070
  return a.scenarioId.localeCompare(b.scenarioId);
1829
2071
  });
1830
2072
  for (const entry of sorted) {
1831
- process.stdout.write(`${entry.recipeId}:${entry.scenarioId} - ${entry.summary ?? "No summary provided."}\n`);
2073
+ process.stdout.write(`${entry.recipeId}:${entry.scenarioId} - ${entry.summary ?? "No summary"}\n`);
1832
2074
  }
1833
2075
  return 0;
1834
2076
  }
@@ -1842,7 +2084,11 @@ async function cmdScenarioInfo(opts) {
1842
2084
  try {
1843
2085
  const [recipeId, scenarioId] = opts.id.split(":");
1844
2086
  if (!recipeId || !scenarioId) {
1845
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SCENARIO_INFO_USAGE });
2087
+ throw new CliError({
2088
+ exitCode: 2,
2089
+ code: "E_USAGE",
2090
+ message: usageMessage(SCENARIO_INFO_USAGE, SCENARIO_INFO_USAGE_EXAMPLE),
2091
+ });
1846
2092
  }
1847
2093
  const installed = await readInstalledRecipesFile(resolveInstalledRecipesPath());
1848
2094
  const entry = installed.recipes.find((recipe) => recipe.id === recipeId);
@@ -1944,7 +2190,11 @@ async function cmdScenarioRun(opts) {
1944
2190
  });
1945
2191
  const [recipeId, scenarioId] = opts.id.split(":");
1946
2192
  if (!recipeId || !scenarioId) {
1947
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SCENARIO_RUN_USAGE });
2193
+ throw new CliError({
2194
+ exitCode: 2,
2195
+ code: "E_USAGE",
2196
+ message: usageMessage(SCENARIO_RUN_USAGE, SCENARIO_RUN_USAGE_EXAMPLE),
2197
+ });
1948
2198
  }
1949
2199
  const installed = await readInstalledRecipesFile(resolveInstalledRecipesPath());
1950
2200
  const entry = installed.recipes.find((recipe) => recipe.id === recipeId);
@@ -2416,7 +2666,7 @@ async function cmdRecipeRemove(opts) {
2416
2666
  updated_at: installed.updated_at,
2417
2667
  recipes: updated,
2418
2668
  });
2419
- process.stdout.write(`Removed recipe ${entry.id}@${entry.version}\n`);
2669
+ process.stdout.write(`${successMessage("removed recipe", `${entry.id}@${entry.version}`)}\n`);
2420
2670
  return 0;
2421
2671
  }
2422
2672
  catch (err) {
@@ -2452,20 +2702,20 @@ async function cmdRecipeCachePrune(opts) {
2452
2702
  try {
2453
2703
  const cacheDir = resolveGlobalRecipesDir();
2454
2704
  if (!(await fileExists(cacheDir))) {
2455
- process.stdout.write("No recipes directory found.\n");
2705
+ process.stdout.write(`${infoMessage(`recipe cache directory not found: ${cacheDir}`)}\n`);
2456
2706
  return 0;
2457
2707
  }
2458
2708
  const cacheEntries = await listRecipeCacheEntries(cacheDir);
2459
2709
  if (cacheEntries.length === 0) {
2460
- process.stdout.write("No cached recipes found.\n");
2710
+ process.stdout.write(`${infoMessage("recipe cache is empty")}\n`);
2461
2711
  return 0;
2462
2712
  }
2463
2713
  if (flags.all) {
2464
2714
  if (flags.dryRun) {
2465
2715
  for (const entry of cacheEntries) {
2466
- process.stdout.write(`Would remove ${entry.id}@${entry.version}\n`);
2716
+ process.stdout.write(`${infoMessage(`dry-run: would remove ${entry.id}@${entry.version}`)}\n`);
2467
2717
  }
2468
- process.stdout.write(`Would remove ${cacheEntries.length} cached recipes.\n`);
2718
+ process.stdout.write(`${infoMessage(`dry-run: would remove ${cacheEntries.length} cached recipes`)}\n`);
2469
2719
  return 0;
2470
2720
  }
2471
2721
  await rm(cacheDir, { recursive: true, force: true });
@@ -2474,21 +2724,21 @@ async function cmdRecipeCachePrune(opts) {
2474
2724
  updated_at: "",
2475
2725
  recipes: [],
2476
2726
  });
2477
- process.stdout.write(`Removed ${cacheEntries.length} cached recipes.\n`);
2727
+ process.stdout.write(`${successMessage("removed cached recipes", undefined, `count=${cacheEntries.length}`)}\n`);
2478
2728
  return 0;
2479
2729
  }
2480
2730
  const installed = await readInstalledRecipesFile(resolveInstalledRecipesPath());
2481
2731
  const keep = new Set(installed.recipes.map((entry) => `${entry.id}@${entry.version}`));
2482
2732
  const prune = cacheEntries.filter((entry) => !keep.has(`${entry.id}@${entry.version}`));
2483
2733
  if (prune.length === 0) {
2484
- process.stdout.write("No cached recipes to prune.\n");
2734
+ process.stdout.write(`${infoMessage("recipe cache already clean (no uninstalled entries)")}\n`);
2485
2735
  return 0;
2486
2736
  }
2487
2737
  if (flags.dryRun) {
2488
2738
  for (const entry of prune) {
2489
- process.stdout.write(`Would remove ${entry.id}@${entry.version}\n`);
2739
+ process.stdout.write(`${infoMessage(`dry-run: would remove ${entry.id}@${entry.version}`)}\n`);
2490
2740
  }
2491
- process.stdout.write(`Would remove ${prune.length} cached recipes.\n`);
2741
+ process.stdout.write(`${infoMessage(`dry-run: would remove ${prune.length} cached recipes`)}\n`);
2492
2742
  return 0;
2493
2743
  }
2494
2744
  const recipeDirs = new Set();
@@ -2502,7 +2752,7 @@ async function cmdRecipeCachePrune(opts) {
2502
2752
  await rm(recipeDir, { recursive: true, force: true });
2503
2753
  }
2504
2754
  }
2505
- process.stdout.write(`Removed ${prune.length} cached recipes.\n`);
2755
+ process.stdout.write(`${successMessage("removed cached recipes", undefined, `count=${prune.length}`)}\n`);
2506
2756
  return 0;
2507
2757
  }
2508
2758
  catch (err) {
@@ -2529,7 +2779,7 @@ async function cmdBackendSync(opts) {
2529
2779
  throw new CliError({
2530
2780
  exitCode: 2,
2531
2781
  code: "E_USAGE",
2532
- message: "Configured backend does not support sync()",
2782
+ message: backendNotSupportedMessage("sync()"),
2533
2783
  });
2534
2784
  }
2535
2785
  await backend.sync({
@@ -2564,7 +2814,7 @@ async function cmdSync(opts) {
2564
2814
  throw new CliError({
2565
2815
  exitCode: 2,
2566
2816
  code: "E_USAGE",
2567
- message: "Configured backend does not support sync()",
2817
+ message: backendNotSupportedMessage("sync()"),
2568
2818
  });
2569
2819
  }
2570
2820
  await backend.sync({
@@ -2581,6 +2831,12 @@ async function cmdSync(opts) {
2581
2831
  throw mapBackendError(err, { command: "sync", root: opts.rootOverride ?? null });
2582
2832
  }
2583
2833
  }
2834
+ const TASK_NEW_USAGE = "Usage: agentplane task new --title <text> --description <text> --priority <low|normal|med|high> --owner <id> --tag <tag> [--tag <tag>...]";
2835
+ const TASK_NEW_USAGE_EXAMPLE = 'agentplane task new --title "Refactor CLI" --description "Improve CLI output" --priority med --owner CODER --tag cli';
2836
+ const TASK_ADD_USAGE = "Usage: agentplane task add <task-id> [<task-id> ...] --title <text> --description <text> --priority <low|normal|med|high> --owner <id> --tag <tag> [--tag <tag>...]";
2837
+ const TASK_ADD_USAGE_EXAMPLE = 'agentplane task add 202602030608-F1Q8AB --title "..." --description "..." --priority med --owner CODER --tag cli';
2838
+ const TASK_SCRUB_USAGE = "Usage: agentplane task scrub --find <text> --replace <text> [flags]";
2839
+ const TASK_SCRUB_USAGE_EXAMPLE = 'agentplane task scrub --find "agentctl" --replace "agentplane" --dry-run';
2584
2840
  function parseTaskNewFlags(args) {
2585
2841
  const out = { tags: [], dependsOn: [], verify: [] };
2586
2842
  for (let i = 0; i < args.length; i++) {
@@ -2596,7 +2852,7 @@ function parseTaskNewFlags(args) {
2596
2852
  }
2597
2853
  const next = args[i + 1];
2598
2854
  if (!next) {
2599
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
2855
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
2600
2856
  }
2601
2857
  switch (arg) {
2602
2858
  case "--title": {
@@ -2642,7 +2898,7 @@ async function cmdTaskNew(opts) {
2642
2898
  throw new CliError({
2643
2899
  exitCode: 2,
2644
2900
  code: "E_USAGE",
2645
- message: "Usage: agentplane task new --title <text> --description <text> --priority <low|normal|med|high> --owner <id> --tag <tag> [--tag <tag>...]",
2901
+ message: usageMessage(TASK_NEW_USAGE, TASK_NEW_USAGE_EXAMPLE),
2646
2902
  });
2647
2903
  }
2648
2904
  try {
@@ -2655,7 +2911,7 @@ async function cmdTaskNew(opts) {
2655
2911
  throw new CliError({
2656
2912
  exitCode: 3,
2657
2913
  code: "E_VALIDATION",
2658
- message: "Configured backend does not support generateTaskId()",
2914
+ message: backendNotSupportedMessage("generateTaskId()"),
2659
2915
  });
2660
2916
  }
2661
2917
  const taskId = await backend.generateTaskId({ length: suffixLength, attempts: 1000 });
@@ -2680,7 +2936,7 @@ async function cmdTaskNew(opts) {
2680
2936
  throw new CliError({
2681
2937
  exitCode: 2,
2682
2938
  code: "E_USAGE",
2683
- message: "verify commands are required for tasks with code/backend/frontend tags",
2939
+ message: "Missing verify commands for tasks with code/backend/frontend tags (use --verify)",
2684
2940
  });
2685
2941
  }
2686
2942
  await backend.writeTask(task);
@@ -2713,10 +2969,14 @@ function parseTaskAddFlags(args) {
2713
2969
  }
2714
2970
  const next = args[i + 1];
2715
2971
  if (arg === "--replace-tags" || arg === "--replace-depends-on" || arg === "--replace-verify") {
2716
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Usage: agentplane task add" });
2972
+ throw new CliError({
2973
+ exitCode: 2,
2974
+ code: "E_USAGE",
2975
+ message: usageMessage(TASK_ADD_USAGE, TASK_ADD_USAGE_EXAMPLE),
2976
+ });
2717
2977
  }
2718
2978
  if (!next) {
2719
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
2979
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
2720
2980
  }
2721
2981
  switch (arg) {
2722
2982
  case "--title": {
@@ -2778,7 +3038,7 @@ async function cmdTaskAdd(opts) {
2778
3038
  throw new CliError({
2779
3039
  exitCode: 2,
2780
3040
  code: "E_USAGE",
2781
- message: "Usage: agentplane task add <task-id> [<task-id> ...] --title <text> --description <text> --priority <low|normal|med|high> --owner <id> --tag <tag> [--tag <tag>...]",
3041
+ message: usageMessage(TASK_ADD_USAGE, TASK_ADD_USAGE_EXAMPLE),
2782
3042
  });
2783
3043
  }
2784
3044
  try {
@@ -2849,7 +3109,7 @@ function parseTaskUpdateFlags(args) {
2849
3109
  throw new CliError({
2850
3110
  exitCode: 2,
2851
3111
  code: "E_USAGE",
2852
- message: "Usage: agentplane task update <task-id> [flags]",
3112
+ message: usageMessage(TASK_UPDATE_USAGE, TASK_UPDATE_USAGE_EXAMPLE),
2853
3113
  });
2854
3114
  }
2855
3115
  const out = {
@@ -2881,12 +3141,12 @@ function parseTaskUpdateFlags(args) {
2881
3141
  throw new CliError({
2882
3142
  exitCode: 2,
2883
3143
  code: "E_USAGE",
2884
- message: "Usage: agentplane task update <task-id> [flags]",
3144
+ message: usageMessage(TASK_UPDATE_USAGE, TASK_UPDATE_USAGE_EXAMPLE),
2885
3145
  });
2886
3146
  }
2887
3147
  const next = rest[i + 1];
2888
3148
  if (!next) {
2889
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
3149
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
2890
3150
  }
2891
3151
  switch (arg) {
2892
3152
  case "--title": {
@@ -2941,7 +3201,7 @@ async function cmdTaskUpdate(opts) {
2941
3201
  throw new CliError({
2942
3202
  exitCode: 2,
2943
3203
  code: "E_USAGE",
2944
- message: `Unknown task id: ${flags.taskId}`,
3204
+ message: unknownEntityMessage("task id", flags.taskId),
2945
3205
  });
2946
3206
  }
2947
3207
  const next = { ...task };
@@ -2973,7 +3233,7 @@ async function cmdTaskUpdate(opts) {
2973
3233
  });
2974
3234
  }
2975
3235
  await backend.writeTask(next);
2976
- process.stdout.write(`✅ updated ${flags.taskId}\n`);
3236
+ process.stdout.write(`${successMessage("updated", flags.taskId)}\n`);
2977
3237
  return 0;
2978
3238
  }
2979
3239
  catch (err) {
@@ -3000,11 +3260,15 @@ function parseTaskScrubFlags(args) {
3000
3260
  continue;
3001
3261
  }
3002
3262
  if (!arg.startsWith("--")) {
3003
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Usage: agentplane task scrub" });
3263
+ throw new CliError({
3264
+ exitCode: 2,
3265
+ code: "E_USAGE",
3266
+ message: usageMessage(TASK_SCRUB_USAGE, TASK_SCRUB_USAGE_EXAMPLE),
3267
+ });
3004
3268
  }
3005
3269
  const next = args[i + 1];
3006
3270
  if (!next)
3007
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
3271
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
3008
3272
  if (arg === "--find") {
3009
3273
  out.find = next;
3010
3274
  }
@@ -3039,7 +3303,7 @@ async function cmdTaskScrub(opts) {
3039
3303
  throw new CliError({
3040
3304
  exitCode: 2,
3041
3305
  code: "E_USAGE",
3042
- message: "Usage: agentplane task scrub --find",
3306
+ message: usageMessage(TASK_SCRUB_USAGE, TASK_SCRUB_USAGE_EXAMPLE),
3043
3307
  });
3044
3308
  }
3045
3309
  try {
@@ -3066,7 +3330,7 @@ async function cmdTaskScrub(opts) {
3066
3330
  }
3067
3331
  if (flags.dryRun) {
3068
3332
  if (!flags.quiet) {
3069
- process.stdout.write(`Would update ${changedIds.size} task(s).\n`);
3333
+ process.stdout.write(`${infoMessage(`dry-run: would update ${changedIds.size} task(s)`)}` + "\n");
3070
3334
  for (const id of [...changedIds].toSorted()) {
3071
3335
  process.stdout.write(`${id}\n`);
3072
3336
  }
@@ -3082,7 +3346,7 @@ async function cmdTaskScrub(opts) {
3082
3346
  }
3083
3347
  }
3084
3348
  if (!flags.quiet) {
3085
- process.stdout.write(`Updated ${changedIds.size} task(s).\n`);
3349
+ process.stdout.write(`${successMessage("updated tasks", undefined, `count=${changedIds.size}`)}` + "\n");
3086
3350
  }
3087
3351
  return 0;
3088
3352
  }
@@ -3103,7 +3367,11 @@ function parseTaskListFilters(args, opts) {
3103
3367
  if (arg === "--status") {
3104
3368
  const next = args[i + 1];
3105
3369
  if (!next)
3106
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Missing value for --status" });
3370
+ throw new CliError({
3371
+ exitCode: 2,
3372
+ code: "E_USAGE",
3373
+ message: missingValueMessage("--status"),
3374
+ });
3107
3375
  out.status.push(next);
3108
3376
  i++;
3109
3377
  continue;
@@ -3111,7 +3379,11 @@ function parseTaskListFilters(args, opts) {
3111
3379
  if (arg === "--owner") {
3112
3380
  const next = args[i + 1];
3113
3381
  if (!next)
3114
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Missing value for --owner" });
3382
+ throw new CliError({
3383
+ exitCode: 2,
3384
+ code: "E_USAGE",
3385
+ message: missingValueMessage("--owner"),
3386
+ });
3115
3387
  out.owner.push(next);
3116
3388
  i++;
3117
3389
  continue;
@@ -3119,7 +3391,7 @@ function parseTaskListFilters(args, opts) {
3119
3391
  if (arg === "--tag") {
3120
3392
  const next = args[i + 1];
3121
3393
  if (!next)
3122
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Missing value for --tag" });
3394
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage("--tag") });
3123
3395
  out.tag.push(next);
3124
3396
  i++;
3125
3397
  continue;
@@ -3127,10 +3399,18 @@ function parseTaskListFilters(args, opts) {
3127
3399
  if (opts?.allowLimit && arg === "--limit") {
3128
3400
  const next = args[i + 1];
3129
3401
  if (!next)
3130
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Missing value for --limit" });
3402
+ throw new CliError({
3403
+ exitCode: 2,
3404
+ code: "E_USAGE",
3405
+ message: missingValueMessage("--limit"),
3406
+ });
3131
3407
  const parsed = Number.parseInt(next, 10);
3132
3408
  if (!Number.isFinite(parsed)) {
3133
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Invalid --limit value" });
3409
+ throw new CliError({
3410
+ exitCode: 2,
3411
+ code: "E_USAGE",
3412
+ message: invalidValueForFlag("--limit", next, "integer"),
3413
+ });
3134
3414
  }
3135
3415
  out.limit = parsed;
3136
3416
  i++;
@@ -3254,10 +3534,10 @@ async function cmdReady(opts) {
3254
3534
  }
3255
3535
  }
3256
3536
  else {
3257
- warnings.push(`Unknown task id: ${opts.taskId}`);
3537
+ warnings.push(unknownEntityMessage("task id", opts.taskId));
3258
3538
  }
3259
3539
  for (const warning of warnings) {
3260
- process.stdout.write(`⚠️ ${warning}\n`);
3540
+ process.stdout.write(`${warnMessage(warning)}\n`);
3261
3541
  }
3262
3542
  if (task) {
3263
3543
  const status = String(task.status || "TODO").toUpperCase();
@@ -3271,14 +3551,14 @@ async function cmdReady(opts) {
3271
3551
  const missing = dep?.missing ?? [];
3272
3552
  const incomplete = dep?.incomplete ?? [];
3273
3553
  if (missing.length > 0) {
3274
- process.stdout.write(`Missing deps: ${missing.join(", ")}\n`);
3554
+ process.stdout.write(`${warnMessage(`missing deps: ${missing.join(", ")}`)}\n`);
3275
3555
  }
3276
3556
  if (incomplete.length > 0) {
3277
- process.stdout.write(`Incomplete deps: ${incomplete.join(", ")}\n`);
3557
+ process.stdout.write(`${warnMessage(`incomplete deps: ${incomplete.join(", ")}`)}\n`);
3278
3558
  }
3279
3559
  }
3280
3560
  const ready = warnings.length === 0;
3281
- process.stdout.write(`${ready ? "ready" : "not ready"}\n`);
3561
+ process.stdout.write(`${ready ? successMessage("ready") : warnMessage("not ready")}` + "\n");
3282
3562
  return ready ? 0 : 2;
3283
3563
  }
3284
3564
  catch (err) {
@@ -3311,7 +3591,11 @@ function taskTextBlob(task) {
3311
3591
  async function cmdTaskSearch(opts) {
3312
3592
  const query = opts.query.trim();
3313
3593
  if (!query) {
3314
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Query must be non-empty" });
3594
+ throw new CliError({
3595
+ exitCode: 2,
3596
+ code: "E_USAGE",
3597
+ message: "Missing query (expected non-empty text)",
3598
+ });
3315
3599
  }
3316
3600
  let regex = false;
3317
3601
  const restArgs = [...opts.args];
@@ -3351,7 +3635,11 @@ async function cmdTaskSearch(opts) {
3351
3635
  }
3352
3636
  catch (err) {
3353
3637
  const message = err instanceof Error ? err.message : "Invalid regex";
3354
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Invalid regex: ${message}` });
3638
+ throw new CliError({
3639
+ exitCode: 2,
3640
+ code: "E_USAGE",
3641
+ message: invalidValueMessage("regex", message, "valid pattern"),
3642
+ });
3355
3643
  }
3356
3644
  matches = filtered.filter((task) => pattern.test(taskTextBlob(task)));
3357
3645
  }
@@ -3380,7 +3668,7 @@ function parseTaskScaffoldFlags(args) {
3380
3668
  throw new CliError({
3381
3669
  exitCode: 2,
3382
3670
  code: "E_USAGE",
3383
- message: "Usage: agentplane task scaffold <task-id> [--title <text>] [--overwrite] [--force]",
3671
+ message: usageMessage(TASK_SCAFFOLD_USAGE, TASK_SCAFFOLD_USAGE_EXAMPLE),
3384
3672
  });
3385
3673
  }
3386
3674
  const out = { taskId, overwrite: false, force: false, quiet: false };
@@ -3403,7 +3691,11 @@ function parseTaskScaffoldFlags(args) {
3403
3691
  if (arg === "--title") {
3404
3692
  const next = rest[i + 1];
3405
3693
  if (!next)
3406
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Missing value for --title" });
3694
+ throw new CliError({
3695
+ exitCode: 2,
3696
+ code: "E_USAGE",
3697
+ message: missingValueMessage("--title"),
3698
+ });
3407
3699
  out.title = next;
3408
3700
  i++;
3409
3701
  continue;
@@ -3428,7 +3720,7 @@ async function cmdTaskScaffold(opts) {
3428
3720
  throw new CliError({
3429
3721
  exitCode: 2,
3430
3722
  code: "E_USAGE",
3431
- message: `Unknown task id: ${flags.taskId}`,
3723
+ message: unknownEntityMessage("task id", flags.taskId),
3432
3724
  });
3433
3725
  }
3434
3726
  const readmePath = taskReadmePath(path.join(resolved.gitRoot, config.paths.workflow_dir), flags.taskId);
@@ -3474,7 +3766,7 @@ async function cmdTaskScaffold(opts) {
3474
3766
  await mkdir(path.dirname(readmePath), { recursive: true });
3475
3767
  await writeFile(readmePath, text.endsWith("\n") ? text : `${text}\n`, "utf8");
3476
3768
  if (!flags.quiet) {
3477
- process.stdout.write(`✅ wrote ${path.relative(resolved.gitRoot, readmePath)}\n`);
3769
+ process.stdout.write(`${successMessage("wrote", path.relative(resolved.gitRoot, readmePath))}\n`);
3478
3770
  }
3479
3771
  return 0;
3480
3772
  }
@@ -3494,7 +3786,7 @@ function parseTaskNormalizeFlags(args) {
3494
3786
  else if (arg === "--force")
3495
3787
  out.force = true;
3496
3788
  else if (arg.startsWith("--"))
3497
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Unknown flag" });
3789
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Unknown flag: ${arg}` });
3498
3790
  }
3499
3791
  return out;
3500
3792
  }
@@ -3517,7 +3809,7 @@ async function cmdTaskNormalize(opts) {
3517
3809
  await backend.writeTask(task);
3518
3810
  }
3519
3811
  if (!flags.quiet) {
3520
- process.stdout.write(`✅ normalized ${tasks.length} task(s)\n`);
3812
+ process.stdout.write(`${successMessage("normalized tasks", undefined, `count=${tasks.length}`)}\n`);
3521
3813
  }
3522
3814
  return 0;
3523
3815
  }
@@ -3542,13 +3834,17 @@ function parseTaskMigrateFlags(args) {
3542
3834
  if (arg === "--source") {
3543
3835
  const next = args[i + 1];
3544
3836
  if (!next)
3545
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Missing value for --source" });
3837
+ throw new CliError({
3838
+ exitCode: 2,
3839
+ code: "E_USAGE",
3840
+ message: missingValueMessage("--source"),
3841
+ });
3546
3842
  out.source = next;
3547
3843
  i++;
3548
3844
  continue;
3549
3845
  }
3550
3846
  if (arg.startsWith("--")) {
3551
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Unknown flag" });
3847
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Unknown flag: ${arg}` });
3552
3848
  }
3553
3849
  }
3554
3850
  return out;
@@ -3576,7 +3872,7 @@ async function cmdTaskMigrate(opts) {
3576
3872
  await backend.writeTask(task);
3577
3873
  }
3578
3874
  if (!flags.quiet) {
3579
- process.stdout.write(`✅ migrated ${tasks.length} task(s) into backend\n`);
3875
+ process.stdout.write(`${successMessage("migrated tasks into backend", undefined, `count=${tasks.length}`)}\n`);
3580
3876
  }
3581
3877
  return 0;
3582
3878
  }
@@ -3602,7 +3898,7 @@ async function cmdTaskComment(opts) {
3602
3898
  doc_updated_by: "agentplane",
3603
3899
  };
3604
3900
  await backend.writeTask(next);
3605
- process.stdout.write(`✅ commented ${opts.taskId}\n`);
3901
+ process.stdout.write(`${successMessage("commented", opts.taskId)}\n`);
3606
3902
  return 0;
3607
3903
  }
3608
3904
  catch (err) {
@@ -3673,10 +3969,10 @@ async function cmdTaskSetStatus(opts) {
3673
3969
  if (dep && (dep.missing.length > 0 || dep.incomplete.length > 0)) {
3674
3970
  if (!opts.quiet) {
3675
3971
  if (dep.missing.length > 0) {
3676
- process.stderr.write(`⚠️ missing deps: ${dep.missing.join(", ")}\n`);
3972
+ process.stderr.write(`${warnMessage(`missing deps: ${dep.missing.join(", ")}`)}\n`);
3677
3973
  }
3678
3974
  if (dep.incomplete.length > 0) {
3679
- process.stderr.write(`⚠️ incomplete deps: ${dep.incomplete.join(", ")}\n`);
3975
+ process.stderr.write(`${warnMessage(`incomplete deps: ${dep.incomplete.join(", ")}`)}\n`);
3680
3976
  }
3681
3977
  }
3682
3978
  throw new CliError({
@@ -3737,7 +4033,7 @@ async function cmdTaskSetStatus(opts) {
3737
4033
  });
3738
4034
  }
3739
4035
  if (!opts.quiet) {
3740
- process.stdout.write(`✅ status ${opts.taskId} -> ${nextStatus}\n`);
4036
+ process.stdout.write(`${successMessage("status", opts.taskId, `next=${nextStatus}`)}\n`);
3741
4037
  }
3742
4038
  return 0;
3743
4039
  }
@@ -3792,7 +4088,7 @@ async function cmdTaskExport(opts) {
3792
4088
  throw new CliError({
3793
4089
  exitCode: 3,
3794
4090
  code: "E_VALIDATION",
3795
- message: "Configured backend does not support exportTasksJson()",
4091
+ message: backendNotSupportedMessage("exportTasksJson()"),
3796
4092
  });
3797
4093
  }
3798
4094
  await backend.exportTasksJson(outPath);
@@ -3823,19 +4119,33 @@ async function cmdTaskLint(opts) {
3823
4119
  }
3824
4120
  }
3825
4121
  const IDE_SYNC_USAGE = "Usage: agentplane ide sync";
4122
+ const IDE_SYNC_USAGE_EXAMPLE = "agentplane ide sync";
3826
4123
  const GUARD_COMMIT_USAGE = "Usage: agentplane guard commit <task-id> -m <message> --allow <path> [--allow <path>...] [--auto-allow] [--allow-tasks] [--require-clean] [--quiet]";
4124
+ const GUARD_COMMIT_USAGE_EXAMPLE = 'agentplane guard commit 202602030608-F1Q8AB -m "✨ F1Q8AB update" --allow packages/agentplane';
3827
4125
  const COMMIT_USAGE = "Usage: agentplane commit <task-id> -m <message>";
4126
+ const COMMIT_USAGE_EXAMPLE = 'agentplane commit 202602030608-F1Q8AB -m "✨ F1Q8AB update"';
3828
4127
  const START_USAGE = "Usage: agentplane start <task-id> --author <id> --body <text> [flags]";
4128
+ const START_USAGE_EXAMPLE = 'agentplane start 202602030608-F1Q8AB --author CODER --body "Start: ..."';
3829
4129
  const BLOCK_USAGE = "Usage: agentplane block <task-id> --author <id> --body <text> [flags]";
4130
+ const BLOCK_USAGE_EXAMPLE = 'agentplane block 202602030608-F1Q8AB --author CODER --body "Blocked: ..."';
3830
4131
  const FINISH_USAGE = "Usage: agentplane finish <task-id> [<task-id>...] --author <id> --body <text> [flags]";
4132
+ const FINISH_USAGE_EXAMPLE = 'agentplane finish 202602030608-F1Q8AB --author INTEGRATOR --body "Verified: ..."';
3831
4133
  const VERIFY_USAGE = "Usage: agentplane verify <task-id> [--cwd <path>] [--log <path>] [--skip-if-unchanged] [--quiet] [--require]";
4134
+ const VERIFY_USAGE_EXAMPLE = "agentplane verify 202602030608-F1Q8AB";
3832
4135
  const WORK_START_USAGE = "Usage: agentplane work start <task-id> --agent <id> --slug <slug> --worktree";
4136
+ const WORK_START_USAGE_EXAMPLE = "agentplane work start 202602030608-F1Q8AB --agent CODER --slug cli --worktree";
3833
4137
  const PR_OPEN_USAGE = "Usage: agentplane pr open <task-id> --author <id> [--branch <name>]";
4138
+ const PR_OPEN_USAGE_EXAMPLE = "agentplane pr open 202602030608-F1Q8AB --author CODER";
3834
4139
  const PR_UPDATE_USAGE = "Usage: agentplane pr update <task-id>";
4140
+ const PR_UPDATE_USAGE_EXAMPLE = "agentplane pr update 202602030608-F1Q8AB";
3835
4141
  const PR_CHECK_USAGE = "Usage: agentplane pr check <task-id>";
4142
+ const PR_CHECK_USAGE_EXAMPLE = "agentplane pr check 202602030608-F1Q8AB";
3836
4143
  const PR_NOTE_USAGE = "Usage: agentplane pr note <task-id> --author <id> --body <text>";
4144
+ const PR_NOTE_USAGE_EXAMPLE = 'agentplane pr note 202602030608-F1Q8AB --author REVIEWER --body "..."';
3837
4145
  const INTEGRATE_USAGE = "Usage: agentplane integrate <task-id> [--branch <name>] [--base <name>] [--merge-strategy squash|merge|rebase] [--run-verify] [--dry-run] [--quiet]";
4146
+ const INTEGRATE_USAGE_EXAMPLE = "agentplane integrate 202602030608-F1Q8AB --run-verify";
3838
4147
  const CLEANUP_MERGED_USAGE = "Usage: agentplane cleanup merged [--base <name>] [--yes] [--archive] [--quiet]";
4148
+ const CLEANUP_MERGED_USAGE_EXAMPLE = "agentplane cleanup merged --yes";
3839
4149
  function pathIsUnder(candidate, prefix) {
3840
4150
  if (prefix === "." || prefix === "")
3841
4151
  return true;
@@ -3856,7 +4166,7 @@ function normalizeTaskStatus(value) {
3856
4166
  throw new CliError({
3857
4167
  exitCode: 2,
3858
4168
  code: "E_USAGE",
3859
- message: `Invalid status: ${value} (allowed: ${[...ALLOWED_TASK_STATUSES].join(", ")})`,
4169
+ message: invalidValueMessage("status", value, `one of ${[...ALLOWED_TASK_STATUSES].join(", ")}`),
3860
4170
  });
3861
4171
  }
3862
4172
  return normalized;
@@ -4010,7 +4320,7 @@ function deriveCommitMessageFromComment(opts) {
4010
4320
  throw new CliError({
4011
4321
  exitCode: 2,
4012
4322
  code: "E_USAGE",
4013
- message: `Invalid task id: ${opts.taskId}`,
4323
+ message: invalidValueMessage("task id", opts.taskId, "valid task id"),
4014
4324
  });
4015
4325
  }
4016
4326
  return `${prefix} ${suffix} ${summary}`;
@@ -4020,7 +4330,7 @@ function enforceStatusCommitPolicy(opts) {
4020
4330
  return;
4021
4331
  if (opts.policy === "warn") {
4022
4332
  if (!opts.quiet && !opts.confirmed) {
4023
- process.stderr.write(`⚠️ ${opts.action}: status/comment-driven commit requested; policy=warn (pass --confirm-status-commit to acknowledge)\n`);
4333
+ process.stderr.write(`${warnMessage(`${opts.action}: status/comment-driven commit requested; policy=warn (pass --confirm-status-commit to acknowledge)`)}\n`);
4024
4334
  }
4025
4335
  return;
4026
4336
  }
@@ -4355,7 +4665,11 @@ function shimScriptText() {
4355
4665
  function validateWorkSlug(slug) {
4356
4666
  const trimmed = slug.trim();
4357
4667
  if (!trimmed) {
4358
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: WORK_START_USAGE });
4668
+ throw new CliError({
4669
+ exitCode: 2,
4670
+ code: "E_USAGE",
4671
+ message: usageMessage(WORK_START_USAGE, WORK_START_USAGE_EXAMPLE),
4672
+ });
4359
4673
  }
4360
4674
  if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(trimmed)) {
4361
4675
  throw new CliError({
@@ -4368,7 +4682,11 @@ function validateWorkSlug(slug) {
4368
4682
  function validateWorkAgent(agent) {
4369
4683
  const trimmed = agent.trim();
4370
4684
  if (!trimmed) {
4371
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: WORK_START_USAGE });
4685
+ throw new CliError({
4686
+ exitCode: 2,
4687
+ code: "E_USAGE",
4688
+ message: usageMessage(WORK_START_USAGE, WORK_START_USAGE_EXAMPLE),
4689
+ });
4372
4690
  }
4373
4691
  if (!/^[A-Z0-9_]+$/.test(trimmed)) {
4374
4692
  throw new CliError({
@@ -4553,7 +4871,7 @@ async function cmdGuardClean(opts) {
4553
4871
  });
4554
4872
  }
4555
4873
  if (!opts.quiet) {
4556
- process.stdout.write("index clean (no staged files)\n");
4874
+ process.stdout.write(`${successMessage("index clean", undefined, "no staged files")}\n`);
4557
4875
  }
4558
4876
  return 0;
4559
4877
  }
@@ -4567,7 +4885,11 @@ async function cmdGuardSuggestAllow(opts) {
4567
4885
  try {
4568
4886
  const staged = await getStagedFiles({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
4569
4887
  if (staged.length === 0) {
4570
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "No staged files" });
4888
+ throw new CliError({
4889
+ exitCode: 2,
4890
+ code: "E_USAGE",
4891
+ message: "No staged files (git index empty)",
4892
+ });
4571
4893
  }
4572
4894
  const prefixes = suggestAllowPrefixes(staged);
4573
4895
  if (opts.format === "args") {
@@ -4600,7 +4922,11 @@ async function guardCommitCheck(opts) {
4600
4922
  }
4601
4923
  const staged = await getStagedFiles({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
4602
4924
  if (staged.length === 0) {
4603
- throw new CliError({ exitCode: 5, code: "E_GIT", message: "No staged files" });
4925
+ throw new CliError({
4926
+ exitCode: 5,
4927
+ code: "E_GIT",
4928
+ message: "No staged files (git index empty)",
4929
+ });
4604
4930
  }
4605
4931
  if (opts.allow.length === 0) {
4606
4932
  throw new CliError({
@@ -4685,7 +5011,11 @@ async function stageAllowlist(opts) {
4685
5011
  });
4686
5012
  const changed = await gitStatusChangedPaths({ cwd: opts.cwd, rootOverride: opts.rootOverride });
4687
5013
  if (changed.length === 0) {
4688
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "No changes to stage" });
5014
+ throw new CliError({
5015
+ exitCode: 2,
5016
+ code: "E_USAGE",
5017
+ message: "No changes to stage (working tree clean)",
5018
+ });
4689
5019
  }
4690
5020
  const allow = opts.allow.map((prefix) => normalizeAllowPrefix(prefix.trim().replace(/^\.?\//, "")));
4691
5021
  const denied = new Set();
@@ -4704,7 +5034,7 @@ async function stageAllowlist(opts) {
4704
5034
  throw new CliError({
4705
5035
  exitCode: 2,
4706
5036
  code: "E_USAGE",
4707
- message: "No changes matched the allowed prefixes (use --commit-auto-allow or broaden --commit-allow)",
5037
+ message: "No changes matched allowed prefixes (use --commit-auto-allow or update --commit-allow)",
4708
5038
  });
4709
5039
  }
4710
5040
  await execFileAsync("git", ["add", "--", ...unique], { cwd: resolved.gitRoot });
@@ -4763,7 +5093,7 @@ async function commitFromComment(opts) {
4763
5093
  const trimmed = stdout.trim();
4764
5094
  const [hash, subject] = trimmed.split(":", 2);
4765
5095
  if (!opts.quiet) {
4766
- process.stdout.write(`✅ committed ${hash?.slice(0, 12) ?? ""} ${subject ?? ""} (staged: ${staged.join(", ")})\n`);
5096
+ process.stdout.write(`${successMessage("committed", `${hash?.slice(0, 12) ?? ""} ${subject ?? ""}`.trim(), `staged=${staged.join(", ")}`)}\n`);
4767
5097
  }
4768
5098
  return { hash: hash ?? "", message: subject ?? "", staged };
4769
5099
  }
@@ -4790,7 +5120,11 @@ async function cmdCommit(opts) {
4790
5120
  });
4791
5121
  const prefixes = suggestAllowPrefixes(staged);
4792
5122
  if (prefixes.length === 0) {
4793
- throw new CliError({ exitCode: 5, code: "E_GIT", message: "No staged files" });
5123
+ throw new CliError({
5124
+ exitCode: 5,
5125
+ code: "E_GIT",
5126
+ message: "No staged files (git index empty)",
5127
+ });
4794
5128
  }
4795
5129
  allow = prefixes;
4796
5130
  }
@@ -4821,7 +5155,7 @@ async function cmdCommit(opts) {
4821
5155
  });
4822
5156
  const trimmed = stdout.trim();
4823
5157
  const [hash, subject] = trimmed.split(":", 2);
4824
- process.stdout.write(`✅ committed ${hash?.slice(0, 12) ?? ""} ${subject ?? ""}\n`);
5158
+ process.stdout.write(`${successMessage("committed", `${hash?.slice(0, 12) ?? ""} ${subject ?? ""}`.trim())}\n`);
4825
5159
  }
4826
5160
  return 0;
4827
5161
  }
@@ -4868,10 +5202,10 @@ async function cmdStart(opts) {
4868
5202
  if (dep && (dep.missing.length > 0 || dep.incomplete.length > 0)) {
4869
5203
  if (!opts.quiet) {
4870
5204
  if (dep.missing.length > 0) {
4871
- process.stderr.write(`⚠️ missing deps: ${dep.missing.join(", ")}\n`);
5205
+ process.stderr.write(`${warnMessage(`missing deps: ${dep.missing.join(", ")}`)}\n`);
4872
5206
  }
4873
5207
  if (dep.incomplete.length > 0) {
4874
- process.stderr.write(`⚠️ incomplete deps: ${dep.incomplete.join(", ")}\n`);
5208
+ process.stderr.write(`${warnMessage(`incomplete deps: ${dep.incomplete.join(", ")}`)}\n`);
4875
5209
  }
4876
5210
  }
4877
5211
  throw new CliError({
@@ -4920,7 +5254,7 @@ async function cmdStart(opts) {
4920
5254
  }
4921
5255
  if (!opts.quiet) {
4922
5256
  const suffix = commitInfo ? ` (commit=${commitInfo.hash.slice(0, 12)})` : "";
4923
- process.stdout.write(`✅ started ${opts.taskId}${suffix}\n`);
5257
+ process.stdout.write(`${successMessage("started", `${opts.taskId}${suffix}`)}\n`);
4924
5258
  }
4925
5259
  return 0;
4926
5260
  }
@@ -4996,7 +5330,7 @@ async function cmdBlock(opts) {
4996
5330
  }
4997
5331
  if (!opts.quiet) {
4998
5332
  const suffix = commitInfo ? ` (commit=${commitInfo.hash.slice(0, 12)})` : "";
4999
- process.stdout.write(`✅ blocked ${opts.taskId}${suffix}\n`);
5333
+ process.stdout.write(`${successMessage("blocked", `${opts.taskId}${suffix}`)}\n`);
5000
5334
  }
5001
5335
  return 0;
5002
5336
  }
@@ -5086,7 +5420,7 @@ async function cmdFinish(opts) {
5086
5420
  throw new CliError({
5087
5421
  exitCode: 3,
5088
5422
  code: "E_VALIDATION",
5089
- message: "Configured backend does not support exportTasksJson()",
5423
+ message: backendNotSupportedMessage("exportTasksJson()"),
5090
5424
  });
5091
5425
  }
5092
5426
  const outPath = path.join(resolved.gitRoot, config.paths.tasks_path);
@@ -5135,7 +5469,7 @@ async function cmdFinish(opts) {
5135
5469
  });
5136
5470
  }
5137
5471
  if (!opts.quiet) {
5138
- process.stdout.write("finished\n");
5472
+ process.stdout.write(`${successMessage("finished")}\n`);
5139
5473
  }
5140
5474
  return 0;
5141
5475
  }
@@ -5175,7 +5509,7 @@ async function cmdVerify(opts) {
5175
5509
  });
5176
5510
  }
5177
5511
  if (!opts.quiet) {
5178
- process.stdout.write(`ℹ️ ${task.id}: no verify commands configured\n`);
5512
+ process.stdout.write(`${infoMessage(`${task.id}: no verify commands configured`)}\n`);
5179
5513
  }
5180
5514
  return 0;
5181
5515
  }
@@ -5223,7 +5557,7 @@ async function cmdVerify(opts) {
5223
5557
  });
5224
5558
  if (changed.length > 0) {
5225
5559
  if (!opts.quiet) {
5226
- process.stdout.write(`⚠️ ${task.id}: working tree is dirty; ignoring --skip-if-unchanged\n`);
5560
+ process.stdout.write(`${warnMessage(`${task.id}: working tree is dirty; ignoring --skip-if-unchanged`)}\n`);
5227
5561
  }
5228
5562
  }
5229
5563
  else {
@@ -5238,7 +5572,7 @@ async function cmdVerify(opts) {
5238
5572
  await appendVerifyLog(logPath, header, "");
5239
5573
  }
5240
5574
  if (!opts.quiet) {
5241
- process.stdout.write(`ℹ️ ${task.id}: verify skipped (unchanged sha ${currentSha.slice(0, 12)})\n`);
5575
+ process.stdout.write(`${infoMessage(`${task.id}: verify skipped (unchanged sha ${currentSha.slice(0, 12)})`)}\n`);
5242
5576
  }
5243
5577
  if (meta) {
5244
5578
  const nextMeta = {
@@ -5279,7 +5613,7 @@ async function cmdVerify(opts) {
5279
5613
  }
5280
5614
  }
5281
5615
  if (!opts.quiet) {
5282
- process.stdout.write(`✅ verify passed for ${task.id}\n`);
5616
+ process.stdout.write(`${successMessage("verify passed", task.id)}\n`);
5283
5617
  }
5284
5618
  if (meta) {
5285
5619
  const nextMeta = {
@@ -5313,11 +5647,15 @@ async function cmdWorkStart(opts) {
5313
5647
  throw new CliError({
5314
5648
  exitCode: 2,
5315
5649
  code: "E_USAGE",
5316
- message: "work start is only supported when workflow_mode=branch_pr",
5650
+ message: workflowModeMessage(loaded.config.workflow_mode, "branch_pr"),
5317
5651
  });
5318
5652
  }
5319
5653
  if (!opts.worktree) {
5320
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: WORK_START_USAGE });
5654
+ throw new CliError({
5655
+ exitCode: 2,
5656
+ code: "E_USAGE",
5657
+ message: usageMessage(WORK_START_USAGE, WORK_START_USAGE_EXAMPLE),
5658
+ });
5321
5659
  }
5322
5660
  await loadBackendTask({
5323
5661
  cwd: opts.cwd,
@@ -5360,7 +5698,7 @@ async function cmdWorkStart(opts) {
5360
5698
  ? ["worktree", "add", worktreePath, branchName]
5361
5699
  : ["worktree", "add", "-b", branchName, worktreePath, baseBranch];
5362
5700
  await execFileAsync("git", worktreeArgs, { cwd: resolved.gitRoot, env: gitEnv() });
5363
- process.stdout.write(`✅ work start ${branchName} (worktree=${path.relative(resolved.gitRoot, worktreePath)})\n`);
5701
+ process.stdout.write(`${successMessage("work start", branchName, `worktree=${path.relative(resolved.gitRoot, worktreePath)}`)}\n`);
5364
5702
  return 0;
5365
5703
  }
5366
5704
  catch (err) {
@@ -5391,7 +5729,11 @@ async function cmdPrOpen(opts) {
5391
5729
  try {
5392
5730
  const author = opts.author.trim();
5393
5731
  if (!author)
5394
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_OPEN_USAGE });
5732
+ throw new CliError({
5733
+ exitCode: 2,
5734
+ code: "E_USAGE",
5735
+ message: usageMessage(PR_OPEN_USAGE, PR_OPEN_USAGE_EXAMPLE),
5736
+ });
5395
5737
  const { task } = await loadBackendTask({
5396
5738
  cwd: opts.cwd,
5397
5739
  rootOverride: opts.rootOverride,
@@ -5402,12 +5744,16 @@ async function cmdPrOpen(opts) {
5402
5744
  throw new CliError({
5403
5745
  exitCode: 2,
5404
5746
  code: "E_USAGE",
5405
- message: "pr commands require workflow_mode=branch_pr",
5747
+ message: workflowModeMessage(config.workflow_mode, "branch_pr"),
5406
5748
  });
5407
5749
  }
5408
5750
  const branch = (opts.branch ?? (await gitCurrentBranch(resolved.gitRoot))).trim();
5409
5751
  if (!branch)
5410
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_OPEN_USAGE });
5752
+ throw new CliError({
5753
+ exitCode: 2,
5754
+ code: "E_USAGE",
5755
+ message: usageMessage(PR_OPEN_USAGE, PR_OPEN_USAGE_EXAMPLE),
5756
+ });
5411
5757
  await mkdir(prDir, { recursive: true });
5412
5758
  const now = nowIso();
5413
5759
  let meta = null;
@@ -5435,7 +5781,7 @@ async function cmdPrOpen(opts) {
5435
5781
  const review = renderPrReviewTemplate({ author, createdAt, branch });
5436
5782
  await writeFile(reviewPath, review, "utf8");
5437
5783
  }
5438
- process.stdout.write(`✅ pr open ${path.relative(resolved.gitRoot, prDir)}\n`);
5784
+ process.stdout.write(`${successMessage("pr open", path.relative(resolved.gitRoot, prDir))}\n`);
5439
5785
  return 0;
5440
5786
  }
5441
5787
  catch (err) {
@@ -5456,14 +5802,19 @@ async function cmdPrUpdate(opts) {
5456
5802
  throw new CliError({
5457
5803
  exitCode: 2,
5458
5804
  code: "E_USAGE",
5459
- message: "pr commands require workflow_mode=branch_pr",
5805
+ message: workflowModeMessage(config.workflow_mode, "branch_pr"),
5460
5806
  });
5461
5807
  }
5462
5808
  if (!(await fileExists(metaPath)) || !(await fileExists(reviewPath))) {
5809
+ const missing = [];
5810
+ if (!(await fileExists(metaPath)))
5811
+ missing.push(path.relative(resolved.gitRoot, metaPath));
5812
+ if (!(await fileExists(reviewPath)))
5813
+ missing.push(path.relative(resolved.gitRoot, reviewPath));
5463
5814
  throw new CliError({
5464
5815
  exitCode: 3,
5465
5816
  code: "E_VALIDATION",
5466
- message: "PR artifacts missing: run `agentplane pr open` first",
5817
+ message: `PR artifacts missing: ${missing.join(", ")} (run \`agentplane pr open\`)`,
5467
5818
  });
5468
5819
  }
5469
5820
  const baseBranch = await getBaseBranch({
@@ -5501,7 +5852,7 @@ async function cmdPrUpdate(opts) {
5501
5852
  last_verified_at: meta.last_verified_at ?? null,
5502
5853
  };
5503
5854
  await writeFile(metaPath, `${JSON.stringify(nextMeta, null, 2)}\n`, "utf8");
5504
- process.stdout.write(`✅ pr update ${path.relative(resolved.gitRoot, prDir)}\n`);
5855
+ process.stdout.write(`${successMessage("pr update", path.relative(resolved.gitRoot, prDir))}\n`);
5505
5856
  return 0;
5506
5857
  }
5507
5858
  catch (err) {
@@ -5522,20 +5873,25 @@ async function cmdPrCheck(opts) {
5522
5873
  throw new CliError({
5523
5874
  exitCode: 2,
5524
5875
  code: "E_USAGE",
5525
- message: "pr commands require workflow_mode=branch_pr",
5876
+ message: workflowModeMessage(config.workflow_mode, "branch_pr"),
5526
5877
  });
5527
5878
  }
5528
5879
  const errors = [];
5880
+ const relPrDir = path.relative(resolved.gitRoot, prDir);
5881
+ const relMetaPath = path.relative(resolved.gitRoot, metaPath);
5882
+ const relDiffstatPath = path.relative(resolved.gitRoot, diffstatPath);
5883
+ const relVerifyLogPath = path.relative(resolved.gitRoot, verifyLogPath);
5884
+ const relReviewPath = path.relative(resolved.gitRoot, reviewPath);
5529
5885
  if (!(await fileExists(prDir)))
5530
- errors.push("Missing PR directory");
5886
+ errors.push(`Missing PR directory: ${relPrDir}`);
5531
5887
  if (!(await fileExists(metaPath)))
5532
- errors.push("Missing pr/meta.json");
5888
+ errors.push(`Missing ${relMetaPath}`);
5533
5889
  if (!(await fileExists(diffstatPath)))
5534
- errors.push("Missing pr/diffstat.txt");
5890
+ errors.push(`Missing ${relDiffstatPath}`);
5535
5891
  if (!(await fileExists(verifyLogPath)))
5536
- errors.push("Missing pr/verify.log");
5892
+ errors.push(`Missing ${relVerifyLogPath}`);
5537
5893
  if (!(await fileExists(reviewPath)))
5538
- errors.push("Missing pr/review.md");
5894
+ errors.push(`Missing ${relReviewPath}`);
5539
5895
  let meta = null;
5540
5896
  if (await fileExists(metaPath)) {
5541
5897
  try {
@@ -5571,7 +5927,7 @@ async function cmdPrCheck(opts) {
5571
5927
  if (errors.length > 0) {
5572
5928
  throw new CliError({ exitCode: 3, code: "E_VALIDATION", message: errors.join("\n") });
5573
5929
  }
5574
- process.stdout.write(`✅ pr check ${path.relative(resolved.gitRoot, prDir)}\n`);
5930
+ process.stdout.write(`${successMessage("pr check", path.relative(resolved.gitRoot, prDir))}\n`);
5575
5931
  return 0;
5576
5932
  }
5577
5933
  catch (err) {
@@ -5585,27 +5941,32 @@ async function cmdPrNote(opts) {
5585
5941
  const author = opts.author.trim();
5586
5942
  const body = opts.body.trim();
5587
5943
  if (!author || !body) {
5588
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_NOTE_USAGE });
5944
+ throw new CliError({
5945
+ exitCode: 2,
5946
+ code: "E_USAGE",
5947
+ message: usageMessage(PR_NOTE_USAGE, PR_NOTE_USAGE_EXAMPLE),
5948
+ });
5589
5949
  }
5590
- const { config, reviewPath } = await resolvePrPaths(opts);
5950
+ const { config, reviewPath, resolved } = await resolvePrPaths(opts);
5591
5951
  if (config.workflow_mode !== "branch_pr") {
5592
5952
  throw new CliError({
5593
5953
  exitCode: 2,
5594
5954
  code: "E_USAGE",
5595
- message: "pr commands require workflow_mode=branch_pr",
5955
+ message: workflowModeMessage(config.workflow_mode, "branch_pr"),
5596
5956
  });
5597
5957
  }
5598
5958
  if (!(await fileExists(reviewPath))) {
5959
+ const relReviewPath = path.relative(resolved.gitRoot, reviewPath);
5599
5960
  throw new CliError({
5600
5961
  exitCode: 3,
5601
5962
  code: "E_VALIDATION",
5602
- message: "Missing pr/review.md (run `agentplane pr open`)",
5963
+ message: `Missing ${relReviewPath} (run \`agentplane pr open\`)`,
5603
5964
  });
5604
5965
  }
5605
5966
  const review = await readFile(reviewPath, "utf8");
5606
5967
  const updated = appendHandoffNote(review, `${author}: ${body}`);
5607
5968
  await writeFile(reviewPath, updated, "utf8");
5608
- process.stdout.write("pr note\n");
5969
+ process.stdout.write(`${successMessage("pr note", opts.taskId)}\n`);
5609
5970
  return 0;
5610
5971
  }
5611
5972
  catch (err) {
@@ -5658,7 +6019,7 @@ async function cmdIntegrate(opts) {
5658
6019
  throw new CliError({
5659
6020
  exitCode: 2,
5660
6021
  code: "E_USAGE",
5661
- message: "integrate is only supported when workflow_mode=branch_pr",
6022
+ message: workflowModeMessage(loaded.config.workflow_mode, "branch_pr"),
5662
6023
  });
5663
6024
  }
5664
6025
  await ensureGitClean({ cwd: opts.cwd, rootOverride: opts.rootOverride });
@@ -5684,13 +6045,17 @@ async function cmdIntegrate(opts) {
5684
6045
  branch = (meta.branch ?? "").trim();
5685
6046
  }
5686
6047
  if (!branch) {
5687
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: INTEGRATE_USAGE });
6048
+ throw new CliError({
6049
+ exitCode: 2,
6050
+ code: "E_USAGE",
6051
+ message: usageMessage(INTEGRATE_USAGE, INTEGRATE_USAGE_EXAMPLE),
6052
+ });
5688
6053
  }
5689
6054
  if (!(await gitBranchExists(resolved.gitRoot, branch))) {
5690
6055
  throw new CliError({
5691
6056
  exitCode: 2,
5692
6057
  code: "E_USAGE",
5693
- message: `Unknown branch: ${branch}`,
6058
+ message: unknownEntityMessage("branch", branch),
5694
6059
  });
5695
6060
  }
5696
6061
  const metaSource = meta ??
@@ -5700,6 +6065,9 @@ async function cmdIntegrate(opts) {
5700
6065
  ? baseCandidate.trim()
5701
6066
  : baseBranch;
5702
6067
  const errors = [];
6068
+ const relDiffstat = path.relative(resolved.gitRoot, path.join(prDir, "diffstat.txt"));
6069
+ const relVerifyLog = path.relative(resolved.gitRoot, path.join(prDir, "verify.log"));
6070
+ const relReview = path.relative(resolved.gitRoot, path.join(prDir, "review.md"));
5703
6071
  const diffstatText = await readPrArtifact({
5704
6072
  resolved,
5705
6073
  prDir,
@@ -5707,7 +6075,7 @@ async function cmdIntegrate(opts) {
5707
6075
  branch,
5708
6076
  });
5709
6077
  if (diffstatText === null)
5710
- errors.push("Missing pr/diffstat.txt");
6078
+ errors.push(`Missing ${relDiffstat}`);
5711
6079
  const verifyLogText = await readPrArtifact({
5712
6080
  resolved,
5713
6081
  prDir,
@@ -5715,7 +6083,7 @@ async function cmdIntegrate(opts) {
5715
6083
  branch,
5716
6084
  });
5717
6085
  if (verifyLogText === null)
5718
- errors.push("Missing pr/verify.log");
6086
+ errors.push(`Missing ${relVerifyLog}`);
5719
6087
  const reviewText = await readPrArtifact({
5720
6088
  resolved,
5721
6089
  prDir,
@@ -5723,7 +6091,7 @@ async function cmdIntegrate(opts) {
5723
6091
  branch,
5724
6092
  });
5725
6093
  if (reviewText === null)
5726
- errors.push("Missing pr/review.md");
6094
+ errors.push(`Missing ${relReview}`);
5727
6095
  if (reviewText)
5728
6096
  validateReviewContents(reviewText, errors);
5729
6097
  if (errors.length > 0) {
@@ -5761,7 +6129,7 @@ async function cmdIntegrate(opts) {
5761
6129
  let shouldRunVerify = opts.runVerify || (verifyCommands.length > 0 && alreadyVerifiedSha === null);
5762
6130
  if (opts.dryRun) {
5763
6131
  if (!opts.quiet) {
5764
- process.stdout.write(`✅ integrate dry-run ${task.id} (base=${base} branch=${branch} verify=${shouldRunVerify ? "yes" : "no"})\n`);
6132
+ process.stdout.write(`${successMessage("integrate dry-run", task.id, `base=${base} branch=${branch} verify=${shouldRunVerify ? "yes" : "no"}`)}\n`);
5765
6133
  }
5766
6134
  return 0;
5767
6135
  }
@@ -5838,7 +6206,7 @@ async function cmdIntegrate(opts) {
5838
6206
  });
5839
6207
  }
5840
6208
  if (!opts.quiet) {
5841
- process.stdout.write(`✅ verify passed for ${task.id}\n`);
6209
+ process.stdout.write(`${successMessage("verify passed", task.id)}\n`);
5842
6210
  }
5843
6211
  }
5844
6212
  const baseShaBeforeMerge = await gitRevParse(resolved.gitRoot, [base]);
@@ -5984,7 +6352,7 @@ async function cmdIntegrate(opts) {
5984
6352
  });
5985
6353
  }
5986
6354
  if (!opts.quiet) {
5987
- process.stdout.write(`✅ verify passed for ${task.id}\n`);
6355
+ process.stdout.write(`${successMessage("verify passed", task.id)}\n`);
5988
6356
  }
5989
6357
  }
5990
6358
  try {
@@ -6007,7 +6375,7 @@ async function cmdIntegrate(opts) {
6007
6375
  throw new CliError({
6008
6376
  exitCode: 3,
6009
6377
  code: "E_VALIDATION",
6010
- message: `Missing PR artifact dir after merge: ${prDir}`,
6378
+ message: `Missing PR artifact dir after merge: ${path.relative(resolved.gitRoot, prDir)}`,
6011
6379
  });
6012
6380
  }
6013
6381
  if (verifyEntries.length > 0) {
@@ -6072,7 +6440,7 @@ async function cmdIntegrate(opts) {
6072
6440
  quiet: opts.quiet,
6073
6441
  });
6074
6442
  if (!opts.quiet) {
6075
- process.stdout.write(`✅ integrate ${task.id} (merge=${mergeHash.slice(0, 12)})\n`);
6443
+ process.stdout.write(`${successMessage("integrate", task.id, `merge=${mergeHash.slice(0, 12)}`)}\n`);
6076
6444
  }
6077
6445
  return 0;
6078
6446
  }
@@ -6106,19 +6474,23 @@ async function cmdCleanupMerged(opts) {
6106
6474
  throw new CliError({
6107
6475
  exitCode: 2,
6108
6476
  code: "E_USAGE",
6109
- message: "cleanup merged is only supported when workflow_mode=branch_pr",
6477
+ message: workflowModeMessage(loaded.config.workflow_mode, "branch_pr"),
6110
6478
  });
6111
6479
  }
6112
6480
  await ensureGitClean({ cwd: opts.cwd, rootOverride: opts.rootOverride });
6113
6481
  const baseBranch = (opts.base ?? (await getBaseBranch({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }))).trim();
6114
6482
  if (!baseBranch) {
6115
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: CLEANUP_MERGED_USAGE });
6483
+ throw new CliError({
6484
+ exitCode: 2,
6485
+ code: "E_USAGE",
6486
+ message: usageMessage(CLEANUP_MERGED_USAGE, CLEANUP_MERGED_USAGE_EXAMPLE),
6487
+ });
6116
6488
  }
6117
6489
  if (!(await gitBranchExists(resolved.gitRoot, baseBranch))) {
6118
6490
  throw new CliError({
6119
6491
  exitCode: 5,
6120
6492
  code: "E_GIT",
6121
- message: `Unknown base branch: ${baseBranch}`,
6493
+ message: unknownEntityMessage("base branch", baseBranch),
6122
6494
  });
6123
6495
  }
6124
6496
  const currentBranch = await gitCurrentBranch(resolved.gitRoot);
@@ -6209,7 +6581,7 @@ async function cmdCleanupMerged(opts) {
6209
6581
  });
6210
6582
  }
6211
6583
  if (!opts.quiet) {
6212
- process.stdout.write(`✅ cleanup merged deleted=${candidates.length}\n`);
6584
+ process.stdout.write(`${successMessage("cleanup merged", undefined, `deleted=${candidates.length}`)}\n`);
6213
6585
  }
6214
6586
  return 0;
6215
6587
  }
@@ -6274,7 +6646,9 @@ async function cmdHooksUninstall(opts) {
6274
6646
  removed++;
6275
6647
  }
6276
6648
  if (!opts.quiet) {
6277
- process.stdout.write(removed > 0 ? "OK\n" : "No agentplane hooks found\n");
6649
+ process.stdout.write(removed > 0
6650
+ ? `${successMessage("removed hooks", undefined, `count=${removed}`)}\n`
6651
+ : `${infoMessage("no agentplane hooks found")}\n`);
6278
6652
  }
6279
6653
  return 0;
6280
6654
  }
@@ -6292,7 +6666,7 @@ async function cmdHooksRun(opts) {
6292
6666
  throw new CliError({
6293
6667
  exitCode: 2,
6294
6668
  code: "E_USAGE",
6295
- message: "Missing commit message file",
6669
+ message: "Missing commit message file path",
6296
6670
  });
6297
6671
  }
6298
6672
  const raw = await readFile(messagePath, "utf8");
@@ -6409,10 +6783,14 @@ async function cmdBranchBaseGet(opts) {
6409
6783
  async function cmdBranchBaseSet(opts) {
6410
6784
  const trimmed = opts.value.trim();
6411
6785
  if (trimmed.length === 0) {
6412
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BRANCH_BASE_USAGE });
6413
- }
6414
- try {
6415
- const value = await setPinnedBaseBranch({
6786
+ throw new CliError({
6787
+ exitCode: 2,
6788
+ code: "E_USAGE",
6789
+ message: usageMessage(BRANCH_BASE_USAGE, BRANCH_BASE_USAGE_EXAMPLE),
6790
+ });
6791
+ }
6792
+ try {
6793
+ const value = await setPinnedBaseBranch({
6416
6794
  cwd: opts.cwd,
6417
6795
  rootOverride: opts.rootOverride ?? null,
6418
6796
  value: trimmed,
@@ -6434,20 +6812,24 @@ async function cmdBranchStatus(opts) {
6434
6812
  const branch = (opts.branch ?? (await gitCurrentBranch(resolved.gitRoot))).trim();
6435
6813
  const base = (opts.base ?? (await getBaseBranch({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }))).trim();
6436
6814
  if (!branch || !base) {
6437
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BRANCH_STATUS_USAGE });
6815
+ throw new CliError({
6816
+ exitCode: 2,
6817
+ code: "E_USAGE",
6818
+ message: usageMessage(BRANCH_STATUS_USAGE, BRANCH_STATUS_USAGE_EXAMPLE),
6819
+ });
6438
6820
  }
6439
6821
  if (!(await gitBranchExists(resolved.gitRoot, branch))) {
6440
6822
  throw new CliError({
6441
6823
  exitCode: 2,
6442
6824
  code: "E_USAGE",
6443
- message: `Unknown branch: ${branch}`,
6825
+ message: unknownEntityMessage("branch", branch),
6444
6826
  });
6445
6827
  }
6446
6828
  if (!(await gitBranchExists(resolved.gitRoot, base))) {
6447
6829
  throw new CliError({
6448
6830
  exitCode: 2,
6449
6831
  code: "E_USAGE",
6450
- message: `Unknown base branch: ${base}`,
6832
+ message: unknownEntityMessage("base branch", base),
6451
6833
  });
6452
6834
  }
6453
6835
  const taskId = parseTaskIdFromBranch(loaded.config.branch.task_prefix, branch);
@@ -6469,7 +6851,11 @@ async function cmdBranchRemove(opts) {
6469
6851
  const branch = (opts.branch ?? "").trim();
6470
6852
  const worktree = (opts.worktree ?? "").trim();
6471
6853
  if (!branch && !worktree) {
6472
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BRANCH_REMOVE_USAGE });
6854
+ throw new CliError({
6855
+ exitCode: 2,
6856
+ code: "E_USAGE",
6857
+ message: usageMessage(BRANCH_REMOVE_USAGE, BRANCH_REMOVE_USAGE_EXAMPLE),
6858
+ });
6473
6859
  }
6474
6860
  try {
6475
6861
  const resolved = await resolveProject({
@@ -6491,7 +6877,7 @@ async function cmdBranchRemove(opts) {
6491
6877
  }
6492
6878
  await execFileAsync("git", ["worktree", "remove", ...(opts.force ? ["--force"] : []), worktreePath], { cwd: resolved.gitRoot, env: gitEnv() });
6493
6879
  if (!opts.quiet) {
6494
- process.stdout.write(`✅ removed worktree ${worktreePath}\n`);
6880
+ process.stdout.write(`${successMessage("removed worktree", worktreePath)}\n`);
6495
6881
  }
6496
6882
  }
6497
6883
  if (branch) {
@@ -6499,7 +6885,7 @@ async function cmdBranchRemove(opts) {
6499
6885
  throw new CliError({
6500
6886
  exitCode: 2,
6501
6887
  code: "E_USAGE",
6502
- message: `Unknown branch: ${branch}`,
6888
+ message: unknownEntityMessage("branch", branch),
6503
6889
  });
6504
6890
  }
6505
6891
  await execFileAsync("git", ["branch", opts.force ? "-D" : "-d", branch], {
@@ -6507,7 +6893,7 @@ async function cmdBranchRemove(opts) {
6507
6893
  env: gitEnv(),
6508
6894
  });
6509
6895
  if (!opts.quiet) {
6510
- process.stdout.write(`✅ removed branch ${branch}\n`);
6896
+ process.stdout.write(`${successMessage("removed branch", branch)}\n`);
6511
6897
  }
6512
6898
  }
6513
6899
  return 0;
@@ -6630,7 +7016,9 @@ async function loadBackendTask(opts) {
6630
7016
  return { backend, backendId, resolved, config, task };
6631
7017
  }
6632
7018
  const TASK_DOC_SET_USAGE = "Usage: agentplane task doc set <task-id> --section <name> (--text <text> | --file <path>)";
7019
+ const TASK_DOC_SET_USAGE_EXAMPLE = 'agentplane task doc set 202602030608-F1Q8AB --section Summary --text "..."';
6633
7020
  const TASK_DOC_SHOW_USAGE = "Usage: agentplane task doc show <task-id> [--section <name>] [--quiet]";
7021
+ const TASK_DOC_SHOW_USAGE_EXAMPLE = "agentplane task doc show 202602030608-F1Q8AB --section Summary";
6634
7022
  function parseTaskDocShowFlags(args) {
6635
7023
  const out = { quiet: false };
6636
7024
  for (let i = 0; i < args.length; i++) {
@@ -6644,7 +7032,7 @@ function parseTaskDocShowFlags(args) {
6644
7032
  if (arg === "--section") {
6645
7033
  const next = args[i + 1];
6646
7034
  if (!next) {
6647
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
7035
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
6648
7036
  }
6649
7037
  out.section = next;
6650
7038
  i++;
@@ -6665,7 +7053,7 @@ function parseTaskDocSetFlags(args) {
6665
7053
  }
6666
7054
  const next = args[i + 1];
6667
7055
  if (!next) {
6668
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: `Missing value for ${arg}` });
7056
+ throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
6669
7057
  }
6670
7058
  switch (arg) {
6671
7059
  case "--section": {
@@ -6695,12 +7083,20 @@ function parseTaskDocSetFlags(args) {
6695
7083
  async function cmdTaskDocSet(opts) {
6696
7084
  const flags = parseTaskDocSetFlags(opts.args);
6697
7085
  if (!flags.section) {
6698
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: TASK_DOC_SET_USAGE });
7086
+ throw new CliError({
7087
+ exitCode: 2,
7088
+ code: "E_USAGE",
7089
+ message: usageMessage(TASK_DOC_SET_USAGE, TASK_DOC_SET_USAGE_EXAMPLE),
7090
+ });
6699
7091
  }
6700
7092
  const hasText = flags.text !== undefined;
6701
7093
  const hasFile = flags.file !== undefined;
6702
7094
  if (hasText === hasFile) {
6703
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: TASK_DOC_SET_USAGE });
7095
+ throw new CliError({
7096
+ exitCode: 2,
7097
+ code: "E_USAGE",
7098
+ message: usageMessage(TASK_DOC_SET_USAGE, TASK_DOC_SET_USAGE_EXAMPLE),
7099
+ });
6704
7100
  }
6705
7101
  const updatedBy = (flags.updatedBy ?? "agentplane").trim();
6706
7102
  if (updatedBy.length === 0) {
@@ -6724,7 +7120,7 @@ async function cmdTaskDocSet(opts) {
6724
7120
  throw new CliError({
6725
7121
  exitCode: 2,
6726
7122
  code: "E_USAGE",
6727
- message: "Configured backend does not support task docs",
7123
+ message: backendNotSupportedMessage("task docs"),
6728
7124
  });
6729
7125
  }
6730
7126
  const allowed = config.tasks.doc.sections;
@@ -6732,13 +7128,44 @@ async function cmdTaskDocSet(opts) {
6732
7128
  throw new CliError({
6733
7129
  exitCode: 2,
6734
7130
  code: "E_USAGE",
6735
- message: `Unknown doc section: ${flags.section}`,
7131
+ message: unknownEntityMessage("doc section", flags.section),
6736
7132
  });
6737
7133
  }
7134
+ const normalizedAllowed = new Set(allowed.map((section) => normalizeDocSectionName(section)));
7135
+ const targetKey = normalizeDocSectionName(flags.section);
7136
+ const headingKeys = new Set();
7137
+ for (const line of text.replaceAll("\r\n", "\n").split("\n")) {
7138
+ const match = /^##\s+(.*)$/.exec(line.trim());
7139
+ if (!match)
7140
+ continue;
7141
+ const key = normalizeDocSectionName(match[1] ?? "");
7142
+ if (key && normalizedAllowed.has(key))
7143
+ headingKeys.add(key);
7144
+ }
6738
7145
  const existing = await backend.getTaskDoc(opts.taskId);
6739
7146
  const baseDoc = ensureDocSections(existing ?? "", config.tasks.doc.required_sections);
6740
- const nextDoc = setMarkdownSection(baseDoc, flags.section, text);
6741
- await backend.setTaskDoc(opts.taskId, nextDoc, updatedBy);
7147
+ if (headingKeys.size > 0 && (headingKeys.size > 1 || !headingKeys.has(targetKey))) {
7148
+ const fullDoc = ensureDocSections(text, config.tasks.doc.required_sections);
7149
+ await backend.setTaskDoc(opts.taskId, fullDoc, updatedBy);
7150
+ }
7151
+ else {
7152
+ let nextText = text;
7153
+ if (headingKeys.size > 0 && headingKeys.has(targetKey)) {
7154
+ const lines = nextText.replaceAll("\r\n", "\n").split("\n");
7155
+ let firstContent = 0;
7156
+ while (firstContent < lines.length && lines[firstContent]?.trim() === "")
7157
+ firstContent++;
7158
+ const headingRe = new RegExp(String.raw `^##\s+${escapeRegExp(flags.section)}\s*$`);
7159
+ if (headingRe.test(lines[firstContent]?.trim() ?? "")) {
7160
+ lines.splice(firstContent, 1);
7161
+ if (lines[firstContent]?.trim() === "")
7162
+ lines.splice(firstContent, 1);
7163
+ nextText = lines.join("\n");
7164
+ }
7165
+ }
7166
+ const nextDoc = setMarkdownSection(baseDoc, flags.section, nextText);
7167
+ await backend.setTaskDoc(opts.taskId, nextDoc, updatedBy);
7168
+ }
6742
7169
  const tasksDir = path.join(resolved.gitRoot, config.paths.workflow_dir);
6743
7170
  process.stdout.write(`${path.join(tasksDir, opts.taskId, "README.md")}\n`);
6744
7171
  return 0;
@@ -6760,7 +7187,7 @@ async function cmdTaskDocShow(opts) {
6760
7187
  throw new CliError({
6761
7188
  exitCode: 2,
6762
7189
  code: "E_USAGE",
6763
- message: "Configured backend does not support task docs",
7190
+ message: backendNotSupportedMessage("task docs"),
6764
7191
  });
6765
7192
  }
6766
7193
  const doc = (await backend.getTaskDoc(opts.taskId)) ?? "";
@@ -6774,7 +7201,7 @@ async function cmdTaskDocShow(opts) {
6774
7201
  return 0;
6775
7202
  }
6776
7203
  if (!flags.quiet) {
6777
- process.stdout.write(`ℹ️ no content for section: ${flags.section}\n`);
7204
+ process.stdout.write(`${infoMessage(`section has no content: ${flags.section}`)}\n`);
6778
7205
  }
6779
7206
  return 0;
6780
7207
  }
@@ -6783,7 +7210,7 @@ async function cmdTaskDocShow(opts) {
6783
7210
  return 0;
6784
7211
  }
6785
7212
  if (!flags.quiet) {
6786
- process.stdout.write("ℹ️ no task doc metadata\n");
7213
+ process.stdout.write(`${infoMessage("task doc metadata missing")}\n`);
6787
7214
  }
6788
7215
  return 0;
6789
7216
  }
@@ -6814,7 +7241,7 @@ export async function runCli(argv) {
6814
7241
  throw new CliError({
6815
7242
  exitCode: 2,
6816
7243
  code: "E_USAGE",
6817
- message: "Usage: agentplane init [--ide <...>] [--workflow <...>] [--hooks <...>] [--require-plan-approval <...>] [--require-network-approval <...>] [--recipes <...>] [--yes] [--force|--backup]",
7244
+ message: usageMessage(INIT_USAGE, INIT_USAGE_EXAMPLE),
6818
7245
  });
6819
7246
  }
6820
7247
  return await cmdInit({ cwd: process.cwd(), rootOverride: globals.root, args: initArgs });
@@ -6822,7 +7249,11 @@ export async function runCli(argv) {
6822
7249
  if (namespace === "upgrade") {
6823
7250
  const upgradeArgs = command ? [command, ...args] : [];
6824
7251
  if (command && !command.startsWith("--")) {
6825
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: UPGRADE_USAGE });
7252
+ throw new CliError({
7253
+ exitCode: 2,
7254
+ code: "E_USAGE",
7255
+ message: usageMessage(UPGRADE_USAGE, UPGRADE_USAGE_EXAMPLE),
7256
+ });
6826
7257
  }
6827
7258
  return await cmdUpgrade({
6828
7259
  cwd: process.cwd(),
@@ -6839,7 +7270,7 @@ export async function runCli(argv) {
6839
7270
  throw new CliError({
6840
7271
  exitCode: 2,
6841
7272
  code: "E_USAGE",
6842
- message: "Usage: agentplane config set <key> <value>",
7273
+ message: usageMessage(CONFIG_SET_USAGE, CONFIG_SET_USAGE_EXAMPLE),
6843
7274
  });
6844
7275
  }
6845
7276
  return await cmdConfigSet({ cwd: process.cwd(), rootOverride: globals.root, key, value });
@@ -6853,7 +7284,7 @@ export async function runCli(argv) {
6853
7284
  throw new CliError({
6854
7285
  exitCode: 2,
6855
7286
  code: "E_USAGE",
6856
- message: "Usage: agentplane mode set <direct|branch_pr>",
7287
+ message: usageMessage(MODE_SET_USAGE, MODE_SET_USAGE_EXAMPLE),
6857
7288
  });
6858
7289
  }
6859
7290
  return await cmdModeSet({ cwd: process.cwd(), rootOverride: globals.root, mode });
@@ -6863,32 +7294,48 @@ export async function runCli(argv) {
6863
7294
  throw new CliError({
6864
7295
  exitCode: 2,
6865
7296
  code: "E_USAGE",
6866
- message: "Usage: agentplane quickstart",
7297
+ message: usageMessage(QUICKSTART_USAGE, QUICKSTART_USAGE_EXAMPLE),
6867
7298
  });
6868
7299
  }
6869
7300
  return cmdQuickstart({ cwd: process.cwd(), rootOverride: globals.root });
6870
7301
  }
6871
7302
  if (namespace === "role") {
6872
7303
  if (!command || command.startsWith("--") || args.length > 0) {
6873
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: ROLE_USAGE });
7304
+ throw new CliError({
7305
+ exitCode: 2,
7306
+ code: "E_USAGE",
7307
+ message: usageMessage(ROLE_USAGE, ROLE_USAGE_EXAMPLE),
7308
+ });
6874
7309
  }
6875
7310
  return cmdRole({ cwd: process.cwd(), rootOverride: globals.root, role: command });
6876
7311
  }
6877
7312
  if (namespace === "agents") {
6878
7313
  if (command) {
6879
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: AGENTS_USAGE });
7314
+ throw new CliError({
7315
+ exitCode: 2,
7316
+ code: "E_USAGE",
7317
+ message: usageMessage(AGENTS_USAGE, AGENTS_USAGE_EXAMPLE),
7318
+ });
6880
7319
  }
6881
7320
  return await cmdAgents({ cwd: process.cwd(), rootOverride: globals.root });
6882
7321
  }
6883
7322
  if (namespace === "ready") {
6884
7323
  if (!command || command.startsWith("--") || args.length > 0) {
6885
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: READY_USAGE });
7324
+ throw new CliError({
7325
+ exitCode: 2,
7326
+ code: "E_USAGE",
7327
+ message: usageMessage(READY_USAGE, READY_USAGE_EXAMPLE),
7328
+ });
6886
7329
  }
6887
7330
  return await cmdReady({ cwd: process.cwd(), rootOverride: globals.root, taskId: command });
6888
7331
  }
6889
7332
  if (namespace === "ide") {
6890
7333
  if (command !== "sync" || args.length > 0) {
6891
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: IDE_SYNC_USAGE });
7334
+ throw new CliError({
7335
+ exitCode: 2,
7336
+ code: "E_USAGE",
7337
+ message: usageMessage(IDE_SYNC_USAGE, IDE_SYNC_USAGE_EXAMPLE),
7338
+ });
6892
7339
  }
6893
7340
  return await cmdIdeSync({ cwd: process.cwd(), rootOverride: globals.root });
6894
7341
  }
@@ -6910,7 +7357,7 @@ export async function runCli(argv) {
6910
7357
  throw new CliError({
6911
7358
  exitCode: 2,
6912
7359
  code: "E_USAGE",
6913
- message: "Usage: agentplane task show <task-id>",
7360
+ message: usageMessage(TASK_SHOW_USAGE, TASK_SHOW_USAGE_EXAMPLE),
6914
7361
  });
6915
7362
  }
6916
7363
  return await cmdTaskShow({ cwd: process.cwd(), rootOverride: globals.root, taskId });
@@ -6927,7 +7374,7 @@ export async function runCli(argv) {
6927
7374
  throw new CliError({
6928
7375
  exitCode: 2,
6929
7376
  code: "E_USAGE",
6930
- message: "Usage: agentplane task search <query> [flags]",
7377
+ message: usageMessage(TASK_SEARCH_USAGE, TASK_SEARCH_USAGE_EXAMPLE),
6931
7378
  });
6932
7379
  }
6933
7380
  return await cmdTaskSearch({
@@ -6956,7 +7403,11 @@ export async function runCli(argv) {
6956
7403
  const [subcommand, taskId, ...restArgs] = args;
6957
7404
  if (subcommand === "show") {
6958
7405
  if (!taskId) {
6959
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: TASK_DOC_SHOW_USAGE });
7406
+ throw new CliError({
7407
+ exitCode: 2,
7408
+ code: "E_USAGE",
7409
+ message: usageMessage(TASK_DOC_SHOW_USAGE, TASK_DOC_SHOW_USAGE_EXAMPLE),
7410
+ });
6960
7411
  }
6961
7412
  return await cmdTaskDocShow({
6962
7413
  cwd: process.cwd(),
@@ -6967,7 +7418,11 @@ export async function runCli(argv) {
6967
7418
  }
6968
7419
  if (subcommand === "set") {
6969
7420
  if (!taskId) {
6970
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: TASK_DOC_SET_USAGE });
7421
+ throw new CliError({
7422
+ exitCode: 2,
7423
+ code: "E_USAGE",
7424
+ message: usageMessage(TASK_DOC_SET_USAGE, TASK_DOC_SET_USAGE_EXAMPLE),
7425
+ });
6971
7426
  }
6972
7427
  return await cmdTaskDocSet({
6973
7428
  cwd: process.cwd(),
@@ -6976,7 +7431,11 @@ export async function runCli(argv) {
6976
7431
  args: restArgs,
6977
7432
  });
6978
7433
  }
6979
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: TASK_DOC_SET_USAGE });
7434
+ throw new CliError({
7435
+ exitCode: 2,
7436
+ code: "E_USAGE",
7437
+ message: usageMessage(TASK_DOC_SET_USAGE, TASK_DOC_SET_USAGE_EXAMPLE),
7438
+ });
6980
7439
  }
6981
7440
  if (namespace === "task" && command === "comment") {
6982
7441
  const [taskId, ...restArgs] = args;
@@ -6984,7 +7443,7 @@ export async function runCli(argv) {
6984
7443
  throw new CliError({
6985
7444
  exitCode: 2,
6986
7445
  code: "E_USAGE",
6987
- message: "Usage: agentplane task comment <task-id>",
7446
+ message: usageMessage(TASK_COMMENT_USAGE, TASK_COMMENT_USAGE_EXAMPLE),
6988
7447
  });
6989
7448
  }
6990
7449
  let author = "";
@@ -6999,7 +7458,7 @@ export async function runCli(argv) {
6999
7458
  throw new CliError({
7000
7459
  exitCode: 2,
7001
7460
  code: "E_USAGE",
7002
- message: "Missing value for --author",
7461
+ message: missingValueMessage("--author"),
7003
7462
  });
7004
7463
  }
7005
7464
  author = next;
@@ -7012,7 +7471,7 @@ export async function runCli(argv) {
7012
7471
  throw new CliError({
7013
7472
  exitCode: 2,
7014
7473
  code: "E_USAGE",
7015
- message: "Missing value for --body",
7474
+ message: missingValueMessage("--body"),
7016
7475
  });
7017
7476
  }
7018
7477
  body = next;
@@ -7023,7 +7482,7 @@ export async function runCli(argv) {
7023
7482
  throw new CliError({
7024
7483
  exitCode: 2,
7025
7484
  code: "E_USAGE",
7026
- message: "Usage: agentplane task comment <task-id>",
7485
+ message: usageMessage(TASK_COMMENT_USAGE, TASK_COMMENT_USAGE_EXAMPLE),
7027
7486
  });
7028
7487
  }
7029
7488
  }
@@ -7031,7 +7490,7 @@ export async function runCli(argv) {
7031
7490
  throw new CliError({
7032
7491
  exitCode: 2,
7033
7492
  code: "E_USAGE",
7034
- message: "Usage: agentplane task comment <task-id>",
7493
+ message: usageMessage(TASK_COMMENT_USAGE, TASK_COMMENT_USAGE_EXAMPLE),
7035
7494
  });
7036
7495
  }
7037
7496
  return await cmdTaskComment({
@@ -7048,7 +7507,7 @@ export async function runCli(argv) {
7048
7507
  throw new CliError({
7049
7508
  exitCode: 2,
7050
7509
  code: "E_USAGE",
7051
- message: "Usage: agentplane task set-status <task-id> <status> [flags]",
7510
+ message: usageMessage(TASK_SET_STATUS_USAGE, TASK_SET_STATUS_USAGE_EXAMPLE),
7052
7511
  });
7053
7512
  }
7054
7513
  let author;
@@ -7073,7 +7532,7 @@ export async function runCli(argv) {
7073
7532
  throw new CliError({
7074
7533
  exitCode: 2,
7075
7534
  code: "E_USAGE",
7076
- message: "Missing value for --author",
7535
+ message: missingValueMessage("--author"),
7077
7536
  });
7078
7537
  author = next;
7079
7538
  i++;
@@ -7085,7 +7544,7 @@ export async function runCli(argv) {
7085
7544
  throw new CliError({
7086
7545
  exitCode: 2,
7087
7546
  code: "E_USAGE",
7088
- message: "Missing value for --body",
7547
+ message: missingValueMessage("--body"),
7089
7548
  });
7090
7549
  body = next;
7091
7550
  i++;
@@ -7097,7 +7556,7 @@ export async function runCli(argv) {
7097
7556
  throw new CliError({
7098
7557
  exitCode: 2,
7099
7558
  code: "E_USAGE",
7100
- message: "Missing value for --commit",
7559
+ message: missingValueMessage("--commit"),
7101
7560
  });
7102
7561
  commit = next;
7103
7562
  i++;
@@ -7117,7 +7576,7 @@ export async function runCli(argv) {
7117
7576
  throw new CliError({
7118
7577
  exitCode: 2,
7119
7578
  code: "E_USAGE",
7120
- message: "Missing value for --commit-emoji",
7579
+ message: missingValueMessage("--commit-emoji"),
7121
7580
  });
7122
7581
  commitEmoji = next;
7123
7582
  i++;
@@ -7129,7 +7588,7 @@ export async function runCli(argv) {
7129
7588
  throw new CliError({
7130
7589
  exitCode: 2,
7131
7590
  code: "E_USAGE",
7132
- message: "Missing value for --commit-allow",
7591
+ message: missingValueMessage("--commit-allow"),
7133
7592
  });
7134
7593
  commitAllow.push(next);
7135
7594
  i++;
@@ -7159,7 +7618,7 @@ export async function runCli(argv) {
7159
7618
  throw new CliError({
7160
7619
  exitCode: 2,
7161
7620
  code: "E_USAGE",
7162
- message: "Usage: agentplane task set-status <task-id> <status> [flags]",
7621
+ message: usageMessage(TASK_SET_STATUS_USAGE, TASK_SET_STATUS_USAGE_EXAMPLE),
7163
7622
  });
7164
7623
  }
7165
7624
  }
@@ -7190,7 +7649,11 @@ export async function runCli(argv) {
7190
7649
  }
7191
7650
  if (subcommand === "set") {
7192
7651
  if (!value) {
7193
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BRANCH_BASE_USAGE });
7652
+ throw new CliError({
7653
+ exitCode: 2,
7654
+ code: "E_USAGE",
7655
+ message: usageMessage(BRANCH_BASE_USAGE, BRANCH_BASE_USAGE_EXAMPLE),
7656
+ });
7194
7657
  }
7195
7658
  return await cmdBranchBaseSet({
7196
7659
  cwd: process.cwd(),
@@ -7198,7 +7661,11 @@ export async function runCli(argv) {
7198
7661
  value,
7199
7662
  });
7200
7663
  }
7201
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BRANCH_BASE_USAGE });
7664
+ throw new CliError({
7665
+ exitCode: 2,
7666
+ code: "E_USAGE",
7667
+ message: usageMessage(BRANCH_BASE_USAGE, BRANCH_BASE_USAGE_EXAMPLE),
7668
+ });
7202
7669
  }
7203
7670
  if (command === "status") {
7204
7671
  let branch;
@@ -7213,7 +7680,7 @@ export async function runCli(argv) {
7213
7680
  throw new CliError({
7214
7681
  exitCode: 2,
7215
7682
  code: "E_USAGE",
7216
- message: BRANCH_STATUS_USAGE,
7683
+ message: usageMessage(BRANCH_STATUS_USAGE, BRANCH_STATUS_USAGE_EXAMPLE),
7217
7684
  });
7218
7685
  branch = next;
7219
7686
  i++;
@@ -7225,7 +7692,7 @@ export async function runCli(argv) {
7225
7692
  throw new CliError({
7226
7693
  exitCode: 2,
7227
7694
  code: "E_USAGE",
7228
- message: BRANCH_STATUS_USAGE,
7695
+ message: usageMessage(BRANCH_STATUS_USAGE, BRANCH_STATUS_USAGE_EXAMPLE),
7229
7696
  });
7230
7697
  base = next;
7231
7698
  i++;
@@ -7235,7 +7702,7 @@ export async function runCli(argv) {
7235
7702
  throw new CliError({
7236
7703
  exitCode: 2,
7237
7704
  code: "E_USAGE",
7238
- message: BRANCH_STATUS_USAGE,
7705
+ message: usageMessage(BRANCH_STATUS_USAGE, BRANCH_STATUS_USAGE_EXAMPLE),
7239
7706
  });
7240
7707
  }
7241
7708
  }
@@ -7261,7 +7728,7 @@ export async function runCli(argv) {
7261
7728
  throw new CliError({
7262
7729
  exitCode: 2,
7263
7730
  code: "E_USAGE",
7264
- message: BRANCH_REMOVE_USAGE,
7731
+ message: usageMessage(BRANCH_REMOVE_USAGE, BRANCH_REMOVE_USAGE_EXAMPLE),
7265
7732
  });
7266
7733
  branch = next;
7267
7734
  i++;
@@ -7273,7 +7740,7 @@ export async function runCli(argv) {
7273
7740
  throw new CliError({
7274
7741
  exitCode: 2,
7275
7742
  code: "E_USAGE",
7276
- message: BRANCH_REMOVE_USAGE,
7743
+ message: usageMessage(BRANCH_REMOVE_USAGE, BRANCH_REMOVE_USAGE_EXAMPLE),
7277
7744
  });
7278
7745
  worktree = next;
7279
7746
  i++;
@@ -7291,7 +7758,7 @@ export async function runCli(argv) {
7291
7758
  throw new CliError({
7292
7759
  exitCode: 2,
7293
7760
  code: "E_USAGE",
7294
- message: BRANCH_REMOVE_USAGE,
7761
+ message: usageMessage(BRANCH_REMOVE_USAGE, BRANCH_REMOVE_USAGE_EXAMPLE),
7295
7762
  });
7296
7763
  }
7297
7764
  }
@@ -7304,12 +7771,20 @@ export async function runCli(argv) {
7304
7771
  quiet,
7305
7772
  });
7306
7773
  }
7307
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BRANCH_BASE_USAGE });
7774
+ throw new CliError({
7775
+ exitCode: 2,
7776
+ code: "E_USAGE",
7777
+ message: usageMessage(BRANCH_BASE_USAGE, BRANCH_BASE_USAGE_EXAMPLE),
7778
+ });
7308
7779
  }
7309
7780
  if (namespace === "work" && command === "start") {
7310
7781
  const [taskId, ...restArgs] = args;
7311
7782
  if (!taskId) {
7312
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: WORK_START_USAGE });
7783
+ throw new CliError({
7784
+ exitCode: 2,
7785
+ code: "E_USAGE",
7786
+ message: usageMessage(WORK_START_USAGE, WORK_START_USAGE_EXAMPLE),
7787
+ });
7313
7788
  }
7314
7789
  let agent = "";
7315
7790
  let slug = "";
@@ -7321,7 +7796,11 @@ export async function runCli(argv) {
7321
7796
  if (arg === "--agent") {
7322
7797
  const next = restArgs[i + 1];
7323
7798
  if (!next)
7324
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: WORK_START_USAGE });
7799
+ throw new CliError({
7800
+ exitCode: 2,
7801
+ code: "E_USAGE",
7802
+ message: usageMessage(WORK_START_USAGE, WORK_START_USAGE_EXAMPLE),
7803
+ });
7325
7804
  agent = next;
7326
7805
  i++;
7327
7806
  continue;
@@ -7329,7 +7808,11 @@ export async function runCli(argv) {
7329
7808
  if (arg === "--slug") {
7330
7809
  const next = restArgs[i + 1];
7331
7810
  if (!next)
7332
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: WORK_START_USAGE });
7811
+ throw new CliError({
7812
+ exitCode: 2,
7813
+ code: "E_USAGE",
7814
+ message: usageMessage(WORK_START_USAGE, WORK_START_USAGE_EXAMPLE),
7815
+ });
7333
7816
  slug = next;
7334
7817
  i++;
7335
7818
  continue;
@@ -7338,10 +7821,18 @@ export async function runCli(argv) {
7338
7821
  worktree = true;
7339
7822
  continue;
7340
7823
  }
7341
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: WORK_START_USAGE });
7824
+ throw new CliError({
7825
+ exitCode: 2,
7826
+ code: "E_USAGE",
7827
+ message: usageMessage(WORK_START_USAGE, WORK_START_USAGE_EXAMPLE),
7828
+ });
7342
7829
  }
7343
7830
  if (!agent || !slug || !worktree) {
7344
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: WORK_START_USAGE });
7831
+ throw new CliError({
7832
+ exitCode: 2,
7833
+ code: "E_USAGE",
7834
+ message: usageMessage(WORK_START_USAGE, WORK_START_USAGE_EXAMPLE),
7835
+ });
7345
7836
  }
7346
7837
  return await cmdWorkStart({
7347
7838
  cwd: process.cwd(),
@@ -7355,7 +7846,11 @@ export async function runCli(argv) {
7355
7846
  if (namespace === "pr") {
7356
7847
  const [taskId, ...restArgs] = args;
7357
7848
  if (!taskId) {
7358
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_OPEN_USAGE });
7849
+ throw new CliError({
7850
+ exitCode: 2,
7851
+ code: "E_USAGE",
7852
+ message: usageMessage(PR_OPEN_USAGE, PR_OPEN_USAGE_EXAMPLE),
7853
+ });
7359
7854
  }
7360
7855
  if (command === "open") {
7361
7856
  let author = "";
@@ -7367,7 +7862,11 @@ export async function runCli(argv) {
7367
7862
  if (arg === "--author") {
7368
7863
  const next = restArgs[i + 1];
7369
7864
  if (!next)
7370
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_OPEN_USAGE });
7865
+ throw new CliError({
7866
+ exitCode: 2,
7867
+ code: "E_USAGE",
7868
+ message: usageMessage(PR_OPEN_USAGE, PR_OPEN_USAGE_EXAMPLE),
7869
+ });
7371
7870
  author = next;
7372
7871
  i++;
7373
7872
  continue;
@@ -7375,15 +7874,27 @@ export async function runCli(argv) {
7375
7874
  if (arg === "--branch") {
7376
7875
  const next = restArgs[i + 1];
7377
7876
  if (!next)
7378
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_OPEN_USAGE });
7877
+ throw new CliError({
7878
+ exitCode: 2,
7879
+ code: "E_USAGE",
7880
+ message: usageMessage(PR_OPEN_USAGE, PR_OPEN_USAGE_EXAMPLE),
7881
+ });
7379
7882
  branch = next;
7380
7883
  i++;
7381
7884
  continue;
7382
7885
  }
7383
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_OPEN_USAGE });
7886
+ throw new CliError({
7887
+ exitCode: 2,
7888
+ code: "E_USAGE",
7889
+ message: usageMessage(PR_OPEN_USAGE, PR_OPEN_USAGE_EXAMPLE),
7890
+ });
7384
7891
  }
7385
7892
  if (!author) {
7386
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_OPEN_USAGE });
7893
+ throw new CliError({
7894
+ exitCode: 2,
7895
+ code: "E_USAGE",
7896
+ message: usageMessage(PR_OPEN_USAGE, PR_OPEN_USAGE_EXAMPLE),
7897
+ });
7387
7898
  }
7388
7899
  return await cmdPrOpen({
7389
7900
  cwd: process.cwd(),
@@ -7395,7 +7906,11 @@ export async function runCli(argv) {
7395
7906
  }
7396
7907
  if (command === "update") {
7397
7908
  if (restArgs.length > 0) {
7398
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_UPDATE_USAGE });
7909
+ throw new CliError({
7910
+ exitCode: 2,
7911
+ code: "E_USAGE",
7912
+ message: usageMessage(PR_UPDATE_USAGE, PR_UPDATE_USAGE_EXAMPLE),
7913
+ });
7399
7914
  }
7400
7915
  return await cmdPrUpdate({
7401
7916
  cwd: process.cwd(),
@@ -7405,7 +7920,11 @@ export async function runCli(argv) {
7405
7920
  }
7406
7921
  if (command === "check") {
7407
7922
  if (restArgs.length > 0) {
7408
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_CHECK_USAGE });
7923
+ throw new CliError({
7924
+ exitCode: 2,
7925
+ code: "E_USAGE",
7926
+ message: usageMessage(PR_CHECK_USAGE, PR_CHECK_USAGE_EXAMPLE),
7927
+ });
7409
7928
  }
7410
7929
  return await cmdPrCheck({
7411
7930
  cwd: process.cwd(),
@@ -7423,7 +7942,11 @@ export async function runCli(argv) {
7423
7942
  if (arg === "--author") {
7424
7943
  const next = restArgs[i + 1];
7425
7944
  if (!next)
7426
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_NOTE_USAGE });
7945
+ throw new CliError({
7946
+ exitCode: 2,
7947
+ code: "E_USAGE",
7948
+ message: usageMessage(PR_NOTE_USAGE, PR_NOTE_USAGE_EXAMPLE),
7949
+ });
7427
7950
  author = next;
7428
7951
  i++;
7429
7952
  continue;
@@ -7431,15 +7954,27 @@ export async function runCli(argv) {
7431
7954
  if (arg === "--body") {
7432
7955
  const next = restArgs[i + 1];
7433
7956
  if (!next)
7434
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_NOTE_USAGE });
7957
+ throw new CliError({
7958
+ exitCode: 2,
7959
+ code: "E_USAGE",
7960
+ message: usageMessage(PR_NOTE_USAGE, PR_NOTE_USAGE_EXAMPLE),
7961
+ });
7435
7962
  body = next;
7436
7963
  i++;
7437
7964
  continue;
7438
7965
  }
7439
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_NOTE_USAGE });
7966
+ throw new CliError({
7967
+ exitCode: 2,
7968
+ code: "E_USAGE",
7969
+ message: usageMessage(PR_NOTE_USAGE, PR_NOTE_USAGE_EXAMPLE),
7970
+ });
7440
7971
  }
7441
7972
  if (!author || !body) {
7442
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: PR_NOTE_USAGE });
7973
+ throw new CliError({
7974
+ exitCode: 2,
7975
+ code: "E_USAGE",
7976
+ message: usageMessage(PR_NOTE_USAGE, PR_NOTE_USAGE_EXAMPLE),
7977
+ });
7443
7978
  }
7444
7979
  return await cmdPrNote({
7445
7980
  cwd: process.cwd(),
@@ -7452,7 +7987,7 @@ export async function runCli(argv) {
7452
7987
  throw new CliError({
7453
7988
  exitCode: 2,
7454
7989
  code: "E_USAGE",
7455
- message: "Usage: agentplane pr open|update|check|note <task-id>",
7990
+ message: usageMessage(PR_GROUP_USAGE, PR_GROUP_USAGE_EXAMPLE),
7456
7991
  });
7457
7992
  }
7458
7993
  if (namespace === "guard") {
@@ -7468,7 +8003,11 @@ export async function runCli(argv) {
7468
8003
  if (formatFlagIndex !== -1) {
7469
8004
  const next = restArgs[formatFlagIndex + 1];
7470
8005
  if (next !== "lines" && next !== "args") {
7471
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: "Invalid --format" });
8006
+ throw new CliError({
8007
+ exitCode: 2,
8008
+ code: "E_USAGE",
8009
+ message: invalidValueForFlag("--format", String(next), "lines|args"),
8010
+ });
7472
8011
  }
7473
8012
  format = next;
7474
8013
  }
@@ -7481,7 +8020,11 @@ export async function runCli(argv) {
7481
8020
  if (subcommand === "commit") {
7482
8021
  const taskId = restArgs[0];
7483
8022
  if (!taskId) {
7484
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: GUARD_COMMIT_USAGE });
8023
+ throw new CliError({
8024
+ exitCode: 2,
8025
+ code: "E_USAGE",
8026
+ message: usageMessage(GUARD_COMMIT_USAGE, GUARD_COMMIT_USAGE_EXAMPLE),
8027
+ });
7485
8028
  }
7486
8029
  const allow = [];
7487
8030
  let message = "";
@@ -7497,7 +8040,11 @@ export async function runCli(argv) {
7497
8040
  if (arg === "--allow") {
7498
8041
  const next = restArgs[i + 1];
7499
8042
  if (!next)
7500
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: GUARD_COMMIT_USAGE });
8043
+ throw new CliError({
8044
+ exitCode: 2,
8045
+ code: "E_USAGE",
8046
+ message: usageMessage(GUARD_COMMIT_USAGE, GUARD_COMMIT_USAGE_EXAMPLE),
8047
+ });
7501
8048
  allow.push(next);
7502
8049
  i++;
7503
8050
  continue;
@@ -7505,7 +8052,11 @@ export async function runCli(argv) {
7505
8052
  if (arg === "-m" || arg === "--message") {
7506
8053
  const next = restArgs[i + 1];
7507
8054
  if (!next)
7508
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: GUARD_COMMIT_USAGE });
8055
+ throw new CliError({
8056
+ exitCode: 2,
8057
+ code: "E_USAGE",
8058
+ message: usageMessage(GUARD_COMMIT_USAGE, GUARD_COMMIT_USAGE_EXAMPLE),
8059
+ });
7509
8060
  message = next;
7510
8061
  i++;
7511
8062
  continue;
@@ -7531,11 +8082,19 @@ export async function runCli(argv) {
7531
8082
  continue;
7532
8083
  }
7533
8084
  if (arg.startsWith("--")) {
7534
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: GUARD_COMMIT_USAGE });
8085
+ throw new CliError({
8086
+ exitCode: 2,
8087
+ code: "E_USAGE",
8088
+ message: usageMessage(GUARD_COMMIT_USAGE, GUARD_COMMIT_USAGE_EXAMPLE),
8089
+ });
7535
8090
  }
7536
8091
  }
7537
8092
  if (!message) {
7538
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: GUARD_COMMIT_USAGE });
8093
+ throw new CliError({
8094
+ exitCode: 2,
8095
+ code: "E_USAGE",
8096
+ message: usageMessage(GUARD_COMMIT_USAGE, GUARD_COMMIT_USAGE_EXAMPLE),
8097
+ });
7539
8098
  }
7540
8099
  if (autoAllow && allow.length === 0) {
7541
8100
  const staged = await getStagedFiles({
@@ -7544,12 +8103,16 @@ export async function runCli(argv) {
7544
8103
  });
7545
8104
  const prefixes = suggestAllowPrefixes(staged);
7546
8105
  if (prefixes.length === 0) {
7547
- throw new CliError({ exitCode: 5, code: "E_GIT", message: "No staged files" });
8106
+ throw new CliError({
8107
+ exitCode: 5,
8108
+ code: "E_GIT",
8109
+ message: "No staged files (git index empty)",
8110
+ });
7548
8111
  }
7549
8112
  allow.push(...prefixes);
7550
8113
  }
7551
8114
  if (allowDirty) {
7552
- // Deprecated no-op for parity with agentctl.
8115
+ // Deprecated no-op retained for compatibility.
7553
8116
  }
7554
8117
  return await cmdGuardCommit({
7555
8118
  cwd: process.cwd(),
@@ -7565,13 +8128,17 @@ export async function runCli(argv) {
7565
8128
  throw new CliError({
7566
8129
  exitCode: 2,
7567
8130
  code: "E_USAGE",
7568
- message: "Usage: agentplane guard <subcommand>",
8131
+ message: usageMessage(GUARD_USAGE, GUARD_USAGE_EXAMPLE),
7569
8132
  });
7570
8133
  }
7571
8134
  if (namespace === "commit") {
7572
8135
  const taskId = command;
7573
8136
  if (!taskId) {
7574
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: COMMIT_USAGE });
8137
+ throw new CliError({
8138
+ exitCode: 2,
8139
+ code: "E_USAGE",
8140
+ message: usageMessage(COMMIT_USAGE, COMMIT_USAGE_EXAMPLE),
8141
+ });
7575
8142
  }
7576
8143
  const allow = [];
7577
8144
  let message = "";
@@ -7587,7 +8154,11 @@ export async function runCli(argv) {
7587
8154
  if (arg === "--allow") {
7588
8155
  const next = args[i + 1];
7589
8156
  if (!next)
7590
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: COMMIT_USAGE });
8157
+ throw new CliError({
8158
+ exitCode: 2,
8159
+ code: "E_USAGE",
8160
+ message: usageMessage(COMMIT_USAGE, COMMIT_USAGE_EXAMPLE),
8161
+ });
7591
8162
  allow.push(next);
7592
8163
  i++;
7593
8164
  continue;
@@ -7595,7 +8166,11 @@ export async function runCli(argv) {
7595
8166
  if (arg === "-m" || arg === "--message") {
7596
8167
  const next = args[i + 1];
7597
8168
  if (!next)
7598
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: COMMIT_USAGE });
8169
+ throw new CliError({
8170
+ exitCode: 2,
8171
+ code: "E_USAGE",
8172
+ message: usageMessage(COMMIT_USAGE, COMMIT_USAGE_EXAMPLE),
8173
+ });
7599
8174
  message = next;
7600
8175
  i++;
7601
8176
  continue;
@@ -7621,11 +8196,19 @@ export async function runCli(argv) {
7621
8196
  continue;
7622
8197
  }
7623
8198
  if (arg.startsWith("--")) {
7624
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: COMMIT_USAGE });
8199
+ throw new CliError({
8200
+ exitCode: 2,
8201
+ code: "E_USAGE",
8202
+ message: usageMessage(COMMIT_USAGE, COMMIT_USAGE_EXAMPLE),
8203
+ });
7625
8204
  }
7626
8205
  }
7627
8206
  if (!message) {
7628
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: COMMIT_USAGE });
8207
+ throw new CliError({
8208
+ exitCode: 2,
8209
+ code: "E_USAGE",
8210
+ message: usageMessage(COMMIT_USAGE, COMMIT_USAGE_EXAMPLE),
8211
+ });
7629
8212
  }
7630
8213
  return await cmdCommit({
7631
8214
  cwd: process.cwd(),
@@ -7650,7 +8233,11 @@ export async function runCli(argv) {
7650
8233
  taskId = process.env.AGENT_PLANE_TASK_ID ?? "";
7651
8234
  }
7652
8235
  if (!taskId) {
7653
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: START_USAGE });
8236
+ throw new CliError({
8237
+ exitCode: 2,
8238
+ code: "E_USAGE",
8239
+ message: usageMessage(START_USAGE, START_USAGE_EXAMPLE),
8240
+ });
7654
8241
  }
7655
8242
  let author = "";
7656
8243
  let body = "";
@@ -7670,7 +8257,11 @@ export async function runCli(argv) {
7670
8257
  if (arg === "--author") {
7671
8258
  const next = startArgs[i + 1];
7672
8259
  if (!next)
7673
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: START_USAGE });
8260
+ throw new CliError({
8261
+ exitCode: 2,
8262
+ code: "E_USAGE",
8263
+ message: usageMessage(START_USAGE, START_USAGE_EXAMPLE),
8264
+ });
7674
8265
  author = next;
7675
8266
  i++;
7676
8267
  continue;
@@ -7678,7 +8269,11 @@ export async function runCli(argv) {
7678
8269
  if (arg === "--body") {
7679
8270
  const next = startArgs[i + 1];
7680
8271
  if (!next)
7681
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: START_USAGE });
8272
+ throw new CliError({
8273
+ exitCode: 2,
8274
+ code: "E_USAGE",
8275
+ message: usageMessage(START_USAGE, START_USAGE_EXAMPLE),
8276
+ });
7682
8277
  body = next;
7683
8278
  i++;
7684
8279
  continue;
@@ -7690,7 +8285,11 @@ export async function runCli(argv) {
7690
8285
  if (arg === "--commit-emoji") {
7691
8286
  const next = startArgs[i + 1];
7692
8287
  if (!next)
7693
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: START_USAGE });
8288
+ throw new CliError({
8289
+ exitCode: 2,
8290
+ code: "E_USAGE",
8291
+ message: usageMessage(START_USAGE, START_USAGE_EXAMPLE),
8292
+ });
7694
8293
  commitEmoji = next;
7695
8294
  i++;
7696
8295
  continue;
@@ -7698,7 +8297,11 @@ export async function runCli(argv) {
7698
8297
  if (arg === "--commit-allow") {
7699
8298
  const next = startArgs[i + 1];
7700
8299
  if (!next)
7701
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: START_USAGE });
8300
+ throw new CliError({
8301
+ exitCode: 2,
8302
+ code: "E_USAGE",
8303
+ message: usageMessage(START_USAGE, START_USAGE_EXAMPLE),
8304
+ });
7702
8305
  commitAllow.push(next);
7703
8306
  i++;
7704
8307
  continue;
@@ -7728,11 +8331,19 @@ export async function runCli(argv) {
7728
8331
  continue;
7729
8332
  }
7730
8333
  if (arg.startsWith("--")) {
7731
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: START_USAGE });
8334
+ throw new CliError({
8335
+ exitCode: 2,
8336
+ code: "E_USAGE",
8337
+ message: usageMessage(START_USAGE, START_USAGE_EXAMPLE),
8338
+ });
7732
8339
  }
7733
8340
  }
7734
8341
  if (!author || !body) {
7735
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: START_USAGE });
8342
+ throw new CliError({
8343
+ exitCode: 2,
8344
+ code: "E_USAGE",
8345
+ message: usageMessage(START_USAGE, START_USAGE_EXAMPLE),
8346
+ });
7736
8347
  }
7737
8348
  return await cmdStart({
7738
8349
  cwd: process.cwd(),
@@ -7761,7 +8372,11 @@ export async function runCli(argv) {
7761
8372
  taskId = process.env.AGENT_PLANE_TASK_ID ?? "";
7762
8373
  }
7763
8374
  if (!taskId) {
7764
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BLOCK_USAGE });
8375
+ throw new CliError({
8376
+ exitCode: 2,
8377
+ code: "E_USAGE",
8378
+ message: usageMessage(BLOCK_USAGE, BLOCK_USAGE_EXAMPLE),
8379
+ });
7765
8380
  }
7766
8381
  let author = "";
7767
8382
  let body = "";
@@ -7781,7 +8396,11 @@ export async function runCli(argv) {
7781
8396
  if (arg === "--author") {
7782
8397
  const next = blockArgs[i + 1];
7783
8398
  if (!next)
7784
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BLOCK_USAGE });
8399
+ throw new CliError({
8400
+ exitCode: 2,
8401
+ code: "E_USAGE",
8402
+ message: usageMessage(BLOCK_USAGE, BLOCK_USAGE_EXAMPLE),
8403
+ });
7785
8404
  author = next;
7786
8405
  i++;
7787
8406
  continue;
@@ -7789,7 +8408,11 @@ export async function runCli(argv) {
7789
8408
  if (arg === "--body") {
7790
8409
  const next = blockArgs[i + 1];
7791
8410
  if (!next)
7792
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BLOCK_USAGE });
8411
+ throw new CliError({
8412
+ exitCode: 2,
8413
+ code: "E_USAGE",
8414
+ message: usageMessage(BLOCK_USAGE, BLOCK_USAGE_EXAMPLE),
8415
+ });
7793
8416
  body = next;
7794
8417
  i++;
7795
8418
  continue;
@@ -7801,7 +8424,11 @@ export async function runCli(argv) {
7801
8424
  if (arg === "--commit-emoji") {
7802
8425
  const next = blockArgs[i + 1];
7803
8426
  if (!next)
7804
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BLOCK_USAGE });
8427
+ throw new CliError({
8428
+ exitCode: 2,
8429
+ code: "E_USAGE",
8430
+ message: usageMessage(BLOCK_USAGE, BLOCK_USAGE_EXAMPLE),
8431
+ });
7805
8432
  commitEmoji = next;
7806
8433
  i++;
7807
8434
  continue;
@@ -7809,7 +8436,11 @@ export async function runCli(argv) {
7809
8436
  if (arg === "--commit-allow") {
7810
8437
  const next = blockArgs[i + 1];
7811
8438
  if (!next)
7812
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BLOCK_USAGE });
8439
+ throw new CliError({
8440
+ exitCode: 2,
8441
+ code: "E_USAGE",
8442
+ message: usageMessage(BLOCK_USAGE, BLOCK_USAGE_EXAMPLE),
8443
+ });
7813
8444
  commitAllow.push(next);
7814
8445
  i++;
7815
8446
  continue;
@@ -7839,11 +8470,19 @@ export async function runCli(argv) {
7839
8470
  continue;
7840
8471
  }
7841
8472
  if (arg.startsWith("--")) {
7842
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BLOCK_USAGE });
8473
+ throw new CliError({
8474
+ exitCode: 2,
8475
+ code: "E_USAGE",
8476
+ message: usageMessage(BLOCK_USAGE, BLOCK_USAGE_EXAMPLE),
8477
+ });
7843
8478
  }
7844
8479
  }
7845
8480
  if (!author || !body) {
7846
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BLOCK_USAGE });
8481
+ throw new CliError({
8482
+ exitCode: 2,
8483
+ code: "E_USAGE",
8484
+ message: usageMessage(BLOCK_USAGE, BLOCK_USAGE_EXAMPLE),
8485
+ });
7847
8486
  }
7848
8487
  return await cmdBlock({
7849
8488
  cwd: process.cwd(),
@@ -7886,7 +8525,11 @@ export async function runCli(argv) {
7886
8525
  taskIds.push(envTaskId);
7887
8526
  }
7888
8527
  if (taskIds.length === 0) {
7889
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8528
+ throw new CliError({
8529
+ exitCode: 2,
8530
+ code: "E_USAGE",
8531
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8532
+ });
7890
8533
  }
7891
8534
  let author = "";
7892
8535
  let body = "";
@@ -7914,7 +8557,11 @@ export async function runCli(argv) {
7914
8557
  if (arg === "--author") {
7915
8558
  const next = flagArgs[i + 1];
7916
8559
  if (!next)
7917
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8560
+ throw new CliError({
8561
+ exitCode: 2,
8562
+ code: "E_USAGE",
8563
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8564
+ });
7918
8565
  author = next;
7919
8566
  i++;
7920
8567
  continue;
@@ -7922,7 +8569,11 @@ export async function runCli(argv) {
7922
8569
  if (arg === "--body") {
7923
8570
  const next = flagArgs[i + 1];
7924
8571
  if (!next)
7925
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8572
+ throw new CliError({
8573
+ exitCode: 2,
8574
+ code: "E_USAGE",
8575
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8576
+ });
7926
8577
  body = next;
7927
8578
  i++;
7928
8579
  continue;
@@ -7930,7 +8581,11 @@ export async function runCli(argv) {
7930
8581
  if (arg === "--commit") {
7931
8582
  const next = flagArgs[i + 1];
7932
8583
  if (!next)
7933
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8584
+ throw new CliError({
8585
+ exitCode: 2,
8586
+ code: "E_USAGE",
8587
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8588
+ });
7934
8589
  commit = next;
7935
8590
  i++;
7936
8591
  continue;
@@ -7958,7 +8613,11 @@ export async function runCli(argv) {
7958
8613
  if (arg === "--commit-emoji") {
7959
8614
  const next = flagArgs[i + 1];
7960
8615
  if (!next)
7961
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8616
+ throw new CliError({
8617
+ exitCode: 2,
8618
+ code: "E_USAGE",
8619
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8620
+ });
7962
8621
  commitEmoji = next;
7963
8622
  i++;
7964
8623
  continue;
@@ -7966,7 +8625,11 @@ export async function runCli(argv) {
7966
8625
  if (arg === "--commit-allow") {
7967
8626
  const next = flagArgs[i + 1];
7968
8627
  if (!next)
7969
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8628
+ throw new CliError({
8629
+ exitCode: 2,
8630
+ code: "E_USAGE",
8631
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8632
+ });
7970
8633
  commitAllow.push(next);
7971
8634
  i++;
7972
8635
  continue;
@@ -7990,7 +8653,11 @@ export async function runCli(argv) {
7990
8653
  if (arg === "--status-commit-emoji") {
7991
8654
  const next = flagArgs[i + 1];
7992
8655
  if (!next)
7993
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8656
+ throw new CliError({
8657
+ exitCode: 2,
8658
+ code: "E_USAGE",
8659
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8660
+ });
7994
8661
  statusCommitEmoji = next;
7995
8662
  i++;
7996
8663
  continue;
@@ -7998,7 +8665,11 @@ export async function runCli(argv) {
7998
8665
  if (arg === "--status-commit-allow") {
7999
8666
  const next = flagArgs[i + 1];
8000
8667
  if (!next)
8001
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8668
+ throw new CliError({
8669
+ exitCode: 2,
8670
+ code: "E_USAGE",
8671
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8672
+ });
8002
8673
  statusCommitAllow.push(next);
8003
8674
  i++;
8004
8675
  continue;
@@ -8016,11 +8687,19 @@ export async function runCli(argv) {
8016
8687
  continue;
8017
8688
  }
8018
8689
  if (arg.startsWith("--")) {
8019
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8690
+ throw new CliError({
8691
+ exitCode: 2,
8692
+ code: "E_USAGE",
8693
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8694
+ });
8020
8695
  }
8021
8696
  }
8022
8697
  if (!author || !body) {
8023
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: FINISH_USAGE });
8698
+ throw new CliError({
8699
+ exitCode: 2,
8700
+ code: "E_USAGE",
8701
+ message: usageMessage(FINISH_USAGE, FINISH_USAGE_EXAMPLE),
8702
+ });
8024
8703
  }
8025
8704
  return await cmdFinish({
8026
8705
  cwd: process.cwd(),
@@ -8057,7 +8736,11 @@ export async function runCli(argv) {
8057
8736
  taskId = process.env.AGENT_PLANE_TASK_ID ?? "";
8058
8737
  }
8059
8738
  if (!taskId) {
8060
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: VERIFY_USAGE });
8739
+ throw new CliError({
8740
+ exitCode: 2,
8741
+ code: "E_USAGE",
8742
+ message: usageMessage(VERIFY_USAGE, VERIFY_USAGE_EXAMPLE),
8743
+ });
8061
8744
  }
8062
8745
  let cwdOverride;
8063
8746
  let logPath;
@@ -8071,7 +8754,11 @@ export async function runCli(argv) {
8071
8754
  if (arg === "--cwd") {
8072
8755
  const next = verifyArgs[i + 1];
8073
8756
  if (!next)
8074
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: VERIFY_USAGE });
8757
+ throw new CliError({
8758
+ exitCode: 2,
8759
+ code: "E_USAGE",
8760
+ message: usageMessage(VERIFY_USAGE, VERIFY_USAGE_EXAMPLE),
8761
+ });
8075
8762
  cwdOverride = next;
8076
8763
  i++;
8077
8764
  continue;
@@ -8079,7 +8766,11 @@ export async function runCli(argv) {
8079
8766
  if (arg === "--log") {
8080
8767
  const next = verifyArgs[i + 1];
8081
8768
  if (!next)
8082
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: VERIFY_USAGE });
8769
+ throw new CliError({
8770
+ exitCode: 2,
8771
+ code: "E_USAGE",
8772
+ message: usageMessage(VERIFY_USAGE, VERIFY_USAGE_EXAMPLE),
8773
+ });
8083
8774
  logPath = next;
8084
8775
  i++;
8085
8776
  continue;
@@ -8097,7 +8788,11 @@ export async function runCli(argv) {
8097
8788
  continue;
8098
8789
  }
8099
8790
  if (arg.startsWith("--")) {
8100
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: VERIFY_USAGE });
8791
+ throw new CliError({
8792
+ exitCode: 2,
8793
+ code: "E_USAGE",
8794
+ message: usageMessage(VERIFY_USAGE, VERIFY_USAGE_EXAMPLE),
8795
+ });
8101
8796
  }
8102
8797
  }
8103
8798
  return await cmdVerify({
@@ -8114,7 +8809,11 @@ export async function runCli(argv) {
8114
8809
  if (namespace === "integrate") {
8115
8810
  const taskId = command;
8116
8811
  if (!taskId) {
8117
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: INTEGRATE_USAGE });
8812
+ throw new CliError({
8813
+ exitCode: 2,
8814
+ code: "E_USAGE",
8815
+ message: usageMessage(INTEGRATE_USAGE, INTEGRATE_USAGE_EXAMPLE),
8816
+ });
8118
8817
  }
8119
8818
  let branch;
8120
8819
  let base;
@@ -8129,7 +8828,11 @@ export async function runCli(argv) {
8129
8828
  if (arg === "--branch") {
8130
8829
  const next = args[i + 1];
8131
8830
  if (!next)
8132
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: INTEGRATE_USAGE });
8831
+ throw new CliError({
8832
+ exitCode: 2,
8833
+ code: "E_USAGE",
8834
+ message: usageMessage(INTEGRATE_USAGE, INTEGRATE_USAGE_EXAMPLE),
8835
+ });
8133
8836
  branch = next;
8134
8837
  i++;
8135
8838
  continue;
@@ -8137,7 +8840,11 @@ export async function runCli(argv) {
8137
8840
  if (arg === "--base") {
8138
8841
  const next = args[i + 1];
8139
8842
  if (!next)
8140
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: INTEGRATE_USAGE });
8843
+ throw new CliError({
8844
+ exitCode: 2,
8845
+ code: "E_USAGE",
8846
+ message: usageMessage(INTEGRATE_USAGE, INTEGRATE_USAGE_EXAMPLE),
8847
+ });
8141
8848
  base = next;
8142
8849
  i++;
8143
8850
  continue;
@@ -8145,7 +8852,11 @@ export async function runCli(argv) {
8145
8852
  if (arg === "--merge-strategy") {
8146
8853
  const next = args[i + 1];
8147
8854
  if (next !== "squash" && next !== "merge" && next !== "rebase") {
8148
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: INTEGRATE_USAGE });
8855
+ throw new CliError({
8856
+ exitCode: 2,
8857
+ code: "E_USAGE",
8858
+ message: usageMessage(INTEGRATE_USAGE, INTEGRATE_USAGE_EXAMPLE),
8859
+ });
8149
8860
  }
8150
8861
  mergeStrategy = next;
8151
8862
  i++;
@@ -8164,9 +8875,17 @@ export async function runCli(argv) {
8164
8875
  continue;
8165
8876
  }
8166
8877
  if (arg.startsWith("--")) {
8167
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: INTEGRATE_USAGE });
8878
+ throw new CliError({
8879
+ exitCode: 2,
8880
+ code: "E_USAGE",
8881
+ message: usageMessage(INTEGRATE_USAGE, INTEGRATE_USAGE_EXAMPLE),
8882
+ });
8168
8883
  }
8169
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: INTEGRATE_USAGE });
8884
+ throw new CliError({
8885
+ exitCode: 2,
8886
+ code: "E_USAGE",
8887
+ message: usageMessage(INTEGRATE_USAGE, INTEGRATE_USAGE_EXAMPLE),
8888
+ });
8170
8889
  }
8171
8890
  return await cmdIntegrate({
8172
8891
  cwd: process.cwd(),
@@ -8183,7 +8902,11 @@ export async function runCli(argv) {
8183
8902
  if (namespace === "cleanup") {
8184
8903
  const subcommand = command;
8185
8904
  if (subcommand !== "merged") {
8186
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: CLEANUP_MERGED_USAGE });
8905
+ throw new CliError({
8906
+ exitCode: 2,
8907
+ code: "E_USAGE",
8908
+ message: usageMessage(CLEANUP_MERGED_USAGE, CLEANUP_MERGED_USAGE_EXAMPLE),
8909
+ });
8187
8910
  }
8188
8911
  let base;
8189
8912
  let yes = false;
@@ -8196,7 +8919,11 @@ export async function runCli(argv) {
8196
8919
  if (arg === "--base") {
8197
8920
  const next = args[i + 1];
8198
8921
  if (!next)
8199
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: CLEANUP_MERGED_USAGE });
8922
+ throw new CliError({
8923
+ exitCode: 2,
8924
+ code: "E_USAGE",
8925
+ message: usageMessage(CLEANUP_MERGED_USAGE, CLEANUP_MERGED_USAGE_EXAMPLE),
8926
+ });
8200
8927
  base = next;
8201
8928
  i++;
8202
8929
  continue;
@@ -8214,9 +8941,17 @@ export async function runCli(argv) {
8214
8941
  continue;
8215
8942
  }
8216
8943
  if (arg.startsWith("--")) {
8217
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: CLEANUP_MERGED_USAGE });
8944
+ throw new CliError({
8945
+ exitCode: 2,
8946
+ code: "E_USAGE",
8947
+ message: usageMessage(CLEANUP_MERGED_USAGE, CLEANUP_MERGED_USAGE_EXAMPLE),
8948
+ });
8218
8949
  }
8219
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: CLEANUP_MERGED_USAGE });
8950
+ throw new CliError({
8951
+ exitCode: 2,
8952
+ code: "E_USAGE",
8953
+ message: usageMessage(CLEANUP_MERGED_USAGE, CLEANUP_MERGED_USAGE_EXAMPLE),
8954
+ });
8220
8955
  }
8221
8956
  return await cmdCleanupMerged({
8222
8957
  cwd: process.cwd(),
@@ -8230,17 +8965,29 @@ export async function runCli(argv) {
8230
8965
  if (namespace === "scenario") {
8231
8966
  const subcommand = command;
8232
8967
  if (!subcommand) {
8233
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SCENARIO_USAGE });
8968
+ throw new CliError({
8969
+ exitCode: 2,
8970
+ code: "E_USAGE",
8971
+ message: usageMessage(SCENARIO_USAGE, SCENARIO_USAGE_EXAMPLE),
8972
+ });
8234
8973
  }
8235
8974
  if (subcommand === "list") {
8236
8975
  if (args.length > 0) {
8237
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SCENARIO_USAGE });
8976
+ throw new CliError({
8977
+ exitCode: 2,
8978
+ code: "E_USAGE",
8979
+ message: usageMessage(SCENARIO_USAGE, SCENARIO_USAGE_EXAMPLE),
8980
+ });
8238
8981
  }
8239
8982
  return await cmdScenarioList({ cwd: process.cwd(), rootOverride: globals.root });
8240
8983
  }
8241
8984
  if (subcommand === "info") {
8242
8985
  if (args.length !== 1) {
8243
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SCENARIO_INFO_USAGE });
8986
+ throw new CliError({
8987
+ exitCode: 2,
8988
+ code: "E_USAGE",
8989
+ message: usageMessage(SCENARIO_INFO_USAGE, SCENARIO_INFO_USAGE_EXAMPLE),
8990
+ });
8244
8991
  }
8245
8992
  return await cmdScenarioInfo({
8246
8993
  cwd: process.cwd(),
@@ -8250,7 +8997,11 @@ export async function runCli(argv) {
8250
8997
  }
8251
8998
  if (subcommand === "run") {
8252
8999
  if (args.length !== 1) {
8253
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SCENARIO_RUN_USAGE });
9000
+ throw new CliError({
9001
+ exitCode: 2,
9002
+ code: "E_USAGE",
9003
+ message: usageMessage(SCENARIO_RUN_USAGE, SCENARIO_RUN_USAGE_EXAMPLE),
9004
+ });
8254
9005
  }
8255
9006
  return await cmdScenarioRun({
8256
9007
  cwd: process.cwd(),
@@ -8258,12 +9009,20 @@ export async function runCli(argv) {
8258
9009
  id: args[0],
8259
9010
  });
8260
9011
  }
8261
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: SCENARIO_USAGE });
9012
+ throw new CliError({
9013
+ exitCode: 2,
9014
+ code: "E_USAGE",
9015
+ message: usageMessage(SCENARIO_USAGE, SCENARIO_USAGE_EXAMPLE),
9016
+ });
8262
9017
  }
8263
9018
  if (namespace === "recipe" || namespace === "recipes") {
8264
9019
  const subcommand = command;
8265
9020
  if (!subcommand) {
8266
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_USAGE });
9021
+ throw new CliError({
9022
+ exitCode: 2,
9023
+ code: "E_USAGE",
9024
+ message: usageMessage(RECIPE_USAGE, RECIPE_USAGE_EXAMPLE),
9025
+ });
8267
9026
  }
8268
9027
  if (subcommand === "list") {
8269
9028
  return await cmdRecipeList({
@@ -8281,7 +9040,11 @@ export async function runCli(argv) {
8281
9040
  }
8282
9041
  if (subcommand === "info") {
8283
9042
  if (args.length !== 1) {
8284
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_INFO_USAGE });
9043
+ throw new CliError({
9044
+ exitCode: 2,
9045
+ code: "E_USAGE",
9046
+ message: usageMessage(RECIPE_INFO_USAGE, RECIPE_INFO_USAGE_EXAMPLE),
9047
+ });
8285
9048
  }
8286
9049
  return await cmdRecipeInfo({
8287
9050
  cwd: process.cwd(),
@@ -8291,7 +9054,11 @@ export async function runCli(argv) {
8291
9054
  }
8292
9055
  if (subcommand === "explain") {
8293
9056
  if (args.length !== 1) {
8294
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_EXPLAIN_USAGE });
9057
+ throw new CliError({
9058
+ exitCode: 2,
9059
+ code: "E_USAGE",
9060
+ message: usageMessage(RECIPE_EXPLAIN_USAGE, RECIPE_EXPLAIN_USAGE_EXAMPLE),
9061
+ });
8295
9062
  }
8296
9063
  return await cmdRecipeExplain({
8297
9064
  cwd: process.cwd(),
@@ -8312,7 +9079,11 @@ export async function runCli(argv) {
8312
9079
  }
8313
9080
  if (subcommand === "remove") {
8314
9081
  if (args.length !== 1) {
8315
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_REMOVE_USAGE });
9082
+ throw new CliError({
9083
+ exitCode: 2,
9084
+ code: "E_USAGE",
9085
+ message: usageMessage(RECIPE_REMOVE_USAGE, RECIPE_REMOVE_USAGE_EXAMPLE),
9086
+ });
8316
9087
  }
8317
9088
  return await cmdRecipeRemove({
8318
9089
  cwd: process.cwd(),
@@ -8324,7 +9095,11 @@ export async function runCli(argv) {
8324
9095
  const cacheSub = args[0];
8325
9096
  const cacheArgs = args.slice(1);
8326
9097
  if (!cacheSub) {
8327
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_CACHE_USAGE });
9098
+ throw new CliError({
9099
+ exitCode: 2,
9100
+ code: "E_USAGE",
9101
+ message: usageMessage(RECIPE_CACHE_USAGE, RECIPE_CACHE_USAGE_EXAMPLE),
9102
+ });
8328
9103
  }
8329
9104
  if (cacheSub === "prune") {
8330
9105
  return await cmdRecipeCachePrune({
@@ -8333,14 +9108,26 @@ export async function runCli(argv) {
8333
9108
  args: cacheArgs,
8334
9109
  });
8335
9110
  }
8336
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_CACHE_USAGE });
9111
+ throw new CliError({
9112
+ exitCode: 2,
9113
+ code: "E_USAGE",
9114
+ message: usageMessage(RECIPE_CACHE_USAGE, RECIPE_CACHE_USAGE_EXAMPLE),
9115
+ });
8337
9116
  }
8338
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: RECIPE_USAGE });
9117
+ throw new CliError({
9118
+ exitCode: 2,
9119
+ code: "E_USAGE",
9120
+ message: usageMessage(RECIPE_USAGE, RECIPE_USAGE_EXAMPLE),
9121
+ });
8339
9122
  }
8340
9123
  if (namespace === "backend") {
8341
9124
  const subcommand = command;
8342
9125
  if (subcommand !== "sync") {
8343
- throw new CliError({ exitCode: 2, code: "E_USAGE", message: BACKEND_SYNC_USAGE });
9126
+ throw new CliError({
9127
+ exitCode: 2,
9128
+ code: "E_USAGE",
9129
+ message: usageMessage(BACKEND_SYNC_USAGE, BACKEND_SYNC_USAGE_EXAMPLE),
9130
+ });
8344
9131
  }
8345
9132
  return await cmdBackendSync({
8346
9133
  cwd: process.cwd(),
@@ -8377,7 +9164,7 @@ export async function runCli(argv) {
8377
9164
  throw new CliError({
8378
9165
  exitCode: 2,
8379
9166
  code: "E_USAGE",
8380
- message: "Usage: agentplane hooks run <hook>",
9167
+ message: usageMessage(HOOKS_RUN_USAGE, HOOKS_RUN_USAGE_EXAMPLE),
8381
9168
  });
8382
9169
  }
8383
9170
  return await cmdHooksRun({
@@ -8390,7 +9177,7 @@ export async function runCli(argv) {
8390
9177
  throw new CliError({
8391
9178
  exitCode: 2,
8392
9179
  code: "E_USAGE",
8393
- message: "Usage: agentplane hooks install|uninstall",
9180
+ message: usageMessage(HOOKS_INSTALL_USAGE, HOOKS_INSTALL_USAGE_EXAMPLE),
8394
9181
  });
8395
9182
  }
8396
9183
  throw new CliError({