@lumenflow/cli 1.4.0 → 1.6.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/README.md +24 -2
- package/dist/cli-entry-point.js +6 -0
- package/dist/docs-sync.js +35 -20
- package/dist/gates.js +77 -20
- package/dist/index.js +10 -0
- package/dist/init.js +57 -16
- package/dist/release.js +12 -6
- package/dist/wu-recover.js +329 -0
- package/dist/wu-status.js +188 -0
- package/package.json +20 -6
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# @lumenflow/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@lumenflow/cli)
|
|
4
|
+
[](https://www.npmjs.com/package/@lumenflow/cli)
|
|
5
|
+
[](https://github.com/hellmai/os/blob/main/LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
> Command-line interface for LumenFlow workflow framework
|
|
4
9
|
|
|
5
10
|
## Installation
|
|
6
11
|
|
|
@@ -105,6 +110,23 @@ npx wu-claim --id WU-123 --lane operations
|
|
|
105
110
|
npx gates
|
|
106
111
|
```
|
|
107
112
|
|
|
113
|
+
## Global Flags
|
|
114
|
+
|
|
115
|
+
All commands support these flags:
|
|
116
|
+
|
|
117
|
+
| Flag | Description |
|
|
118
|
+
| ----------------- | ------------------------- |
|
|
119
|
+
| `--help`, `-h` | Show help for the command |
|
|
120
|
+
| `--version`, `-V` | Show version number |
|
|
121
|
+
| `--no-color` | Disable colored output |
|
|
122
|
+
|
|
123
|
+
## Environment Variables
|
|
124
|
+
|
|
125
|
+
| Variable | Description |
|
|
126
|
+
| ------------- | -------------------------------------------------------------------------------------- |
|
|
127
|
+
| `NO_COLOR` | Disable colored output when set (any value, per [no-color.org](https://no-color.org/)) |
|
|
128
|
+
| `FORCE_COLOR` | Override color level: `0` (disabled), `1` (basic), `2` (256 colors), `3` (16m colors) |
|
|
129
|
+
|
|
108
130
|
## Integration
|
|
109
131
|
|
|
110
132
|
The CLI integrates with other LumenFlow packages:
|
|
@@ -116,7 +138,7 @@ The CLI integrates with other LumenFlow packages:
|
|
|
116
138
|
|
|
117
139
|
## Documentation
|
|
118
140
|
|
|
119
|
-
For complete documentation, see
|
|
141
|
+
For complete documentation, see [lumenflow.dev](https://lumenflow.dev/reference/cli).
|
|
120
142
|
|
|
121
143
|
## License
|
|
122
144
|
|
package/dist/cli-entry-point.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* path but import.meta.url resolves to the real path - they never match
|
|
11
11
|
* so main() is never called.
|
|
12
12
|
*
|
|
13
|
+
* WU-1085: Initializes color support respecting NO_COLOR/FORCE_COLOR/--no-color
|
|
14
|
+
*
|
|
13
15
|
* @example
|
|
14
16
|
* ```typescript
|
|
15
17
|
* // At the bottom of each CLI file:
|
|
@@ -21,13 +23,17 @@
|
|
|
21
23
|
* ```
|
|
22
24
|
*/
|
|
23
25
|
import { EXIT_CODES } from '@lumenflow/core/dist/wu-constants.js';
|
|
26
|
+
import { initColorSupport } from '@lumenflow/core';
|
|
24
27
|
/**
|
|
25
28
|
* Wraps an async main function with proper error handling.
|
|
29
|
+
* WU-1085: Also initializes color support based on NO_COLOR/FORCE_COLOR/--no-color
|
|
26
30
|
*
|
|
27
31
|
* @param main - The async main function to execute
|
|
28
32
|
* @returns Promise that resolves when main completes (or after error handling)
|
|
29
33
|
*/
|
|
30
34
|
export async function runCLI(main) {
|
|
35
|
+
// WU-1085: Initialize color support before running command
|
|
36
|
+
initColorSupport();
|
|
31
37
|
try {
|
|
32
38
|
await main();
|
|
33
39
|
}
|
package/dist/docs-sync.js
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file docs-sync.ts
|
|
3
3
|
* LumenFlow docs:sync command for syncing agent docs to existing projects (WU-1083)
|
|
4
|
+
* WU-1085: Added createWUParser for proper --help support
|
|
4
5
|
*/
|
|
5
6
|
import * as fs from 'node:fs';
|
|
6
7
|
import * as path from 'node:path';
|
|
8
|
+
import { createWUParser, WU_OPTIONS } from '@lumenflow/core';
|
|
9
|
+
/**
|
|
10
|
+
* WU-1085: CLI option definitions for docs-sync command
|
|
11
|
+
*/
|
|
12
|
+
const DOCS_SYNC_OPTIONS = {
|
|
13
|
+
vendor: {
|
|
14
|
+
name: 'vendor',
|
|
15
|
+
flags: '--vendor <type>',
|
|
16
|
+
description: 'Vendor type (claude, cursor, aider, all, none)',
|
|
17
|
+
default: 'claude',
|
|
18
|
+
},
|
|
19
|
+
force: WU_OPTIONS.force,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* WU-1085: Parse docs-sync command options using createWUParser
|
|
23
|
+
* Provides proper --help, --version, and option parsing
|
|
24
|
+
*/
|
|
25
|
+
export function parseDocsSyncOptions() {
|
|
26
|
+
const opts = createWUParser({
|
|
27
|
+
name: 'lumenflow-docs-sync',
|
|
28
|
+
description: 'Sync agent onboarding docs to existing projects (skips existing files by default)',
|
|
29
|
+
options: Object.values(DOCS_SYNC_OPTIONS),
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
force: opts.force ?? false,
|
|
33
|
+
vendor: opts.vendor ?? 'claude',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
7
36
|
/**
|
|
8
37
|
* Get current date in YYYY-MM-DD format
|
|
9
38
|
*/
|
|
@@ -407,32 +436,18 @@ export async function syncSkills(targetDir, options) {
|
|
|
407
436
|
await createFile(path.join(gatesDir, 'SKILL.md'), processTemplate(LUMENFLOW_GATES_SKILL_TEMPLATE, tokens), options.force, result, targetDir);
|
|
408
437
|
return result;
|
|
409
438
|
}
|
|
410
|
-
/**
|
|
411
|
-
* Parse vendor flag from arguments
|
|
412
|
-
*/
|
|
413
|
-
function parseVendorArg(args) {
|
|
414
|
-
const vendorIndex = args.findIndex((arg) => arg === '--vendor');
|
|
415
|
-
if (vendorIndex !== -1 && args[vendorIndex + 1]) {
|
|
416
|
-
const vendor = args[vendorIndex + 1].toLowerCase();
|
|
417
|
-
if (['claude', 'cursor', 'aider', 'all', 'none'].includes(vendor)) {
|
|
418
|
-
return vendor;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
return undefined;
|
|
422
|
-
}
|
|
423
439
|
/**
|
|
424
440
|
* CLI entry point for docs:sync command
|
|
441
|
+
* WU-1085: Updated to use parseDocsSyncOptions for proper --help support
|
|
425
442
|
*/
|
|
426
443
|
export async function main() {
|
|
427
|
-
const
|
|
428
|
-
const force = args.includes('--force') || args.includes('-f');
|
|
429
|
-
const vendor = parseVendorArg(args) ?? 'claude'; // Default to claude
|
|
444
|
+
const opts = parseDocsSyncOptions();
|
|
430
445
|
const targetDir = process.cwd();
|
|
431
446
|
console.log('[lumenflow docs:sync] Syncing agent documentation...');
|
|
432
|
-
console.log(` Vendor: ${vendor}`);
|
|
433
|
-
console.log(` Force: ${force}`);
|
|
434
|
-
const docsResult = await syncAgentDocs(targetDir, { force });
|
|
435
|
-
const skillsResult = await syncSkills(targetDir, { force, vendor });
|
|
447
|
+
console.log(` Vendor: ${opts.vendor}`);
|
|
448
|
+
console.log(` Force: ${opts.force}`);
|
|
449
|
+
const docsResult = await syncAgentDocs(targetDir, { force: opts.force });
|
|
450
|
+
const skillsResult = await syncSkills(targetDir, { force: opts.force, vendor: opts.vendor });
|
|
436
451
|
const created = [...docsResult.created, ...skillsResult.created];
|
|
437
452
|
const skipped = [...docsResult.skipped, ...skillsResult.skipped];
|
|
438
453
|
if (created.length > 0) {
|
package/dist/gates.js
CHANGED
|
@@ -54,35 +54,92 @@ import { buildGatesLogPath, shouldUseGatesAgentMode, updateGatesLatestSymlink, }
|
|
|
54
54
|
import { detectRiskTier, RISK_TIERS, } from '@lumenflow/core/dist/risk-detector.js';
|
|
55
55
|
// WU-2252: Import invariants runner for first-check validation
|
|
56
56
|
import { runInvariants } from '@lumenflow/core/dist/invariants-runner.js';
|
|
57
|
-
import {
|
|
57
|
+
import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
|
|
58
58
|
import { BRANCHES, PACKAGES, PKG_MANAGER, PKG_FLAGS, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS, TOOL_PATHS, CLI_MODES, EXIT_CODES, FILE_SYSTEM, PRETTIER_ARGS, PRETTIER_FLAGS, } from '@lumenflow/core/dist/wu-constants.js';
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* WU-1087: Gates-specific option definitions for createWUParser.
|
|
61
|
+
* Exported for testing and consistency with other CLI commands.
|
|
62
|
+
*/
|
|
63
|
+
export const GATES_OPTIONS = {
|
|
64
|
+
docsOnly: {
|
|
65
|
+
name: 'docsOnly',
|
|
66
|
+
flags: '--docs-only',
|
|
67
|
+
description: 'Run docs-only gates (format, spec-linter, prompts-lint, backlog-sync)',
|
|
68
|
+
},
|
|
69
|
+
fullLint: {
|
|
70
|
+
name: 'fullLint',
|
|
71
|
+
flags: '--full-lint',
|
|
72
|
+
description: 'Run full lint instead of incremental',
|
|
73
|
+
},
|
|
74
|
+
fullTests: {
|
|
75
|
+
name: 'fullTests',
|
|
76
|
+
flags: '--full-tests',
|
|
77
|
+
description: 'Run full test suite instead of incremental',
|
|
78
|
+
},
|
|
79
|
+
fullCoverage: {
|
|
80
|
+
name: 'fullCoverage',
|
|
81
|
+
flags: '--full-coverage',
|
|
82
|
+
description: 'Force full test suite and coverage gate (implies --full-tests)',
|
|
83
|
+
},
|
|
84
|
+
coverageMode: {
|
|
85
|
+
name: 'coverageMode',
|
|
86
|
+
flags: '--coverage-mode <mode>',
|
|
87
|
+
description: 'Coverage gate mode: "warn" logs warnings, "block" fails gate',
|
|
88
|
+
default: 'block',
|
|
89
|
+
},
|
|
90
|
+
verbose: {
|
|
91
|
+
name: 'verbose',
|
|
92
|
+
flags: '--verbose',
|
|
93
|
+
description: 'Stream output in agent mode instead of logging to file',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* WU-1087: Parse gates options using createWUParser for consistency.
|
|
98
|
+
* Handles pnpm's `--` separator and provides automatic --help support.
|
|
99
|
+
*
|
|
100
|
+
* @returns Parsed options object
|
|
101
|
+
*/
|
|
102
|
+
export function parseGatesOptions() {
|
|
61
103
|
// WU-2465: Pre-filter argv to handle pnpm's `--` separator
|
|
62
104
|
// When invoked via `pnpm gates -- --docs-only`, pnpm passes ["--", "--docs-only"]
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
// Remove it if it's followed by an option (starts with -)
|
|
105
|
+
// createWUParser's default filtering removes all `--`, but we need smarter handling:
|
|
106
|
+
// Remove `--` only if it's followed by an option (starts with -)
|
|
107
|
+
const originalArgv = process.argv;
|
|
108
|
+
const filteredArgv = originalArgv.filter((arg, index, arr) => {
|
|
68
109
|
if (arg === '--') {
|
|
69
110
|
const nextArg = arr[index + 1];
|
|
70
111
|
return nextArg && !nextArg.startsWith('-');
|
|
71
112
|
}
|
|
72
113
|
return true;
|
|
73
114
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
// Temporarily replace process.argv for createWUParser
|
|
116
|
+
process.argv = filteredArgv;
|
|
117
|
+
try {
|
|
118
|
+
const opts = createWUParser({
|
|
119
|
+
name: 'gates',
|
|
120
|
+
description: 'Run quality gates with support for docs-only mode, incremental linting, and tiered testing',
|
|
121
|
+
options: Object.values(GATES_OPTIONS),
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
docsOnly: opts.docsOnly,
|
|
125
|
+
fullLint: opts.fullLint,
|
|
126
|
+
fullTests: opts.fullTests,
|
|
127
|
+
fullCoverage: opts.fullCoverage,
|
|
128
|
+
coverageMode: opts.coverageMode ?? 'block',
|
|
129
|
+
verbose: opts.verbose,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
// Restore original process.argv
|
|
134
|
+
process.argv = originalArgv;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* @deprecated Use parseGatesOptions() instead (WU-1087)
|
|
139
|
+
* Kept for backward compatibility during migration.
|
|
140
|
+
*/
|
|
141
|
+
function parseGatesArgs(argv = process.argv) {
|
|
142
|
+
return parseGatesOptions();
|
|
86
143
|
}
|
|
87
144
|
/**
|
|
88
145
|
* Build a pnpm command string
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @lumenflow/cli - Command-line interface for LumenFlow workflow framework
|
|
3
|
+
*
|
|
4
|
+
* This package provides CLI commands for the LumenFlow workflow framework.
|
|
5
|
+
* Most functionality is exposed via bin commands, but the cli-entry-point
|
|
6
|
+
* helper is exported for use in custom CLI wrappers.
|
|
7
|
+
*
|
|
8
|
+
* @see https://lumenflow.dev/reference/cli
|
|
9
|
+
*/
|
|
10
|
+
export { runCLI } from './cli-entry-point.js';
|
package/dist/init.js
CHANGED
|
@@ -3,13 +3,58 @@
|
|
|
3
3
|
* LumenFlow project scaffolding command (WU-1045)
|
|
4
4
|
* WU-1006: Library-First - use core defaults for config generation
|
|
5
5
|
* WU-1028: Vendor-agnostic core + vendor overlays
|
|
6
|
+
* WU-1085: Added createWUParser for proper --help support
|
|
6
7
|
*/
|
|
7
8
|
import * as fs from 'node:fs';
|
|
8
9
|
import * as path from 'node:path';
|
|
9
10
|
import * as yaml from 'yaml';
|
|
10
|
-
import { getDefaultConfig } from '@lumenflow/core';
|
|
11
|
+
import { getDefaultConfig, createWUParser, WU_OPTIONS } from '@lumenflow/core';
|
|
11
12
|
// WU-1067: Import GATE_PRESETS for --preset support
|
|
12
13
|
import { GATE_PRESETS } from '@lumenflow/core/dist/gates-config.js';
|
|
14
|
+
/**
|
|
15
|
+
* WU-1085: CLI option definitions for init command
|
|
16
|
+
*/
|
|
17
|
+
const INIT_OPTIONS = {
|
|
18
|
+
full: {
|
|
19
|
+
name: 'full',
|
|
20
|
+
flags: '--full',
|
|
21
|
+
description: 'Add docs + agent onboarding + task scaffolding',
|
|
22
|
+
},
|
|
23
|
+
framework: {
|
|
24
|
+
name: 'framework',
|
|
25
|
+
flags: '--framework <name>',
|
|
26
|
+
description: 'Add framework hint + overlay docs',
|
|
27
|
+
},
|
|
28
|
+
vendor: {
|
|
29
|
+
name: 'vendor',
|
|
30
|
+
flags: '--vendor <type>',
|
|
31
|
+
description: 'Vendor type (claude, cursor, aider, all, none)',
|
|
32
|
+
},
|
|
33
|
+
preset: {
|
|
34
|
+
name: 'preset',
|
|
35
|
+
flags: '--preset <preset>',
|
|
36
|
+
description: 'Gate preset for config (node, python, go, rust, dotnet)',
|
|
37
|
+
},
|
|
38
|
+
force: WU_OPTIONS.force,
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* WU-1085: Parse init command options using createWUParser
|
|
42
|
+
* Provides proper --help, --version, and option parsing
|
|
43
|
+
*/
|
|
44
|
+
export function parseInitOptions() {
|
|
45
|
+
const opts = createWUParser({
|
|
46
|
+
name: 'lumenflow-init',
|
|
47
|
+
description: 'Initialize LumenFlow in a project',
|
|
48
|
+
options: Object.values(INIT_OPTIONS),
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
force: opts.force ?? false,
|
|
52
|
+
full: opts.full ?? false,
|
|
53
|
+
framework: opts.framework,
|
|
54
|
+
vendor: opts.vendor,
|
|
55
|
+
preset: opts.preset,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
13
58
|
const CONFIG_FILE_NAME = '.lumenflow.config.yaml';
|
|
14
59
|
const FRAMEWORK_HINT_FILE = '.lumenflow.framework.yaml';
|
|
15
60
|
const LUMENFLOW_DIR = '.lumenflow';
|
|
@@ -1100,26 +1145,22 @@ async function createFile(filePath, content, force, result, targetDir) {
|
|
|
1100
1145
|
}
|
|
1101
1146
|
/**
|
|
1102
1147
|
* CLI entry point
|
|
1148
|
+
* WU-1085: Updated to use parseInitOptions for proper --help support
|
|
1103
1149
|
*/
|
|
1104
1150
|
export async function main() {
|
|
1105
|
-
const
|
|
1106
|
-
const force = args.includes('--force') || args.includes('-f');
|
|
1107
|
-
const full = args.includes('--full');
|
|
1108
|
-
const vendor = parseVendorArg(args);
|
|
1109
|
-
const framework = parseFrameworkArg(args);
|
|
1110
|
-
const gatePreset = parsePresetArg(args); // WU-1067
|
|
1151
|
+
const opts = parseInitOptions();
|
|
1111
1152
|
const targetDir = process.cwd();
|
|
1112
1153
|
console.log('[lumenflow init] Scaffolding LumenFlow project...');
|
|
1113
|
-
console.log(` Mode: ${full ? 'full' : 'minimal'}`);
|
|
1114
|
-
console.log(` Framework: ${framework ?? 'none'}`);
|
|
1115
|
-
console.log(` Vendor overlays: ${vendor ?? 'auto'}`);
|
|
1116
|
-
console.log(` Gate preset: ${
|
|
1154
|
+
console.log(` Mode: ${opts.full ? 'full' : 'minimal'}`);
|
|
1155
|
+
console.log(` Framework: ${opts.framework ?? 'none'}`);
|
|
1156
|
+
console.log(` Vendor overlays: ${opts.vendor ?? 'auto'}`);
|
|
1157
|
+
console.log(` Gate preset: ${opts.preset ?? 'none (manual config)'}`);
|
|
1117
1158
|
const result = await scaffoldProject(targetDir, {
|
|
1118
|
-
force,
|
|
1119
|
-
full,
|
|
1120
|
-
vendor,
|
|
1121
|
-
framework,
|
|
1122
|
-
gatePreset,
|
|
1159
|
+
force: opts.force,
|
|
1160
|
+
full: opts.full,
|
|
1161
|
+
vendor: opts.vendor,
|
|
1162
|
+
framework: opts.framework,
|
|
1163
|
+
gatePreset: opts.preset,
|
|
1123
1164
|
});
|
|
1124
1165
|
if (result.created.length > 0) {
|
|
1125
1166
|
console.log('\nCreated:');
|
package/dist/release.js
CHANGED
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
* - Creates git tag vX.Y.Z
|
|
14
14
|
*
|
|
15
15
|
* Usage:
|
|
16
|
-
* pnpm release --version 1.3.0
|
|
17
|
-
* pnpm release --version 1.3.0 --dry-run # Preview without making changes
|
|
18
|
-
* pnpm release --version 1.3.0 --skip-publish # Bump and tag only (no npm publish)
|
|
16
|
+
* pnpm release --release-version 1.3.0
|
|
17
|
+
* pnpm release --release-version 1.3.0 --dry-run # Preview without making changes
|
|
18
|
+
* pnpm release --release-version 1.3.0 --skip-publish # Bump and tag only (no npm publish)
|
|
19
|
+
*
|
|
20
|
+
* WU-1085: The --release-version flag was renamed from --version to avoid conflict
|
|
21
|
+
* with the standard CLI --version flag that shows the CLI version.
|
|
19
22
|
*
|
|
20
23
|
* WU-1074: Add release command for npm publishing
|
|
21
24
|
*/
|
|
@@ -262,19 +265,22 @@ export async function pushTagWithForce(git, tagName, reason = 'release: tag push
|
|
|
262
265
|
}
|
|
263
266
|
/**
|
|
264
267
|
* Main release function
|
|
268
|
+
* WU-1085: Renamed --version to --release-version to avoid conflict with CLI --version flag
|
|
265
269
|
*/
|
|
266
270
|
async function main() {
|
|
267
271
|
const program = new Command()
|
|
268
|
-
.name('release')
|
|
272
|
+
.name('lumenflow-release')
|
|
269
273
|
.description('Release @lumenflow/* packages to npm with version bump, tag, and publish')
|
|
270
|
-
.
|
|
274
|
+
.version('1.0.0', '-V, --version', 'Output the CLI version')
|
|
275
|
+
.requiredOption('-v, --release-version <version>', 'Semver version to release (e.g., 1.3.0)')
|
|
271
276
|
.option('--dry-run', 'Preview changes without making them', false)
|
|
272
277
|
.option('--skip-publish', 'Skip npm publish (only bump and tag)', false)
|
|
273
278
|
.option('--skip-build', 'Skip build step (use existing dist)', false)
|
|
274
279
|
.helpOption('-h, --help', 'Display help for command');
|
|
275
280
|
program.parse();
|
|
276
281
|
const opts = program.opts();
|
|
277
|
-
|
|
282
|
+
// WU-1085: Use releaseVersion instead of version (renamed to avoid CLI --version conflict)
|
|
283
|
+
const { releaseVersion: version, dryRun, skipPublish, skipBuild } = opts;
|
|
278
284
|
console.log(`${LOG_PREFIX} Starting release process for v${version}`);
|
|
279
285
|
if (dryRun) {
|
|
280
286
|
console.log(`${LOG_PREFIX} DRY RUN MODE - no changes will be made`);
|
|
@@ -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": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Command-line interface for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -21,6 +21,18 @@
|
|
|
21
21
|
"url": "https://hellm.ai"
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./cli-entry-point": {
|
|
32
|
+
"types": "./dist/cli-entry-point.d.ts",
|
|
33
|
+
"import": "./dist/cli-entry-point.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
24
36
|
"bin": {
|
|
25
37
|
"wu-claim": "./dist/wu-claim.js",
|
|
26
38
|
"wu-done": "./dist/wu-done.js",
|
|
@@ -39,6 +51,8 @@
|
|
|
39
51
|
"wu-delete": "./dist/wu-delete.js",
|
|
40
52
|
"wu-unlock-lane": "./dist/wu-unlock-lane.js",
|
|
41
53
|
"wu-release": "./dist/wu-release.js",
|
|
54
|
+
"wu-status": "./dist/wu-status.js",
|
|
55
|
+
"wu-recover": "./dist/wu-recover.js",
|
|
42
56
|
"mem-init": "./dist/mem-init.js",
|
|
43
57
|
"mem-checkpoint": "./dist/mem-checkpoint.js",
|
|
44
58
|
"mem-start": "./dist/mem-start.js",
|
|
@@ -88,11 +102,11 @@
|
|
|
88
102
|
"pretty-ms": "^9.2.0",
|
|
89
103
|
"simple-git": "^3.30.0",
|
|
90
104
|
"yaml": "^2.8.2",
|
|
91
|
-
"@lumenflow/core": "1.
|
|
92
|
-
"@lumenflow/
|
|
93
|
-
"@lumenflow/
|
|
94
|
-
"@lumenflow/
|
|
95
|
-
"@lumenflow/
|
|
105
|
+
"@lumenflow/core": "1.6.0",
|
|
106
|
+
"@lumenflow/initiatives": "1.6.0",
|
|
107
|
+
"@lumenflow/agent": "1.6.0",
|
|
108
|
+
"@lumenflow/metrics": "1.6.0",
|
|
109
|
+
"@lumenflow/memory": "1.6.0"
|
|
96
110
|
},
|
|
97
111
|
"devDependencies": {
|
|
98
112
|
"@vitest/coverage-v8": "^4.0.17",
|