@laitszkin/apollo-toolkit 4.1.4 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/bin/apollo-toolkit.ts +4 -0
- package/dist/bin/apollo-toolkit.js +4 -0
- package/package.json +4 -2
- package/packages/cli/dist/help-text-builder.d.ts +23 -0
- package/packages/cli/dist/help-text-builder.js +166 -0
- package/packages/cli/dist/index.d.ts +6 -17
- package/packages/cli/dist/index.js +52 -246
- package/packages/cli/dist/installer.d.ts +1 -0
- package/packages/cli/dist/installer.js +20 -7
- package/packages/cli/dist/parsers/install-parser.d.ts +15 -0
- package/packages/cli/dist/parsers/install-parser.js +87 -0
- package/packages/cli/dist/parsers/parser-utils.d.ts +9 -0
- package/packages/cli/dist/parsers/parser-utils.js +16 -0
- package/packages/cli/dist/parsers/tool-parser.d.ts +16 -0
- package/packages/cli/dist/parsers/tool-parser.js +58 -0
- package/packages/cli/dist/parsers/types.d.ts +50 -0
- package/packages/cli/dist/parsers/types.js +1 -0
- package/packages/cli/dist/parsers/uninstall-parser.d.ts +15 -0
- package/packages/cli/dist/parsers/uninstall-parser.js +67 -0
- package/packages/cli/dist/tool-registration.d.ts +2 -0
- package/packages/cli/dist/tool-registration.js +2 -0
- package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/cli/dist/types.d.ts +3 -1
- package/packages/cli/dist/updater.js +11 -5
- package/packages/cli/help-text-builder.ts +180 -0
- package/packages/cli/index.ts +59 -251
- package/packages/cli/installer.ts +19 -7
- package/packages/cli/package.json +6 -3
- package/packages/cli/parsers/install-parser.ts +94 -0
- package/packages/cli/parsers/parser-utils.ts +17 -0
- package/packages/cli/parsers/tool-parser.ts +65 -0
- package/packages/cli/parsers/types.ts +56 -0
- package/packages/cli/parsers/uninstall-parser.ts +75 -0
- package/packages/cli/tool-registration.ts +3 -0
- package/packages/cli/types.ts +6 -1
- package/packages/cli/updater.ts +11 -5
- package/packages/tool-registry/dist/registry.js +3 -4
- package/packages/tool-registry/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tool-registry/dist/types.d.ts +2 -9
- package/packages/tool-registry/package.json +3 -3
- package/packages/tool-registry/registry.ts +3 -4
- package/packages/tool-registry/tsconfig.json +6 -2
- package/packages/tool-registry/types.ts +3 -9
- package/packages/tool-utils/app-error.ts +97 -0
- package/packages/tool-utils/dist/app-error.d.ts +49 -0
- package/packages/tool-utils/dist/app-error.js +80 -0
- package/packages/tool-utils/dist/index.d.ts +5 -0
- package/packages/tool-utils/dist/index.js +3 -0
- package/packages/tool-utils/dist/platform-adapter.d.ts +48 -0
- package/packages/tool-utils/dist/platform-adapter.js +73 -0
- package/packages/tool-utils/dist/schema.d.ts +68 -0
- package/packages/tool-utils/dist/schema.js +67 -0
- package/packages/tool-utils/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tool-utils/index.ts +12 -0
- package/packages/tool-utils/package.json +3 -3
- package/packages/tool-utils/platform-adapter.ts +112 -0
- package/packages/tool-utils/schema.ts +122 -0
- package/packages/tools/architecture/dist/index.d.ts +13 -0
- package/packages/tools/architecture/dist/index.js +55 -57
- package/packages/tools/architecture/dist/index.test.js +17 -4
- package/packages/tools/architecture/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/architecture/index.test.ts +27 -14
- package/packages/tools/architecture/index.ts +85 -88
- package/packages/tools/architecture/package.json +3 -3
- package/packages/tools/codegraph/dist/index.js +12 -22
- package/packages/tools/codegraph/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/codegraph/index.ts +13 -22
- package/packages/tools/codegraph/package.json +3 -3
- package/packages/tools/create-review-report/dist/index.d.ts +1 -2
- package/packages/tools/create-review-report/dist/index.js +46 -77
- package/packages/tools/create-review-report/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/create-review-report/index.ts +52 -81
- package/packages/tools/create-review-report/package.json +3 -3
- package/packages/tools/create-specs/dist/index.d.ts +1 -2
- package/packages/tools/create-specs/dist/index.js +70 -123
- package/packages/tools/create-specs/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/create-specs/index.ts +82 -128
- package/packages/tools/create-specs/package.json +3 -3
- package/packages/tools/docs-to-voice/dist/index.d.ts +1 -2
- package/packages/tools/docs-to-voice/dist/index.js +116 -219
- package/packages/tools/docs-to-voice/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/docs-to-voice/index.ts +265 -385
- package/packages/tools/docs-to-voice/package.json +3 -3
- package/packages/tools/enforce-video-aspect-ratio/dist/index.d.ts +1 -2
- package/packages/tools/enforce-video-aspect-ratio/dist/index.js +77 -154
- package/packages/tools/enforce-video-aspect-ratio/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/enforce-video-aspect-ratio/index.ts +87 -172
- package/packages/tools/enforce-video-aspect-ratio/package.json +3 -3
- package/packages/tools/eval/dist/index.js +7 -0
- package/packages/tools/eval/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/eval/index.ts +8 -0
- package/packages/tools/eval/package.json +3 -3
- package/packages/tools/extract-conversations/dist/index.d.ts +1 -2
- package/packages/tools/extract-conversations/dist/index.js +31 -29
- package/packages/tools/extract-conversations/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/extract-conversations/index.ts +37 -30
- package/packages/tools/extract-conversations/package.json +3 -3
- package/packages/tools/extract-pdf-text/dist/index.d.ts +1 -2
- package/packages/tools/extract-pdf-text/dist/index.js +44 -65
- package/packages/tools/extract-pdf-text/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/extract-pdf-text/index.ts +55 -74
- package/packages/tools/extract-pdf-text/package.json +3 -3
- package/packages/tools/filter-logs/dist/index.js +60 -84
- package/packages/tools/filter-logs/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/filter-logs/index.ts +67 -97
- package/packages/tools/filter-logs/package.json +3 -3
- package/packages/tools/find-github-issues/dist/index.d.ts +10 -0
- package/packages/tools/find-github-issues/dist/index.js +34 -5
- package/packages/tools/find-github-issues/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/find-github-issues/index.ts +37 -5
- package/packages/tools/find-github-issues/package.json +3 -3
- package/packages/tools/generate-storyboard-images/dist/index.d.ts +1 -2
- package/packages/tools/generate-storyboard-images/dist/index.js +98 -173
- package/packages/tools/generate-storyboard-images/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/generate-storyboard-images/index.ts +100 -188
- package/packages/tools/generate-storyboard-images/package.json +3 -3
- package/packages/tools/open-github-issue/dist/index.d.ts +13 -0
- package/packages/tools/open-github-issue/dist/index.js +67 -68
- package/packages/tools/open-github-issue/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/open-github-issue/index.ts +71 -72
- package/packages/tools/open-github-issue/package.json +3 -3
- package/packages/tools/read-github-issue/dist/index.d.ts +16 -1
- package/packages/tools/read-github-issue/dist/index.js +32 -40
- package/packages/tools/read-github-issue/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/read-github-issue/index.ts +32 -45
- package/packages/tools/read-github-issue/package.json +3 -3
- package/packages/tools/render-error-book/dist/index.d.ts +1 -2
- package/packages/tools/render-error-book/dist/index.js +74 -95
- package/packages/tools/render-error-book/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/render-error-book/index.ts +88 -103
- package/packages/tools/render-error-book/package.json +3 -3
- package/packages/tools/render-katex/dist/index.d.ts +1 -2
- package/packages/tools/render-katex/dist/index.js +70 -157
- package/packages/tools/render-katex/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/render-katex/index.ts +138 -222
- package/packages/tools/render-katex/package.json +3 -3
- package/packages/tools/review-threads/dist/index.d.ts +12 -0
- package/packages/tools/review-threads/dist/index.js +83 -86
- package/packages/tools/review-threads/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/review-threads/index.ts +90 -84
- package/packages/tools/review-threads/package.json +3 -3
- package/packages/tools/search-logs/dist/index.js +100 -136
- package/packages/tools/search-logs/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/search-logs/index.ts +113 -145
- package/packages/tools/search-logs/package.json +3 -3
- package/packages/tools/sync-memory-index/dist/index.js +34 -28
- package/packages/tools/sync-memory-index/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/sync-memory-index/index.ts +37 -28
- package/packages/tools/sync-memory-index/package.json +3 -3
- package/packages/tools/validate-openai-agent-config/dist/index.js +13 -7
- package/packages/tools/validate-openai-agent-config/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/validate-openai-agent-config/index.ts +13 -7
- package/packages/tools/validate-openai-agent-config/package.json +3 -3
- package/packages/tools/validate-skill-frontmatter/dist/index.js +12 -6
- package/packages/tools/validate-skill-frontmatter/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/validate-skill-frontmatter/index.ts +12 -6
- package/packages/tools/validate-skill-frontmatter/package.json +3 -3
- package/packages/tui/dist/index.d.ts +2 -1
- package/packages/tui/dist/index.js +1 -0
- package/packages/tui/dist/stdio-adapter.d.ts +36 -0
- package/packages/tui/dist/stdio-adapter.js +69 -0
- package/packages/tui/dist/terminal.js +3 -1
- package/packages/tui/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tui/dist/types.d.ts +17 -0
- package/packages/tui/index.ts +2 -1
- package/packages/tui/package.json +6 -5
- package/packages/tui/stdio-adapter.ts +85 -0
- package/packages/tui/terminal.ts +3 -1
- package/packages/tui/tsconfig.json +5 -2
- package/packages/tui/types.ts +19 -0
- package/resources/project-architecture/assets/architecture.css +2 -1
- package/resources/project-architecture/atlas/atlas.history.log +1 -0
- package/resources/project-architecture/atlas/atlas.history.undo.json +13 -2
- package/resources/project-architecture/atlas/atlas.history.undo.stack.json +610 -0
- package/resources/project-architecture/atlas/atlas.index.yaml +81 -5
- package/resources/project-architecture/atlas/features/cli-dispatch.yaml +43 -0
- package/resources/project-architecture/atlas/features/terminal-ui.yaml +29 -0
- package/resources/project-architecture/atlas/features/tool-registry.yaml +22 -0
- package/resources/project-architecture/atlas/features/tool-utils.yaml +22 -0
- package/resources/project-architecture/features/cli-dispatch/arg-parser.html +40 -0
- package/resources/project-architecture/features/cli-dispatch/help-builder.html +40 -0
- package/resources/project-architecture/features/cli-dispatch/index.html +64 -0
- package/resources/project-architecture/features/cli-dispatch/installer-core.html +40 -0
- package/resources/project-architecture/features/cli-dispatch/tool-discovery.html +40 -0
- package/resources/project-architecture/features/cli-dispatch/update-checker.html +40 -0
- package/resources/project-architecture/features/terminal-ui/banner-display.html +40 -0
- package/resources/project-architecture/features/terminal-ui/index.html +50 -0
- package/resources/project-architecture/features/terminal-ui/interactive-prompts.html +40 -0
- package/resources/project-architecture/features/terminal-ui/terminal-detection.html +40 -0
- package/resources/project-architecture/features/tool-registry/formatter.html +40 -0
- package/resources/project-architecture/features/tool-registry/index.html +43 -0
- package/resources/project-architecture/features/tool-registry/registry-core.html +40 -0
- package/resources/project-architecture/features/tool-utils/index.html +43 -0
- package/resources/project-architecture/features/tool-utils/log-utils.html +40 -0
- package/resources/project-architecture/features/tool-utils/skill-discovery.html +40 -0
- package/resources/project-architecture/index.html +365 -121
- package/scripts/rewrite-imports.mjs +2 -2
- package/scripts/test.sh +144 -8
- package/skills/design/SKILL.md +57 -64
- package/skills/design/assets/templates/DESIGN.md +12 -0
- package/skills/design/references/code-smells.md +94 -0
- package/skills/design/references/module-boundary-adjustment.md +126 -0
- package/skills/design/references/module-internal-restructuring.md +132 -0
- 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
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
return null;
|
|
33
|
+
throw new UserInputError(`Directory not found: ${dir}`);
|
|
35
34
|
}
|
|
36
35
|
if (!dirStat.isDirectory()) {
|
|
37
|
-
|
|
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
|
-
|
|
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(
|
|
66
|
+
function autoDetectTargetDir(): string {
|
|
70
67
|
const plansDir = path.resolve(PLANS_DIR);
|
|
71
68
|
if (!fs.existsSync(plansDir)) {
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
+
let msg = `Multiple batch specs found in ${datePath}. Specify the path manually:`;
|
|
114
109
|
for (const bd of batchDirs) {
|
|
115
|
-
|
|
110
|
+
msg += `\n apltk create-review-report ${bd.path}`;
|
|
116
111
|
}
|
|
117
|
-
|
|
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
|
-
|
|
120
|
+
let msg = `Multiple specs found in ${datePath}. Specify the path manually:`;
|
|
126
121
|
for (const ss of singleSpecs) {
|
|
127
|
-
|
|
122
|
+
msg += `\n apltk create-review-report ${ss.path}`;
|
|
128
123
|
}
|
|
129
|
-
|
|
124
|
+
throw new UserInputError(msg);
|
|
130
125
|
}
|
|
131
126
|
|
|
132
|
-
|
|
133
|
-
return null;
|
|
127
|
+
throw new UserInputError(`No specs found in ${datePath}.`);
|
|
134
128
|
}
|
|
135
129
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
// Resolve target directory
|
|
154
|
+
const targetDir = positionals.length > 0
|
|
155
|
+
? resolveTargetDir(path.resolve(positionals[0]))
|
|
156
|
+
: autoDetectTargetDir();
|
|
150
157
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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:
|
|
174
|
+
handler: createToolRunner(schema),
|
|
204
175
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@laitszkin/tool-create-review-report",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Apollo Toolkit
|
|
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",
|
|
@@ -19,4 +19,4 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@laitszkin/tool-registry": "*"
|
|
21
21
|
}
|
|
22
|
-
}
|
|
22
|
+
}
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { ToolDefinition
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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:
|
|
103
|
+
handler: createToolRunner(schema),
|
|
157
104
|
};
|