@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/dist/branch-check.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+
}
|
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';
|
|
@@ -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
|
|
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
|
package/dist/micro-worktree.d.ts
CHANGED
|
@@ -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
|
*
|