@lumenflow/core 1.0.0 → 1.3.0
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.js +31 -1
- package/dist/backlog-generator.js +1 -1
- package/dist/backlog-sync-validator.js +3 -3
- package/dist/branch-check.d.ts +21 -0
- package/dist/branch-check.js +77 -0
- package/dist/cli/is-agent-branch.d.ts +11 -0
- package/dist/cli/is-agent-branch.js +15 -0
- package/dist/code-paths-overlap.js +2 -2
- package/dist/error-handler.d.ts +1 -0
- package/dist/error-handler.js +4 -1
- package/dist/git-adapter.d.ts +16 -0
- package/dist/git-adapter.js +23 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/lane-checker.d.ts +36 -3
- package/dist/lane-checker.js +128 -17
- package/dist/lane-inference.js +3 -4
- package/dist/lumenflow-config-schema.d.ts +125 -0
- package/dist/lumenflow-config-schema.js +76 -0
- package/dist/orchestration-rules.d.ts +1 -1
- package/dist/orchestration-rules.js +2 -2
- package/dist/path-classifiers.d.ts +1 -1
- package/dist/path-classifiers.js +1 -1
- package/dist/rebase-artifact-cleanup.d.ts +17 -0
- package/dist/rebase-artifact-cleanup.js +49 -8
- package/dist/spawn-strategy.d.ts +53 -0
- package/dist/spawn-strategy.js +106 -0
- package/dist/stamp-utils.d.ts +10 -0
- package/dist/stamp-utils.js +17 -19
- package/dist/token-counter.js +2 -2
- package/dist/wu-consistency-checker.js +5 -5
- package/dist/wu-constants.d.ts +21 -3
- package/dist/wu-constants.js +28 -3
- package/dist/wu-done-branch-utils.d.ts +10 -0
- package/dist/wu-done-branch-utils.js +31 -0
- package/dist/wu-done-cleanup.d.ts +8 -0
- package/dist/wu-done-cleanup.js +122 -0
- package/dist/wu-done-docs-only.d.ts +20 -0
- package/dist/wu-done-docs-only.js +65 -0
- package/dist/wu-done-errors.d.ts +17 -0
- package/dist/wu-done-errors.js +24 -0
- package/dist/wu-done-inputs.d.ts +12 -0
- package/dist/wu-done-inputs.js +51 -0
- package/dist/wu-done-metadata.d.ts +100 -0
- package/dist/wu-done-metadata.js +193 -0
- package/dist/wu-done-paths.d.ts +69 -0
- package/dist/wu-done-paths.js +237 -0
- package/dist/wu-done-preflight.d.ts +48 -0
- package/dist/wu-done-preflight.js +185 -0
- package/dist/wu-done-validation.d.ts +82 -0
- package/dist/wu-done-validation.js +340 -0
- package/dist/wu-done-validators.d.ts +13 -409
- package/dist/wu-done-validators.js +9 -1225
- package/dist/wu-done-worktree.d.ts +0 -1
- package/dist/wu-done-worktree.js +12 -30
- package/dist/wu-schema.js +1 -3
- package/dist/wu-spawn-skills.d.ts +19 -0
- package/dist/wu-spawn-skills.js +148 -0
- package/dist/wu-spawn.d.ts +17 -4
- package/dist/wu-spawn.js +99 -176
- package/dist/wu-validation.d.ts +1 -0
- package/dist/wu-validation.js +21 -1
- package/dist/wu-validator.d.ts +51 -0
- package/dist/wu-validator.js +108 -0
- package/package.json +11 -8
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup helpers for wu:done.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { getGitForCwd } from './git-adapter.js';
|
|
6
|
+
import { withCleanupLock } from './cleanup-lock.js';
|
|
7
|
+
import { validateWorktreeOwnership } from './worktree-ownership.js';
|
|
8
|
+
import { getCleanupInstallConfig, CLEANUP_INSTALL_TIMEOUT_MS } from './cleanup-install-config.js';
|
|
9
|
+
import { createValidationError } from './wu-done-errors.js';
|
|
10
|
+
import { defaultWorktreeFrom, defaultBranchFrom, branchExists } from './wu-done-paths.js';
|
|
11
|
+
import { isBranchAlreadyMerged } from './wu-done-branch-utils.js';
|
|
12
|
+
import { CLAIMED_MODES, EMOJI, LOG_PREFIX, REMOTES } from './wu-constants.js';
|
|
13
|
+
import { exec as execCallback } from 'node:child_process';
|
|
14
|
+
import { promisify } from 'node:util';
|
|
15
|
+
const execAsync = promisify(execCallback);
|
|
16
|
+
/**
|
|
17
|
+
* Run cleanup operations after successful merge
|
|
18
|
+
* Removes worktree and optionally deletes lane branch
|
|
19
|
+
*/
|
|
20
|
+
export async function runCleanup(docMain, args) {
|
|
21
|
+
const wuId = docMain.id;
|
|
22
|
+
const worktreePath = args.worktree || (await defaultWorktreeFrom(docMain));
|
|
23
|
+
// WU-2278: Validate worktree ownership before cleanup
|
|
24
|
+
// Prevents cross-agent worktree deletion
|
|
25
|
+
if (!args.overrideOwner) {
|
|
26
|
+
const ownershipResult = validateWorktreeOwnership({ worktreePath, wuId });
|
|
27
|
+
if (!ownershipResult.valid) {
|
|
28
|
+
throw createValidationError(`${ownershipResult.error}\n\nTo override (DANGEROUS): pnpm wu:done --id ${wuId} --override-owner --reason "explanation"`, { wuId, worktreePath, error: ownershipResult.error });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// WU-2241: Wrap cleanup operations in cleanup lock to prevent concurrent collision
|
|
32
|
+
await withCleanupLock(wuId, async () => {
|
|
33
|
+
await runCleanupInternal(docMain, args, worktreePath);
|
|
34
|
+
}, { worktreePath });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Internal cleanup implementation (runs under cleanup lock)
|
|
38
|
+
*/
|
|
39
|
+
async function runCleanupInternal(docMain, args, worktreePath) {
|
|
40
|
+
// Step 6: Remove worktree (runs even if commit/push failed)
|
|
41
|
+
// Skip removal in PR mode (worktree needed for cleanup after PR merge)
|
|
42
|
+
const claimedMode = docMain.claimed_mode || CLAIMED_MODES.WORKTREE;
|
|
43
|
+
const requiresReview = docMain.requires_review === true;
|
|
44
|
+
const prModeEnabled = claimedMode === CLAIMED_MODES.WORKTREE_PR || args.createPR || requiresReview;
|
|
45
|
+
// WU-2241: Track branch for cleanup after worktree removal
|
|
46
|
+
const laneBranch = await defaultBranchFrom(docMain);
|
|
47
|
+
if (!args.noRemove && !prModeEnabled) {
|
|
48
|
+
if (worktreePath && existsSync(worktreePath)) {
|
|
49
|
+
try {
|
|
50
|
+
await getGitForCwd().worktreeRemove(worktreePath, { force: true });
|
|
51
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Removed worktree ${worktreePath}`);
|
|
52
|
+
// WU-2241: Delete branch AFTER worktree removal (correct ordering)
|
|
53
|
+
// This ensures we don't leave orphan branches when worktree is removed
|
|
54
|
+
if (laneBranch && (await branchExists(laneBranch))) {
|
|
55
|
+
await deleteBranchWithCleanup(laneBranch);
|
|
56
|
+
}
|
|
57
|
+
// WU-1743: Re-run pnpm install to fix broken symlinks
|
|
58
|
+
// When pnpm install runs in a worktree, it may create symlinks with absolute paths
|
|
59
|
+
// to the worktree. After worktree removal, these symlinks break.
|
|
60
|
+
// Re-running pnpm install regenerates them with correct paths.
|
|
61
|
+
// WU-2278: Use timeout and CI=true to prevent hangs
|
|
62
|
+
console.log(`${LOG_PREFIX.DONE} Reinstalling dependencies to fix symlinks...`);
|
|
63
|
+
try {
|
|
64
|
+
const installConfig = getCleanupInstallConfig();
|
|
65
|
+
await execAsync(installConfig.command, {
|
|
66
|
+
timeout: installConfig.timeout,
|
|
67
|
+
env: installConfig.env,
|
|
68
|
+
});
|
|
69
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Dependencies reinstalled`);
|
|
70
|
+
}
|
|
71
|
+
catch (installErr) {
|
|
72
|
+
// Non-fatal: warn but don't fail wu:done
|
|
73
|
+
// WU-2278: Include timeout info in error message
|
|
74
|
+
const isTimeout = installErr.killed || installErr.signal === 'SIGTERM';
|
|
75
|
+
const errorMsg = isTimeout
|
|
76
|
+
? `pnpm install timed out after ${CLEANUP_INSTALL_TIMEOUT_MS / 1000}s`
|
|
77
|
+
: `pnpm install failed: ${installErr.message}`;
|
|
78
|
+
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} ${errorMsg}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.warn(`${LOG_PREFIX.DONE} Could not remove worktree ${worktreePath}: ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(`${LOG_PREFIX.DONE} Worktree not found; skipping removal`);
|
|
87
|
+
// WU-2241: Still cleanup branch if worktree doesn't exist (orphan branch scenario)
|
|
88
|
+
if (!prModeEnabled && laneBranch && (await branchExists(laneBranch))) {
|
|
89
|
+
await deleteBranchWithCleanup(laneBranch);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (prModeEnabled) {
|
|
94
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Worktree preserved (PR mode - run wu:cleanup after PR merge)`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* WU-2241: Delete both local and remote branch with proper error handling
|
|
99
|
+
*/
|
|
100
|
+
async function deleteBranchWithCleanup(laneBranch) {
|
|
101
|
+
const gitAdapter = getGitForCwd();
|
|
102
|
+
// WU-1440: Check if branch is merged before deletion
|
|
103
|
+
// Use -D (force) when confirmed merged to handle rebased branches
|
|
104
|
+
const isMerged = await isBranchAlreadyMerged(laneBranch);
|
|
105
|
+
try {
|
|
106
|
+
await gitAdapter.deleteBranch(laneBranch, { force: isMerged });
|
|
107
|
+
const modeIndicator = isMerged ? ' (force: merged)' : '';
|
|
108
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Deleted local branch ${laneBranch}${modeIndicator}`);
|
|
109
|
+
// Also delete remote if it exists
|
|
110
|
+
try {
|
|
111
|
+
await gitAdapter.raw(['push', REMOTES.ORIGIN, '--delete', laneBranch]);
|
|
112
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Deleted remote branch ${laneBranch}`);
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
// WU-2241: Non-fatal - remote branch may already be deleted or never existed
|
|
116
|
+
console.warn(`${LOG_PREFIX.DONE} Could not delete remote branch: ${e.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
console.warn(`${LOG_PREFIX.DONE} Could not delete branch ${laneBranch}: ${e.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs-only detection utilities for wu:done
|
|
3
|
+
*
|
|
4
|
+
* WU-1234 + WU-1255 + WU-1539: Detect docs-only WUs from code_paths.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Detect docs-only WU from code_paths.
|
|
8
|
+
* Returns true if all code_paths are documentation paths only.
|
|
9
|
+
*
|
|
10
|
+
* Docs-only paths: docs/, ai/, .claude/, memory-bank/, README*, CLAUDE*.md
|
|
11
|
+
* NOT docs-only: tools/, scripts/ (these are code, not documentation)
|
|
12
|
+
*
|
|
13
|
+
* WU-1539: Fixed misclassification where tools/ was treated as docs-only
|
|
14
|
+
* but then rejected by validateDocsOnly(). tools/ should skip web tests
|
|
15
|
+
* but NOT be classified as docs-only.
|
|
16
|
+
*
|
|
17
|
+
* @param {string[]|null|undefined} codePaths - Array of file paths from WU YAML
|
|
18
|
+
* @returns {boolean} True if WU is docs-only (all paths are documentation)
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectDocsOnlyByPaths(codePaths: any): boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs-only detection utilities for wu:done
|
|
3
|
+
*
|
|
4
|
+
* WU-1234 + WU-1255 + WU-1539: Detect docs-only WUs from code_paths.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Prefixes for paths that qualify as "docs-only" (no code changes).
|
|
8
|
+
* Unlike SKIP_TESTS_PREFIXES, this excludes tools/ and scripts/ because
|
|
9
|
+
* those contain code files that require full gate validation.
|
|
10
|
+
*
|
|
11
|
+
* WU-1539: Split from shouldSkipWebTests to fix docs-only misclassification.
|
|
12
|
+
* @constant {string[]}
|
|
13
|
+
*/
|
|
14
|
+
const DOCS_ONLY_PREFIXES = Object.freeze(['docs/', 'ai/', '.claude/', 'memory-bank/']);
|
|
15
|
+
/**
|
|
16
|
+
* Root file patterns that qualify as docs-only.
|
|
17
|
+
* @constant {string[]}
|
|
18
|
+
*/
|
|
19
|
+
const DOCS_ONLY_ROOT_FILES = Object.freeze(['readme', 'claude']);
|
|
20
|
+
/**
|
|
21
|
+
* Detect docs-only WU from code_paths.
|
|
22
|
+
* Returns true if all code_paths are documentation paths only.
|
|
23
|
+
*
|
|
24
|
+
* Docs-only paths: docs/, ai/, .claude/, memory-bank/, README*, CLAUDE*.md
|
|
25
|
+
* NOT docs-only: tools/, scripts/ (these are code, not documentation)
|
|
26
|
+
*
|
|
27
|
+
* WU-1539: Fixed misclassification where tools/ was treated as docs-only
|
|
28
|
+
* but then rejected by validateDocsOnly(). tools/ should skip web tests
|
|
29
|
+
* but NOT be classified as docs-only.
|
|
30
|
+
*
|
|
31
|
+
* @param {string[]|null|undefined} codePaths - Array of file paths from WU YAML
|
|
32
|
+
* @returns {boolean} True if WU is docs-only (all paths are documentation)
|
|
33
|
+
*/
|
|
34
|
+
export function detectDocsOnlyByPaths(codePaths) {
|
|
35
|
+
if (!codePaths || !Array.isArray(codePaths) || codePaths.length === 0) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return codePaths.every((filePath) => {
|
|
39
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const path = filePath.trim();
|
|
43
|
+
if (path.length === 0) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
// Check docs-only prefixes (docs/, ai/, .claude/, memory-bank/)
|
|
47
|
+
for (const prefix of DOCS_ONLY_PREFIXES) {
|
|
48
|
+
if (path.startsWith(prefix)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Check if it's a markdown file (*.md)
|
|
53
|
+
if (path.endsWith('.md')) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// Check root file patterns (README*, CLAUDE*.md)
|
|
57
|
+
const lowerPath = path.toLowerCase();
|
|
58
|
+
for (const pattern of DOCS_ONLY_ROOT_FILES) {
|
|
59
|
+
if (lowerPath.startsWith(pattern)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error helpers for wu:done validators
|
|
3
|
+
*
|
|
4
|
+
* WU-1049: Standardize error handling patterns across wu-done validator paths.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Create a validation error for wu:done validators.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createValidationError(message: any, details?: {}): import("./error-handler.js").WUError;
|
|
10
|
+
/**
|
|
11
|
+
* Create a file-not-found error for wu:done validators.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createFileNotFoundError(message: any, details?: {}): import("./error-handler.js").WUError;
|
|
14
|
+
/**
|
|
15
|
+
* Create a recovery error for wu:done recovery flows.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createRecoveryError(message: any, details?: {}): import("./error-handler.js").WUError;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error helpers for wu:done validators
|
|
3
|
+
*
|
|
4
|
+
* WU-1049: Standardize error handling patterns across wu-done validator paths.
|
|
5
|
+
*/
|
|
6
|
+
import { createError, ErrorCodes } from './error-handler.js';
|
|
7
|
+
/**
|
|
8
|
+
* Create a validation error for wu:done validators.
|
|
9
|
+
*/
|
|
10
|
+
export function createValidationError(message, details = {}) {
|
|
11
|
+
return createError(ErrorCodes.VALIDATION_ERROR, message, details);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a file-not-found error for wu:done validators.
|
|
15
|
+
*/
|
|
16
|
+
export function createFileNotFoundError(message, details = {}) {
|
|
17
|
+
return createError(ErrorCodes.FILE_NOT_FOUND, message, details);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a recovery error for wu:done recovery flows.
|
|
21
|
+
*/
|
|
22
|
+
export function createRecoveryError(message, details = {}) {
|
|
23
|
+
return createError(ErrorCodes.RECOVERY_ERROR, message, details);
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input parsing for wu:done validators.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validates command-line inputs and WU ID format
|
|
6
|
+
* @param {string[]} argv - Process arguments
|
|
7
|
+
* @returns {{ args: object, id: string }} Parsed args and validated WU ID
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateInputs(argv: any): {
|
|
10
|
+
args: import("commander").OptionValues;
|
|
11
|
+
id: any;
|
|
12
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input parsing for wu:done validators.
|
|
3
|
+
*/
|
|
4
|
+
import { parseWUArgs } from './arg-parser.js';
|
|
5
|
+
import { die } from './error-handler.js';
|
|
6
|
+
import { EXIT_CODES, PATTERNS } from './wu-constants.js';
|
|
7
|
+
/**
|
|
8
|
+
* Validates command-line inputs and WU ID format
|
|
9
|
+
* @param {string[]} argv - Process arguments
|
|
10
|
+
* @returns {{ args: object, id: string }} Parsed args and validated WU ID
|
|
11
|
+
*/
|
|
12
|
+
export function validateInputs(argv) {
|
|
13
|
+
const args = parseWUArgs(argv);
|
|
14
|
+
if (args.help || !args.id) {
|
|
15
|
+
console.log('Usage: pnpm wu:done --id WU-334 [OPTIONS]\n\n' +
|
|
16
|
+
'Options:\n' +
|
|
17
|
+
' --worktree <path> Override worktree path (default: worktrees/<lane>-<wu-id>)\n' +
|
|
18
|
+
' --no-auto Skip auto-updating YAML/backlog/status (you staged manually)\n' +
|
|
19
|
+
' --no-remove Skip worktree removal\n' +
|
|
20
|
+
' --no-merge Skip auto-merging lane branch to main\n' +
|
|
21
|
+
' --delete-branch Delete lane branch after merge (both local and remote)\n' +
|
|
22
|
+
' --create-pr Create PR instead of auto-merge (requires gh CLI)\n' +
|
|
23
|
+
' --pr-draft Create PR as draft (use with --create-pr)\n' +
|
|
24
|
+
' --skip-gates Skip gates check (USE WITH EXTREME CAUTION)\n' +
|
|
25
|
+
' --docs-only Run docs-only gates (requires exposure: documentation)\n' +
|
|
26
|
+
' --reason "<text>" Required with --skip-gates or --override-owner\n' +
|
|
27
|
+
' --fix-wu WU-{id} Required with --skip-gates: WU ID that will fix the failures\n' +
|
|
28
|
+
' --allow-todo Allow TODO comments in code (requires justification in WU notes)\n' +
|
|
29
|
+
' --override-owner Override ownership check (requires --reason, audited)\n' +
|
|
30
|
+
' --no-auto-rebase Disable auto-rebase on branch divergence (WU-1303)\n' +
|
|
31
|
+
' --require-agents Block completion if mandatory agents not invoked (WU-1542)\n' +
|
|
32
|
+
' --help, -h Show this help\n\n' +
|
|
33
|
+
'⚠️ SKIP-GATES WARNING:\n' +
|
|
34
|
+
' Only use --skip-gates when:\n' +
|
|
35
|
+
' • Test failures are confirmed pre-existing (not introduced by your WU)\n' +
|
|
36
|
+
' • A separate WU exists to fix those failures (specify with --fix-wu)\n' +
|
|
37
|
+
' • Your WU work is genuinely complete\n\n' +
|
|
38
|
+
' NEVER use --skip-gates for failures introduced by your WU!\n' +
|
|
39
|
+
' All skip-gates events are logged to .beacon/skip-gates-audit.log\n\n' +
|
|
40
|
+
'📝 WU VALIDATOR:\n' +
|
|
41
|
+
' Automatically scans code_paths for:\n' +
|
|
42
|
+
' • TODO/FIXME/HACK/XXX comments (fails validation unless --allow-todo)\n' +
|
|
43
|
+
' • Mock/Stub/Fake classes in production code (warning only)\n' +
|
|
44
|
+
' Use --allow-todo only for legitimate cases with justification in WU notes.\n');
|
|
45
|
+
process.exit(args.help ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
|
|
46
|
+
}
|
|
47
|
+
const id = args.id.toUpperCase();
|
|
48
|
+
if (!PATTERNS.WU_ID.test(id))
|
|
49
|
+
die(`Invalid WU id '${args.id}'. Expected format WU-123`);
|
|
50
|
+
return { args, id };
|
|
51
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata update helpers for wu:done.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate commit message for WU completion
|
|
6
|
+
* Extracted from wu-done.mjs (WU-1215 Phase 2 Extraction #1 Helper)
|
|
7
|
+
* @param {string} id - WU ID (e.g., "WU-1215")
|
|
8
|
+
* @param {string} title - WU title
|
|
9
|
+
* @param {number} maxLength - Maximum commit header length from commitlint config
|
|
10
|
+
* @returns {string} Formatted commit message
|
|
11
|
+
* @throws {Error} If generated message exceeds maxLength
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateCommitMessage(id: any, title: any, maxLength?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Validate that required metadata files exist before updating
|
|
16
|
+
* WU-1275: Fail fast before mutations to prevent partial state
|
|
17
|
+
*
|
|
18
|
+
* @param {object} params - Parameters object
|
|
19
|
+
* @param {string} params.statusPath - Path to status.md file
|
|
20
|
+
* @param {string} params.backlogPath - Path to backlog.md file
|
|
21
|
+
* @throws {WUError} If any required file is missing
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateMetadataFilesExist({ statusPath, backlogPath }: {
|
|
24
|
+
statusPath: any;
|
|
25
|
+
backlogPath: any;
|
|
26
|
+
}): void;
|
|
27
|
+
/**
|
|
28
|
+
* Update all metadata files for WU completion
|
|
29
|
+
* Extracted from wu-done.mjs (WU-1215 Phase 2 Extraction #1 Helper)
|
|
30
|
+
* WU-1572: Made async for WUStateStore integration
|
|
31
|
+
* @param {object} params - Parameters object
|
|
32
|
+
* @param {string} params.id - WU ID
|
|
33
|
+
* @param {string} params.title - WU title
|
|
34
|
+
* @param {object} params.doc - WU YAML document to update
|
|
35
|
+
* @param {string} params.wuPath - Path to WU YAML file
|
|
36
|
+
* @param {string} params.statusPath - Path to status.md file
|
|
37
|
+
* @param {string} params.backlogPath - Path to backlog.md file
|
|
38
|
+
*/
|
|
39
|
+
export declare function updateMetadataFiles({ id, title, doc, wuPath, statusPath, backlogPath }: {
|
|
40
|
+
id: any;
|
|
41
|
+
title: any;
|
|
42
|
+
doc: any;
|
|
43
|
+
wuPath: any;
|
|
44
|
+
statusPath: any;
|
|
45
|
+
backlogPath: any;
|
|
46
|
+
}): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Collect metadata updates to a transaction (WU-1369: Atomic pattern)
|
|
49
|
+
*
|
|
50
|
+
* This is the atomic version of updateMetadataFiles.
|
|
51
|
+
* Instead of writing files immediately, it collects all changes
|
|
52
|
+
* into a WUTransaction object for atomic commit.
|
|
53
|
+
*
|
|
54
|
+
* Usage:
|
|
55
|
+
* ```js
|
|
56
|
+
* const tx = new WUTransaction(id);
|
|
57
|
+
* collectMetadataToTransaction({ id, title, doc, wuPath, statusPath, backlogPath, stampPath, transaction: tx });
|
|
58
|
+
* // All changes are now in tx.pendingWrites
|
|
59
|
+
* // Validate, then commit or abort
|
|
60
|
+
* tx.commit();
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @param {object} params - Parameters object
|
|
64
|
+
* @param {string} params.id - WU ID
|
|
65
|
+
* @param {string} params.title - WU title
|
|
66
|
+
* @param {object} params.doc - WU YAML document to update (will be mutated)
|
|
67
|
+
* @param {string} params.wuPath - Path to WU YAML file
|
|
68
|
+
* @param {string} params.statusPath - Path to status.md file
|
|
69
|
+
* @param {string} params.backlogPath - Path to backlog.md file
|
|
70
|
+
* @param {string} params.stampPath - Path to stamp file
|
|
71
|
+
* @param {WUTransaction} params.transaction - Transaction to add writes to
|
|
72
|
+
*/
|
|
73
|
+
export declare function collectMetadataToTransaction({ id, title, doc, wuPath, statusPath, backlogPath, stampPath, transaction, }: {
|
|
74
|
+
id: any;
|
|
75
|
+
title: any;
|
|
76
|
+
doc: any;
|
|
77
|
+
wuPath: any;
|
|
78
|
+
statusPath: any;
|
|
79
|
+
backlogPath: any;
|
|
80
|
+
stampPath: any;
|
|
81
|
+
transaction: any;
|
|
82
|
+
}): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Stage and format metadata files
|
|
85
|
+
* Extracted from wu-done.mjs (WU-1215 Phase 2 Extraction #1 Helper)
|
|
86
|
+
* @param {object} params - Parameters object
|
|
87
|
+
* @param {string} params.id - WU ID (for error reporting)
|
|
88
|
+
* @param {string} params.wuPath - Path to WU YAML file
|
|
89
|
+
* @param {string} params.statusPath - Path to status.md file
|
|
90
|
+
* @param {string} params.backlogPath - Path to backlog.md file
|
|
91
|
+
* @param {string} params.stampsDir - Path to stamps directory
|
|
92
|
+
* @throws {Error} If formatting fails
|
|
93
|
+
*/
|
|
94
|
+
export declare function stageAndFormatMetadata({ id, wuPath, statusPath, backlogPath, stampsDir }: {
|
|
95
|
+
id: any;
|
|
96
|
+
wuPath: any;
|
|
97
|
+
statusPath: any;
|
|
98
|
+
backlogPath: any;
|
|
99
|
+
stampsDir: any;
|
|
100
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata update helpers for wu:done.
|
|
3
|
+
*/
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { exec as execCallback } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import { getGitForCwd } from './git-adapter.js';
|
|
9
|
+
import { updateStatusRemoveInProgress, addToStatusCompleted } from './wu-status-updater.js';
|
|
10
|
+
import { moveWUToDoneBacklog } from './wu-backlog-updater.js';
|
|
11
|
+
import { createStamp } from './stamp-utils.js';
|
|
12
|
+
import { WU_EVENTS_FILE_NAME } from './wu-state-store.js';
|
|
13
|
+
import { computeWUYAMLContent, computeStatusContent, computeBacklogContent, computeWUEventsContentAfterComplete, computeStampContent, } from './wu-transaction-collectors.js';
|
|
14
|
+
import { DEFAULTS, LOG_PREFIX, EMOJI, PKG_MANAGER, SCRIPTS, PRETTIER_FLAGS, BEACON_PATHS, } from './wu-constants.js';
|
|
15
|
+
import { applyExposureDefaults } from './wu-done-validation.js';
|
|
16
|
+
import { createFileNotFoundError, createValidationError } from './wu-done-errors.js';
|
|
17
|
+
import { writeWU } from './wu-yaml.js';
|
|
18
|
+
const execAsync = promisify(execCallback);
|
|
19
|
+
/**
|
|
20
|
+
* Generate commit message for WU completion
|
|
21
|
+
* Extracted from wu-done.mjs (WU-1215 Phase 2 Extraction #1 Helper)
|
|
22
|
+
* @param {string} id - WU ID (e.g., "WU-1215")
|
|
23
|
+
* @param {string} title - WU title
|
|
24
|
+
* @param {number} maxLength - Maximum commit header length from commitlint config
|
|
25
|
+
* @returns {string} Formatted commit message
|
|
26
|
+
* @throws {Error} If generated message exceeds maxLength
|
|
27
|
+
*/
|
|
28
|
+
export function generateCommitMessage(id, title, maxLength = DEFAULTS.MAX_COMMIT_SUBJECT) {
|
|
29
|
+
const prefix = `wu(${id.toLowerCase()}): done - `;
|
|
30
|
+
const safe = String(title).trim().toLowerCase().replace(/\s+/g, ' ');
|
|
31
|
+
const room = Math.max(0, maxLength - prefix.length);
|
|
32
|
+
const short = safe.length > room ? `${safe.slice(0, room - 1)}…` : safe;
|
|
33
|
+
const msg = `${prefix}${short}`;
|
|
34
|
+
if (msg.length > maxLength) {
|
|
35
|
+
const error = new Error(`Commit message too long (${msg.length}/${maxLength}).\n` +
|
|
36
|
+
`Fix: Shorten WU title\n` +
|
|
37
|
+
`Current title: "${title}" (${title.length} chars)\n` +
|
|
38
|
+
`Suggested max: ~${maxLength - prefix.length} chars`);
|
|
39
|
+
error.code = 'COMMIT_MESSAGE_TOO_LONG';
|
|
40
|
+
error.data = {
|
|
41
|
+
title,
|
|
42
|
+
titleLength: title.length,
|
|
43
|
+
messageLength: msg.length,
|
|
44
|
+
maxLength,
|
|
45
|
+
suggestedMax: maxLength - prefix.length,
|
|
46
|
+
};
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
return msg;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Validate that required metadata files exist before updating
|
|
53
|
+
* WU-1275: Fail fast before mutations to prevent partial state
|
|
54
|
+
*
|
|
55
|
+
* @param {object} params - Parameters object
|
|
56
|
+
* @param {string} params.statusPath - Path to status.md file
|
|
57
|
+
* @param {string} params.backlogPath - Path to backlog.md file
|
|
58
|
+
* @throws {WUError} If any required file is missing
|
|
59
|
+
*/
|
|
60
|
+
export function validateMetadataFilesExist({ statusPath, backlogPath }) {
|
|
61
|
+
const missing = [];
|
|
62
|
+
if (!existsSync(statusPath)) {
|
|
63
|
+
missing.push(`Status: ${statusPath}`);
|
|
64
|
+
}
|
|
65
|
+
if (!existsSync(backlogPath)) {
|
|
66
|
+
missing.push(`Backlog: ${backlogPath}`);
|
|
67
|
+
}
|
|
68
|
+
if (missing.length > 0) {
|
|
69
|
+
throw createFileNotFoundError(`Required metadata files missing:\n ${missing.join('\n ')}\n\nCannot complete WU - verify worktree has latest metadata files.`, { missingFiles: missing });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Update all metadata files for WU completion
|
|
74
|
+
* Extracted from wu-done.mjs (WU-1215 Phase 2 Extraction #1 Helper)
|
|
75
|
+
* WU-1572: Made async for WUStateStore integration
|
|
76
|
+
* @param {object} params - Parameters object
|
|
77
|
+
* @param {string} params.id - WU ID
|
|
78
|
+
* @param {string} params.title - WU title
|
|
79
|
+
* @param {object} params.doc - WU YAML document to update
|
|
80
|
+
* @param {string} params.wuPath - Path to WU YAML file
|
|
81
|
+
* @param {string} params.statusPath - Path to status.md file
|
|
82
|
+
* @param {string} params.backlogPath - Path to backlog.md file
|
|
83
|
+
*/
|
|
84
|
+
export async function updateMetadataFiles({ id, title, doc, wuPath, statusPath, backlogPath }) {
|
|
85
|
+
// WU-1275: Fail fast before any mutations
|
|
86
|
+
validateMetadataFilesExist({ statusPath, backlogPath });
|
|
87
|
+
const exposureUpdate = applyExposureDefaults(doc);
|
|
88
|
+
if (exposureUpdate.applied) {
|
|
89
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Auto-set exposure to ${exposureUpdate.exposure} for ${id}`);
|
|
90
|
+
}
|
|
91
|
+
// Update WU YAML (mark as done, lock, set completion timestamp)
|
|
92
|
+
doc.status = 'done';
|
|
93
|
+
doc.locked = true;
|
|
94
|
+
doc.completed_at = new Date().toISOString();
|
|
95
|
+
writeWU(wuPath, doc);
|
|
96
|
+
// Update status.md (remove from In Progress, add to Completed)
|
|
97
|
+
updateStatusRemoveInProgress(statusPath, id);
|
|
98
|
+
addToStatusCompleted(statusPath, id, title);
|
|
99
|
+
// Update backlog.md (move to Done section)
|
|
100
|
+
// WU-1572: Now async for state store integration
|
|
101
|
+
await moveWUToDoneBacklog(backlogPath, id, title);
|
|
102
|
+
// Create completion stamp
|
|
103
|
+
createStamp({ id, title });
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Collect metadata updates to a transaction (WU-1369: Atomic pattern)
|
|
107
|
+
*
|
|
108
|
+
* This is the atomic version of updateMetadataFiles.
|
|
109
|
+
* Instead of writing files immediately, it collects all changes
|
|
110
|
+
* into a WUTransaction object for atomic commit.
|
|
111
|
+
*
|
|
112
|
+
* Usage:
|
|
113
|
+
* ```js
|
|
114
|
+
* const tx = new WUTransaction(id);
|
|
115
|
+
* collectMetadataToTransaction({ id, title, doc, wuPath, statusPath, backlogPath, stampPath, transaction: tx });
|
|
116
|
+
* // All changes are now in tx.pendingWrites
|
|
117
|
+
* // Validate, then commit or abort
|
|
118
|
+
* tx.commit();
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @param {object} params - Parameters object
|
|
122
|
+
* @param {string} params.id - WU ID
|
|
123
|
+
* @param {string} params.title - WU title
|
|
124
|
+
* @param {object} params.doc - WU YAML document to update (will be mutated)
|
|
125
|
+
* @param {string} params.wuPath - Path to WU YAML file
|
|
126
|
+
* @param {string} params.statusPath - Path to status.md file
|
|
127
|
+
* @param {string} params.backlogPath - Path to backlog.md file
|
|
128
|
+
* @param {string} params.stampPath - Path to stamp file
|
|
129
|
+
* @param {WUTransaction} params.transaction - Transaction to add writes to
|
|
130
|
+
*/
|
|
131
|
+
// WU-1574: Made async for computeBacklogContent
|
|
132
|
+
export async function collectMetadataToTransaction({ id, title, doc, wuPath, statusPath, backlogPath, stampPath, transaction, }) {
|
|
133
|
+
// WU-1369: Fail fast before any computations
|
|
134
|
+
validateMetadataFilesExist({ statusPath, backlogPath });
|
|
135
|
+
const exposureUpdate = applyExposureDefaults(doc);
|
|
136
|
+
if (exposureUpdate.applied) {
|
|
137
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Auto-set exposure to ${exposureUpdate.exposure} for ${id}`);
|
|
138
|
+
}
|
|
139
|
+
// Compute WU YAML content (mutates doc, returns YAML string)
|
|
140
|
+
const wuYAMLContent = computeWUYAMLContent(doc);
|
|
141
|
+
transaction.addWrite(wuPath, wuYAMLContent, 'WU YAML');
|
|
142
|
+
// Compute status.md content
|
|
143
|
+
const statusContent = computeStatusContent(statusPath, id, title);
|
|
144
|
+
transaction.addWrite(statusPath, statusContent, 'status.md');
|
|
145
|
+
// Compute backlog.md content (WU-1574: now async)
|
|
146
|
+
const backlogContent = await computeBacklogContent(backlogPath, id, title);
|
|
147
|
+
transaction.addWrite(backlogPath, backlogContent, 'backlog.md');
|
|
148
|
+
const wuEventsUpdate = await computeWUEventsContentAfterComplete(backlogPath, id);
|
|
149
|
+
if (wuEventsUpdate) {
|
|
150
|
+
transaction.addWrite(wuEventsUpdate.eventsPath, wuEventsUpdate.content, 'wu-events.jsonl');
|
|
151
|
+
}
|
|
152
|
+
// Compute stamp content
|
|
153
|
+
const stampContent = computeStampContent(id, title);
|
|
154
|
+
transaction.addWrite(stampPath, stampContent, 'completion stamp');
|
|
155
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Collected ${transaction.size} metadata updates for atomic commit`);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Stage and format metadata files
|
|
159
|
+
* Extracted from wu-done.mjs (WU-1215 Phase 2 Extraction #1 Helper)
|
|
160
|
+
* @param {object} params - Parameters object
|
|
161
|
+
* @param {string} params.id - WU ID (for error reporting)
|
|
162
|
+
* @param {string} params.wuPath - Path to WU YAML file
|
|
163
|
+
* @param {string} params.statusPath - Path to status.md file
|
|
164
|
+
* @param {string} params.backlogPath - Path to backlog.md file
|
|
165
|
+
* @param {string} params.stampsDir - Path to stamps directory
|
|
166
|
+
* @throws {Error} If formatting fails
|
|
167
|
+
*/
|
|
168
|
+
export async function stageAndFormatMetadata({ id, wuPath, statusPath, backlogPath, stampsDir }) {
|
|
169
|
+
// WU-1235: Use getGitForCwd() to capture current directory (worktree after chdir)
|
|
170
|
+
// The singleton git adapter captures cwd at import time, which is wrong after process.chdir()
|
|
171
|
+
const gitCwd = getGitForCwd();
|
|
172
|
+
// Stage files
|
|
173
|
+
const wuEventsPath = path.join(BEACON_PATHS.STATE_DIR, WU_EVENTS_FILE_NAME);
|
|
174
|
+
const filesToStage = [wuPath, statusPath, backlogPath, stampsDir];
|
|
175
|
+
if (existsSync(wuEventsPath)) {
|
|
176
|
+
filesToStage.push(wuEventsPath);
|
|
177
|
+
}
|
|
178
|
+
await gitCwd.add(filesToStage);
|
|
179
|
+
// Format documentation
|
|
180
|
+
console.log(`${LOG_PREFIX.DONE} Formatting auto-generated documentation...`);
|
|
181
|
+
try {
|
|
182
|
+
const prettierCmd = `${PKG_MANAGER} ${SCRIPTS.PRETTIER} ${PRETTIER_FLAGS.WRITE} "${wuPath}" "${statusPath}" "${backlogPath}"`;
|
|
183
|
+
await execAsync(prettierCmd);
|
|
184
|
+
await gitCwd.add([wuPath, statusPath, backlogPath]);
|
|
185
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Documentation formatted`);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
throw createValidationError(`Failed to format documentation: ${err.message}`, {
|
|
189
|
+
wuId: id,
|
|
190
|
+
error: err.message,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|