@lumenflow/cli 1.5.0 → 2.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/dist/__tests__/backlog-prune.test.js +478 -0
- package/dist/__tests__/deps-operations.test.js +206 -0
- package/dist/__tests__/file-operations.test.js +906 -0
- package/dist/__tests__/git-operations.test.js +668 -0
- package/dist/__tests__/guards-validation.test.js +416 -0
- package/dist/__tests__/init-plan.test.js +340 -0
- package/dist/__tests__/lumenflow-upgrade.test.js +107 -0
- package/dist/__tests__/metrics-cli.test.js +619 -0
- package/dist/__tests__/rotate-progress.test.js +127 -0
- package/dist/__tests__/session-coordinator.test.js +109 -0
- package/dist/__tests__/state-bootstrap.test.js +432 -0
- package/dist/__tests__/trace-gen.test.js +115 -0
- package/dist/backlog-prune.js +299 -0
- package/dist/deps-add.js +215 -0
- package/dist/deps-remove.js +94 -0
- package/dist/file-delete.js +236 -0
- package/dist/file-edit.js +247 -0
- package/dist/file-read.js +197 -0
- package/dist/file-write.js +220 -0
- package/dist/git-branch.js +187 -0
- package/dist/git-diff.js +177 -0
- package/dist/git-log.js +230 -0
- package/dist/git-status.js +208 -0
- package/dist/guard-locked.js +169 -0
- package/dist/guard-main-branch.js +202 -0
- package/dist/guard-worktree-commit.js +160 -0
- package/dist/init-plan.js +337 -0
- package/dist/lumenflow-upgrade.js +178 -0
- package/dist/metrics-cli.js +433 -0
- package/dist/rotate-progress.js +247 -0
- package/dist/session-coordinator.js +300 -0
- package/dist/state-bootstrap.js +307 -0
- package/dist/trace-gen.js +331 -0
- package/dist/validate-agent-skills.js +218 -0
- package/dist/validate-agent-sync.js +148 -0
- package/dist/validate-backlog-sync.js +152 -0
- package/dist/validate-skills-spec.js +206 -0
- package/dist/validate.js +230 -0
- package/dist/wu-recover.js +329 -0
- package/dist/wu-status.js +188 -0
- package/package.json +37 -7
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* WU Recovery Command
|
|
4
|
+
*
|
|
5
|
+
* WU-1090: Context-aware state machine for WU lifecycle commands
|
|
6
|
+
*
|
|
7
|
+
* Analyzes WU state inconsistencies and offers recovery actions:
|
|
8
|
+
* - resume: Reconcile state and continue working (preserves work)
|
|
9
|
+
* - reset: Discard worktree and reset WU to ready
|
|
10
|
+
* - nuke: Remove all artifacts completely (requires --force)
|
|
11
|
+
* - cleanup: Remove leftover worktree for done WUs
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* pnpm wu:recover --id WU-123 # Analyze issues
|
|
15
|
+
* pnpm wu:recover --id WU-123 --action resume # Apply fix
|
|
16
|
+
* pnpm wu:recover --id WU-123 --action nuke --force # Destructive
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
19
|
+
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
20
|
+
import { computeContext } from '@lumenflow/core/dist/context/index.js';
|
|
21
|
+
import { analyzeRecovery, } from '@lumenflow/core/dist/recovery/recovery-analyzer.js';
|
|
22
|
+
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
23
|
+
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
24
|
+
import { readWU, writeWU } from '@lumenflow/core/dist/wu-yaml.js';
|
|
25
|
+
import { CONTEXT_VALIDATION, EMOJI, WU_STATUS, DEFAULTS, toKebab, } from '@lumenflow/core/dist/wu-constants.js';
|
|
26
|
+
import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
const { RECOVERY_ACTIONS } = CONTEXT_VALIDATION;
|
|
29
|
+
const LOG_PREFIX = '[wu:recover]';
|
|
30
|
+
/**
|
|
31
|
+
* Valid recovery action types
|
|
32
|
+
*/
|
|
33
|
+
const VALID_ACTIONS = [
|
|
34
|
+
RECOVERY_ACTIONS.RESUME,
|
|
35
|
+
RECOVERY_ACTIONS.RESET,
|
|
36
|
+
RECOVERY_ACTIONS.NUKE,
|
|
37
|
+
RECOVERY_ACTIONS.CLEANUP,
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Check if action requires --force flag
|
|
41
|
+
*/
|
|
42
|
+
export function requiresForceFlag(action) {
|
|
43
|
+
return action === RECOVERY_ACTIONS.NUKE;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validate recovery action
|
|
47
|
+
*/
|
|
48
|
+
export function validateRecoveryAction(action) {
|
|
49
|
+
if (VALID_ACTIONS.includes(action)) {
|
|
50
|
+
return { valid: true };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
valid: false,
|
|
54
|
+
error: `Invalid action '${action}'. Valid actions: ${VALID_ACTIONS.join(', ')}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Format recovery analysis output
|
|
59
|
+
*/
|
|
60
|
+
export function formatRecoveryOutput(analysis) {
|
|
61
|
+
const lines = [];
|
|
62
|
+
lines.push(`## Recovery Analysis for ${analysis.wuId || 'unknown'}`);
|
|
63
|
+
lines.push('');
|
|
64
|
+
if (!analysis.hasIssues) {
|
|
65
|
+
lines.push(`${EMOJI.SUCCESS} No issues found - WU state is healthy`);
|
|
66
|
+
return lines.join('\n');
|
|
67
|
+
}
|
|
68
|
+
// Issues section
|
|
69
|
+
lines.push('### Issues Detected');
|
|
70
|
+
for (const issue of analysis.issues) {
|
|
71
|
+
lines.push(` ${EMOJI.FAILURE} ${issue.code}: ${issue.description}`);
|
|
72
|
+
}
|
|
73
|
+
lines.push('');
|
|
74
|
+
// Actions section
|
|
75
|
+
if (analysis.actions.length > 0) {
|
|
76
|
+
lines.push('### Available Recovery Actions');
|
|
77
|
+
for (const action of analysis.actions) {
|
|
78
|
+
lines.push(` **${action.type}**: ${action.description}`);
|
|
79
|
+
lines.push(` Command: ${action.command}`);
|
|
80
|
+
if (action.warning) {
|
|
81
|
+
lines.push(` ${EMOJI.WARNING} Warning: ${action.warning}`);
|
|
82
|
+
}
|
|
83
|
+
if (action.requiresForce) {
|
|
84
|
+
lines.push(` Requires: --force flag`);
|
|
85
|
+
}
|
|
86
|
+
lines.push('');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get exit code for recovery command
|
|
93
|
+
*/
|
|
94
|
+
export function getRecoveryExitCode(analysis, actionFailed) {
|
|
95
|
+
if (actionFailed) {
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get expected worktree path for a WU
|
|
102
|
+
*/
|
|
103
|
+
function getWorktreePath(wuId, lane) {
|
|
104
|
+
const laneKebab = toKebab(lane);
|
|
105
|
+
const wuIdLower = wuId.toLowerCase();
|
|
106
|
+
return join(process.cwd(), DEFAULTS.WORKTREES_DIR, `${laneKebab}-${wuIdLower}`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Execute resume action - reconcile state and continue
|
|
110
|
+
*/
|
|
111
|
+
async function executeResume(wuId) {
|
|
112
|
+
console.log(`${LOG_PREFIX} Executing resume action for ${wuId}...`);
|
|
113
|
+
const wuPath = WU_PATHS.WU(wuId);
|
|
114
|
+
if (!existsSync(wuPath)) {
|
|
115
|
+
console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} WU file not found: ${wuPath}`);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const doc = readWU(wuPath, wuId);
|
|
119
|
+
// Update status to in_progress if it was ready
|
|
120
|
+
if (doc.status === WU_STATUS.READY) {
|
|
121
|
+
doc.status = WU_STATUS.IN_PROGRESS;
|
|
122
|
+
writeWU(wuPath, doc);
|
|
123
|
+
console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Updated ${wuId} status to in_progress`);
|
|
124
|
+
}
|
|
125
|
+
console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Resume completed - you can continue working in the worktree`);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Execute reset action - discard worktree and reset to ready
|
|
130
|
+
*/
|
|
131
|
+
async function executeReset(wuId) {
|
|
132
|
+
console.log(`${LOG_PREFIX} Executing reset action for ${wuId}...`);
|
|
133
|
+
const wuPath = WU_PATHS.WU(wuId);
|
|
134
|
+
if (!existsSync(wuPath)) {
|
|
135
|
+
console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} WU file not found: ${wuPath}`);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const doc = readWU(wuPath, wuId);
|
|
139
|
+
const worktreePath = getWorktreePath(wuId, doc.lane || '');
|
|
140
|
+
// Remove worktree if exists
|
|
141
|
+
// WU-1097: Use worktreeRemove() instead of deprecated run() with shell strings
|
|
142
|
+
// This properly handles paths with spaces and special characters
|
|
143
|
+
if (existsSync(worktreePath)) {
|
|
144
|
+
try {
|
|
145
|
+
const git = getGitForCwd();
|
|
146
|
+
await git.worktreeRemove(worktreePath, { force: true });
|
|
147
|
+
console.log(`${LOG_PREFIX} Removed worktree: ${worktreePath}`);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
// Try manual removal if git command fails
|
|
151
|
+
try {
|
|
152
|
+
rmSync(worktreePath, { recursive: true, force: true });
|
|
153
|
+
console.log(`${LOG_PREFIX} Manually removed worktree directory: ${worktreePath}`);
|
|
154
|
+
}
|
|
155
|
+
catch (rmError) {
|
|
156
|
+
console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} Failed to remove worktree: ${rmError.message}`);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Reset WU status to ready
|
|
162
|
+
doc.status = WU_STATUS.READY;
|
|
163
|
+
delete doc.worktree_path;
|
|
164
|
+
delete doc.claimed_at;
|
|
165
|
+
delete doc.session_id;
|
|
166
|
+
writeWU(wuPath, doc);
|
|
167
|
+
console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Reset completed - ${wuId} is now ready for re-claiming`);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Execute nuke action - remove all artifacts completely
|
|
172
|
+
*/
|
|
173
|
+
async function executeNuke(wuId) {
|
|
174
|
+
console.log(`${LOG_PREFIX} Executing nuke action for ${wuId}...`);
|
|
175
|
+
const wuPath = WU_PATHS.WU(wuId);
|
|
176
|
+
if (!existsSync(wuPath)) {
|
|
177
|
+
console.log(`${LOG_PREFIX} WU file does not exist: ${wuPath}`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const doc = readWU(wuPath, wuId);
|
|
181
|
+
const worktreePath = getWorktreePath(wuId, doc.lane || '');
|
|
182
|
+
// Remove worktree if exists
|
|
183
|
+
// WU-1097: Use worktreeRemove() instead of deprecated run() with shell strings
|
|
184
|
+
if (existsSync(worktreePath)) {
|
|
185
|
+
try {
|
|
186
|
+
const git = getGitForCwd();
|
|
187
|
+
await git.worktreeRemove(worktreePath, { force: true });
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
rmSync(worktreePath, { recursive: true, force: true });
|
|
191
|
+
}
|
|
192
|
+
console.log(`${LOG_PREFIX} Removed worktree: ${worktreePath}`);
|
|
193
|
+
}
|
|
194
|
+
// Try to delete branch
|
|
195
|
+
// WU-1097: Use deleteBranch() instead of deprecated run() with shell strings
|
|
196
|
+
try {
|
|
197
|
+
const git = getGitForCwd();
|
|
198
|
+
const laneKebab = toKebab(doc.lane || '');
|
|
199
|
+
const branchName = `lane/${laneKebab}/${wuId.toLowerCase()}`;
|
|
200
|
+
await git.deleteBranch(branchName, { force: true });
|
|
201
|
+
console.log(`${LOG_PREFIX} Deleted branch: ${branchName}`);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Branch may not exist, that's fine
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Nuke completed - all artifacts removed for ${wuId}`);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Execute cleanup action - remove leftover worktree for done WUs
|
|
212
|
+
*/
|
|
213
|
+
async function executeCleanup(wuId) {
|
|
214
|
+
console.log(`${LOG_PREFIX} Executing cleanup action for ${wuId}...`);
|
|
215
|
+
const wuPath = WU_PATHS.WU(wuId);
|
|
216
|
+
if (!existsSync(wuPath)) {
|
|
217
|
+
console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} WU file not found: ${wuPath}`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
const doc = readWU(wuPath, wuId);
|
|
221
|
+
if (doc.status !== WU_STATUS.DONE) {
|
|
222
|
+
console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} Cannot cleanup: WU status is '${doc.status}', expected 'done'`);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const worktreePath = getWorktreePath(wuId, doc.lane || '');
|
|
226
|
+
if (!existsSync(worktreePath)) {
|
|
227
|
+
console.log(`${LOG_PREFIX} Worktree does not exist, nothing to cleanup`);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
// WU-1097: Use worktreeRemove() instead of deprecated run() with shell strings
|
|
231
|
+
try {
|
|
232
|
+
const git = getGitForCwd();
|
|
233
|
+
await git.worktreeRemove(worktreePath, { force: true });
|
|
234
|
+
console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Removed leftover worktree: ${worktreePath}`);
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
rmSync(worktreePath, { recursive: true, force: true });
|
|
238
|
+
console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Manually removed leftover worktree: ${worktreePath}`);
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Execute recovery action
|
|
244
|
+
*/
|
|
245
|
+
async function executeAction(action, wuId) {
|
|
246
|
+
switch (action) {
|
|
247
|
+
case RECOVERY_ACTIONS.RESUME:
|
|
248
|
+
return executeResume(wuId);
|
|
249
|
+
case RECOVERY_ACTIONS.RESET:
|
|
250
|
+
return executeReset(wuId);
|
|
251
|
+
case RECOVERY_ACTIONS.NUKE:
|
|
252
|
+
return executeNuke(wuId);
|
|
253
|
+
case RECOVERY_ACTIONS.CLEANUP:
|
|
254
|
+
return executeCleanup(wuId);
|
|
255
|
+
default:
|
|
256
|
+
console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} Unknown action: ${action}`);
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Main entry point
|
|
262
|
+
*/
|
|
263
|
+
async function main() {
|
|
264
|
+
const args = createWUParser({
|
|
265
|
+
name: 'wu-recover',
|
|
266
|
+
description: 'Analyze and fix WU state inconsistencies (WU-1090)',
|
|
267
|
+
options: [
|
|
268
|
+
WU_OPTIONS.id,
|
|
269
|
+
{
|
|
270
|
+
name: 'action',
|
|
271
|
+
flags: '-a, --action <action>',
|
|
272
|
+
type: 'string',
|
|
273
|
+
description: 'Recovery action: resume, reset, nuke, cleanup',
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'force',
|
|
277
|
+
flags: '-f, --force',
|
|
278
|
+
type: 'boolean',
|
|
279
|
+
description: 'Required for destructive actions (nuke)',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: 'json',
|
|
283
|
+
flags: '-j, --json',
|
|
284
|
+
type: 'boolean',
|
|
285
|
+
description: 'Output as JSON',
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
required: ['id'],
|
|
289
|
+
allowPositionalId: true,
|
|
290
|
+
});
|
|
291
|
+
const { id, action, force, json } = args;
|
|
292
|
+
// Compute context for the WU
|
|
293
|
+
const { context } = await computeContext({ wuId: id });
|
|
294
|
+
// Analyze recovery issues
|
|
295
|
+
const analysis = await analyzeRecovery(context);
|
|
296
|
+
// If no action specified, just show analysis
|
|
297
|
+
if (!action) {
|
|
298
|
+
if (json) {
|
|
299
|
+
console.log(JSON.stringify(analysis, null, 2));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.log(formatRecoveryOutput(analysis));
|
|
303
|
+
}
|
|
304
|
+
process.exit(getRecoveryExitCode(analysis, false));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// Validate action
|
|
308
|
+
const validation = validateRecoveryAction(action);
|
|
309
|
+
if (!validation.valid) {
|
|
310
|
+
die(validation.error);
|
|
311
|
+
}
|
|
312
|
+
// Check force flag for destructive actions
|
|
313
|
+
if (requiresForceFlag(action) && !force) {
|
|
314
|
+
die(`Action '${action}' requires --force flag`);
|
|
315
|
+
}
|
|
316
|
+
// Execute action
|
|
317
|
+
const success = await executeAction(action, id);
|
|
318
|
+
if (!success) {
|
|
319
|
+
console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} Recovery action failed`);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
324
|
+
// Guard main() for testability
|
|
325
|
+
import { fileURLToPath } from 'node:url';
|
|
326
|
+
import { runCLI } from './cli-entry-point.js';
|
|
327
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
328
|
+
runCLI(main);
|
|
329
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* WU Status Command
|
|
4
|
+
*
|
|
5
|
+
* WU-1090: Context-aware state machine for WU lifecycle commands
|
|
6
|
+
*
|
|
7
|
+
* Shows:
|
|
8
|
+
* - Current location (main checkout vs worktree)
|
|
9
|
+
* - WU state if in worktree or --id provided
|
|
10
|
+
* - Git state (branch, dirty, ahead/behind)
|
|
11
|
+
* - Valid commands for current context
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* pnpm wu:status # Auto-detect from current directory
|
|
15
|
+
* pnpm wu:status --id WU-123 # Show status for specific WU
|
|
16
|
+
*/
|
|
17
|
+
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
18
|
+
import { computeContext } from '@lumenflow/core/dist/context/index.js';
|
|
19
|
+
import { getValidCommandsForContext } from '@lumenflow/core/dist/validation/command-registry.js';
|
|
20
|
+
import { CONTEXT_VALIDATION, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
21
|
+
const { LOCATION_TYPES } = CONTEXT_VALIDATION;
|
|
22
|
+
const LOG_PREFIX = '[wu:status]';
|
|
23
|
+
/**
|
|
24
|
+
* Format location type for display
|
|
25
|
+
*/
|
|
26
|
+
function formatLocationType(type) {
|
|
27
|
+
switch (type) {
|
|
28
|
+
case LOCATION_TYPES.MAIN:
|
|
29
|
+
return 'main checkout';
|
|
30
|
+
case LOCATION_TYPES.WORKTREE:
|
|
31
|
+
return 'worktree';
|
|
32
|
+
case LOCATION_TYPES.DETACHED:
|
|
33
|
+
return 'detached HEAD';
|
|
34
|
+
default:
|
|
35
|
+
return 'unknown location';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format git state for display
|
|
40
|
+
*/
|
|
41
|
+
function formatGitState(context) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
const { git } = context;
|
|
44
|
+
if (git.hasError) {
|
|
45
|
+
lines.push(` ${EMOJI.FAILURE} Git error: ${git.errorMessage}`);
|
|
46
|
+
return lines;
|
|
47
|
+
}
|
|
48
|
+
const branchInfo = git.branch || '(detached)';
|
|
49
|
+
lines.push(` Branch: ${branchInfo}`);
|
|
50
|
+
if (git.isDirty) {
|
|
51
|
+
lines.push(` ${EMOJI.WARNING} Working tree: dirty (${git.modifiedFiles.length} files)`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
lines.push(` ${EMOJI.SUCCESS} Working tree: clean`);
|
|
55
|
+
}
|
|
56
|
+
if (git.hasStaged) {
|
|
57
|
+
lines.push(` Staged: yes`);
|
|
58
|
+
}
|
|
59
|
+
if (git.ahead > 0 || git.behind > 0) {
|
|
60
|
+
const parts = [];
|
|
61
|
+
if (git.ahead > 0)
|
|
62
|
+
parts.push(`${git.ahead} ahead`);
|
|
63
|
+
if (git.behind > 0)
|
|
64
|
+
parts.push(`${git.behind} behind`);
|
|
65
|
+
lines.push(` Tracking: ${parts.join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Format WU state for display
|
|
71
|
+
*/
|
|
72
|
+
function formatWuState(context) {
|
|
73
|
+
const lines = [];
|
|
74
|
+
const { wu } = context;
|
|
75
|
+
if (!wu) {
|
|
76
|
+
lines.push(` No WU context`);
|
|
77
|
+
return lines;
|
|
78
|
+
}
|
|
79
|
+
lines.push(` ID: ${wu.id}`);
|
|
80
|
+
lines.push(` Title: ${wu.title}`);
|
|
81
|
+
lines.push(` Lane: ${wu.lane}`);
|
|
82
|
+
lines.push(` Status: ${wu.status}`);
|
|
83
|
+
if (!wu.isConsistent) {
|
|
84
|
+
lines.push(` ${EMOJI.WARNING} State inconsistency: ${wu.inconsistencyReason}`);
|
|
85
|
+
}
|
|
86
|
+
return lines;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Format valid commands for display
|
|
90
|
+
*/
|
|
91
|
+
function formatValidCommands(context) {
|
|
92
|
+
const lines = [];
|
|
93
|
+
const validCommands = getValidCommandsForContext(context);
|
|
94
|
+
if (validCommands.length === 0) {
|
|
95
|
+
lines.push(` No commands available for current context`);
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
98
|
+
for (const cmd of validCommands) {
|
|
99
|
+
lines.push(` ${cmd.name} - ${cmd.description}`);
|
|
100
|
+
}
|
|
101
|
+
return lines;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Format complete status output
|
|
105
|
+
*/
|
|
106
|
+
export function formatStatusOutput(context) {
|
|
107
|
+
const lines = [];
|
|
108
|
+
// Location section
|
|
109
|
+
lines.push('## Location');
|
|
110
|
+
lines.push(` Type: ${formatLocationType(context.location.type)}`);
|
|
111
|
+
lines.push(` Path: ${context.location.cwd}`);
|
|
112
|
+
if (context.location.worktreeName) {
|
|
113
|
+
lines.push(` Worktree: ${context.location.worktreeName}`);
|
|
114
|
+
}
|
|
115
|
+
if (context.location.worktreeWuId) {
|
|
116
|
+
lines.push(` WU ID: ${context.location.worktreeWuId}`);
|
|
117
|
+
}
|
|
118
|
+
lines.push('');
|
|
119
|
+
// Git section
|
|
120
|
+
lines.push('## Git State');
|
|
121
|
+
lines.push(...formatGitState(context));
|
|
122
|
+
lines.push('');
|
|
123
|
+
// WU section
|
|
124
|
+
lines.push('## WU State');
|
|
125
|
+
lines.push(...formatWuState(context));
|
|
126
|
+
lines.push('');
|
|
127
|
+
// Valid commands section
|
|
128
|
+
lines.push('## Valid Commands');
|
|
129
|
+
lines.push(...formatValidCommands(context));
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get exit code based on context state
|
|
134
|
+
*/
|
|
135
|
+
export function getStatusExitCode(context) {
|
|
136
|
+
// Error if git has errors
|
|
137
|
+
if (context.git.hasError) {
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
// Error if location is unknown
|
|
141
|
+
if (context.location.type === LOCATION_TYPES.UNKNOWN) {
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Main entry point
|
|
148
|
+
*/
|
|
149
|
+
async function main() {
|
|
150
|
+
const args = createWUParser({
|
|
151
|
+
name: 'wu-status',
|
|
152
|
+
description: 'Show WU status, location, and valid commands (WU-1090)',
|
|
153
|
+
options: [
|
|
154
|
+
{ ...WU_OPTIONS.id, required: false },
|
|
155
|
+
{
|
|
156
|
+
name: 'json',
|
|
157
|
+
flags: '-j, --json',
|
|
158
|
+
type: 'boolean',
|
|
159
|
+
description: 'Output as JSON',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
required: [],
|
|
163
|
+
allowPositionalId: true,
|
|
164
|
+
});
|
|
165
|
+
const { id, json } = args;
|
|
166
|
+
// Compute context
|
|
167
|
+
const { context, computationMs, exceededBudget } = await computeContext({
|
|
168
|
+
wuId: id,
|
|
169
|
+
});
|
|
170
|
+
if (exceededBudget) {
|
|
171
|
+
console.warn(`${LOG_PREFIX} ${EMOJI.WARNING} Context computation took ${computationMs.toFixed(0)}ms (exceeded 100ms budget)`);
|
|
172
|
+
}
|
|
173
|
+
// Output
|
|
174
|
+
if (json) {
|
|
175
|
+
console.log(JSON.stringify(context, null, 2));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.log(formatStatusOutput(context));
|
|
179
|
+
}
|
|
180
|
+
// Exit with appropriate code
|
|
181
|
+
process.exit(getStatusExitCode(context));
|
|
182
|
+
}
|
|
183
|
+
// Guard main() for testability
|
|
184
|
+
import { fileURLToPath } from 'node:url';
|
|
185
|
+
import { runCLI } from './cli-entry-point.js';
|
|
186
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
187
|
+
runCLI(main);
|
|
188
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Command-line interface for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"wu-delete": "./dist/wu-delete.js",
|
|
52
52
|
"wu-unlock-lane": "./dist/wu-unlock-lane.js",
|
|
53
53
|
"wu-release": "./dist/wu-release.js",
|
|
54
|
+
"wu-status": "./dist/wu-status.js",
|
|
55
|
+
"wu-recover": "./dist/wu-recover.js",
|
|
54
56
|
"mem-init": "./dist/mem-init.js",
|
|
55
57
|
"mem-checkpoint": "./dist/mem-checkpoint.js",
|
|
56
58
|
"mem-start": "./dist/mem-start.js",
|
|
@@ -66,6 +68,7 @@
|
|
|
66
68
|
"initiative-list": "./dist/initiative-list.js",
|
|
67
69
|
"initiative-status": "./dist/initiative-status.js",
|
|
68
70
|
"initiative-add-wu": "./dist/initiative-add-wu.js",
|
|
71
|
+
"init-plan": "./dist/init-plan.js",
|
|
69
72
|
"agent-session": "./dist/agent-session.js",
|
|
70
73
|
"agent-session-end": "./dist/agent-session-end.js",
|
|
71
74
|
"agent-log-issue": "./dist/agent-log-issue.js",
|
|
@@ -76,6 +79,8 @@
|
|
|
76
79
|
"flow-report": "./dist/flow-report.js",
|
|
77
80
|
"flow-bottlenecks": "./dist/flow-bottlenecks.js",
|
|
78
81
|
"metrics-snapshot": "./dist/metrics-snapshot.js",
|
|
82
|
+
"metrics": "./dist/metrics-cli.js",
|
|
83
|
+
"lumenflow-metrics": "./dist/metrics-cli.js",
|
|
79
84
|
"initiative-bulk-assign-wus": "./dist/initiative-bulk-assign-wus.js",
|
|
80
85
|
"agent-issues-query": "./dist/agent-issues-query.js",
|
|
81
86
|
"gates": "./dist/gates.js",
|
|
@@ -83,7 +88,32 @@
|
|
|
83
88
|
"lumenflow-init": "./dist/init.js",
|
|
84
89
|
"lumenflow": "./dist/init.js",
|
|
85
90
|
"lumenflow-release": "./dist/release.js",
|
|
86
|
-
"lumenflow-docs-sync": "./dist/docs-sync.js"
|
|
91
|
+
"lumenflow-docs-sync": "./dist/docs-sync.js",
|
|
92
|
+
"backlog-prune": "./dist/backlog-prune.js",
|
|
93
|
+
"file-read": "./dist/file-read.js",
|
|
94
|
+
"file-write": "./dist/file-write.js",
|
|
95
|
+
"file-edit": "./dist/file-edit.js",
|
|
96
|
+
"file-delete": "./dist/file-delete.js",
|
|
97
|
+
"guard-worktree-commit": "./dist/guard-worktree-commit.js",
|
|
98
|
+
"guard-locked": "./dist/guard-locked.js",
|
|
99
|
+
"validate": "./dist/validate.js",
|
|
100
|
+
"lumenflow-validate": "./dist/validate.js",
|
|
101
|
+
"validate-agent-skills": "./dist/validate-agent-skills.js",
|
|
102
|
+
"validate-agent-sync": "./dist/validate-agent-sync.js",
|
|
103
|
+
"validate-backlog-sync": "./dist/validate-backlog-sync.js",
|
|
104
|
+
"validate-skills-spec": "./dist/validate-skills-spec.js",
|
|
105
|
+
"deps-add": "./dist/deps-add.js",
|
|
106
|
+
"deps-remove": "./dist/deps-remove.js",
|
|
107
|
+
"session-coordinator": "./dist/session-coordinator.js",
|
|
108
|
+
"rotate-progress": "./dist/rotate-progress.js",
|
|
109
|
+
"lumenflow-upgrade": "./dist/lumenflow-upgrade.js",
|
|
110
|
+
"trace-gen": "./dist/trace-gen.js",
|
|
111
|
+
"git-status": "./dist/git-status.js",
|
|
112
|
+
"git-diff": "./dist/git-diff.js",
|
|
113
|
+
"git-log": "./dist/git-log.js",
|
|
114
|
+
"git-branch": "./dist/git-branch.js",
|
|
115
|
+
"guard-main-branch": "./dist/guard-main-branch.js",
|
|
116
|
+
"state-bootstrap": "./dist/state-bootstrap.js"
|
|
87
117
|
},
|
|
88
118
|
"files": [
|
|
89
119
|
"dist",
|
|
@@ -100,11 +130,11 @@
|
|
|
100
130
|
"pretty-ms": "^9.2.0",
|
|
101
131
|
"simple-git": "^3.30.0",
|
|
102
132
|
"yaml": "^2.8.2",
|
|
103
|
-
"@lumenflow/
|
|
104
|
-
"@lumenflow/
|
|
105
|
-
"@lumenflow/
|
|
106
|
-
"@lumenflow/
|
|
107
|
-
"@lumenflow/
|
|
133
|
+
"@lumenflow/memory": "2.0.0",
|
|
134
|
+
"@lumenflow/metrics": "1.6.0",
|
|
135
|
+
"@lumenflow/core": "2.0.0",
|
|
136
|
+
"@lumenflow/initiatives": "2.0.0",
|
|
137
|
+
"@lumenflow/agent": "2.0.0"
|
|
108
138
|
},
|
|
109
139
|
"devDependencies": {
|
|
110
140
|
"@vitest/coverage-v8": "^4.0.17",
|