@lumenflow/core 1.0.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/arg-parser.d.ts +6 -0
- package/dist/arg-parser.js +57 -1
- package/dist/backlog-generator.js +1 -1
- package/dist/backlog-sync-validator.js +3 -3
- package/dist/branch-check.d.ts +21 -0
- package/dist/branch-check.js +77 -0
- package/dist/cli/is-agent-branch.d.ts +11 -0
- package/dist/cli/is-agent-branch.js +15 -0
- package/dist/code-paths-overlap.js +2 -2
- package/dist/error-handler.d.ts +1 -0
- package/dist/error-handler.js +4 -1
- package/dist/git-adapter.d.ts +23 -0
- package/dist/git-adapter.js +38 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/lane-checker.d.ts +36 -3
- package/dist/lane-checker.js +128 -17
- package/dist/lane-inference.js +3 -4
- package/dist/lumenflow-config-schema.d.ts +125 -0
- package/dist/lumenflow-config-schema.js +76 -0
- package/dist/lumenflow-home.d.ts +130 -0
- package/dist/lumenflow-home.js +208 -0
- package/dist/manual-test-validator.js +1 -1
- package/dist/orchestration-rules.d.ts +1 -1
- package/dist/orchestration-rules.js +2 -2
- package/dist/orphan-detector.d.ts +16 -0
- package/dist/orphan-detector.js +24 -0
- package/dist/path-classifiers.d.ts +1 -1
- package/dist/path-classifiers.js +1 -1
- package/dist/rebase-artifact-cleanup.d.ts +17 -0
- package/dist/rebase-artifact-cleanup.js +49 -8
- package/dist/spawn-strategy.d.ts +53 -0
- package/dist/spawn-strategy.js +106 -0
- package/dist/spec-branch-helpers.d.ts +118 -0
- package/dist/spec-branch-helpers.js +192 -0
- package/dist/stamp-utils.d.ts +10 -0
- package/dist/stamp-utils.js +17 -19
- package/dist/token-counter.js +2 -2
- package/dist/wu-consistency-checker.d.ts +2 -0
- package/dist/wu-consistency-checker.js +40 -6
- package/dist/wu-constants.d.ts +98 -3
- package/dist/wu-constants.js +108 -3
- package/dist/wu-create-validators.d.ts +40 -2
- package/dist/wu-create-validators.js +76 -2
- package/dist/wu-done-branch-only.js +9 -0
- package/dist/wu-done-branch-utils.d.ts +10 -0
- package/dist/wu-done-branch-utils.js +31 -0
- package/dist/wu-done-cleanup.d.ts +8 -0
- package/dist/wu-done-cleanup.js +122 -0
- package/dist/wu-done-docs-generate.d.ts +73 -0
- package/dist/wu-done-docs-generate.js +108 -0
- package/dist/wu-done-docs-only.d.ts +20 -0
- package/dist/wu-done-docs-only.js +65 -0
- package/dist/wu-done-errors.d.ts +17 -0
- package/dist/wu-done-errors.js +24 -0
- package/dist/wu-done-inputs.d.ts +12 -0
- package/dist/wu-done-inputs.js +51 -0
- package/dist/wu-done-metadata.d.ts +100 -0
- package/dist/wu-done-metadata.js +193 -0
- package/dist/wu-done-paths.d.ts +69 -0
- package/dist/wu-done-paths.js +237 -0
- package/dist/wu-done-preflight.d.ts +48 -0
- package/dist/wu-done-preflight.js +185 -0
- package/dist/wu-done-validation.d.ts +82 -0
- package/dist/wu-done-validation.js +340 -0
- package/dist/wu-done-validators.d.ts +13 -409
- package/dist/wu-done-validators.js +9 -1225
- package/dist/wu-done-worktree.d.ts +0 -1
- package/dist/wu-done-worktree.js +24 -30
- package/dist/wu-schema.js +4 -4
- package/dist/wu-spawn-skills.d.ts +19 -0
- package/dist/wu-spawn-skills.js +148 -0
- package/dist/wu-spawn.d.ts +17 -4
- package/dist/wu-spawn.js +113 -177
- package/dist/wu-validation.d.ts +1 -0
- package/dist/wu-validation.js +21 -1
- package/dist/wu-validator.d.ts +51 -0
- package/dist/wu-validator.js +108 -0
- package/package.json +12 -8
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LumenFlow Home Directory Resolution
|
|
3
|
+
*
|
|
4
|
+
* WU-1062: External plan storage and no-main-write mode
|
|
5
|
+
*
|
|
6
|
+
* Provides helpers for resolving the $LUMENFLOW_HOME directory and related paths.
|
|
7
|
+
* Plans are stored externally in $LUMENFLOW_HOME/plans/ instead of in the repo.
|
|
8
|
+
*
|
|
9
|
+
* Default: ~/.lumenflow/
|
|
10
|
+
* Override: Set $LUMENFLOW_HOME environment variable
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
/**
|
|
17
|
+
* Environment variable name for LumenFlow home directory
|
|
18
|
+
*/
|
|
19
|
+
export const LUMENFLOW_HOME_ENV = 'LUMENFLOW_HOME';
|
|
20
|
+
/**
|
|
21
|
+
* Default LumenFlow home directory name
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_LUMENFLOW_DIR = '.lumenflow';
|
|
24
|
+
/**
|
|
25
|
+
* Plans subdirectory name
|
|
26
|
+
*/
|
|
27
|
+
export const PLANS_SUBDIR = 'plans';
|
|
28
|
+
/**
|
|
29
|
+
* Custom protocol for external LumenFlow paths
|
|
30
|
+
*/
|
|
31
|
+
export const LUMENFLOW_PROTOCOL = 'lumenflow://';
|
|
32
|
+
/**
|
|
33
|
+
* Environment variable prefix for spec_refs
|
|
34
|
+
*/
|
|
35
|
+
export const LUMENFLOW_HOME_VAR_PREFIX = '$LUMENFLOW_HOME';
|
|
36
|
+
/**
|
|
37
|
+
* Expand ~ to user's home directory
|
|
38
|
+
*
|
|
39
|
+
* @param {string} path - Path that may contain ~
|
|
40
|
+
* @returns {string} Expanded path
|
|
41
|
+
*/
|
|
42
|
+
function expandTilde(path) {
|
|
43
|
+
if (path.startsWith('~/')) {
|
|
44
|
+
return join(homedir(), path.slice(2));
|
|
45
|
+
}
|
|
46
|
+
if (path === '~') {
|
|
47
|
+
return homedir();
|
|
48
|
+
}
|
|
49
|
+
return path;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Remove trailing slashes from path
|
|
53
|
+
*
|
|
54
|
+
* @param {string} path - Path that may have trailing slashes
|
|
55
|
+
* @returns {string} Path without trailing slashes
|
|
56
|
+
*/
|
|
57
|
+
function removeTrailingSlash(path) {
|
|
58
|
+
return path.replace(/\/+$/, '');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the LumenFlow home directory path
|
|
62
|
+
*
|
|
63
|
+
* Resolution order:
|
|
64
|
+
* 1. $LUMENFLOW_HOME environment variable (with ~ expansion)
|
|
65
|
+
* 2. ~/.lumenflow/ default
|
|
66
|
+
*
|
|
67
|
+
* @returns {string} Absolute path to LumenFlow home directory
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // With LUMENFLOW_HOME=/custom/path
|
|
71
|
+
* getLumenflowHome() // '/custom/path'
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // With LUMENFLOW_HOME=~/.custom-lumenflow
|
|
75
|
+
* getLumenflowHome() // '/home/user/.custom-lumenflow'
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Without LUMENFLOW_HOME set
|
|
79
|
+
* getLumenflowHome() // '/home/user/.lumenflow'
|
|
80
|
+
*/
|
|
81
|
+
export function getLumenflowHome() {
|
|
82
|
+
const envValue = process.env[LUMENFLOW_HOME_ENV];
|
|
83
|
+
if (envValue) {
|
|
84
|
+
const expanded = expandTilde(envValue);
|
|
85
|
+
return removeTrailingSlash(expanded);
|
|
86
|
+
}
|
|
87
|
+
return join(homedir(), DEFAULT_LUMENFLOW_DIR);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get the plans directory path
|
|
91
|
+
*
|
|
92
|
+
* Plans are stored in $LUMENFLOW_HOME/plans/
|
|
93
|
+
*
|
|
94
|
+
* @returns {string} Absolute path to plans directory
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* getPlansDir() // '/home/user/.lumenflow/plans'
|
|
98
|
+
*/
|
|
99
|
+
export function getPlansDir() {
|
|
100
|
+
return join(getLumenflowHome(), PLANS_SUBDIR);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if a path is an external (non-repo) path
|
|
104
|
+
*
|
|
105
|
+
* External paths include:
|
|
106
|
+
* - Paths starting with ~/
|
|
107
|
+
* - Paths starting with $LUMENFLOW_HOME
|
|
108
|
+
* - Paths using lumenflow:// protocol
|
|
109
|
+
* - Absolute paths (starting with /)
|
|
110
|
+
*
|
|
111
|
+
* @param {string} path - Path to check
|
|
112
|
+
* @returns {boolean} True if path is external
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* isExternalPath('~/.lumenflow/plans/plan.md') // true
|
|
116
|
+
* isExternalPath('$LUMENFLOW_HOME/plans/plan.md') // true
|
|
117
|
+
* isExternalPath('lumenflow://plans/plan.md') // true
|
|
118
|
+
* isExternalPath('/home/user/.lumenflow/plans/plan.md') // true
|
|
119
|
+
* isExternalPath('docs/04-operations/plans/plan.md') // false
|
|
120
|
+
*/
|
|
121
|
+
export function isExternalPath(path) {
|
|
122
|
+
// Check for tilde-prefixed paths
|
|
123
|
+
if (path.startsWith('~/')) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
// Check for environment variable reference
|
|
127
|
+
if (path.startsWith(LUMENFLOW_HOME_VAR_PREFIX)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// Check for lumenflow:// protocol
|
|
131
|
+
if (path.startsWith(LUMENFLOW_PROTOCOL)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
// Check for absolute paths (starting with /)
|
|
135
|
+
if (path.startsWith('/')) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Normalize a spec_ref path by expanding variables and protocols
|
|
142
|
+
*
|
|
143
|
+
* Expands:
|
|
144
|
+
* - lumenflow://path -> $LUMENFLOW_HOME/path
|
|
145
|
+
* - ~/path -> /home/user/path
|
|
146
|
+
* - $LUMENFLOW_HOME/path -> actual LUMENFLOW_HOME value
|
|
147
|
+
*
|
|
148
|
+
* Repo-relative paths are returned unchanged.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} specRef - Spec reference path
|
|
151
|
+
* @returns {string} Normalized absolute path or unchanged relative path
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* normalizeSpecRef('lumenflow://plans/WU-1062-plan.md')
|
|
155
|
+
* // '/home/user/.lumenflow/plans/WU-1062-plan.md'
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* normalizeSpecRef('docs/04-operations/plans/plan.md')
|
|
159
|
+
* // 'docs/04-operations/plans/plan.md' (unchanged)
|
|
160
|
+
*/
|
|
161
|
+
export function normalizeSpecRef(specRef) {
|
|
162
|
+
// Handle lumenflow:// protocol
|
|
163
|
+
if (specRef.startsWith(LUMENFLOW_PROTOCOL)) {
|
|
164
|
+
const relativePath = specRef.slice(LUMENFLOW_PROTOCOL.length);
|
|
165
|
+
return join(getLumenflowHome(), relativePath);
|
|
166
|
+
}
|
|
167
|
+
// Handle $LUMENFLOW_HOME variable
|
|
168
|
+
if (specRef.startsWith(LUMENFLOW_HOME_VAR_PREFIX)) {
|
|
169
|
+
const relativePath = specRef.slice(LUMENFLOW_HOME_VAR_PREFIX.length);
|
|
170
|
+
// Remove leading slash if present
|
|
171
|
+
const cleanPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
|
|
172
|
+
return join(getLumenflowHome(), cleanPath);
|
|
173
|
+
}
|
|
174
|
+
// Handle tilde expansion
|
|
175
|
+
if (specRef.startsWith('~/')) {
|
|
176
|
+
return expandTilde(specRef);
|
|
177
|
+
}
|
|
178
|
+
// Return relative paths unchanged
|
|
179
|
+
return specRef;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the full path for a plan file given a WU ID
|
|
183
|
+
*
|
|
184
|
+
* @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
|
|
185
|
+
* @returns {string} Full path to the plan file
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* getPlanPath('WU-1062')
|
|
189
|
+
* // '/home/user/.lumenflow/plans/WU-1062-plan.md'
|
|
190
|
+
*/
|
|
191
|
+
export function getPlanPath(wuId) {
|
|
192
|
+
const filename = `${wuId}-plan.md`;
|
|
193
|
+
return join(getPlansDir(), filename);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get the lumenflow:// protocol reference for a plan
|
|
197
|
+
*
|
|
198
|
+
* @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
|
|
199
|
+
* @returns {string} Protocol reference (e.g., 'lumenflow://plans/WU-1062-plan.md')
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* getPlanProtocolRef('WU-1062')
|
|
203
|
+
* // 'lumenflow://plans/WU-1062-plan.md'
|
|
204
|
+
*/
|
|
205
|
+
export function getPlanProtocolRef(wuId) {
|
|
206
|
+
const filename = `${wuId}-plan.md`;
|
|
207
|
+
return `${LUMENFLOW_PROTOCOL}${PLANS_SUBDIR}/${filename}`;
|
|
208
|
+
}
|
|
@@ -20,7 +20,7 @@ import { TEST_TYPES, WU_TYPES } from './wu-constants.js';
|
|
|
20
20
|
* Code file extensions that require automated tests.
|
|
21
21
|
* @constant {string[]}
|
|
22
22
|
*/
|
|
23
|
-
const CODE_EXTENSIONS = Object.freeze(['.js', '.ts', '.tsx', '.
|
|
23
|
+
const CODE_EXTENSIONS = Object.freeze(['.js', '.ts', '.tsx', '.mjs']);
|
|
24
24
|
/**
|
|
25
25
|
* Non-code file extensions (documentation, data, config).
|
|
26
26
|
* @constant {string[]}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module orchestration-rules
|
|
8
8
|
* @see {@link ./domain/orchestration.constants.mjs} - MANDATORY_TRIGGERS patterns
|
|
9
|
-
* @see {@link ../../../
|
|
9
|
+
* @see {@link ../../../docs/04-operations/_frameworks/lumenflow/agent/onboarding/agent-selection-guide.md} - Agent selection rules
|
|
10
10
|
*/
|
|
11
11
|
import { type MandatoryAgentName } from './domain/orchestration.constants.js';
|
|
12
12
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module orchestration-rules
|
|
8
8
|
* @see {@link ./domain/orchestration.constants.mjs} - MANDATORY_TRIGGERS patterns
|
|
9
|
-
* @see {@link ../../../
|
|
9
|
+
* @see {@link ../../../docs/04-operations/_frameworks/lumenflow/agent/onboarding/agent-selection-guide.md} - Agent selection rules
|
|
10
10
|
*/
|
|
11
11
|
import { minimatch } from 'minimatch';
|
|
12
12
|
import { MANDATORY_TRIGGERS } from './domain/orchestration.constants.js';
|
|
@@ -162,7 +162,7 @@ export function buildMandatoryAgentsErrorMessage(wuId, missingAgents, codePaths)
|
|
|
162
162
|
lines.push('To bypass (NOT RECOMMENDED for PHI/auth work):');
|
|
163
163
|
lines.push(' Remove --require-agents flag from wu:done command');
|
|
164
164
|
lines.push('');
|
|
165
|
-
lines.push('See:
|
|
165
|
+
lines.push('See: docs/04-operations/_frameworks/lumenflow/agent/onboarding/agent-selection-guide.md for agent invocation guidance');
|
|
166
166
|
lines.push('='.repeat(70));
|
|
167
167
|
return lines.join('\n');
|
|
168
168
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* @see {@link tools/wu-claim.mjs} - Pre-flight orphan check
|
|
19
19
|
* @see {@link tools/lib/git-adapter.mjs} - worktreeRemove with cleanup
|
|
20
20
|
*/
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
21
22
|
/**
|
|
22
23
|
* Result of orphan detection
|
|
23
24
|
* @typedef {object} OrphanDetectionResult
|
|
@@ -50,6 +51,21 @@ export declare function parseWorktreeList(porcelainOutput: string): WorktreeEntr
|
|
|
50
51
|
* @returns {Promise<Set<string>>} Set of absolute paths tracked by git worktree
|
|
51
52
|
*/
|
|
52
53
|
export declare function getTrackedWorktreePaths(projectRoot: any): Promise<Set<string>>;
|
|
54
|
+
/**
|
|
55
|
+
* Identify tracked worktrees that are missing on disk
|
|
56
|
+
*
|
|
57
|
+
* @param {string[]} trackedPaths - Absolute paths from git worktree list
|
|
58
|
+
* @param {(path: string) => boolean} [existsFn] - Optional fs.existsSync override
|
|
59
|
+
* @returns {string[]} Missing worktree paths
|
|
60
|
+
*/
|
|
61
|
+
export declare function getMissingWorktreesFromTracked(trackedPaths: any, existsFn?: typeof existsSync): any[];
|
|
62
|
+
/**
|
|
63
|
+
* Detect tracked worktrees that are missing on disk
|
|
64
|
+
*
|
|
65
|
+
* @param {string} [projectRoot] - Project root directory (defaults to cwd)
|
|
66
|
+
* @returns {Promise<string[]>} Missing tracked worktree paths
|
|
67
|
+
*/
|
|
68
|
+
export declare function detectMissingTrackedWorktrees(projectRoot: any): Promise<any[]>;
|
|
53
69
|
/**
|
|
54
70
|
* Get list of directories in worktrees/ folder
|
|
55
71
|
*
|
package/dist/orphan-detector.js
CHANGED
|
@@ -75,6 +75,30 @@ export async function getTrackedWorktreePaths(projectRoot) {
|
|
|
75
75
|
const entries = parseWorktreeList(output);
|
|
76
76
|
return new Set(entries.map((e) => e.path));
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Identify tracked worktrees that are missing on disk
|
|
80
|
+
*
|
|
81
|
+
* @param {string[]} trackedPaths - Absolute paths from git worktree list
|
|
82
|
+
* @param {(path: string) => boolean} [existsFn] - Optional fs.existsSync override
|
|
83
|
+
* @returns {string[]} Missing worktree paths
|
|
84
|
+
*/
|
|
85
|
+
export function getMissingWorktreesFromTracked(trackedPaths, existsFn = existsSync) {
|
|
86
|
+
if (!Array.isArray(trackedPaths)) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
return trackedPaths.filter((trackedPath) => !existsFn(trackedPath));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Detect tracked worktrees that are missing on disk
|
|
93
|
+
*
|
|
94
|
+
* @param {string} [projectRoot] - Project root directory (defaults to cwd)
|
|
95
|
+
* @returns {Promise<string[]>} Missing tracked worktree paths
|
|
96
|
+
*/
|
|
97
|
+
export async function detectMissingTrackedWorktrees(projectRoot) {
|
|
98
|
+
const root = projectRoot || process.cwd();
|
|
99
|
+
const tracked = await getTrackedWorktreePaths(root);
|
|
100
|
+
return getMissingWorktreesFromTracked([...tracked]);
|
|
101
|
+
}
|
|
78
102
|
/**
|
|
79
103
|
* Get list of directories in worktrees/ folder
|
|
80
104
|
*
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Documentation: docs/, ai/, .claude/, README*, CLAUDE*.md
|
|
10
10
|
* - Tooling: tools/, scripts/
|
|
11
11
|
*
|
|
12
|
-
* @see {@link
|
|
12
|
+
* @see {@link ./wu-done-paths.js} - Consumer for detectDocsOnlyByPaths
|
|
13
13
|
*/
|
|
14
14
|
/**
|
|
15
15
|
* Prefixes for paths that should skip web app tests.
|
package/dist/path-classifiers.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Documentation: docs/, ai/, .claude/, README*, CLAUDE*.md
|
|
10
10
|
* - Tooling: tools/, scripts/
|
|
11
11
|
*
|
|
12
|
-
* @see {@link
|
|
12
|
+
* @see {@link ./wu-done-paths.js} - Consumer for detectDocsOnlyByPaths
|
|
13
13
|
*/
|
|
14
14
|
/**
|
|
15
15
|
* Prefixes for paths that should skip web app tests.
|
|
@@ -143,3 +143,20 @@ export declare function deduplicateBacklogAfterRebase(worktreePath: any, wuId: a
|
|
|
143
143
|
cleaned: boolean;
|
|
144
144
|
errors: any[];
|
|
145
145
|
}>;
|
|
146
|
+
/**
|
|
147
|
+
* Clean up build artifacts in a worktree (dist folders + tsbuildinfo files)
|
|
148
|
+
*
|
|
149
|
+
* WU-1042: Provide a safe helper for clearing build artifacts that can
|
|
150
|
+
* trigger TS5055 or stale build issues in worktrees.
|
|
151
|
+
*
|
|
152
|
+
* @param {string} worktreePath - Path to the worktree directory
|
|
153
|
+
* @returns {Promise<object>} Cleanup result
|
|
154
|
+
* @returns {string[]} result.distDirectories - Removed dist directories (relative paths)
|
|
155
|
+
* @returns {string[]} result.tsbuildinfoFiles - Removed tsbuildinfo files (relative paths)
|
|
156
|
+
* @returns {number} result.removedCount - Total removed artifacts
|
|
157
|
+
*/
|
|
158
|
+
export declare function cleanupWorktreeBuildArtifacts(worktreePath: any): Promise<{
|
|
159
|
+
distDirectories: string[];
|
|
160
|
+
tsbuildinfoFiles: string[];
|
|
161
|
+
removedCount: number;
|
|
162
|
+
}>;
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
* @see {@link tools/lib/wu-recovery.mjs} - Related zombie state handling
|
|
16
16
|
*/
|
|
17
17
|
import { readFile, writeFile, unlink, access } from 'node:fs/promises';
|
|
18
|
-
import { existsSync } from 'node:fs';
|
|
19
|
-
import { join } from 'node:path';
|
|
20
|
-
import
|
|
18
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
19
|
+
import { join, resolve } from 'node:path';
|
|
20
|
+
import fg from 'fast-glob';
|
|
21
|
+
import { parseYAML, stringifyYAML } from './wu-yaml.js';
|
|
21
22
|
import { WU_PATHS } from './wu-paths.js';
|
|
22
|
-
import { WU_STATUS, LOG_PREFIX, EMOJI, YAML_OPTIONS, BACKLOG_SECTIONS, STATUS_SECTIONS, REMOTES, BRANCHES, } from './wu-constants.js';
|
|
23
|
+
import { WU_STATUS, LOG_PREFIX, EMOJI, YAML_OPTIONS, BACKLOG_SECTIONS, STATUS_SECTIONS, REMOTES, BRANCHES, BUILD_ARTIFACT_GLOBS, BUILD_ARTIFACT_IGNORES, } from './wu-constants.js';
|
|
23
24
|
import { findSectionBounds, removeBulletFromSection } from './backlog-editor.js';
|
|
24
25
|
/** @constant {string} FRONTMATTER_DELIMITER - YAML frontmatter delimiter */
|
|
25
26
|
const FRONTMATTER_DELIMITER = '---';
|
|
@@ -68,7 +69,7 @@ async function yamlIsDoneOnMain(gitAdapter, wuId) {
|
|
|
68
69
|
'show',
|
|
69
70
|
`${REMOTES.ORIGIN}/${BRANCHES.MAIN}:${WU_PATHS.WU(wuId)}`,
|
|
70
71
|
]);
|
|
71
|
-
const doc =
|
|
72
|
+
const doc = parseYAML(content);
|
|
72
73
|
return doc && doc.status === WU_STATUS.DONE;
|
|
73
74
|
}
|
|
74
75
|
catch {
|
|
@@ -162,7 +163,7 @@ export async function detectRebasedArtifacts(worktreePath, wuId, gitAdapter) {
|
|
|
162
163
|
if (await fileExists(wuYamlPath)) {
|
|
163
164
|
try {
|
|
164
165
|
const content = await readFile(wuYamlPath, { encoding: 'utf-8' });
|
|
165
|
-
const doc =
|
|
166
|
+
const doc = parseYAML(content);
|
|
166
167
|
if (doc && doc.status === WU_STATUS.DONE) {
|
|
167
168
|
localYamlDone = true;
|
|
168
169
|
}
|
|
@@ -236,7 +237,7 @@ export async function cleanupRebasedArtifacts(worktreePath, wuId) {
|
|
|
236
237
|
try {
|
|
237
238
|
if (await fileExists(wuYamlPath)) {
|
|
238
239
|
const content = await readFile(wuYamlPath, { encoding: 'utf-8' });
|
|
239
|
-
const doc =
|
|
240
|
+
const doc = parseYAML(content);
|
|
240
241
|
if (doc && doc.status === WU_STATUS.DONE) {
|
|
241
242
|
// Reset status
|
|
242
243
|
doc.status = WU_STATUS.IN_PROGRESS;
|
|
@@ -244,7 +245,7 @@ export async function cleanupRebasedArtifacts(worktreePath, wuId) {
|
|
|
244
245
|
delete doc.locked;
|
|
245
246
|
delete doc.completed_at;
|
|
246
247
|
// Write back
|
|
247
|
-
const updatedContent =
|
|
248
|
+
const updatedContent = stringifyYAML(doc, { lineWidth: YAML_OPTIONS.LINE_WIDTH });
|
|
248
249
|
await writeFile(wuYamlPath, updatedContent, { encoding: 'utf-8' });
|
|
249
250
|
yamlReset = true;
|
|
250
251
|
console.log(LOG_PREFIX.CLEANUP, `${EMOJI.WARNING} Reset YAML status from done to in_progress for ${wuId} (artifact from main rebase)`);
|
|
@@ -431,3 +432,43 @@ export async function deduplicateBacklogAfterRebase(worktreePath, wuId) {
|
|
|
431
432
|
errors,
|
|
432
433
|
};
|
|
433
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* Clean up build artifacts in a worktree (dist folders + tsbuildinfo files)
|
|
437
|
+
*
|
|
438
|
+
* WU-1042: Provide a safe helper for clearing build artifacts that can
|
|
439
|
+
* trigger TS5055 or stale build issues in worktrees.
|
|
440
|
+
*
|
|
441
|
+
* @param {string} worktreePath - Path to the worktree directory
|
|
442
|
+
* @returns {Promise<object>} Cleanup result
|
|
443
|
+
* @returns {string[]} result.distDirectories - Removed dist directories (relative paths)
|
|
444
|
+
* @returns {string[]} result.tsbuildinfoFiles - Removed tsbuildinfo files (relative paths)
|
|
445
|
+
* @returns {number} result.removedCount - Total removed artifacts
|
|
446
|
+
*/
|
|
447
|
+
export async function cleanupWorktreeBuildArtifacts(worktreePath) {
|
|
448
|
+
const root = resolve(worktreePath);
|
|
449
|
+
const distDirectories = await fg(BUILD_ARTIFACT_GLOBS.DIST_DIRS, {
|
|
450
|
+
cwd: root,
|
|
451
|
+
onlyDirectories: true,
|
|
452
|
+
dot: true,
|
|
453
|
+
followSymbolicLinks: false,
|
|
454
|
+
ignore: BUILD_ARTIFACT_IGNORES,
|
|
455
|
+
});
|
|
456
|
+
const tsbuildinfoFiles = await fg(BUILD_ARTIFACT_GLOBS.TSBUILDINFO_FILES, {
|
|
457
|
+
cwd: root,
|
|
458
|
+
onlyFiles: true,
|
|
459
|
+
dot: true,
|
|
460
|
+
followSymbolicLinks: false,
|
|
461
|
+
ignore: BUILD_ARTIFACT_IGNORES,
|
|
462
|
+
});
|
|
463
|
+
for (const dir of distDirectories) {
|
|
464
|
+
rmSync(join(root, dir), { recursive: true, force: true });
|
|
465
|
+
}
|
|
466
|
+
for (const file of tsbuildinfoFiles) {
|
|
467
|
+
rmSync(join(root, file), { force: true });
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
distDirectories,
|
|
471
|
+
tsbuildinfoFiles,
|
|
472
|
+
removedCount: distDirectories.length + tsbuildinfoFiles.length,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy interface for client-specific spawn behavior
|
|
3
|
+
*/
|
|
4
|
+
export interface SpawnStrategy {
|
|
5
|
+
/**
|
|
6
|
+
* Get the context loading preamble for the specific client
|
|
7
|
+
*/
|
|
8
|
+
getPreamble(wuId: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Get instructions for loading agent skills/tools
|
|
11
|
+
*/
|
|
12
|
+
getSkillLoadingInstruction(): string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Base class with shared preamble logic
|
|
16
|
+
*/
|
|
17
|
+
declare abstract class BaseSpawnStrategy implements SpawnStrategy {
|
|
18
|
+
protected getCorePreamble(wuId: string): string;
|
|
19
|
+
abstract getPreamble(wuId: string): string;
|
|
20
|
+
abstract getSkillLoadingInstruction(): string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Strategy for Claude Code (Local/Terminal)
|
|
24
|
+
*/
|
|
25
|
+
export declare class ClaudeCodeStrategy extends BaseSpawnStrategy {
|
|
26
|
+
getPreamble(wuId: string): string;
|
|
27
|
+
getSkillLoadingInstruction(): string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Strategy for Gemini CLI (Multimodal/Ecosystem)
|
|
31
|
+
*/
|
|
32
|
+
export declare class GeminiCliStrategy extends BaseSpawnStrategy {
|
|
33
|
+
getPreamble(wuId: string): string;
|
|
34
|
+
getSkillLoadingInstruction(): string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generic Strategy (Unknown/Other clients)
|
|
38
|
+
*/
|
|
39
|
+
export declare class GenericStrategy extends BaseSpawnStrategy {
|
|
40
|
+
getPreamble(wuId: string): string;
|
|
41
|
+
getSkillLoadingInstruction(): string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Factory for creating strategies
|
|
45
|
+
*/
|
|
46
|
+
export declare class SpawnStrategyFactory {
|
|
47
|
+
/**
|
|
48
|
+
* Create a strategy for the given client
|
|
49
|
+
* @param clientName - Client name (e.g. 'claude-code', 'gemini-cli')
|
|
50
|
+
*/
|
|
51
|
+
static create(clientName: string): SpawnStrategy;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Base class with shared preamble logic
|
|
4
|
+
*/
|
|
5
|
+
class BaseSpawnStrategy {
|
|
6
|
+
getCorePreamble(wuId) {
|
|
7
|
+
return `Load the following context in this order:
|
|
8
|
+
|
|
9
|
+
1. Read LUMENFLOW.md (workflow fundamentals and critical rules)
|
|
10
|
+
2. Read .lumenflow/constraints.md (non-negotiable constraints)
|
|
11
|
+
3. Read README.md (project structure and tech stack)
|
|
12
|
+
4. Read docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md sections 1-7 (TDD, gates, Definition of Done)
|
|
13
|
+
5. Read docs/04-operations/tasks/wu/${wuId}.yaml (the specific WU you're working on)`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Strategy for Claude Code (Local/Terminal)
|
|
18
|
+
*/
|
|
19
|
+
export class ClaudeCodeStrategy extends BaseSpawnStrategy {
|
|
20
|
+
getPreamble(wuId) {
|
|
21
|
+
let preamble = this.getCorePreamble(wuId);
|
|
22
|
+
// Vendor overlay
|
|
23
|
+
if (existsSync('.claude/CLAUDE.md')) {
|
|
24
|
+
// Insert after LUMENFLOW.md if possible, or just append/prepend
|
|
25
|
+
// For simplicity and clarity, we'll prepend the vendor specific instructions
|
|
26
|
+
// relying on the user to follow the specific order if stated.
|
|
27
|
+
// Actually, checking original behavior: CLAUDE.md was #1.
|
|
28
|
+
// But new plan says LUMENFLOW.md is core.
|
|
29
|
+
// We will append it as an overlay step.
|
|
30
|
+
preamble += `\n6. Read .claude/CLAUDE.md (Claude-specific workflow overlay)`;
|
|
31
|
+
}
|
|
32
|
+
return preamble;
|
|
33
|
+
}
|
|
34
|
+
getSkillLoadingInstruction() {
|
|
35
|
+
return `## Skills Selection
|
|
36
|
+
|
|
37
|
+
1. Check \`.lumenflow/agents\` for available skills.
|
|
38
|
+
2. Check \`.claude/agents\` for Claude-specific overrides or additions.
|
|
39
|
+
3. Select relevant skills for this task.`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Strategy for Gemini CLI (Multimodal/Ecosystem)
|
|
44
|
+
*/
|
|
45
|
+
export class GeminiCliStrategy extends BaseSpawnStrategy {
|
|
46
|
+
getPreamble(wuId) {
|
|
47
|
+
let preamble = this.getCorePreamble(wuId);
|
|
48
|
+
if (existsSync('GEMINI.md')) {
|
|
49
|
+
preamble += `\n6. Read GEMINI.md (Gemini-specific workflow overlay)`;
|
|
50
|
+
}
|
|
51
|
+
return preamble;
|
|
52
|
+
}
|
|
53
|
+
getSkillLoadingInstruction() {
|
|
54
|
+
return `## Skills Selection
|
|
55
|
+
|
|
56
|
+
1. Check \`.lumenflow/agents\` for available skills.
|
|
57
|
+
2. Select relevant skills for this task.`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generic Strategy (Unknown/Other clients)
|
|
62
|
+
*/
|
|
63
|
+
export class GenericStrategy extends BaseSpawnStrategy {
|
|
64
|
+
getPreamble(wuId) {
|
|
65
|
+
return this.getCorePreamble(wuId);
|
|
66
|
+
}
|
|
67
|
+
getSkillLoadingInstruction() {
|
|
68
|
+
return `## Skills Selection
|
|
69
|
+
|
|
70
|
+
1. Check \`.lumenflow/agents\` for available skills.
|
|
71
|
+
2. Select relevant skills for this task.`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Factory for creating strategies
|
|
76
|
+
*/
|
|
77
|
+
export class SpawnStrategyFactory {
|
|
78
|
+
/**
|
|
79
|
+
* Create a strategy for the given client
|
|
80
|
+
* @param clientName - Client name (e.g. 'claude-code', 'gemini-cli')
|
|
81
|
+
*/
|
|
82
|
+
static create(clientName) {
|
|
83
|
+
switch (clientName.toLowerCase()) {
|
|
84
|
+
case 'claude': // Legacy alias
|
|
85
|
+
case 'claude-code':
|
|
86
|
+
return new ClaudeCodeStrategy();
|
|
87
|
+
case 'gemini': // Alias
|
|
88
|
+
case 'gemini-cli':
|
|
89
|
+
return new GeminiCliStrategy();
|
|
90
|
+
case 'codex': // Deprecated alias
|
|
91
|
+
case 'codex-cli':
|
|
92
|
+
// Codex might need its own strategy later (sandbox), but for now generic or claude-like?
|
|
93
|
+
// Plan says: "codex: preamble: false, strategy: cloud-sandbox" -> implies Generic or dedicated.
|
|
94
|
+
// For now, let's map to Generic but maybe we should add CodexStrategy if it has diff behavior.
|
|
95
|
+
// Re-reading plan: "CodexStrategy: Emphasizes cloud sandbox constraints"
|
|
96
|
+
// But for this "Minimal" pass, let's stick to Generic with a comment,
|
|
97
|
+
// OR essentially treat it as Generic since we don't have constraints logic here yet (it's in wu-spawn).
|
|
98
|
+
// Actually, let's return GenericStrategy but we might handle constraints elsewhere.
|
|
99
|
+
return new GenericStrategy();
|
|
100
|
+
default:
|
|
101
|
+
// Warn? The factory just creates. The caller should warn if it fell back.
|
|
102
|
+
// But here we just return Generic.
|
|
103
|
+
return new GenericStrategy();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|