@sylphx/flow 2.18.1 → 2.18.2

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,13 @@
1
1
  # @sylphx/flow
2
2
 
3
+ ## 2.18.2 (2025-12-18)
4
+
5
+ ### ♻️ Refactoring
6
+
7
+ - **commands:** align /review with /continue - skills as step 1 ([b700fa5](https://github.com/SylphxAI/flow/commit/b700fa5272d6a656cd88acf94e3851cf31d29727))
8
+ - **commands:** simplify /continue - half the length, skills in execution flow ([b04facb](https://github.com/SylphxAI/flow/commit/b04facb60138155b0e203ac6fbf3f03d5252f226))
9
+ - **core:** remove dead code and consolidate duplications ([ee54d3f](https://github.com/SylphxAI/flow/commit/ee54d3f885f8e7a7011e39d95e5403c70fa595be))
10
+
3
11
  ## 2.18.1 (2025-12-18)
4
12
 
5
13
  ### 🐛 Bug Fixes
@@ -10,85 +10,40 @@ Find what's incomplete. Finish it.
10
10
 
11
11
  ## Mandate
12
12
 
13
- * **Think, don't checklist.** Understand the project first. What is it trying to do? What would "done" look like?
14
- * **Delegate workers** for parallel research. You synthesize and verify.
15
- * **Fix, don't report.** Implement solutions directly.
16
- * **One pass.** No deferrals. Complete each fix fully.
17
-
18
- ## How to Find Incomplete Work
19
-
20
- Don't grep for TODO and call it done. Incomplete work hides in:
21
-
22
- **What's explicitly unfinished** — Yes, scan for TODO/FIXME/HACK. But ask: why are they there? What was the person avoiding?
23
-
24
- **What's implicitly broken** — Code that "works" but:
25
- - Fails silently (empty catch blocks, swallowed errors)
26
- - Works only in happy path (no validation, no edge cases)
27
- - Works but confuses users (unclear errors, missing feedback)
28
- - Works but can't be debugged (no logs, no context)
29
-
30
- **What's missing entirely** — Features referenced but not implemented. UI that leads nowhere. Promises in docs that code doesn't deliver.
31
-
32
- ## The Real Test
33
-
34
- For each part of the system, ask:
35
-
36
- > "If I were a user trying to accomplish their goal, where would I get stuck?"
37
-
38
- > "If this broke at 3am, could someone figure out why?"
39
-
40
- > "If requirements changed tomorrow, what would be painful to modify?"
41
-
42
- > "If we had 100x traffic, what would fall over first?"
43
-
44
- These questions reveal incompleteness that checklists miss.
13
+ - **Think, don't checklist.** Understand the project. What would "done" look like?
14
+ - **Delegate workers** for parallel research. You synthesize and verify.
15
+ - **Fix, don't report.** Implement solutions directly.
45
16
 
46
17
  ## Execution
47
18
 
48
- 1. **Understand the project** — Read README, main entry points, core flows. What is this thing supposed to do?
19
+ 1. **Understand** — Read README, entry points, core flows
49
20
 
50
- 2. **Walk the critical paths** — Trace actual user journeys through code. Where does the path get uncertain, error-prone, or incomplete?
21
+ 2. **Find gaps** — Not just TODO/FIXME, but:
22
+ - Implicit broken (empty catch, happy path only, no logs)
23
+ - Missing entirely (referenced but not implemented)
51
24
 
52
- 3. **Find the gaps** — Not just TODOs, but:
53
- - Dead ends (code that starts something but doesn't finish)
54
- - Weak spots (minimal implementation that will break under pressure)
55
- - Missing pieces (what's referenced but doesn't exist)
25
+ 3. **Invoke skills** — Before fixing, load guidelines for relevant domains:
26
+ ```
27
+ auth, account-security, billing, security, database, performance, observability...
28
+ ```
29
+ Skills contain: tech stack decisions, non-negotiables, patterns, anti-patterns.
56
30
 
57
- 4. **Prioritize by impact** — What blocks users? What causes data loss? What makes debugging impossible? Fix those first.
31
+ 4. **Fix by impact** — What blocks users? Causes data loss? Fix those first. Fix completely.
58
32
 
59
- 5. **Fix completely** — Don't patch. Understand root cause. Implement proper solution. Test it works.
60
-
61
- ## Skills (Guidelines)
62
-
63
- **Skills contain implementation guidelines** — tech stack decisions, non-negotiables, patterns, anti-patterns for each domain.
64
-
65
- Available skills:
66
- auth, account-security, privacy, billing, pricing, ledger, security, trust-safety, uiux, seo, pwa, performance, i18n, database, data-architecture, storage, observability, operability, delivery, growth, referral, support, admin, discovery, code-quality
67
-
68
- **You MUST invoke relevant skills** using the Skill tool before fixing. This loads the guidelines for that domain.
69
-
70
- ## Loop
71
-
72
- After fixing: "Did my fixes introduce new gaps? Did fixing X reveal Y was also broken?"
73
-
74
- If yes → run `/continue` again. If no Critical/High issues remain → done.
33
+ 5. **Loop** — New gaps from fixes? `/continue` again
75
34
 
76
35
  ## Output
77
36
 
78
37
  ```
79
- ## What I Found
80
-
81
- [Describe the gaps discovered — not a checklist, but an understanding of what's incomplete and why]
82
-
83
- ## What I Fixed
38
+ ## Found
39
+ [What's incomplete and why]
84
40
 
85
- - [Description of fix and why it matters]
41
+ ## Fixed
42
+ [Changes made]
86
43
 
87
- ## What Remains
88
-
89
- - [Issues that need human decision or are blocked]
44
+ ## Remains
45
+ [Needs human decision]
90
46
 
91
47
  ## Next
92
-
93
- [/continue again | done]
48
+ [/continue | done]
94
49
  ```
@@ -12,19 +12,23 @@ args:
12
12
 
13
13
  ## Mandate
14
14
 
15
- * **Understand first.** Absorb the principles, then apply judgment.
16
- * **Think like the failure mode.** Security? Think like an attacker. Performance? Think like a slow network. Auth? Think like a confused user.
17
- * **Delegate workers** for parallel research. You synthesize and verify.
18
- * **Fix, don't report.** Implement solutions directly.
15
+ - **Think like the failure mode.** Security attacker. Performance slow network. Auth → confused user.
16
+ - **Delegate workers** for parallel research. You synthesize and verify.
17
+ - **Fix, don't report.** Implement solutions directly.
19
18
 
20
- ## Skills (Guidelines)
19
+ ## Execution
21
20
 
22
- **Skills contain implementation guidelines** — tech stack decisions, non-negotiables, patterns, anti-patterns for each domain.
21
+ 1. **Invoke skills** — Load guidelines for relevant domains:
22
+ ```
23
+ auth, account-security, billing, security, database, performance, observability...
24
+ ```
25
+ Skills contain: tech stack decisions, non-negotiables, patterns, anti-patterns.
23
26
 
24
- Available skills:
25
- auth, account-security, privacy, billing, pricing, ledger, security, trust-safety, uiux, seo, pwa, performance, i18n, database, data-architecture, storage, observability, operability, delivery, growth, referral, support, admin, discovery, code-quality
27
+ 2. **Understand** — How is this implemented? Architecture, choices, tradeoffs.
26
28
 
27
- **You MUST invoke relevant skills** using the Skill tool before reviewing. This loads the guidelines for that domain.
29
+ 3. **Find issues** What violates the guidelines? What's wrong and why it matters?
30
+
31
+ 4. **Fix** — Implement solutions directly.
28
32
 
29
33
  ## Output
30
34
 
@@ -32,14 +36,14 @@ auth, account-security, privacy, billing, pricing, ledger, security, trust-safet
32
36
  ## Review: [topic]
33
37
 
34
38
  ### Understanding
35
- [How this is implemented. Architecture, choices, tradeoffs.]
39
+ [Architecture, choices, tradeoffs]
36
40
 
37
41
  ### Issues
38
- [What's wrong and why it matters]
42
+ [What's wrong and why]
39
43
 
40
44
  ### Fixed
41
45
  [Changes made]
42
46
 
43
- ### Remaining
44
- [Needs human decision or blocked]
47
+ ### Remains
48
+ [Needs human decision]
45
49
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/flow",
3
- "version": "2.18.1",
3
+ "version": "2.18.2",
4
4
  "description": "One CLI to rule them all. Unified orchestration layer for Claude Code, OpenCode, Cursor and all AI development tools. Auto-detection, auto-installation, auto-upgrade.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,7 @@ import fs from 'node:fs/promises';
9
9
  import path from 'node:path';
10
10
  import type { Target } from '../types/target.types.js';
11
11
  import type { ProjectManager } from './project-manager.js';
12
- import { targetManager } from './target-manager.js';
12
+ import { resolveTarget, resolveTargetOrId } from './target-resolver.js';
13
13
 
14
14
  export interface BackupInfo {
15
15
  sessionId: string;
@@ -69,17 +69,6 @@ export class BackupManager {
69
69
  this.projectManager = projectManager;
70
70
  }
71
71
 
72
- /**
73
- * Resolve target from ID string to Target object
74
- */
75
- private resolveTarget(targetId: string): Target {
76
- const targetOption = targetManager.getTarget(targetId);
77
- if (targetOption._tag === 'None') {
78
- throw new Error(`Unknown target: ${targetId}`);
79
- }
80
- return targetOption.value;
81
- }
82
-
83
72
  /**
84
73
  * Create full backup of project environment
85
74
  */
@@ -88,7 +77,7 @@ export class BackupManager {
88
77
  projectHash: string,
89
78
  targetOrId: Target | string
90
79
  ): Promise<BackupInfo> {
91
- const target = typeof targetOrId === 'string' ? this.resolveTarget(targetOrId) : targetOrId;
80
+ const target = resolveTargetOrId(targetOrId);
92
81
  const targetId = target.id;
93
82
  const sessionId = `session-${Date.now()}`;
94
83
  const timestamp = new Date().toISOString();
@@ -178,7 +167,7 @@ export class BackupManager {
178
167
  const targetId = manifest.target;
179
168
 
180
169
  // Resolve target to get config
181
- const target = this.resolveTarget(targetId);
170
+ const target = resolveTarget(targetId);
182
171
 
183
172
  // Get target config directory
184
173
  const targetConfigDir = this.projectManager.getTargetConfigDir(projectPath, target);
@@ -9,7 +9,7 @@ import fs from 'node:fs/promises';
9
9
  import path from 'node:path';
10
10
  import type { Target } from '../types/target.types.js';
11
11
  import type { ProjectManager } from './project-manager.js';
12
- import { targetManager } from './target-manager.js';
12
+ import { resolveTargetOrId } from './target-resolver.js';
13
13
 
14
14
  export interface MCPSecrets {
15
15
  version: string;
@@ -30,17 +30,6 @@ export class SecretsManager {
30
30
  this.projectManager = projectManager;
31
31
  }
32
32
 
33
- /**
34
- * Resolve target from ID string to Target object
35
- */
36
- private resolveTarget(targetId: string): Target {
37
- const targetOption = targetManager.getTarget(targetId);
38
- if (targetOption._tag === 'None') {
39
- throw new Error(`Unknown target: ${targetId}`);
40
- }
41
- return targetOption.value;
42
- }
43
-
44
33
  /**
45
34
  * Extract MCP secrets from project config
46
35
  */
@@ -49,7 +38,7 @@ export class SecretsManager {
49
38
  _projectHash: string,
50
39
  targetOrId: Target | string
51
40
  ): Promise<MCPSecrets> {
52
- const target = typeof targetOrId === 'string' ? this.resolveTarget(targetOrId) : targetOrId;
41
+ const target = resolveTargetOrId(targetOrId);
53
42
  // configFile is at project root, not in targetDir
54
43
  const configPath = path.join(projectPath, target.config.configFile);
55
44
  const mcpPath = target.config.mcpConfigPath;
@@ -145,7 +134,7 @@ export class SecretsManager {
145
134
  return;
146
135
  }
147
136
 
148
- const target = typeof targetOrId === 'string' ? this.resolveTarget(targetOrId) : targetOrId;
137
+ const target = resolveTargetOrId(targetOrId);
149
138
  // configFile is at project root, not in targetDir
150
139
  const configPath = path.join(projectPath, target.config.configFile);
151
140
  const mcpPath = target.config.mcpConfigPath;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Target Resolver
3
+ * Shared utility for resolving target IDs to Target objects
4
+ * Eliminates duplication across BackupManager and SecretsManager
5
+ */
6
+
7
+ import type { Target } from '../types/target.types.js';
8
+ import { targetManager } from './target-manager.js';
9
+
10
+ /**
11
+ * Resolve a target from ID string to Target object
12
+ * @throws Error if target ID is not found
13
+ */
14
+ export function resolveTarget(targetId: string): Target {
15
+ const targetOption = targetManager.getTarget(targetId);
16
+ if (targetOption._tag === 'None') {
17
+ throw new Error(`Unknown target: ${targetId}`);
18
+ }
19
+ return targetOption.value;
20
+ }
21
+
22
+ /**
23
+ * Resolve target, accepting either string ID or Target object
24
+ * Returns the Target object in both cases
25
+ */
26
+ export function resolveTargetOrId(targetOrId: Target | string): Target {
27
+ return typeof targetOrId === 'string' ? resolveTarget(targetOrId) : targetOrId;
28
+ }
@@ -37,13 +37,9 @@ export * from './error-handler.js';
37
37
  export * from './files/file-operations.js';
38
38
  export * from './files/sync-utils.js';
39
39
  // ============================================================================
40
- // FUNCTIONAL PROGRAMMING
41
- // ============================================================================
42
- export * from './functional.js';
43
- export * from './security/secret-utils.js';
44
- // ============================================================================
45
40
  // SECURITY
46
41
  // ============================================================================
42
+ export * from './security/secret-utils.js';
47
43
  export * from './security/security.js';
48
44
  // ============================================================================
49
45
  // VERSIONING
@@ -1,355 +0,0 @@
1
- /**
2
- * Functional array utilities
3
- * Pure array transformation functions
4
- *
5
- * DESIGN RATIONALE:
6
- * - Pure functions for array operations
7
- * - Composable transformations
8
- * - Type-safe operations
9
- * - No side effects (no mutations)
10
- */
11
-
12
- /**
13
- * Map over array
14
- */
15
- export const map =
16
- <T, U>(fn: (item: T, index: number) => U) =>
17
- (arr: T[]): U[] =>
18
- arr.map(fn);
19
-
20
- /**
21
- * Filter array
22
- */
23
- export const filter =
24
- <T>(predicate: (item: T, index: number) => boolean) =>
25
- (arr: T[]): T[] =>
26
- arr.filter(predicate);
27
-
28
- /**
29
- * Reduce array
30
- */
31
- export const reduce =
32
- <T, U>(fn: (acc: U, item: T, index: number) => U, initial: U) =>
33
- (arr: T[]): U =>
34
- arr.reduce(fn, initial);
35
-
36
- /**
37
- * Find first element matching predicate
38
- */
39
- export const find =
40
- <T>(predicate: (item: T, index: number) => boolean) =>
41
- (arr: T[]): T | undefined =>
42
- arr.find(predicate);
43
-
44
- /**
45
- * Find index of first element matching predicate
46
- */
47
- export const findIndex =
48
- <T>(predicate: (item: T, index: number) => boolean) =>
49
- (arr: T[]): number =>
50
- arr.findIndex(predicate);
51
-
52
- /**
53
- * Check if any element matches predicate
54
- */
55
- export const some =
56
- <T>(predicate: (item: T, index: number) => boolean) =>
57
- (arr: T[]): boolean =>
58
- arr.some(predicate);
59
-
60
- /**
61
- * Check if all elements match predicate
62
- */
63
- export const every =
64
- <T>(predicate: (item: T, index: number) => boolean) =>
65
- (arr: T[]): boolean =>
66
- arr.every(predicate);
67
-
68
- /**
69
- * Flatten array one level
70
- */
71
- export const flatten = <T>(arr: T[][]): T[] => arr.flat();
72
-
73
- /**
74
- * Flatten array deeply
75
- */
76
- export const flattenDeep = <T>(arr: any[]): T[] => arr.flat(Number.POSITIVE_INFINITY);
77
-
78
- /**
79
- * Map and flatten (flatMap)
80
- */
81
- export const flatMap =
82
- <T, U>(fn: (item: T, index: number) => U[]) =>
83
- (arr: T[]): U[] =>
84
- arr.flatMap(fn);
85
-
86
- /**
87
- * Take first n elements
88
- */
89
- export const take =
90
- (n: number) =>
91
- <T>(arr: T[]): T[] =>
92
- arr.slice(0, n);
93
-
94
- /**
95
- * Skip first n elements
96
- */
97
- export const skip =
98
- (n: number) =>
99
- <T>(arr: T[]): T[] =>
100
- arr.slice(n);
101
-
102
- /**
103
- * Take while predicate is true
104
- */
105
- export const takeWhile =
106
- <T>(predicate: (item: T) => boolean) =>
107
- (arr: T[]): T[] => {
108
- const index = arr.findIndex((item) => !predicate(item));
109
- return index === -1 ? arr : arr.slice(0, index);
110
- };
111
-
112
- /**
113
- * Skip while predicate is true
114
- */
115
- export const skipWhile =
116
- <T>(predicate: (item: T) => boolean) =>
117
- (arr: T[]): T[] => {
118
- const index = arr.findIndex((item) => !predicate(item));
119
- return index === -1 ? [] : arr.slice(index);
120
- };
121
-
122
- /**
123
- * Reverse array
124
- */
125
- export const reverse = <T>(arr: T[]): T[] => [...arr].reverse();
126
-
127
- /**
128
- * Sort array
129
- */
130
- export const sort =
131
- <T>(compareFn?: (a: T, b: T) => number) =>
132
- (arr: T[]): T[] =>
133
- [...arr].sort(compareFn);
134
-
135
- /**
136
- * Sort by key
137
- */
138
- export const sortBy =
139
- <T, K extends keyof T>(key: K, order: 'asc' | 'desc' = 'asc') =>
140
- (arr: T[]): T[] => {
141
- return [...arr].sort((a, b) => {
142
- const aVal = a[key];
143
- const bVal = b[key];
144
-
145
- if (aVal < bVal) {
146
- return order === 'asc' ? -1 : 1;
147
- }
148
- if (aVal > bVal) {
149
- return order === 'asc' ? 1 : -1;
150
- }
151
- return 0;
152
- });
153
- };
154
-
155
- /**
156
- * Remove duplicates
157
- */
158
- export const unique = <T>(arr: T[]): T[] => [...new Set(arr)];
159
-
160
- /**
161
- * Remove duplicates by key
162
- */
163
- export const uniqueBy =
164
- <T, K extends keyof T>(key: K) =>
165
- (arr: T[]): T[] => {
166
- const seen = new Set();
167
- return arr.filter((item) => {
168
- const value = item[key];
169
- if (seen.has(value)) {
170
- return false;
171
- }
172
- seen.add(value);
173
- return true;
174
- });
175
- };
176
-
177
- /**
178
- * Partition array into two based on predicate
179
- */
180
- export const partition =
181
- <T>(predicate: (item: T) => boolean) =>
182
- (arr: T[]): [T[], T[]] => {
183
- const pass: T[] = [];
184
- const fail: T[] = [];
185
-
186
- for (const item of arr) {
187
- if (predicate(item)) {
188
- pass.push(item);
189
- } else {
190
- fail.push(item);
191
- }
192
- }
193
-
194
- return [pass, fail];
195
- };
196
-
197
- /**
198
- * Group by key
199
- */
200
- export const groupBy =
201
- <T, K extends keyof T>(key: K) =>
202
- (arr: T[]): Record<string, T[]> => {
203
- return arr.reduce(
204
- (acc, item) => {
205
- const groupKey = String(item[key]);
206
- if (!acc[groupKey]) {
207
- acc[groupKey] = [];
208
- }
209
- acc[groupKey].push(item);
210
- return acc;
211
- },
212
- {} as Record<string, T[]>
213
- );
214
- };
215
-
216
- /**
217
- * Count occurrences
218
- */
219
- export const countBy =
220
- <T>(fn: (item: T) => string) =>
221
- (arr: T[]): Record<string, number> => {
222
- return arr.reduce(
223
- (acc, item) => {
224
- const key = fn(item);
225
- acc[key] = (acc[key] || 0) + 1;
226
- return acc;
227
- },
228
- {} as Record<string, number>
229
- );
230
- };
231
-
232
- /**
233
- * Chunk array into smaller arrays
234
- */
235
- export const chunk =
236
- (size: number) =>
237
- <T>(arr: T[]): T[][] => {
238
- const chunks: T[][] = [];
239
- for (let i = 0; i < arr.length; i += size) {
240
- chunks.push(arr.slice(i, i + size));
241
- }
242
- return chunks;
243
- };
244
-
245
- /**
246
- * Zip arrays together
247
- */
248
- export const zip = <T, U>(arr1: T[], arr2: U[]): [T, U][] => {
249
- const length = Math.min(arr1.length, arr2.length);
250
- const result: [T, U][] = [];
251
-
252
- for (let i = 0; i < length; i++) {
253
- result.push([arr1[i], arr2[i]]);
254
- }
255
-
256
- return result;
257
- };
258
-
259
- /**
260
- * Unzip array of tuples
261
- */
262
- export const unzip = <T, U>(arr: [T, U][]): [T[], U[]] => {
263
- const first: T[] = [];
264
- const second: U[] = [];
265
-
266
- for (const [a, b] of arr) {
267
- first.push(a);
268
- second.push(b);
269
- }
270
-
271
- return [first, second];
272
- };
273
-
274
- /**
275
- * Intersperse value between array elements
276
- */
277
- export const intersperse =
278
- <T>(separator: T) =>
279
- (arr: T[]): T[] => {
280
- if (arr.length === 0) {
281
- return [];
282
- }
283
-
284
- const result: T[] = [arr[0]];
285
- for (let i = 1; i < arr.length; i++) {
286
- result.push(separator, arr[i]);
287
- }
288
-
289
- return result;
290
- };
291
-
292
- /**
293
- * Get first element
294
- */
295
- export const head = <T>(arr: T[]): T | undefined => arr[0];
296
-
297
- /**
298
- * Get last element
299
- */
300
- export const last = <T>(arr: T[]): T | undefined => arr[arr.length - 1];
301
-
302
- /**
303
- * Get all but first element
304
- */
305
- export const tail = <T>(arr: T[]): T[] => arr.slice(1);
306
-
307
- /**
308
- * Get all but last element
309
- */
310
- export const init = <T>(arr: T[]): T[] => arr.slice(0, -1);
311
-
312
- /**
313
- * Check if array is empty
314
- */
315
- export const isEmpty = <T>(arr: T[]): boolean => arr.length === 0;
316
-
317
- /**
318
- * Check if array is not empty
319
- */
320
- export const isNotEmpty = <T>(arr: T[]): boolean => arr.length > 0;
321
-
322
- /**
323
- * Sum numbers in array
324
- */
325
- export const sum = (arr: number[]): number => arr.reduce((a, b) => a + b, 0);
326
-
327
- /**
328
- * Get average of numbers in array
329
- */
330
- export const average = (arr: number[]): number => {
331
- if (arr.length === 0) {
332
- return 0;
333
- }
334
- return sum(arr) / arr.length;
335
- };
336
-
337
- /**
338
- * Get min value
339
- */
340
- export const min = (arr: number[]): number | undefined => {
341
- if (arr.length === 0) {
342
- return undefined;
343
- }
344
- return Math.min(...arr);
345
- };
346
-
347
- /**
348
- * Get max value
349
- */
350
- export const max = (arr: number[]): number | undefined => {
351
- if (arr.length === 0) {
352
- return undefined;
353
- }
354
- return Math.max(...arr);
355
- };
@@ -1,15 +0,0 @@
1
- /**
2
- * Functional utilities
3
- * Pure, composable utility functions
4
- *
5
- * DESIGN RATIONALE:
6
- * - Pure functions for common operations
7
- * - Composable through currying
8
- * - Type-safe
9
- * - No side effects
10
- * - Point-free style support
11
- */
12
-
13
- export * as Arr from './array.js';
14
- export * as Obj from './object.js';
15
- export * as Str from './string.js';