@lumenflow/core 1.4.0 → 1.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/README.md +37 -6
- package/dist/agent-patterns-registry.d.ts +100 -2
- package/dist/agent-patterns-registry.js +124 -0
- package/dist/arg-parser.js +7 -0
- package/dist/branch-check.d.ts +32 -3
- package/dist/branch-check.js +81 -15
- package/dist/color-support.d.ts +32 -0
- package/dist/color-support.js +64 -0
- package/dist/cycle-detector.d.ts +51 -0
- package/dist/cycle-detector.js +89 -0
- package/dist/dependency-graph.js +1 -11
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/lumenflow-config-schema.d.ts +6 -0
- package/dist/lumenflow-config-schema.js +47 -4
- package/dist/wu-done-preflight.js +8 -1
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -70,23 +70,54 @@ await worktrees.remove('worktrees/operations-wu-123');
|
|
|
70
70
|
|
|
71
71
|
### Agent Branch Patterns
|
|
72
72
|
|
|
73
|
-
Check if a branch is an agent branch that can bypass worktree requirements. Patterns are fetched from a central registry with 7-day caching.
|
|
73
|
+
Check if a branch is an agent branch that can bypass worktree requirements. Patterns are fetched from a central registry with 7-day caching, and can be configured via `.lumenflow.config.yaml`.
|
|
74
74
|
|
|
75
75
|
```typescript
|
|
76
|
-
import { isAgentBranch,
|
|
76
|
+
import { isAgentBranch, isAgentBranchWithDetails, resolveAgentPatterns } from '@lumenflow/core';
|
|
77
77
|
|
|
78
78
|
// Check if branch can bypass worktree requirements (async, uses registry)
|
|
79
79
|
if (await isAgentBranch('claude/session-12345')) {
|
|
80
80
|
console.log('Agent branch - bypass allowed');
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// Get
|
|
84
|
-
const
|
|
85
|
-
|
|
83
|
+
// Get detailed result for observability
|
|
84
|
+
const result = await isAgentBranchWithDetails('claude/session-123');
|
|
85
|
+
if (result.isMatch) {
|
|
86
|
+
console.log(`Matched via ${result.patternResult.source}`); // 'registry', 'merged', 'override', 'config', 'defaults'
|
|
87
|
+
console.log(`Registry fetched: ${result.patternResult.registryFetched}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Resolve patterns with custom options (useful for testing)
|
|
91
|
+
const resolved = await resolveAgentPatterns({
|
|
92
|
+
configPatterns: ['my-agent/*'], // Merge with registry
|
|
93
|
+
// overridePatterns: ['only-this/*'], // Replace registry entirely
|
|
94
|
+
// disableAgentPatternRegistry: true, // Airgapped mode
|
|
95
|
+
});
|
|
96
|
+
console.log(resolved.patterns, resolved.source);
|
|
86
97
|
|
|
87
98
|
// Synchronous version (uses local config only, no registry fetch)
|
|
88
99
|
import { isAgentBranchSync } from '@lumenflow/core';
|
|
89
|
-
const
|
|
100
|
+
const syncResult = isAgentBranchSync('agent/task-123');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Configuration Options
|
|
104
|
+
|
|
105
|
+
In `.lumenflow.config.yaml`:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
git:
|
|
109
|
+
# Patterns to MERGE with registry (default: [])
|
|
110
|
+
agentBranchPatterns:
|
|
111
|
+
- 'my-custom-agent/*'
|
|
112
|
+
- 'internal-tool/*'
|
|
113
|
+
|
|
114
|
+
# Patterns that REPLACE registry entirely (optional)
|
|
115
|
+
# agentBranchPatternsOverride:
|
|
116
|
+
# - 'claude/*'
|
|
117
|
+
# - 'codex/*'
|
|
118
|
+
|
|
119
|
+
# Disable registry fetch for airgapped environments (default: false)
|
|
120
|
+
# disableAgentPatternRegistry: true
|
|
90
121
|
```
|
|
91
122
|
|
|
92
123
|
Protected branches (main, master, lane/\*) are **never** bypassed, regardless of patterns.
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* that bypass worktree requirements. Static JSON served from lumenflow.dev,
|
|
6
6
|
* cached locally for 7 days with fallback to defaults.
|
|
7
7
|
*
|
|
8
|
+
* WU-1089: Added merge/override/airgapped modes via resolveAgentPatterns()
|
|
9
|
+
*
|
|
8
10
|
* @module agent-patterns-registry
|
|
9
11
|
*/
|
|
10
12
|
/** Default agent branch patterns (narrow: just agent/*) */
|
|
@@ -16,12 +18,48 @@ export declare const CACHE_TTL_MS: number;
|
|
|
16
18
|
/**
|
|
17
19
|
* Options for getAgentPatterns
|
|
18
20
|
*/
|
|
19
|
-
interface GetAgentPatternsOptions {
|
|
21
|
+
export interface GetAgentPatternsOptions {
|
|
20
22
|
/** Override cache directory (default: ~/.lumenflow/cache) */
|
|
21
23
|
cacheDir?: string;
|
|
22
24
|
/** Fetch timeout in milliseconds (default: 5000) */
|
|
23
25
|
timeoutMs?: number;
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Source of agent patterns for observability
|
|
29
|
+
*/
|
|
30
|
+
export type AgentPatternSource = 'registry' | 'merged' | 'override' | 'config' | 'defaults';
|
|
31
|
+
/**
|
|
32
|
+
* Result of resolveAgentPatterns with observability fields
|
|
33
|
+
*/
|
|
34
|
+
export interface AgentPatternResult {
|
|
35
|
+
/** Resolved patterns to use for agent branch matching */
|
|
36
|
+
patterns: string[];
|
|
37
|
+
/** Source of the patterns for observability */
|
|
38
|
+
source: AgentPatternSource;
|
|
39
|
+
/** Whether the registry was successfully fetched */
|
|
40
|
+
registryFetched: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Type for injectable registry fetcher function
|
|
44
|
+
*/
|
|
45
|
+
export type RegistryFetcher = (options: GetAgentPatternsOptions) => Promise<string[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Options for resolveAgentPatterns (WU-1089)
|
|
48
|
+
*/
|
|
49
|
+
export interface ResolveAgentPatternsOptions {
|
|
50
|
+
/** Injectable registry fetcher for testing (uses getAgentPatterns by default) */
|
|
51
|
+
registryFetcher?: RegistryFetcher;
|
|
52
|
+
/** Patterns from config.git.agentBranchPatterns (merged with registry by default) */
|
|
53
|
+
configPatterns?: string[];
|
|
54
|
+
/** Patterns from config.git.agentBranchPatternsOverride (replaces everything if set) */
|
|
55
|
+
overridePatterns?: string[];
|
|
56
|
+
/** config.git.disableAgentPatternRegistry - skips network fetch (airgapped mode) */
|
|
57
|
+
disableAgentPatternRegistry?: boolean;
|
|
58
|
+
/** Override cache directory (passed to fetcher) */
|
|
59
|
+
cacheDir?: string;
|
|
60
|
+
/** Fetch timeout in milliseconds (passed to fetcher) */
|
|
61
|
+
timeoutMs?: number;
|
|
62
|
+
}
|
|
25
63
|
/**
|
|
26
64
|
* Get the default cache directory
|
|
27
65
|
*
|
|
@@ -44,10 +82,70 @@ export declare function getCacheDir(): string;
|
|
|
44
82
|
* ```
|
|
45
83
|
*/
|
|
46
84
|
export declare function getAgentPatterns(options?: GetAgentPatternsOptions): Promise<string[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Resolve agent branch patterns based on config with merge/override/airgapped support
|
|
87
|
+
*
|
|
88
|
+
* Behavior matrix (WU-1089):
|
|
89
|
+
*
|
|
90
|
+
* | disableRegistry | override patterns | config patterns | Result | Source |
|
|
91
|
+
* |-----------------|-------------------|-----------------|------------------------|---------------|
|
|
92
|
+
* | false | undefined | undefined/[] | registry | 'registry' |
|
|
93
|
+
* | false | undefined | ['custom/*'] | config + registry | 'merged' |
|
|
94
|
+
* | false | ['only/*'] | any | override only | 'override' |
|
|
95
|
+
* | true | undefined | undefined/[] | defaults | 'defaults' |
|
|
96
|
+
* | true | undefined | ['custom/*'] | config only | 'config' |
|
|
97
|
+
* | true | ['only/*'] | any | override only | 'override' |
|
|
98
|
+
*
|
|
99
|
+
* When registry fetch fails:
|
|
100
|
+
* - Falls back to config patterns if provided, source = 'config'
|
|
101
|
+
* - Falls back to defaults if no config, source = 'defaults'
|
|
102
|
+
*
|
|
103
|
+
* @param options - Resolution options
|
|
104
|
+
* @returns Result with patterns, source, and registryFetched flag
|
|
105
|
+
*
|
|
106
|
+
* @example Default (fetch from registry)
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const result = await resolveAgentPatterns({});
|
|
109
|
+
* // result.patterns = ['claude/*', 'codex/*', ...] (from registry)
|
|
110
|
+
* // result.source = 'registry'
|
|
111
|
+
* // result.registryFetched = true
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @example Merge mode (config + registry)
|
|
115
|
+
* ```typescript
|
|
116
|
+
* const result = await resolveAgentPatterns({
|
|
117
|
+
* configPatterns: ['my-agent/*'],
|
|
118
|
+
* });
|
|
119
|
+
* // result.patterns = ['my-agent/*', 'claude/*', 'codex/*', ...]
|
|
120
|
+
* // result.source = 'merged'
|
|
121
|
+
* // result.registryFetched = true
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* @example Override mode (explicit replacement)
|
|
125
|
+
* ```typescript
|
|
126
|
+
* const result = await resolveAgentPatterns({
|
|
127
|
+
* overridePatterns: ['only-this/*'],
|
|
128
|
+
* });
|
|
129
|
+
* // result.patterns = ['only-this/*']
|
|
130
|
+
* // result.source = 'override'
|
|
131
|
+
* // result.registryFetched = false
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* @example Airgapped mode (no network)
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const result = await resolveAgentPatterns({
|
|
137
|
+
* disableAgentPatternRegistry: true,
|
|
138
|
+
* configPatterns: ['my-agent/*'],
|
|
139
|
+
* });
|
|
140
|
+
* // result.patterns = ['my-agent/*']
|
|
141
|
+
* // result.source = 'config'
|
|
142
|
+
* // result.registryFetched = false
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export declare function resolveAgentPatterns(options?: ResolveAgentPatternsOptions): Promise<AgentPatternResult>;
|
|
47
146
|
/**
|
|
48
147
|
* Clear the in-memory cache
|
|
49
148
|
*
|
|
50
149
|
* Used primarily for testing.
|
|
51
150
|
*/
|
|
52
151
|
export declare function clearCache(): void;
|
|
53
|
-
export {};
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* that bypass worktree requirements. Static JSON served from lumenflow.dev,
|
|
6
6
|
* cached locally for 7 days with fallback to defaults.
|
|
7
7
|
*
|
|
8
|
+
* WU-1089: Added merge/override/airgapped modes via resolveAgentPatterns()
|
|
9
|
+
*
|
|
8
10
|
* @module agent-patterns-registry
|
|
9
11
|
*/
|
|
10
12
|
import * as fs from 'node:fs';
|
|
@@ -179,6 +181,128 @@ export async function getAgentPatterns(options = {}) {
|
|
|
179
181
|
// No cache, no network - use defaults
|
|
180
182
|
return DEFAULT_AGENT_PATTERNS;
|
|
181
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Resolve agent branch patterns based on config with merge/override/airgapped support
|
|
186
|
+
*
|
|
187
|
+
* Behavior matrix (WU-1089):
|
|
188
|
+
*
|
|
189
|
+
* | disableRegistry | override patterns | config patterns | Result | Source |
|
|
190
|
+
* |-----------------|-------------------|-----------------|------------------------|---------------|
|
|
191
|
+
* | false | undefined | undefined/[] | registry | 'registry' |
|
|
192
|
+
* | false | undefined | ['custom/*'] | config + registry | 'merged' |
|
|
193
|
+
* | false | ['only/*'] | any | override only | 'override' |
|
|
194
|
+
* | true | undefined | undefined/[] | defaults | 'defaults' |
|
|
195
|
+
* | true | undefined | ['custom/*'] | config only | 'config' |
|
|
196
|
+
* | true | ['only/*'] | any | override only | 'override' |
|
|
197
|
+
*
|
|
198
|
+
* When registry fetch fails:
|
|
199
|
+
* - Falls back to config patterns if provided, source = 'config'
|
|
200
|
+
* - Falls back to defaults if no config, source = 'defaults'
|
|
201
|
+
*
|
|
202
|
+
* @param options - Resolution options
|
|
203
|
+
* @returns Result with patterns, source, and registryFetched flag
|
|
204
|
+
*
|
|
205
|
+
* @example Default (fetch from registry)
|
|
206
|
+
* ```typescript
|
|
207
|
+
* const result = await resolveAgentPatterns({});
|
|
208
|
+
* // result.patterns = ['claude/*', 'codex/*', ...] (from registry)
|
|
209
|
+
* // result.source = 'registry'
|
|
210
|
+
* // result.registryFetched = true
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* @example Merge mode (config + registry)
|
|
214
|
+
* ```typescript
|
|
215
|
+
* const result = await resolveAgentPatterns({
|
|
216
|
+
* configPatterns: ['my-agent/*'],
|
|
217
|
+
* });
|
|
218
|
+
* // result.patterns = ['my-agent/*', 'claude/*', 'codex/*', ...]
|
|
219
|
+
* // result.source = 'merged'
|
|
220
|
+
* // result.registryFetched = true
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @example Override mode (explicit replacement)
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const result = await resolveAgentPatterns({
|
|
226
|
+
* overridePatterns: ['only-this/*'],
|
|
227
|
+
* });
|
|
228
|
+
* // result.patterns = ['only-this/*']
|
|
229
|
+
* // result.source = 'override'
|
|
230
|
+
* // result.registryFetched = false
|
|
231
|
+
* ```
|
|
232
|
+
*
|
|
233
|
+
* @example Airgapped mode (no network)
|
|
234
|
+
* ```typescript
|
|
235
|
+
* const result = await resolveAgentPatterns({
|
|
236
|
+
* disableAgentPatternRegistry: true,
|
|
237
|
+
* configPatterns: ['my-agent/*'],
|
|
238
|
+
* });
|
|
239
|
+
* // result.patterns = ['my-agent/*']
|
|
240
|
+
* // result.source = 'config'
|
|
241
|
+
* // result.registryFetched = false
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
export async function resolveAgentPatterns(options = {}) {
|
|
245
|
+
const { registryFetcher = getAgentPatterns, configPatterns, overridePatterns, disableAgentPatternRegistry = false, cacheDir, timeoutMs, } = options;
|
|
246
|
+
// Scenario 3/6: Override mode - overridePatterns replaces everything
|
|
247
|
+
if (overridePatterns && overridePatterns.length > 0) {
|
|
248
|
+
return {
|
|
249
|
+
patterns: overridePatterns,
|
|
250
|
+
source: 'override',
|
|
251
|
+
registryFetched: false,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
// Scenario 4/5: Airgapped mode - disableAgentPatternRegistry skips network
|
|
255
|
+
if (disableAgentPatternRegistry) {
|
|
256
|
+
const hasConfigPatterns = configPatterns && configPatterns.length > 0;
|
|
257
|
+
return {
|
|
258
|
+
patterns: hasConfigPatterns ? configPatterns : DEFAULT_AGENT_PATTERNS,
|
|
259
|
+
source: hasConfigPatterns ? 'config' : 'defaults',
|
|
260
|
+
registryFetched: false,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Scenario 1/2: Normal mode - fetch from registry, optionally merge with config
|
|
264
|
+
const hasConfigPatterns = configPatterns && configPatterns.length > 0;
|
|
265
|
+
// Try to fetch from registry
|
|
266
|
+
let registryPatterns = null;
|
|
267
|
+
let fetchedSuccessfully = false;
|
|
268
|
+
try {
|
|
269
|
+
registryPatterns = await registryFetcher({ cacheDir, timeoutMs });
|
|
270
|
+
fetchedSuccessfully = registryPatterns !== null && registryPatterns.length > 0;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// Fetch failed - will use fallback below
|
|
274
|
+
fetchedSuccessfully = false;
|
|
275
|
+
}
|
|
276
|
+
// If registry fetch succeeded
|
|
277
|
+
if (fetchedSuccessfully && registryPatterns) {
|
|
278
|
+
if (hasConfigPatterns) {
|
|
279
|
+
// Scenario 2: Merge mode - config first, then registry (deduplicated)
|
|
280
|
+
const merged = [...configPatterns];
|
|
281
|
+
for (const pattern of registryPatterns) {
|
|
282
|
+
if (!merged.includes(pattern)) {
|
|
283
|
+
merged.push(pattern);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
patterns: merged,
|
|
288
|
+
source: 'merged',
|
|
289
|
+
registryFetched: true,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// Scenario 1: Registry only
|
|
293
|
+
return {
|
|
294
|
+
patterns: registryPatterns,
|
|
295
|
+
source: 'registry',
|
|
296
|
+
registryFetched: true,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// Registry fetch failed - fallback to config or defaults
|
|
300
|
+
return {
|
|
301
|
+
patterns: hasConfigPatterns ? configPatterns : DEFAULT_AGENT_PATTERNS,
|
|
302
|
+
source: hasConfigPatterns ? 'config' : 'defaults',
|
|
303
|
+
registryFetched: false,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
182
306
|
/**
|
|
183
307
|
* Clear the in-memory cache
|
|
184
308
|
*
|
package/dist/arg-parser.js
CHANGED
|
@@ -247,6 +247,13 @@ export const WU_OPTIONS = {
|
|
|
247
247
|
flags: '--color',
|
|
248
248
|
description: 'Enable colored output',
|
|
249
249
|
},
|
|
250
|
+
// WU-1085: NO_COLOR standard support (https://no-color.org/)
|
|
251
|
+
noColor: {
|
|
252
|
+
name: 'noColor',
|
|
253
|
+
flags: '--no-color',
|
|
254
|
+
description: 'Disable colored output (respects NO_COLOR env var)',
|
|
255
|
+
isNegated: true,
|
|
256
|
+
},
|
|
250
257
|
status: {
|
|
251
258
|
name: 'status',
|
|
252
259
|
flags: '--status <status>',
|
package/dist/branch-check.d.ts
CHANGED
|
@@ -4,14 +4,19 @@
|
|
|
4
4
|
* Provides functions to check if a branch is an agent branch that can
|
|
5
5
|
* bypass worktree requirements, and if headless mode is allowed.
|
|
6
6
|
*
|
|
7
|
+
* WU-1089: Updated to use resolveAgentPatterns with merge/override/airgapped support.
|
|
8
|
+
*
|
|
7
9
|
* @module branch-check
|
|
8
10
|
*/
|
|
11
|
+
import { type AgentPatternResult } from './agent-patterns-registry.js';
|
|
9
12
|
/**
|
|
10
13
|
* Check if branch is an agent branch that can bypass worktree requirements.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
+
* WU-1089: Now uses resolveAgentPatterns with proper merge/override/airgapped support:
|
|
16
|
+
* - Default: Fetches from registry (lumenflow.dev) with 7-day cache
|
|
17
|
+
* - Config patterns merge with registry patterns (config first)
|
|
18
|
+
* - Override patterns (agentBranchPatternsOverride) replace everything
|
|
19
|
+
* - Airgapped mode (disableAgentPatternRegistry) skips network fetch
|
|
15
20
|
*
|
|
16
21
|
* @param branch - Branch name to check
|
|
17
22
|
* @returns Promise<true> if branch matches agent patterns
|
|
@@ -24,12 +29,36 @@
|
|
|
24
29
|
* ```
|
|
25
30
|
*/
|
|
26
31
|
export declare function isAgentBranch(branch: string | null | undefined): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Check if branch is an agent branch with full result details.
|
|
34
|
+
*
|
|
35
|
+
* Same as isAgentBranch but returns the full AgentPatternResult
|
|
36
|
+
* for observability and debugging.
|
|
37
|
+
*
|
|
38
|
+
* @param branch - Branch name to check
|
|
39
|
+
* @returns Promise with match result and pattern resolution details
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const result = await isAgentBranchWithDetails('claude/session-123');
|
|
44
|
+
* if (result.isMatch) {
|
|
45
|
+
* console.log(`Matched via ${result.patternResult.source}`);
|
|
46
|
+
* console.log(`Registry fetched: ${result.patternResult.registryFetched}`);
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function isAgentBranchWithDetails(branch: string | null | undefined): Promise<{
|
|
51
|
+
isMatch: boolean;
|
|
52
|
+
patternResult: AgentPatternResult;
|
|
53
|
+
}>;
|
|
27
54
|
/**
|
|
28
55
|
* Synchronous version of isAgentBranch for backwards compatibility.
|
|
29
56
|
*
|
|
30
57
|
* Uses only local config patterns or defaults - does NOT fetch from registry.
|
|
31
58
|
* Prefer async isAgentBranch() when possible.
|
|
32
59
|
*
|
|
60
|
+
* WU-1089: Updated to respect override and disable flags, but cannot fetch from registry.
|
|
61
|
+
*
|
|
33
62
|
* @param branch - Branch name to check
|
|
34
63
|
* @returns True if branch matches agent patterns
|
|
35
64
|
*
|
package/dist/branch-check.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* Provides functions to check if a branch is an agent branch that can
|
|
5
5
|
* bypass worktree requirements, and if headless mode is allowed.
|
|
6
6
|
*
|
|
7
|
+
* WU-1089: Updated to use resolveAgentPatterns with merge/override/airgapped support.
|
|
8
|
+
*
|
|
7
9
|
* @module branch-check
|
|
8
10
|
*/
|
|
9
11
|
import micromatch from 'micromatch';
|
|
10
12
|
import { getConfig } from './lumenflow-config.js';
|
|
11
|
-
import {
|
|
13
|
+
import { resolveAgentPatterns, DEFAULT_AGENT_PATTERNS, } from './agent-patterns-registry.js';
|
|
12
14
|
/** Legacy protected branch (always protected regardless of mainBranch setting) */
|
|
13
15
|
const LEGACY_PROTECTED = 'master';
|
|
14
16
|
/**
|
|
@@ -36,9 +38,11 @@ function getProtectedBranches() {
|
|
|
36
38
|
/**
|
|
37
39
|
* Check if branch is an agent branch that can bypass worktree requirements.
|
|
38
40
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
41
|
+
* WU-1089: Now uses resolveAgentPatterns with proper merge/override/airgapped support:
|
|
42
|
+
* - Default: Fetches from registry (lumenflow.dev) with 7-day cache
|
|
43
|
+
* - Config patterns merge with registry patterns (config first)
|
|
44
|
+
* - Override patterns (agentBranchPatternsOverride) replace everything
|
|
45
|
+
* - Airgapped mode (disableAgentPatternRegistry) skips network fetch
|
|
42
46
|
*
|
|
43
47
|
* @param branch - Branch name to check
|
|
44
48
|
* @returns Promise<true> if branch matches agent patterns
|
|
@@ -66,18 +70,73 @@ export async function isAgentBranch(branch) {
|
|
|
66
70
|
// LumenFlow lane branches require worktrees (uses config's laneBranchPrefix)
|
|
67
71
|
if (getLaneBranchPattern().test(branch))
|
|
68
72
|
return false;
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
// WU-1089: Use resolveAgentPatterns with full merge/override/airgapped support
|
|
74
|
+
const result = await resolveAgentPatterns({
|
|
75
|
+
configPatterns: config?.git?.agentBranchPatterns,
|
|
76
|
+
overridePatterns: config?.git?.agentBranchPatternsOverride,
|
|
77
|
+
disableAgentPatternRegistry: config?.git?.disableAgentPatternRegistry,
|
|
78
|
+
});
|
|
79
|
+
// Use micromatch for proper glob matching
|
|
80
|
+
return micromatch.isMatch(branch, result.patterns);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if branch is an agent branch with full result details.
|
|
84
|
+
*
|
|
85
|
+
* Same as isAgentBranch but returns the full AgentPatternResult
|
|
86
|
+
* for observability and debugging.
|
|
87
|
+
*
|
|
88
|
+
* @param branch - Branch name to check
|
|
89
|
+
* @returns Promise with match result and pattern resolution details
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* const result = await isAgentBranchWithDetails('claude/session-123');
|
|
94
|
+
* if (result.isMatch) {
|
|
95
|
+
* console.log(`Matched via ${result.patternResult.source}`);
|
|
96
|
+
* console.log(`Registry fetched: ${result.patternResult.registryFetched}`);
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export async function isAgentBranchWithDetails(branch) {
|
|
101
|
+
// Fail-closed: no branch = protected
|
|
102
|
+
if (!branch) {
|
|
103
|
+
return {
|
|
104
|
+
isMatch: false,
|
|
105
|
+
patternResult: { patterns: [], source: 'defaults', registryFetched: false },
|
|
106
|
+
};
|
|
74
107
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
// Detached HEAD = protected (fail-closed)
|
|
109
|
+
if (branch === 'HEAD') {
|
|
110
|
+
return {
|
|
111
|
+
isMatch: false,
|
|
112
|
+
patternResult: { patterns: [], source: 'defaults', registryFetched: false },
|
|
113
|
+
};
|
|
78
114
|
}
|
|
79
|
-
//
|
|
80
|
-
|
|
115
|
+
// Load config
|
|
116
|
+
const config = getConfig();
|
|
117
|
+
const protectedBranches = getProtectedBranches();
|
|
118
|
+
// Protected branches are NEVER bypassed
|
|
119
|
+
if (protectedBranches.includes(branch)) {
|
|
120
|
+
return {
|
|
121
|
+
isMatch: false,
|
|
122
|
+
patternResult: { patterns: [], source: 'defaults', registryFetched: false },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Lane branches require worktrees
|
|
126
|
+
if (getLaneBranchPattern().test(branch)) {
|
|
127
|
+
return {
|
|
128
|
+
isMatch: false,
|
|
129
|
+
patternResult: { patterns: [], source: 'defaults', registryFetched: false },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// Resolve patterns with full details
|
|
133
|
+
const patternResult = await resolveAgentPatterns({
|
|
134
|
+
configPatterns: config?.git?.agentBranchPatterns,
|
|
135
|
+
overridePatterns: config?.git?.agentBranchPatternsOverride,
|
|
136
|
+
disableAgentPatternRegistry: config?.git?.disableAgentPatternRegistry,
|
|
137
|
+
});
|
|
138
|
+
const isMatch = micromatch.isMatch(branch, patternResult.patterns);
|
|
139
|
+
return { isMatch, patternResult };
|
|
81
140
|
}
|
|
82
141
|
/**
|
|
83
142
|
* Synchronous version of isAgentBranch for backwards compatibility.
|
|
@@ -85,6 +144,8 @@ export async function isAgentBranch(branch) {
|
|
|
85
144
|
* Uses only local config patterns or defaults - does NOT fetch from registry.
|
|
86
145
|
* Prefer async isAgentBranch() when possible.
|
|
87
146
|
*
|
|
147
|
+
* WU-1089: Updated to respect override and disable flags, but cannot fetch from registry.
|
|
148
|
+
*
|
|
88
149
|
* @param branch - Branch name to check
|
|
89
150
|
* @returns True if branch matches agent patterns
|
|
90
151
|
*
|
|
@@ -106,7 +167,12 @@ export function isAgentBranchSync(branch) {
|
|
|
106
167
|
// LumenFlow lane branches require worktrees (uses config's laneBranchPrefix)
|
|
107
168
|
if (getLaneBranchPattern().test(branch))
|
|
108
169
|
return false;
|
|
109
|
-
//
|
|
170
|
+
// WU-1089: Check override first
|
|
171
|
+
if (config?.git?.agentBranchPatternsOverride?.length) {
|
|
172
|
+
return micromatch.isMatch(branch, config.git.agentBranchPatternsOverride);
|
|
173
|
+
}
|
|
174
|
+
// Use config patterns if provided, otherwise defaults
|
|
175
|
+
// Note: sync version cannot fetch from registry
|
|
110
176
|
const patterns = config?.git?.agentBranchPatterns?.length > 0
|
|
111
177
|
? config.git.agentBranchPatterns
|
|
112
178
|
: DEFAULT_AGENT_PATTERNS;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file color-support.ts
|
|
3
|
+
* Color control for CLI commands (WU-1085)
|
|
4
|
+
*
|
|
5
|
+
* Respects standard environment variables and CLI flags:
|
|
6
|
+
* - NO_COLOR: Disable colors (https://no-color.org/)
|
|
7
|
+
* - FORCE_COLOR: Override color level 0-3 (chalk standard)
|
|
8
|
+
* - --no-color: CLI flag to disable colors
|
|
9
|
+
*
|
|
10
|
+
* @see https://no-color.org/
|
|
11
|
+
* @see https://github.com/chalk/chalk#supportscolor
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Get the current color level.
|
|
15
|
+
* @returns Color level 0-3 (0 = no colors, 3 = full 16m colors)
|
|
16
|
+
*/
|
|
17
|
+
export declare function getColorLevel(): number;
|
|
18
|
+
/**
|
|
19
|
+
* Initialize color support respecting NO_COLOR and FORCE_COLOR standards.
|
|
20
|
+
* Call this before any colored output.
|
|
21
|
+
*
|
|
22
|
+
* Priority order:
|
|
23
|
+
* 1. NO_COLOR env var (always wins, per spec)
|
|
24
|
+
* 2. --no-color CLI flag
|
|
25
|
+
* 3. FORCE_COLOR env var
|
|
26
|
+
* 4. Default chalk detection
|
|
27
|
+
*
|
|
28
|
+
* @param argv - Command line arguments (defaults to process.argv)
|
|
29
|
+
* @see https://no-color.org/
|
|
30
|
+
* @see https://github.com/chalk/chalk#supportscolor
|
|
31
|
+
*/
|
|
32
|
+
export declare function initColorSupport(argv?: string[]): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file color-support.ts
|
|
3
|
+
* Color control for CLI commands (WU-1085)
|
|
4
|
+
*
|
|
5
|
+
* Respects standard environment variables and CLI flags:
|
|
6
|
+
* - NO_COLOR: Disable colors (https://no-color.org/)
|
|
7
|
+
* - FORCE_COLOR: Override color level 0-3 (chalk standard)
|
|
8
|
+
* - --no-color: CLI flag to disable colors
|
|
9
|
+
*
|
|
10
|
+
* @see https://no-color.org/
|
|
11
|
+
* @see https://github.com/chalk/chalk#supportscolor
|
|
12
|
+
*/
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
/** Internal storage for color level (allows testing without chalk singleton issues) */
|
|
15
|
+
let currentColorLevel = chalk.level;
|
|
16
|
+
/**
|
|
17
|
+
* Get the current color level.
|
|
18
|
+
* @returns Color level 0-3 (0 = no colors, 3 = full 16m colors)
|
|
19
|
+
*/
|
|
20
|
+
export function getColorLevel() {
|
|
21
|
+
return currentColorLevel;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Initialize color support respecting NO_COLOR and FORCE_COLOR standards.
|
|
25
|
+
* Call this before any colored output.
|
|
26
|
+
*
|
|
27
|
+
* Priority order:
|
|
28
|
+
* 1. NO_COLOR env var (always wins, per spec)
|
|
29
|
+
* 2. --no-color CLI flag
|
|
30
|
+
* 3. FORCE_COLOR env var
|
|
31
|
+
* 4. Default chalk detection
|
|
32
|
+
*
|
|
33
|
+
* @param argv - Command line arguments (defaults to process.argv)
|
|
34
|
+
* @see https://no-color.org/
|
|
35
|
+
* @see https://github.com/chalk/chalk#supportscolor
|
|
36
|
+
*/
|
|
37
|
+
export function initColorSupport(argv = process.argv) {
|
|
38
|
+
// NO_COLOR standard (https://no-color.org/)
|
|
39
|
+
// "When set (to any value, including empty string), it should disable colors"
|
|
40
|
+
if (process.env.NO_COLOR !== undefined) {
|
|
41
|
+
chalk.level = 0;
|
|
42
|
+
currentColorLevel = 0;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// CLI --no-color flag
|
|
46
|
+
if (argv.includes('--no-color')) {
|
|
47
|
+
chalk.level = 0;
|
|
48
|
+
currentColorLevel = 0;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// FORCE_COLOR override (chalk standard)
|
|
52
|
+
// Values: 0 = no color, 1 = basic, 2 = 256, 3 = 16m
|
|
53
|
+
if (process.env.FORCE_COLOR !== undefined) {
|
|
54
|
+
const level = parseInt(process.env.FORCE_COLOR, 10);
|
|
55
|
+
if (!isNaN(level) && level >= 0 && level <= 3) {
|
|
56
|
+
chalk.level = level;
|
|
57
|
+
currentColorLevel = level;
|
|
58
|
+
}
|
|
59
|
+
// Invalid values are ignored, keep default
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Use chalk's default detection
|
|
63
|
+
currentColorLevel = chalk.level;
|
|
64
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cycle Detection Module (WU-1088)
|
|
3
|
+
*
|
|
4
|
+
* Provides cycle detection for WU dependency graphs.
|
|
5
|
+
* Extracted from @lumenflow/initiatives to break circular dependency.
|
|
6
|
+
*
|
|
7
|
+
* @module @lumenflow/core/cycle-detector
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* WU object interface for validation and cycle detection
|
|
11
|
+
* Note: This interface is shared with @lumenflow/initiatives for backward compatibility
|
|
12
|
+
*/
|
|
13
|
+
export interface WUObject {
|
|
14
|
+
id?: string;
|
|
15
|
+
blocks?: string[];
|
|
16
|
+
blocked_by?: string[];
|
|
17
|
+
initiative?: string;
|
|
18
|
+
phase?: number;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Result of cycle detection
|
|
23
|
+
*/
|
|
24
|
+
export interface CycleResult {
|
|
25
|
+
hasCycle: boolean;
|
|
26
|
+
cycles: string[][];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Detects circular dependencies in WU dependency graph using DFS
|
|
30
|
+
*
|
|
31
|
+
* Uses standard cycle detection: tracks visited nodes and nodes in current
|
|
32
|
+
* recursion stack. If we encounter a node already in the recursion stack,
|
|
33
|
+
* we've found a cycle.
|
|
34
|
+
*
|
|
35
|
+
* Note: This function treats both `blocks` and `blocked_by` as edges for
|
|
36
|
+
* traversal. This means if WU-A blocks WU-B, and WU-B's blocked_by includes
|
|
37
|
+
* WU-A, following both directions will find a path back to WU-A.
|
|
38
|
+
*
|
|
39
|
+
* @param wuMap - Map of WU ID to WU object
|
|
40
|
+
* @returns Cycle detection result with hasCycle boolean and cycles array
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const wuMap = new Map([
|
|
44
|
+
* ['WU-001', { id: 'WU-001', blocks: ['WU-002'] }],
|
|
45
|
+
* ['WU-002', { id: 'WU-002', blocks: ['WU-001'] }],
|
|
46
|
+
* ]);
|
|
47
|
+
* const result = detectCycles(wuMap);
|
|
48
|
+
* // result.hasCycle === true
|
|
49
|
+
* // result.cycles contains the cycle path
|
|
50
|
+
*/
|
|
51
|
+
export declare function detectCycles(wuMap: Map<string, WUObject>): CycleResult;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cycle Detection Module (WU-1088)
|
|
3
|
+
*
|
|
4
|
+
* Provides cycle detection for WU dependency graphs.
|
|
5
|
+
* Extracted from @lumenflow/initiatives to break circular dependency.
|
|
6
|
+
*
|
|
7
|
+
* @module @lumenflow/core/cycle-detector
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Detects circular dependencies in WU dependency graph using DFS
|
|
11
|
+
*
|
|
12
|
+
* Uses standard cycle detection: tracks visited nodes and nodes in current
|
|
13
|
+
* recursion stack. If we encounter a node already in the recursion stack,
|
|
14
|
+
* we've found a cycle.
|
|
15
|
+
*
|
|
16
|
+
* Note: This function treats both `blocks` and `blocked_by` as edges for
|
|
17
|
+
* traversal. This means if WU-A blocks WU-B, and WU-B's blocked_by includes
|
|
18
|
+
* WU-A, following both directions will find a path back to WU-A.
|
|
19
|
+
*
|
|
20
|
+
* @param wuMap - Map of WU ID to WU object
|
|
21
|
+
* @returns Cycle detection result with hasCycle boolean and cycles array
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const wuMap = new Map([
|
|
25
|
+
* ['WU-001', { id: 'WU-001', blocks: ['WU-002'] }],
|
|
26
|
+
* ['WU-002', { id: 'WU-002', blocks: ['WU-001'] }],
|
|
27
|
+
* ]);
|
|
28
|
+
* const result = detectCycles(wuMap);
|
|
29
|
+
* // result.hasCycle === true
|
|
30
|
+
* // result.cycles contains the cycle path
|
|
31
|
+
*/
|
|
32
|
+
export function detectCycles(wuMap) {
|
|
33
|
+
const visited = new Set();
|
|
34
|
+
const recursionStack = new Set();
|
|
35
|
+
const cycles = [];
|
|
36
|
+
/**
|
|
37
|
+
* DFS traversal to detect cycles
|
|
38
|
+
* @param wuId - Current WU ID
|
|
39
|
+
* @param path - Current path from root
|
|
40
|
+
* @returns True if cycle found
|
|
41
|
+
*/
|
|
42
|
+
function dfs(wuId, path) {
|
|
43
|
+
// If node is in recursion stack, we found a cycle
|
|
44
|
+
if (recursionStack.has(wuId)) {
|
|
45
|
+
const cycleStart = path.indexOf(wuId);
|
|
46
|
+
if (cycleStart !== -1) {
|
|
47
|
+
cycles.push([...path.slice(cycleStart), wuId]);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Self-reference case
|
|
51
|
+
cycles.push([wuId, wuId]);
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
// If already fully visited, skip
|
|
56
|
+
if (visited.has(wuId)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// Mark as being processed
|
|
60
|
+
visited.add(wuId);
|
|
61
|
+
recursionStack.add(wuId);
|
|
62
|
+
// Get dependencies (both blocks and blocked_by create edges)
|
|
63
|
+
// Only use arrays - ignore legacy string format for backward compatibility
|
|
64
|
+
const wu = wuMap.get(wuId);
|
|
65
|
+
const blocks = Array.isArray(wu?.blocks) ? wu.blocks : [];
|
|
66
|
+
const blockedBy = Array.isArray(wu?.blocked_by) ? wu.blocked_by : [];
|
|
67
|
+
const deps = [...blocks, ...blockedBy];
|
|
68
|
+
// Visit all dependencies
|
|
69
|
+
for (const dep of deps) {
|
|
70
|
+
// Only traverse if the dependency exists in our map
|
|
71
|
+
if (wuMap.has(dep)) {
|
|
72
|
+
dfs(dep, [...path, wuId]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Remove from recursion stack (done processing)
|
|
76
|
+
recursionStack.delete(wuId);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// Run DFS from each node to find all cycles
|
|
80
|
+
for (const wuId of wuMap.keys()) {
|
|
81
|
+
if (!visited.has(wuId)) {
|
|
82
|
+
dfs(wuId, []);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
hasCycle: cycles.length > 0,
|
|
87
|
+
cycles,
|
|
88
|
+
};
|
|
89
|
+
}
|
package/dist/dependency-graph.js
CHANGED
|
@@ -3,17 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { readWU } from './wu-yaml.js';
|
|
4
4
|
import { WU_PATHS } from './wu-paths.js';
|
|
5
5
|
import { STRING_LITERALS, WU_STATUS } from './wu-constants.js';
|
|
6
|
-
|
|
7
|
-
let detectCycles;
|
|
8
|
-
try {
|
|
9
|
-
// Dynamic import for optional peer dependency
|
|
10
|
-
const module = await import('@lumenflow/initiatives');
|
|
11
|
-
detectCycles = module.detectCycles;
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
// Fallback stub if @lumenflow/initiatives is not available
|
|
15
|
-
detectCycles = () => ({ hasCycle: false, cycles: [] });
|
|
16
|
-
}
|
|
6
|
+
import { detectCycles } from './cycle-detector.js';
|
|
17
7
|
/**
|
|
18
8
|
* Dependency Graph Module (WU-1247, WU-1568)
|
|
19
9
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './date-utils.js';
|
|
|
8
8
|
export * from './error-handler.js';
|
|
9
9
|
export * from './retry-strategy.js';
|
|
10
10
|
export * from './beacon-migration.js';
|
|
11
|
+
export * from './cycle-detector.js';
|
|
11
12
|
export { DEFAULT_DOMAIN, inferDefaultDomain, normalizeToEmail, isValidEmail, } from './user-normalizer.js';
|
|
12
13
|
export * from './git-adapter.js';
|
|
13
14
|
export * from './state-machine.js';
|
|
@@ -47,3 +48,4 @@ export * from './agent-patterns-registry.js';
|
|
|
47
48
|
export * from './lumenflow-home.js';
|
|
48
49
|
export * from './force-bypass-audit.js';
|
|
49
50
|
export { LUMENFLOW_PATHS, BEACON_PATHS } from './wu-constants.js';
|
|
51
|
+
export * from './color-support.js';
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,8 @@ export * from './error-handler.js';
|
|
|
11
11
|
export * from './retry-strategy.js';
|
|
12
12
|
// Migration utilities (WU-1075)
|
|
13
13
|
export * from './beacon-migration.js';
|
|
14
|
+
// Cycle detection (WU-1088 - extracted from initiatives to break circular dependency)
|
|
15
|
+
export * from './cycle-detector.js';
|
|
14
16
|
// User normalizer (explicit exports to avoid conflicts)
|
|
15
17
|
export { DEFAULT_DOMAIN, inferDefaultDomain, normalizeToEmail, isValidEmail, } from './user-normalizer.js';
|
|
16
18
|
// Git operations
|
|
@@ -73,3 +75,5 @@ export * from './lumenflow-home.js';
|
|
|
73
75
|
export * from './force-bypass-audit.js';
|
|
74
76
|
// WU-1075: LumenFlow directory paths (exported from wu-constants)
|
|
75
77
|
export { LUMENFLOW_PATHS, BEACON_PATHS } from './wu-constants.js';
|
|
78
|
+
// WU-1085: Color support for NO_COLOR/FORCE_COLOR/--no-color
|
|
79
|
+
export * from './color-support.js';
|
|
@@ -53,6 +53,8 @@ export declare const GitConfigSchema: z.ZodObject<{
|
|
|
53
53
|
branchDriftWarning: z.ZodDefault<z.ZodNumber>;
|
|
54
54
|
branchDriftInfo: z.ZodDefault<z.ZodNumber>;
|
|
55
55
|
agentBranchPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
56
|
+
agentBranchPatternsOverride: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
57
|
+
disableAgentPatternRegistry: z.ZodDefault<z.ZodBoolean>;
|
|
56
58
|
}, z.core.$strip>;
|
|
57
59
|
/**
|
|
58
60
|
* WU (Work Unit) configuration
|
|
@@ -245,6 +247,8 @@ export declare const LumenFlowConfigSchema: z.ZodObject<{
|
|
|
245
247
|
branchDriftWarning: z.ZodDefault<z.ZodNumber>;
|
|
246
248
|
branchDriftInfo: z.ZodDefault<z.ZodNumber>;
|
|
247
249
|
agentBranchPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
250
|
+
agentBranchPatternsOverride: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
251
|
+
disableAgentPatternRegistry: z.ZodDefault<z.ZodBoolean>;
|
|
248
252
|
}, z.core.$strip>>;
|
|
249
253
|
wu: z.ZodDefault<z.ZodObject<{
|
|
250
254
|
idPattern: z.ZodDefault<z.ZodString>;
|
|
@@ -394,6 +398,8 @@ export declare function validateConfig(data: unknown): z.ZodSafeParseResult<{
|
|
|
394
398
|
branchDriftWarning: number;
|
|
395
399
|
branchDriftInfo: number;
|
|
396
400
|
agentBranchPatterns: string[];
|
|
401
|
+
disableAgentPatternRegistry: boolean;
|
|
402
|
+
agentBranchPatternsOverride?: string[];
|
|
397
403
|
};
|
|
398
404
|
wu: {
|
|
399
405
|
idPattern: string;
|
|
@@ -86,12 +86,55 @@ export const GitConfigSchema = z.object({
|
|
|
86
86
|
/** Info threshold for branch drift */
|
|
87
87
|
branchDriftInfo: z.number().int().positive().default(10),
|
|
88
88
|
/**
|
|
89
|
-
* Agent branch patterns
|
|
90
|
-
*
|
|
91
|
-
*
|
|
89
|
+
* Agent branch patterns to MERGE with the registry patterns.
|
|
90
|
+
* These patterns are merged with patterns from lumenflow.dev/registry/agent-patterns.json.
|
|
91
|
+
* Use this to add custom patterns that should work alongside the standard vendor patterns.
|
|
92
92
|
* Protected branches (mainBranch + 'master') are NEVER bypassed.
|
|
93
|
+
*
|
|
94
|
+
* WU-1089: Changed default from ['agent/*'] to [] to allow registry to be used by default.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```yaml
|
|
98
|
+
* git:
|
|
99
|
+
* agentBranchPatterns:
|
|
100
|
+
* - 'my-custom-agent/*'
|
|
101
|
+
* - 'internal-tool/*'
|
|
102
|
+
* ```
|
|
93
103
|
*/
|
|
94
|
-
agentBranchPatterns: z.array(z.string()).default([
|
|
104
|
+
agentBranchPatterns: z.array(z.string()).default([]),
|
|
105
|
+
/**
|
|
106
|
+
* Agent branch patterns that REPLACE the registry patterns entirely.
|
|
107
|
+
* When set, these patterns are used instead of fetching from the registry.
|
|
108
|
+
* The agentBranchPatterns field is ignored when this is set.
|
|
109
|
+
*
|
|
110
|
+
* Use this for strict control over which agent patterns are allowed.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```yaml
|
|
114
|
+
* git:
|
|
115
|
+
* agentBranchPatternsOverride:
|
|
116
|
+
* - 'claude/*'
|
|
117
|
+
* - 'codex/*'
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
agentBranchPatternsOverride: z.array(z.string()).optional(),
|
|
121
|
+
/**
|
|
122
|
+
* Disable fetching agent patterns from the registry (airgapped mode).
|
|
123
|
+
* When true, only uses agentBranchPatterns from config or defaults to ['agent/*'].
|
|
124
|
+
* Useful for environments without network access or strict security requirements.
|
|
125
|
+
*
|
|
126
|
+
* @default false
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```yaml
|
|
130
|
+
* git:
|
|
131
|
+
* disableAgentPatternRegistry: true
|
|
132
|
+
* agentBranchPatterns:
|
|
133
|
+
* - 'claude/*'
|
|
134
|
+
* - 'cursor/*'
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
disableAgentPatternRegistry: z.boolean().default(false),
|
|
95
138
|
});
|
|
96
139
|
/**
|
|
97
140
|
* WU (Work Unit) configuration
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Preflight validation helpers for wu:done.
|
|
3
3
|
*/
|
|
4
4
|
import { execSync as execSyncImport } from 'node:child_process';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
5
6
|
import { validatePreflight } from './wu-preflight-validators.js';
|
|
6
7
|
import { LOG_PREFIX, EMOJI, STDIO } from './wu-constants.js';
|
|
7
8
|
/**
|
|
@@ -164,8 +165,14 @@ export function validateAllPreCommitHooks(id, worktreePath = null, options = {})
|
|
|
164
165
|
if (worktreePath) {
|
|
165
166
|
execOptions.cwd = worktreePath;
|
|
166
167
|
}
|
|
168
|
+
// WU-1086: Check for .mjs extension first, fall back to .js for backwards compatibility
|
|
169
|
+
const basePath = worktreePath || '.';
|
|
170
|
+
const mjsPath = `${basePath}/tools/gates-pre-commit.mjs`;
|
|
171
|
+
const gateScript = existsSync(mjsPath)
|
|
172
|
+
? 'tools/gates-pre-commit.mjs'
|
|
173
|
+
: 'tools/gates-pre-commit.js';
|
|
167
174
|
// Run the gates-pre-commit script that contains all validation gates
|
|
168
|
-
execSyncFn(
|
|
175
|
+
execSyncFn(`node ${gateScript}`, execOptions);
|
|
169
176
|
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} All pre-commit hooks passed`);
|
|
170
177
|
return { valid: true, errors: [] };
|
|
171
178
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Core WU lifecycle tools for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"README.md"
|
|
67
67
|
],
|
|
68
68
|
"dependencies": {
|
|
69
|
+
"chalk": "^5.6.2",
|
|
69
70
|
"change-case": "^5.4.4",
|
|
70
71
|
"cli-progress": "^3.12.0",
|
|
71
72
|
"cli-table3": "^0.6.5",
|
|
@@ -91,15 +92,11 @@
|
|
|
91
92
|
"vitest": "^4.0.17"
|
|
92
93
|
},
|
|
93
94
|
"peerDependencies": {
|
|
94
|
-
"@lumenflow/memory": "1.
|
|
95
|
-
"@lumenflow/initiatives": "1.4.0"
|
|
95
|
+
"@lumenflow/memory": "1.5.0"
|
|
96
96
|
},
|
|
97
97
|
"peerDependenciesMeta": {
|
|
98
98
|
"@lumenflow/memory": {
|
|
99
99
|
"optional": true
|
|
100
|
-
},
|
|
101
|
-
"@lumenflow/initiatives": {
|
|
102
|
-
"optional": true
|
|
103
100
|
}
|
|
104
101
|
},
|
|
105
102
|
"engines": {
|