@lumenflow/core 1.0.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/LICENSE +190 -0
- package/README.md +119 -0
- package/dist/active-wu-detector.d.ts +33 -0
- package/dist/active-wu-detector.js +106 -0
- package/dist/adapters/filesystem-metrics.adapter.d.ts +108 -0
- package/dist/adapters/filesystem-metrics.adapter.js +519 -0
- package/dist/adapters/terminal-renderer.adapter.d.ts +106 -0
- package/dist/adapters/terminal-renderer.adapter.js +337 -0
- package/dist/arg-parser.d.ts +63 -0
- package/dist/arg-parser.js +560 -0
- package/dist/backlog-editor.d.ts +98 -0
- package/dist/backlog-editor.js +179 -0
- package/dist/backlog-generator.d.ts +111 -0
- package/dist/backlog-generator.js +381 -0
- package/dist/backlog-parser.d.ts +45 -0
- package/dist/backlog-parser.js +102 -0
- package/dist/backlog-sync-validator.d.ts +78 -0
- package/dist/backlog-sync-validator.js +294 -0
- package/dist/branch-drift.d.ts +34 -0
- package/dist/branch-drift.js +51 -0
- package/dist/cleanup-install-config.d.ts +33 -0
- package/dist/cleanup-install-config.js +37 -0
- package/dist/cleanup-lock.d.ts +139 -0
- package/dist/cleanup-lock.js +313 -0
- package/dist/code-path-validator.d.ts +146 -0
- package/dist/code-path-validator.js +537 -0
- package/dist/code-paths-overlap.d.ts +55 -0
- package/dist/code-paths-overlap.js +245 -0
- package/dist/commands-logger.d.ts +77 -0
- package/dist/commands-logger.js +254 -0
- package/dist/commit-message-utils.d.ts +25 -0
- package/dist/commit-message-utils.js +41 -0
- package/dist/compliance-parser.d.ts +150 -0
- package/dist/compliance-parser.js +507 -0
- package/dist/constants/backlog-patterns.d.ts +20 -0
- package/dist/constants/backlog-patterns.js +23 -0
- package/dist/constants/dora-constants.d.ts +49 -0
- package/dist/constants/dora-constants.js +53 -0
- package/dist/constants/gate-constants.d.ts +15 -0
- package/dist/constants/gate-constants.js +15 -0
- package/dist/constants/linter-constants.d.ts +16 -0
- package/dist/constants/linter-constants.js +16 -0
- package/dist/constants/tokenizer-constants.d.ts +15 -0
- package/dist/constants/tokenizer-constants.js +15 -0
- package/dist/core/scope-checker.d.ts +97 -0
- package/dist/core/scope-checker.js +163 -0
- package/dist/core/tool-runner.d.ts +161 -0
- package/dist/core/tool-runner.js +393 -0
- package/dist/core/tool.constants.d.ts +105 -0
- package/dist/core/tool.constants.js +101 -0
- package/dist/core/tool.schemas.d.ts +226 -0
- package/dist/core/tool.schemas.js +226 -0
- package/dist/core/worktree-guard.d.ts +130 -0
- package/dist/core/worktree-guard.js +242 -0
- package/dist/coverage-gate.d.ts +108 -0
- package/dist/coverage-gate.js +196 -0
- package/dist/date-utils.d.ts +75 -0
- package/dist/date-utils.js +140 -0
- package/dist/dependency-graph.d.ts +142 -0
- package/dist/dependency-graph.js +550 -0
- package/dist/dependency-guard.d.ts +54 -0
- package/dist/dependency-guard.js +142 -0
- package/dist/dependency-validator.d.ts +105 -0
- package/dist/dependency-validator.js +154 -0
- package/dist/docs-path-validator.d.ts +36 -0
- package/dist/docs-path-validator.js +95 -0
- package/dist/domain/orchestration.constants.d.ts +99 -0
- package/dist/domain/orchestration.constants.js +97 -0
- package/dist/domain/orchestration.schemas.d.ts +280 -0
- package/dist/domain/orchestration.schemas.js +211 -0
- package/dist/domain/orchestration.types.d.ts +133 -0
- package/dist/domain/orchestration.types.js +12 -0
- package/dist/error-handler.d.ts +116 -0
- package/dist/error-handler.js +136 -0
- package/dist/file-classifiers.d.ts +62 -0
- package/dist/file-classifiers.js +108 -0
- package/dist/gates-agent-mode.d.ts +81 -0
- package/dist/gates-agent-mode.js +94 -0
- package/dist/generate-traceability.d.ts +107 -0
- package/dist/generate-traceability.js +411 -0
- package/dist/git-adapter.d.ts +395 -0
- package/dist/git-adapter.js +649 -0
- package/dist/git-staged-validator.d.ts +32 -0
- package/dist/git-staged-validator.js +48 -0
- package/dist/hardcoded-strings.d.ts +61 -0
- package/dist/hardcoded-strings.js +270 -0
- package/dist/incremental-lint.d.ts +78 -0
- package/dist/incremental-lint.js +129 -0
- package/dist/incremental-test.d.ts +39 -0
- package/dist/incremental-test.js +61 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +61 -0
- package/dist/invariants/check-automated-tests.d.ts +50 -0
- package/dist/invariants/check-automated-tests.js +166 -0
- package/dist/invariants-runner.d.ts +103 -0
- package/dist/invariants-runner.js +527 -0
- package/dist/lane-checker.d.ts +50 -0
- package/dist/lane-checker.js +319 -0
- package/dist/lane-inference.d.ts +39 -0
- package/dist/lane-inference.js +195 -0
- package/dist/lane-lock.d.ts +211 -0
- package/dist/lane-lock.js +474 -0
- package/dist/lane-validator.d.ts +48 -0
- package/dist/lane-validator.js +114 -0
- package/dist/logs-lib.d.ts +104 -0
- package/dist/logs-lib.js +207 -0
- package/dist/lumenflow-config-schema.d.ts +272 -0
- package/dist/lumenflow-config-schema.js +207 -0
- package/dist/lumenflow-config.d.ts +95 -0
- package/dist/lumenflow-config.js +236 -0
- package/dist/manual-test-validator.d.ts +80 -0
- package/dist/manual-test-validator.js +200 -0
- package/dist/merge-lock.d.ts +115 -0
- package/dist/merge-lock.js +251 -0
- package/dist/micro-worktree.d.ts +159 -0
- package/dist/micro-worktree.js +427 -0
- package/dist/migration-deployer.d.ts +69 -0
- package/dist/migration-deployer.js +151 -0
- package/dist/orchestration-advisory-loader.d.ts +28 -0
- package/dist/orchestration-advisory-loader.js +87 -0
- package/dist/orchestration-advisory.d.ts +58 -0
- package/dist/orchestration-advisory.js +94 -0
- package/dist/orchestration-di.d.ts +48 -0
- package/dist/orchestration-di.js +57 -0
- package/dist/orchestration-rules.d.ts +57 -0
- package/dist/orchestration-rules.js +201 -0
- package/dist/orphan-detector.d.ts +131 -0
- package/dist/orphan-detector.js +226 -0
- package/dist/path-classifiers.d.ts +57 -0
- package/dist/path-classifiers.js +93 -0
- package/dist/piped-command-detector.d.ts +34 -0
- package/dist/piped-command-detector.js +64 -0
- package/dist/ports/dashboard-renderer.port.d.ts +112 -0
- package/dist/ports/dashboard-renderer.port.js +25 -0
- package/dist/ports/metrics-collector.port.d.ts +132 -0
- package/dist/ports/metrics-collector.port.js +26 -0
- package/dist/process-detector.d.ts +84 -0
- package/dist/process-detector.js +172 -0
- package/dist/prompt-linter.d.ts +72 -0
- package/dist/prompt-linter.js +312 -0
- package/dist/prompt-monitor.d.ts +15 -0
- package/dist/prompt-monitor.js +205 -0
- package/dist/rebase-artifact-cleanup.d.ts +145 -0
- package/dist/rebase-artifact-cleanup.js +433 -0
- package/dist/retry-strategy.d.ts +189 -0
- package/dist/retry-strategy.js +283 -0
- package/dist/risk-detector.d.ts +108 -0
- package/dist/risk-detector.js +252 -0
- package/dist/rollback-utils.d.ts +76 -0
- package/dist/rollback-utils.js +104 -0
- package/dist/section-headings.d.ts +43 -0
- package/dist/section-headings.js +49 -0
- package/dist/spawn-escalation.d.ts +90 -0
- package/dist/spawn-escalation.js +253 -0
- package/dist/spawn-monitor.d.ts +229 -0
- package/dist/spawn-monitor.js +672 -0
- package/dist/spawn-recovery.d.ts +82 -0
- package/dist/spawn-recovery.js +298 -0
- package/dist/spawn-registry-schema.d.ts +98 -0
- package/dist/spawn-registry-schema.js +108 -0
- package/dist/spawn-registry-store.d.ts +146 -0
- package/dist/spawn-registry-store.js +273 -0
- package/dist/spawn-tree.d.ts +121 -0
- package/dist/spawn-tree.js +285 -0
- package/dist/stamp-status-validator.d.ts +84 -0
- package/dist/stamp-status-validator.js +134 -0
- package/dist/stamp-utils.d.ts +100 -0
- package/dist/stamp-utils.js +229 -0
- package/dist/state-machine.d.ts +26 -0
- package/dist/state-machine.js +83 -0
- package/dist/system-map-validator.d.ts +80 -0
- package/dist/system-map-validator.js +272 -0
- package/dist/telemetry.d.ts +80 -0
- package/dist/telemetry.js +213 -0
- package/dist/token-counter.d.ts +51 -0
- package/dist/token-counter.js +145 -0
- package/dist/usecases/get-dashboard-data.usecase.d.ts +52 -0
- package/dist/usecases/get-dashboard-data.usecase.js +61 -0
- package/dist/usecases/get-suggestions.usecase.d.ts +100 -0
- package/dist/usecases/get-suggestions.usecase.js +153 -0
- package/dist/user-normalizer.d.ts +41 -0
- package/dist/user-normalizer.js +141 -0
- package/dist/validators/phi-constants.d.ts +97 -0
- package/dist/validators/phi-constants.js +152 -0
- package/dist/validators/phi-scanner.d.ts +58 -0
- package/dist/validators/phi-scanner.js +215 -0
- package/dist/worktree-ownership.d.ts +50 -0
- package/dist/worktree-ownership.js +74 -0
- package/dist/worktree-scanner.d.ts +103 -0
- package/dist/worktree-scanner.js +168 -0
- package/dist/worktree-symlink.d.ts +99 -0
- package/dist/worktree-symlink.js +359 -0
- package/dist/wu-backlog-updater.d.ts +17 -0
- package/dist/wu-backlog-updater.js +37 -0
- package/dist/wu-checkpoint.d.ts +124 -0
- package/dist/wu-checkpoint.js +233 -0
- package/dist/wu-claim-helpers.d.ts +26 -0
- package/dist/wu-claim-helpers.js +63 -0
- package/dist/wu-claim-resume.d.ts +106 -0
- package/dist/wu-claim-resume.js +276 -0
- package/dist/wu-consistency-checker.d.ts +95 -0
- package/dist/wu-consistency-checker.js +567 -0
- package/dist/wu-constants.d.ts +1275 -0
- package/dist/wu-constants.js +1382 -0
- package/dist/wu-create-validators.d.ts +42 -0
- package/dist/wu-create-validators.js +93 -0
- package/dist/wu-done-branch-only.d.ts +63 -0
- package/dist/wu-done-branch-only.js +191 -0
- package/dist/wu-done-messages.d.ts +119 -0
- package/dist/wu-done-messages.js +185 -0
- package/dist/wu-done-pr.d.ts +72 -0
- package/dist/wu-done-pr.js +174 -0
- package/dist/wu-done-retry-helpers.d.ts +85 -0
- package/dist/wu-done-retry-helpers.js +172 -0
- package/dist/wu-done-ui.d.ts +37 -0
- package/dist/wu-done-ui.js +69 -0
- package/dist/wu-done-validators.d.ts +411 -0
- package/dist/wu-done-validators.js +1229 -0
- package/dist/wu-done-worktree.d.ts +182 -0
- package/dist/wu-done-worktree.js +1097 -0
- package/dist/wu-helpers.d.ts +128 -0
- package/dist/wu-helpers.js +248 -0
- package/dist/wu-lint.d.ts +70 -0
- package/dist/wu-lint.js +234 -0
- package/dist/wu-paths.d.ts +171 -0
- package/dist/wu-paths.js +178 -0
- package/dist/wu-preflight-validators.d.ts +86 -0
- package/dist/wu-preflight-validators.js +251 -0
- package/dist/wu-recovery.d.ts +138 -0
- package/dist/wu-recovery.js +341 -0
- package/dist/wu-repair-core.d.ts +131 -0
- package/dist/wu-repair-core.js +669 -0
- package/dist/wu-schema-normalization.d.ts +17 -0
- package/dist/wu-schema-normalization.js +82 -0
- package/dist/wu-schema.d.ts +793 -0
- package/dist/wu-schema.js +881 -0
- package/dist/wu-spawn-helpers.d.ts +121 -0
- package/dist/wu-spawn-helpers.js +271 -0
- package/dist/wu-spawn.d.ts +158 -0
- package/dist/wu-spawn.js +1306 -0
- package/dist/wu-state-schema.d.ts +213 -0
- package/dist/wu-state-schema.js +156 -0
- package/dist/wu-state-store.d.ts +264 -0
- package/dist/wu-state-store.js +691 -0
- package/dist/wu-status-transition.d.ts +63 -0
- package/dist/wu-status-transition.js +382 -0
- package/dist/wu-status-updater.d.ts +25 -0
- package/dist/wu-status-updater.js +116 -0
- package/dist/wu-transaction-collectors.d.ts +116 -0
- package/dist/wu-transaction-collectors.js +272 -0
- package/dist/wu-transaction.d.ts +170 -0
- package/dist/wu-transaction.js +273 -0
- package/dist/wu-validation-constants.d.ts +60 -0
- package/dist/wu-validation-constants.js +66 -0
- package/dist/wu-validation.d.ts +118 -0
- package/dist/wu-validation.js +243 -0
- package/dist/wu-validator.d.ts +62 -0
- package/dist/wu-validator.js +325 -0
- package/dist/wu-yaml-fixer.d.ts +97 -0
- package/dist/wu-yaml-fixer.js +264 -0
- package/dist/wu-yaml.d.ts +86 -0
- package/dist/wu-yaml.js +222 -0
- package/package.json +114 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file worktree-guard.mjs
|
|
3
|
+
* @description WU context validation and main branch protection (WU-1396)
|
|
4
|
+
*
|
|
5
|
+
* Provides runtime guards to enforce worktree discipline:
|
|
6
|
+
* - Detect if current directory is inside a worktree
|
|
7
|
+
* - Extract WU ID and lane from worktree path or git branch
|
|
8
|
+
* - Throw descriptive error when write operations attempted outside worktree
|
|
9
|
+
* - Check if on main/master branch
|
|
10
|
+
*
|
|
11
|
+
* Used by wu- scripts to prevent writes to main checkout when worktrees exist.
|
|
12
|
+
* Complements .claude/hooks/user-prompt-submit-hook (human agent protection).
|
|
13
|
+
*
|
|
14
|
+
* @see {@link .claude/hooks/user-prompt-submit-hook} - Agent blocking hook
|
|
15
|
+
* @see {@link docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md} - Worktree discipline
|
|
16
|
+
*/
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { createGitForPath } from '../git-adapter.js';
|
|
19
|
+
import { BRANCHES } from '../wu-constants.js';
|
|
20
|
+
/**
|
|
21
|
+
* Worktree path pattern: worktrees/<lane-kebab>-wu-<id>
|
|
22
|
+
* Captures: lane (kebab-case) and WU ID number
|
|
23
|
+
*
|
|
24
|
+
* Examples:
|
|
25
|
+
* - worktrees/operations-tooling-wu-1396
|
|
26
|
+
* - worktrees/intelligence-wu-789
|
|
27
|
+
* - worktrees/core-systems-api-wu-456
|
|
28
|
+
*/
|
|
29
|
+
const WORKTREE_PATH_PATTERN = /worktrees\/([\w-]+)-wu-(\d+)/;
|
|
30
|
+
/**
|
|
31
|
+
* Lane branch pattern: lane/<lane-kebab>/wu-<id>
|
|
32
|
+
* Captures: lane (kebab-case) and WU ID number
|
|
33
|
+
*
|
|
34
|
+
* Examples:
|
|
35
|
+
* - lane/operations-tooling/wu-1396
|
|
36
|
+
* - lane/intelligence/wu-789
|
|
37
|
+
* - lane/core-systems-api/wu-456
|
|
38
|
+
*/
|
|
39
|
+
const LANE_BRANCH_PATTERN = /^lane\/([\w-]+)\/wu-(\d+)$/;
|
|
40
|
+
/**
|
|
41
|
+
* Check if on main or master branch
|
|
42
|
+
*
|
|
43
|
+
* @param {GitOptions} [options] - Options
|
|
44
|
+
* @returns {Promise<boolean>} True if on main/master branch
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* if (await isMainBranch()) {
|
|
48
|
+
* console.log('On main branch');
|
|
49
|
+
* }
|
|
50
|
+
*/
|
|
51
|
+
export async function isMainBranch(options = {}) {
|
|
52
|
+
const git = options.git || createGitForPath(process.cwd());
|
|
53
|
+
const branch = await git.getCurrentBranch();
|
|
54
|
+
return branch === BRANCHES.MAIN || branch === BRANCHES.MASTER;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Normalize path separators to forward slashes
|
|
58
|
+
*
|
|
59
|
+
* Handles both Unix and Windows path separators for cross-platform compatibility.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} p - Path to normalize
|
|
62
|
+
* @returns {string} Path with forward slashes
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
function normalizePath(p) {
|
|
66
|
+
// Replace both backslashes and path.sep with forward slashes
|
|
67
|
+
// This handles Windows paths on Linux during testing
|
|
68
|
+
return p.replace(/\\/g, '/').split(path.sep).join('/');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if current directory is inside a worktree
|
|
72
|
+
*
|
|
73
|
+
* Detects worktree by checking if path contains worktrees/<lane>-wu-<id> pattern.
|
|
74
|
+
* Works correctly from nested directories within worktree.
|
|
75
|
+
*
|
|
76
|
+
* @param {Object} [options] - Options
|
|
77
|
+
* @param {string} [options.cwd] - Current working directory (defaults to process.cwd())
|
|
78
|
+
* @returns {boolean} True if inside a worktree directory
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* if (isInWorktree()) {
|
|
82
|
+
* console.log('Working in a worktree');
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* // From nested directory
|
|
86
|
+
* isInWorktree({ cwd: '/project/worktrees/operations-wu-123/tools/lib' }); // true
|
|
87
|
+
*/
|
|
88
|
+
export function isInWorktree(options = {}) {
|
|
89
|
+
const cwd = options.cwd || process.cwd();
|
|
90
|
+
// Normalize path separators for cross-platform compatibility
|
|
91
|
+
const normalizedPath = normalizePath(cwd);
|
|
92
|
+
return WORKTREE_PATH_PATTERN.test(normalizedPath);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Extract WU context from worktree path
|
|
96
|
+
*
|
|
97
|
+
* @param {string} cwd - Current working directory
|
|
98
|
+
* @returns {Object|null} Context object or null if not a worktree path
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
function extractFromWorktreePath(cwd) {
|
|
102
|
+
const normalizedPath = normalizePath(cwd);
|
|
103
|
+
const match = normalizedPath.match(WORKTREE_PATH_PATTERN);
|
|
104
|
+
if (!match) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const [fullMatch, lane, wuIdNumber] = match;
|
|
108
|
+
const wuId = `WU-${wuIdNumber}`;
|
|
109
|
+
// Extract just the worktrees/<lane>-wu-<id> part (not full absolute path)
|
|
110
|
+
const worktreePathMatch = fullMatch.match(/(worktrees\/[\w-]+-wu-\d+)/);
|
|
111
|
+
const worktreePath = worktreePathMatch ? worktreePathMatch[1] : null;
|
|
112
|
+
return {
|
|
113
|
+
wuId,
|
|
114
|
+
lane,
|
|
115
|
+
worktreePath,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Extract WU context from git branch name
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} git - GitAdapter instance
|
|
122
|
+
* @returns {Promise<Object|null>} Context object or null if not a lane branch
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
async function extractFromBranch(git) {
|
|
126
|
+
const branch = await git.getCurrentBranch();
|
|
127
|
+
const match = branch.match(LANE_BRANCH_PATTERN);
|
|
128
|
+
if (!match) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const [, lane, wuIdNumber] = match;
|
|
132
|
+
const wuId = `WU-${wuIdNumber}`;
|
|
133
|
+
return {
|
|
134
|
+
wuId,
|
|
135
|
+
lane,
|
|
136
|
+
worktreePath: null, // Not in worktree, on lane branch
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get WU context from current directory or git branch
|
|
141
|
+
*
|
|
142
|
+
* Extracts WU ID, lane, and worktree path from:
|
|
143
|
+
* 1. Worktree directory path (priority) - works from nested directories
|
|
144
|
+
* 2. Git branch name (fallback) - lane/operations-tooling/wu-1396
|
|
145
|
+
*
|
|
146
|
+
* @param {Object} [options] - Options
|
|
147
|
+
* @param {string} [options.cwd] - Current working directory (defaults to process.cwd())
|
|
148
|
+
* @param {Object} [options.git] - GitAdapter instance (for testing)
|
|
149
|
+
* @returns {Promise<Object|null>} WU context or null if not in WU workspace
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* // From worktree
|
|
153
|
+
* const ctx = await getWUContext();
|
|
154
|
+
* // { wuId: 'WU-1396', lane: 'operations-tooling', worktreePath: 'worktrees/operations-tooling-wu-1396' }
|
|
155
|
+
*
|
|
156
|
+
* // From lane branch (not in worktree)
|
|
157
|
+
* const ctx = await getWUContext();
|
|
158
|
+
* // { wuId: 'WU-1396', lane: 'operations-tooling', worktreePath: null }
|
|
159
|
+
*
|
|
160
|
+
* // From main checkout on main branch
|
|
161
|
+
* const ctx = await getWUContext();
|
|
162
|
+
* // null
|
|
163
|
+
*/
|
|
164
|
+
export async function getWUContext(options = {}) {
|
|
165
|
+
const cwd = options.cwd || process.cwd();
|
|
166
|
+
// Fast path: Try worktree path first (no git operations needed)
|
|
167
|
+
const worktreeContext = extractFromWorktreePath(cwd);
|
|
168
|
+
if (worktreeContext) {
|
|
169
|
+
return worktreeContext;
|
|
170
|
+
}
|
|
171
|
+
// Fallback to git branch detection (requires git operations)
|
|
172
|
+
// Only create git adapter if we have one provided or if we need to check branch
|
|
173
|
+
if (options.git) {
|
|
174
|
+
return await extractFromBranch(options.git);
|
|
175
|
+
}
|
|
176
|
+
// Create git adapter only if needed (path wasn't a worktree)
|
|
177
|
+
const git = createGitForPath(cwd);
|
|
178
|
+
return await extractFromBranch(git);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Assert that current context is inside a worktree or on a lane branch
|
|
182
|
+
*
|
|
183
|
+
* Throws descriptive error if:
|
|
184
|
+
* - On main/master branch AND
|
|
185
|
+
* - Not in a worktree directory
|
|
186
|
+
*
|
|
187
|
+
* Used by write operations to prevent modifications to main checkout.
|
|
188
|
+
*
|
|
189
|
+
* @param {Object} [options] - Options
|
|
190
|
+
* @param {string} [options.cwd] - Current working directory (defaults to process.cwd())
|
|
191
|
+
* @param {Object} [options.git] - GitAdapter instance (for testing)
|
|
192
|
+
* @param {string} [options.operation] - Operation name for error message
|
|
193
|
+
* @throws {Error} If not in worktree and on main branch
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* // In a wu- script write operation
|
|
197
|
+
* await assertWorktreeRequired({ operation: 'wu:claim' });
|
|
198
|
+
*
|
|
199
|
+
* // Will throw if on main in main checkout:
|
|
200
|
+
* // Error: BLOCKED: Operation 'wu:claim' requires a worktree.
|
|
201
|
+
* // You are on 'main' branch in main checkout.
|
|
202
|
+
* // ...
|
|
203
|
+
*/
|
|
204
|
+
export async function assertWorktreeRequired(options = {}) {
|
|
205
|
+
const cwd = options.cwd || process.cwd();
|
|
206
|
+
const operation = options.operation || 'this operation';
|
|
207
|
+
// Fast path: Check worktree path first (no git operations needed)
|
|
208
|
+
const worktreeContext = extractFromWorktreePath(cwd);
|
|
209
|
+
if (worktreeContext) {
|
|
210
|
+
return; // In worktree, allow operation
|
|
211
|
+
}
|
|
212
|
+
// Need git operations to check branch
|
|
213
|
+
const git = options.git || createGitForPath(cwd);
|
|
214
|
+
// Check if on lane branch
|
|
215
|
+
const branchContext = await extractFromBranch(git);
|
|
216
|
+
if (branchContext) {
|
|
217
|
+
return; // On lane branch, allow operation
|
|
218
|
+
}
|
|
219
|
+
// Check if on main branch
|
|
220
|
+
const onMain = await isMainBranch({ git });
|
|
221
|
+
if (onMain) {
|
|
222
|
+
throw new Error(`❌ BLOCKED: Operation '${operation}' requires a worktree.
|
|
223
|
+
|
|
224
|
+
You are on 'main' branch in main checkout.
|
|
225
|
+
|
|
226
|
+
To fix:
|
|
227
|
+
1. Claim a WU first:
|
|
228
|
+
pnpm wu:claim --id WU-1234 --lane "Operations: Tooling"
|
|
229
|
+
|
|
230
|
+
2. Navigate to the worktree:
|
|
231
|
+
cd worktrees/operations-tooling-wu-1234/
|
|
232
|
+
|
|
233
|
+
3. Run your operation from the worktree.
|
|
234
|
+
|
|
235
|
+
For more information:
|
|
236
|
+
See CLAUDE.md §2 (Worktree Discipline)
|
|
237
|
+
See .claude/skills/worktree-discipline/SKILL.md
|
|
238
|
+
`);
|
|
239
|
+
}
|
|
240
|
+
// Pass if on a non-main branch (e.g., feature branch) - allow for flexibility
|
|
241
|
+
// This case is less strict since it's not the main branch
|
|
242
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Coverage Gate for Quality Gates
|
|
4
|
+
*
|
|
5
|
+
* WU-1433: Adds coverage checking to gates with configurable mode (warn/block).
|
|
6
|
+
* Enforces ≥90% coverage on hex core files (application layer).
|
|
7
|
+
*
|
|
8
|
+
* Mode flag allows gradual rollout:
|
|
9
|
+
* - warn: Log failures but don't block (default)
|
|
10
|
+
* - block: Fail the gate if thresholds not met
|
|
11
|
+
*
|
|
12
|
+
* @see {@link tools/gates.mjs} - Integration point
|
|
13
|
+
* @see {@link vitest.config.ts} - Coverage thresholds
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Coverage gate modes
|
|
17
|
+
* @constant
|
|
18
|
+
*/
|
|
19
|
+
export declare const COVERAGE_GATE_MODES: Readonly<{
|
|
20
|
+
/** Log warnings but don't fail the gate */
|
|
21
|
+
WARN: "warn";
|
|
22
|
+
/** Fail the gate if thresholds not met */
|
|
23
|
+
BLOCK: "block";
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Glob patterns for hex core files that require ≥90% coverage.
|
|
27
|
+
* These are the critical application layer files.
|
|
28
|
+
*
|
|
29
|
+
* @constant {string[]}
|
|
30
|
+
*/
|
|
31
|
+
export declare const HEX_CORE_PATTERNS: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* Coverage threshold for hex core files (percentage)
|
|
34
|
+
* @constant {number}
|
|
35
|
+
*/
|
|
36
|
+
export declare const COVERAGE_THRESHOLD = 90;
|
|
37
|
+
/**
|
|
38
|
+
* Default path to coverage summary JSON
|
|
39
|
+
* @constant {string}
|
|
40
|
+
*/
|
|
41
|
+
export declare const DEFAULT_COVERAGE_PATH = "coverage/coverage-summary.json";
|
|
42
|
+
/**
|
|
43
|
+
* Check if a file path is in the hex core layer.
|
|
44
|
+
*
|
|
45
|
+
* WU-2448: Coverage reporters may emit absolute paths (e.g., /home/.../packages/...)
|
|
46
|
+
* or file:// URLs; use substring matching so hex-core checks still apply.
|
|
47
|
+
*
|
|
48
|
+
* @param {string|null|undefined} filePath - File path to check
|
|
49
|
+
* @returns {boolean} True if file is in hex core layer
|
|
50
|
+
*/
|
|
51
|
+
export declare function isHexCoreFile(filePath: any): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Parse coverage JSON file.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} coveragePath - Path to coverage-summary.json
|
|
56
|
+
* @returns {object|null} Parsed coverage data or null if invalid
|
|
57
|
+
*/
|
|
58
|
+
export declare function parseCoverageJson(coveragePath: any): {
|
|
59
|
+
total: any;
|
|
60
|
+
files: {};
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Check if coverage meets thresholds for hex core files.
|
|
64
|
+
*
|
|
65
|
+
* @param {object|null} coverageData - Parsed coverage data
|
|
66
|
+
* @returns {{ pass: boolean, failures: Array<{ file: string, actual: number, threshold: number, metric: string }> }}
|
|
67
|
+
*/
|
|
68
|
+
export declare function checkCoverageThresholds(coverageData: any): {
|
|
69
|
+
pass: boolean;
|
|
70
|
+
failures: any[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Format coverage data for display.
|
|
74
|
+
*
|
|
75
|
+
* @param {object|null} coverageData - Parsed coverage data
|
|
76
|
+
* @returns {string} Formatted output string
|
|
77
|
+
*/
|
|
78
|
+
export declare function formatCoverageDelta(coverageData: any): string;
|
|
79
|
+
/**
|
|
80
|
+
* Logger interface for coverage gate output
|
|
81
|
+
*/
|
|
82
|
+
interface CoverageGateLogger {
|
|
83
|
+
log: (...args: unknown[]) => void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Options for running coverage gate
|
|
87
|
+
*/
|
|
88
|
+
export interface CoverageGateOptions {
|
|
89
|
+
/** Gate mode ('warn' or 'block') */
|
|
90
|
+
mode?: string;
|
|
91
|
+
/** Path to coverage JSON */
|
|
92
|
+
coveragePath?: string;
|
|
93
|
+
/** Logger for output */
|
|
94
|
+
logger?: CoverageGateLogger;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Run coverage gate.
|
|
98
|
+
*
|
|
99
|
+
* @param {CoverageGateOptions} options - Gate options
|
|
100
|
+
* @returns {Promise<{ ok: boolean, mode: string, duration: number, message: string }>}
|
|
101
|
+
*/
|
|
102
|
+
export declare function runCoverageGate(options?: CoverageGateOptions): Promise<{
|
|
103
|
+
ok: boolean;
|
|
104
|
+
mode: string;
|
|
105
|
+
duration: number;
|
|
106
|
+
message: string;
|
|
107
|
+
}>;
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Coverage Gate for Quality Gates
|
|
4
|
+
*
|
|
5
|
+
* WU-1433: Adds coverage checking to gates with configurable mode (warn/block).
|
|
6
|
+
* Enforces ≥90% coverage on hex core files (application layer).
|
|
7
|
+
*
|
|
8
|
+
* Mode flag allows gradual rollout:
|
|
9
|
+
* - warn: Log failures but don't block (default)
|
|
10
|
+
* - block: Fail the gate if thresholds not met
|
|
11
|
+
*
|
|
12
|
+
* @see {@link tools/gates.mjs} - Integration point
|
|
13
|
+
* @see {@link vitest.config.ts} - Coverage thresholds
|
|
14
|
+
*/
|
|
15
|
+
/* eslint-disable security/detect-non-literal-fs-filename, security/detect-object-injection */
|
|
16
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
17
|
+
import { EMOJI, STRING_LITERALS } from './wu-constants.js';
|
|
18
|
+
/**
|
|
19
|
+
* Coverage gate modes
|
|
20
|
+
* @constant
|
|
21
|
+
*/
|
|
22
|
+
export const COVERAGE_GATE_MODES = Object.freeze({
|
|
23
|
+
/** Log warnings but don't fail the gate */
|
|
24
|
+
WARN: 'warn',
|
|
25
|
+
/** Fail the gate if thresholds not met */
|
|
26
|
+
BLOCK: 'block',
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Glob patterns for hex core files that require ≥90% coverage.
|
|
30
|
+
* These are the critical application layer files.
|
|
31
|
+
*
|
|
32
|
+
* @constant {string[]}
|
|
33
|
+
*/
|
|
34
|
+
export const HEX_CORE_PATTERNS = Object.freeze([
|
|
35
|
+
'packages/@patientpath/application/',
|
|
36
|
+
'packages/@patientpath/prompts/',
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* Coverage threshold for hex core files (percentage)
|
|
40
|
+
* @constant {number}
|
|
41
|
+
*/
|
|
42
|
+
export const COVERAGE_THRESHOLD = 90;
|
|
43
|
+
/**
|
|
44
|
+
* Default path to coverage summary JSON
|
|
45
|
+
* @constant {string}
|
|
46
|
+
*/
|
|
47
|
+
export const DEFAULT_COVERAGE_PATH = 'coverage/coverage-summary.json';
|
|
48
|
+
/**
|
|
49
|
+
* Check if a file path is in the hex core layer.
|
|
50
|
+
*
|
|
51
|
+
* WU-2448: Coverage reporters may emit absolute paths (e.g., /home/.../packages/...)
|
|
52
|
+
* or file:// URLs; use substring matching so hex-core checks still apply.
|
|
53
|
+
*
|
|
54
|
+
* @param {string|null|undefined} filePath - File path to check
|
|
55
|
+
* @returns {boolean} True if file is in hex core layer
|
|
56
|
+
*/
|
|
57
|
+
export function isHexCoreFile(filePath) {
|
|
58
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// Normalize backslashes to forward slashes for cross-platform compatibility
|
|
62
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
63
|
+
return HEX_CORE_PATTERNS.some((pattern) => normalizedPath.includes(pattern));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parse coverage JSON file.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} coveragePath - Path to coverage-summary.json
|
|
69
|
+
* @returns {object|null} Parsed coverage data or null if invalid
|
|
70
|
+
*/
|
|
71
|
+
export function parseCoverageJson(coveragePath) {
|
|
72
|
+
if (!existsSync(coveragePath)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const content = readFileSync(coveragePath, { encoding: 'utf-8' });
|
|
77
|
+
const data = JSON.parse(content);
|
|
78
|
+
// Transform to consistent format
|
|
79
|
+
const files = {};
|
|
80
|
+
for (const [key, value] of Object.entries(data)) {
|
|
81
|
+
if (key === 'total')
|
|
82
|
+
continue;
|
|
83
|
+
files[key] = value;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
total: data.total,
|
|
87
|
+
files,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if coverage meets thresholds for hex core files.
|
|
96
|
+
*
|
|
97
|
+
* @param {object|null} coverageData - Parsed coverage data
|
|
98
|
+
* @returns {{ pass: boolean, failures: Array<{ file: string, actual: number, threshold: number, metric: string }> }}
|
|
99
|
+
*/
|
|
100
|
+
export function checkCoverageThresholds(coverageData) {
|
|
101
|
+
if (!coverageData || !coverageData.files) {
|
|
102
|
+
return { pass: true, failures: [] };
|
|
103
|
+
}
|
|
104
|
+
const failures = [];
|
|
105
|
+
for (const [file, metricsValue] of Object.entries(coverageData.files)) {
|
|
106
|
+
if (!isHexCoreFile(file)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Check lines coverage (primary metric)
|
|
110
|
+
const metrics = metricsValue;
|
|
111
|
+
const linesCoverage = metrics.lines?.pct ?? 0;
|
|
112
|
+
if (linesCoverage < COVERAGE_THRESHOLD) {
|
|
113
|
+
failures.push({
|
|
114
|
+
file,
|
|
115
|
+
actual: linesCoverage,
|
|
116
|
+
threshold: COVERAGE_THRESHOLD,
|
|
117
|
+
metric: 'lines',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
pass: failures.length === 0,
|
|
123
|
+
failures,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Format coverage data for display.
|
|
128
|
+
*
|
|
129
|
+
* @param {object|null} coverageData - Parsed coverage data
|
|
130
|
+
* @returns {string} Formatted output string
|
|
131
|
+
*/
|
|
132
|
+
export function formatCoverageDelta(coverageData) {
|
|
133
|
+
if (!coverageData) {
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
const lines = [];
|
|
137
|
+
const totalPct = coverageData.total?.lines?.pct ?? 0;
|
|
138
|
+
lines.push(`${STRING_LITERALS.NEWLINE}Coverage Summary: ${totalPct.toFixed(1)}% lines${STRING_LITERALS.NEWLINE}`);
|
|
139
|
+
// Show hex core files
|
|
140
|
+
const hexCoreFiles = Object.entries(coverageData.files || {}).filter(([file]) => isHexCoreFile(file));
|
|
141
|
+
if (hexCoreFiles.length > 0) {
|
|
142
|
+
lines.push('Hex Core Files:');
|
|
143
|
+
for (const [file, metricsValue] of hexCoreFiles) {
|
|
144
|
+
const metrics = metricsValue;
|
|
145
|
+
const pct = metrics.lines?.pct ?? 0;
|
|
146
|
+
const status = pct >= COVERAGE_THRESHOLD ? EMOJI.SUCCESS : EMOJI.FAILURE;
|
|
147
|
+
const shortFile = file.replace('packages/@patientpath/', '');
|
|
148
|
+
lines.push(` ${status} ${shortFile}: ${pct.toFixed(1)}%`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return lines.join(STRING_LITERALS.NEWLINE);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Run coverage gate.
|
|
155
|
+
*
|
|
156
|
+
* @param {CoverageGateOptions} options - Gate options
|
|
157
|
+
* @returns {Promise<{ ok: boolean, mode: string, duration: number, message: string }>}
|
|
158
|
+
*/
|
|
159
|
+
export async function runCoverageGate(options = {}) {
|
|
160
|
+
const start = Date.now();
|
|
161
|
+
const mode = options.mode || COVERAGE_GATE_MODES.WARN;
|
|
162
|
+
const coveragePath = options.coveragePath || DEFAULT_COVERAGE_PATH;
|
|
163
|
+
const logger = options.logger && typeof options.logger.log === 'function' ? options.logger : console;
|
|
164
|
+
// Parse coverage data
|
|
165
|
+
const coverageData = parseCoverageJson(coveragePath);
|
|
166
|
+
if (!coverageData) {
|
|
167
|
+
const duration = Date.now() - start;
|
|
168
|
+
logger.log(`\n${EMOJI.WARNING} Coverage gate: No coverage data found at ${coveragePath}`);
|
|
169
|
+
logger.log(' Run tests with coverage first: pnpm test:coverage\n');
|
|
170
|
+
return { ok: true, mode, duration, message: 'No coverage data' };
|
|
171
|
+
}
|
|
172
|
+
// Check thresholds
|
|
173
|
+
const { pass, failures } = checkCoverageThresholds(coverageData);
|
|
174
|
+
// Format and display
|
|
175
|
+
const output = formatCoverageDelta(coverageData);
|
|
176
|
+
logger.log(output);
|
|
177
|
+
const duration = Date.now() - start;
|
|
178
|
+
if (!pass) {
|
|
179
|
+
logger.log(`\n${EMOJI.FAILURE} Coverage below ${COVERAGE_THRESHOLD}% for hex core files:`);
|
|
180
|
+
for (const failure of failures) {
|
|
181
|
+
const shortFile = failure.file.replace('packages/@patientpath/', '');
|
|
182
|
+
logger.log(` - ${shortFile}: ${failure.actual.toFixed(1)}% (requires ${failure.threshold}%)`);
|
|
183
|
+
}
|
|
184
|
+
if (mode === COVERAGE_GATE_MODES.BLOCK) {
|
|
185
|
+
logger.log(`\n${EMOJI.FAILURE} Coverage gate FAILED (mode: block)\n`);
|
|
186
|
+
return { ok: false, mode, duration, message: 'Coverage threshold not met' };
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
logger.log(`\n${EMOJI.WARNING} Coverage gate WARNING (mode: warn)\n`);
|
|
190
|
+
logger.log(' Note: This will become blocking in future. Fix coverage now.\n');
|
|
191
|
+
return { ok: true, mode, duration, message: 'Coverage warning' };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
logger.log(`\n${EMOJI.SUCCESS} Coverage gate passed\n`);
|
|
195
|
+
return { ok: true, mode, duration, message: 'Coverage OK' };
|
|
196
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file date-utils.mjs
|
|
3
|
+
* @description Date formatting utilities using date-fns library
|
|
4
|
+
* WU-1082: Extract shared utilities (eliminate date formatting duplication)
|
|
5
|
+
*
|
|
6
|
+
* Replaces manual date formatting in:
|
|
7
|
+
* - tools/wu-block.mjs (todayISO)
|
|
8
|
+
* - tools/wu-unblock.mjs (todayISO)
|
|
9
|
+
* - tools/wu-done.mjs (todayISO - already uses date-fns)
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Get current date in ISO format (YYYY-MM-DD)
|
|
13
|
+
* @returns {string} Current date in YYYY-MM-DD format
|
|
14
|
+
* @example
|
|
15
|
+
* todayISO(); // "2025-11-12"
|
|
16
|
+
*/
|
|
17
|
+
export declare function todayISO(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Format a date with a custom format string
|
|
20
|
+
* @param {Date|string|number} date - Date to format
|
|
21
|
+
* @param {string} formatString - date-fns format string
|
|
22
|
+
* @returns {string} Formatted date string
|
|
23
|
+
* @example
|
|
24
|
+
* formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss');
|
|
25
|
+
* formatDate('2025-11-12', 'MMMM d, yyyy'); // "November 12, 2025"
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatDate(date: any, formatString: any): string;
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a Date object or ISO timestamp string to YYYY-MM-DD format
|
|
30
|
+
*
|
|
31
|
+
* WU-1442: Fix date corruption when js-yaml parses YYYY-MM-DD as Date objects
|
|
32
|
+
* Library-First: Uses date-fns for date formatting (no manual parsing)
|
|
33
|
+
*
|
|
34
|
+
* Use case: js-yaml parses `created: 2025-12-04` (unquoted) as a Date object.
|
|
35
|
+
* When yaml.dump() serializes it back, it outputs `2025-12-04T00:00:00.000Z`.
|
|
36
|
+
* This function normalizes Date objects back to YYYY-MM-DD string format.
|
|
37
|
+
*
|
|
38
|
+
* Handles:
|
|
39
|
+
* - Date objects → YYYY-MM-DD string
|
|
40
|
+
* - ISO timestamp strings → YYYY-MM-DD string (date portion)
|
|
41
|
+
* - YYYY-MM-DD strings → preserved as-is
|
|
42
|
+
* - undefined/null → preserved as undefined
|
|
43
|
+
*
|
|
44
|
+
* @param {Date|string|undefined|null} value - Date value to normalize
|
|
45
|
+
* @returns {string|undefined} Date in YYYY-MM-DD format or undefined
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* normalizeToDateString(new Date('2025-12-04')); // '2025-12-04'
|
|
49
|
+
* normalizeToDateString('2025-12-04T00:00:00.000Z'); // '2025-12-04'
|
|
50
|
+
* normalizeToDateString('2025-12-04'); // '2025-12-04'
|
|
51
|
+
* normalizeToDateString(undefined); // undefined
|
|
52
|
+
*/
|
|
53
|
+
export declare function normalizeToDateString(value: any): string;
|
|
54
|
+
/**
|
|
55
|
+
* Normalize various date formats to ISO 8601 datetime (YYYY-MM-DDTHH:mm:ss.sssZ)
|
|
56
|
+
*
|
|
57
|
+
* WU-1337: Auto-repair date fields in WU YAML to consistent format
|
|
58
|
+
* Library-First: Uses date-fns for date handling (no manual parsing)
|
|
59
|
+
*
|
|
60
|
+
* Handles:
|
|
61
|
+
* - ISO date strings (YYYY-MM-DD) → midnight UTC
|
|
62
|
+
* - ISO datetime strings (already valid) → preserved
|
|
63
|
+
* - Unix timestamps (milliseconds) → converted
|
|
64
|
+
* - undefined/null → preserved as undefined
|
|
65
|
+
*
|
|
66
|
+
* @param {string|number|undefined|null} value - Date value to normalize
|
|
67
|
+
* @returns {string|undefined} ISO datetime string or undefined
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* normalizeISODateTime('2025-11-29'); // '2025-11-29T00:00:00.000Z'
|
|
71
|
+
* normalizeISODateTime('2025-11-29T14:30:00.000Z'); // '2025-11-29T14:30:00.000Z'
|
|
72
|
+
* normalizeISODateTime(1732896000000); // '2024-11-29T16:00:00.000Z'
|
|
73
|
+
* normalizeISODateTime(undefined); // undefined
|
|
74
|
+
*/
|
|
75
|
+
export declare function normalizeISODateTime(value: any): string;
|