@laitszkin/apollo-toolkit 4.1.4 → 5.0.1

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 (205) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/bin/apollo-toolkit.ts +4 -0
  3. package/dist/bin/apollo-toolkit.js +4 -0
  4. package/package.json +3 -2
  5. package/packages/cli/dist/help-text-builder.d.ts +23 -0
  6. package/packages/cli/dist/help-text-builder.js +166 -0
  7. package/packages/cli/dist/index.d.ts +6 -17
  8. package/packages/cli/dist/index.js +52 -246
  9. package/packages/cli/dist/installer.d.ts +1 -0
  10. package/packages/cli/dist/installer.js +20 -7
  11. package/packages/cli/dist/parsers/install-parser.d.ts +15 -0
  12. package/packages/cli/dist/parsers/install-parser.js +87 -0
  13. package/packages/cli/dist/parsers/parser-utils.d.ts +9 -0
  14. package/packages/cli/dist/parsers/parser-utils.js +16 -0
  15. package/packages/cli/dist/parsers/tool-parser.d.ts +16 -0
  16. package/packages/cli/dist/parsers/tool-parser.js +58 -0
  17. package/packages/cli/dist/parsers/types.d.ts +50 -0
  18. package/packages/cli/dist/parsers/types.js +1 -0
  19. package/packages/cli/dist/parsers/uninstall-parser.d.ts +15 -0
  20. package/packages/cli/dist/parsers/uninstall-parser.js +67 -0
  21. package/packages/cli/dist/tool-registration.d.ts +2 -0
  22. package/packages/cli/dist/tool-registration.js +2 -0
  23. package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
  24. package/packages/cli/dist/types.d.ts +3 -1
  25. package/packages/cli/dist/updater.js +11 -5
  26. package/packages/cli/help-text-builder.ts +180 -0
  27. package/packages/cli/index.ts +59 -251
  28. package/packages/cli/installer.ts +19 -7
  29. package/packages/cli/package.json +14 -4
  30. package/packages/cli/parsers/install-parser.ts +94 -0
  31. package/packages/cli/parsers/parser-utils.ts +17 -0
  32. package/packages/cli/parsers/tool-parser.ts +65 -0
  33. package/packages/cli/parsers/types.ts +56 -0
  34. package/packages/cli/parsers/uninstall-parser.ts +75 -0
  35. package/packages/cli/tool-registration.ts +3 -0
  36. package/packages/cli/types.ts +6 -1
  37. package/packages/cli/updater.ts +11 -5
  38. package/packages/tool-registry/dist/registry.js +3 -4
  39. package/packages/tool-registry/dist/tsconfig.tsbuildinfo +1 -1
  40. package/packages/tool-registry/dist/types.d.ts +2 -9
  41. package/packages/tool-registry/package.json +11 -4
  42. package/packages/tool-registry/registry.ts +3 -4
  43. package/packages/tool-registry/tsconfig.json +6 -2
  44. package/packages/tool-registry/types.ts +3 -9
  45. package/packages/tool-utils/app-error.ts +97 -0
  46. package/packages/tool-utils/dist/app-error.d.ts +49 -0
  47. package/packages/tool-utils/dist/app-error.js +80 -0
  48. package/packages/tool-utils/dist/index.d.ts +5 -0
  49. package/packages/tool-utils/dist/index.js +3 -0
  50. package/packages/tool-utils/dist/platform-adapter.d.ts +48 -0
  51. package/packages/tool-utils/dist/platform-adapter.js +73 -0
  52. package/packages/tool-utils/dist/schema.d.ts +68 -0
  53. package/packages/tool-utils/dist/schema.js +67 -0
  54. package/packages/tool-utils/dist/tsconfig.tsbuildinfo +1 -1
  55. package/packages/tool-utils/index.ts +12 -0
  56. package/packages/tool-utils/package.json +11 -4
  57. package/packages/tool-utils/platform-adapter.ts +112 -0
  58. package/packages/tool-utils/schema.ts +122 -0
  59. package/packages/tools/architecture/dist/index.d.ts +13 -0
  60. package/packages/tools/architecture/dist/index.js +55 -57
  61. package/packages/tools/architecture/dist/index.test.js +17 -4
  62. package/packages/tools/architecture/dist/tsconfig.tsbuildinfo +1 -1
  63. package/packages/tools/architecture/index.test.ts +27 -14
  64. package/packages/tools/architecture/index.ts +85 -88
  65. package/packages/tools/architecture/package.json +11 -4
  66. package/packages/tools/codegraph/dist/index.js +12 -22
  67. package/packages/tools/codegraph/dist/tsconfig.tsbuildinfo +1 -1
  68. package/packages/tools/codegraph/index.ts +13 -22
  69. package/packages/tools/codegraph/package.json +11 -4
  70. package/packages/tools/create-review-report/dist/index.d.ts +1 -2
  71. package/packages/tools/create-review-report/dist/index.js +46 -77
  72. package/packages/tools/create-review-report/dist/tsconfig.tsbuildinfo +1 -1
  73. package/packages/tools/create-review-report/index.ts +52 -81
  74. package/packages/tools/create-review-report/package.json +11 -4
  75. package/packages/tools/create-specs/dist/index.d.ts +1 -2
  76. package/packages/tools/create-specs/dist/index.js +70 -123
  77. package/packages/tools/create-specs/dist/tsconfig.tsbuildinfo +1 -1
  78. package/packages/tools/create-specs/index.ts +82 -128
  79. package/packages/tools/create-specs/package.json +11 -4
  80. package/packages/tools/docs-to-voice/dist/index.d.ts +1 -2
  81. package/packages/tools/docs-to-voice/dist/index.js +116 -219
  82. package/packages/tools/docs-to-voice/dist/tsconfig.tsbuildinfo +1 -1
  83. package/packages/tools/docs-to-voice/index.ts +265 -385
  84. package/packages/tools/docs-to-voice/package.json +11 -4
  85. package/packages/tools/enforce-video-aspect-ratio/dist/index.d.ts +1 -2
  86. package/packages/tools/enforce-video-aspect-ratio/dist/index.js +77 -154
  87. package/packages/tools/enforce-video-aspect-ratio/dist/tsconfig.tsbuildinfo +1 -1
  88. package/packages/tools/enforce-video-aspect-ratio/index.ts +87 -172
  89. package/packages/tools/enforce-video-aspect-ratio/package.json +11 -4
  90. package/packages/tools/eval/dist/index.js +7 -0
  91. package/packages/tools/eval/dist/tsconfig.tsbuildinfo +1 -1
  92. package/packages/tools/eval/index.ts +8 -0
  93. package/packages/tools/eval/package.json +11 -4
  94. package/packages/tools/extract-conversations/dist/index.d.ts +1 -2
  95. package/packages/tools/extract-conversations/dist/index.js +31 -29
  96. package/packages/tools/extract-conversations/dist/tsconfig.tsbuildinfo +1 -1
  97. package/packages/tools/extract-conversations/index.ts +37 -30
  98. package/packages/tools/extract-conversations/package.json +11 -4
  99. package/packages/tools/extract-pdf-text/dist/index.d.ts +1 -2
  100. package/packages/tools/extract-pdf-text/dist/index.js +44 -65
  101. package/packages/tools/extract-pdf-text/dist/tsconfig.tsbuildinfo +1 -1
  102. package/packages/tools/extract-pdf-text/index.ts +55 -74
  103. package/packages/tools/extract-pdf-text/package.json +11 -4
  104. package/packages/tools/filter-logs/dist/index.js +60 -84
  105. package/packages/tools/filter-logs/dist/tsconfig.tsbuildinfo +1 -1
  106. package/packages/tools/filter-logs/index.ts +67 -97
  107. package/packages/tools/filter-logs/package.json +11 -4
  108. package/packages/tools/find-github-issues/dist/index.d.ts +10 -0
  109. package/packages/tools/find-github-issues/dist/index.js +34 -5
  110. package/packages/tools/find-github-issues/dist/tsconfig.tsbuildinfo +1 -1
  111. package/packages/tools/find-github-issues/index.ts +37 -5
  112. package/packages/tools/find-github-issues/package.json +11 -4
  113. package/packages/tools/generate-storyboard-images/dist/index.d.ts +1 -2
  114. package/packages/tools/generate-storyboard-images/dist/index.js +98 -173
  115. package/packages/tools/generate-storyboard-images/dist/tsconfig.tsbuildinfo +1 -1
  116. package/packages/tools/generate-storyboard-images/index.ts +100 -188
  117. package/packages/tools/generate-storyboard-images/package.json +11 -4
  118. package/packages/tools/open-github-issue/dist/index.d.ts +13 -0
  119. package/packages/tools/open-github-issue/dist/index.js +67 -68
  120. package/packages/tools/open-github-issue/dist/tsconfig.tsbuildinfo +1 -1
  121. package/packages/tools/open-github-issue/index.ts +71 -72
  122. package/packages/tools/open-github-issue/package.json +11 -4
  123. package/packages/tools/read-github-issue/dist/index.d.ts +16 -1
  124. package/packages/tools/read-github-issue/dist/index.js +32 -40
  125. package/packages/tools/read-github-issue/dist/tsconfig.tsbuildinfo +1 -1
  126. package/packages/tools/read-github-issue/index.ts +32 -45
  127. package/packages/tools/read-github-issue/package.json +11 -4
  128. package/packages/tools/render-error-book/dist/index.d.ts +1 -2
  129. package/packages/tools/render-error-book/dist/index.js +74 -95
  130. package/packages/tools/render-error-book/dist/tsconfig.tsbuildinfo +1 -1
  131. package/packages/tools/render-error-book/index.ts +88 -103
  132. package/packages/tools/render-error-book/package.json +11 -4
  133. package/packages/tools/render-katex/dist/index.d.ts +1 -2
  134. package/packages/tools/render-katex/dist/index.js +70 -157
  135. package/packages/tools/render-katex/dist/tsconfig.tsbuildinfo +1 -1
  136. package/packages/tools/render-katex/index.ts +138 -222
  137. package/packages/tools/render-katex/package.json +11 -4
  138. package/packages/tools/review-threads/dist/index.d.ts +12 -0
  139. package/packages/tools/review-threads/dist/index.js +83 -86
  140. package/packages/tools/review-threads/dist/tsconfig.tsbuildinfo +1 -1
  141. package/packages/tools/review-threads/index.ts +90 -84
  142. package/packages/tools/review-threads/package.json +11 -4
  143. package/packages/tools/search-logs/dist/index.js +100 -136
  144. package/packages/tools/search-logs/dist/tsconfig.tsbuildinfo +1 -1
  145. package/packages/tools/search-logs/index.ts +113 -145
  146. package/packages/tools/search-logs/package.json +11 -4
  147. package/packages/tools/sync-memory-index/dist/index.js +34 -28
  148. package/packages/tools/sync-memory-index/dist/tsconfig.tsbuildinfo +1 -1
  149. package/packages/tools/sync-memory-index/index.ts +37 -28
  150. package/packages/tools/sync-memory-index/package.json +11 -4
  151. package/packages/tools/validate-openai-agent-config/dist/index.js +13 -7
  152. package/packages/tools/validate-openai-agent-config/dist/tsconfig.tsbuildinfo +1 -1
  153. package/packages/tools/validate-openai-agent-config/index.ts +13 -7
  154. package/packages/tools/validate-openai-agent-config/package.json +11 -4
  155. package/packages/tools/validate-skill-frontmatter/dist/index.js +12 -6
  156. package/packages/tools/validate-skill-frontmatter/dist/tsconfig.tsbuildinfo +1 -1
  157. package/packages/tools/validate-skill-frontmatter/index.ts +12 -6
  158. package/packages/tools/validate-skill-frontmatter/package.json +11 -4
  159. package/packages/tui/dist/index.d.ts +2 -1
  160. package/packages/tui/dist/index.js +1 -0
  161. package/packages/tui/dist/stdio-adapter.d.ts +36 -0
  162. package/packages/tui/dist/stdio-adapter.js +69 -0
  163. package/packages/tui/dist/terminal.js +3 -1
  164. package/packages/tui/dist/tsconfig.tsbuildinfo +1 -1
  165. package/packages/tui/dist/types.d.ts +17 -0
  166. package/packages/tui/index.ts +2 -1
  167. package/packages/tui/package.json +14 -6
  168. package/packages/tui/stdio-adapter.ts +85 -0
  169. package/packages/tui/terminal.ts +3 -1
  170. package/packages/tui/tsconfig.json +5 -2
  171. package/packages/tui/types.ts +19 -0
  172. package/resources/project-architecture/assets/architecture.css +2 -1
  173. package/resources/project-architecture/atlas/atlas.history.log +1 -0
  174. package/resources/project-architecture/atlas/atlas.history.undo.json +13 -2
  175. package/resources/project-architecture/atlas/atlas.history.undo.stack.json +610 -0
  176. package/resources/project-architecture/atlas/atlas.index.yaml +81 -5
  177. package/resources/project-architecture/atlas/features/cli-dispatch.yaml +43 -0
  178. package/resources/project-architecture/atlas/features/terminal-ui.yaml +29 -0
  179. package/resources/project-architecture/atlas/features/tool-registry.yaml +22 -0
  180. package/resources/project-architecture/atlas/features/tool-utils.yaml +22 -0
  181. package/resources/project-architecture/features/cli-dispatch/arg-parser.html +40 -0
  182. package/resources/project-architecture/features/cli-dispatch/help-builder.html +40 -0
  183. package/resources/project-architecture/features/cli-dispatch/index.html +64 -0
  184. package/resources/project-architecture/features/cli-dispatch/installer-core.html +40 -0
  185. package/resources/project-architecture/features/cli-dispatch/tool-discovery.html +40 -0
  186. package/resources/project-architecture/features/cli-dispatch/update-checker.html +40 -0
  187. package/resources/project-architecture/features/terminal-ui/banner-display.html +40 -0
  188. package/resources/project-architecture/features/terminal-ui/index.html +50 -0
  189. package/resources/project-architecture/features/terminal-ui/interactive-prompts.html +40 -0
  190. package/resources/project-architecture/features/terminal-ui/terminal-detection.html +40 -0
  191. package/resources/project-architecture/features/tool-registry/formatter.html +40 -0
  192. package/resources/project-architecture/features/tool-registry/index.html +43 -0
  193. package/resources/project-architecture/features/tool-registry/registry-core.html +40 -0
  194. package/resources/project-architecture/features/tool-utils/index.html +43 -0
  195. package/resources/project-architecture/features/tool-utils/log-utils.html +40 -0
  196. package/resources/project-architecture/features/tool-utils/skill-discovery.html +40 -0
  197. package/resources/project-architecture/index.html +365 -121
  198. package/scripts/rewrite-imports.mjs +2 -2
  199. package/scripts/test.sh +144 -8
  200. package/skills/design/SKILL.md +57 -64
  201. package/skills/design/assets/templates/DESIGN.md +12 -0
  202. package/skills/design/references/code-smells.md +94 -0
  203. package/skills/design/references/module-boundary-adjustment.md +126 -0
  204. package/skills/design/references/module-internal-restructuring.md +132 -0
  205. package/skills/design/references/module-internal-simplification.md +164 -0
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import type { ToolDefinition, ToolContext } from '@laitszkin/tool-registry';
5
+ import { UserInputError, createToolRunner } from '@laitszkin/tool-utils';
5
6
 
6
7
  const TEMPLATE_RELATIVE_PATH = 'skills/review/assets/templates/REPORT.md';
7
8
  const OUTPUT_FILENAME = 'REPORT.md';
@@ -13,13 +14,12 @@ function hasSpecFile(dirPath: string): boolean {
13
14
  return SPEC_FILENAMES.some((name) => fs.existsSync(path.join(dirPath, name)));
14
15
  }
15
16
 
16
- function resolveTargetDir(inputPath: string, stderr: { write(msg: string): boolean }): string | null {
17
+ function resolveTargetDir(inputPath: string): string {
17
18
  let stat: fs.Stats;
18
19
  try {
19
20
  stat = fs.statSync(inputPath);
20
21
  } catch {
21
- stderr.write(`Error: Path not found: ${inputPath}\n`);
22
- return null;
22
+ throw new UserInputError(`Path not found: ${inputPath}`);
23
23
  }
24
24
 
25
25
  // If it's a file, use its parent directory
@@ -30,12 +30,10 @@ function resolveTargetDir(inputPath: string, stderr: { write(msg: string): boole
30
30
  try {
31
31
  dirStat = fs.statSync(dir);
32
32
  } catch {
33
- stderr.write(`Error: Directory not found: ${dir}\n`);
34
- return null;
33
+ throw new UserInputError(`Directory not found: ${dir}`);
35
34
  }
36
35
  if (!dirStat.isDirectory()) {
37
- stderr.write(`Error: Not a directory: ${dir}\n`);
38
- return null;
36
+ throw new UserInputError(`Not a directory: ${dir}`);
39
37
  }
40
38
 
41
39
  const parentDir = path.dirname(dir);
@@ -50,8 +48,7 @@ function resolveTargetDir(inputPath: string, stderr: { write(msg: string): boole
50
48
  return parentDir;
51
49
  }
52
50
 
53
- stderr.write(`Error: ${dir} is not a valid spec directory (no SPEC.md found).\n`);
54
- return null;
51
+ throw new UserInputError(`${dir} is not a valid spec directory (no SPEC.md found).`);
55
52
  }
56
53
 
57
54
  function findLatestDateDir(baseDir: string): string | null {
@@ -66,17 +63,15 @@ function findLatestDateDir(baseDir: string): string | null {
66
63
  return dateDirs.length > 0 ? dateDirs[0] : null;
67
64
  }
68
65
 
69
- function autoDetectTargetDir(stderr: { write(msg: string): boolean }): string | null {
66
+ function autoDetectTargetDir(): string {
70
67
  const plansDir = path.resolve(PLANS_DIR);
71
68
  if (!fs.existsSync(plansDir)) {
72
- stderr.write(`Error: No ${PLANS_DIR}/ directory found. Specify the spec path manually.\n`);
73
- return null;
69
+ throw new UserInputError(`No ${PLANS_DIR}/ directory found. Specify the spec path manually.`);
74
70
  }
75
71
 
76
72
  const latestDate = findLatestDateDir(plansDir);
77
73
  if (!latestDate) {
78
- stderr.write(`Error: No dated spec directories found in ${PLANS_DIR}/. Specify the spec path manually.\n`);
79
- return null;
74
+ throw new UserInputError(`No dated spec directories found in ${PLANS_DIR}/. Specify the spec path manually.`);
80
75
  }
81
76
 
82
77
  const datePath = path.join(plansDir, latestDate);
@@ -110,11 +105,11 @@ function autoDetectTargetDir(stderr: { write(msg: string): boolean }): string |
110
105
  }
111
106
 
112
107
  if (batchDirs.length > 1) {
113
- stderr.write(`Error: Multiple batch specs found in ${datePath}. Specify the path manually:\n`);
108
+ let msg = `Multiple batch specs found in ${datePath}. Specify the path manually:`;
114
109
  for (const bd of batchDirs) {
115
- stderr.write(` apltk create-review-report ${bd.path}\n`);
110
+ msg += `\n apltk create-review-report ${bd.path}`;
116
111
  }
117
- return null;
112
+ throw new UserInputError(msg);
118
113
  }
119
114
 
120
115
  if (singleSpecs.length === 1) {
@@ -122,83 +117,59 @@ function autoDetectTargetDir(stderr: { write(msg: string): boolean }): string |
122
117
  }
123
118
 
124
119
  if (singleSpecs.length > 1) {
125
- stderr.write(`Error: Multiple specs found in ${datePath}. Specify the path manually:\n`);
120
+ let msg = `Multiple specs found in ${datePath}. Specify the path manually:`;
126
121
  for (const ss of singleSpecs) {
127
- stderr.write(` apltk create-review-report ${ss.path}\n`);
122
+ msg += `\n apltk create-review-report ${ss.path}`;
128
123
  }
129
- return null;
124
+ throw new UserInputError(msg);
130
125
  }
131
126
 
132
- stderr.write(`Error: No specs found in ${datePath}.\n`);
133
- return null;
127
+ throw new UserInputError(`No specs found in ${datePath}.`);
134
128
  }
135
129
 
136
- export async function createReviewReportHandler(args: string[], context: ToolContext): Promise<number> {
137
- const stdout = context.stdout || process.stdout;
138
- const stderr = context.stderr || process.stderr;
139
- const sourceRoot = context.sourceRoot || path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
140
-
141
- if (args.includes('--help') || args.includes('-h')) {
142
- stdout.write(`Usage: apltk create-review-report [options] [<spec-path>]
143
-
144
- Copy the review report template (REPORT.md)
145
- to the appropriate spec directory.
130
+ const schema = {
131
+ options: {
132
+ force: { type: 'boolean' as const, short: 'f' },
133
+ },
134
+ allowPositionals: true,
135
+ usage: 'apltk create-review-report [options] [<spec-path>]',
136
+ description: 'Copy the code review report template (REPORT.md) to the spec directory.',
137
+ handler: async (
138
+ values: Record<string, unknown>,
139
+ positionals: string[],
140
+ context: ToolContext,
141
+ ): Promise<number> => {
142
+ const stdout = context.stdout || process.stdout;
143
+ const sourceRoot = context.sourceRoot || path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
144
+
145
+ const force = values.force === true;
146
+
147
+ // Resolve template path
148
+ const templatePath = path.join(sourceRoot, TEMPLATE_RELATIVE_PATH);
149
+ if (!fs.existsSync(templatePath)) {
150
+ throw new UserInputError(`Review report template not found: ${templatePath}`);
151
+ }
146
152
 
147
- Positional:
148
- <spec-path> Path to the spec directory, spec.md file, or batch root.
149
- If omitted, auto-detects the latest spec in docs/plans/.
153
+ // Resolve target directory
154
+ const targetDir = positionals.length > 0
155
+ ? resolveTargetDir(path.resolve(positionals[0]))
156
+ : autoDetectTargetDir();
150
157
 
151
- Options:
152
- --force, -f Overwrite existing REPORT.md if it exists
153
- --help, -h Show this help message
158
+ // Copy template
159
+ const outputPath = path.join(targetDir, OUTPUT_FILENAME);
160
+ if (fs.existsSync(outputPath) && !force) {
161
+ throw new UserInputError(`${outputPath} already exists. Use --force to overwrite.`);
162
+ }
154
163
 
155
- Examples:
156
- apltk create-review-report
157
- apltk create-review-report docs/plans/2026-05-21/my-feature
158
- apltk create-review-report docs/plans/2026-05-21/my-batch
159
- apltk create-review-report --force
160
- `);
164
+ fs.copyFileSync(templatePath, outputPath);
165
+ stdout.write(`${outputPath}\n`);
161
166
  return 0;
162
- }
163
-
164
- const force = args.includes('--force') || args.includes('-f');
165
- const positionalArgs = args.filter((a) => !a.startsWith('--'));
166
-
167
- // Resolve template path
168
- const templatePath = path.join(sourceRoot, TEMPLATE_RELATIVE_PATH);
169
- if (!fs.existsSync(templatePath)) {
170
- stderr.write(`Error: Review report template not found: ${templatePath}\n`);
171
- return 1;
172
- }
173
-
174
- // Resolve target directory
175
- let targetDir: string | null = null;
176
-
177
- if (positionalArgs.length > 0) {
178
- targetDir = resolveTargetDir(path.resolve(positionalArgs[0]), stderr);
179
- } else {
180
- targetDir = autoDetectTargetDir(stderr);
181
- }
182
-
183
- if (!targetDir) {
184
- return 1;
185
- }
186
-
187
- // Copy template
188
- const outputPath = path.join(targetDir, OUTPUT_FILENAME);
189
- if (fs.existsSync(outputPath) && !force) {
190
- stderr.write(`Error: ${outputPath} already exists. Use --force to overwrite.\n`);
191
- return 1;
192
- }
193
-
194
- fs.copyFileSync(templatePath, outputPath);
195
- stdout.write(`${outputPath}\n`);
196
- return 0;
197
- }
167
+ },
168
+ };
198
169
 
199
170
  export const tool: ToolDefinition = {
200
171
  name: 'create-review-report',
201
172
  category: 'Planning & architecture',
202
173
  description: 'Copy the code review report template (REPORT.md) to the spec directory.',
203
- handler: createReviewReportHandler,
174
+ handler: createToolRunner(schema),
204
175
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@laitszkin/tool-create-review-report",
3
- "version": "4.0.8",
4
- "description": "Apollo Toolkit CLI tool",
3
+ "version": "5.0.0",
4
+ "description": "Apollo Toolkit \u2014 CLI tool",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -18,5 +18,12 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@laitszkin/tool-registry": "*"
21
- }
22
- }
21
+ },
22
+ "publishConfig": {
23
+ "access": "public",
24
+ "registry": "https://registry.npmjs.org"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ]
29
+ }
@@ -1,3 +1,2 @@
1
- import type { ToolDefinition, ToolContext } from '@laitszkin/tool-registry';
2
- export declare function createSpecsHandler(args: string[], context: ToolContext): Promise<number>;
1
+ import type { ToolDefinition } from '@laitszkin/tool-registry';
3
2
  export declare const tool: ToolDefinition;
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { UserInputError, createToolRunner } from '../../../tool-utils/dist/index.js';
4
5
  const TEMPLATE_FILENAMES = ['SPEC.md'];
5
6
  const PLACEHOLDER_NAMES = ['[Feature Name]', '[功能名稱]'];
6
7
  function slugify(text) {
@@ -21,137 +22,83 @@ function renderContent(content, today, featureName, changeName, batchName) {
21
22
  rendered = rendered.replace(/\[batch_name\]/g, batchName || 'None');
22
23
  return rendered;
23
24
  }
24
- export async function createSpecsHandler(args, context) {
25
- const stderr = context.stderr || process.stderr;
26
- // Parse CLI args manually for portability (no argparse dependency)
27
- const parsed = {};
28
- const positionalArgs = [];
29
- for (let i = 0; i < args.length; i++) {
30
- const arg = args[i];
31
- if (arg.startsWith('--')) {
32
- const eqIndex = arg.indexOf('=');
33
- let key;
34
- let value;
35
- if (eqIndex !== -1) {
36
- key = arg.slice(2, eqIndex);
37
- value = arg.slice(eqIndex + 1);
38
- }
39
- else {
40
- key = arg.slice(2);
41
- const next = args[i + 1];
42
- if (next !== undefined && !next.startsWith('--')) {
43
- value = next;
44
- i++;
45
- }
46
- else {
47
- value = true;
48
- }
49
- }
50
- if (key === 'force') {
51
- parsed[key] = value === true || value === 'true';
52
- }
53
- else if (key === 'change-name' || key === 'slug') {
54
- parsed['change-name'] = String(value);
55
- }
56
- else if (key === 'batch-name') {
57
- parsed['batch-name'] = String(value);
58
- }
59
- else if (key === 'output-dir') {
60
- parsed['output-dir'] = String(value);
61
- }
62
- else if (key === 'template-dir') {
63
- parsed['template-dir'] = String(value);
64
- }
65
- else if (key === 'help' || key === 'h') {
66
- parsed['help'] = true;
67
- }
68
- }
69
- else {
70
- positionalArgs.push(arg);
71
- }
72
- }
73
- if (parsed['help']) {
74
- const stdout = context.stdout || process.stdout;
75
- stdout.write(`Usage: apltk create-specs <feature_name> [options]
76
-
77
- The tool auto-creates a <today> folder under --output-dir. Batch names
25
+ const schema = {
26
+ options: {
27
+ 'batch-name': { type: 'string' },
28
+ 'change-name': { type: 'string' },
29
+ 'slug': { type: 'string' },
30
+ 'output-dir': { type: 'string', default: 'docs/plans' },
31
+ 'template-dir': { type: 'string' },
32
+ 'force': { type: 'boolean', default: false },
33
+ },
34
+ allowPositionals: true,
35
+ usage: 'apltk create-specs <feature_name> [options]',
36
+ description: `The tool auto-creates a <today> folder under --output-dir. Batch names
78
37
  should group related specs (e.g. "membership-cutover"), NOT include date
79
38
  prefixes like "2026-05-22-membership" — that produces nested date folders.
80
39
 
81
40
  Output:
82
41
  Single spec: <output-dir>/<today>/<change-name>/SPEC.md
83
- Batch: <output-dir>/<today>/<batch-name>/<change-name>/SPEC.md
84
-
85
- Options:
86
- --change-name, --slug Folder name (defaults to slugified feature_name)
87
- --batch-name Batch folder name (do NOT include date prefix)
88
- --output-dir Output base directory (default: docs/plans)
89
- --template-dir Template directory
90
- --force Overwrite existing files
91
- `);
42
+ Batch: <output-dir>/<today>/<batch-name>/<change-name>/SPEC.md`,
43
+ handler: async (values, positionals, context) => {
44
+ const stderr = context.stderr ?? process.stderr;
45
+ const featureName = (positionals[0] || '').trim();
46
+ if (!featureName) {
47
+ throw new UserInputError('feature_name is required.');
48
+ }
49
+ const changeName = (values['change-name'] || values['slug'] || '').trim() || slugify(featureName);
50
+ if (!changeName) {
51
+ throw new UserInputError('Unable to build change_name. Provide --change-name with ASCII letters/numbers.');
52
+ }
53
+ const batchName = values['batch-name']?.trim() || null;
54
+ // Warn if batch name looks like it starts with a date (common agent mistake
55
+ // that produces nested date folders like <today>/2026-05-22-my-batch/).
56
+ if (batchName && /^\d{4}-\d{2}-\d{2}/.test(batchName)) {
57
+ stderr.write(`Warning: --batch-name "${batchName}" starts with a date pattern. The tool already\n`);
58
+ stderr.write(`creates a <today> folder automatically, so this will produce nested date folders.\n`);
59
+ stderr.write(`Use a descriptive name without date prefix, e.g. --batch-name "membership-cutover".\n\n`);
60
+ }
61
+ // Resolve template directory
62
+ const sourceRoot = context.sourceRoot || path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
63
+ const templateDirRaw = values['template-dir'] || path.join(sourceRoot, 'skills', 'spec', 'assets', 'templates');
64
+ const templateDir = path.resolve(templateDirRaw);
65
+ if (!fs.existsSync(templateDir)) {
66
+ throw new UserInputError(`Template directory not found: ${templateDir}`);
67
+ }
68
+ // Check template files exist
69
+ const missingTemplates = TEMPLATE_FILENAMES.filter((name) => !fs.existsSync(path.join(templateDir, name)));
70
+ if (missingTemplates.length > 0) {
71
+ throw new UserInputError(`Missing template files in ${templateDir}: ${missingTemplates.join(', ')}`);
72
+ }
73
+ const outputDir = path.resolve(values['output-dir'] || 'docs/plans');
74
+ const today = new Date().toISOString().slice(0, 10);
75
+ // Prevent double-nesting: if outputDir's last component is already today's date,
76
+ // use it directly as the date root rather than appending the date again.
77
+ const dateRoot = path.basename(outputDir) === today ? outputDir : path.join(outputDir, today);
78
+ const batchRoot = batchName ? path.join(dateRoot, batchName) : null;
79
+ const outputRoot = batchRoot ? path.join(batchRoot, changeName) : path.join(dateRoot, changeName);
80
+ const outputPaths = TEMPLATE_FILENAMES.map((name) => path.join(outputRoot, name));
81
+ const force = values['force'] === true;
82
+ const existingFiles = outputPaths.filter((p) => fs.existsSync(p));
83
+ if (existingFiles.length > 0 && !force) {
84
+ throw new UserInputError(`Files already exist: ${existingFiles.join(', ')}. Use --force to overwrite.`);
85
+ }
86
+ fs.mkdirSync(outputRoot, { recursive: true });
87
+ const stdout = context.stdout ?? process.stdout;
88
+ const todayStr = today;
89
+ for (const filename of TEMPLATE_FILENAMES) {
90
+ const templatePath = path.join(templateDir, filename);
91
+ const outputPath = path.join(outputRoot, filename);
92
+ const content = fs.readFileSync(templatePath, 'utf-8');
93
+ fs.writeFileSync(outputPath, renderContent(content, todayStr, featureName, changeName, batchName), 'utf-8');
94
+ stdout.write(`${outputPath}\n`);
95
+ }
92
96
  return 0;
93
- }
94
- const featureName = (positionalArgs[0] || '').trim();
95
- if (!featureName) {
96
- stderr.write('Error: feature_name is required.\n');
97
- return 1;
98
- }
99
- const changeName = parsed['change-name']?.trim() || slugify(featureName);
100
- if (!changeName) {
101
- stderr.write('Error: Unable to build change_name. Provide --change-name with ASCII letters/numbers.\n');
102
- return 1;
103
- }
104
- const batchName = parsed['batch-name']?.trim() || null;
105
- // Warn if batch name looks like it starts with a date (common agent mistake
106
- // that produces nested date folders like <today>/2026-05-22-my-batch/).
107
- if (batchName && /^\d{4}-\d{2}-\d{2}/.test(batchName)) {
108
- stderr.write(`Warning: --batch-name "${batchName}" starts with a date pattern. The tool already\n`);
109
- stderr.write(`creates a <today> folder automatically, so this will produce nested date folders.\n`);
110
- stderr.write(`Use a descriptive name without date prefix, e.g. --batch-name "membership-cutover".\n\n`);
111
- }
112
- // Resolve template directory
113
- const sourceRoot = context.sourceRoot || path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
114
- const templateDirRaw = parsed['template-dir'] || path.join(sourceRoot, 'skills', 'spec', 'assets', 'templates');
115
- const templateDir = path.resolve(templateDirRaw);
116
- if (!fs.existsSync(templateDir)) {
117
- stderr.write(`Error: Template directory not found: ${templateDir}\n`);
118
- return 1;
119
- }
120
- // Check template files exist
121
- const missingTemplates = TEMPLATE_FILENAMES.filter((name) => !fs.existsSync(path.join(templateDir, name)));
122
- if (missingTemplates.length > 0) {
123
- stderr.write(`Error: Missing template files in ${templateDir}: ${missingTemplates.join(', ')}\n`);
124
- return 1;
125
- }
126
- const outputDir = path.resolve(parsed['output-dir'] || 'docs/plans');
127
- const today = new Date().toISOString().slice(0, 10);
128
- // Prevent double-nesting: if outputDir's last component is already today's date,
129
- // use it directly as the date root rather than appending the date again.
130
- const dateRoot = path.basename(outputDir) === today ? outputDir : path.join(outputDir, today);
131
- const batchRoot = batchName ? path.join(dateRoot, batchName) : null;
132
- const outputRoot = batchRoot ? path.join(batchRoot, changeName) : path.join(dateRoot, changeName);
133
- const outputPaths = TEMPLATE_FILENAMES.map((name) => path.join(outputRoot, name));
134
- const force = parsed['force'] === true;
135
- const existingFiles = outputPaths.filter((p) => fs.existsSync(p));
136
- if (existingFiles.length > 0 && !force) {
137
- stderr.write(`Error: Files already exist: ${existingFiles.join(', ')}. Use --force to overwrite.\n`);
138
- return 1;
139
- }
140
- fs.mkdirSync(outputRoot, { recursive: true });
141
- const stdout = context.stdout || process.stdout;
142
- const todayStr = today;
143
- for (const filename of TEMPLATE_FILENAMES) {
144
- const templatePath = path.join(templateDir, filename);
145
- const outputPath = path.join(outputRoot, filename);
146
- const content = fs.readFileSync(templatePath, 'utf-8');
147
- fs.writeFileSync(outputPath, renderContent(content, todayStr, featureName, changeName, batchName), 'utf-8');
148
- stdout.write(`${outputPath}\n`);
149
- }
150
- return 0;
151
- }
97
+ },
98
+ };
152
99
  export const tool = {
153
100
  name: 'create-specs',
154
101
  category: 'Planning & architecture',
155
102
  description: 'Create spec planning documents from templates.',
156
- handler: createSpecsHandler,
103
+ handler: createToolRunner(schema),
157
104
  };