@sylphx/flow 3.2.0 → 3.4.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 CHANGED
@@ -1,5 +1,32 @@
1
1
  # @sylphx/flow
2
2
 
3
+ ## 3.4.0 (2026-01-26)
4
+
5
+ ### ✨ Features
6
+
7
+ - add /e2e-audit and business logic to /audit ([8e87e10](https://github.com/SylphxAI/flow/commit/8e87e10b49eaec37bb85c2677a0d8b1f602c5584))
8
+
9
+ ### ♻️ Refactoring
10
+
11
+ - replace any with proper types in claude-code.ts ([d498d41](https://github.com/SylphxAI/flow/commit/d498d41dcae95cc9d98628f7860be2f48b7c342e))
12
+ - replace any with proper types in target-utils.ts ([aebbf5d](https://github.com/SylphxAI/flow/commit/aebbf5dad8b4705664db1784127a4af0da8f8e5c))
13
+ - remove unused securityMiddleware from security.ts ([a8efc37](https://github.com/SylphxAI/flow/commit/a8efc37fd06db866b5a08715a5141c19fbb3f54e))
14
+ - add generics to jsonc.ts functions ([aac10af](https://github.com/SylphxAI/flow/commit/aac10afa3aa8cbd5c0319932c09d34e7a7ca4fec))
15
+ - replace any with unknown in settings.ts ([8b76e17](https://github.com/SylphxAI/flow/commit/8b76e179ffc6f4533dbc5b58894cb1c0b468f83a))
16
+ - replace any with unknown in prompt-helpers ([56e2b37](https://github.com/SylphxAI/flow/commit/56e2b375992232395ea58682ef7b04d396321b25))
17
+ - remove unused FlowOptions properties ([273f521](https://github.com/SylphxAI/flow/commit/273f521a37e6b63f64b3fe6667f7489681bcb9fa))
18
+ - remove unused async utility functions ([3b2c8fd](https://github.com/SylphxAI/flow/commit/3b2c8fdb6ea997e195a15e19bdd0ab931fae0f15))
19
+
20
+ ## 3.3.0 (2026-01-26)
21
+
22
+ ### ✨ Features
23
+
24
+ - add /catchup command and architecture principles ([f5d6cd9](https://github.com/SylphxAI/flow/commit/f5d6cd9475aba40094f5c434fb962b7451632f1d))
25
+
26
+ ### 🐛 Bug Fixes
27
+
28
+ - **builder:** simplify CLAUDE.md memory instructions ([fedd464](https://github.com/SylphxAI/flow/commit/fedd46482dc01ef5988d5552b9f7a7c8675c6240))
29
+
3
30
  ## 3.2.0 (2026-01-26)
4
31
 
5
32
  ### ✨ Features
@@ -86,7 +86,7 @@ Vercel CLI, Neon CLI, GitHub CLI
86
86
 
87
87
  **Todos.** Track what needs to be done next. This is your memory of what to do.
88
88
 
89
- **CLAUDE.md** — Your persistent memory file. Update it when you discover:
89
+ **CLAUDE.md** — Your persistent memory file. Write to it when you discover:
90
90
  - Project-specific commands (build, test, deploy, lint)
91
91
  - Environment setup (env vars, prerequisites, dependencies)
92
92
  - Architecture decisions and their reasoning
@@ -94,10 +94,8 @@ Vercel CLI, Neon CLI, GitHub CLI
94
94
  - Gotchas, workarounds, or non-obvious behaviors
95
95
  - Frequently referenced paths, configs, or resources
96
96
 
97
- This file is auto-attached every session. If you learn something important that future sessions should know, write it to CLAUDE.md immediately.
98
-
99
97
  **Recovery:**
100
- - Lost context? → Check `git log` for history, read CLAUDE.md
98
+ - Lost context? → Check `git log` for history
101
99
  - Forgot next steps? → Check todos
102
100
 
103
101
  ## Issue Ownership
@@ -119,11 +117,18 @@ This file is auto-attached every session. If you learn something important that
119
117
 
120
118
  * No workarounds, no hacks — all implementations must meet state-of-the-art industrial standards
121
119
  * Single Source of Truth — one authoritative source for every state, behavior, and decision
122
- * Safety and strong typing use tRPC for end-to-end type safety across all server communication
123
- * Observability: comprehensive logging, metrics, and tracing
124
- * Recoverability: systems must be swiftly restorable without data loss
120
+ * Type safety — end-to-end type safety across all boundaries (tRPC, Zod, strict TypeScript)
121
+ * Observability logging, metrics, tracing; platform code must be observable by design
122
+ * Recoverability systems must be swiftly restorable without data loss
125
123
  * If automation exists for a task, manual execution is prohibited
126
124
 
125
+ ## Architecture Principles
126
+
127
+ * **Pure functions** — no side effects, deterministic output; isolate impure code at boundaries
128
+ * **Decoupling** — minimize dependencies between modules, use interfaces and dependency injection
129
+ * **Modularisation** — single responsibility, clear boundaries, independent deployability
130
+ * **Reusable composition** — build primitives that compose; prefer composition over inheritance
131
+
127
132
  ## Codebase
128
133
 
129
134
  * Zero tolerance: no TODOs, no dead code, no unused code
@@ -23,7 +23,22 @@ Scan the entire project for issues. Find problems, don't fix them. Open GitHub i
23
23
  - Inconsistent naming conventions
24
24
  - Outdated dependencies with known issues
25
25
 
26
- ### 2. Architecture
26
+ ### 2. Business Logic Correctness
27
+
28
+ **Code-correct ≠ Business-correct. Review business rules in the code.**
29
+
30
+ - Region/locale logic: Does HK user see HK-specific data? (not China's 五險三金)
31
+ - Currency handling: Correct currency for user's region?
32
+ - Date/time: Timezone handling, week start day, date formats
33
+ - Tax/legal: Region-specific rules applied correctly?
34
+ - Permissions: Do access rules make business sense?
35
+ - Calculations: Business formulas correct? (not just mathematically)
36
+ - State machines: Valid business state transitions only?
37
+ - Validation rules: Match real-world business constraints?
38
+ - Default values: Sensible for the business context?
39
+ - Edge cases: Business-impossible states prevented?
40
+
41
+ ### 3. Architecture
27
42
  - Circular dependencies
28
43
  - God objects/files doing too much
29
44
  - Tight coupling between modules
@@ -33,7 +48,7 @@ Scan the entire project for issues. Find problems, don't fix them. Open GitHub i
33
48
  - Missing SSOT (multiple sources of truth)
34
49
  - Inconsistent patterns across codebase
35
50
 
36
- ### 3. UI/UX Issues
51
+ ### 4. UI/UX Issues
37
52
  - Confusing user flows
38
53
  - Missing loading states
39
54
  - Missing error states
@@ -45,7 +60,7 @@ Scan the entire project for issues. Find problems, don't fix them. Open GitHub i
45
60
  - Unclear CTAs or labels
46
61
  - Information overload
47
62
 
48
- ### 4. Product Design
63
+ ### 5. Product Design
49
64
  - Unclear value proposition
50
65
  - Friction in core user journey
51
66
  - Missing onboarding guidance
@@ -55,7 +70,7 @@ Scan the entire project for issues. Find problems, don't fix them. Open GitHub i
55
70
  - Power user needs unmet
56
71
  - Beginner barriers too high
57
72
 
58
- ### 5. Performance
73
+ ### 6. Performance
59
74
  - Slow page loads
60
75
  - Unnecessary re-renders
61
76
  - Large bundle sizes
@@ -64,7 +79,7 @@ Scan the entire project for issues. Find problems, don't fix them. Open GitHub i
64
79
  - Missing caching opportunities
65
80
  - Unoptimized images/assets
66
81
 
67
- ### 6. Security
82
+ ### 7. Security
68
83
  - Exposed secrets or credentials
69
84
  - Missing input validation
70
85
  - XSS vulnerabilities
@@ -73,7 +88,7 @@ Scan the entire project for issues. Find problems, don't fix them. Open GitHub i
73
88
  - Missing rate limiting
74
89
  - Overly permissive CORS
75
90
 
76
- ### 7. Developer Experience
91
+ ### 8. Developer Experience
77
92
  - Missing or outdated documentation
78
93
  - Unclear setup instructions
79
94
  - Flaky or missing tests
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: catchup
3
+ description: Deep dive into project history, direction, and implementation details
4
+ ---
5
+
6
+ # Catchup: Understand the Project Deeply
7
+
8
+ Get up to speed with the entire project — history, direction, and details.
9
+
10
+ ## Phase 1: Git History Analysis
11
+
12
+ ### Recent Commits
13
+ ```bash
14
+ git log --oneline -50
15
+ git log --oneline --since="1 week ago"
16
+ git log --oneline --since="1 month ago" --until="1 week ago"
17
+ ```
18
+
19
+ ### Commit Patterns
20
+ - What areas are being actively worked on?
21
+ - What types of changes dominate? (feat, fix, refactor, docs)
22
+ - Who are the contributors and what do they focus on?
23
+ - Are there any recurring issues being fixed?
24
+
25
+ ### Recent Activity by Area
26
+ ```bash
27
+ git log --oneline --all -- "src/components/*" | head -20
28
+ git log --oneline --all -- "src/api/*" | head -20
29
+ git log --oneline --all -- "src/lib/*" | head -20
30
+ ```
31
+
32
+ ## Phase 2: Development Direction
33
+
34
+ ### Read Project Documentation
35
+ 1. **PRODUCT.md** — Vision, goals, target users, success metrics
36
+ 2. **ARCHITECTURE.md** — Tech decisions, patterns, system design
37
+ 3. **CLAUDE.md** — Project-specific knowledge, commands, gotchas
38
+ 4. **README.md** — Setup, usage, contribution guidelines
39
+ 5. **CHANGELOG.md** — Release history, breaking changes
40
+
41
+ ### Identify Current Focus
42
+ - What features are in progress?
43
+ - What's the roadmap or next milestones?
44
+ - Are there open issues or PRs that indicate direction?
45
+
46
+ ```bash
47
+ gh issue list --state open --limit 20
48
+ gh pr list --state open --limit 10
49
+ ```
50
+
51
+ ### Branch Analysis
52
+ ```bash
53
+ git branch -a
54
+ git log main..HEAD --oneline # if on feature branch
55
+ ```
56
+
57
+ ## Phase 3: Deep Implementation Research
58
+
59
+ ### Codebase Structure
60
+ ```bash
61
+ tree -L 2 -d src/ # or relevant directories
62
+ ```
63
+
64
+ ### Key Files to Understand
65
+ - Entry points (main, index, app)
66
+ - Configuration files (config, env, settings)
67
+ - Core business logic
68
+ - Database schema and migrations
69
+ - API routes and handlers
70
+ - Shared utilities and helpers
71
+
72
+ ### Dependency Analysis
73
+ - What are the major dependencies?
74
+ - Are there any custom abstractions worth understanding?
75
+ - What patterns are consistently used?
76
+
77
+ ### Test Coverage
78
+ - Where are tests located?
79
+ - What's tested, what's not?
80
+ - What do test failures tell us about the system?
81
+
82
+ ## Output
83
+
84
+ ### Executive Summary
85
+ ```
86
+ Project: [name]
87
+ Stage: [MVP / Growth / Mature]
88
+ Focus: [current development focus]
89
+ Health: [assessment based on commits, issues, code quality]
90
+ ```
91
+
92
+ ### Recent Activity (Last 2 Weeks)
93
+ | Date | Area | Changes | Impact |
94
+ |------|------|---------|--------|
95
+ | ... | ... | ... | H/M/L |
96
+
97
+ ### Development Direction
98
+ - **Current sprint/focus:** ...
99
+ - **Next milestones:** ...
100
+ - **Technical debt:** ...
101
+ - **Opportunities:** ...
102
+
103
+ ### Key Findings
104
+ 1. ...
105
+ 2. ...
106
+ 3. ...
107
+
108
+ ### Recommendations
109
+ - **Immediate:** ...
110
+ - **Short-term:** ...
111
+ - **Consider:** ...
112
+
113
+ ## Mindset
114
+
115
+ * Be thorough — read everything, assume nothing
116
+ * Connect the dots — understand WHY decisions were made
117
+ * Think forward — where is this project heading?
118
+ * Be critical — identify risks, gaps, opportunities
119
+ * Document discoveries — update CLAUDE.md with important findings
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: e2e-audit
3
+ description: Browser-based audit for business logic and product correctness
4
+ ---
5
+
6
+ # E2E Audit: Browser-Based Product Validation
7
+
8
+ Open a browser, walk through the product, find business logic and UX issues.
9
+
10
+ **This is NOT about code correctness — code can be "correct" but business logic can be WRONG.**
11
+
12
+ ## Why E2E Audit?
13
+
14
+ Static code analysis can't catch:
15
+ - User selects "Hong Kong" but sees China's "五險三金"
16
+ - Calendar showing Monday as week start for US users
17
+ - Currency conversion using stale rates
18
+ - Tax calculations wrong for specific regions
19
+ - Permissions that make no business sense
20
+ - Data inconsistency across related features
21
+
22
+ ## Process
23
+
24
+ ### 1. Launch Browser
25
+ ```
26
+ mcp__playwright__browser_navigate - Go to product URL
27
+ mcp__playwright__browser_snapshot - Get current page state
28
+ ```
29
+
30
+ ### 2. Walk Through User Journeys
31
+
32
+ **Authentication:**
33
+ - [ ] Signup flow (all fields, validations, error messages)
34
+ - [ ] Login flow (remember me, forgot password)
35
+ - [ ] Logout and session handling
36
+
37
+ **Core Features:**
38
+ - [ ] Primary user workflow end-to-end
39
+ - [ ] Secondary features
40
+ - [ ] Settings and configuration
41
+ - [ ] Profile management
42
+
43
+ **Edge Cases:**
44
+ - [ ] Empty states
45
+ - [ ] Error states
46
+ - [ ] Boundary values (min/max)
47
+ - [ ] Invalid inputs
48
+
49
+ ### 3. Test Business Logic
50
+
51
+ **Region/Locale Consistency:**
52
+ - Change user region → verify ALL related data updates
53
+ - Currency, date format, language, legal requirements
54
+ - Region-specific features show/hide correctly
55
+
56
+ **Data Consistency:**
57
+ - Related fields stay in sync
58
+ - Calculations produce business-correct results
59
+ - Aggregates match detail records
60
+
61
+ **Business Rules:**
62
+ - Permissions enforced correctly
63
+ - Workflow states valid
64
+ - Business constraints respected
65
+
66
+ ### 4. Document Issues
67
+
68
+ For each issue found:
69
+ ```bash
70
+ mcp__playwright__browser_take_screenshot # Capture evidence
71
+ ```
72
+
73
+ ## Issue Categories
74
+
75
+ ### Business Logic Errors
76
+ - Data doesn't match business expectations
77
+ - Rules applied incorrectly for context
78
+ - Cross-feature data inconsistency
79
+
80
+ ### UX Issues
81
+ - Confusing user flows
82
+ - Missing feedback
83
+ - Unclear error messages
84
+ - Accessibility problems
85
+
86
+ ### Visual Issues
87
+ - Layout broken
88
+ - Responsive issues
89
+ - Inconsistent styling
90
+
91
+ ## Browser Tools Reference
92
+
93
+ ```
94
+ mcp__playwright__browser_navigate - Go to URL
95
+ mcp__playwright__browser_snapshot - Get page state (use this frequently)
96
+ mcp__playwright__browser_click - Click elements
97
+ mcp__playwright__browser_fill_form - Fill forms
98
+ mcp__playwright__browser_type - Type text
99
+ mcp__playwright__browser_select_option - Select dropdown
100
+ mcp__playwright__browser_take_screenshot - Capture evidence
101
+ mcp__playwright__browser_press_key - Keyboard input
102
+ ```
103
+
104
+ ## Output
105
+
106
+ ### Issues Found
107
+ | # | Type | Description | Severity | Screenshot |
108
+ |---|------|-------------|----------|------------|
109
+ | 1 | Business Logic | HK user sees CN insurance | High | screenshot_1.png |
110
+ | 2 | UX | No loading state on submit | Medium | screenshot_2.png |
111
+
112
+ ### Open GitHub Issues
113
+ ```bash
114
+ gh issue create --title "[E2E] Brief description" --body "..." --label "e2e-audit"
115
+ ```
116
+
117
+ ## Mindset
118
+
119
+ * **Code-correct ≠ Business-correct** — always verify business logic
120
+ * Think like a real user, not a developer
121
+ * Test the unhappy paths
122
+ * Question every assumption
123
+ * If something feels wrong, it probably is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/flow",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "description": "One CLI to rule them all. Unified orchestration layer for AI coding assistants. Auto-detection, auto-installation, auto-upgrade.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -7,8 +7,6 @@ export interface FlowOptions {
7
7
  verbose?: boolean;
8
8
  dryRun?: boolean;
9
9
  sync?: boolean;
10
- initOnly?: boolean;
11
- runOnly?: boolean;
12
10
  repair?: boolean;
13
11
  upgrade?: boolean;
14
12
  upgradeTarget?: boolean;
@@ -23,7 +21,6 @@ export interface FlowOptions {
23
21
 
24
22
  // Smart configuration options
25
23
  selectProvider?: boolean;
26
- selectAgent?: boolean;
27
24
  provider?: string;
28
25
 
29
26
  // Execution modes
@@ -35,5 +32,4 @@ export interface FlowOptions {
35
32
 
36
33
  // Loop mode
37
34
  loop?: number;
38
- maxRuns?: number;
39
35
  }
@@ -35,33 +35,6 @@ export const fromPromise = async <T>(
35
35
  }
36
36
  };
37
37
 
38
- /**
39
- * Map over async result
40
- */
41
- export const mapAsync =
42
- <T, U>(fn: (value: T) => U | Promise<U>) =>
43
- async <E>(result: AsyncResult<T, E>): AsyncResult<U, E> => {
44
- const resolved = await result;
45
- if (isSuccess(resolved)) {
46
- const mapped = await fn(resolved.value);
47
- return success(mapped);
48
- }
49
- return resolved;
50
- };
51
-
52
- /**
53
- * FlatMap over async result
54
- */
55
- export const flatMapAsync =
56
- <T, U, E>(fn: (value: T) => AsyncResult<U, E>) =>
57
- async (result: AsyncResult<T, E>): AsyncResult<U, E> => {
58
- const resolved = await result;
59
- if (isSuccess(resolved)) {
60
- return fn(resolved.value);
61
- }
62
- return resolved;
63
- };
64
-
65
38
  /**
66
39
  * Run async operation with timeout
67
40
  */
@@ -120,195 +93,9 @@ export const retry = async <T>(
120
93
  return failure(lastError);
121
94
  };
122
95
 
123
- /**
124
- * Run promises in parallel and collect results
125
- * Fails if any promise fails
126
- */
127
- export const allAsync = async <T>(promises: AsyncResult<T, AppError>[]): AsyncResult<T[]> => {
128
- const results = await Promise.all(promises);
129
-
130
- const values: T[] = [];
131
- for (const result of results) {
132
- if (isSuccess(result)) {
133
- values.push(result.value);
134
- } else {
135
- return result;
136
- }
137
- }
138
-
139
- return success(values);
140
- };
141
-
142
- /**
143
- * Run promises in parallel and collect all results
144
- * Returns both successes and failures
145
- */
146
- export const allSettledAsync = async <T>(
147
- promises: AsyncResult<T, AppError>[]
148
- ): Promise<Result<T, AppError>[]> => {
149
- return Promise.all(promises);
150
- };
151
-
152
- /**
153
- * Run promises sequentially
154
- */
155
- export const sequenceAsync = async <T>(
156
- promises: Array<() => AsyncResult<T, AppError>>
157
- ): AsyncResult<T[]> => {
158
- const values: T[] = [];
159
-
160
- for (const promiseFn of promises) {
161
- const result = await promiseFn();
162
- if (isSuccess(result)) {
163
- values.push(result.value);
164
- } else {
165
- return result;
166
- }
167
- }
168
-
169
- return success(values);
170
- };
171
-
172
- /**
173
- * Race promises - return first to complete
174
- */
175
- export const raceAsync = async <T>(promises: AsyncResult<T, AppError>[]): AsyncResult<T> => {
176
- return Promise.race(promises);
177
- };
178
-
179
96
  /**
180
97
  * Delay execution
181
98
  */
182
99
  export const delay = (ms: number): Promise<void> => {
183
100
  return new Promise((resolve) => setTimeout(resolve, ms));
184
101
  };
185
-
186
- /**
187
- * Run with concurrency limit
188
- */
189
- export const withConcurrency = async <T>(
190
- tasks: Array<() => AsyncResult<T, AppError>>,
191
- concurrency: number
192
- ): AsyncResult<T[]> => {
193
- const results: T[] = [];
194
- const executing: Promise<void>[] = [];
195
-
196
- for (const task of tasks) {
197
- const promise = (async () => {
198
- const result = await task();
199
- if (isSuccess(result)) {
200
- results.push(result.value);
201
- } else {
202
- throw result.error;
203
- }
204
- })();
205
-
206
- executing.push(promise);
207
-
208
- if (executing.length >= concurrency) {
209
- await Promise.race(executing);
210
- executing.splice(executing.indexOf(promise), 1);
211
- }
212
- }
213
-
214
- try {
215
- await Promise.all(executing);
216
- return success(results);
217
- } catch (error) {
218
- return failure(toAppError(error));
219
- }
220
- };
221
-
222
- /**
223
- * Memoize async function
224
- */
225
- export const memoizeAsync = <Args extends any[], T>(
226
- fn: (...args: Args) => AsyncResult<T, AppError>,
227
- keyFn?: (...args: Args) => string
228
- ): ((...args: Args) => AsyncResult<T, AppError>) => {
229
- const cache = new Map<string, AsyncResult<T, AppError>>();
230
-
231
- return (...args: Args): AsyncResult<T, AppError> => {
232
- const key = keyFn ? keyFn(...args) : JSON.stringify(args);
233
-
234
- const cached = cache.get(key);
235
- if (cached !== undefined) {
236
- return cached;
237
- }
238
-
239
- const result = fn(...args);
240
- cache.set(key, result);
241
- return result;
242
- };
243
- };
244
-
245
- /**
246
- * Debounce async function
247
- */
248
- export const debounceAsync = <Args extends any[], T>(
249
- fn: (...args: Args) => AsyncResult<T, AppError>,
250
- delayMs: number
251
- ): ((...args: Args) => AsyncResult<T, AppError>) => {
252
- let timeoutId: NodeJS.Timeout | null = null;
253
- let latestArgs: Args | null = null;
254
- let latestPromise: AsyncResult<T, AppError> | null = null;
255
-
256
- return (...args: Args): AsyncResult<T, AppError> => {
257
- latestArgs = args;
258
-
259
- if (timeoutId) {
260
- clearTimeout(timeoutId);
261
- }
262
-
263
- if (!latestPromise) {
264
- latestPromise = new Promise((resolve) => {
265
- timeoutId = setTimeout(async () => {
266
- if (latestArgs) {
267
- const result = await fn(...latestArgs);
268
- resolve(result);
269
- latestPromise = null;
270
- timeoutId = null;
271
- }
272
- }, delayMs);
273
- }) as AsyncResult<T, AppError>;
274
- }
275
-
276
- return latestPromise;
277
- };
278
- };
279
-
280
- /**
281
- * Throttle async function
282
- */
283
- export const throttleAsync = <Args extends any[], T>(
284
- fn: (...args: Args) => AsyncResult<T, AppError>,
285
- limitMs: number
286
- ): ((...args: Args) => AsyncResult<T, AppError>) => {
287
- let lastRun = 0;
288
- let pending: AsyncResult<T, AppError> | null = null;
289
-
290
- return (...args: Args): AsyncResult<T, AppError> => {
291
- const now = Date.now();
292
-
293
- if (now - lastRun >= limitMs) {
294
- lastRun = now;
295
- return fn(...args);
296
- }
297
-
298
- if (!pending) {
299
- pending = new Promise((resolve) => {
300
- setTimeout(
301
- async () => {
302
- lastRun = Date.now();
303
- const result = await fn(...args);
304
- pending = null;
305
- resolve(result);
306
- },
307
- limitMs - (now - lastRun)
308
- );
309
- }) as AsyncResult<T, AppError>;
310
- }
311
-
312
- return pending;
313
- };
314
- };
@@ -4,10 +4,16 @@ import path from 'node:path';
4
4
  import chalk from 'chalk';
5
5
  import { installToDirectory } from '../core/installers/file-installer.js';
6
6
  import { createMCPInstaller } from '../core/installers/mcp-installer.js';
7
- import type { AgentMetadata } from '../types/target-config.types.js';
7
+ import type { AgentMetadata, FrontMatterMetadata } from '../types/target-config.types.js';
8
8
  import type { CommonOptions, MCPServerConfigUnion, SetupResult, Target } from '../types.js';
9
9
  import { getAgentsDir } from '../utils/config/paths.js';
10
- import { fileUtils, generateHelpText, pathUtils, yamlUtils } from '../utils/config/target-utils.js';
10
+ import {
11
+ type ConfigData,
12
+ fileUtils,
13
+ generateHelpText,
14
+ pathUtils,
15
+ yamlUtils,
16
+ } from '../utils/config/target-utils.js';
11
17
  import { CLIError } from '../utils/error-handler.js';
12
18
  import { sanitize } from '../utils/security/security.js';
13
19
  import {
@@ -17,6 +23,23 @@ import {
17
23
  transformMCPConfig as transformMCP,
18
24
  } from './shared/index.js';
19
25
 
26
+ /** Claude Code configuration data with MCP servers */
27
+ interface ClaudeCodeConfigData extends ConfigData {
28
+ mcpServers?: Record<string, unknown>;
29
+ }
30
+
31
+ /** Claude Code agent metadata */
32
+ interface ClaudeCodeAgentMetadata {
33
+ name: string;
34
+ description: string;
35
+ model?: string;
36
+ }
37
+
38
+ /** Error with exit code from child process */
39
+ interface ProcessExitError extends Error {
40
+ code: number | null;
41
+ }
42
+
20
43
  /**
21
44
  * Claude Code target - composition approach with all original functionality
22
45
  */
@@ -86,8 +109,11 @@ export const claudeCodeTarget: Target = {
86
109
  /**
87
110
  * Read Claude Code configuration with structure normalization
88
111
  */
89
- async readConfig(cwd: string): Promise<any> {
90
- const config = await fileUtils.readConfig(claudeCodeTarget.config, cwd);
112
+ async readConfig(cwd: string): Promise<ClaudeCodeConfigData> {
113
+ const config = (await fileUtils.readConfig(
114
+ claudeCodeTarget.config,
115
+ cwd
116
+ )) as ClaudeCodeConfigData;
91
117
 
92
118
  // Ensure the config has the expected structure for Claude Code
93
119
  if (!config.mcpServers) {
@@ -258,7 +284,7 @@ Please begin your response with a comprehensive summary of all the instructions
258
284
  if (code === 0) {
259
285
  resolve();
260
286
  } else {
261
- const error = new Error(`Claude Code exited with code ${code}`) as any;
287
+ const error = new Error(`Claude Code exited with code ${code}`) as ProcessExitError;
262
288
  error.code = code;
263
289
  reject(error);
264
290
  }
@@ -494,26 +520,29 @@ Please begin your response with a comprehensive summary of all the instructions
494
520
  * Convert OpenCode frontmatter to Claude Code format
495
521
  */
496
522
  function convertToClaudeCodeFormat(
497
- openCodeMetadata: any,
523
+ openCodeMetadata: FrontMatterMetadata,
498
524
  content: string,
499
525
  sourcePath?: string
500
- ): any {
526
+ ): ClaudeCodeAgentMetadata {
501
527
  // Use explicit name from metadata if available, otherwise extract from content or path
528
+ const metadataName = openCodeMetadata.name as string | undefined;
502
529
  const agentName =
503
- openCodeMetadata.name || pathUtils.extractAgentName(content, openCodeMetadata, sourcePath);
530
+ metadataName || pathUtils.extractAgentName(content, openCodeMetadata, sourcePath);
504
531
 
505
532
  // Extract description from metadata or content
506
- const description = openCodeMetadata.description || pathUtils.extractDescription(content);
533
+ const metadataDescription = openCodeMetadata.description as string | undefined;
534
+ const description = metadataDescription || pathUtils.extractDescription(content);
507
535
 
508
536
  // Only keep supported fields for Claude Code
509
- const result: any = {
537
+ const result: ClaudeCodeAgentMetadata = {
510
538
  name: agentName,
511
539
  description: description,
512
540
  };
513
541
 
514
542
  // Only add model if it exists and is not 'inherit' (default)
515
- if (openCodeMetadata.model && openCodeMetadata.model !== 'inherit') {
516
- result.model = openCodeMetadata.model;
543
+ const model = openCodeMetadata.model as string | undefined;
544
+ if (model && model !== 'inherit') {
545
+ result.model = model;
517
546
  }
518
547
 
519
548
  // Remove unsupported fields that might cause issues
@@ -49,12 +49,13 @@ export const loadSettings = async (
49
49
  const content = await fs.readFile(settingsPath, 'utf8');
50
50
  return JSON.parse(content) as ProjectSettings;
51
51
  },
52
- (error: any) => {
52
+ (error: unknown) => {
53
53
  // File not found is not an error - return empty settings
54
- if (error.code === 'ENOENT') {
54
+ const nodeError = error as { code?: string; message?: string };
55
+ if (nodeError.code === 'ENOENT') {
55
56
  return new Error('EMPTY_SETTINGS');
56
57
  }
57
- return new Error(`Failed to load settings: ${error.message}`);
58
+ return new Error(`Failed to load settings: ${nodeError.message ?? 'Unknown error'}`);
58
59
  }
59
60
  ).then((result) => {
60
61
  // Convert EMPTY_SETTINGS error to success with empty object
@@ -89,7 +90,10 @@ export const saveSettings = async (
89
90
  // Write settings with proper formatting
90
91
  await fs.writeFile(settingsPath, `${JSON.stringify(settingsWithVersion, null, 2)}\n`, 'utf8');
91
92
  },
92
- (error: any) => new Error(`Failed to save settings: ${error.message}`)
93
+ (error: unknown) => {
94
+ const nodeError = error as { message?: string };
95
+ return new Error(`Failed to save settings: ${nodeError.message ?? 'Unknown error'}`);
96
+ }
93
97
  );
94
98
  };
95
99
 
@@ -2,9 +2,13 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
4
4
  import type { MCPServerConfigUnion, TargetConfig } from '../../types.js';
5
+ import type { FrontMatterMetadata } from '../../types/target-config.types.js';
5
6
  import { readJSONCFile, writeJSONCFile } from '../files/jsonc.js';
6
7
  import { pathSecurity, sanitize } from '../security/security.js';
7
8
 
9
+ /** Configuration data structure returned by readConfig */
10
+ export type ConfigData = Record<string, unknown>;
11
+
8
12
  /**
9
13
  * File system utilities for targets
10
14
  */
@@ -17,7 +21,7 @@ export const fileUtils = {
17
21
  return pathSecurity.safeJoin(cwd, configFileName);
18
22
  },
19
23
 
20
- async readConfig(config: TargetConfig, cwd: string): Promise<any> {
24
+ async readConfig(config: TargetConfig, cwd: string): Promise<ConfigData> {
21
25
  const configPath = fileUtils.getConfigPath(config, cwd);
22
26
 
23
27
  try {
@@ -40,7 +44,7 @@ export const fileUtils = {
40
44
  throw new Error(`Unsupported config file format: ${config.configFile}`);
41
45
  },
42
46
 
43
- async writeConfig(config: TargetConfig, cwd: string, data: any): Promise<void> {
47
+ async writeConfig(config: TargetConfig, cwd: string, data: ConfigData): Promise<void> {
44
48
  const configPath = fileUtils.getConfigPath(config, cwd);
45
49
 
46
50
  await fs.mkdir(path.dirname(configPath), { recursive: true });
@@ -91,7 +95,7 @@ export const fileUtils = {
91
95
  * YAML utilities for targets
92
96
  */
93
97
  export const yamlUtils = {
94
- async extractFrontMatter(content: string): Promise<{ metadata: any; content: string }> {
98
+ async extractFrontMatter(content: string): Promise<{ metadata: FrontMatterMetadata; content: string }> {
95
99
  const yamlRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
96
100
  const match = content.match(yamlRegex);
97
101
 
@@ -111,7 +115,7 @@ export const yamlUtils = {
111
115
  return { metadata: {}, content };
112
116
  },
113
117
 
114
- async addFrontMatter(content: string, metadata: any): Promise<string> {
118
+ async addFrontMatter(content: string, metadata: FrontMatterMetadata): Promise<string> {
115
119
  if (!metadata || Object.keys(metadata).length === 0) {
116
120
  return content;
117
121
  }
@@ -136,14 +140,14 @@ export const yamlUtils = {
136
140
  return yamlRegex.test(content);
137
141
  },
138
142
 
139
- async ensureFrontMatter(content: string, defaultMetadata: any = {}): Promise<string> {
143
+ async ensureFrontMatter(content: string, defaultMetadata: FrontMatterMetadata = {}): Promise<string> {
140
144
  if (yamlUtils.hasValidFrontMatter(content)) {
141
145
  return content;
142
146
  }
143
147
  return yamlUtils.addFrontMatter(content, defaultMetadata);
144
148
  },
145
149
 
146
- async extractAgentMetadata(content: string): Promise<any> {
150
+ async extractAgentMetadata(content: string): Promise<FrontMatterMetadata> {
147
151
  const { metadata } = await yamlUtils.extractFrontMatter(content);
148
152
 
149
153
  if (typeof metadata === 'string') {
@@ -157,14 +161,14 @@ export const yamlUtils = {
157
161
  return metadata || {};
158
162
  },
159
163
 
160
- async updateAgentMetadata(content: string, updates: any): Promise<string> {
164
+ async updateAgentMetadata(content: string, updates: Partial<FrontMatterMetadata>): Promise<string> {
161
165
  const { metadata: existingMetadata, content: baseContent } =
162
166
  await yamlUtils.extractFrontMatter(content);
163
167
  const updatedMetadata = { ...existingMetadata, ...updates };
164
168
  return yamlUtils.addFrontMatter(baseContent, updatedMetadata);
165
169
  },
166
170
 
167
- validateClaudeCodeFrontMatter(metadata: any): boolean {
171
+ validateClaudeCodeFrontMatter(metadata: unknown): metadata is FrontMatterMetadata {
168
172
  if (typeof metadata !== 'object' || metadata === null) {
169
173
  return false;
170
174
  }
@@ -183,7 +187,7 @@ export const yamlUtils = {
183
187
  return true;
184
188
  },
185
189
 
186
- normalizeClaudeCodeFrontMatter(metadata: any): any {
190
+ normalizeClaudeCodeFrontMatter(metadata: FrontMatterMetadata): FrontMatterMetadata {
187
191
  const normalized = { ...metadata };
188
192
 
189
193
  if (normalized.tools && typeof normalized.tools === 'string') {
@@ -276,7 +280,7 @@ export const pathUtils = {
276
280
  return kebabName || null;
277
281
  },
278
282
 
279
- extractAgentName(content: string, metadata: any, sourcePath?: string): string {
283
+ extractAgentName(content: string, metadata: FrontMatterMetadata, sourcePath?: string): string {
280
284
  // Try to extract from file path first
281
285
  if (sourcePath) {
282
286
  const pathName = pathUtils.extractNameFromPath(sourcePath);
@@ -363,13 +367,13 @@ ${basePrompt}`;
363
367
  export const transformUtils = {
364
368
  defaultTransformAgentContent(
365
369
  content: string,
366
- _metadata?: any,
370
+ _metadata?: FrontMatterMetadata,
367
371
  _sourcePath?: string
368
372
  ): Promise<string> {
369
373
  return Promise.resolve(content);
370
374
  },
371
375
 
372
- defaultTransformMCPConfig(config: MCPServerConfigUnion): any {
376
+ defaultTransformMCPConfig(config: MCPServerConfigUnion): MCPServerConfigUnion {
373
377
  return config;
374
378
  },
375
379
  };
@@ -21,16 +21,16 @@ function stripComments(content: string): string {
21
21
  /**
22
22
  * Read and parse JSONC file
23
23
  */
24
- export async function readJSONCFile(filePath: string): Promise<any> {
24
+ export async function readJSONCFile<T = unknown>(filePath: string): Promise<T> {
25
25
  const content = await fs.readFile(filePath, 'utf-8');
26
26
  const stripped = stripComments(content);
27
- return JSON.parse(stripped);
27
+ return JSON.parse(stripped) as T;
28
28
  }
29
29
 
30
30
  /**
31
31
  * Write JSONC file (writes as regular JSON)
32
32
  */
33
- export async function writeJSONCFile(filePath: string, data: any): Promise<void> {
33
+ export async function writeJSONCFile<T>(filePath: string, data: T): Promise<void> {
34
34
  const content = JSON.stringify(data, null, 2);
35
35
  await fs.writeFile(filePath, content, 'utf-8');
36
36
  }
@@ -10,8 +10,12 @@ import { UserCancelledError } from './errors.js';
10
10
  * @param error - Error to check
11
11
  * @returns True if error represents user cancellation
12
12
  */
13
- export function isUserCancellation(error: any): boolean {
14
- return error?.name === 'ExitPromptError' || error?.message?.includes('force closed');
13
+ export function isUserCancellation(error: unknown): boolean {
14
+ if (error === null || typeof error !== 'object') {
15
+ return false;
16
+ }
17
+ const errorObj = error as { name?: string; message?: string };
18
+ return errorObj.name === 'ExitPromptError' || errorObj.message?.includes('force closed') === true;
15
19
  }
16
20
 
17
21
  /**
@@ -22,7 +26,7 @@ export function isUserCancellation(error: any): boolean {
22
26
  * @throws {UserCancelledError} If user cancelled the prompt
23
27
  * @throws Original error if not a cancellation
24
28
  */
25
- export function handlePromptError(error: any, message: string): never {
29
+ export function handlePromptError(error: unknown, message: string): never {
26
30
  if (isUserCancellation(error)) {
27
31
  throw new UserCancelledError(message);
28
32
  }
@@ -489,46 +489,6 @@ export class RateLimiter {
489
489
  }
490
490
  }
491
491
 
492
- // ============================================================================
493
- // SECURITY MIDDLEWARE
494
- // ============================================================================
495
-
496
- /**
497
- * Security middleware for common patterns
498
- */
499
- export const securityMiddleware = {
500
- /**
501
- * Rate limiting middleware
502
- */
503
- rateLimit: (limiter: RateLimiter, getIdentifier: (req: any) => string) => {
504
- return (req: any, res: any, next: any) => {
505
- const identifier = getIdentifier(req);
506
-
507
- if (!limiter.isAllowed(identifier)) {
508
- return res.status(429).json({ error: 'Too many requests' });
509
- }
510
-
511
- next();
512
- };
513
- },
514
-
515
- /**
516
- * Input validation middleware
517
- */
518
- validateInput: (schema: z.ZodSchema, source: 'body' | 'query' | 'params' = 'body') => {
519
- return (req: any, res: any, next: any) => {
520
- try {
521
- const data = req[source];
522
- const validated = schema.parse(data);
523
- req[source] = validated;
524
- next();
525
- } catch (error) {
526
- return res.status(400).json({ error: 'Invalid input', details: error });
527
- }
528
- };
529
- },
530
- };
531
-
532
492
  export default {
533
493
  securitySchemas,
534
494
  pathSecurity,
@@ -537,5 +497,4 @@ export default {
537
497
  envSecurity,
538
498
  cryptoUtils,
539
499
  RateLimiter,
540
- securityMiddleware,
541
500
  };