@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,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 { SimpleGit } from 'simple-git';
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: SimpleGit): 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: SimpleGit): 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: SimpleGit): 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: SimpleGit): 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: SimpleGit): 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: SimpleGit): 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: SimpleGit): Promise<void>;
@@ -0,0 +1,192 @@
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
+ /**
13
+ * Spec branch prefix
14
+ */
15
+ export const SPEC_BRANCH_PREFIX = 'spec/';
16
+ /**
17
+ * WU source location constants
18
+ */
19
+ export const WU_SOURCE = {
20
+ /** WU exists on main branch only */
21
+ MAIN: 'main',
22
+ /** WU exists on spec branch only */
23
+ SPEC_BRANCH: 'spec_branch',
24
+ /** WU exists on both main and spec branch */
25
+ BOTH: 'both',
26
+ /** WU not found anywhere */
27
+ NOT_FOUND: 'not_found',
28
+ };
29
+ /**
30
+ * Get the spec branch name for a WU
31
+ *
32
+ * @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
33
+ * @returns {string} Spec branch name (e.g., 'spec/wu-1062')
34
+ *
35
+ * @example
36
+ * getSpecBranchName('WU-1062') // 'spec/wu-1062'
37
+ */
38
+ export function getSpecBranchName(wuId) {
39
+ return `${SPEC_BRANCH_PREFIX}${wuId.toLowerCase()}`;
40
+ }
41
+ /**
42
+ * Get the origin-qualified spec branch name
43
+ *
44
+ * @param {string} wuId - Work Unit ID
45
+ * @returns {string} Origin-qualified branch name (e.g., 'origin/spec/wu-1062')
46
+ */
47
+ export function getOriginSpecBranch(wuId) {
48
+ return `origin/${getSpecBranchName(wuId)}`;
49
+ }
50
+ /**
51
+ * Check if a spec branch exists on origin
52
+ *
53
+ * @param {string} wuId - Work Unit ID
54
+ * @param {SimpleGit} git - Git adapter instance
55
+ * @returns {Promise<boolean>} True if spec branch exists
56
+ *
57
+ * @example
58
+ * const exists = await specBranchExists('WU-1062', git);
59
+ */
60
+ export async function specBranchExists(wuId, git) {
61
+ try {
62
+ const originBranch = getOriginSpecBranch(wuId);
63
+ // Use branchExists if available, otherwise check with ls-remote
64
+ if ('branchExists' in git && typeof git.branchExists === 'function') {
65
+ return await git.branchExists(originBranch);
66
+ }
67
+ // Fallback: use ls-remote to check if branch exists
68
+ const result = await git.raw(['ls-remote', '--heads', 'origin', getSpecBranchName(wuId)]);
69
+ return result.trim().length > 0;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ /**
76
+ * Check if a WU exists on main branch
77
+ *
78
+ * @param {string} wuId - Work Unit ID
79
+ * @param {SimpleGit} git - Git adapter instance
80
+ * @returns {Promise<boolean>} True if WU YAML exists on main
81
+ */
82
+ export async function isWUOnMain(wuId, git) {
83
+ try {
84
+ const wuPath = WU_PATHS.WU(wuId);
85
+ // Check if file exists on origin/main
86
+ await git.raw(['ls-tree', 'origin/main', '--', wuPath]);
87
+ return true;
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
93
+ /**
94
+ * Merge spec branch to main branch (fast-forward only)
95
+ *
96
+ * This is used by wu:claim when a WU exists only on a spec branch.
97
+ * The spec branch is merged to main before creating the worktree.
98
+ *
99
+ * @param {string} wuId - Work Unit ID
100
+ * @param {SimpleGit} git - Git adapter instance
101
+ * @throws {Error} If merge fails (e.g., due to conflicts)
102
+ *
103
+ * @example
104
+ * await mergeSpecBranchToMain('WU-1062', git);
105
+ */
106
+ export async function mergeSpecBranchToMain(wuId, git) {
107
+ const specBranch = getSpecBranchName(wuId);
108
+ const originSpecBranch = getOriginSpecBranch(wuId);
109
+ // Fetch the spec branch
110
+ await git.fetch('origin', specBranch);
111
+ // Merge with fast-forward only (safe merge)
112
+ await git.merge([originSpecBranch, '--ff-only']);
113
+ }
114
+ /**
115
+ * Delete spec branch after merge
116
+ *
117
+ * @param {string} wuId - Work Unit ID
118
+ * @param {SimpleGit} git - Git adapter instance
119
+ */
120
+ export async function deleteSpecBranch(wuId, git) {
121
+ const specBranch = getSpecBranchName(wuId);
122
+ try {
123
+ // Delete local branch if exists
124
+ await git.branch(['-d', specBranch]);
125
+ }
126
+ catch {
127
+ // Ignore if local branch doesn't exist
128
+ }
129
+ try {
130
+ // Delete remote branch
131
+ await git.push(['origin', '--delete', specBranch]);
132
+ }
133
+ catch {
134
+ // Ignore if remote branch doesn't exist
135
+ }
136
+ }
137
+ /**
138
+ * Determine the source of a WU (main, spec branch, both, or not found)
139
+ *
140
+ * Used by wu:claim to decide whether to merge spec branch before creating worktree.
141
+ *
142
+ * @param {string} wuId - Work Unit ID
143
+ * @param {SimpleGit} git - Git adapter instance
144
+ * @returns {Promise<WUSourceType>} Source location constant
145
+ *
146
+ * @example
147
+ * const source = await getWUSource('WU-1062', git);
148
+ * if (source === WU_SOURCE.SPEC_BRANCH) {
149
+ * await mergeSpecBranchToMain('WU-1062', git);
150
+ * }
151
+ */
152
+ export async function getWUSource(wuId, git) {
153
+ // Check both locations in parallel for efficiency
154
+ const [onMain, hasSpecBranch] = await Promise.all([
155
+ isWUOnMain(wuId, git),
156
+ specBranchExists(wuId, git),
157
+ ]);
158
+ if (onMain && hasSpecBranch) {
159
+ return WU_SOURCE.BOTH;
160
+ }
161
+ if (onMain) {
162
+ return WU_SOURCE.MAIN;
163
+ }
164
+ if (hasSpecBranch) {
165
+ return WU_SOURCE.SPEC_BRANCH;
166
+ }
167
+ return WU_SOURCE.NOT_FOUND;
168
+ }
169
+ /**
170
+ * Create a spec branch from current HEAD
171
+ *
172
+ * Used by wu:create in default mode (no --direct flag).
173
+ *
174
+ * @param {string} wuId - Work Unit ID
175
+ * @param {SimpleGit} git - Git adapter instance
176
+ */
177
+ export async function createSpecBranch(wuId, git) {
178
+ const specBranch = getSpecBranchName(wuId);
179
+ // Create local branch
180
+ await git.checkoutLocalBranch(specBranch);
181
+ }
182
+ /**
183
+ * Push spec branch to origin
184
+ *
185
+ * @param {string} wuId - Work Unit ID
186
+ * @param {SimpleGit} git - Git adapter instance
187
+ */
188
+ export async function pushSpecBranch(wuId, git) {
189
+ const specBranch = getSpecBranchName(wuId);
190
+ // Push to origin
191
+ await git.push(['origin', specBranch]);
192
+ }
@@ -26,6 +26,16 @@ export declare const STAMP_FORMAT_ERRORS: Readonly<{
26
26
  /** WU ID in stamp does not match expected ID */
27
27
  WU_ID_MISMATCH: "WU_ID_MISMATCH";
28
28
  }>;
29
+ /**
30
+ * Validate that a date string is a valid ISO date (YYYY-MM-DD)
31
+ *
32
+ * WU-1006: Uses date-fns parse() and isValid() instead of manual parseInt parsing
33
+ * Library-First principle: leverage well-known libraries over brittle custom code
34
+ *
35
+ * @param {string} dateStr - Date string in YYYY-MM-DD format
36
+ * @returns {boolean} True if date is valid
37
+ */
38
+ export declare function isValidDateString(dateStr: string): boolean;
29
39
  /**
30
40
  * Create stamp file (idempotent - safe to call multiple times)
31
41
  *
@@ -14,6 +14,7 @@ import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
14
14
  import { readFile, access } from 'node:fs/promises';
15
15
  import { constants } from 'node:fs';
16
16
  import path from 'node:path';
17
+ import { parse, isValid } from 'date-fns';
17
18
  import { WU_PATHS } from './wu-paths.js';
18
19
  import { todayISO } from './date-utils.js';
19
20
  /**
@@ -34,34 +35,31 @@ export const STAMP_FORMAT_ERRORS = Object.freeze({
34
35
  WU_ID_MISMATCH: 'WU_ID_MISMATCH',
35
36
  });
36
37
  /**
37
- * Valid date regex: YYYY-MM-DD format
38
+ * Valid date regex: YYYY-MM-DD format (for format checking before parsing)
38
39
  * @type {RegExp}
39
40
  */
40
- const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
41
+ const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
41
42
  /**
42
- * Validate that a date string is a valid ISO date
43
+ * Validate that a date string is a valid ISO date (YYYY-MM-DD)
44
+ *
45
+ * WU-1006: Uses date-fns parse() and isValid() instead of manual parseInt parsing
46
+ * Library-First principle: leverage well-known libraries over brittle custom code
47
+ *
43
48
  * @param {string} dateStr - Date string in YYYY-MM-DD format
44
49
  * @returns {boolean} True if date is valid
45
50
  */
46
- function isValidDate(dateStr) {
47
- const match = dateStr.match(DATE_PATTERN);
48
- if (!match) {
49
- return false;
50
- }
51
- const year = parseInt(match[1], 10);
52
- const month = parseInt(match[2], 10);
53
- const day = parseInt(match[3], 10);
54
- // Basic validation
55
- if (month < 1 || month > 12) {
56
- return false;
57
- }
58
- // Check day validity for the month
59
- const daysInMonth = new Date(year, month, 0).getDate();
60
- if (day < 1 || day > daysInMonth) {
51
+ export function isValidDateString(dateStr) {
52
+ // Quick format check - must be YYYY-MM-DD pattern
53
+ if (!dateStr || !DATE_FORMAT_PATTERN.test(dateStr)) {
61
54
  return false;
62
55
  }
63
- return true;
56
+ // Parse with date-fns and validate the result
57
+ // parse() with strict format ensures proper date validation
58
+ const parsed = parse(dateStr, 'yyyy-MM-dd', new Date());
59
+ return isValid(parsed);
64
60
  }
61
+ // Internal alias for backward compatibility
62
+ const isValidDate = isValidDateString;
65
63
  /**
66
64
  * Stamp file body template (eliminates magic string)
67
65
  * Single source of truth for stamp format
@@ -9,7 +9,7 @@
9
9
  import { get_encoding } from 'tiktoken';
10
10
  import { readFileSync } from 'fs';
11
11
  import { createHash } from 'crypto';
12
- import { load as loadYAML } from 'js-yaml';
12
+ import { parseYAML } from './wu-yaml.js';
13
13
  import { createError, ErrorCodes } from './error-handler.js';
14
14
  import { EXIT_CODES, STRING_LITERALS } from './wu-constants.js';
15
15
  // Cache tokenizer instance (expensive to create)
@@ -65,7 +65,7 @@ export function loadPrompt(promptPath) {
65
65
  try {
66
66
  const raw = readFileSync(promptPath, { encoding: 'utf-8' });
67
67
  // Parse YAML to access prompt structure
68
- const parsed = loadYAML(raw);
68
+ const parsed = parseYAML(raw);
69
69
  // Extract prompt text (handle different YAML structures)
70
70
  let promptText = '';
71
71
  if (typeof parsed === 'string') {
@@ -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
  }>;
@@ -15,9 +15,9 @@
15
15
  import { readFile, writeFile, readdir, mkdir, access } from 'node:fs/promises';
16
16
  import { constants } from 'node:fs';
17
17
  import path from 'node:path';
18
- import yaml from 'js-yaml';
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
  /**
@@ -41,10 +41,11 @@ export async function checkWUConsistency(id, projectRoot = process.cwd()) {
41
41
  return { valid: true, errors: [], stats: { wuExists: false } };
42
42
  }
43
43
  const wuContent = await readFile(wuPath, { encoding: 'utf-8' });
44
- const wuDoc = yaml.load(wuContent);
44
+ const wuDoc = parseYAML(wuContent);
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
  }
@@ -202,7 +218,7 @@ export async function checkLaneForOrphanDoneWU(lane, excludeId, projectRoot = pr
202
218
  }
203
219
  let wuDoc;
204
220
  try {
205
- wuDoc = yaml.load(wuContent);
221
+ wuDoc = parseYAML(wuContent);
206
222
  }
207
223
  catch {
208
224
  // Skip malformed YAML files - they're a separate issue
@@ -341,7 +357,7 @@ async function updateYamlToDone(id, projectRoot) {
341
357
  const wuPath = path.join(projectRoot, WU_PATHS.WU(id));
342
358
  // Read current YAML
343
359
  const content = await readFile(wuPath, { encoding: 'utf-8' });
344
- const wuDoc = yaml.load(content);
360
+ const wuDoc = parseYAML(content);
345
361
  if (!wuDoc) {
346
362
  throw new Error(`Failed to parse WU YAML: ${wuPath}`);
347
363
  }
@@ -353,7 +369,7 @@ async function updateYamlToDone(id, projectRoot) {
353
369
  wuDoc.completed = todayISO();
354
370
  }
355
371
  // Write updated YAML
356
- const updatedContent = yaml.dump(wuDoc, { lineWidth: YAML_OPTIONS.LINE_WIDTH });
372
+ const updatedContent = stringifyYAML(wuDoc, { lineWidth: YAML_OPTIONS.LINE_WIDTH });
357
373
  await writeFile(wuPath, updatedContent, { encoding: 'utf-8' });
358
374
  }
359
375
  /**
@@ -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
+ }
@@ -40,6 +40,10 @@ export declare const GIT_REFS: {
40
40
  ORIGIN_MAIN: string;
41
41
  /** Current HEAD ref */
42
42
  HEAD: string;
43
+ /** Upstream ref */
44
+ UPSTREAM: string;
45
+ /** Range of upstream..HEAD */
46
+ UPSTREAM_RANGE: string;
43
47
  /** Fetch head ref */
44
48
  FETCH_HEAD: string;
45
49
  };
@@ -258,6 +262,22 @@ export declare const CONSISTENCY_TYPES: {
258
262
  ORPHAN_WORKTREE_DONE: string;
259
263
  /** Stamp file exists but WU YAML status is not 'done' (partial wu:done failure) */
260
264
  STAMP_EXISTS_YAML_NOT_DONE: string;
265
+ /** WU is claimed but its worktree directory is missing */
266
+ MISSING_WORKTREE_CLAIMED: string;
267
+ };
268
+ /**
269
+ * Consistency check messages
270
+ */
271
+ export declare const CONSISTENCY_MESSAGES: {
272
+ MISSING_WORKTREE_CLAIMED: (id: any, status: any, worktreePath: any) => string;
273
+ MISSING_WORKTREE_CLAIMED_REPAIR: string;
274
+ };
275
+ /**
276
+ * Worktree warning messages
277
+ */
278
+ export declare const WORKTREE_WARNINGS: {
279
+ MISSING_TRACKED_HEADER: string;
280
+ MISSING_TRACKED_LINE: (worktreePath: any) => string;
261
281
  };
262
282
  /**
263
283
  * File system constants
@@ -270,6 +290,23 @@ export declare const FILE_SYSTEM: {
270
290
  /** UTF-8 encoding (alias for compatibility) */
271
291
  UTF8: string;
272
292
  };
293
+ /**
294
+ * Build artifact cleanup globs
295
+ *
296
+ * Centralized glob patterns for worktree artifact cleanup.
297
+ */
298
+ export declare const BUILD_ARTIFACT_GLOBS: {
299
+ /** Common dist directories inside worktrees */
300
+ DIST_DIRS: string[];
301
+ /** TypeScript build info files */
302
+ TSBUILDINFO_FILES: string[];
303
+ };
304
+ /**
305
+ * Build artifact cleanup ignore patterns
306
+ *
307
+ * Centralized ignore globs for artifact cleanup.
308
+ */
309
+ export declare const BUILD_ARTIFACT_IGNORES: string[];
273
310
  /**
274
311
  * Process stdio constants
275
312
  *
@@ -352,12 +389,12 @@ export declare const DEFAULTS: {
352
389
  * YAML serialization options
353
390
  *
354
391
  * Centralized from duplicated { lineWidth: 100 } across wu-* scripts (WU-1256).
355
- * Use with js-yaml dump() function.
392
+ * Use with yaml stringify() options.
356
393
  */
357
394
  export declare const YAML_OPTIONS: {
358
395
  /** Standard line width for YAML dump (100 chars) */
359
396
  LINE_WIDTH: number;
360
- /** No line wrapping (-1 disables wrapping in js-yaml) */
397
+ /** No line wrapping (-1 disables wrapping) */
361
398
  NO_WRAP: number;
362
399
  };
363
400
  /**
@@ -387,6 +424,59 @@ export declare const BOX: {
387
424
  /** Side border for content lines */
388
425
  SIDE: string;
389
426
  };
427
+ /**
428
+ * Cleanup guard constants
429
+ */
430
+ export declare const CLEANUP_GUARD: {
431
+ REASONS: {
432
+ UNCOMMITTED_CHANGES: string;
433
+ UNPUSHED_COMMITS: string;
434
+ STATUS_NOT_DONE: string;
435
+ MISSING_STAMP: string;
436
+ PR_NOT_MERGED: string;
437
+ };
438
+ TITLES: {
439
+ BLOCKED: string;
440
+ NEXT_STEPS: string;
441
+ };
442
+ MESSAGES: {
443
+ UNCOMMITTED_CHANGES: string;
444
+ UNPUSHED_COMMITS: string;
445
+ STATUS_NOT_DONE: string;
446
+ MISSING_STAMP: string;
447
+ PR_NOT_MERGED: string;
448
+ };
449
+ NEXT_STEPS: {
450
+ DEFAULT: {
451
+ text: string;
452
+ appendId: boolean;
453
+ }[];
454
+ UNCOMMITTED_CHANGES: {
455
+ text: string;
456
+ appendId: boolean;
457
+ }[];
458
+ UNPUSHED_COMMITS: {
459
+ text: string;
460
+ appendId: boolean;
461
+ }[];
462
+ STATUS_NOT_DONE: {
463
+ text: string;
464
+ appendId: boolean;
465
+ }[];
466
+ MISSING_STAMP: {
467
+ text: string;
468
+ appendId: boolean;
469
+ }[];
470
+ PR_NOT_MERGED: {
471
+ text: string;
472
+ appendId: boolean;
473
+ }[];
474
+ };
475
+ PR_CHECK: {
476
+ START: string;
477
+ RESULT: string;
478
+ };
479
+ };
390
480
  /**
391
481
  * Git display constants
392
482
  *
@@ -454,6 +544,8 @@ export declare const GIT_FLAGS: {
454
544
  NO_VERIFY: string;
455
545
  /** No GPG sign flag (skip commit signing) */
456
546
  NO_GPG_SIGN: string;
547
+ /** One-line log format */
548
+ ONELINE: string;
457
549
  };
458
550
  /**
459
551
  * Git commands
@@ -470,6 +562,8 @@ export declare const GIT_COMMANDS: {
470
562
  LS_TREE: string;
471
563
  /** Git diff command */
472
564
  DIFF: string;
565
+ /** Git log command */
566
+ LOG: string;
473
567
  /** Git merge-base command */
474
568
  MERGE_BASE: string;
475
569
  /** Git rev-parse command */
@@ -918,7 +1012,6 @@ export declare const ESLINT_DEFAULTS: {
918
1012
  *
919
1013
  * WU-1866: Temporarily increased from 0 to 100 to unblock gates.
920
1014
  * There are ~82 pre-existing warnings that need proper fixes.
921
- * TODO: Create follow-up WU to fix warnings and restore zero-warnings policy.
922
1015
  */
923
1016
  MAX_WARNINGS: string;
924
1017
  };
@@ -997,6 +1090,8 @@ export declare const GITLEAKS_ARGS: {
997
1090
  export declare const PRETTIER_ARGS: {
998
1091
  /** Check formatting without writing */
999
1092
  CHECK: string;
1093
+ /** List files with formatting differences */
1094
+ LIST_DIFFERENT: string;
1000
1095
  };
1001
1096
  /**
1002
1097
  * Audit command arguments