@laitszkin/apollo-toolkit 4.1.3 → 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 +45 -0
- package/bin/apollo-toolkit.ts +4 -0
- package/dist/bin/apollo-toolkit.js +4 -0
- package/package.json +7 -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 +21 -17
- package/packages/tools/codegraph/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/codegraph/index.ts +21 -17
- 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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { EOL } from 'node:os';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import type { ParseArgsOptionsConfig } from 'node:util';
|
|
4
|
+
import { formatAppError } from './app-error.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Minimal tool execution context.
|
|
8
|
+
* Defined locally to avoid a circular build dependency:
|
|
9
|
+
* tool-utils → tool-registry → tui → tool-utils.
|
|
10
|
+
*/
|
|
11
|
+
export interface ToolContext {
|
|
12
|
+
sourceRoot?: string;
|
|
13
|
+
stdout?: NodeJS.WriteStream;
|
|
14
|
+
stderr?: NodeJS.WriteStream;
|
|
15
|
+
env?: NodeJS.ProcessEnv;
|
|
16
|
+
spawnCommand?: Function;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
/** Structured output adapter — created by createStdioWriter(@laitszkin/tui). */
|
|
19
|
+
stdioWriter?: { info?(msg: string): void; warn?(msg: string): void; error?(msg: string): void };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Option definition for parseArgs schema. */
|
|
23
|
+
export type SchemaOption =
|
|
24
|
+
| { type: 'string'; default?: string; short?: string; multiple?: boolean; description?: string }
|
|
25
|
+
| { type: 'boolean'; default?: boolean; short?: string; multiple?: boolean; description?: string };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Complete tool schema — single source of truth for args, help, and validation.
|
|
29
|
+
*
|
|
30
|
+
* Example:
|
|
31
|
+
* ```ts
|
|
32
|
+
* const schema: ToolSchema = {
|
|
33
|
+
* options: {
|
|
34
|
+
* start: { type: 'string', short: 's' },
|
|
35
|
+
* end: { type: 'string', short: 'e' },
|
|
36
|
+
* help: { type: 'boolean', short: 'h' },
|
|
37
|
+
* },
|
|
38
|
+
* allowPositionals: true,
|
|
39
|
+
* usage: 'apltk filter-logs [options] [<file>...]',
|
|
40
|
+
* description: 'Filter log lines by time window.',
|
|
41
|
+
* handler: async (values, positionals, ctx) => { ... },
|
|
42
|
+
* };
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export interface ToolSchema {
|
|
46
|
+
options: Record<string, SchemaOption>;
|
|
47
|
+
allowPositionals?: boolean;
|
|
48
|
+
strict?: boolean;
|
|
49
|
+
usage?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
category?: string;
|
|
52
|
+
handler: (
|
|
53
|
+
values: Record<string, unknown>,
|
|
54
|
+
positionals: string[],
|
|
55
|
+
context: ToolContext,
|
|
56
|
+
) => Promise<number> | number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildHelpText(schema: ToolSchema): string {
|
|
60
|
+
const lines: string[] = [];
|
|
61
|
+
if (schema.usage) {
|
|
62
|
+
lines.push(`Usage: ${schema.usage}`);
|
|
63
|
+
}
|
|
64
|
+
if (schema.description) {
|
|
65
|
+
lines.push('', schema.description);
|
|
66
|
+
}
|
|
67
|
+
lines.push('', 'Options:');
|
|
68
|
+
for (const [key, opt] of Object.entries(schema.options)) {
|
|
69
|
+
if (key === 'help') continue;
|
|
70
|
+
const short = opt.short ? `, -${opt.short}` : '';
|
|
71
|
+
const typeLabel = opt.type === 'string' ? ' <value>' : '';
|
|
72
|
+
const multiLabel = opt.multiple ? ' [...]' : '';
|
|
73
|
+
const def = opt.default !== undefined ? ` (default: ${opt.default})` : '';
|
|
74
|
+
const desc = opt.description ? ` ${opt.description}` : '';
|
|
75
|
+
lines.push(` --${key}${short}${typeLabel}${multiLabel}${def}${desc}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push(' --help, -h Show this help');
|
|
78
|
+
return lines.join(EOL);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Creates a tool handler function from a ToolSchema declaration.
|
|
83
|
+
* Automatically handles:
|
|
84
|
+
* - Argument parsing via node:util.parseArgs
|
|
85
|
+
* - --help / -h flag (auto-generates help text from options)
|
|
86
|
+
* - Strict mode validation
|
|
87
|
+
*/
|
|
88
|
+
export function createToolRunner(schema: ToolSchema) {
|
|
89
|
+
const options: ParseArgsOptionsConfig = {};
|
|
90
|
+
for (const [key, opt] of Object.entries(schema.options)) {
|
|
91
|
+
const entry: { type: 'string' | 'boolean'; default?: string | boolean; short?: string; multiple?: boolean } = { type: opt.type };
|
|
92
|
+
if (opt.default !== undefined) entry.default = opt.default;
|
|
93
|
+
if (opt.short) entry.short = opt.short;
|
|
94
|
+
if (opt.multiple) entry.multiple = true;
|
|
95
|
+
options[key] = entry;
|
|
96
|
+
}
|
|
97
|
+
options.help = { type: 'boolean', short: 'h' };
|
|
98
|
+
|
|
99
|
+
return async (args: string[], context: ToolContext): Promise<number> => {
|
|
100
|
+
const stdout = context.stdout ?? process.stdout;
|
|
101
|
+
const stderr = context.stderr ?? process.stderr;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const { values, positionals } = parseArgs({
|
|
105
|
+
args,
|
|
106
|
+
options,
|
|
107
|
+
allowPositionals: schema.allowPositionals ?? false,
|
|
108
|
+
strict: schema.strict ?? true,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (values.help) {
|
|
112
|
+
stdout.write(buildHelpText(schema) + '\n');
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return await schema.handler(values as Record<string, unknown>, positionals, context);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
formatAppError(stderr, err);
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
import type { ToolDefinition, ToolContext } from '@laitszkin/tool-registry';
|
|
2
|
+
/**
|
|
3
|
+
* architectureHandler — Known carryover from the createToolRunner migration.
|
|
4
|
+
*
|
|
5
|
+
* Reason for not using createToolRunner:
|
|
6
|
+
* - Mixed TS/JS dispatch: "apply" and "template" subcommands use TypeScript
|
|
7
|
+
* with AppError throws. Other subcommands delegate to the JS atlas CLI
|
|
8
|
+
* (cli.js) which has its own error handling.
|
|
9
|
+
* - Subcommand-level flag parsing: Each subcommand has unique flags; a single
|
|
10
|
+
* ToolSchema can't express this. See DESIGN.md §2.3 for the full picture.
|
|
11
|
+
*
|
|
12
|
+
* Error handling: All TS paths throw UserInputError/SystemError. JS paths are
|
|
13
|
+
* handled by cli.dispatch()'s internal catch.
|
|
14
|
+
*/
|
|
2
15
|
export declare function architectureHandler(args: string[], context: ToolContext): Promise<number>;
|
|
3
16
|
export declare const tool: ToolDefinition;
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
import yaml from 'js-yaml';
|
|
6
|
+
import { UserInputError, SystemError, createPlatformAdapter } from '../../../tool-utils/dist/index.js';
|
|
6
7
|
// ── Apply & Template helpers (mirrors cli.js internals for the new verbs) ─────
|
|
7
8
|
function findFeature(state, slug) {
|
|
8
9
|
return (state.features || []).find((f) => f.slug === slug);
|
|
@@ -71,7 +72,7 @@ function removeSubmodule(feature, slug, merged) {
|
|
|
71
72
|
function parseEndpoint(value) {
|
|
72
73
|
const parts = value.split('/').filter(Boolean);
|
|
73
74
|
if (parts.length === 0)
|
|
74
|
-
throw new
|
|
75
|
+
throw new UserInputError(`Invalid endpoint: "${value}"`);
|
|
75
76
|
return parts.length > 1
|
|
76
77
|
? { feature: parts[0], submodule: parts[1] }
|
|
77
78
|
: { feature: parts[0] };
|
|
@@ -131,13 +132,11 @@ function yamlStr(value) {
|
|
|
131
132
|
// ── apply ────────────────────────────────────────────────────────────────────
|
|
132
133
|
async function handleApply(applyArgs, context) {
|
|
133
134
|
const stdout = context.stdout || process.stdout;
|
|
134
|
-
const stderr = context.stderr || process.stderr;
|
|
135
135
|
const sourceRoot = context.sourceRoot ||
|
|
136
136
|
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
|
|
137
137
|
const yamlArg = applyArgs[0];
|
|
138
138
|
if (!yamlArg || yamlArg.startsWith('--')) {
|
|
139
|
-
|
|
140
|
-
return 1;
|
|
139
|
+
throw new UserInputError('Missing architecture specification YAML file path. Usage: apltk architecture apply <yaml-file>');
|
|
141
140
|
}
|
|
142
141
|
// Simple flag parser for trailing flags (--spec, --project, --no-render)
|
|
143
142
|
const rest = applyArgs.slice(1);
|
|
@@ -160,12 +159,10 @@ async function handleApply(applyArgs, context) {
|
|
|
160
159
|
}
|
|
161
160
|
catch (e) {
|
|
162
161
|
const location = e.mark ? ` at line ${e.mark.line + 1}` : '';
|
|
163
|
-
|
|
164
|
-
return 1;
|
|
162
|
+
throw new UserInputError(`Error parsing apply YAML (${yamlArg})${location}: ${e.message}`);
|
|
165
163
|
}
|
|
166
164
|
if (!batch || typeof batch !== 'object') {
|
|
167
|
-
|
|
168
|
-
return 1;
|
|
165
|
+
throw new UserInputError('Invalid apply YAML: expected an object with "features" / "edges" keys.');
|
|
169
166
|
}
|
|
170
167
|
// Import atlas modules (shared with the existing JS CLI)
|
|
171
168
|
const cliPath = path.join(sourceRoot, 'skills', 'init-project-html', 'lib', 'atlas', 'cli.js');
|
|
@@ -182,8 +179,7 @@ async function handleApply(applyArgs, context) {
|
|
|
182
179
|
projectRoot = cli.resolveProjectRoot(flags);
|
|
183
180
|
}
|
|
184
181
|
catch (e) {
|
|
185
|
-
|
|
186
|
-
return 1;
|
|
182
|
+
throw new UserInputError(e.message);
|
|
187
183
|
}
|
|
188
184
|
const isSpec = Boolean(flags.spec);
|
|
189
185
|
const atlasDir = cli.baseAtlasDir(projectRoot);
|
|
@@ -209,7 +205,7 @@ async function handleApply(applyArgs, context) {
|
|
|
209
205
|
for (const feat of batch.features || []) {
|
|
210
206
|
const slug = feat.slug;
|
|
211
207
|
if (!slug)
|
|
212
|
-
throw new
|
|
208
|
+
throw new UserInputError('"features" entry missing required "slug" field');
|
|
213
209
|
switch (feat.action) {
|
|
214
210
|
case 'add': {
|
|
215
211
|
const init = {};
|
|
@@ -227,7 +223,7 @@ async function handleApply(applyArgs, context) {
|
|
|
227
223
|
case 'modify': {
|
|
228
224
|
const existing = findFeature(merged, slug);
|
|
229
225
|
if (!existing)
|
|
230
|
-
throw new
|
|
226
|
+
throw new UserInputError(`feature "${slug}" not found for action "modify"`);
|
|
231
227
|
if (feat.title !== undefined)
|
|
232
228
|
existing.title = String(feat.title);
|
|
233
229
|
if (feat.story !== undefined)
|
|
@@ -242,7 +238,7 @@ async function handleApply(applyArgs, context) {
|
|
|
242
238
|
removeFeature(merged, slug);
|
|
243
239
|
break;
|
|
244
240
|
default:
|
|
245
|
-
throw new
|
|
241
|
+
throw new UserInputError(`feature "${slug}": unknown action "${feat.action}"`);
|
|
246
242
|
}
|
|
247
243
|
}
|
|
248
244
|
// 2) Submodules (add / remove) — skip features that were removed
|
|
@@ -251,7 +247,7 @@ async function handleApply(applyArgs, context) {
|
|
|
251
247
|
continue;
|
|
252
248
|
const parent = findFeature(merged, feat.slug);
|
|
253
249
|
if (!parent)
|
|
254
|
-
throw new
|
|
250
|
+
throw new UserInputError(`feature "${feat.slug}" not found for submodule operations`);
|
|
255
251
|
for (const sub of feat.submodules || []) {
|
|
256
252
|
switch (sub.action) {
|
|
257
253
|
case 'add': {
|
|
@@ -269,7 +265,7 @@ async function handleApply(applyArgs, context) {
|
|
|
269
265
|
removeSubmodule(parent, sub.slug, merged);
|
|
270
266
|
break;
|
|
271
267
|
default:
|
|
272
|
-
throw new
|
|
268
|
+
throw new UserInputError(`submodule "${feat.slug}/${sub.slug}": unknown action "${sub.action}"`);
|
|
273
269
|
}
|
|
274
270
|
}
|
|
275
271
|
}
|
|
@@ -285,7 +281,7 @@ async function handleApply(applyArgs, context) {
|
|
|
285
281
|
continue;
|
|
286
282
|
const subMod = findSubmodule(parent, sub.slug);
|
|
287
283
|
if (!subMod)
|
|
288
|
-
throw new
|
|
284
|
+
throw new UserInputError(`submodule "${feat.slug}/${sub.slug}" not found for function operations`);
|
|
289
285
|
for (const fn of sub.functions || []) {
|
|
290
286
|
switch (fn.action) {
|
|
291
287
|
case 'add': {
|
|
@@ -308,7 +304,7 @@ async function handleApply(applyArgs, context) {
|
|
|
308
304
|
subMod.functions = (subMod.functions || []).filter((f) => f.name !== fn.name);
|
|
309
305
|
break;
|
|
310
306
|
default:
|
|
311
|
-
throw new
|
|
307
|
+
throw new UserInputError(`function "${feat.slug}/${sub.slug}/${fn.name}": unknown action "${fn.action}"`);
|
|
312
308
|
}
|
|
313
309
|
}
|
|
314
310
|
}
|
|
@@ -322,29 +318,29 @@ async function handleApply(applyArgs, context) {
|
|
|
322
318
|
to = parseEndpoint(edge.to);
|
|
323
319
|
}
|
|
324
320
|
catch (er) {
|
|
325
|
-
throw new
|
|
321
|
+
throw new UserInputError(`edge: ${er.message}`, undefined, { cause: er });
|
|
326
322
|
}
|
|
327
323
|
switch (edge.action) {
|
|
328
324
|
case 'add': {
|
|
329
325
|
// Referential integrity validation
|
|
330
326
|
const fromFeature = findFeature(merged, from.feature);
|
|
331
327
|
if (!fromFeature) {
|
|
332
|
-
throw new
|
|
328
|
+
throw new UserInputError(`edge "${edge.from} → ${edge.to}": source feature "${from.feature}" not found`);
|
|
333
329
|
}
|
|
334
330
|
if (from.submodule) {
|
|
335
331
|
const fromSub = findSubmodule(fromFeature, from.submodule);
|
|
336
332
|
if (!fromSub) {
|
|
337
|
-
throw new
|
|
333
|
+
throw new UserInputError(`edge "${edge.from} → ${edge.to}": source submodule "${from.submodule}" not found in feature "${from.feature}"`);
|
|
338
334
|
}
|
|
339
335
|
}
|
|
340
336
|
const toFeature = findFeature(merged, to.feature);
|
|
341
337
|
if (!toFeature) {
|
|
342
|
-
throw new
|
|
338
|
+
throw new UserInputError(`edge "${edge.from} → ${edge.to}": target feature "${to.feature}" not found`);
|
|
343
339
|
}
|
|
344
340
|
if (to.submodule) {
|
|
345
341
|
const toSub = findSubmodule(toFeature, to.submodule);
|
|
346
342
|
if (!toSub) {
|
|
347
|
-
throw new
|
|
343
|
+
throw new UserInputError(`edge "${edge.from} → ${edge.to}": target submodule "${to.submodule}" not found in feature "${to.feature}"`);
|
|
348
344
|
}
|
|
349
345
|
}
|
|
350
346
|
const eid = edge.id || `e-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -393,13 +389,15 @@ async function handleApply(applyArgs, context) {
|
|
|
393
389
|
break;
|
|
394
390
|
}
|
|
395
391
|
default:
|
|
396
|
-
throw new
|
|
392
|
+
throw new UserInputError(`edge "${edge.from} → ${edge.to}": unknown action "${edge.action}"`);
|
|
397
393
|
}
|
|
398
394
|
}
|
|
399
395
|
}
|
|
400
396
|
catch (e) {
|
|
401
|
-
|
|
402
|
-
|
|
397
|
+
if (e instanceof UserInputError || e instanceof SystemError) {
|
|
398
|
+
throw e;
|
|
399
|
+
}
|
|
400
|
+
throw new UserInputError(e.message);
|
|
403
401
|
}
|
|
404
402
|
// ── All mutations succeeded — persist ──
|
|
405
403
|
const saveDir = isSpec ? overlayDir : atlasDir;
|
|
@@ -442,7 +440,7 @@ async function handleApply(applyArgs, context) {
|
|
|
442
440
|
// ── template ─────────────────────────────────────────────────────────────────
|
|
443
441
|
async function handleTemplate(templateArgs, context) {
|
|
444
442
|
const stdout = context.stdout || process.stdout;
|
|
445
|
-
const
|
|
443
|
+
const adapter = createPlatformAdapter();
|
|
446
444
|
// Parse --spec <dir> --output <dir>
|
|
447
445
|
let specDir;
|
|
448
446
|
let outputDir;
|
|
@@ -454,8 +452,7 @@ async function handleTemplate(templateArgs, context) {
|
|
|
454
452
|
outputDir = templateArgs[++i];
|
|
455
453
|
}
|
|
456
454
|
if (!specDir || !outputDir) {
|
|
457
|
-
|
|
458
|
-
return 1;
|
|
455
|
+
throw new UserInputError('Missing --spec and/or --output arguments. Usage: apltk architecture template --spec <spec-dir> --output <output-dir>');
|
|
459
456
|
}
|
|
460
457
|
const specPath = path.resolve(specDir, 'SPEC.md');
|
|
461
458
|
const outputDirPath = path.resolve(outputDir);
|
|
@@ -477,18 +474,13 @@ async function handleTemplate(templateArgs, context) {
|
|
|
477
474
|
else {
|
|
478
475
|
const resolvedSpecDir = path.resolve(specDir);
|
|
479
476
|
if (!fs.existsSync(resolvedSpecDir)) {
|
|
480
|
-
|
|
477
|
+
throw new UserInputError(`Spec directory not found: ${resolvedSpecDir}`);
|
|
481
478
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
stderr.write(`Spec directory found but no SPEC.md. Found: ${mdFiles.join(', ')}\n`);
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
stderr.write(`Spec directory found but no SPEC.md. No .md files found.\n`);
|
|
489
|
-
}
|
|
479
|
+
const mdFiles = fs.readdirSync(resolvedSpecDir).filter((f) => f.endsWith('.md'));
|
|
480
|
+
if (mdFiles.length > 0) {
|
|
481
|
+
throw new UserInputError(`Spec directory found but no SPEC.md. Found: ${mdFiles.join(', ')}`);
|
|
490
482
|
}
|
|
491
|
-
|
|
483
|
+
throw new UserInputError('Spec directory found but no SPEC.md. No .md files found.');
|
|
492
484
|
}
|
|
493
485
|
// Build proposal.yaml content
|
|
494
486
|
const lines = [
|
|
@@ -506,12 +498,11 @@ async function handleTemplate(templateArgs, context) {
|
|
|
506
498
|
lines.push(' submodules: [] # LLM: fill in submodule entries', '', '# Cross-feature edges (leave empty for single-feature proposals)', 'edges: [] # LLM: fill in edge entries', '');
|
|
507
499
|
try {
|
|
508
500
|
fs.mkdirSync(outputDirPath, { recursive: true });
|
|
509
|
-
fs.writeFileSync(outputPath, lines.join(
|
|
501
|
+
fs.writeFileSync(outputPath, lines.join(adapter.EOL), 'utf8');
|
|
510
502
|
stdout.write(`${outputPath}\n`);
|
|
511
503
|
}
|
|
512
504
|
catch (e) {
|
|
513
|
-
|
|
514
|
-
return 1;
|
|
505
|
+
throw new SystemError(`Error writing proposal.yaml: ${e.message}`);
|
|
515
506
|
}
|
|
516
507
|
// Try to enrich with CodeGraph API listing
|
|
517
508
|
try {
|
|
@@ -531,7 +522,7 @@ async function handleTemplate(templateArgs, context) {
|
|
|
531
522
|
}
|
|
532
523
|
apiLines.push('#');
|
|
533
524
|
const existing = fs.readFileSync(outputPath, 'utf8');
|
|
534
|
-
fs.writeFileSync(outputPath, apiLines.join(
|
|
525
|
+
fs.writeFileSync(outputPath, apiLines.join(adapter.EOL) + adapter.EOL + existing);
|
|
535
526
|
cg.close();
|
|
536
527
|
}
|
|
537
528
|
}
|
|
@@ -541,30 +532,37 @@ async function handleTemplate(templateArgs, context) {
|
|
|
541
532
|
return 0;
|
|
542
533
|
}
|
|
543
534
|
// ── Handler entrypoint ───────────────────────────────────────────────────────
|
|
535
|
+
/**
|
|
536
|
+
* architectureHandler — Known carryover from the createToolRunner migration.
|
|
537
|
+
*
|
|
538
|
+
* Reason for not using createToolRunner:
|
|
539
|
+
* - Mixed TS/JS dispatch: "apply" and "template" subcommands use TypeScript
|
|
540
|
+
* with AppError throws. Other subcommands delegate to the JS atlas CLI
|
|
541
|
+
* (cli.js) which has its own error handling.
|
|
542
|
+
* - Subcommand-level flag parsing: Each subcommand has unique flags; a single
|
|
543
|
+
* ToolSchema can't express this. See DESIGN.md §2.3 for the full picture.
|
|
544
|
+
*
|
|
545
|
+
* Error handling: All TS paths throw UserInputError/SystemError. JS paths are
|
|
546
|
+
* handled by cli.dispatch()'s internal catch.
|
|
547
|
+
*/
|
|
544
548
|
export async function architectureHandler(args, context) {
|
|
545
549
|
// Intercept apply / template before passing through to the JS CLI
|
|
546
550
|
const first = args[0] || '';
|
|
547
551
|
if (first === 'apply')
|
|
548
|
-
return handleApply(args.slice(1), context);
|
|
552
|
+
return await handleApply(args.slice(1), context);
|
|
549
553
|
if (first === 'template')
|
|
550
|
-
return handleTemplate(args.slice(1), context);
|
|
554
|
+
return await handleTemplate(args.slice(1), context);
|
|
551
555
|
// Delegate to the existing atlas CLI (still in JS)
|
|
552
556
|
const sourceRoot = context.sourceRoot ||
|
|
553
557
|
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
|
|
554
558
|
const cliPath = path.join(sourceRoot, 'skills', 'init-project-html', 'lib', 'atlas', 'cli.js');
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
catch (error) {
|
|
565
|
-
(context.stderr || process.stderr).write(`Error loading atlas CLI: ${error.message}\n`);
|
|
566
|
-
return 1;
|
|
567
|
-
}
|
|
559
|
+
// Use file URL for ESM import compatibility on Windows — import() requires forward slashes.
|
|
560
|
+
const cliModule = await import(pathToFileURL(cliPath).href);
|
|
561
|
+
const cli = cliModule.default;
|
|
562
|
+
return cli.dispatch(args, {
|
|
563
|
+
stdout: context.stdout || process.stdout,
|
|
564
|
+
stderr: context.stderr || process.stderr,
|
|
565
|
+
});
|
|
568
566
|
}
|
|
569
567
|
export const tool = {
|
|
570
568
|
name: 'architecture',
|
|
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import os from 'node:os';
|
|
6
|
+
import { UserInputError } from '../../../tool-utils/dist/index.js';
|
|
6
7
|
import { tool } from './index.js';
|
|
7
8
|
function makeIo() {
|
|
8
9
|
let stdoutBuf = '';
|
|
@@ -88,6 +89,11 @@ describe('REGTEST-15: Wrong spec path error', () => {
|
|
|
88
89
|
assert.equal(exitCode, 1, 'Expected exit code 1 for missing spec path');
|
|
89
90
|
assert.ok(io.stderrText.includes('not found'), `stderr should contain "not found": got ${JSON.stringify(io.stderrText)}`);
|
|
90
91
|
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
// handler may throw — verify error type and message
|
|
94
|
+
assert.ok(err instanceof UserInputError, 'Expected UserInputError');
|
|
95
|
+
assert.ok(err.message.includes('not found'), 'Error message should indicate path not found');
|
|
96
|
+
}
|
|
91
97
|
finally {
|
|
92
98
|
mock.restoreAll();
|
|
93
99
|
}
|
|
@@ -221,9 +227,16 @@ describe('REGTEST-17: Edge referential integrity', () => {
|
|
|
221
227
|
const handler = tool.handler;
|
|
222
228
|
if (!handler)
|
|
223
229
|
throw new Error('tool.handler is undefined');
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
230
|
+
try {
|
|
231
|
+
const exitCode = await handler(['apply', yamlPath, '--no-render'], makeContext(io, { sourceRoot: tmpDir }));
|
|
232
|
+
assert.equal(exitCode, 1, 'Expected exit code 1 for edge targeting missing feature');
|
|
233
|
+
assert.ok(io.stderrText.includes('non-existent-feature'), `stderr should contain "non-existent-feature": got ${JSON.stringify(io.stderrText)}`);
|
|
234
|
+
assert.ok(io.stderrText.length > 0, `stderr should have error text: got ${JSON.stringify(io.stderrText)}`);
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
// handler may throw instead of returning 1
|
|
238
|
+
assert.ok(err instanceof UserInputError, 'Expected UserInputError');
|
|
239
|
+
assert.ok(err.message.includes('non-existent-feature'), 'Error message should reference the missing feature');
|
|
240
|
+
}
|
|
228
241
|
});
|
|
229
242
|
});
|