@sylphx/flow 3.3.0 → 3.5.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.5.0 (2026-01-28)
4
+
5
+ ### ✨ Features
6
+
7
+ - add /excellence command, rename /review to /challenge ([533c0d0](https://github.com/SylphxAI/flow/commit/533c0d016380d7b37680633d6773792030b07753))
8
+
9
+ ### ♻️ Refactoring
10
+
11
+ - rename /review to /challenge ([ec3d9b5](https://github.com/SylphxAI/flow/commit/ec3d9b5510dc76337e64711aea614b00c2ba8de9))
12
+
13
+ ## 3.4.0 (2026-01-26)
14
+
15
+ ### ✨ Features
16
+
17
+ - add /e2e-audit and business logic to /audit ([8e87e10](https://github.com/SylphxAI/flow/commit/8e87e10b49eaec37bb85c2677a0d8b1f602c5584))
18
+
19
+ ### ♻️ Refactoring
20
+
21
+ - replace any with proper types in claude-code.ts ([d498d41](https://github.com/SylphxAI/flow/commit/d498d41dcae95cc9d98628f7860be2f48b7c342e))
22
+ - replace any with proper types in target-utils.ts ([aebbf5d](https://github.com/SylphxAI/flow/commit/aebbf5dad8b4705664db1784127a4af0da8f8e5c))
23
+ - remove unused securityMiddleware from security.ts ([a8efc37](https://github.com/SylphxAI/flow/commit/a8efc37fd06db866b5a08715a5141c19fbb3f54e))
24
+ - add generics to jsonc.ts functions ([aac10af](https://github.com/SylphxAI/flow/commit/aac10afa3aa8cbd5c0319932c09d34e7a7ca4fec))
25
+ - replace any with unknown in settings.ts ([8b76e17](https://github.com/SylphxAI/flow/commit/8b76e179ffc6f4533dbc5b58894cb1c0b468f83a))
26
+ - replace any with unknown in prompt-helpers ([56e2b37](https://github.com/SylphxAI/flow/commit/56e2b375992232395ea58682ef7b04d396321b25))
27
+ - remove unused FlowOptions properties ([273f521](https://github.com/SylphxAI/flow/commit/273f521a37e6b63f64b3fe6667f7489681bcb9fa))
28
+ - remove unused async utility functions ([3b2c8fd](https://github.com/SylphxAI/flow/commit/3b2c8fdb6ea997e195a15e19bdd0ab931fae0f15))
29
+
3
30
  ## 3.3.0 (2026-01-26)
4
31
 
5
32
  ### ✨ Features
@@ -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
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: review
2
+ name: challenge
3
3
  description: Challenge your work - is it truly state-of-the-art?
4
4
  ---
5
5
 
6
- # Review: Ruthless Self-Critique
6
+ # Challenge: Ruthless Self-Critique
7
7
 
8
8
  Stop. Step back. Challenge everything you just did.
9
9
 
@@ -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
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: excellence
3
+ description: Is this project truly State of the Art?
4
+ ---
5
+
6
+ # Excellence: Is This State of the Art?
7
+
8
+ You are a world-class expert and harsh critic. Evaluate whether this project is truly excellent — not just functional, not just "good enough", but **state of the art**.
9
+
10
+ ## Mindset
11
+
12
+ Don't use checklist thinking. Ask yourself:
13
+
14
+ > "If I showed this project to the top experts in the industry, what would their reaction be?"
15
+
16
+ - Would they be **impressed**?
17
+ - Would they find it **mediocre**?
18
+ - Would they spot **obvious gaps**?
19
+
20
+ Based on this specific project's nature, determine what "excellence" means here.
21
+
22
+ ## Evaluation Dimensions
23
+
24
+ ### Architecture
25
+ - Is the architecture cutting-edge or outdated?
26
+ - Would system design experts approve?
27
+ - Is it scalable, maintainable, elegant?
28
+ - Are the abstractions right — not too much, not too little?
29
+
30
+ ### Code Quality
31
+ - Could this code be used as teaching material?
32
+ - Is it exemplary or just acceptable?
33
+ - Would senior engineers at top companies approve?
34
+ - Is it clean, readable, well-structured?
35
+
36
+ ### Business Logic & Design
37
+ - Does the business flow make sense?
38
+ - Are there logical gaps or inconsistencies?
39
+ - Is the product design sound?
40
+ - Would domain experts find issues?
41
+
42
+ ### User Experience
43
+ - Is the UX **delightful** or just **functional**?
44
+ - Would users choose this over competitors?
45
+ - Is it intuitive, fast, polished?
46
+ - Are edge cases handled gracefully?
47
+
48
+ ### Performance
49
+ - Is it best-in-class or just acceptable?
50
+ - Are there obvious bottlenecks?
51
+ - Would performance engineers approve?
52
+
53
+ ### Public-Facing (Docs, README, Marketing)
54
+ - Is the messaging compelling?
55
+ - Would it attract users/contributors?
56
+ - Is documentation clear and complete?
57
+ - Does it convey professionalism and quality?
58
+
59
+ ## Process
60
+
61
+ 1. **Explore** the project deeply — architecture, code, UI, docs
62
+ 2. **Evaluate** each dimension with fresh, critical eyes
63
+ 3. **Compare** mentally to best-in-class examples
64
+ 4. **Judge** honestly — would you stake your reputation on this?
65
+ 5. **Report** findings with specific observations
66
+
67
+ ## Output
68
+
69
+ ### Overall Verdict
70
+ ```
71
+ [ ] World-Class — Experts would be impressed
72
+ [ ] Strong — Good, but room for improvement
73
+ [ ] Acceptable — Works, but not exceptional
74
+ [ ] Needs Work — Obvious gaps exist
75
+ ```
76
+
77
+ ### Dimension Scores
78
+ | Dimension | Rating | Key Observations |
79
+ |-----------|--------|------------------|
80
+ | Architecture | ⭐⭐⭐⭐⭐ | ... |
81
+ | Code Quality | ⭐⭐⭐⭐☆ | ... |
82
+ | Business Logic | ⭐⭐⭐☆☆ | ... |
83
+ | UX | ⭐⭐⭐⭐☆ | ... |
84
+ | Performance | ⭐⭐⭐⭐⭐ | ... |
85
+ | Public-Facing | ⭐⭐⭐☆☆ | ... |
86
+
87
+ ### What's Excellent
88
+ - ...
89
+
90
+ ### What's Holding It Back
91
+ - ...
92
+
93
+ ### To Reach World-Class
94
+ - ...
95
+
96
+ ## Remember
97
+
98
+ - Excellence is contextual — judge based on what this project aims to be
99
+ - Be specific — vague praise or criticism is useless
100
+ - Be honest — if it's mediocre, say so
101
+ - Think holistically — a project with great code but terrible UX is not excellent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/flow",
3
- "version": "3.3.0",
3
+ "version": "3.5.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
  };