@lumenflow/core 1.3.6 → 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 +54 -0
- package/dist/agent-patterns-registry.d.ts +151 -0
- package/dist/agent-patterns-registry.js +314 -0
- package/dist/arg-parser.js +7 -0
- package/dist/branch-check.d.ts +53 -2
- package/dist/branch-check.js +123 -7
- package/dist/cli/is-agent-branch.d.ts +2 -0
- package/dist/cli/is-agent-branch.js +13 -4
- 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 +3 -0
- package/dist/index.js +6 -0
- package/dist/lumenflow-config-schema.d.ts +6 -0
- package/dist/lumenflow-config-schema.js +47 -4
- package/dist/micro-worktree.d.ts +32 -0
- package/dist/micro-worktree.js +59 -1
- package/dist/wu-done-preflight.js +8 -1
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -68,6 +68,60 @@ const exists = await worktrees.exists('/path/to/worktree');
|
|
|
68
68
|
await worktrees.remove('worktrees/operations-wu-123');
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
### Agent Branch Patterns
|
|
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, and can be configured via `.lumenflow.config.yaml`.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { isAgentBranch, isAgentBranchWithDetails, resolveAgentPatterns } from '@lumenflow/core';
|
|
77
|
+
|
|
78
|
+
// Check if branch can bypass worktree requirements (async, uses registry)
|
|
79
|
+
if (await isAgentBranch('claude/session-12345')) {
|
|
80
|
+
console.log('Agent branch - bypass allowed');
|
|
81
|
+
}
|
|
82
|
+
|
|
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);
|
|
97
|
+
|
|
98
|
+
// Synchronous version (uses local config only, no registry fetch)
|
|
99
|
+
import { isAgentBranchSync } from '@lumenflow/core';
|
|
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
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Protected branches (main, master, lane/\*) are **never** bypassed, regardless of patterns.
|
|
124
|
+
|
|
71
125
|
## API Reference
|
|
72
126
|
|
|
73
127
|
### GitAdapter
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Patterns Registry
|
|
3
|
+
*
|
|
4
|
+
* Central registry for AI agent branch patterns (claude/*, codex/*, copilot/*, cursor/*, etc.)
|
|
5
|
+
* that bypass worktree requirements. Static JSON served from lumenflow.dev,
|
|
6
|
+
* cached locally for 7 days with fallback to defaults.
|
|
7
|
+
*
|
|
8
|
+
* WU-1089: Added merge/override/airgapped modes via resolveAgentPatterns()
|
|
9
|
+
*
|
|
10
|
+
* @module agent-patterns-registry
|
|
11
|
+
*/
|
|
12
|
+
/** Default agent branch patterns (narrow: just agent/*) */
|
|
13
|
+
export declare const DEFAULT_AGENT_PATTERNS: string[];
|
|
14
|
+
/** Remote registry URL */
|
|
15
|
+
export declare const REGISTRY_URL = "https://lumenflow.dev/registry/agent-patterns.json";
|
|
16
|
+
/** Cache TTL: 7 days in milliseconds */
|
|
17
|
+
export declare const CACHE_TTL_MS: number;
|
|
18
|
+
/**
|
|
19
|
+
* Options for getAgentPatterns
|
|
20
|
+
*/
|
|
21
|
+
export interface GetAgentPatternsOptions {
|
|
22
|
+
/** Override cache directory (default: ~/.lumenflow/cache) */
|
|
23
|
+
cacheDir?: string;
|
|
24
|
+
/** Fetch timeout in milliseconds (default: 5000) */
|
|
25
|
+
timeoutMs?: number;
|
|
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
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get the default cache directory
|
|
65
|
+
*
|
|
66
|
+
* @returns Path to cache directory (~/.lumenflow/cache or LUMENFLOW_HOME/cache)
|
|
67
|
+
*/
|
|
68
|
+
export declare function getCacheDir(): string;
|
|
69
|
+
/**
|
|
70
|
+
* Get agent branch patterns from registry with caching
|
|
71
|
+
*
|
|
72
|
+
* Fetches patterns from remote registry, caches locally for 7 days,
|
|
73
|
+
* and falls back to defaults on failure.
|
|
74
|
+
*
|
|
75
|
+
* @param options - Options
|
|
76
|
+
* @returns Array of agent branch patterns (glob format)
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const patterns = await getAgentPatterns();
|
|
81
|
+
* // ['claude/*', 'codex/*', 'copilot/*', 'cursor/*', 'agent/*']
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
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>;
|
|
146
|
+
/**
|
|
147
|
+
* Clear the in-memory cache
|
|
148
|
+
*
|
|
149
|
+
* Used primarily for testing.
|
|
150
|
+
*/
|
|
151
|
+
export declare function clearCache(): void;
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Patterns Registry
|
|
3
|
+
*
|
|
4
|
+
* Central registry for AI agent branch patterns (claude/*, codex/*, copilot/*, cursor/*, etc.)
|
|
5
|
+
* that bypass worktree requirements. Static JSON served from lumenflow.dev,
|
|
6
|
+
* cached locally for 7 days with fallback to defaults.
|
|
7
|
+
*
|
|
8
|
+
* WU-1089: Added merge/override/airgapped modes via resolveAgentPatterns()
|
|
9
|
+
*
|
|
10
|
+
* @module agent-patterns-registry
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import * as os from 'node:os';
|
|
15
|
+
/** Default agent branch patterns (narrow: just agent/*) */
|
|
16
|
+
export const DEFAULT_AGENT_PATTERNS = ['agent/*'];
|
|
17
|
+
/** Remote registry URL */
|
|
18
|
+
export const REGISTRY_URL = 'https://lumenflow.dev/registry/agent-patterns.json';
|
|
19
|
+
/** Cache TTL: 7 days in milliseconds */
|
|
20
|
+
export const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
21
|
+
/** Cache file name */
|
|
22
|
+
const CACHE_FILE_NAME = 'agent-patterns-cache.json';
|
|
23
|
+
/** Default fetch timeout in milliseconds */
|
|
24
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
25
|
+
/** In-memory cache for patterns */
|
|
26
|
+
let memoryCache = null;
|
|
27
|
+
/** In-memory cache timestamp */
|
|
28
|
+
let memoryCacheTime = 0;
|
|
29
|
+
/**
|
|
30
|
+
* Get the default cache directory
|
|
31
|
+
*
|
|
32
|
+
* @returns Path to cache directory (~/.lumenflow/cache or LUMENFLOW_HOME/cache)
|
|
33
|
+
*/
|
|
34
|
+
export function getCacheDir() {
|
|
35
|
+
const lumenflowHome = process.env.LUMENFLOW_HOME;
|
|
36
|
+
if (lumenflowHome) {
|
|
37
|
+
return path.join(lumenflowHome, 'cache');
|
|
38
|
+
}
|
|
39
|
+
return path.join(os.homedir(), '.lumenflow', 'cache');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate registry response
|
|
43
|
+
*
|
|
44
|
+
* @param data - Data to validate
|
|
45
|
+
* @returns True if valid registry response
|
|
46
|
+
*/
|
|
47
|
+
function isValidRegistryResponse(data) {
|
|
48
|
+
if (!data || typeof data !== 'object')
|
|
49
|
+
return false;
|
|
50
|
+
const obj = data;
|
|
51
|
+
if (!Array.isArray(obj.patterns))
|
|
52
|
+
return false;
|
|
53
|
+
if (!obj.patterns.every((p) => typeof p === 'string'))
|
|
54
|
+
return false;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Read cache from disk
|
|
59
|
+
*
|
|
60
|
+
* @param cacheDir - Cache directory
|
|
61
|
+
* @returns Cache data or null if not found/invalid
|
|
62
|
+
*/
|
|
63
|
+
function readCache(cacheDir) {
|
|
64
|
+
try {
|
|
65
|
+
const cacheFile = path.join(cacheDir, CACHE_FILE_NAME);
|
|
66
|
+
if (!fs.existsSync(cacheFile))
|
|
67
|
+
return null;
|
|
68
|
+
const content = fs.readFileSync(cacheFile, 'utf8');
|
|
69
|
+
const data = JSON.parse(content);
|
|
70
|
+
// Validate cache structure
|
|
71
|
+
if (!Array.isArray(data.patterns))
|
|
72
|
+
return null;
|
|
73
|
+
if (typeof data.fetchedAt !== 'number')
|
|
74
|
+
return null;
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Write cache to disk
|
|
83
|
+
*
|
|
84
|
+
* @param cacheDir - Cache directory
|
|
85
|
+
* @param data - Data to cache
|
|
86
|
+
*/
|
|
87
|
+
function writeCache(cacheDir, data) {
|
|
88
|
+
try {
|
|
89
|
+
// Ensure cache directory exists
|
|
90
|
+
if (!fs.existsSync(cacheDir)) {
|
|
91
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
const cacheFile = path.join(cacheDir, CACHE_FILE_NAME);
|
|
94
|
+
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Fail silently - cache is optional
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fetch patterns from remote registry with timeout
|
|
102
|
+
*
|
|
103
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
104
|
+
* @returns Registry response or null on failure
|
|
105
|
+
*/
|
|
106
|
+
async function fetchFromRegistry(timeoutMs) {
|
|
107
|
+
try {
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
110
|
+
const response = await fetch(REGISTRY_URL, {
|
|
111
|
+
signal: controller.signal,
|
|
112
|
+
headers: {
|
|
113
|
+
Accept: 'application/json',
|
|
114
|
+
'User-Agent': 'lumenflow-core',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
clearTimeout(timeoutId);
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
if (!isValidRegistryResponse(data)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
return data;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get agent branch patterns from registry with caching
|
|
133
|
+
*
|
|
134
|
+
* Fetches patterns from remote registry, caches locally for 7 days,
|
|
135
|
+
* and falls back to defaults on failure.
|
|
136
|
+
*
|
|
137
|
+
* @param options - Options
|
|
138
|
+
* @returns Array of agent branch patterns (glob format)
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const patterns = await getAgentPatterns();
|
|
143
|
+
* // ['claude/*', 'codex/*', 'copilot/*', 'cursor/*', 'agent/*']
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export async function getAgentPatterns(options = {}) {
|
|
147
|
+
const { cacheDir = getCacheDir(), timeoutMs = DEFAULT_TIMEOUT_MS } = options;
|
|
148
|
+
// Check memory cache first (fast path)
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
if (memoryCache && now - memoryCacheTime < CACHE_TTL_MS) {
|
|
151
|
+
return memoryCache;
|
|
152
|
+
}
|
|
153
|
+
// Check disk cache
|
|
154
|
+
const diskCache = readCache(cacheDir);
|
|
155
|
+
if (diskCache && now - diskCache.fetchedAt < CACHE_TTL_MS) {
|
|
156
|
+
// Fresh disk cache - use it and update memory cache
|
|
157
|
+
memoryCache = diskCache.patterns;
|
|
158
|
+
memoryCacheTime = diskCache.fetchedAt;
|
|
159
|
+
return diskCache.patterns;
|
|
160
|
+
}
|
|
161
|
+
// Cache is stale or missing - try to fetch
|
|
162
|
+
const registryData = await fetchFromRegistry(timeoutMs);
|
|
163
|
+
if (registryData) {
|
|
164
|
+
// Update caches
|
|
165
|
+
const cacheData = {
|
|
166
|
+
version: registryData.version,
|
|
167
|
+
patterns: registryData.patterns,
|
|
168
|
+
fetchedAt: now,
|
|
169
|
+
};
|
|
170
|
+
writeCache(cacheDir, cacheData);
|
|
171
|
+
memoryCache = registryData.patterns;
|
|
172
|
+
memoryCacheTime = now;
|
|
173
|
+
return registryData.patterns;
|
|
174
|
+
}
|
|
175
|
+
// Fetch failed - try stale cache as fallback
|
|
176
|
+
if (diskCache) {
|
|
177
|
+
memoryCache = diskCache.patterns;
|
|
178
|
+
memoryCacheTime = diskCache.fetchedAt; // Keep stale time
|
|
179
|
+
return diskCache.patterns;
|
|
180
|
+
}
|
|
181
|
+
// No cache, no network - use defaults
|
|
182
|
+
return DEFAULT_AGENT_PATTERNS;
|
|
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
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Clear the in-memory cache
|
|
308
|
+
*
|
|
309
|
+
* Used primarily for testing.
|
|
310
|
+
*/
|
|
311
|
+
export function clearCache() {
|
|
312
|
+
memoryCache = null;
|
|
313
|
+
memoryCacheTime = 0;
|
|
314
|
+
}
|
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,16 +4,67 @@
|
|
|
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
|
+
*
|
|
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
|
|
20
|
+
*
|
|
21
|
+
* @param branch - Branch name to check
|
|
22
|
+
* @returns Promise<true> if branch matches agent patterns
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* if (await isAgentBranch('claude/session-123')) {
|
|
27
|
+
* // Allow bypass for agent branch
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
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
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Synchronous version of isAgentBranch for backwards compatibility.
|
|
56
|
+
*
|
|
57
|
+
* Uses only local config patterns or defaults - does NOT fetch from registry.
|
|
58
|
+
* Prefer async isAgentBranch() when possible.
|
|
59
|
+
*
|
|
60
|
+
* WU-1089: Updated to respect override and disable flags, but cannot fetch from registry.
|
|
12
61
|
*
|
|
13
62
|
* @param branch - Branch name to check
|
|
14
63
|
* @returns True if branch matches agent patterns
|
|
64
|
+
*
|
|
65
|
+
* @deprecated Use async isAgentBranch() instead for registry support
|
|
15
66
|
*/
|
|
16
|
-
export declare function
|
|
67
|
+
export declare function isAgentBranchSync(branch: string | null | undefined): boolean;
|
|
17
68
|
/**
|
|
18
69
|
* Check if headless mode is allowed (guarded).
|
|
19
70
|
* Requires LUMENFLOW_HEADLESS=1 AND (LUMENFLOW_ADMIN=1 OR CI truthy OR GITHUB_ACTIONS truthy)
|