@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
@@ -21,6 +21,12 @@ interface WUOption {
21
21
  isRepeatable?: boolean;
22
22
  }
23
23
  export declare const WU_OPTIONS: Record<string, WUOption>;
24
+ /**
25
+ * WU-1062: Additional options for wu:create command
26
+ *
27
+ * These options control how wu:create handles spec storage and branch creation.
28
+ */
29
+ export declare const WU_CREATE_OPTIONS: Record<string, WUOption>;
24
30
  /**
25
31
  * Create a commander-based CLI parser for a WU script.
26
32
  *
@@ -15,6 +15,16 @@ function collectRepeatable(value, previous) {
15
15
  }
16
16
  export const WU_OPTIONS = {
17
17
  // String options (require values)
18
+ client: {
19
+ name: 'client',
20
+ flags: '--client <client>',
21
+ description: 'Client name (claude-code, gemini-cli, etc)',
22
+ },
23
+ vendor: {
24
+ name: 'vendor',
25
+ flags: '--vendor <vendor>',
26
+ description: 'Deprecated alias for --client',
27
+ },
18
28
  id: {
19
29
  name: 'id',
20
30
  flags: '-i, --id <wuId>',
@@ -124,6 +134,11 @@ export const WU_OPTIONS = {
124
134
  flags: '--skip-gates',
125
135
  description: 'Skip gates check (requires --reason and --fix-wu)',
126
136
  },
137
+ docsOnly: {
138
+ name: 'docsOnly',
139
+ flags: '--docs-only',
140
+ description: 'Run docs-only gates (requires exposure: documentation or docs-only code_paths)',
141
+ },
127
142
  allowTodo: {
128
143
  name: 'allowTodo',
129
144
  flags: '--allow-todo',
@@ -154,6 +169,12 @@ export const WU_OPTIONS = {
154
169
  flags: '--fix',
155
170
  description: 'Auto-fix common YAML validation issues (WU-1359)',
156
171
  },
172
+ noPush: {
173
+ name: 'noPush',
174
+ flags: '--no-push',
175
+ description: 'Skip pushing claim branch or canonical updates (air-gapped/offline)',
176
+ isNegated: true,
177
+ },
157
178
  createPr: {
158
179
  name: 'createPr',
159
180
  flags: '--create-pr',
@@ -368,12 +389,44 @@ export const WU_OPTIONS = {
368
389
  flags: '--resume',
369
390
  description: 'Resume a WU from a crashed/killed agent (handoff) by taking over the existing worktree and updating the lock with new PID. Fails if original PID is still running (safety) or worktree does not exist.',
370
391
  },
392
+ // WU-1023: Skip auto-setup for fast claims
393
+ skipSetup: {
394
+ name: 'skipSetup',
395
+ flags: '--skip-setup',
396
+ description: 'Skip automatic pnpm install in worktree after creation (faster claims when deps already built)',
397
+ },
398
+ };
399
+ /**
400
+ * WU-1062: Additional options for wu:create command
401
+ *
402
+ * These options control how wu:create handles spec storage and branch creation.
403
+ */
404
+ export const WU_CREATE_OPTIONS = {
405
+ /**
406
+ * Create plan template in $LUMENFLOW_HOME/plans/
407
+ * Used with spec branch mode to store plans externally.
408
+ */
409
+ plan: {
410
+ name: 'plan',
411
+ flags: '--plan',
412
+ description: 'Create plan template in $LUMENFLOW_HOME/plans/ (external plan storage for traceability)',
413
+ },
414
+ /**
415
+ * Direct main-write mode (legacy behavior)
416
+ * Writes WU spec directly to main branch instead of spec branch.
417
+ * Use for emergency/hotfix scenarios or when spec branch workflow is not desired.
418
+ */
419
+ direct: {
420
+ name: 'direct',
421
+ flags: '--direct',
422
+ description: 'Write to main directly (legacy behavior). Default is spec branch mode (spec/wu-XXXX)',
423
+ },
371
424
  };
372
425
  /**
373
426
  * Negated options that commander handles specially.
374
427
  * --no-foo creates opts.foo = false. We convert to noFoo = true.
375
428
  */
376
- const NEGATED_OPTIONS = ['auto', 'remove', 'merge', 'autoRebase'];
429
+ const NEGATED_OPTIONS = ['auto', 'remove', 'merge', 'autoRebase', 'push'];
377
430
  /**
378
431
  * Post-process commander opts to handle negated boolean options.
379
432
  * Commander's --no-* flags create opts.foo = false.
@@ -527,6 +580,7 @@ export function parseWUArgs(argv) {
527
580
  WU_OPTIONS.noMerge,
528
581
  WU_OPTIONS.help,
529
582
  WU_OPTIONS.skipGates,
583
+ WU_OPTIONS.docsOnly,
530
584
  WU_OPTIONS.allowTodo,
531
585
  WU_OPTIONS.skipExposureCheck,
532
586
  WU_OPTIONS.skipAccessibilityCheck,
@@ -535,6 +589,8 @@ export function parseWUArgs(argv) {
535
589
  WU_OPTIONS.overrideOwner,
536
590
  WU_OPTIONS.noAutoRebase,
537
591
  WU_OPTIONS.requireAgents,
592
+ WU_OPTIONS.client,
593
+ WU_OPTIONS.vendor,
538
594
  ];
539
595
  for (const opt of allOptions) {
540
596
  program.option(opt.flags, opt.description, opt.default);
@@ -54,7 +54,7 @@ sections:
54
54
  insertion: after_heading_blank_line
55
55
  ---
56
56
 
57
- > Agent: Read **ai/onboarding/starting-prompt.md** first, then follow **docs/04-operations/\\_frameworks/lumenflow/lumenflow-complete.md** for execution.
57
+ > Agent: Read **docs/04-operations/_frameworks/lumenflow/agent/onboarding/starting-prompt.md** first, then follow **docs/04-operations/\\_frameworks/lumenflow/lumenflow-complete.md** for execution.
58
58
 
59
59
  # Backlog (single source of truth)
60
60
 
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'node:fs';
9
9
  import path from 'node:path';
10
- import yaml from 'js-yaml';
10
+ import { parseYAML } from './wu-yaml.js';
11
11
  import { parseBacklogFrontmatter, getSectionHeadings } from './backlog-parser.js';
12
12
  import { extractParent } from './lane-checker.js';
13
13
  import { CONFIG_FILES, STRING_LITERALS, getProjectRoot, } from './wu-constants.js';
@@ -24,7 +24,7 @@ function hasSubLaneTaxonomy(parent, projectRoot) {
24
24
  }
25
25
  try {
26
26
  const taxonomyContent = readFileSync(taxonomyPath, { encoding: 'utf-8' });
27
- const taxonomy = yaml.load(taxonomyContent);
27
+ const taxonomy = parseYAML(taxonomyContent);
28
28
  const normalizedParent = parent.trim().toLowerCase();
29
29
  return Object.keys(taxonomy).some((key) => key.toLowerCase().trim() === normalizedParent);
30
30
  }
@@ -126,7 +126,7 @@ export function validateBacklogSync(backlogPath) {
126
126
  }
127
127
  try {
128
128
  const wuContent = readFileSync(wuPath, { encoding: 'utf-8' });
129
- const wuDoc = yaml.load(wuContent);
129
+ const wuDoc = parseYAML(wuContent);
130
130
  if (wuDoc && wuDoc.lane) {
131
131
  const lane = wuDoc.lane.toString().trim();
132
132
  const hasColon = lane.includes(':');
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Branch-aware bypass detection for cloud/automation agents.
3
+ *
4
+ * Provides functions to check if a branch is an agent branch that can
5
+ * bypass worktree requirements, and if headless mode is allowed.
6
+ *
7
+ * @module branch-check
8
+ */
9
+ /**
10
+ * Check if branch is an agent branch that can bypass worktree requirements.
11
+ * Uses the existing config loader (which handles caching/validation).
12
+ *
13
+ * @param branch - Branch name to check
14
+ * @returns True if branch matches agent patterns
15
+ */
16
+ export declare function isAgentBranch(branch: string | null | undefined): boolean;
17
+ /**
18
+ * Check if headless mode is allowed (guarded).
19
+ * Requires LUMENFLOW_HEADLESS=1 AND (LUMENFLOW_ADMIN=1 OR CI truthy OR GITHUB_ACTIONS truthy)
20
+ */
21
+ export declare function isHeadlessAllowed(): boolean;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Branch-aware bypass detection for cloud/automation agents.
3
+ *
4
+ * Provides functions to check if a branch is an agent branch that can
5
+ * bypass worktree requirements, and if headless mode is allowed.
6
+ *
7
+ * @module branch-check
8
+ */
9
+ import micromatch from 'micromatch';
10
+ import { getConfig } from './lumenflow-config.js';
11
+ /** Default agent branch patterns (narrow: just agent/*) */
12
+ const DEFAULT_AGENT_BRANCH_PATTERNS = ['agent/*'];
13
+ /** Legacy protected branch (always protected regardless of mainBranch setting) */
14
+ const LEGACY_PROTECTED = 'master';
15
+ /**
16
+ * Get lane branch pattern from config (or default).
17
+ * Lane branches always require worktrees - never bypassed.
18
+ */
19
+ function getLaneBranchPattern() {
20
+ const config = getConfig();
21
+ const prefix = config?.git?.laneBranchPrefix ?? 'lane/';
22
+ // Escape regex special chars in prefix, then anchor to start
23
+ const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
24
+ return new RegExp(`^${escaped}`);
25
+ }
26
+ /**
27
+ * Get protected branches derived from config.
28
+ * Returns [mainBranch, 'master'] to avoid config duplication.
29
+ */
30
+ function getProtectedBranches() {
31
+ const config = getConfig();
32
+ const mainBranch = config?.git?.mainBranch ?? 'main';
33
+ // Deduplicate in case mainBranch is 'master'
34
+ const protectedSet = new Set([mainBranch, LEGACY_PROTECTED]);
35
+ return Array.from(protectedSet);
36
+ }
37
+ /**
38
+ * Check if branch is an agent branch that can bypass worktree requirements.
39
+ * Uses the existing config loader (which handles caching/validation).
40
+ *
41
+ * @param branch - Branch name to check
42
+ * @returns True if branch matches agent patterns
43
+ */
44
+ export function isAgentBranch(branch) {
45
+ // Fail-closed: no branch = protected
46
+ if (!branch)
47
+ return false;
48
+ // Detached HEAD = protected (fail-closed)
49
+ if (branch === 'HEAD')
50
+ return false;
51
+ // Load config (uses existing loader with caching)
52
+ const config = getConfig();
53
+ const protectedBranches = getProtectedBranches();
54
+ const patterns = config?.git?.agentBranchPatterns?.length > 0
55
+ ? config.git.agentBranchPatterns
56
+ : DEFAULT_AGENT_BRANCH_PATTERNS;
57
+ // Protected branches are NEVER bypassed (mainBranch + 'master')
58
+ if (protectedBranches.includes(branch))
59
+ return false;
60
+ // LumenFlow lane branches require worktrees (uses config's laneBranchPrefix)
61
+ if (getLaneBranchPattern().test(branch))
62
+ return false;
63
+ // Use micromatch for proper glob matching
64
+ return micromatch.isMatch(branch, patterns);
65
+ }
66
+ /**
67
+ * Check if headless mode is allowed (guarded).
68
+ * Requires LUMENFLOW_HEADLESS=1 AND (LUMENFLOW_ADMIN=1 OR CI truthy OR GITHUB_ACTIONS truthy)
69
+ */
70
+ export function isHeadlessAllowed() {
71
+ if (process.env.LUMENFLOW_HEADLESS !== '1')
72
+ return false;
73
+ return (process.env.LUMENFLOW_ADMIN === '1' ||
74
+ Boolean(process.env.CI) || // Any truthy CI value (true, 1, yes, etc.)
75
+ Boolean(process.env.GITHUB_ACTIONS) // Any truthy value
76
+ );
77
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI helper for bash hooks to check if branch is an agent branch.
4
+ * Uses the same isAgentBranch() logic as TypeScript code.
5
+ *
6
+ * Usage: node dist/cli/is-agent-branch.js [branch-name]
7
+ * Exit codes: 0 = agent branch (allowed), 1 = not agent branch (protected)
8
+ *
9
+ * @module cli/is-agent-branch
10
+ */
11
+ export {};
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI helper for bash hooks to check if branch is an agent branch.
4
+ * Uses the same isAgentBranch() logic as TypeScript code.
5
+ *
6
+ * Usage: node dist/cli/is-agent-branch.js [branch-name]
7
+ * Exit codes: 0 = agent branch (allowed), 1 = not agent branch (protected)
8
+ *
9
+ * @module cli/is-agent-branch
10
+ */
11
+ 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);
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { readFileSync, existsSync } from 'fs';
11
11
  import path from 'path';
12
- import yaml from 'js-yaml';
12
+ import { parseYAML } from './wu-yaml.js';
13
13
  import fg from 'fast-glob';
14
14
  import micromatch from 'micromatch';
15
15
  import { STATUS_SECTIONS, BACKLOG_SECTIONS, STRING_LITERALS } from './wu-constants.js';
@@ -166,7 +166,7 @@ export function detectConflicts(statusPath, claimingPaths, claimingWU) {
166
166
  continue; // Skip if YAML doesn't exist
167
167
  }
168
168
  const wuContent = readFileSync(wuPath, { encoding: 'utf-8' });
169
- const wuDoc = yaml.load(wuContent);
169
+ const wuDoc = parseYAML(wuContent);
170
170
  // Extract code_paths (skip if not defined)
171
171
  const existingPaths = wuDoc?.code_paths;
172
172
  if (!existingPaths || existingPaths.length === 0) {
@@ -2,6 +2,7 @@
2
2
  * @file error-handler.mjs
3
3
  * @description Structured error handling with error codes
4
4
  * WU-1082: Extract shared utilities (eliminate die() duplication)
5
+ * WU-1006: Library-First - use path.basename() instead of manual split
5
6
  *
6
7
  * Replaces die() function in:
7
8
  * - tools/wu-claim.mjs
@@ -2,6 +2,7 @@
2
2
  * @file error-handler.mjs
3
3
  * @description Structured error handling with error codes
4
4
  * WU-1082: Extract shared utilities (eliminate die() duplication)
5
+ * WU-1006: Library-First - use path.basename() instead of manual split
5
6
  *
6
7
  * Replaces die() function in:
7
8
  * - tools/wu-claim.mjs
@@ -14,6 +15,7 @@
14
15
  * - tools/validate.mjs
15
16
  * - tools/guard-worktree-commit.mjs
16
17
  */
18
+ import path from 'node:path';
17
19
  /**
18
20
  * Structured error class with error codes and details
19
21
  * @class WUError
@@ -56,8 +58,9 @@ export class WUError extends Error {
56
58
  */
57
59
  export function die(message, exitCode = 1) {
58
60
  // Auto-detect script name from process.argv[1] (eliminates string literal duplication)
61
+ // WU-1006: Use path.basename() instead of manual split (Library-First principle)
59
62
  const scriptPath = process.argv[1] || 'unknown';
60
- const scriptName = scriptPath.split('/').pop().replace('.js', '');
63
+ const scriptName = path.basename(scriptPath, '.js');
61
64
  console.error(`[${scriptName}] ${message}`);
62
65
  process.exit(exitCode);
63
66
  }
@@ -66,6 +66,13 @@ export declare class GitAdapter {
66
66
  * await git.getStatus(); // " M file.txt\n?? untracked.txt"
67
67
  */
68
68
  getStatus(): Promise<string>;
69
+ /**
70
+ * Get unpushed commits (compared to upstream)
71
+ * @returns {Promise<string>} Oneline log output for unpushed commits
72
+ * @example
73
+ * await git.getUnpushedCommits(); // "abc123 fix: ...\n"
74
+ */
75
+ getUnpushedCommits(): Promise<string>;
69
76
  /**
70
77
  * Check if a branch exists
71
78
  * @param {string} branch - Branch name
@@ -77,6 +84,17 @@ export declare class GitAdapter {
77
84
  * await git.branchExists('nonexistent'); // false
78
85
  */
79
86
  branchExists(branch: string): Promise<boolean>;
87
+ /**
88
+ * Check if a remote branch exists via git ls-remote --heads
89
+ * @param {string} remote - Remote name (e.g., 'origin')
90
+ * @param {string} branch - Branch name
91
+ * @returns {Promise<boolean>} True if remote branch exists
92
+ * @throws {TypeError} If remote or branch is not a string
93
+ * @throws {Error} If remote or branch is empty
94
+ * @example
95
+ * await git.remoteBranchExists('origin', 'lane/operations/wu-123'); // true/false
96
+ */
97
+ remoteBranchExists(remote: string, branch: string): Promise<boolean>;
80
98
  /**
81
99
  * Fetch from remote
82
100
  * @param {string} [remote] - Remote name (defaults to fetching all)
@@ -391,5 +409,10 @@ export declare function createGitForPath(baseDir: string): GitAdapter;
391
409
  * const git = getGitForCwd(); // Uses new directory
392
410
  */
393
411
  export declare function getGitForCwd(): GitAdapter;
412
+ /**
413
+ * Reset singleton warning flag (for testing only)
414
+ * @internal
415
+ */
416
+ export declare function _resetSingletonWarning(): void;
394
417
  export declare const git: GitAdapter;
395
418
  export {};
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { simpleGit } from 'simple-git';
20
20
  import { existsSync, rmSync } from 'node:fs';
21
- import { GIT_FLAGS } from './wu-constants.js';
21
+ import { GIT_COMMANDS, GIT_FLAGS, GIT_REFS } from './wu-constants.js';
22
22
  // WU-2242: Runtime assertion helpers
23
23
  /**
24
24
  * Assert that a value is a non-empty string
@@ -117,6 +117,20 @@ export class GitAdapter {
117
117
  const result = await this.git.raw(['status', GIT_FLAGS.PORCELAIN]);
118
118
  return result.trim();
119
119
  }
120
+ /**
121
+ * Get unpushed commits (compared to upstream)
122
+ * @returns {Promise<string>} Oneline log output for unpushed commits
123
+ * @example
124
+ * await git.getUnpushedCommits(); // "abc123 fix: ...\n"
125
+ */
126
+ async getUnpushedCommits() {
127
+ const result = await this.git.raw([
128
+ GIT_COMMANDS.LOG,
129
+ GIT_REFS.UPSTREAM_RANGE,
130
+ GIT_FLAGS.ONELINE,
131
+ ]);
132
+ return result.trim();
133
+ }
120
134
  /**
121
135
  * Check if a branch exists
122
136
  * @param {string} branch - Branch name
@@ -137,6 +151,22 @@ export class GitAdapter {
137
151
  return false;
138
152
  }
139
153
  }
154
+ /**
155
+ * Check if a remote branch exists via git ls-remote --heads
156
+ * @param {string} remote - Remote name (e.g., 'origin')
157
+ * @param {string} branch - Branch name
158
+ * @returns {Promise<boolean>} True if remote branch exists
159
+ * @throws {TypeError} If remote or branch is not a string
160
+ * @throws {Error} If remote or branch is empty
161
+ * @example
162
+ * await git.remoteBranchExists('origin', 'lane/operations/wu-123'); // true/false
163
+ */
164
+ async remoteBranchExists(remote, branch) {
165
+ assertNonEmptyString(remote, 'remote');
166
+ assertNonEmptyString(branch, 'branch');
167
+ const result = await this.git.raw(['ls-remote', '--heads', remote, branch]);
168
+ return result.trim().length > 0;
169
+ }
140
170
  /**
141
171
  * Fetch from remote
142
172
  * @param {string} [remote] - Remote name (defaults to fetching all)
@@ -465,7 +495,13 @@ export class GitAdapter {
465
495
  // This handles edge cases where git worktree remove succeeds but leaves the directory
466
496
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool with validated worktree path
467
497
  if (existsSync(worktreePath)) {
468
- rmSync(worktreePath, { recursive: true, force: true });
498
+ try {
499
+ rmSync(worktreePath, { recursive: true, force: true });
500
+ }
501
+ catch (rmErr) {
502
+ // WU-1014: Log but don't throw - git worktree remove succeeded, directory cleanup is best-effort
503
+ console.warn(`[git-adapter] worktreeRemove: git succeeded but directory cleanup failed for ${worktreePath}: ${rmErr instanceof Error ? rmErr.message : String(rmErr)}`);
504
+ }
469
505
  }
470
506
  }
471
507
  /**
package/dist/index.d.ts CHANGED
@@ -40,3 +40,6 @@ export * from './dependency-guard.js';
40
40
  export * from './stamp-utils.js';
41
41
  export * from './lumenflow-config.js';
42
42
  export * from './lumenflow-config-schema.js';
43
+ export * from './branch-check.js';
44
+ export * from './lumenflow-home.js';
45
+ export * from './spec-branch-helpers.js';
package/dist/index.js CHANGED
@@ -59,3 +59,8 @@ export * from './stamp-utils.js';
59
59
  // Configuration
60
60
  export * from './lumenflow-config.js';
61
61
  export * from './lumenflow-config-schema.js';
62
+ // Branch check utilities
63
+ export * from './branch-check.js';
64
+ // WU-1062: External plan storage and spec branch helpers
65
+ export * from './lumenflow-home.js';
66
+ export * from './spec-branch-helpers.js';
@@ -18,6 +18,22 @@ interface CheckLaneFreeResult {
18
18
  free: boolean;
19
19
  occupiedBy: string | null;
20
20
  error: string | null;
21
+ /** WU-1016: List of WU IDs currently in progress in this lane */
22
+ inProgressWUs?: string[];
23
+ /** WU-1016: The configured WIP limit for this lane */
24
+ wipLimit?: number;
25
+ /** WU-1016: Current count of in-progress WUs in this lane */
26
+ currentCount?: number;
27
+ }
28
+ /** WU-1016: Options for checkLaneFree */
29
+ interface CheckLaneFreeOptions {
30
+ /** Path to .lumenflow.config.yaml (for testing) */
31
+ configPath?: string;
32
+ }
33
+ /** WU-1016: Options for getWipLimitForLane */
34
+ interface GetWipLimitOptions {
35
+ /** Path to .lumenflow.config.yaml (for testing) */
36
+ configPath?: string;
21
37
  }
22
38
  export { getSubLanesForParent };
23
39
  /**
@@ -41,10 +57,27 @@ export declare function extractParent(lane: string): string;
41
57
  */
42
58
  export declare function validateLaneFormat(lane: string, configPath?: string | null, options?: ValidateLaneOptions): ValidateLaneResult;
43
59
  /**
44
- * Check if a lane is free (no in_progress WU currently in that lane)
60
+ * WU-1016: Get WIP limit for a lane from config
61
+ *
62
+ * Reads the wip_limit field from .lumenflow.config.yaml for the specified lane.
63
+ * Returns DEFAULT_WIP_LIMIT (1) if the lane is not found or wip_limit is not specified.
64
+ *
65
+ * @param {string} lane - Lane name (e.g., "Core", "CLI")
66
+ * @param {GetWipLimitOptions} options - Options including configPath for testing
67
+ * @returns {number} The WIP limit for the lane (default: 1)
68
+ */
69
+ export declare function getWipLimitForLane(lane: string, options?: GetWipLimitOptions): number;
70
+ /**
71
+ * Check if a lane is free (in_progress WU count is below wip_limit)
72
+ *
73
+ * WU-1016: Now respects configurable wip_limit per lane from .lumenflow.config.yaml.
74
+ * Lane is considered "free" if current in_progress count < wip_limit.
75
+ * Default wip_limit is 1 if not specified in config (backward compatible).
76
+ *
45
77
  * @param {string} statusPath - Path to status.md
46
78
  * @param {string} lane - Lane name (e.g., "Operations", "Intelligence")
47
79
  * @param {string} wuid - WU ID being claimed (e.g., "WU-419")
48
- * @returns {{ free: boolean, occupiedBy: string | null, error: string | null }}
80
+ * @param {CheckLaneFreeOptions} options - Options including configPath for testing
81
+ * @returns {{ free: boolean, occupiedBy: string | null, error: string | null, inProgressWUs?: string[], wipLimit?: number, currentCount?: number }}
49
82
  */
50
- export declare function checkLaneFree(statusPath: string, lane: string, wuid: string): CheckLaneFreeResult;
83
+ export declare function checkLaneFree(statusPath: string, lane: string, wuid: string, options?: CheckLaneFreeOptions): CheckLaneFreeResult;