@lumenflow/core 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/arg-parser.d.ts +6 -0
- package/dist/arg-parser.js +26 -0
- package/dist/git-adapter.d.ts +7 -0
- package/dist/git-adapter.js +15 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/lumenflow-home.d.ts +130 -0
- package/dist/lumenflow-home.js +208 -0
- package/dist/manual-test-validator.js +1 -1
- package/dist/orphan-detector.d.ts +16 -0
- package/dist/orphan-detector.js +24 -0
- package/dist/spec-branch-helpers.d.ts +118 -0
- package/dist/spec-branch-helpers.js +192 -0
- package/dist/wu-consistency-checker.d.ts +2 -0
- package/dist/wu-consistency-checker.js +35 -1
- package/dist/wu-constants.d.ts +77 -0
- package/dist/wu-constants.js +80 -0
- package/dist/wu-create-validators.d.ts +40 -2
- package/dist/wu-create-validators.js +76 -2
- package/dist/wu-done-branch-only.js +9 -0
- package/dist/wu-done-docs-generate.d.ts +73 -0
- package/dist/wu-done-docs-generate.js +108 -0
- package/dist/wu-done-worktree.js +12 -0
- package/dist/wu-schema.js +3 -1
- package/dist/wu-spawn.js +14 -1
- package/package.json +4 -3
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/wu-constants.d.ts
CHANGED
|
@@ -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
|
|
@@ -404,6 +424,59 @@ export declare const BOX: {
|
|
|
404
424
|
/** Side border for content lines */
|
|
405
425
|
SIDE: string;
|
|
406
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
|
+
};
|
|
407
480
|
/**
|
|
408
481
|
* Git display constants
|
|
409
482
|
*
|
|
@@ -471,6 +544,8 @@ export declare const GIT_FLAGS: {
|
|
|
471
544
|
NO_VERIFY: string;
|
|
472
545
|
/** No GPG sign flag (skip commit signing) */
|
|
473
546
|
NO_GPG_SIGN: string;
|
|
547
|
+
/** One-line log format */
|
|
548
|
+
ONELINE: string;
|
|
474
549
|
};
|
|
475
550
|
/**
|
|
476
551
|
* Git commands
|
|
@@ -487,6 +562,8 @@ export declare const GIT_COMMANDS: {
|
|
|
487
562
|
LS_TREE: string;
|
|
488
563
|
/** Git diff command */
|
|
489
564
|
DIFF: string;
|
|
565
|
+
/** Git log command */
|
|
566
|
+
LOG: string;
|
|
490
567
|
/** Git merge-base command */
|
|
491
568
|
MERGE_BASE: string;
|
|
492
569
|
/** Git rev-parse command */
|
package/dist/wu-constants.js
CHANGED
|
@@ -42,6 +42,10 @@ export const GIT_REFS = {
|
|
|
42
42
|
ORIGIN_MAIN: 'origin/main',
|
|
43
43
|
/** Current HEAD ref */
|
|
44
44
|
HEAD: 'HEAD',
|
|
45
|
+
/** Upstream ref */
|
|
46
|
+
UPSTREAM: '@{u}',
|
|
47
|
+
/** Range of upstream..HEAD */
|
|
48
|
+
UPSTREAM_RANGE: '@{u}..HEAD',
|
|
45
49
|
/** Fetch head ref */
|
|
46
50
|
FETCH_HEAD: 'FETCH_HEAD',
|
|
47
51
|
};
|
|
@@ -273,6 +277,22 @@ export const CONSISTENCY_TYPES = {
|
|
|
273
277
|
ORPHAN_WORKTREE_DONE: 'ORPHAN_WORKTREE_DONE',
|
|
274
278
|
/** Stamp file exists but WU YAML status is not 'done' (partial wu:done failure) */
|
|
275
279
|
STAMP_EXISTS_YAML_NOT_DONE: 'STAMP_EXISTS_YAML_NOT_DONE',
|
|
280
|
+
/** WU is claimed but its worktree directory is missing */
|
|
281
|
+
MISSING_WORKTREE_CLAIMED: 'MISSING_WORKTREE_CLAIMED',
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Consistency check messages
|
|
285
|
+
*/
|
|
286
|
+
export const CONSISTENCY_MESSAGES = {
|
|
287
|
+
MISSING_WORKTREE_CLAIMED: (id, status, worktreePath) => `WU ${id} is '${status}' but worktree path is missing (${worktreePath})`,
|
|
288
|
+
MISSING_WORKTREE_CLAIMED_REPAIR: 'Recover worktree or re-claim WU',
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Worktree warning messages
|
|
292
|
+
*/
|
|
293
|
+
export const WORKTREE_WARNINGS = {
|
|
294
|
+
MISSING_TRACKED_HEADER: 'Tracked worktrees missing on disk (possible manual deletion):',
|
|
295
|
+
MISSING_TRACKED_LINE: (worktreePath) => `Missing: ${worktreePath}`,
|
|
276
296
|
};
|
|
277
297
|
/**
|
|
278
298
|
* File system constants
|
|
@@ -426,6 +446,62 @@ export const BOX = {
|
|
|
426
446
|
/** Side border for content lines */
|
|
427
447
|
SIDE: '║',
|
|
428
448
|
};
|
|
449
|
+
/**
|
|
450
|
+
* Cleanup guard constants
|
|
451
|
+
*/
|
|
452
|
+
export const CLEANUP_GUARD = {
|
|
453
|
+
REASONS: {
|
|
454
|
+
UNCOMMITTED_CHANGES: 'UNCOMMITTED_CHANGES',
|
|
455
|
+
UNPUSHED_COMMITS: 'UNPUSHED_COMMITS',
|
|
456
|
+
STATUS_NOT_DONE: 'STATUS_NOT_DONE',
|
|
457
|
+
MISSING_STAMP: 'MISSING_STAMP',
|
|
458
|
+
PR_NOT_MERGED: 'PR_NOT_MERGED',
|
|
459
|
+
},
|
|
460
|
+
TITLES: {
|
|
461
|
+
BLOCKED: 'CLEANUP BLOCKED',
|
|
462
|
+
NEXT_STEPS: 'Next steps:',
|
|
463
|
+
},
|
|
464
|
+
MESSAGES: {
|
|
465
|
+
UNCOMMITTED_CHANGES: 'Worktree has uncommitted changes. Refusing to delete.',
|
|
466
|
+
UNPUSHED_COMMITS: 'Worktree has unpushed commits. Refusing to delete.',
|
|
467
|
+
STATUS_NOT_DONE: 'WU YAML status is not done. Refusing to delete.',
|
|
468
|
+
MISSING_STAMP: 'WU stamp is missing. Refusing to delete.',
|
|
469
|
+
PR_NOT_MERGED: 'PR is not merged (or cannot be verified). Refusing to delete.',
|
|
470
|
+
},
|
|
471
|
+
NEXT_STEPS: {
|
|
472
|
+
DEFAULT: [
|
|
473
|
+
{ text: '1. Resolve the issue above', appendId: false },
|
|
474
|
+
{ text: '2. Re-run: pnpm wu:cleanup --id', appendId: true },
|
|
475
|
+
],
|
|
476
|
+
UNCOMMITTED_CHANGES: [
|
|
477
|
+
{ text: '1. Commit or stash changes in the worktree', appendId: false },
|
|
478
|
+
{ text: '2. Re-run: pnpm wu:cleanup --id', appendId: true },
|
|
479
|
+
],
|
|
480
|
+
UNPUSHED_COMMITS: [
|
|
481
|
+
{ text: '1. Push the lane branch to origin', appendId: false },
|
|
482
|
+
{ text: '2. Re-run: pnpm wu:cleanup --id', appendId: true },
|
|
483
|
+
],
|
|
484
|
+
STATUS_NOT_DONE: [
|
|
485
|
+
{
|
|
486
|
+
text: `1. Complete the WU with ${LOG_PREFIX.DONE} (creates stamp + done status)`,
|
|
487
|
+
appendId: false,
|
|
488
|
+
},
|
|
489
|
+
{ text: '2. Re-run: pnpm wu:cleanup --id', appendId: true },
|
|
490
|
+
],
|
|
491
|
+
MISSING_STAMP: [
|
|
492
|
+
{ text: '1. Run wu:done to create the stamp file', appendId: false },
|
|
493
|
+
{ text: '2. Re-run: pnpm wu:cleanup --id', appendId: true },
|
|
494
|
+
],
|
|
495
|
+
PR_NOT_MERGED: [
|
|
496
|
+
{ text: '1. Merge the PR in GitHub', appendId: false },
|
|
497
|
+
{ text: '2. Re-run: pnpm wu:cleanup --id', appendId: true },
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
PR_CHECK: {
|
|
501
|
+
START: 'Verifying PR merge status...',
|
|
502
|
+
RESULT: 'PR merge verification via',
|
|
503
|
+
},
|
|
504
|
+
};
|
|
429
505
|
/**
|
|
430
506
|
* Git display constants
|
|
431
507
|
*
|
|
@@ -493,6 +569,8 @@ export const GIT_FLAGS = {
|
|
|
493
569
|
NO_VERIFY: '--no-verify',
|
|
494
570
|
/** No GPG sign flag (skip commit signing) */
|
|
495
571
|
NO_GPG_SIGN: '--no-gpg-sign',
|
|
572
|
+
/** One-line log format */
|
|
573
|
+
ONELINE: '--oneline',
|
|
496
574
|
};
|
|
497
575
|
/**
|
|
498
576
|
* Git commands
|
|
@@ -509,6 +587,8 @@ export const GIT_COMMANDS = {
|
|
|
509
587
|
LS_TREE: 'ls-tree',
|
|
510
588
|
/** Git diff command */
|
|
511
589
|
DIFF: 'diff',
|
|
590
|
+
/** Git log command */
|
|
591
|
+
LOG: 'log',
|
|
512
592
|
/** Git merge-base command */
|
|
513
593
|
MERGE_BASE: 'merge-base',
|
|
514
594
|
/** Git rev-parse command */
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WU Create Validators (WU-2107)
|
|
2
|
+
* WU Create Validators (WU-2107, WU-1062)
|
|
3
3
|
*
|
|
4
|
-
* Validation helpers for wu:create, including
|
|
4
|
+
* Validation helpers for wu:create, including:
|
|
5
|
+
* - Lane inference surfacing (WU-2107)
|
|
6
|
+
* - External spec_refs validation (WU-1062)
|
|
5
7
|
*
|
|
6
8
|
* When agents create WUs, this module helps surface lane inference suggestions
|
|
7
9
|
* to guide better lane selection and improve parallelization.
|
|
8
10
|
*
|
|
11
|
+
* WU-1062: Validates spec_refs paths, accepting both repo-relative paths
|
|
12
|
+
* and external paths (lumenflow://, ~/.lumenflow/, $LUMENFLOW_HOME/).
|
|
13
|
+
*
|
|
9
14
|
* NOTE: This is domain-specific WU workflow code, not a general utility.
|
|
10
15
|
* No external library exists for LumenFlow lane inference validation.
|
|
11
16
|
*/
|
|
@@ -40,3 +45,36 @@ export declare function validateLaneWithInference(providedLane: any, codePaths:
|
|
|
40
45
|
inferredLane: any;
|
|
41
46
|
confidence: any;
|
|
42
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* WU-1062: Validate spec_refs paths
|
|
50
|
+
*
|
|
51
|
+
* Accepts:
|
|
52
|
+
* - Repo-relative paths: docs/04-operations/plans/WU-XXX-plan.md
|
|
53
|
+
* - External paths: lumenflow://plans/WU-XXX-plan.md
|
|
54
|
+
* - Tilde paths: ~/.lumenflow/plans/WU-XXX-plan.md
|
|
55
|
+
* - Env var paths: $LUMENFLOW_HOME/plans/WU-XXX-plan.md
|
|
56
|
+
*
|
|
57
|
+
* @param {string[]} specRefs - Array of spec reference paths
|
|
58
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[] }} Validation result
|
|
59
|
+
*/
|
|
60
|
+
export declare function validateSpecRefs(specRefs: string[]): {
|
|
61
|
+
valid: boolean;
|
|
62
|
+
errors: string[];
|
|
63
|
+
warnings: string[];
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* WU-1062: Check if spec_refs contains external paths
|
|
67
|
+
*
|
|
68
|
+
* @param {string[]} specRefs - Array of spec reference paths
|
|
69
|
+
* @returns {boolean} True if any spec_ref is an external path
|
|
70
|
+
*/
|
|
71
|
+
export declare function hasExternalSpecRefs(specRefs: string[]): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* WU-1062: Normalize all spec_refs paths
|
|
74
|
+
*
|
|
75
|
+
* Expands external paths to absolute paths while keeping repo-relative paths unchanged.
|
|
76
|
+
*
|
|
77
|
+
* @param {string[]} specRefs - Array of spec reference paths
|
|
78
|
+
* @returns {string[]} Normalized paths
|
|
79
|
+
*/
|
|
80
|
+
export declare function normalizeSpecRefs(specRefs: string[]): string[];
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WU Create Validators (WU-2107)
|
|
2
|
+
* WU Create Validators (WU-2107, WU-1062)
|
|
3
3
|
*
|
|
4
|
-
* Validation helpers for wu:create, including
|
|
4
|
+
* Validation helpers for wu:create, including:
|
|
5
|
+
* - Lane inference surfacing (WU-2107)
|
|
6
|
+
* - External spec_refs validation (WU-1062)
|
|
5
7
|
*
|
|
6
8
|
* When agents create WUs, this module helps surface lane inference suggestions
|
|
7
9
|
* to guide better lane selection and improve parallelization.
|
|
8
10
|
*
|
|
11
|
+
* WU-1062: Validates spec_refs paths, accepting both repo-relative paths
|
|
12
|
+
* and external paths (lumenflow://, ~/.lumenflow/, $LUMENFLOW_HOME/).
|
|
13
|
+
*
|
|
9
14
|
* NOTE: This is domain-specific WU workflow code, not a general utility.
|
|
10
15
|
* No external library exists for LumenFlow lane inference validation.
|
|
11
16
|
*/
|
|
17
|
+
import { isExternalPath, normalizeSpecRef } from './lumenflow-home.js';
|
|
12
18
|
/** Confidence threshold for showing suggestion (percentage) */
|
|
13
19
|
const CONFIDENCE_THRESHOLD_LOW = 30;
|
|
14
20
|
/**
|
|
@@ -91,3 +97,71 @@ export function validateLaneWithInference(providedLane, codePaths, description,
|
|
|
91
97
|
return { shouldWarn: false, warning: '' };
|
|
92
98
|
}
|
|
93
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* WU-1062: Validate spec_refs paths
|
|
102
|
+
*
|
|
103
|
+
* Accepts:
|
|
104
|
+
* - Repo-relative paths: docs/04-operations/plans/WU-XXX-plan.md
|
|
105
|
+
* - External paths: lumenflow://plans/WU-XXX-plan.md
|
|
106
|
+
* - Tilde paths: ~/.lumenflow/plans/WU-XXX-plan.md
|
|
107
|
+
* - Env var paths: $LUMENFLOW_HOME/plans/WU-XXX-plan.md
|
|
108
|
+
*
|
|
109
|
+
* @param {string[]} specRefs - Array of spec reference paths
|
|
110
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[] }} Validation result
|
|
111
|
+
*/
|
|
112
|
+
export function validateSpecRefs(specRefs) {
|
|
113
|
+
const errors = [];
|
|
114
|
+
const warnings = [];
|
|
115
|
+
if (!specRefs || specRefs.length === 0) {
|
|
116
|
+
return { valid: true, errors, warnings };
|
|
117
|
+
}
|
|
118
|
+
for (const ref of specRefs) {
|
|
119
|
+
// Check for empty refs
|
|
120
|
+
if (!ref || ref.trim().length === 0) {
|
|
121
|
+
errors.push('Empty spec_ref detected');
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
// External paths are valid (will be resolved at runtime)
|
|
125
|
+
if (isExternalPath(ref)) {
|
|
126
|
+
// Add informational warning about external paths
|
|
127
|
+
warnings.push(`External spec_ref: "${ref}" - ensure plan exists at ${normalizeSpecRef(ref)}`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// Repo-relative paths should follow conventions
|
|
131
|
+
const isValidRepoPath = ref.startsWith('docs/') || ref.startsWith('./docs/') || ref.endsWith('.md');
|
|
132
|
+
if (!isValidRepoPath) {
|
|
133
|
+
warnings.push(`Unconventional spec_ref path: "${ref}" - consider using docs/04-operations/plans/ or lumenflow://plans/`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
valid: errors.length === 0,
|
|
138
|
+
errors,
|
|
139
|
+
warnings,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* WU-1062: Check if spec_refs contains external paths
|
|
144
|
+
*
|
|
145
|
+
* @param {string[]} specRefs - Array of spec reference paths
|
|
146
|
+
* @returns {boolean} True if any spec_ref is an external path
|
|
147
|
+
*/
|
|
148
|
+
export function hasExternalSpecRefs(specRefs) {
|
|
149
|
+
if (!specRefs || specRefs.length === 0) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return specRefs.some((ref) => isExternalPath(ref));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* WU-1062: Normalize all spec_refs paths
|
|
156
|
+
*
|
|
157
|
+
* Expands external paths to absolute paths while keeping repo-relative paths unchanged.
|
|
158
|
+
*
|
|
159
|
+
* @param {string[]} specRefs - Array of spec reference paths
|
|
160
|
+
* @returns {string[]} Normalized paths
|
|
161
|
+
*/
|
|
162
|
+
export function normalizeSpecRefs(specRefs) {
|
|
163
|
+
if (!specRefs || specRefs.length === 0) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
return specRefs.map((ref) => normalizeSpecRef(ref));
|
|
167
|
+
}
|
|
@@ -23,6 +23,8 @@ import { die, createError, ErrorCodes } from './error-handler.js';
|
|
|
23
23
|
import { validateWU, validateDoneWU } from './wu-schema.js';
|
|
24
24
|
import { assertTransition } from './state-machine.js';
|
|
25
25
|
import { detectZombieState, recoverZombieState } from './wu-recovery.js';
|
|
26
|
+
// WU-1061: Import docs regeneration utilities
|
|
27
|
+
import { maybeRegenerateAndStageDocs } from './wu-done-docs-generate.js';
|
|
26
28
|
/**
|
|
27
29
|
* @typedef {Object} BranchOnlyContext
|
|
28
30
|
* @property {string} id - WU ID (e.g., "WU-1215")
|
|
@@ -119,6 +121,13 @@ export async function executeBranchOnlyCompletion(context) {
|
|
|
119
121
|
statusPath: metadataStatusPath,
|
|
120
122
|
backlogPath: metadataBacklogPath,
|
|
121
123
|
});
|
|
124
|
+
// WU-1061: Regenerate docs if doc-source files changed
|
|
125
|
+
// This runs BEFORE stageAndFormatMetadata to include doc outputs
|
|
126
|
+
// in the single atomic commit
|
|
127
|
+
await maybeRegenerateAndStageDocs({
|
|
128
|
+
baseBranch: BRANCHES.MAIN,
|
|
129
|
+
repoRoot: metadataBasePath,
|
|
130
|
+
});
|
|
122
131
|
// Step 7: Stage and format files
|
|
123
132
|
await stageAndFormatMetadata({
|
|
124
133
|
id,
|