@towles/tool 0.0.109 → 0.0.111

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.
Files changed (190) hide show
  1. package/package.json +9 -4
  2. package/{plugins/tt-agentboard → packages/agentboard}/README.md +1 -1
  3. package/{plugins/tt-agentboard → packages/agentboard}/apps/server/package.json +2 -1
  4. package/{plugins/tt-agentboard → packages/agentboard}/apps/server/src/main.ts +6 -20
  5. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/package.json +4 -0
  6. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/DetailPanel.tsx +3 -2
  7. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/StatusBar.tsx +35 -0
  8. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/constants.ts +1 -0
  9. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/index.tsx +206 -226
  10. package/packages/agentboard/apps/tui/src/session-status.test.ts +70 -0
  11. package/packages/agentboard/apps/tui/src/session-status.ts +19 -0
  12. package/{plugins/tt-agentboard → packages/agentboard}/package.json +2 -6
  13. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/package.json +3 -0
  14. package/{plugins/tt-agentboard/packages/runtime/test → packages/agentboard/packages/runtime/src/agents}/tracker.test.ts +2 -2
  15. package/packages/agentboard/packages/runtime/src/agents/watchers/claude-code.test.ts +63 -0
  16. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/claude-code.ts +26 -2
  17. package/packages/agentboard/packages/runtime/src/config.test.ts +107 -0
  18. package/packages/agentboard/packages/runtime/src/config.ts +80 -0
  19. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/index.ts +1 -1
  20. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/plugins/loader.ts +1 -33
  21. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/git-info.ts +3 -2
  22. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/index.ts +23 -37
  23. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/launcher.ts +6 -18
  24. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/pane-scanner.ts +6 -0
  25. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/shared.ts +7 -2
  26. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/tsconfig.json +1 -1
  27. package/packages/shared/package.json +15 -0
  28. package/packages/shared/src/git/exec.ts +41 -0
  29. package/{src/utils → packages/shared/src}/git/gh-cli-wrapper.ts +13 -18
  30. package/packages/shared/src/index.ts +8 -0
  31. package/packages/shared/tsconfig.json +16 -0
  32. package/src/cli.ts +1 -1
  33. package/src/commands/agentboard.ts +51 -67
  34. package/src/{lib → commands}/auto-claude/claude-cli.ts +1 -1
  35. package/src/commands/auto-claude/config-init-helpers.ts +79 -0
  36. package/src/commands/auto-claude/config-init.test.ts +137 -0
  37. package/src/commands/auto-claude/config-init.ts +159 -0
  38. package/src/{lib → commands}/auto-claude/config.ts +4 -8
  39. package/src/{lib → commands}/auto-claude/e2e.test.ts +6 -6
  40. package/src/commands/auto-claude/explain.test.ts +58 -0
  41. package/src/commands/auto-claude/explain.ts +97 -0
  42. package/src/commands/auto-claude/index.ts +37 -14
  43. package/src/{lib → commands}/auto-claude/labels.ts +1 -1
  44. package/src/commands/auto-claude/list.ts +5 -4
  45. package/src/{lib → commands}/auto-claude/pipeline-execution.test.ts +1 -1
  46. package/src/{lib → commands}/auto-claude/pipeline.ts +1 -3
  47. package/src/commands/auto-claude/retry.test.ts +2 -2
  48. package/src/commands/auto-claude/retry.ts +5 -5
  49. package/src/commands/auto-claude/shell.ts +3 -0
  50. package/src/commands/auto-claude/status.test.ts +2 -2
  51. package/src/commands/auto-claude/status.ts +4 -4
  52. package/src/{lib → commands}/auto-claude/steps/create-pr.ts +1 -3
  53. package/src/{lib → commands}/auto-claude/steps/fetch-issues.ts +1 -1
  54. package/src/{lib → commands}/auto-claude/steps/implement.ts +1 -2
  55. package/src/{lib → commands}/auto-claude/utils-execution.test.ts +6 -6
  56. package/src/{lib → commands}/auto-claude/utils.ts +10 -4
  57. package/src/{lib/install → commands}/claude-settings.ts +1 -1
  58. package/src/commands/config/config.test.ts +129 -0
  59. package/src/commands/config/index.ts +11 -0
  60. package/src/commands/config/reset.ts +53 -0
  61. package/src/commands/config/schema.ts +19 -0
  62. package/src/commands/{config.ts → config/show.ts} +2 -2
  63. package/src/commands/config/validate.ts +51 -0
  64. package/src/commands/doctor/checks.ts +167 -0
  65. package/src/commands/doctor/format.test.ts +63 -0
  66. package/src/commands/doctor/format.ts +5 -0
  67. package/src/commands/doctor/history.test.ts +161 -0
  68. package/src/commands/doctor/history.ts +130 -0
  69. package/src/commands/doctor.ts +80 -151
  70. package/src/commands/gh/branch-clean.ts +4 -4
  71. package/src/commands/gh/branch.test.ts +4 -5
  72. package/src/commands/gh/branch.ts +10 -5
  73. package/src/commands/gh/pr.ts +6 -7
  74. package/src/{lib → commands}/graph/analyzer.test.ts +4 -4
  75. package/src/commands/graph/format.test.ts +130 -0
  76. package/src/commands/graph/format.ts +94 -0
  77. package/src/commands/graph/index.ts +69 -41
  78. package/src/{lib → commands}/graph/labels.ts +4 -4
  79. package/src/{lib → commands}/graph/server.ts +2 -2
  80. package/src/{lib → commands}/graph/types.ts +2 -0
  81. package/src/commands/graph.test.ts +1 -1
  82. package/src/commands/install.ts +6 -6
  83. package/src/commands/journal/daily-notes.ts +4 -7
  84. package/src/{lib → commands}/journal/fs.ts +1 -1
  85. package/src/commands/journal/index.ts +2 -0
  86. package/src/commands/journal/list.test.ts +174 -0
  87. package/src/commands/journal/list.ts +213 -0
  88. package/src/commands/journal/meeting.ts +4 -7
  89. package/src/commands/journal/note.ts +4 -7
  90. package/src/{lib → commands}/journal/paths.ts +1 -1
  91. package/src/commands/journal/search.test.ts +156 -0
  92. package/src/commands/journal/search.ts +256 -0
  93. package/src/{lib → commands}/journal/templates.ts +1 -1
  94. package/src/config/settings.ts +35 -26
  95. package/plugins/tt-agentboard/bun.lock +0 -444
  96. package/plugins/tt-agentboard/packages/runtime/src/config.ts +0 -70
  97. package/plugins/tt-agentboard/packages/runtime/test/config.test.ts +0 -83
  98. package/plugins/tt-auto-claude/.claude-plugin/plugin.json +0 -8
  99. package/plugins/tt-auto-claude/commands/create-issue.md +0 -20
  100. package/plugins/tt-auto-claude/commands/list.md +0 -21
  101. package/plugins/tt-auto-claude/skills/auto-claude/SKILL.md +0 -71
  102. package/plugins/tt-core/promptfooconfig.interview-me.yaml +0 -155
  103. package/plugins/tt-core/promptfooconfig.refine-text.yaml +0 -242
  104. package/plugins/tt-core/promptfooconfig.tdd.yaml +0 -144
  105. package/plugins/tt-core/promptfooconfig.write-prd.yaml +0 -145
  106. package/src/commands/config.test.ts +0 -9
  107. package/src/lib/auto-claude/index.ts +0 -15
  108. package/src/lib/auto-claude/shell.ts +0 -6
  109. package/src/lib/graph/index.ts +0 -24
  110. package/src/lib/journal/index.ts +0 -11
  111. package/src/utils/git/exec.ts +0 -18
  112. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/build.ts +0 -0
  113. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/bunfig.toml +0 -0
  114. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/scripts/sessionizer.sh +0 -0
  115. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/DiffStats.tsx +0 -0
  116. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/SessionCard.tsx +0 -0
  117. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/detail-panel-height.ts +0 -0
  118. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/mux-context.ts +0 -0
  119. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/tsconfig.json +0 -0
  120. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/package.json +0 -0
  121. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/client.ts +0 -0
  122. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/index.ts +0 -0
  123. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/provider.ts +0 -0
  124. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/tsconfig.json +0 -0
  125. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/tracker.ts +0 -0
  126. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/amp.ts +0 -0
  127. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/codex.ts +0 -0
  128. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/opencode.ts +0 -0
  129. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/agent-watcher.ts +0 -0
  130. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/agent.ts +0 -0
  131. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/index.ts +0 -0
  132. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/mux.ts +0 -0
  133. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/debug.ts +0 -0
  134. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/mux/detect.ts +0 -0
  135. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/mux/registry.ts +0 -0
  136. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/context.ts +0 -0
  137. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/metadata-store.ts +0 -0
  138. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/port-scanner.ts +0 -0
  139. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/session-order.ts +0 -0
  140. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/sidebar-manager.ts +0 -0
  141. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/sidebar-width-sync.ts +0 -0
  142. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/themes.ts +0 -0
  143. /package/{plugins/tt-agentboard → packages/agentboard}/tsconfig.json +0 -0
  144. /package/{plugins/tt-core → packages/core}/.claude-plugin/plugin.json +0 -0
  145. /package/{plugins/tt-core → packages/core}/README.md +0 -0
  146. /package/{plugins/tt-core → packages/core}/commands/improve-architecture.md +0 -0
  147. /package/{plugins/tt-core → packages/core}/commands/interview-me.md +0 -0
  148. /package/{plugins/tt-core → packages/core}/commands/prd-to-issues.md +0 -0
  149. /package/{plugins/tt-core → packages/core}/commands/refine-text.md +0 -0
  150. /package/{plugins/tt-core → packages/core}/commands/task.md +0 -0
  151. /package/{plugins/tt-core → packages/core}/commands/tdd.md +0 -0
  152. /package/{plugins/tt-core → packages/core}/commands/write-prd.md +0 -0
  153. /package/{plugins/tt-core → packages/core}/skills/towles-tool/SKILL.md +0 -0
  154. /package/{src/utils → packages/shared/src}/date-utils.test.ts +0 -0
  155. /package/{src/utils → packages/shared/src}/date-utils.ts +0 -0
  156. /package/{src/utils → packages/shared/src}/fs.ts +0 -0
  157. /package/{src/utils → packages/shared/src}/git/branch-name.test.ts +0 -0
  158. /package/{src/utils → packages/shared/src}/git/branch-name.ts +0 -0
  159. /package/{src/utils → packages/shared/src}/git/gh-cli-wrapper.test.ts +0 -0
  160. /package/{src/utils → packages/shared/src}/render.test.ts +0 -0
  161. /package/{src/utils → packages/shared/src}/render.ts +0 -0
  162. /package/src/{lib → commands}/auto-claude/config.test.ts +0 -0
  163. /package/src/{lib → commands}/auto-claude/labels.test.ts +0 -0
  164. /package/src/{lib → commands}/auto-claude/pipeline.test.ts +0 -0
  165. /package/src/{lib → commands}/auto-claude/prompt-templates/01_plan.prompt.md +0 -0
  166. /package/src/{lib → commands}/auto-claude/prompt-templates/02_implement.prompt.md +0 -0
  167. /package/src/{lib → commands}/auto-claude/prompt-templates/03_simplify.prompt.md +0 -0
  168. /package/src/{lib → commands}/auto-claude/prompt-templates/04_review.prompt.md +0 -0
  169. /package/src/{lib → commands}/auto-claude/prompt-templates/CLAUDE.md +0 -0
  170. /package/src/{lib → commands}/auto-claude/prompt-templates/index.test.ts +0 -0
  171. /package/src/{lib → commands}/auto-claude/prompt-templates/index.ts +0 -0
  172. /package/src/{lib → commands}/auto-claude/run-claude.test.ts +0 -0
  173. /package/src/{lib → commands}/auto-claude/spawn-claude.ts +0 -0
  174. /package/src/{lib → commands}/auto-claude/steps/simple-steps.ts +0 -0
  175. /package/src/{lib → commands}/auto-claude/steps/steps.test.ts +0 -0
  176. /package/src/{lib → commands}/auto-claude/stream-parser.test.ts +0 -0
  177. /package/src/{lib → commands}/auto-claude/stream-parser.ts +0 -0
  178. /package/src/{lib → commands}/auto-claude/templates.test.ts +0 -0
  179. /package/src/{lib → commands}/auto-claude/templates.ts +0 -0
  180. /package/src/{lib → commands}/auto-claude/test-helpers.ts +0 -0
  181. /package/src/{lib → commands}/auto-claude/utils.test.ts +0 -0
  182. /package/src/{lib → commands}/graph/analyzer.ts +0 -0
  183. /package/src/{lib → commands}/graph/graph-template.html +0 -0
  184. /package/src/{lib → commands}/graph/parser.test.ts +0 -0
  185. /package/src/{lib → commands}/graph/parser.ts +0 -0
  186. /package/src/{lib → commands}/graph/render.ts +0 -0
  187. /package/src/{lib → commands}/graph/sessions.ts +0 -0
  188. /package/src/{lib → commands}/graph/tools.ts +0 -0
  189. /package/src/{lib → commands}/graph/treemap.ts +0 -0
  190. /package/src/{lib → commands}/journal/editor.ts +0 -0
@@ -0,0 +1,159 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { defineCommand } from "citty";
5
+ import consola from "consola";
6
+ import prompts from "prompts";
7
+
8
+ import { git, run } from "@towles/shared";
9
+ import { debugArg } from "../shared.js";
10
+ import { AutoClaudeConfigSchema } from "./config.js";
11
+ import {
12
+ CONFIG_DIR,
13
+ CONFIG_PATH,
14
+ buildConfig,
15
+ formatConfigSummary,
16
+ serializeConfig,
17
+ validateBranchName,
18
+ validateScopePath,
19
+ validateTriggerLabel,
20
+ } from "./config-init-helpers.js";
21
+
22
+ async function detectRepo(): Promise<string> {
23
+ const result = await run(
24
+ "gh",
25
+ ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
26
+ { throwOnError: true },
27
+ );
28
+ return result.stdout.trim();
29
+ }
30
+
31
+ async function detectMainBranch(): Promise<string> {
32
+ try {
33
+ const branch = await git(["symbolic-ref", "refs/remotes/origin/HEAD"]);
34
+ return branch.trim().replace("refs/remotes/origin/", "");
35
+ } catch {
36
+ return "main";
37
+ }
38
+ }
39
+
40
+ async function detectGitRoot(): Promise<string> {
41
+ return (await git(["rev-parse", "--show-toplevel"])).trim();
42
+ }
43
+
44
+ export default defineCommand({
45
+ meta: { name: "config-init", description: "Initialize auto-claude config for the current repo" },
46
+ args: {
47
+ debug: debugArg,
48
+ "non-interactive": {
49
+ type: "boolean" as const,
50
+ description: "Use all defaults without prompting",
51
+ default: false,
52
+ },
53
+ },
54
+ async run({ args }) {
55
+ const gitRoot = await detectGitRoot();
56
+ const configPath = join(gitRoot, CONFIG_PATH);
57
+
58
+ if (existsSync(configPath)) {
59
+ consola.warn(`Config already exists at ${CONFIG_PATH}`);
60
+ if (!args["non-interactive"]) {
61
+ const { overwrite } = await prompts({
62
+ type: "confirm",
63
+ name: "overwrite",
64
+ message: "Overwrite existing config?",
65
+ initial: false,
66
+ });
67
+ if (!overwrite) {
68
+ consola.info("Aborted.");
69
+ return;
70
+ }
71
+ }
72
+ }
73
+
74
+ const defaults = AutoClaudeConfigSchema.parse({
75
+ repo: await detectRepo(),
76
+ mainBranch: await detectMainBranch(),
77
+ });
78
+
79
+ if (args["non-interactive"]) {
80
+ const config = buildConfig({
81
+ triggerLabel: defaults.triggerLabel,
82
+ mainBranch: defaults.mainBranch,
83
+ scopePath: defaults.scopePath,
84
+ model: defaults.model,
85
+ repo: defaults.repo,
86
+ });
87
+
88
+ const dirPath = join(gitRoot, CONFIG_DIR);
89
+ mkdirSync(dirPath, { recursive: true });
90
+ writeFileSync(configPath, serializeConfig(config));
91
+ consola.success(`Config written to ${CONFIG_PATH}`);
92
+ return;
93
+ }
94
+
95
+ // Interactive prompts
96
+ const answers = await prompts([
97
+ {
98
+ type: "text",
99
+ name: "triggerLabel",
100
+ message: "Trigger label",
101
+ initial: defaults.triggerLabel,
102
+ validate: (v: string) => validateTriggerLabel(v),
103
+ },
104
+ {
105
+ type: "text",
106
+ name: "mainBranch",
107
+ message: "Main branch name",
108
+ initial: defaults.mainBranch,
109
+ validate: (v: string) => validateBranchName(v),
110
+ },
111
+ {
112
+ type: "text",
113
+ name: "scopePath",
114
+ message: "Scope path",
115
+ initial: defaults.scopePath,
116
+ validate: (v: string) => validateScopePath(v),
117
+ },
118
+ {
119
+ type: "text",
120
+ name: "model",
121
+ message: "Claude model",
122
+ initial: defaults.model,
123
+ },
124
+ ]);
125
+
126
+ // User cancelled (Ctrl-C during prompts)
127
+ if (!answers.triggerLabel) {
128
+ consola.info("Aborted.");
129
+ return;
130
+ }
131
+
132
+ const config = buildConfig({
133
+ triggerLabel: answers.triggerLabel,
134
+ mainBranch: answers.mainBranch,
135
+ scopePath: answers.scopePath,
136
+ model: answers.model,
137
+ repo: defaults.repo,
138
+ });
139
+
140
+ consola.box(`Config summary:\n\n${formatConfigSummary(config)}`);
141
+
142
+ const { confirmed } = await prompts({
143
+ type: "confirm",
144
+ name: "confirmed",
145
+ message: `Write config to ${CONFIG_PATH}?`,
146
+ initial: true,
147
+ });
148
+
149
+ if (!confirmed) {
150
+ consola.info("Aborted.");
151
+ return;
152
+ }
153
+
154
+ const dirPath = join(gitRoot, CONFIG_DIR);
155
+ mkdirSync(dirPath, { recursive: true });
156
+ writeFileSync(configPath, serializeConfig(config));
157
+ consola.success(`Config written to ${CONFIG_PATH}`);
158
+ },
159
+ });
@@ -1,5 +1,5 @@
1
1
  import consola from "consola";
2
- import { x } from "tinyexec";
2
+ import { run } from "@towles/shared";
3
3
  import { z } from "zod/v4";
4
4
 
5
5
  export const AutoClaudeConfigSchema = z.object({
@@ -25,13 +25,10 @@ export async function initConfig(
25
25
  // Auto-detect repo
26
26
  let repo = overrides.repo;
27
27
  if (!repo) {
28
- const result = await x(
28
+ const result = await run(
29
29
  "gh",
30
30
  ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
31
- {
32
- nodeOptions: { cwd: process.cwd() },
33
- throwOnError: true,
34
- },
31
+ { throwOnError: true },
35
32
  );
36
33
  repo = result.stdout.trim();
37
34
  }
@@ -41,8 +38,7 @@ export async function initConfig(
41
38
  let mainBranch = overrides.mainBranch;
42
39
  if (!mainBranch) {
43
40
  try {
44
- const result = await x("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
45
- nodeOptions: { cwd: process.cwd() },
41
+ const result = await run("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
46
42
  throwOnError: true,
47
43
  });
48
44
  mainBranch = result.stdout.trim().replace("refs/remotes/origin/", "");
@@ -78,7 +78,7 @@ describe("auto-claude e2e: full pipeline lifecycle", () => {
78
78
  return { stdout: "", ok: true };
79
79
  }
80
80
  // Pass through non-gh commands to real exec
81
- const { execSafe } = await import("../../utils/git/exec");
81
+ const { execSafe } = await import("@towles/shared");
82
82
  return execSafe(cmd, args);
83
83
  }) as ExecSafeFn;
84
84
  });
@@ -221,7 +221,7 @@ describe("auto-claude e2e: label state transitions", () => {
221
221
  }
222
222
  return { stdout: "", ok: true };
223
223
  }
224
- const { execSafe } = await import("../../utils/git/exec");
224
+ const { execSafe } = await import("@towles/shared");
225
225
  return execSafe(cmd, args);
226
226
  }) as ExecSafeFn;
227
227
  });
@@ -369,7 +369,7 @@ describe("auto-claude e2e: retry loop behavior", () => {
369
369
  }
370
370
  return { stdout: "", ok: true };
371
371
  }
372
- const { execSafe } = await import("../../utils/git/exec");
372
+ const { execSafe } = await import("@towles/shared");
373
373
  return execSafe(cmd, args);
374
374
  }) as ExecSafeFn;
375
375
  });
@@ -511,7 +511,7 @@ describe("auto-claude e2e: --until flag behavior", () => {
511
511
  if (cmd === "gh") {
512
512
  return { stdout: "[]", ok: true };
513
513
  }
514
- const { execSafe } = await import("../../utils/git/exec");
514
+ const { execSafe } = await import("@towles/shared");
515
515
  return execSafe(cmd, args);
516
516
  }) as ExecSafeFn;
517
517
  });
@@ -661,7 +661,7 @@ describe("auto-claude e2e: git operations", () => {
661
661
  }
662
662
  return { stdout: "", ok: true };
663
663
  }
664
- const { execSafe } = await import("../../utils/git/exec");
664
+ const { execSafe } = await import("@towles/shared");
665
665
  return execSafe(cmd, args);
666
666
  }) as ExecSafeFn;
667
667
  });
@@ -791,7 +791,7 @@ describe("auto-claude e2e: edge cases", () => {
791
791
  if (cmd === "gh") {
792
792
  return { stdout: "[]", ok: true };
793
793
  }
794
- const { execSafe } = await import("../../utils/git/exec");
794
+ const { execSafe } = await import("@towles/shared");
795
795
  return execSafe(cmd, args);
796
796
  }) as ExecSafeFn;
797
797
  });
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { ARTIFACTS, PIPELINE_STEPS, STEP_NAMES } from "./prompt-templates/index";
4
+ import { buildStepInfos, printStepTemplate } from "./explain";
5
+
6
+ describe("buildStepInfos", () => {
7
+ it("returns one entry per pipeline step", () => {
8
+ const infos = buildStepInfos();
9
+ expect(infos).toHaveLength(PIPELINE_STEPS.length);
10
+ });
11
+
12
+ it("preserves pipeline step order", () => {
13
+ const infos = buildStepInfos();
14
+ expect(infos.map((i) => i.name)).toEqual(STEP_NAMES);
15
+ });
16
+
17
+ it("every step has a template file, description, inputs, and outputs", () => {
18
+ for (const info of buildStepInfos()) {
19
+ expect(info.templateFile).toBeTruthy();
20
+ expect(info.description.length).toBeGreaterThan(0);
21
+ expect(info.inputs.length).toBeGreaterThan(0);
22
+ expect(info.outputs.length).toBeGreaterThan(0);
23
+ }
24
+ });
25
+
26
+ it("plan step reads initial-ramblings and outputs plan", () => {
27
+ const plan = buildStepInfos().find((i) => i.name === "plan")!;
28
+ expect(plan.inputs).toContain(ARTIFACTS.initialRamblings);
29
+ expect(plan.outputs).toContain(ARTIFACTS.plan);
30
+ });
31
+
32
+ it("implement step reads plan and outputs completed-summary", () => {
33
+ const impl = buildStepInfos().find((i) => i.name === "implement")!;
34
+ expect(impl.inputs).toContain(ARTIFACTS.plan);
35
+ expect(impl.outputs).toContain(ARTIFACTS.completedSummary);
36
+ });
37
+
38
+ it("review step outputs review artifact", () => {
39
+ const review = buildStepInfos().find((i) => i.name === "review")!;
40
+ expect(review.outputs).toContain(ARTIFACTS.review);
41
+ });
42
+ });
43
+
44
+ describe("printStepTemplate", () => {
45
+ it("throws for an unknown step name", () => {
46
+ expect(() => printStepTemplate("nonexistent")).toThrow(/Unknown step "nonexistent"/);
47
+ });
48
+
49
+ it("throws with valid step names in the error message", () => {
50
+ expect(() => printStepTemplate("bad")).toThrow(/plan, implement, simplify, review/);
51
+ });
52
+
53
+ it("does not throw for valid step names", () => {
54
+ for (const name of STEP_NAMES) {
55
+ expect(() => printStepTemplate(name)).not.toThrow();
56
+ }
57
+ });
58
+ });
@@ -0,0 +1,97 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import consola from "consola";
5
+ import { colorize } from "consola/utils";
6
+
7
+ import { ARTIFACTS, PIPELINE_STEPS, TEMPLATES } from "./prompt-templates/index.js";
8
+ import type { StepName } from "./prompt-templates/index.js";
9
+ import { TEMPLATES_DIR } from "./templates.js";
10
+
11
+ interface StepInfo {
12
+ order: number;
13
+ name: string;
14
+ label: string;
15
+ templateFile: string;
16
+ description: string;
17
+ inputs: string[];
18
+ outputs: string[];
19
+ }
20
+
21
+ const STEP_DETAILS: Record<StepName, { description: string; inputs: string[]; outputs: string[] }> =
22
+ {
23
+ plan: {
24
+ description: "Research the issue and codebase, then produce a detailed implementation plan",
25
+ inputs: [ARTIFACTS.initialRamblings],
26
+ outputs: [ARTIFACTS.plan],
27
+ },
28
+ implement: {
29
+ description:
30
+ "Follow the plan checklist task-by-task using red/green TDD, then verify all checks pass",
31
+ inputs: [ARTIFACTS.plan, `${ARTIFACTS.review} (on retry)`],
32
+ outputs: [ARTIFACTS.completedSummary],
33
+ },
34
+ simplify: {
35
+ description:
36
+ "Review branch changes and simplify: remove dead code, inline over-abstractions, tighten types",
37
+ inputs: [ARTIFACTS.completedSummary],
38
+ outputs: [ARTIFACTS.simplifySummary],
39
+ },
40
+ review: {
41
+ description:
42
+ "Run automated checks then manually review the diff for correctness, security, and coverage",
43
+ inputs: [ARTIFACTS.plan, "git diff"],
44
+ outputs: [ARTIFACTS.review],
45
+ },
46
+ };
47
+
48
+ export function buildStepInfos(): StepInfo[] {
49
+ const templateMap: Record<string, string> = TEMPLATES;
50
+ return PIPELINE_STEPS.map((s) => ({
51
+ order: s.order,
52
+ name: s.name,
53
+ label: s.label,
54
+ templateFile: templateMap[s.name] ?? "(none)",
55
+ ...STEP_DETAILS[s.name],
56
+ }));
57
+ }
58
+
59
+ export function printExplain(): void {
60
+ consola.log("");
61
+ consola.log(colorize("bold", "auto-claude pipeline"));
62
+ consola.log(
63
+ colorize("dim", "Steps 2-4 loop up to maxReviewRetries times if review returns FAIL\n"),
64
+ );
65
+
66
+ for (const step of buildStepInfos()) {
67
+ consola.log(colorize("cyan", ` ${step.label}`));
68
+ consola.log(` ${step.description}`);
69
+ consola.log(` ${colorize("dim", "template:")} ${step.templateFile}`);
70
+ consola.log(` ${colorize("green", "inputs:")} ${step.inputs.join(", ")}`);
71
+ consola.log(` ${colorize("yellow", "outputs:")} ${step.outputs.join(", ")}`);
72
+ consola.log("");
73
+ }
74
+ }
75
+
76
+ export function printStepTemplate(stepName: string): void {
77
+ const step = PIPELINE_STEPS.find((s) => s.name === stepName);
78
+ if (!step) {
79
+ throw new Error(
80
+ `Unknown step "${stepName}". Valid steps: ${PIPELINE_STEPS.map((s) => s.name).join(", ")}`,
81
+ );
82
+ }
83
+
84
+ const templateMap: Record<string, string> = TEMPLATES;
85
+ const templateFile = templateMap[step.name];
86
+ if (!templateFile) {
87
+ throw new Error(`No template found for step "${stepName}"`);
88
+ }
89
+
90
+ const templatePath = join(TEMPLATES_DIR, templateFile);
91
+ const content = readFileSync(templatePath, "utf-8");
92
+
93
+ consola.log("");
94
+ consola.log(colorize("bold", `Template: ${templateFile}`));
95
+ consola.log(colorize("dim", `Step: ${step.label} (${step.name})\n`));
96
+ consola.log(content);
97
+ }
@@ -6,20 +6,16 @@ import { defineCommand } from "citty";
6
6
  import consola from "consola";
7
7
 
8
8
  import { debugArg } from "../shared.js";
9
- import {
10
- STEP_NAMES,
11
- fetchIssue,
12
- fetchIssues,
13
- getConfig,
14
- git,
15
- initConfig,
16
- log,
17
- logBanner,
18
- runClaude,
19
- runPipeline,
20
- sleep,
21
- } from "../../lib/auto-claude/index.js";
22
- import type { IssueContext, StepName } from "../../lib/auto-claude/index.js";
9
+ import { printExplain, printStepTemplate } from "./explain.js";
10
+ import { STEP_NAMES, runPipeline } from "./pipeline.js";
11
+ import { fetchIssue, fetchIssues } from "./steps/fetch-issues.js";
12
+ import { getConfig, initConfig } from "./config.js";
13
+ import { git } from "@towles/shared";
14
+ import { runClaude } from "./claude-cli.js";
15
+ import { sleep } from "./shell.js";
16
+ import { log, logBanner } from "./utils.js";
17
+ import type { IssueContext } from "./utils.js";
18
+ import type { StepName } from "./prompt-templates/index.js";
23
19
 
24
20
  export default defineCommand({
25
21
  meta: { name: "auto-claude", description: "Automated issue-to-PR pipeline using Claude Code" },
@@ -80,13 +76,40 @@ export default defineCommand({
80
76
  type: "string" as const,
81
77
  description: "Path within repo to scope work (default: .)",
82
78
  },
79
+ explain: {
80
+ type: "boolean" as const,
81
+ description: "Print a summary of all pipeline steps and exit",
82
+ default: false,
83
+ },
84
+ "step-template": {
85
+ type: "string" as const,
86
+ description: `Print the raw prompt template for a step and exit (${STEP_NAMES.join(", ")})`,
87
+ },
83
88
  },
84
89
  subCommands: {
85
90
  list: () => import("./list.js").then((m) => m.default),
86
91
  status: () => import("./status.js").then((m) => m.default),
87
92
  retry: () => import("./retry.js").then((m) => m.default),
93
+ "config-init": () => import("./config-init.js").then((m) => m.default),
88
94
  },
89
95
  async run({ args }) {
96
+ // Explain mode: print pipeline summary and exit
97
+ if (args.explain) {
98
+ printExplain();
99
+ return;
100
+ }
101
+
102
+ // Step template mode: print raw template and exit
103
+ if (args["step-template"]) {
104
+ try {
105
+ printStepTemplate(args["step-template"] as string);
106
+ } catch (e) {
107
+ consola.error(e instanceof Error ? e.message : String(e));
108
+ process.exit(1);
109
+ }
110
+ return;
111
+ }
112
+
90
113
  // Prompt mode: run a single prompt with structured output, skip issue pipeline
91
114
  if (args.prompt) {
92
115
  await initConfig({ model: args.model });
@@ -1,4 +1,4 @@
1
- import { execSafe as defaultExecSafe } from "../../utils/git/exec.js";
1
+ import { execSafe as defaultExecSafe } from "@towles/shared";
2
2
 
3
3
  // ── Label helpers ──
4
4
 
@@ -7,10 +7,11 @@ import type { Choice } from "prompts";
7
7
 
8
8
  import { debugArg } from "../shared.js";
9
9
  import { buildIssueChoices, computeColumnLayout } from "../gh/branch.js";
10
- import { STEP_NAMES, fetchIssue, initConfig, runPipeline } from "../../lib/auto-claude/index.js";
11
- import type { StepName } from "../../lib/auto-claude/index.js";
12
- import { getIssues, isGithubCliInstalled } from "../../utils/git/gh-cli-wrapper.js";
13
- import { getTerminalColumns } from "../../utils/render.js";
10
+ import { STEP_NAMES, runPipeline } from "./pipeline.js";
11
+ import { fetchIssue } from "./steps/fetch-issues.js";
12
+ import { initConfig } from "./config.js";
13
+ import type { StepName } from "./prompt-templates/index.js";
14
+ import { getIssues, getTerminalColumns, isGithubCliInstalled } from "@towles/shared";
14
15
 
15
16
  export default defineCommand({
16
17
  meta: { name: "list", description: "Interactively pick an auto-claude issue to process" },
@@ -57,7 +57,7 @@ describe("runPipeline", () => {
57
57
  return { stdout: "[]", ok: true };
58
58
  }
59
59
  // Pass through non-gh commands to real exec
60
- const { execSafe } = await import("../../utils/git/exec");
60
+ const { execSafe } = await import("@towles/shared");
61
61
  return execSafe(cmd, args);
62
62
  }) as ExecSafeFn;
63
63
  });
@@ -9,9 +9,7 @@ import { stepImplement } from "./steps/implement.js";
9
9
  import { stepPlan, stepReview, stepSimplify } from "./steps/simple-steps.js";
10
10
  import { LABELS, ensureLabelsExist, removeLabel, setLabel } from "./labels.js";
11
11
  import type { ExecSafeFn } from "./labels.js";
12
- import { ensureDir, fileExists, readFile, writeFile } from "../../utils/fs.js";
13
- import { execSafe, git } from "../../utils/git/exec.js";
14
- import { ghRaw } from "../../utils/git/gh-cli-wrapper.js";
12
+ import { ensureDir, execSafe, fileExists, ghRaw, git, readFile, writeFile } from "@towles/shared";
15
13
  import { log } from "./utils.js";
16
14
  import type { IssueContext } from "./utils.js";
17
15
  import type { SpawnClaudeFn } from "./spawn-claude.js";
@@ -7,8 +7,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
7
7
 
8
8
  import type { Mock } from "vitest";
9
9
 
10
- import type { ExecSafeFn } from "../../lib/auto-claude/labels.js";
11
- import { LABELS } from "../../lib/auto-claude/labels.js";
10
+ import type { ExecSafeFn } from "./labels.js";
11
+ import { LABELS } from "./labels.js";
12
12
  import { retryIssues } from "./retry.js";
13
13
 
14
14
  // Suppress consola output during tests
@@ -7,11 +7,11 @@ import { colors } from "consola/utils";
7
7
  import prompts from "prompts";
8
8
 
9
9
  import { debugArg } from "../shared.js";
10
- import { initConfig } from "../../lib/auto-claude/index.js";
11
- import { LABELS, removeLabel, setLabel } from "../../lib/auto-claude/labels.js";
12
- import type { ExecSafeFn } from "../../lib/auto-claude/labels.js";
13
- import { getIssues, isGithubCliInstalled } from "../../utils/git/gh-cli-wrapper.js";
14
- import type { Issue } from "../../utils/git/gh-cli-wrapper.js";
10
+ import { initConfig } from "./config.js";
11
+ import { LABELS, removeLabel, setLabel } from "./labels.js";
12
+ import type { ExecSafeFn } from "./labels.js";
13
+ import { getIssues, isGithubCliInstalled } from "@towles/shared";
14
+ import type { Issue } from "@towles/shared";
15
15
 
16
16
  /**
17
17
  * Core retry logic: swap labels on failed issues to re-trigger the pipeline.
@@ -0,0 +1,3 @@
1
+ export function sleep(ms: number): Promise<void> {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ }
@@ -4,8 +4,8 @@ import { join } from "node:path";
4
4
 
5
5
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
6
6
 
7
- import type { Issue } from "../../utils/git/gh-cli-wrapper.js";
8
- import { LABELS } from "../../lib/auto-claude/labels.js";
7
+ import type { Issue } from "@towles/shared";
8
+ import { LABELS } from "./labels.js";
9
9
  import { checkArtifacts, findAcLabel, formatIssueStatus } from "./status.js";
10
10
 
11
11
  // ── Test fixtures ──
@@ -5,10 +5,10 @@ import { defineCommand } from "citty";
5
5
  import consola from "consola";
6
6
  import { colors } from "consola/utils";
7
7
 
8
- import type { Issue } from "../../utils/git/gh-cli-wrapper.js";
9
- import { getIssues, isGithubCliInstalled } from "../../utils/git/gh-cli-wrapper.js";
10
- import { ARTIFACTS } from "../../lib/auto-claude/prompt-templates/index.js";
11
- import { LABELS } from "../../lib/auto-claude/labels.js";
8
+ import type { Issue } from "@towles/shared";
9
+ import { getIssues, isGithubCliInstalled } from "@towles/shared";
10
+ import { ARTIFACTS } from "./prompt-templates/index.js";
11
+ import { LABELS } from "./labels.js";
12
12
  import { debugArg } from "../shared.js";
13
13
 
14
14
  /** All labels that indicate an issue is part of the auto-claude pipeline. */
@@ -2,9 +2,7 @@ import { join } from "node:path";
2
2
 
3
3
  import consola from "consola";
4
4
 
5
- import { fileExists, readFile, writeFile } from "../../../utils/fs.js";
6
- import { execSafe, git } from "../../../utils/git/exec.js";
7
- import { ghRaw } from "../../../utils/git/gh-cli-wrapper.js";
5
+ import { execSafe, fileExists, ghRaw, git, readFile, writeFile } from "@towles/shared";
8
6
  import { getConfig } from "../config.js";
9
7
  import { ARTIFACTS } from "../prompt-templates/index.js";
10
8
  import { log } from "../utils.js";
@@ -1,6 +1,6 @@
1
1
  import consola from "consola";
2
2
  import { getConfig } from "../config.js";
3
- import { gh } from "../../../utils/git/gh-cli-wrapper.js";
3
+ import { gh } from "@towles/shared";
4
4
  import { buildIssueContext, log } from "../utils.js";
5
5
  import type { IssueContext } from "../utils.js";
6
6
 
@@ -4,8 +4,7 @@ import consola from "consola";
4
4
 
5
5
  import { getConfig } from "../config.js";
6
6
  import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
7
- import { fileExists, readFile } from "../../../utils/fs.js";
8
- import { git } from "../../../utils/git/exec.js";
7
+ import { fileExists, git, readFile } from "@towles/shared";
9
8
  import { runClaude } from "../claude-cli.js";
10
9
  import { resolveTemplate } from "../templates.js";
11
10
  import { buildTokens, log, logStep } from "../utils.js";
@@ -28,20 +28,20 @@ describe("shell helpers (real execution)", () => {
28
28
  });
29
29
 
30
30
  it("execSafe returns ok:true for a successful command", async () => {
31
- const { execSafe } = await import("./shell");
31
+ const { execSafe } = await import("@towles/shared");
32
32
  const result = await execSafe("echo", ["hello"]);
33
33
  expect(result.ok).toBe(true);
34
34
  expect(result.stdout).toBe("hello");
35
35
  });
36
36
 
37
37
  it("execSafe returns ok:false for a failing command", async () => {
38
- const { execSafe } = await import("./shell");
38
+ const { execSafe } = await import("@towles/shared");
39
39
  const result = await execSafe("git", ["checkout", "nonexistent-branch-xyz"]);
40
40
  expect(result.ok).toBe(false);
41
41
  });
42
42
 
43
43
  it("git() runs real git commands", async () => {
44
- const { git } = await import("./shell");
44
+ const { git } = await import("@towles/shared");
45
45
  const status = await git(["status", "--porcelain"]);
46
46
  expect(typeof status).toBe("string");
47
47
  });
@@ -67,7 +67,7 @@ describe("ensureBranch (real git)", () => {
67
67
 
68
68
  it("creates a new branch from main when branch doesn't exist", async () => {
69
69
  const { ensureBranch } = await import("./utils");
70
- const { git } = await import("./shell");
70
+ const { git } = await import("@towles/shared");
71
71
 
72
72
  await ensureBranch("feature/42-new-branch");
73
73
 
@@ -77,7 +77,7 @@ describe("ensureBranch (real git)", () => {
77
77
 
78
78
  it("checks out an existing local branch", async () => {
79
79
  const { ensureBranch } = await import("./utils");
80
- const { git } = await import("./shell");
80
+ const { git } = await import("@towles/shared");
81
81
 
82
82
  execSync("git checkout -b feature/existing-branch", { cwd: repo.dir, stdio: "ignore" });
83
83
  execSync("git checkout main", { cwd: repo.dir, stdio: "ignore" });
@@ -90,7 +90,7 @@ describe("ensureBranch (real git)", () => {
90
90
 
91
91
  it("can checkout after branch creation", async () => {
92
92
  const { ensureBranch } = await import("./utils");
93
- const { git } = await import("./shell");
93
+ const { git } = await import("@towles/shared");
94
94
 
95
95
  await ensureBranch("feature/99-test-checkout");
96
96
  const branch1 = await git(["branch", "--show-current"]);