@lumenflow/core 1.3.0 → 1.3.3

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 (44) hide show
  1. package/dist/arg-parser.d.ts +6 -0
  2. package/dist/arg-parser.js +16 -0
  3. package/dist/core/tool.schemas.d.ts +1 -1
  4. package/dist/coverage-gate.d.ts +3 -0
  5. package/dist/coverage-gate.js +7 -4
  6. package/dist/force-bypass-audit.d.ts +63 -0
  7. package/dist/force-bypass-audit.js +140 -0
  8. package/dist/gates-config.d.ts +132 -0
  9. package/dist/gates-config.js +229 -0
  10. package/dist/git-adapter.d.ts +7 -0
  11. package/dist/git-adapter.js +15 -1
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +6 -0
  14. package/dist/lumenflow-config-schema.d.ts +97 -0
  15. package/dist/lumenflow-config-schema.js +9 -0
  16. package/dist/lumenflow-home.d.ts +130 -0
  17. package/dist/lumenflow-home.js +211 -0
  18. package/dist/manual-test-validator.d.ts +3 -0
  19. package/dist/manual-test-validator.js +7 -4
  20. package/dist/orphan-detector.d.ts +16 -0
  21. package/dist/orphan-detector.js +24 -0
  22. package/dist/prompt-linter.js +2 -1
  23. package/dist/prompt-monitor.js +3 -1
  24. package/dist/spec-branch-helpers.d.ts +118 -0
  25. package/dist/spec-branch-helpers.js +199 -0
  26. package/dist/user-normalizer.d.ts +5 -1
  27. package/dist/user-normalizer.js +6 -1
  28. package/dist/validators/phi-scanner.js +6 -0
  29. package/dist/worktree-symlink.d.ts +4 -0
  30. package/dist/worktree-symlink.js +14 -20
  31. package/dist/wu-consistency-checker.d.ts +2 -0
  32. package/dist/wu-consistency-checker.js +35 -1
  33. package/dist/wu-constants.d.ts +193 -0
  34. package/dist/wu-constants.js +200 -4
  35. package/dist/wu-create-validators.d.ts +57 -2
  36. package/dist/wu-create-validators.js +111 -2
  37. package/dist/wu-done-branch-only.js +9 -0
  38. package/dist/wu-done-docs-generate.d.ts +73 -0
  39. package/dist/wu-done-docs-generate.js +108 -0
  40. package/dist/wu-done-worktree.js +12 -0
  41. package/dist/wu-schema.js +3 -1
  42. package/dist/wu-spawn.js +15 -2
  43. package/dist/wu-yaml-fixer.js +6 -3
  44. package/package.json +12 -11
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Spec Branch Helpers
3
+ *
4
+ * WU-1062: External plan storage and no-main-write mode
5
+ *
6
+ * Provides helpers for working with spec branches (spec/wu-XXXX).
7
+ * wu:create writes to spec branches by default; wu:claim merges them to main.
8
+ *
9
+ * @module
10
+ */
11
+ import type { GitAdapter } from './git-adapter.js';
12
+ /**
13
+ * Spec branch prefix
14
+ */
15
+ export declare const SPEC_BRANCH_PREFIX = "spec/";
16
+ /**
17
+ * WU source location constants
18
+ */
19
+ export declare const WU_SOURCE: {
20
+ /** WU exists on main branch only */
21
+ readonly MAIN: "main";
22
+ /** WU exists on spec branch only */
23
+ readonly SPEC_BRANCH: "spec_branch";
24
+ /** WU exists on both main and spec branch */
25
+ readonly BOTH: "both";
26
+ /** WU not found anywhere */
27
+ readonly NOT_FOUND: "not_found";
28
+ };
29
+ export type WUSourceType = (typeof WU_SOURCE)[keyof typeof WU_SOURCE];
30
+ /**
31
+ * Get the spec branch name for a WU
32
+ *
33
+ * @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
34
+ * @returns {string} Spec branch name (e.g., 'spec/wu-1062')
35
+ *
36
+ * @example
37
+ * getSpecBranchName('WU-1062') // 'spec/wu-1062'
38
+ */
39
+ export declare function getSpecBranchName(wuId: string): string;
40
+ /**
41
+ * Get the origin-qualified spec branch name
42
+ *
43
+ * @param {string} wuId - Work Unit ID
44
+ * @returns {string} Origin-qualified branch name (e.g., 'origin/spec/wu-1062')
45
+ */
46
+ export declare function getOriginSpecBranch(wuId: string): string;
47
+ /**
48
+ * Check if a spec branch exists on origin
49
+ *
50
+ * @param {string} wuId - Work Unit ID
51
+ * @param {SimpleGit} git - Git adapter instance
52
+ * @returns {Promise<boolean>} True if spec branch exists
53
+ *
54
+ * @example
55
+ * const exists = await specBranchExists('WU-1062', git);
56
+ */
57
+ export declare function specBranchExists(wuId: string, git: GitAdapter): Promise<boolean>;
58
+ /**
59
+ * Check if a WU exists on main branch
60
+ *
61
+ * @param {string} wuId - Work Unit ID
62
+ * @param {SimpleGit} git - Git adapter instance
63
+ * @returns {Promise<boolean>} True if WU YAML exists on main
64
+ */
65
+ export declare function isWUOnMain(wuId: string, git: GitAdapter): Promise<boolean>;
66
+ /**
67
+ * Merge spec branch to main branch (fast-forward only)
68
+ *
69
+ * This is used by wu:claim when a WU exists only on a spec branch.
70
+ * The spec branch is merged to main before creating the worktree.
71
+ *
72
+ * @param {string} wuId - Work Unit ID
73
+ * @param {SimpleGit} git - Git adapter instance
74
+ * @throws {Error} If merge fails (e.g., due to conflicts)
75
+ *
76
+ * @example
77
+ * await mergeSpecBranchToMain('WU-1062', git);
78
+ */
79
+ export declare function mergeSpecBranchToMain(wuId: string, git: GitAdapter): Promise<void>;
80
+ /**
81
+ * Delete spec branch after merge
82
+ *
83
+ * @param {string} wuId - Work Unit ID
84
+ * @param {SimpleGit} git - Git adapter instance
85
+ */
86
+ export declare function deleteSpecBranch(wuId: string, git: GitAdapter): Promise<void>;
87
+ /**
88
+ * Determine the source of a WU (main, spec branch, both, or not found)
89
+ *
90
+ * Used by wu:claim to decide whether to merge spec branch before creating worktree.
91
+ *
92
+ * @param {string} wuId - Work Unit ID
93
+ * @param {SimpleGit} git - Git adapter instance
94
+ * @returns {Promise<WUSourceType>} Source location constant
95
+ *
96
+ * @example
97
+ * const source = await getWUSource('WU-1062', git);
98
+ * if (source === WU_SOURCE.SPEC_BRANCH) {
99
+ * await mergeSpecBranchToMain('WU-1062', git);
100
+ * }
101
+ */
102
+ export declare function getWUSource(wuId: string, git: GitAdapter): Promise<WUSourceType>;
103
+ /**
104
+ * Create a spec branch from current HEAD
105
+ *
106
+ * Used by wu:create in default mode (no --direct flag).
107
+ *
108
+ * @param {string} wuId - Work Unit ID
109
+ * @param {SimpleGit} git - Git adapter instance
110
+ */
111
+ export declare function createSpecBranch(wuId: string, git: GitAdapter): Promise<void>;
112
+ /**
113
+ * Push spec branch to origin
114
+ *
115
+ * @param {string} wuId - Work Unit ID
116
+ * @param {SimpleGit} git - Git adapter instance
117
+ */
118
+ export declare function pushSpecBranch(wuId: string, git: GitAdapter): Promise<void>;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Spec Branch Helpers
3
+ *
4
+ * WU-1062: External plan storage and no-main-write mode
5
+ *
6
+ * Provides helpers for working with spec branches (spec/wu-XXXX).
7
+ * wu:create writes to spec branches by default; wu:claim merges them to main.
8
+ *
9
+ * @module
10
+ */
11
+ import { WU_PATHS } from './wu-paths.js';
12
+ import { REMOTES, GIT_REFS, GIT_COMMANDS, GIT_FLAGS } from './wu-constants.js';
13
+ /**
14
+ * Spec branch prefix
15
+ */
16
+ export const SPEC_BRANCH_PREFIX = 'spec/';
17
+ /**
18
+ * WU source location constants
19
+ */
20
+ export const WU_SOURCE = {
21
+ /** WU exists on main branch only */
22
+ MAIN: 'main',
23
+ /** WU exists on spec branch only */
24
+ SPEC_BRANCH: 'spec_branch',
25
+ /** WU exists on both main and spec branch */
26
+ BOTH: 'both',
27
+ /** WU not found anywhere */
28
+ NOT_FOUND: 'not_found',
29
+ };
30
+ /**
31
+ * Get the spec branch name for a WU
32
+ *
33
+ * @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
34
+ * @returns {string} Spec branch name (e.g., 'spec/wu-1062')
35
+ *
36
+ * @example
37
+ * getSpecBranchName('WU-1062') // 'spec/wu-1062'
38
+ */
39
+ export function getSpecBranchName(wuId) {
40
+ return `${SPEC_BRANCH_PREFIX}${wuId.toLowerCase()}`;
41
+ }
42
+ /**
43
+ * Get the origin-qualified spec branch name
44
+ *
45
+ * @param {string} wuId - Work Unit ID
46
+ * @returns {string} Origin-qualified branch name (e.g., 'origin/spec/wu-1062')
47
+ */
48
+ export function getOriginSpecBranch(wuId) {
49
+ return GIT_REFS.remote(REMOTES.ORIGIN, getSpecBranchName(wuId));
50
+ }
51
+ /**
52
+ * Check if a spec branch exists on origin
53
+ *
54
+ * @param {string} wuId - Work Unit ID
55
+ * @param {SimpleGit} git - Git adapter instance
56
+ * @returns {Promise<boolean>} True if spec branch exists
57
+ *
58
+ * @example
59
+ * const exists = await specBranchExists('WU-1062', git);
60
+ */
61
+ export async function specBranchExists(wuId, git) {
62
+ try {
63
+ const originBranch = getOriginSpecBranch(wuId);
64
+ // Use branchExists if available, otherwise check with ls-remote
65
+ if ('branchExists' in git && typeof git.branchExists === 'function') {
66
+ return await git.branchExists(originBranch);
67
+ }
68
+ // Fallback: use ls-remote to check if branch exists
69
+ const result = await git.raw([
70
+ GIT_COMMANDS.LS_REMOTE,
71
+ GIT_FLAGS.HEADS,
72
+ REMOTES.ORIGIN,
73
+ getSpecBranchName(wuId),
74
+ ]);
75
+ return result.trim().length > 0;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * Check if a WU exists on main branch
83
+ *
84
+ * @param {string} wuId - Work Unit ID
85
+ * @param {SimpleGit} git - Git adapter instance
86
+ * @returns {Promise<boolean>} True if WU YAML exists on main
87
+ */
88
+ export async function isWUOnMain(wuId, git) {
89
+ try {
90
+ const wuPath = WU_PATHS.WU(wuId);
91
+ // Check if file exists on origin/main
92
+ // git ls-tree returns exit 0 with empty output if file doesn't exist
93
+ const result = await git.raw([GIT_COMMANDS.LS_TREE, GIT_REFS.ORIGIN_MAIN, GIT_FLAGS.PATH_SEPARATOR, wuPath]);
94
+ return result.trim().length > 0;
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
100
+ /**
101
+ * Merge spec branch to main branch (fast-forward only)
102
+ *
103
+ * This is used by wu:claim when a WU exists only on a spec branch.
104
+ * The spec branch is merged to main before creating the worktree.
105
+ *
106
+ * @param {string} wuId - Work Unit ID
107
+ * @param {SimpleGit} git - Git adapter instance
108
+ * @throws {Error} If merge fails (e.g., due to conflicts)
109
+ *
110
+ * @example
111
+ * await mergeSpecBranchToMain('WU-1062', git);
112
+ */
113
+ export async function mergeSpecBranchToMain(wuId, git) {
114
+ const specBranch = getSpecBranchName(wuId);
115
+ const originSpecBranch = getOriginSpecBranch(wuId);
116
+ // Fetch the spec branch
117
+ await git.fetch(REMOTES.ORIGIN, specBranch);
118
+ // Merge with fast-forward only (safe merge)
119
+ await git.merge(originSpecBranch, { ffOnly: true });
120
+ }
121
+ /**
122
+ * Delete spec branch after merge
123
+ *
124
+ * @param {string} wuId - Work Unit ID
125
+ * @param {SimpleGit} git - Git adapter instance
126
+ */
127
+ export async function deleteSpecBranch(wuId, git) {
128
+ const specBranch = getSpecBranchName(wuId);
129
+ try {
130
+ // Delete local branch if exists
131
+ await git.deleteBranch(specBranch);
132
+ }
133
+ catch {
134
+ // Ignore if local branch doesn't exist
135
+ }
136
+ try {
137
+ // Delete remote branch
138
+ await git.raw([GIT_COMMANDS.PUSH, REMOTES.ORIGIN, GIT_FLAGS.DELETE_REMOTE, specBranch]);
139
+ }
140
+ catch {
141
+ // Ignore if remote branch doesn't exist
142
+ }
143
+ }
144
+ /**
145
+ * Determine the source of a WU (main, spec branch, both, or not found)
146
+ *
147
+ * Used by wu:claim to decide whether to merge spec branch before creating worktree.
148
+ *
149
+ * @param {string} wuId - Work Unit ID
150
+ * @param {SimpleGit} git - Git adapter instance
151
+ * @returns {Promise<WUSourceType>} Source location constant
152
+ *
153
+ * @example
154
+ * const source = await getWUSource('WU-1062', git);
155
+ * if (source === WU_SOURCE.SPEC_BRANCH) {
156
+ * await mergeSpecBranchToMain('WU-1062', git);
157
+ * }
158
+ */
159
+ export async function getWUSource(wuId, git) {
160
+ // Check both locations in parallel for efficiency
161
+ const [onMain, hasSpecBranch] = await Promise.all([
162
+ isWUOnMain(wuId, git),
163
+ specBranchExists(wuId, git),
164
+ ]);
165
+ if (onMain && hasSpecBranch) {
166
+ return WU_SOURCE.BOTH;
167
+ }
168
+ if (onMain) {
169
+ return WU_SOURCE.MAIN;
170
+ }
171
+ if (hasSpecBranch) {
172
+ return WU_SOURCE.SPEC_BRANCH;
173
+ }
174
+ return WU_SOURCE.NOT_FOUND;
175
+ }
176
+ /**
177
+ * Create a spec branch from current HEAD
178
+ *
179
+ * Used by wu:create in default mode (no --direct flag).
180
+ *
181
+ * @param {string} wuId - Work Unit ID
182
+ * @param {SimpleGit} git - Git adapter instance
183
+ */
184
+ export async function createSpecBranch(wuId, git) {
185
+ const specBranch = getSpecBranchName(wuId);
186
+ // Create local branch and checkout
187
+ await git.createBranch(specBranch);
188
+ }
189
+ /**
190
+ * Push spec branch to origin
191
+ *
192
+ * @param {string} wuId - Work Unit ID
193
+ * @param {SimpleGit} git - Git adapter instance
194
+ */
195
+ export async function pushSpecBranch(wuId, git) {
196
+ const specBranch = getSpecBranchName(wuId);
197
+ // Push to origin
198
+ await git.push(REMOTES.ORIGIN, specBranch);
199
+ }
@@ -4,11 +4,15 @@
4
4
  * Provides email normalization and domain inference for WU ownership.
5
5
  * Converts plain usernames (e.g., "tom") to email format (e.g., "tom@hellm.ai")
6
6
  * using domain from git config or .lumenflow.config.yaml.
7
+ *
8
+ * WU-1068: Removed hardcoded patientpath.co.uk domain. Domain is now inferred
9
+ * from git config user.email or .lumenflow.config.yaml OWNER_EMAIL.
7
10
  */
8
11
  /**
9
12
  * Default domain fallback when git config and lumenflow config unavailable
13
+ * WU-1068: Changed from hardcoded 'patientpath.co.uk' to generic default
10
14
  */
11
- export declare const DEFAULT_DOMAIN = "patientpath.co.uk";
15
+ export declare const DEFAULT_DOMAIN: string;
12
16
  /**
13
17
  * Check if a value is a valid email address (simple check)
14
18
  *
@@ -4,14 +4,19 @@
4
4
  * Provides email normalization and domain inference for WU ownership.
5
5
  * Converts plain usernames (e.g., "tom") to email format (e.g., "tom@hellm.ai")
6
6
  * using domain from git config or .lumenflow.config.yaml.
7
+ *
8
+ * WU-1068: Removed hardcoded patientpath.co.uk domain. Domain is now inferred
9
+ * from git config user.email or .lumenflow.config.yaml OWNER_EMAIL.
7
10
  */
8
11
  import { readFile, access } from 'node:fs/promises';
9
12
  import { join } from 'node:path';
10
13
  import { getGitForCwd } from './git-adapter.js';
14
+ import { DEFAULTS } from './wu-constants.js';
11
15
  /**
12
16
  * Default domain fallback when git config and lumenflow config unavailable
17
+ * WU-1068: Changed from hardcoded 'patientpath.co.uk' to generic default
13
18
  */
14
- export const DEFAULT_DOMAIN = 'patientpath.co.uk';
19
+ export const DEFAULT_DOMAIN = DEFAULTS.EMAIL_DOMAIN;
15
20
  /**
16
21
  * Minimum length for a valid email local part
17
22
  */
@@ -13,6 +13,7 @@
13
13
  import nhsValidator from 'nhs-number-validator';
14
14
  import { isValid as isValidPostcode, parse as parsePostcode } from 'postcode';
15
15
  import { PHI_TYPES, MEDICAL_CONTEXT_KEYWORDS, MEDICAL_CONTEXT_WINDOW_SIZE, TEST_NHS_NUMBERS, NHS_TEST_PREFIX, TEST_POSTCODES, TEST_DATA_MARKERS, EXCLUDED_PATH_PATTERNS, NHS_CANDIDATE_PATTERN, } from './phi-constants.js';
16
+ import { PHI_CONFIG } from '../wu-constants.js';
16
17
  /**
17
18
  * Check if a file path should be excluded from PHI scanning
18
19
  *
@@ -137,6 +138,11 @@ export function scanForPHI(content, options = {}) {
137
138
  matches: [],
138
139
  warnings: [],
139
140
  };
141
+ // WU-1068: PHI scanning is gated behind config flag
142
+ // Projects must explicitly opt-in via LUMENFLOW_PHI_ENABLED=1 or config
143
+ if (!PHI_CONFIG.ENABLED) {
144
+ return result;
145
+ }
140
146
  // Handle null/undefined/empty content
141
147
  if (!content || typeof content !== 'string' || content.trim() === '') {
142
148
  return result;
@@ -18,6 +18,10 @@
18
18
  * pnpm monorepos create node_modules in each package with symlinks
19
19
  * to the .pnpm store. turbo typecheck and tests need these to resolve imports.
20
20
  *
21
+ * WU-1068: Removed @patientpath references. Only @lumenflow packages are
22
+ * relevant to the LumenFlow framework. Project-specific paths should be
23
+ * configured in .lumenflow.config.yaml.
24
+ *
21
25
  * @type {string[]}
22
26
  */
23
27
  export declare const NESTED_PACKAGE_PATHS: string[];
@@ -43,30 +43,24 @@ const PNPM_DIR = '.pnpm';
43
43
  * pnpm monorepos create node_modules in each package with symlinks
44
44
  * to the .pnpm store. turbo typecheck and tests need these to resolve imports.
45
45
  *
46
+ * WU-1068: Removed @patientpath references. Only @lumenflow packages are
47
+ * relevant to the LumenFlow framework. Project-specific paths should be
48
+ * configured in .lumenflow.config.yaml.
49
+ *
46
50
  * @type {string[]}
47
51
  */
48
52
  export const NESTED_PACKAGE_PATHS = [
49
- // Packages - supabase
50
- 'packages/supabase',
51
- // Packages - @patientpath/*
52
- 'packages/@patientpath/prompts',
53
- 'packages/@patientpath/shared',
54
- 'packages/@patientpath/application',
55
- 'packages/@patientpath/ports',
56
- 'packages/@patientpath/infrastructure',
57
- // Packages - @lumenflow/* (WU-2427: added for typecheck resolution)
58
- 'packages/@lumenflow/api',
59
- 'packages/@lumenflow/application',
60
- 'packages/@lumenflow/infrastructure',
61
- // Packages - lumenflow-*
62
- 'packages/lumenflow-cli',
63
- 'packages/lumenflow-tools',
64
- // Packages - beacon-explainer (WU-2427: added for typecheck resolution)
65
- 'packages/beacon-explainer',
66
- // Apps
53
+ // Packages - @lumenflow/*
54
+ 'packages/@lumenflow/core',
55
+ 'packages/@lumenflow/cli',
56
+ 'packages/@lumenflow/memory',
57
+ 'packages/@lumenflow/agent',
58
+ 'packages/@lumenflow/metrics',
59
+ 'packages/@lumenflow/initiatives',
60
+ 'packages/@lumenflow/shims',
61
+ // Apps (generic placeholders)
67
62
  'apps/web',
68
- 'apps/mobile',
69
- 'apps/hellm-ai', // WU-2427: added for typecheck resolution
63
+ 'apps/docs',
70
64
  ];
71
65
  /**
72
66
  * Check if a symlink target points into a worktrees directory
@@ -30,6 +30,7 @@ export declare function checkWUConsistency(id: any, projectRoot?: string): Promi
30
30
  backlogInProgress?: undefined;
31
31
  statusInProgress?: undefined;
32
32
  hasWorktree?: undefined;
33
+ worktreePathExists?: undefined;
33
34
  };
34
35
  } | {
35
36
  valid: boolean;
@@ -41,6 +42,7 @@ export declare function checkWUConsistency(id: any, projectRoot?: string): Promi
41
42
  backlogInProgress: boolean;
42
43
  statusInProgress: boolean;
43
44
  hasWorktree: boolean;
45
+ worktreePathExists: boolean;
44
46
  wuExists?: undefined;
45
47
  };
46
48
  }>;
@@ -17,7 +17,7 @@ import { constants } from 'node:fs';
17
17
  import path from 'node:path';
18
18
  import { parseYAML, stringifyYAML } from './wu-yaml.js';
19
19
  import { WU_PATHS } from './wu-paths.js';
20
- import { CONSISTENCY_TYPES, LOG_PREFIX, REMOTES, STRING_LITERALS, toKebab, WU_STATUS, YAML_OPTIONS, } from './wu-constants.js';
20
+ import { CONSISTENCY_TYPES, CONSISTENCY_MESSAGES, LOG_PREFIX, REMOTES, STRING_LITERALS, toKebab, WU_STATUS, YAML_OPTIONS, } from './wu-constants.js';
21
21
  import { todayISO } from './date-utils.js';
22
22
  import { createGitForPath } from './git-adapter.js';
23
23
  /**
@@ -45,6 +45,7 @@ export async function checkWUConsistency(id, projectRoot = process.cwd()) {
45
45
  const yamlStatus = wuDoc?.status || 'unknown';
46
46
  const lane = wuDoc?.lane || '';
47
47
  const title = wuDoc?.title || '';
48
+ const worktreePathFromYaml = wuDoc?.worktree_path || '';
48
49
  // Check stamp existence
49
50
  let hasStamp = false;
50
51
  try {
@@ -74,6 +75,7 @@ export async function checkWUConsistency(id, projectRoot = process.cwd()) {
74
75
  const { inProgress: statusInProgress } = parseStatusSections(statusContent, id);
75
76
  // Check for worktree
76
77
  const hasWorktree = await checkWorktreeExists(id, projectRoot);
78
+ const worktreePathExists = await checkWorktreePathExists(worktreePathFromYaml);
77
79
  // Detection logic
78
80
  // 1. YAML done but in status.md In Progress
79
81
  if (yamlStatus === WU_STATUS.DONE && statusInProgress) {
@@ -129,6 +131,19 @@ export async function checkWUConsistency(id, projectRoot = process.cwd()) {
129
131
  canAutoRepair: true,
130
132
  });
131
133
  }
134
+ // 6. Claimed WU missing worktree directory
135
+ if (worktreePathFromYaml &&
136
+ !worktreePathExists &&
137
+ (yamlStatus === WU_STATUS.IN_PROGRESS || yamlStatus === WU_STATUS.BLOCKED)) {
138
+ errors.push({
139
+ type: CONSISTENCY_TYPES.MISSING_WORKTREE_CLAIMED,
140
+ wuId: id,
141
+ title,
142
+ description: CONSISTENCY_MESSAGES.MISSING_WORKTREE_CLAIMED(id, yamlStatus, worktreePathFromYaml),
143
+ repairAction: CONSISTENCY_MESSAGES.MISSING_WORKTREE_CLAIMED_REPAIR,
144
+ canAutoRepair: false,
145
+ });
146
+ }
132
147
  return {
133
148
  valid: errors.length === 0,
134
149
  errors,
@@ -139,6 +154,7 @@ export async function checkWUConsistency(id, projectRoot = process.cwd()) {
139
154
  backlogInProgress,
140
155
  statusInProgress,
141
156
  hasWorktree,
157
+ worktreePathExists,
142
158
  },
143
159
  };
144
160
  }
@@ -565,3 +581,21 @@ async function checkWorktreeExists(id, projectRoot) {
565
581
  return false;
566
582
  }
567
583
  }
584
+ /**
585
+ * Check whether a worktree path exists on disk
586
+ *
587
+ * @param {string} worktreePath - Worktree path from WU YAML
588
+ * @returns {Promise<boolean>} True if path exists
589
+ */
590
+ async function checkWorktreePathExists(worktreePath) {
591
+ if (!worktreePath) {
592
+ return false;
593
+ }
594
+ try {
595
+ await access(worktreePath, constants.R_OK);
596
+ return true;
597
+ }
598
+ catch {
599
+ return false;
600
+ }
601
+ }