@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,669 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU Repair Core Module (WU-1826)
|
|
3
|
+
*
|
|
4
|
+
* Unified repair logic for all WU repair operations:
|
|
5
|
+
* - Consistency repair (default mode): detect/repair state inconsistencies
|
|
6
|
+
* - Claim repair (--claim mode): repair missing claim metadata in worktrees
|
|
7
|
+
* - Admin repair (--admin mode): administrative fixes for done WUs
|
|
8
|
+
*
|
|
9
|
+
* This module consolidates logic from:
|
|
10
|
+
* - wu-consistency-checker.mjs (consistency checks)
|
|
11
|
+
* - wu-repair-claim.mjs (claim metadata repair)
|
|
12
|
+
* - wu-admin-repair.mjs (admin fixes)
|
|
13
|
+
* - wu-recovery.mjs (zombie state recovery)
|
|
14
|
+
*
|
|
15
|
+
* @see {@link ../wu-repair.mjs} - Unified CLI interface
|
|
16
|
+
*/
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { existsSync, writeFileSync, appendFileSync, mkdirSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { checkWUConsistency, checkAllWUConsistency, repairWUInconsistency, } from './wu-consistency-checker.js';
|
|
20
|
+
import { readWU, writeWU, parseYAML, stringifyYAML } from './wu-yaml.js';
|
|
21
|
+
import { WU_PATHS } from './wu-paths.js';
|
|
22
|
+
import { WUStateStore, WU_EVENTS_FILE_NAME } from './wu-state-store.js';
|
|
23
|
+
import { getGitForCwd, createGitForPath } from './git-adapter.js';
|
|
24
|
+
import { EXIT_CODES, LOG_PREFIX, EMOJI, WU_STATUS } from './wu-constants.js';
|
|
25
|
+
import { die } from './error-handler.js';
|
|
26
|
+
import { ensureOnMain, ensureMainUpToDate, validateWUIDFormat } from './wu-helpers.js';
|
|
27
|
+
import { withMicroWorktree } from './micro-worktree.js';
|
|
28
|
+
import { validateLaneFormat } from './lane-checker.js';
|
|
29
|
+
import { normalizeToDateString } from './date-utils.js';
|
|
30
|
+
// Re-export for backwards compatibility
|
|
31
|
+
export { checkWUConsistency, checkAllWUConsistency, repairWUInconsistency };
|
|
32
|
+
// Re-export recovery utilities from wu-recovery.mjs
|
|
33
|
+
export { detectZombieState, recoverZombieState, resetWorktreeYAMLForRecovery, getRecoveryMarkerPath, getRecoveryAttemptCount, incrementRecoveryAttempt, clearRecoveryAttempts, shouldEscalateToManualIntervention, MAX_RECOVERY_ATTEMPTS, } from './wu-recovery.js';
|
|
34
|
+
const PREFIX = LOG_PREFIX.REPAIR;
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// CLAIM REPAIR MODE
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Detect worktree path from WU ID using git worktree list
|
|
40
|
+
*
|
|
41
|
+
* @param {string} id - WU ID (e.g., 'WU-1804')
|
|
42
|
+
* @returns {Promise<string|null>} Worktree path or null if not found
|
|
43
|
+
*/
|
|
44
|
+
export async function findWorktreePathForWU(id) {
|
|
45
|
+
try {
|
|
46
|
+
const git = getGitForCwd();
|
|
47
|
+
const worktreeOutput = await git.worktreeList();
|
|
48
|
+
const lines = worktreeOutput.split('\n');
|
|
49
|
+
// Look for worktree with matching WU ID in branch name
|
|
50
|
+
const idLower = id.toLowerCase();
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
// Line format: "worktree <path>" followed by "branch refs/heads/lane/<lane>/<id>"
|
|
53
|
+
if (line.includes(idLower)) {
|
|
54
|
+
const worktreeMatch = line.match(/^worktree\s+(.+)$/);
|
|
55
|
+
if (worktreeMatch) {
|
|
56
|
+
return worktreeMatch[1].trim();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Try porcelain format
|
|
61
|
+
const porcelainOutput = await git.raw(['worktree', 'list', '--porcelain']);
|
|
62
|
+
const entries = porcelainOutput.split('\n\n');
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (entry.toLowerCase().includes(idLower)) {
|
|
65
|
+
const pathMatch = entry.match(/^worktree\s+(.+)$/m);
|
|
66
|
+
if (pathMatch) {
|
|
67
|
+
return pathMatch[1].trim();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Check claim metadata state for a WU worktree
|
|
79
|
+
*
|
|
80
|
+
* @param {string} id - WU ID
|
|
81
|
+
* @param {string} worktreePath - Path to the worktree
|
|
82
|
+
* @returns {Promise<{valid: boolean, errors: string[], yamlStatus: string|null, stateStoreHasClaim: boolean}>}
|
|
83
|
+
*/
|
|
84
|
+
export async function checkClaimMetadata(id, worktreePath) {
|
|
85
|
+
const errors = [];
|
|
86
|
+
let yamlStatus = null;
|
|
87
|
+
let stateStoreHasClaim = false;
|
|
88
|
+
// Check worktree YAML status
|
|
89
|
+
const wuPath = path.join(worktreePath, WU_PATHS.WU(id));
|
|
90
|
+
if (existsSync(wuPath)) {
|
|
91
|
+
try {
|
|
92
|
+
const doc = readWU(wuPath, id);
|
|
93
|
+
yamlStatus = doc.status;
|
|
94
|
+
if (yamlStatus !== WU_STATUS.IN_PROGRESS) {
|
|
95
|
+
errors.push(`WU YAML status is '${yamlStatus}', expected '${WU_STATUS.IN_PROGRESS}'`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
errors.push(`Failed to read WU YAML: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
errors.push(`WU YAML not found at: ${wuPath}`);
|
|
104
|
+
}
|
|
105
|
+
// Check state store
|
|
106
|
+
const stateDir = path.join(worktreePath, '.beacon', 'state');
|
|
107
|
+
const eventsPath = path.join(stateDir, WU_EVENTS_FILE_NAME);
|
|
108
|
+
if (existsSync(eventsPath)) {
|
|
109
|
+
try {
|
|
110
|
+
const store = new WUStateStore(stateDir);
|
|
111
|
+
await store.load();
|
|
112
|
+
const inProgress = store.getByStatus(WU_STATUS.IN_PROGRESS);
|
|
113
|
+
stateStoreHasClaim = inProgress.has(id);
|
|
114
|
+
if (!stateStoreHasClaim) {
|
|
115
|
+
errors.push(`State store does not show ${id} as in_progress`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
errors.push(`Failed to read state store: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
errors.push(`State store not found at: ${eventsPath}`);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
valid: errors.length === 0,
|
|
127
|
+
errors,
|
|
128
|
+
yamlStatus,
|
|
129
|
+
stateStoreHasClaim,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Repair claim metadata in worktree
|
|
134
|
+
*
|
|
135
|
+
* SAFETY: Only modifies files inside the worktree, never main.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} id - WU ID
|
|
138
|
+
* @param {string} worktreePath - Path to the worktree
|
|
139
|
+
* @param {object} checkResult - Result from checkClaimMetadata
|
|
140
|
+
* @returns {Promise<{success: boolean, repaired: string[], errors: string[]}>}
|
|
141
|
+
*/
|
|
142
|
+
export async function repairClaimMetadata(id, worktreePath, checkResult) {
|
|
143
|
+
const repaired = [];
|
|
144
|
+
const errors = [];
|
|
145
|
+
// Read current WU YAML to get lane and title
|
|
146
|
+
const wuPath = path.join(worktreePath, WU_PATHS.WU(id));
|
|
147
|
+
let doc;
|
|
148
|
+
try {
|
|
149
|
+
doc = readWU(wuPath, id);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
repaired: [],
|
|
155
|
+
errors: [`Cannot read WU YAML to repair: ${err.message}`],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const lane = doc.lane || '';
|
|
159
|
+
const title = doc.title || `WU ${id}`;
|
|
160
|
+
// Repair 1: Fix YAML status if needed
|
|
161
|
+
if (checkResult.yamlStatus !== WU_STATUS.IN_PROGRESS) {
|
|
162
|
+
try {
|
|
163
|
+
doc.status = WU_STATUS.IN_PROGRESS;
|
|
164
|
+
// Remove done-state fields that shouldn't be present
|
|
165
|
+
delete doc.locked;
|
|
166
|
+
delete doc.completed_at;
|
|
167
|
+
writeWU(wuPath, doc);
|
|
168
|
+
repaired.push(`WU YAML status set to '${WU_STATUS.IN_PROGRESS}'`);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
errors.push(`Failed to update WU YAML status: ${err.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Repair 2: Add claim event to state store if missing
|
|
175
|
+
if (!checkResult.stateStoreHasClaim) {
|
|
176
|
+
try {
|
|
177
|
+
const stateDir = path.join(worktreePath, '.beacon', 'state');
|
|
178
|
+
const eventsPath = path.join(stateDir, WU_EVENTS_FILE_NAME);
|
|
179
|
+
// Ensure directory exists
|
|
180
|
+
mkdirSync(stateDir, { recursive: true });
|
|
181
|
+
// Create claim event
|
|
182
|
+
const claimEvent = {
|
|
183
|
+
type: 'claim',
|
|
184
|
+
wuId: id,
|
|
185
|
+
lane: lane,
|
|
186
|
+
title: title,
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
};
|
|
189
|
+
// Append to events file
|
|
190
|
+
const line = `${JSON.stringify(claimEvent)}\n`;
|
|
191
|
+
appendFileSync(eventsPath, line, 'utf-8');
|
|
192
|
+
repaired.push(`Claim event added to state store`);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
errors.push(`Failed to add claim event to state store: ${err.message}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Stage and commit the repairs
|
|
199
|
+
if (repaired.length > 0) {
|
|
200
|
+
try {
|
|
201
|
+
const gitWorktree = createGitForPath(worktreePath);
|
|
202
|
+
// Stage repaired files
|
|
203
|
+
const filesToStage = [wuPath];
|
|
204
|
+
const stateDir = path.join(worktreePath, '.beacon', 'state');
|
|
205
|
+
const eventsPath = path.join(stateDir, WU_EVENTS_FILE_NAME);
|
|
206
|
+
if (existsSync(eventsPath)) {
|
|
207
|
+
filesToStage.push(eventsPath);
|
|
208
|
+
}
|
|
209
|
+
await gitWorktree.add(filesToStage);
|
|
210
|
+
// Commit with repair message
|
|
211
|
+
const commitMsg = `wu(${id.toLowerCase()}): repair-claim - restore missing claim metadata`;
|
|
212
|
+
await gitWorktree.commit(commitMsg);
|
|
213
|
+
repaired.push(`Committed repair: ${commitMsg}`);
|
|
214
|
+
// Push to remote
|
|
215
|
+
const currentBranch = await gitWorktree.getCurrentBranch();
|
|
216
|
+
await gitWorktree.push('origin', currentBranch);
|
|
217
|
+
repaired.push(`Pushed to origin/${currentBranch}`);
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
// Don't fail the entire repair if commit/push fails
|
|
221
|
+
errors.push(`Git operations failed: ${err.message}. Manual commit may be required.`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
success: errors.length === 0,
|
|
226
|
+
repaired,
|
|
227
|
+
errors,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Run claim repair mode
|
|
232
|
+
*
|
|
233
|
+
* @param {object} options - CLI options
|
|
234
|
+
* @param {string} options.id - WU ID
|
|
235
|
+
* @param {boolean} [options.check] - Check only, no repair
|
|
236
|
+
* @param {string} [options.worktree] - Override worktree path
|
|
237
|
+
* @returns {Promise<{success: boolean, exitCode: number}>}
|
|
238
|
+
*/
|
|
239
|
+
export async function runClaimRepairMode(options) {
|
|
240
|
+
const { id, check, worktree } = options;
|
|
241
|
+
console.log(`${PREFIX} Checking claim metadata for ${id}...`);
|
|
242
|
+
// Find worktree path
|
|
243
|
+
let worktreePath = worktree;
|
|
244
|
+
if (!worktreePath) {
|
|
245
|
+
worktreePath = await findWorktreePathForWU(id);
|
|
246
|
+
}
|
|
247
|
+
if (!worktreePath) {
|
|
248
|
+
console.error(`${PREFIX} Error: Could not find worktree for ${id}`);
|
|
249
|
+
console.error(`${PREFIX} Ensure the worktree exists, or specify with --worktree <path>`);
|
|
250
|
+
return { success: false, exitCode: EXIT_CODES.FAILURE };
|
|
251
|
+
}
|
|
252
|
+
if (!existsSync(worktreePath)) {
|
|
253
|
+
console.error(`${PREFIX} Error: Worktree path does not exist: ${worktreePath}`);
|
|
254
|
+
return { success: false, exitCode: EXIT_CODES.FAILURE };
|
|
255
|
+
}
|
|
256
|
+
console.log(`${PREFIX} Found worktree: ${worktreePath}`);
|
|
257
|
+
// Check claim metadata state
|
|
258
|
+
const checkResult = await checkClaimMetadata(id, worktreePath);
|
|
259
|
+
if (checkResult.valid) {
|
|
260
|
+
console.log(`${PREFIX} ${EMOJI.SUCCESS} Claim metadata is valid for ${id}`);
|
|
261
|
+
console.log(`${PREFIX} - YAML status: ${checkResult.yamlStatus}`);
|
|
262
|
+
console.log(`${PREFIX} - State store: has claim event`);
|
|
263
|
+
return { success: true, exitCode: EXIT_CODES.SUCCESS };
|
|
264
|
+
}
|
|
265
|
+
// Report issues
|
|
266
|
+
console.log(`${PREFIX} ${EMOJI.WARNING} Found ${checkResult.errors.length} issue(s):`);
|
|
267
|
+
for (const error of checkResult.errors) {
|
|
268
|
+
console.log(`${PREFIX} - ${error}`);
|
|
269
|
+
}
|
|
270
|
+
// Check-only mode
|
|
271
|
+
if (check) {
|
|
272
|
+
console.log(`${PREFIX} --check mode: no changes made`);
|
|
273
|
+
return { success: false, exitCode: EXIT_CODES.ERROR };
|
|
274
|
+
}
|
|
275
|
+
// Repair
|
|
276
|
+
console.log(`${PREFIX} Repairing claim metadata...`);
|
|
277
|
+
const repairResult = await repairClaimMetadata(id, worktreePath, checkResult);
|
|
278
|
+
// Report repairs
|
|
279
|
+
if (repairResult.repaired.length > 0) {
|
|
280
|
+
console.log(`${PREFIX} ${EMOJI.SUCCESS} Repairs applied:`);
|
|
281
|
+
for (const repair of repairResult.repaired) {
|
|
282
|
+
console.log(`${PREFIX} - ${repair}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Report repair errors
|
|
286
|
+
if (repairResult.errors.length > 0) {
|
|
287
|
+
console.log(`${PREFIX} ${EMOJI.WARNING} Repair warnings:`);
|
|
288
|
+
for (const error of repairResult.errors) {
|
|
289
|
+
console.log(`${PREFIX} - ${error}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (repairResult.success) {
|
|
293
|
+
console.log(`\n${PREFIX} ${EMOJI.SUCCESS} Repair complete!`);
|
|
294
|
+
console.log(`${PREFIX} You can now retry: pnpm wu:done --id ${id}`);
|
|
295
|
+
return { success: true, exitCode: EXIT_CODES.SUCCESS };
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
console.error(`\n${PREFIX} ${EMOJI.FAILURE} Repair failed. Manual intervention required.`);
|
|
299
|
+
return { success: false, exitCode: EXIT_CODES.FAILURE };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// ADMIN REPAIR MODE
|
|
304
|
+
// ============================================================================
|
|
305
|
+
const ADMIN_PREFIX = '[wu:admin-repair]';
|
|
306
|
+
const OPERATION_NAME = 'wu-admin-repair';
|
|
307
|
+
const VALID_STATUSES = Object.values(WU_STATUS);
|
|
308
|
+
/**
|
|
309
|
+
* Validate status value against WU_STATUS enum
|
|
310
|
+
*
|
|
311
|
+
* @param {string} status - Status value to validate
|
|
312
|
+
*/
|
|
313
|
+
function validateStatus(status) {
|
|
314
|
+
if (!VALID_STATUSES.includes(status)) {
|
|
315
|
+
die(`Invalid status: '${status}'\n\n` + `Valid statuses: ${VALID_STATUSES.join(', ')}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Check WU exists (does NOT block on done status unlike wu:edit)
|
|
320
|
+
*
|
|
321
|
+
* @param {string} id - WU ID
|
|
322
|
+
* @returns {object} WU object
|
|
323
|
+
*/
|
|
324
|
+
function validateWUExists(id) {
|
|
325
|
+
const wuPath = WU_PATHS.WU(id);
|
|
326
|
+
if (!existsSync(wuPath)) {
|
|
327
|
+
die(`WU ${id} not found at ${wuPath}\n\nEnsure the WU exists and you're in the repo root.`);
|
|
328
|
+
}
|
|
329
|
+
const content = readFileSync(wuPath, { encoding: 'utf-8' });
|
|
330
|
+
const wu = parseYAML(content);
|
|
331
|
+
// Admin repair ALLOWS editing done WUs (key difference from wu:edit)
|
|
332
|
+
return wu;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Ensure working tree is clean
|
|
336
|
+
*/
|
|
337
|
+
async function ensureCleanWorkingTree() {
|
|
338
|
+
const status = await getGitForCwd().getStatus();
|
|
339
|
+
if (status.trim()) {
|
|
340
|
+
die(`Working tree is not clean. Cannot run admin-repair.\n\nUncommitted changes:\n${status}\n\nCommit or stash changes before running admin-repair:\n git add . && git commit -m "..."\n`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Generate audit trail entry for repairs
|
|
345
|
+
*
|
|
346
|
+
* @param {string[]} changes - List of changes made
|
|
347
|
+
* @returns {string} Audit trail entry
|
|
348
|
+
*/
|
|
349
|
+
function generateAuditEntry(changes) {
|
|
350
|
+
const date = new Date().toISOString().split('T')[0];
|
|
351
|
+
return `\n\n[ADMIN-REPAIR ${date}]: ${changes.join('; ')}`;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Normalize date fields in WU object to prevent date corruption
|
|
355
|
+
*
|
|
356
|
+
* @param {object} wu - WU object from yaml.load()
|
|
357
|
+
* @returns {object} WU object with normalized date fields
|
|
358
|
+
*/
|
|
359
|
+
function normalizeWUDates(wu) {
|
|
360
|
+
if (wu.created !== undefined) {
|
|
361
|
+
wu.created = normalizeToDateString(wu.created);
|
|
362
|
+
}
|
|
363
|
+
if (wu.completed !== undefined) {
|
|
364
|
+
wu.completed = normalizeToDateString(wu.completed);
|
|
365
|
+
}
|
|
366
|
+
return wu;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Apply lane repair and return changes
|
|
370
|
+
*/
|
|
371
|
+
function applyLaneRepair(wu, updated, opts, changes) {
|
|
372
|
+
if (!opts.lane)
|
|
373
|
+
return;
|
|
374
|
+
validateLaneFormat(opts.lane);
|
|
375
|
+
if (wu.lane === opts.lane)
|
|
376
|
+
return;
|
|
377
|
+
changes.push(`lane changed from '${wu.lane}' to '${opts.lane}'`);
|
|
378
|
+
updated.lane = opts.lane;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Apply status repair and return changes
|
|
382
|
+
*/
|
|
383
|
+
function applyStatusRepair(wu, updated, opts, changes) {
|
|
384
|
+
if (!opts.status)
|
|
385
|
+
return;
|
|
386
|
+
validateStatus(opts.status);
|
|
387
|
+
if (wu.status === opts.status)
|
|
388
|
+
return;
|
|
389
|
+
changes.push(`status changed from '${wu.status}' to '${opts.status}'`);
|
|
390
|
+
updated.status = opts.status;
|
|
391
|
+
// Update locked flag based on status
|
|
392
|
+
if (opts.status === WU_STATUS.DONE) {
|
|
393
|
+
updated.locked = true;
|
|
394
|
+
}
|
|
395
|
+
else if (wu.locked === true) {
|
|
396
|
+
// Unlock if moving away from done
|
|
397
|
+
updated.locked = false;
|
|
398
|
+
changes.push('locked changed from true to false');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Apply initiative repair and return changes
|
|
403
|
+
*/
|
|
404
|
+
function applyInitiativeRepair(wu, updated, opts, changes) {
|
|
405
|
+
if (!opts.initiative || wu.initiative === opts.initiative)
|
|
406
|
+
return;
|
|
407
|
+
const oldVal = wu.initiative || '(none)';
|
|
408
|
+
changes.push(`initiative changed from '${oldVal}' to '${opts.initiative}'`);
|
|
409
|
+
updated.initiative = opts.initiative;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Apply notes update and return changes
|
|
413
|
+
*/
|
|
414
|
+
function applyNotesUpdate(wu, updated, opts, changes) {
|
|
415
|
+
if (!opts.notes)
|
|
416
|
+
return;
|
|
417
|
+
const existingNotes = wu.notes || '';
|
|
418
|
+
changes.push(`notes updated`);
|
|
419
|
+
updated.notes = `${existingNotes}\n\n${opts.notes}`;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Append audit trail to notes based on changes made
|
|
423
|
+
*/
|
|
424
|
+
function appendAuditTrail(updated, opts, changes) {
|
|
425
|
+
if (changes.length === 0)
|
|
426
|
+
return;
|
|
427
|
+
if (opts.notes) {
|
|
428
|
+
// If notes were explicitly provided, add audit for non-notes changes only
|
|
429
|
+
const nonNotesChanges = changes.filter((c) => c !== 'notes updated');
|
|
430
|
+
if (nonNotesChanges.length > 0) {
|
|
431
|
+
updated.notes = `${updated.notes}${generateAuditEntry(nonNotesChanges)}`;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
// All changes get audit trail
|
|
436
|
+
const existingNotes = updated.notes || '';
|
|
437
|
+
updated.notes = `${existingNotes}${generateAuditEntry(changes)}`;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Apply repairs to WU and track changes for audit
|
|
442
|
+
*
|
|
443
|
+
* @param {object} wu - Original WU object
|
|
444
|
+
* @param {object} opts - CLI options
|
|
445
|
+
* @returns {{ updated: object, changes: string[] }} Updated WU and list of changes
|
|
446
|
+
*/
|
|
447
|
+
export function applyAdminRepairs(wu, opts) {
|
|
448
|
+
const updated = { ...wu };
|
|
449
|
+
const changes = [];
|
|
450
|
+
applyLaneRepair(wu, updated, opts, changes);
|
|
451
|
+
applyStatusRepair(wu, updated, opts, changes);
|
|
452
|
+
applyInitiativeRepair(wu, updated, opts, changes);
|
|
453
|
+
applyNotesUpdate(wu, updated, opts, changes);
|
|
454
|
+
appendAuditTrail(updated, opts, changes);
|
|
455
|
+
return { updated, changes };
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Generate commit message for admin repair
|
|
459
|
+
*
|
|
460
|
+
* @param {string} id - WU ID
|
|
461
|
+
* @param {string[]} changes - List of changes made
|
|
462
|
+
* @returns {string} Commit message
|
|
463
|
+
*/
|
|
464
|
+
function generateAdminCommitMessage(id, changes) {
|
|
465
|
+
// Extract field names from changes
|
|
466
|
+
const fields = changes.map((c) => c.split(' ')[0]).filter((f) => f !== 'notes');
|
|
467
|
+
const uniqueFields = [...new Set(fields)];
|
|
468
|
+
const fieldSummary = uniqueFields.length > 0 ? uniqueFields.join(', ') : 'notes';
|
|
469
|
+
return `fix(${id.toLowerCase()}): admin-repair ${fieldSummary}`;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Run admin repair mode
|
|
473
|
+
*
|
|
474
|
+
* @param {object} options - CLI options
|
|
475
|
+
* @param {string} options.id - WU ID
|
|
476
|
+
* @param {string} [options.lane] - New lane assignment
|
|
477
|
+
* @param {string} [options.status] - New status value
|
|
478
|
+
* @param {string} [options.notes] - Notes to add
|
|
479
|
+
* @param {string} [options.initiative] - New initiative reference
|
|
480
|
+
* @returns {Promise<{success: boolean, exitCode: number}>}
|
|
481
|
+
*/
|
|
482
|
+
export async function runAdminRepairMode(options) {
|
|
483
|
+
const { id } = options;
|
|
484
|
+
console.log(`${ADMIN_PREFIX} Starting admin repair for ${id}`);
|
|
485
|
+
// Validate inputs
|
|
486
|
+
validateWUIDFormat(id);
|
|
487
|
+
// Check we have at least one field to repair BEFORE checking WU existence
|
|
488
|
+
// (no point in looking up the WU if no repair fields are provided)
|
|
489
|
+
const hasRepairs = options.lane || options.status || options.notes || options.initiative;
|
|
490
|
+
if (!hasRepairs) {
|
|
491
|
+
die('No repairs specified.\n\n' +
|
|
492
|
+
'Provide at least one of:\n' +
|
|
493
|
+
' --lane <lane> Fix lane assignment (e.g., "Operations: Tooling")\n' +
|
|
494
|
+
' --status <status> Fix status value (ready, in_progress, blocked, done, cancelled)\n' +
|
|
495
|
+
' --notes <text> Add repair notes (appends with audit trail)\n' +
|
|
496
|
+
' --initiative <ref> Fix initiative reference (e.g., INIT-001)\n\n' +
|
|
497
|
+
'Example:\n' +
|
|
498
|
+
' pnpm wu:repair --admin --id WU-123 --lane "Operations: Tooling"');
|
|
499
|
+
}
|
|
500
|
+
// Now validate WU exists
|
|
501
|
+
const originalWU = validateWUExists(id);
|
|
502
|
+
// Apply repairs
|
|
503
|
+
const { updated: updatedWU, changes } = applyAdminRepairs(originalWU, options);
|
|
504
|
+
// Check if any actual changes were made
|
|
505
|
+
if (changes.length === 0) {
|
|
506
|
+
console.log(`${ADMIN_PREFIX} No changes needed - WU already has specified values`);
|
|
507
|
+
return { success: true, exitCode: EXIT_CODES.SUCCESS };
|
|
508
|
+
}
|
|
509
|
+
console.log(`${ADMIN_PREFIX} Changes to apply:`);
|
|
510
|
+
for (const change of changes) {
|
|
511
|
+
console.log(`${ADMIN_PREFIX} • ${change}`);
|
|
512
|
+
}
|
|
513
|
+
// Pre-flight checks for micro-worktree operation
|
|
514
|
+
await ensureOnMain(getGitForCwd());
|
|
515
|
+
await ensureCleanWorkingTree();
|
|
516
|
+
await ensureMainUpToDate(getGitForCwd(), 'wu:repair --admin');
|
|
517
|
+
console.log(`${ADMIN_PREFIX} Applying repairs via micro-worktree...`);
|
|
518
|
+
try {
|
|
519
|
+
await withMicroWorktree({
|
|
520
|
+
operation: OPERATION_NAME,
|
|
521
|
+
id: id,
|
|
522
|
+
logPrefix: ADMIN_PREFIX,
|
|
523
|
+
execute: async ({ worktreePath }) => {
|
|
524
|
+
// Write updated WU to micro-worktree
|
|
525
|
+
const wuPath = path.join(worktreePath, WU_PATHS.WU(id));
|
|
526
|
+
// Normalize dates before dumping to prevent ISO timestamp corruption
|
|
527
|
+
normalizeWUDates(updatedWU);
|
|
528
|
+
const yamlContent = stringifyYAML(updatedWU);
|
|
529
|
+
writeFileSync(wuPath, yamlContent, { encoding: 'utf-8' });
|
|
530
|
+
console.log(`${ADMIN_PREFIX} ${EMOJI.SUCCESS} Updated ${id}.yaml in micro-worktree`);
|
|
531
|
+
return {
|
|
532
|
+
commitMessage: generateAdminCommitMessage(id, changes),
|
|
533
|
+
files: [WU_PATHS.WU(id)],
|
|
534
|
+
};
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
console.log(`${ADMIN_PREFIX} ${EMOJI.SUCCESS} Successfully repaired ${id}`);
|
|
538
|
+
console.log(`${ADMIN_PREFIX} Changes pushed to origin/main`);
|
|
539
|
+
console.log(`${ADMIN_PREFIX} Audit trail logged to WU notes`);
|
|
540
|
+
return { success: true, exitCode: EXIT_CODES.SUCCESS };
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
console.error(`${ADMIN_PREFIX} ${EMOJI.FAILURE} ${err.message}`);
|
|
544
|
+
return { success: false, exitCode: EXIT_CODES.ERROR };
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// ============================================================================
|
|
548
|
+
// CONSISTENCY REPAIR MODE (default)
|
|
549
|
+
// ============================================================================
|
|
550
|
+
/**
|
|
551
|
+
* Format error for display
|
|
552
|
+
* @param {object} error - Error object
|
|
553
|
+
* @returns {string} Formatted error string
|
|
554
|
+
*/
|
|
555
|
+
function formatError(error) {
|
|
556
|
+
return ` - ${error.type}: ${error.description}`;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Print consistency report
|
|
560
|
+
* @param {object} report - Consistency report
|
|
561
|
+
*/
|
|
562
|
+
function printReport(report) {
|
|
563
|
+
if (report.valid) {
|
|
564
|
+
console.log(`${PREFIX} ${report.id}: No inconsistencies detected`);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
console.log(`${PREFIX} ${report.id}: ${report.errors.length} inconsistency(ies) found`);
|
|
568
|
+
for (const error of report.errors) {
|
|
569
|
+
console.log(formatError(error));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Repair a single WU for consistency issues
|
|
574
|
+
*
|
|
575
|
+
* @param {string} id - WU ID
|
|
576
|
+
* @param {object} options - CLI options
|
|
577
|
+
* @returns {Promise<{success: boolean, repaired: number, failed: number}>}
|
|
578
|
+
*/
|
|
579
|
+
export async function repairSingleWU(id, options) {
|
|
580
|
+
console.log(`${PREFIX} Checking ${id}...`);
|
|
581
|
+
const report = await checkWUConsistency(id);
|
|
582
|
+
if (report.valid) {
|
|
583
|
+
console.log(`${PREFIX} ${id}: No inconsistencies detected`);
|
|
584
|
+
return { success: true, repaired: 0, failed: 0 };
|
|
585
|
+
}
|
|
586
|
+
printReport(report);
|
|
587
|
+
if (options.check) {
|
|
588
|
+
return { success: false, repaired: 0, failed: report.errors.length };
|
|
589
|
+
}
|
|
590
|
+
console.log(`${PREFIX} Repairing ${id}...`);
|
|
591
|
+
const result = await repairWUInconsistency(report);
|
|
592
|
+
if (result.failed > 0) {
|
|
593
|
+
console.error(`${PREFIX} Repair partially failed: ${result.repaired} repaired, ${result.failed} failed`);
|
|
594
|
+
return { success: false, repaired: result.repaired, failed: result.failed };
|
|
595
|
+
}
|
|
596
|
+
console.log(`${PREFIX} Successfully repaired ${result.repaired} issue(s)`);
|
|
597
|
+
return { success: true, repaired: result.repaired, failed: 0 };
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Repair all WUs for consistency issues
|
|
601
|
+
*
|
|
602
|
+
* @param {object} options - CLI options
|
|
603
|
+
* @returns {Promise<{success: boolean, repaired: number, failed: number}>}
|
|
604
|
+
*/
|
|
605
|
+
export async function repairAllWUs(options = {}) {
|
|
606
|
+
console.log(`${PREFIX} Checking all WUs...`);
|
|
607
|
+
const report = await checkAllWUConsistency();
|
|
608
|
+
if (report.valid) {
|
|
609
|
+
console.log(`${PREFIX} All ${report.checked} WUs are consistent`);
|
|
610
|
+
return { success: true, repaired: 0, failed: 0 };
|
|
611
|
+
}
|
|
612
|
+
console.log(`${PREFIX} Found ${report.errors.length} inconsistency issue(s) out of ${report.checked} WUs checked`);
|
|
613
|
+
console.log();
|
|
614
|
+
// Print all errors
|
|
615
|
+
for (const error of report.errors) {
|
|
616
|
+
console.log(` - ${error.type}: ${error.description}`);
|
|
617
|
+
}
|
|
618
|
+
console.log();
|
|
619
|
+
if (options.dryRun) {
|
|
620
|
+
return { success: false, repaired: 0, failed: report.errors.length };
|
|
621
|
+
}
|
|
622
|
+
// Repair the inconsistencies
|
|
623
|
+
console.log(`${PREFIX} Repairing inconsistencies...`);
|
|
624
|
+
const result = await repairWUInconsistency(report);
|
|
625
|
+
if (result.failed > 0) {
|
|
626
|
+
console.error(`${PREFIX} Partial failure - ${result.repaired} repaired, ${result.failed} failed`);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
console.log(`${PREFIX} Repaired ${result.repaired} issue(s)`);
|
|
630
|
+
}
|
|
631
|
+
console.log();
|
|
632
|
+
console.log(`${PREFIX} Summary: ${result.repaired} repaired, ${result.failed} failed`);
|
|
633
|
+
return { success: result.failed === 0, repaired: result.repaired, failed: result.failed };
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Run consistency repair mode (default)
|
|
637
|
+
*
|
|
638
|
+
* @param {object} options - CLI options
|
|
639
|
+
* @param {string} [options.id] - WU ID to check/repair
|
|
640
|
+
* @param {boolean} [options.all] - Check/repair all WUs
|
|
641
|
+
* @param {boolean} [options.check] - Audit only, no changes
|
|
642
|
+
* @returns {Promise<{success: boolean, exitCode: number}>}
|
|
643
|
+
*/
|
|
644
|
+
export async function runConsistencyRepairMode(options) {
|
|
645
|
+
let result;
|
|
646
|
+
try {
|
|
647
|
+
if (options.all) {
|
|
648
|
+
result = await repairAllWUs(options);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
result = await repairSingleWU(options.id, options);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
catch (error) {
|
|
655
|
+
console.error(`${PREFIX} Fatal error: ${error.message}`);
|
|
656
|
+
return { success: false, exitCode: EXIT_CODES.FAILURE };
|
|
657
|
+
}
|
|
658
|
+
// Exit codes:
|
|
659
|
+
// 0: Success (no issues or all repaired)
|
|
660
|
+
// 1: Issues detected (--check mode)
|
|
661
|
+
// 2: Repair failed
|
|
662
|
+
if (!result.success) {
|
|
663
|
+
return {
|
|
664
|
+
success: false,
|
|
665
|
+
exitCode: options.check ? EXIT_CODES.ERROR : EXIT_CODES.FAILURE,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
return { success: true, exitCode: EXIT_CODES.SUCCESS };
|
|
669
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file wu-schema-normalization.mjs
|
|
3
|
+
* @description Schema normalization for legacy WU YAML formats (WU-2004)
|
|
4
|
+
*
|
|
5
|
+
* Handles migration of legacy fields to current schema:
|
|
6
|
+
* - summary → description
|
|
7
|
+
* - string risks → array
|
|
8
|
+
* - test_paths → tests
|
|
9
|
+
* - ISO/Date created → YYYY-MM-DD
|
|
10
|
+
* - Removes deprecated fields: owner, context, spec_refs
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Normalize a WU schema object, converting legacy formats to current schema.
|
|
14
|
+
* @param {Object} wu - Raw WU object (from YAML parse)
|
|
15
|
+
* @returns {Object} Normalized WU object
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeWUSchema(wu: any): any;
|