@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.
Files changed (65) hide show
  1. package/dist/arg-parser.js +31 -1
  2. package/dist/backlog-generator.js +1 -1
  3. package/dist/backlog-sync-validator.js +3 -3
  4. package/dist/branch-check.d.ts +21 -0
  5. package/dist/branch-check.js +77 -0
  6. package/dist/cli/is-agent-branch.d.ts +11 -0
  7. package/dist/cli/is-agent-branch.js +15 -0
  8. package/dist/code-paths-overlap.js +2 -2
  9. package/dist/error-handler.d.ts +1 -0
  10. package/dist/error-handler.js +4 -1
  11. package/dist/git-adapter.d.ts +16 -0
  12. package/dist/git-adapter.js +23 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +2 -0
  15. package/dist/lane-checker.d.ts +36 -3
  16. package/dist/lane-checker.js +128 -17
  17. package/dist/lane-inference.js +3 -4
  18. package/dist/lumenflow-config-schema.d.ts +125 -0
  19. package/dist/lumenflow-config-schema.js +76 -0
  20. package/dist/orchestration-rules.d.ts +1 -1
  21. package/dist/orchestration-rules.js +2 -2
  22. package/dist/path-classifiers.d.ts +1 -1
  23. package/dist/path-classifiers.js +1 -1
  24. package/dist/rebase-artifact-cleanup.d.ts +17 -0
  25. package/dist/rebase-artifact-cleanup.js +49 -8
  26. package/dist/spawn-strategy.d.ts +53 -0
  27. package/dist/spawn-strategy.js +106 -0
  28. package/dist/stamp-utils.d.ts +10 -0
  29. package/dist/stamp-utils.js +17 -19
  30. package/dist/token-counter.js +2 -2
  31. package/dist/wu-consistency-checker.js +5 -5
  32. package/dist/wu-constants.d.ts +21 -3
  33. package/dist/wu-constants.js +28 -3
  34. package/dist/wu-done-branch-utils.d.ts +10 -0
  35. package/dist/wu-done-branch-utils.js +31 -0
  36. package/dist/wu-done-cleanup.d.ts +8 -0
  37. package/dist/wu-done-cleanup.js +122 -0
  38. package/dist/wu-done-docs-only.d.ts +20 -0
  39. package/dist/wu-done-docs-only.js +65 -0
  40. package/dist/wu-done-errors.d.ts +17 -0
  41. package/dist/wu-done-errors.js +24 -0
  42. package/dist/wu-done-inputs.d.ts +12 -0
  43. package/dist/wu-done-inputs.js +51 -0
  44. package/dist/wu-done-metadata.d.ts +100 -0
  45. package/dist/wu-done-metadata.js +193 -0
  46. package/dist/wu-done-paths.d.ts +69 -0
  47. package/dist/wu-done-paths.js +237 -0
  48. package/dist/wu-done-preflight.d.ts +48 -0
  49. package/dist/wu-done-preflight.js +185 -0
  50. package/dist/wu-done-validation.d.ts +82 -0
  51. package/dist/wu-done-validation.js +340 -0
  52. package/dist/wu-done-validators.d.ts +13 -409
  53. package/dist/wu-done-validators.js +9 -1225
  54. package/dist/wu-done-worktree.d.ts +0 -1
  55. package/dist/wu-done-worktree.js +12 -30
  56. package/dist/wu-schema.js +1 -3
  57. package/dist/wu-spawn-skills.d.ts +19 -0
  58. package/dist/wu-spawn-skills.js +148 -0
  59. package/dist/wu-spawn.d.ts +17 -4
  60. package/dist/wu-spawn.js +99 -176
  61. package/dist/wu-validation.d.ts +1 -0
  62. package/dist/wu-validation.js +21 -1
  63. package/dist/wu-validator.d.ts +51 -0
  64. package/dist/wu-validator.js +108 -0
  65. 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
+ }