@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.
@@ -4,12 +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
- /** Default agent branch patterns (narrow: just agent/*) */
12
- const DEFAULT_AGENT_BRANCH_PATTERNS = ['agent/*'];
13
+ import { resolveAgentPatterns, DEFAULT_AGENT_PATTERNS, } from './agent-patterns-registry.js';
13
14
  /** Legacy protected branch (always protected regardless of mainBranch setting) */
14
15
  const LEGACY_PROTECTED = 'master';
15
16
  /**
@@ -36,12 +37,121 @@ function getProtectedBranches() {
36
37
  }
37
38
  /**
38
39
  * Check if branch is an agent branch that can bypass worktree requirements.
39
- * Uses the existing config loader (which handles caching/validation).
40
+ *
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
46
+ *
47
+ * @param branch - Branch name to check
48
+ * @returns Promise<true> if branch matches agent patterns
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * if (await isAgentBranch('claude/session-123')) {
53
+ * // Allow bypass for agent branch
54
+ * }
55
+ * ```
56
+ */
57
+ export async function isAgentBranch(branch) {
58
+ // Fail-closed: no branch = protected
59
+ if (!branch)
60
+ return false;
61
+ // Detached HEAD = protected (fail-closed)
62
+ if (branch === 'HEAD')
63
+ return false;
64
+ // Load config (uses existing loader with caching)
65
+ const config = getConfig();
66
+ const protectedBranches = getProtectedBranches();
67
+ // Protected branches are NEVER bypassed (mainBranch + 'master')
68
+ if (protectedBranches.includes(branch))
69
+ return false;
70
+ // LumenFlow lane branches require worktrees (uses config's laneBranchPrefix)
71
+ if (getLaneBranchPattern().test(branch))
72
+ return false;
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
+ };
107
+ }
108
+ // Detached HEAD = protected (fail-closed)
109
+ if (branch === 'HEAD') {
110
+ return {
111
+ isMatch: false,
112
+ patternResult: { patterns: [], source: 'defaults', registryFetched: false },
113
+ };
114
+ }
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 };
140
+ }
141
+ /**
142
+ * Synchronous version of isAgentBranch for backwards compatibility.
143
+ *
144
+ * Uses only local config patterns or defaults - does NOT fetch from registry.
145
+ * Prefer async isAgentBranch() when possible.
146
+ *
147
+ * WU-1089: Updated to respect override and disable flags, but cannot fetch from registry.
40
148
  *
41
149
  * @param branch - Branch name to check
42
150
  * @returns True if branch matches agent patterns
151
+ *
152
+ * @deprecated Use async isAgentBranch() instead for registry support
43
153
  */
44
- export function isAgentBranch(branch) {
154
+ export function isAgentBranchSync(branch) {
45
155
  // Fail-closed: no branch = protected
46
156
  if (!branch)
47
157
  return false;
@@ -51,15 +161,21 @@ export function isAgentBranch(branch) {
51
161
  // Load config (uses existing loader with caching)
52
162
  const config = getConfig();
53
163
  const protectedBranches = getProtectedBranches();
54
- const patterns = config?.git?.agentBranchPatterns?.length > 0
55
- ? config.git.agentBranchPatterns
56
- : DEFAULT_AGENT_BRANCH_PATTERNS;
57
164
  // Protected branches are NEVER bypassed (mainBranch + 'master')
58
165
  if (protectedBranches.includes(branch))
59
166
  return false;
60
167
  // LumenFlow lane branches require worktrees (uses config's laneBranchPrefix)
61
168
  if (getLaneBranchPattern().test(branch))
62
169
  return false;
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
176
+ const patterns = config?.git?.agentBranchPatterns?.length > 0
177
+ ? config.git.agentBranchPatterns
178
+ : DEFAULT_AGENT_PATTERNS;
63
179
  // Use micromatch for proper glob matching
64
180
  return micromatch.isMatch(branch, patterns);
65
181
  }
@@ -3,6 +3,8 @@
3
3
  * CLI helper for bash hooks to check if branch is an agent branch.
4
4
  * Uses the same isAgentBranch() logic as TypeScript code.
5
5
  *
6
+ * Now async to support registry pattern lookup with fetch + cache.
7
+ *
6
8
  * Usage: node dist/cli/is-agent-branch.js [branch-name]
7
9
  * Exit codes: 0 = agent branch (allowed), 1 = not agent branch (protected)
8
10
  *
@@ -3,13 +3,22 @@
3
3
  * CLI helper for bash hooks to check if branch is an agent branch.
4
4
  * Uses the same isAgentBranch() logic as TypeScript code.
5
5
  *
6
+ * Now async to support registry pattern lookup with fetch + cache.
7
+ *
6
8
  * Usage: node dist/cli/is-agent-branch.js [branch-name]
7
9
  * Exit codes: 0 = agent branch (allowed), 1 = not agent branch (protected)
8
10
  *
9
11
  * @module cli/is-agent-branch
10
12
  */
11
13
  import { isAgentBranch } from '../branch-check.js';
12
- const branch = process.argv[2] || null;
13
- const result = isAgentBranch(branch);
14
- // Exit 0 = agent branch (truthy), Exit 1 = not agent branch
15
- process.exit(result ? 0 : 1);
14
+ async function main() {
15
+ const branch = process.argv[2] || null;
16
+ const result = await isAgentBranch(branch);
17
+ // Exit 0 = agent branch (truthy), Exit 1 = not agent branch
18
+ process.exit(result ? 0 : 1);
19
+ }
20
+ main().catch((error) => {
21
+ console.error('Error checking agent branch:', error.message);
22
+ // Fail-closed: error = not allowed
23
+ process.exit(1);
24
+ });
@@ -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
+ }
@@ -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
- // Optional import from @lumenflow/initiatives - if not available, provide stub
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';
@@ -43,6 +44,8 @@ export * from './lumenflow-config.js';
43
44
  export * from './lumenflow-config-schema.js';
44
45
  export * from './gates-config.js';
45
46
  export * from './branch-check.js';
47
+ export * from './agent-patterns-registry.js';
46
48
  export * from './lumenflow-home.js';
47
49
  export * from './force-bypass-audit.js';
48
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
@@ -65,9 +67,13 @@ export * from './lumenflow-config-schema.js';
65
67
  export * from './gates-config.js';
66
68
  // Branch check utilities
67
69
  export * from './branch-check.js';
70
+ // WU-1082: Agent patterns registry (fetch + cache)
71
+ export * from './agent-patterns-registry.js';
68
72
  // WU-1062: External plan storage
69
73
  export * from './lumenflow-home.js';
70
74
  // WU-1070: Force bypass audit logging
71
75
  export * from './force-bypass-audit.js';
72
76
  // WU-1075: LumenFlow directory paths (exported from wu-constants)
73
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 that bypass worktree requirements.
90
- * Branches matching these glob patterns can work in the main checkout.
91
- * Default: ['agent/*'] - narrow default, add vendor patterns as needed.
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(['agent/*']),
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
@@ -27,6 +27,7 @@
27
27
  * @see {@link tools/wu-edit.mjs} - Spec edits (WU-1274)
28
28
  * @see {@link tools/initiative-create.mjs} - Initiative creation (WU-1439)
29
29
  */
30
+ import type { GitAdapter } from './git-adapter.js';
30
31
  /**
31
32
  * Maximum retry attempts for ff-only merge when main moves
32
33
  *
@@ -34,6 +35,18 @@
34
35
  * concurrently. Each retry fetches latest main and rebases.
35
36
  */
36
37
  export declare const MAX_MERGE_RETRIES = 3;
38
+ /**
39
+ * Environment variable name for LUMENFLOW_FORCE bypass
40
+ *
41
+ * WU-1081: Exported for use in micro-worktree push operations.
42
+ */
43
+ export declare const LUMENFLOW_FORCE_ENV = "LUMENFLOW_FORCE";
44
+ /**
45
+ * Environment variable name for LUMENFLOW_FORCE_REASON audit trail
46
+ *
47
+ * WU-1081: Exported for use in micro-worktree push operations.
48
+ */
49
+ export declare const LUMENFLOW_FORCE_REASON_ENV = "LUMENFLOW_FORCE_REASON";
37
50
  /**
38
51
  * Default log prefix for micro-worktree operations
39
52
  *
@@ -135,6 +148,25 @@ export declare function formatFiles(files: any, worktreePath: any, logPrefix?: s
135
148
  * @throws {Error} If merge fails after all retries
136
149
  */
137
150
  export declare function mergeWithRetry(tempBranchName: any, microWorktreePath: any, logPrefix?: string): Promise<void>;
151
+ /**
152
+ * Push using refspec with LUMENFLOW_FORCE to bypass pre-push hooks
153
+ *
154
+ * WU-1081: Micro-worktree pushes to origin/main need to bypass pre-push hooks
155
+ * because they operate from temp branches in /tmp directories, which would
156
+ * otherwise be blocked by hook validation.
157
+ *
158
+ * Sets LUMENFLOW_FORCE=1 and LUMENFLOW_FORCE_REASON during the push,
159
+ * then restores original environment values (even on error).
160
+ *
161
+ * @param {GitAdapter} gitAdapter - GitAdapter instance to use for push
162
+ * @param {string} remote - Remote name (e.g., 'origin')
163
+ * @param {string} localRef - Local ref to push (e.g., 'tmp/wu-claim/wu-123')
164
+ * @param {string} remoteRef - Remote ref to update (e.g., 'main')
165
+ * @param {string} reason - Audit reason for the LUMENFLOW_FORCE bypass
166
+ * @returns {Promise<void>}
167
+ * @throws {Error} If push fails (env vars still restored)
168
+ */
169
+ export declare function pushRefspecWithForce(gitAdapter: GitAdapter, remote: string, localRef: string, remoteRef: string, reason: string): Promise<void>;
138
170
  /**
139
171
  * Execute an operation in a micro-worktree with full isolation
140
172
  *