@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.
Files changed (79) hide show
  1. package/dist/arg-parser.d.ts +6 -0
  2. package/dist/arg-parser.js +57 -1
  3. package/dist/backlog-generator.js +1 -1
  4. package/dist/backlog-sync-validator.js +3 -3
  5. package/dist/branch-check.d.ts +21 -0
  6. package/dist/branch-check.js +77 -0
  7. package/dist/cli/is-agent-branch.d.ts +11 -0
  8. package/dist/cli/is-agent-branch.js +15 -0
  9. package/dist/code-paths-overlap.js +2 -2
  10. package/dist/error-handler.d.ts +1 -0
  11. package/dist/error-handler.js +4 -1
  12. package/dist/git-adapter.d.ts +23 -0
  13. package/dist/git-adapter.js +38 -2
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +5 -0
  16. package/dist/lane-checker.d.ts +36 -3
  17. package/dist/lane-checker.js +128 -17
  18. package/dist/lane-inference.js +3 -4
  19. package/dist/lumenflow-config-schema.d.ts +125 -0
  20. package/dist/lumenflow-config-schema.js +76 -0
  21. package/dist/lumenflow-home.d.ts +130 -0
  22. package/dist/lumenflow-home.js +208 -0
  23. package/dist/manual-test-validator.js +1 -1
  24. package/dist/orchestration-rules.d.ts +1 -1
  25. package/dist/orchestration-rules.js +2 -2
  26. package/dist/orphan-detector.d.ts +16 -0
  27. package/dist/orphan-detector.js +24 -0
  28. package/dist/path-classifiers.d.ts +1 -1
  29. package/dist/path-classifiers.js +1 -1
  30. package/dist/rebase-artifact-cleanup.d.ts +17 -0
  31. package/dist/rebase-artifact-cleanup.js +49 -8
  32. package/dist/spawn-strategy.d.ts +53 -0
  33. package/dist/spawn-strategy.js +106 -0
  34. package/dist/spec-branch-helpers.d.ts +118 -0
  35. package/dist/spec-branch-helpers.js +192 -0
  36. package/dist/stamp-utils.d.ts +10 -0
  37. package/dist/stamp-utils.js +17 -19
  38. package/dist/token-counter.js +2 -2
  39. package/dist/wu-consistency-checker.d.ts +2 -0
  40. package/dist/wu-consistency-checker.js +40 -6
  41. package/dist/wu-constants.d.ts +98 -3
  42. package/dist/wu-constants.js +108 -3
  43. package/dist/wu-create-validators.d.ts +40 -2
  44. package/dist/wu-create-validators.js +76 -2
  45. package/dist/wu-done-branch-only.js +9 -0
  46. package/dist/wu-done-branch-utils.d.ts +10 -0
  47. package/dist/wu-done-branch-utils.js +31 -0
  48. package/dist/wu-done-cleanup.d.ts +8 -0
  49. package/dist/wu-done-cleanup.js +122 -0
  50. package/dist/wu-done-docs-generate.d.ts +73 -0
  51. package/dist/wu-done-docs-generate.js +108 -0
  52. package/dist/wu-done-docs-only.d.ts +20 -0
  53. package/dist/wu-done-docs-only.js +65 -0
  54. package/dist/wu-done-errors.d.ts +17 -0
  55. package/dist/wu-done-errors.js +24 -0
  56. package/dist/wu-done-inputs.d.ts +12 -0
  57. package/dist/wu-done-inputs.js +51 -0
  58. package/dist/wu-done-metadata.d.ts +100 -0
  59. package/dist/wu-done-metadata.js +193 -0
  60. package/dist/wu-done-paths.d.ts +69 -0
  61. package/dist/wu-done-paths.js +237 -0
  62. package/dist/wu-done-preflight.d.ts +48 -0
  63. package/dist/wu-done-preflight.js +185 -0
  64. package/dist/wu-done-validation.d.ts +82 -0
  65. package/dist/wu-done-validation.js +340 -0
  66. package/dist/wu-done-validators.d.ts +13 -409
  67. package/dist/wu-done-validators.js +9 -1225
  68. package/dist/wu-done-worktree.d.ts +0 -1
  69. package/dist/wu-done-worktree.js +24 -30
  70. package/dist/wu-schema.js +4 -4
  71. package/dist/wu-spawn-skills.d.ts +19 -0
  72. package/dist/wu-spawn-skills.js +148 -0
  73. package/dist/wu-spawn.d.ts +17 -4
  74. package/dist/wu-spawn.js +113 -177
  75. package/dist/wu-validation.d.ts +1 -0
  76. package/dist/wu-validation.js +21 -1
  77. package/dist/wu-validator.d.ts +51 -0
  78. package/dist/wu-validator.js +108 -0
  79. 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', '.js']);
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 ../../../ai/onboarding/agent-selection-guide.md} - Agent selection rules
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 ../../../ai/onboarding/agent-selection-guide.md} - Agent selection rules
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: ai/onboarding/agent-selection-guide.md for agent invocation guidance');
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
  *
@@ -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 tools/lib/wu-done-validators.mjs} - Consumer for detectDocsOnlyByPaths
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.
@@ -9,7 +9,7 @@
9
9
  * - Documentation: docs/, ai/, .claude/, README*, CLAUDE*.md
10
10
  * - Tooling: tools/, scripts/
11
11
  *
12
- * @see {@link tools/lib/wu-done-validators.mjs} - Consumer for detectDocsOnlyByPaths
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 yaml from 'js-yaml';
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 = yaml.load(content);
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 = yaml.load(content);
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 = yaml.load(content);
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 = yaml.dump(doc, { lineWidth: YAML_OPTIONS.LINE_WIDTH });
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
+ }