@lumenflow/cli 1.6.0 → 2.1.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 +19 -0
- 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/docs-sync.js +72 -326
- 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/sync-templates.js +212 -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/package.json +37 -7
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Rotate Progress CLI Command
|
|
4
|
+
*
|
|
5
|
+
* Moves completed WUs from status.md In Progress section to Completed section.
|
|
6
|
+
* Keeps the status file tidy by archiving done work.
|
|
7
|
+
*
|
|
8
|
+
* WU-1112: INIT-003 Phase 6 - Migrate remaining Tier 1 tools
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* pnpm rotate:progress
|
|
12
|
+
* pnpm rotate:progress --dry-run
|
|
13
|
+
* pnpm rotate:progress --limit 10
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { parse as parseYaml } from 'yaml';
|
|
18
|
+
import { EXIT_CODES, STATUS_SECTIONS, DIRECTORIES, FILE_SYSTEM, } from '@lumenflow/core/dist/wu-constants.js';
|
|
19
|
+
import { runCLI } from './cli-entry-point.js';
|
|
20
|
+
/** Log prefix for console output */
|
|
21
|
+
const LOG_PREFIX = '[rotate:progress]';
|
|
22
|
+
/**
|
|
23
|
+
* Parse command line arguments for rotate-progress
|
|
24
|
+
*
|
|
25
|
+
* @param argv - Process argv array
|
|
26
|
+
* @returns Parsed arguments
|
|
27
|
+
*/
|
|
28
|
+
export function parseRotateArgs(argv) {
|
|
29
|
+
const args = {};
|
|
30
|
+
// Skip node and script name
|
|
31
|
+
const cliArgs = argv.slice(2);
|
|
32
|
+
for (let i = 0; i < cliArgs.length; i++) {
|
|
33
|
+
const arg = cliArgs[i];
|
|
34
|
+
if (arg === '--help' || arg === '-h') {
|
|
35
|
+
args.help = true;
|
|
36
|
+
}
|
|
37
|
+
else if (arg === '--dry-run' || arg === '-n') {
|
|
38
|
+
args.dryRun = true;
|
|
39
|
+
}
|
|
40
|
+
else if (arg === '--limit' || arg === '-l') {
|
|
41
|
+
const val = cliArgs[++i];
|
|
42
|
+
if (val)
|
|
43
|
+
args.limit = parseInt(val, 10);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return args;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get WU status from YAML file
|
|
50
|
+
*/
|
|
51
|
+
function getWuStatus(wuId, baseDir = process.cwd()) {
|
|
52
|
+
const yamlPath = join(baseDir, DIRECTORIES.WU_DIR, `${wuId}.yaml`);
|
|
53
|
+
if (!existsSync(yamlPath)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const content = readFileSync(yamlPath, { encoding: FILE_SYSTEM.ENCODING });
|
|
58
|
+
const yaml = parseYaml(content);
|
|
59
|
+
return yaml?.status || null;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get all WU statuses from YAML files
|
|
67
|
+
*/
|
|
68
|
+
function getAllWuStatuses(baseDir = process.cwd()) {
|
|
69
|
+
const statuses = new Map();
|
|
70
|
+
const wuDir = join(baseDir, DIRECTORIES.WU_DIR);
|
|
71
|
+
if (!existsSync(wuDir)) {
|
|
72
|
+
return statuses;
|
|
73
|
+
}
|
|
74
|
+
const files = readdirSync(wuDir);
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
77
|
+
const wuId = file.replace(/\.ya?ml$/, '');
|
|
78
|
+
const status = getWuStatus(wuId, baseDir);
|
|
79
|
+
if (status) {
|
|
80
|
+
statuses.set(wuId, status);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return statuses;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Find WUs in the In Progress section that have status=done in YAML
|
|
88
|
+
*
|
|
89
|
+
* @param statusContent - Content of status.md file
|
|
90
|
+
* @param wuStatuses - Map of WU IDs to their statuses from YAML
|
|
91
|
+
* @returns Array of WU IDs that should be moved to Completed
|
|
92
|
+
*/
|
|
93
|
+
export function findCompletedWUs(statusContent, wuStatuses) {
|
|
94
|
+
const completed = [];
|
|
95
|
+
// Find the In Progress section
|
|
96
|
+
const inProgressStart = statusContent.indexOf(STATUS_SECTIONS.IN_PROGRESS);
|
|
97
|
+
if (inProgressStart === -1) {
|
|
98
|
+
return completed;
|
|
99
|
+
}
|
|
100
|
+
// Find the end of In Progress section (next ## heading or end of file)
|
|
101
|
+
const afterInProgress = statusContent.slice(inProgressStart + STATUS_SECTIONS.IN_PROGRESS.length);
|
|
102
|
+
const nextSectionMatch = afterInProgress.match(/\n##/);
|
|
103
|
+
const inProgressSection = nextSectionMatch
|
|
104
|
+
? afterInProgress.slice(0, nextSectionMatch.index)
|
|
105
|
+
: afterInProgress;
|
|
106
|
+
// Extract WU IDs from In Progress section
|
|
107
|
+
const wuIdMatches = inProgressSection.match(/WU-\d+/g) || [];
|
|
108
|
+
const uniqueWuIds = [...new Set(wuIdMatches)];
|
|
109
|
+
// Check which ones have done status
|
|
110
|
+
for (const wuId of uniqueWuIds) {
|
|
111
|
+
const status = wuStatuses.get(wuId);
|
|
112
|
+
if (status === 'done' || status === 'completed') {
|
|
113
|
+
completed.push(wuId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return completed;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build the rotated status.md content
|
|
120
|
+
*
|
|
121
|
+
* @param statusContent - Original status.md content
|
|
122
|
+
* @param completedWUs - WU IDs to move to Completed
|
|
123
|
+
* @returns Updated status.md content
|
|
124
|
+
*/
|
|
125
|
+
export function buildRotatedContent(statusContent, completedWUs) {
|
|
126
|
+
if (completedWUs.length === 0) {
|
|
127
|
+
return statusContent;
|
|
128
|
+
}
|
|
129
|
+
let content = statusContent;
|
|
130
|
+
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
131
|
+
// For each completed WU, move it from In Progress to Completed
|
|
132
|
+
for (const wuId of completedWUs) {
|
|
133
|
+
// Find and remove the line from In Progress section
|
|
134
|
+
const wuLineRegex = new RegExp(`\\n?-\\s*\\[?[\\sx]?\\]?\\s*\\[?${wuId}[^\\n]*`, 'gi');
|
|
135
|
+
const match = content.match(wuLineRegex);
|
|
136
|
+
if (match) {
|
|
137
|
+
// Extract the original line text
|
|
138
|
+
const originalLine = match[0].trim();
|
|
139
|
+
// Remove from current position
|
|
140
|
+
content = content.replace(wuLineRegex, '');
|
|
141
|
+
// Extract title from the original line
|
|
142
|
+
// Match "WU-XXXX - Title" or "WU-XXXX Title" patterns
|
|
143
|
+
const titleMatch = originalLine.match(/WU-\d+\s*[-—]?\s*([^(]*)/);
|
|
144
|
+
let title = wuId;
|
|
145
|
+
if (titleMatch) {
|
|
146
|
+
const fullMatch = titleMatch[0].trim();
|
|
147
|
+
const wuPart = wuId;
|
|
148
|
+
// Get everything after the WU ID
|
|
149
|
+
const rest = fullMatch
|
|
150
|
+
.slice(wuId.length)
|
|
151
|
+
.replace(/^[\s-—]+/, '')
|
|
152
|
+
.trim();
|
|
153
|
+
title = rest ? `${wuPart} - ${rest}` : wuPart;
|
|
154
|
+
}
|
|
155
|
+
// Build the completed entry with date
|
|
156
|
+
const completedEntry = `- [x] ${title} (${today})`;
|
|
157
|
+
// Add to Completed section
|
|
158
|
+
const completedSectionIndex = content.indexOf(STATUS_SECTIONS.COMPLETED);
|
|
159
|
+
if (completedSectionIndex !== -1) {
|
|
160
|
+
const insertPoint = completedSectionIndex + STATUS_SECTIONS.COMPLETED.length;
|
|
161
|
+
content =
|
|
162
|
+
content.slice(0, insertPoint) + '\n' + completedEntry + content.slice(insertPoint);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Clean up any double newlines
|
|
167
|
+
content = content.replace(/\n{3,}/g, '\n\n');
|
|
168
|
+
return content;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Print help message for rotate-progress
|
|
172
|
+
*/
|
|
173
|
+
/* istanbul ignore next -- CLI entry point */
|
|
174
|
+
function printHelp() {
|
|
175
|
+
console.log(`
|
|
176
|
+
Usage: rotate-progress [options]
|
|
177
|
+
|
|
178
|
+
Move completed WUs from status.md In Progress to Completed section.
|
|
179
|
+
|
|
180
|
+
Options:
|
|
181
|
+
-n, --dry-run Show changes without writing
|
|
182
|
+
-l, --limit <n> Maximum number of WUs to rotate
|
|
183
|
+
-h, --help Show this help message
|
|
184
|
+
|
|
185
|
+
How it works:
|
|
186
|
+
1. Scans status.md for WUs listed in "In Progress" section
|
|
187
|
+
2. Checks each WU's YAML file for status=done
|
|
188
|
+
3. Moves done WUs to "Completed" section with date stamp
|
|
189
|
+
|
|
190
|
+
Examples:
|
|
191
|
+
rotate:progress # Rotate all completed WUs
|
|
192
|
+
rotate:progress --dry-run # Preview what would be rotated
|
|
193
|
+
rotate:progress --limit 5 # Rotate at most 5 WUs
|
|
194
|
+
`);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Main entry point for rotate-progress command
|
|
198
|
+
*/
|
|
199
|
+
/* istanbul ignore next -- CLI entry point */
|
|
200
|
+
async function main() {
|
|
201
|
+
const args = parseRotateArgs(process.argv);
|
|
202
|
+
if (args.help) {
|
|
203
|
+
printHelp();
|
|
204
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
205
|
+
}
|
|
206
|
+
// Read status.md
|
|
207
|
+
const statusPath = join(process.cwd(), DIRECTORIES.STATUS_PATH);
|
|
208
|
+
if (!existsSync(statusPath)) {
|
|
209
|
+
console.error(`${LOG_PREFIX} Error: ${statusPath} not found`);
|
|
210
|
+
process.exit(EXIT_CODES.ERROR);
|
|
211
|
+
}
|
|
212
|
+
const statusContent = readFileSync(statusPath, {
|
|
213
|
+
encoding: FILE_SYSTEM.ENCODING,
|
|
214
|
+
});
|
|
215
|
+
// Get all WU statuses
|
|
216
|
+
const wuStatuses = getAllWuStatuses();
|
|
217
|
+
// Find completed WUs
|
|
218
|
+
let completedWUs = findCompletedWUs(statusContent, wuStatuses);
|
|
219
|
+
if (completedWUs.length === 0) {
|
|
220
|
+
console.log(`${LOG_PREFIX} No completed WUs to rotate.`);
|
|
221
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
222
|
+
}
|
|
223
|
+
// Apply limit if specified
|
|
224
|
+
if (args.limit && args.limit > 0) {
|
|
225
|
+
completedWUs = completedWUs.slice(0, args.limit);
|
|
226
|
+
}
|
|
227
|
+
console.log(`${LOG_PREFIX} Found ${completedWUs.length} WU(s) to rotate:`);
|
|
228
|
+
for (const wuId of completedWUs) {
|
|
229
|
+
console.log(` - ${wuId}`);
|
|
230
|
+
}
|
|
231
|
+
if (args.dryRun) {
|
|
232
|
+
console.log(`\n${LOG_PREFIX} DRY RUN - No changes made.`);
|
|
233
|
+
const newContent = buildRotatedContent(statusContent, completedWUs);
|
|
234
|
+
console.log(`\n${LOG_PREFIX} Preview of changes:`);
|
|
235
|
+
console.log('---');
|
|
236
|
+
console.log(newContent.slice(0, 500) + '...');
|
|
237
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
238
|
+
}
|
|
239
|
+
// Build and write updated content
|
|
240
|
+
const newContent = buildRotatedContent(statusContent, completedWUs);
|
|
241
|
+
writeFileSync(statusPath, newContent, { encoding: FILE_SYSTEM.ENCODING });
|
|
242
|
+
console.log(`\n${LOG_PREFIX} ✅ Rotated ${completedWUs.length} WU(s) to Completed section.`);
|
|
243
|
+
}
|
|
244
|
+
// Run main if executed directly
|
|
245
|
+
if (import.meta.main) {
|
|
246
|
+
runCLI(main);
|
|
247
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Session Coordinator CLI Command
|
|
4
|
+
*
|
|
5
|
+
* Manages agent sessions - starting, stopping, status, and handoffs.
|
|
6
|
+
* Sessions track which agent is working on which WU and facilitate
|
|
7
|
+
* coordination between multiple agents.
|
|
8
|
+
*
|
|
9
|
+
* WU-1112: INIT-003 Phase 6 - Migrate remaining Tier 1 tools
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* pnpm session:start --wu WU-1112 --agent claude-code
|
|
13
|
+
* pnpm session:stop --reason "Completed work"
|
|
14
|
+
* pnpm session:status
|
|
15
|
+
* pnpm session:handoff --wu WU-1112 --agent cursor
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
18
|
+
import { join, dirname } from 'node:path';
|
|
19
|
+
import { EXIT_CODES, LUMENFLOW_PATHS, FILE_SYSTEM } from '@lumenflow/core/dist/wu-constants.js';
|
|
20
|
+
import { runCLI } from './cli-entry-point.js';
|
|
21
|
+
/** Log prefix for console output */
|
|
22
|
+
const LOG_PREFIX = '[session]';
|
|
23
|
+
/**
|
|
24
|
+
* Session subcommands
|
|
25
|
+
*/
|
|
26
|
+
export var SessionCommand;
|
|
27
|
+
(function (SessionCommand) {
|
|
28
|
+
SessionCommand["START"] = "start";
|
|
29
|
+
SessionCommand["STOP"] = "stop";
|
|
30
|
+
SessionCommand["STATUS"] = "status";
|
|
31
|
+
SessionCommand["HANDOFF"] = "handoff";
|
|
32
|
+
})(SessionCommand || (SessionCommand = {}));
|
|
33
|
+
/**
|
|
34
|
+
* Parse command line arguments for session-coordinator
|
|
35
|
+
*
|
|
36
|
+
* @param argv - Process argv array
|
|
37
|
+
* @returns Parsed arguments
|
|
38
|
+
*/
|
|
39
|
+
export function parseSessionArgs(argv) {
|
|
40
|
+
const args = {};
|
|
41
|
+
// Skip node and script name
|
|
42
|
+
const cliArgs = argv.slice(2);
|
|
43
|
+
for (let i = 0; i < cliArgs.length; i++) {
|
|
44
|
+
const arg = cliArgs[i];
|
|
45
|
+
if (arg === '--help' || arg === '-h') {
|
|
46
|
+
args.help = true;
|
|
47
|
+
}
|
|
48
|
+
else if (arg === '--wu' || arg === '-w') {
|
|
49
|
+
args.wuId = cliArgs[++i];
|
|
50
|
+
}
|
|
51
|
+
else if (arg === '--agent' || arg === '-a') {
|
|
52
|
+
args.agent = cliArgs[++i];
|
|
53
|
+
}
|
|
54
|
+
else if (arg === '--reason' || arg === '-r') {
|
|
55
|
+
args.reason = cliArgs[++i];
|
|
56
|
+
}
|
|
57
|
+
else if (!arg.startsWith('-')) {
|
|
58
|
+
// Subcommand
|
|
59
|
+
if (Object.values(SessionCommand).includes(arg)) {
|
|
60
|
+
args.command = arg;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Default to status if no command given
|
|
65
|
+
if (!args.command && !args.help) {
|
|
66
|
+
args.command = SessionCommand.STATUS;
|
|
67
|
+
}
|
|
68
|
+
return args;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate session command arguments
|
|
72
|
+
*
|
|
73
|
+
* @param args - Parsed session arguments
|
|
74
|
+
* @returns Validation result
|
|
75
|
+
*/
|
|
76
|
+
export function validateSessionCommand(args) {
|
|
77
|
+
const { command, wuId } = args;
|
|
78
|
+
switch (command) {
|
|
79
|
+
case SessionCommand.START:
|
|
80
|
+
if (!wuId) {
|
|
81
|
+
return {
|
|
82
|
+
valid: false,
|
|
83
|
+
error: 'session start requires --wu <id> to specify which WU to work on',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
case SessionCommand.HANDOFF:
|
|
88
|
+
if (!wuId) {
|
|
89
|
+
return {
|
|
90
|
+
valid: false,
|
|
91
|
+
error: 'session handoff requires --wu <id> to specify which WU to hand off',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
case SessionCommand.STOP:
|
|
96
|
+
case SessionCommand.STATUS:
|
|
97
|
+
// No required arguments
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
return {
|
|
101
|
+
valid: false,
|
|
102
|
+
error: `Unknown command: ${command}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { valid: true };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get path to current session file
|
|
109
|
+
*/
|
|
110
|
+
function getSessionPath() {
|
|
111
|
+
return join(process.cwd(), LUMENFLOW_PATHS.SESSION_CURRENT);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Read current session state
|
|
115
|
+
*/
|
|
116
|
+
function readCurrentSession() {
|
|
117
|
+
const path = getSessionPath();
|
|
118
|
+
if (!existsSync(path)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const content = readFileSync(path, { encoding: FILE_SYSTEM.ENCODING });
|
|
123
|
+
return JSON.parse(content);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Write session state
|
|
131
|
+
*/
|
|
132
|
+
function writeSession(session) {
|
|
133
|
+
const path = getSessionPath();
|
|
134
|
+
const dir = dirname(path);
|
|
135
|
+
if (!existsSync(dir)) {
|
|
136
|
+
mkdirSync(dir, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
if (session === null) {
|
|
139
|
+
// Remove session file if clearing
|
|
140
|
+
if (existsSync(path)) {
|
|
141
|
+
const { unlinkSync } = require('node:fs');
|
|
142
|
+
unlinkSync(path);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
writeFileSync(path, JSON.stringify(session, null, 2), {
|
|
147
|
+
encoding: FILE_SYSTEM.ENCODING,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Print help message for session-coordinator
|
|
153
|
+
*/
|
|
154
|
+
/* istanbul ignore next -- CLI entry point */
|
|
155
|
+
function printHelp() {
|
|
156
|
+
console.log(`
|
|
157
|
+
Usage: session <command> [options]
|
|
158
|
+
|
|
159
|
+
Manage agent sessions for WU work coordination.
|
|
160
|
+
|
|
161
|
+
Commands:
|
|
162
|
+
start Start a new session
|
|
163
|
+
stop Stop current session
|
|
164
|
+
status Show current session status
|
|
165
|
+
handoff Hand off session to another agent
|
|
166
|
+
|
|
167
|
+
Options:
|
|
168
|
+
-w, --wu <id> WU ID to work on (required for start/handoff)
|
|
169
|
+
-a, --agent <type> Agent type (e.g., claude-code, cursor, aider)
|
|
170
|
+
-r, --reason <msg> Reason for stopping session
|
|
171
|
+
-h, --help Show this help message
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
session start --wu WU-1112 --agent claude-code
|
|
175
|
+
session stop --reason "Completed acceptance criteria"
|
|
176
|
+
session status
|
|
177
|
+
session handoff --wu WU-1112 --agent cursor
|
|
178
|
+
|
|
179
|
+
Session files are stored in: ${LUMENFLOW_PATHS.SESSION_CURRENT}
|
|
180
|
+
`);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Handle start command
|
|
184
|
+
*/
|
|
185
|
+
/* istanbul ignore next -- CLI entry point */
|
|
186
|
+
function handleStart(args) {
|
|
187
|
+
const current = readCurrentSession();
|
|
188
|
+
if (current) {
|
|
189
|
+
console.log(`${LOG_PREFIX} Active session already exists:`);
|
|
190
|
+
console.log(` WU: ${current.wuId}`);
|
|
191
|
+
console.log(` Agent: ${current.agent}`);
|
|
192
|
+
console.log(` Started: ${current.startedAt}`);
|
|
193
|
+
console.log(`\n${LOG_PREFIX} Stop current session first with: session stop`);
|
|
194
|
+
process.exit(EXIT_CODES.ERROR);
|
|
195
|
+
}
|
|
196
|
+
const session = {
|
|
197
|
+
wuId: args.wuId,
|
|
198
|
+
agent: args.agent || 'unknown',
|
|
199
|
+
startedAt: new Date().toISOString(),
|
|
200
|
+
lastActivity: new Date().toISOString(),
|
|
201
|
+
};
|
|
202
|
+
writeSession(session);
|
|
203
|
+
console.log(`${LOG_PREFIX} ✅ Session started`);
|
|
204
|
+
console.log(` WU: ${session.wuId}`);
|
|
205
|
+
console.log(` Agent: ${session.agent}`);
|
|
206
|
+
console.log(` Started: ${session.startedAt}`);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Handle stop command
|
|
210
|
+
*/
|
|
211
|
+
/* istanbul ignore next -- CLI entry point */
|
|
212
|
+
function handleStop(args) {
|
|
213
|
+
const current = readCurrentSession();
|
|
214
|
+
if (!current) {
|
|
215
|
+
console.log(`${LOG_PREFIX} No active session to stop.`);
|
|
216
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
217
|
+
}
|
|
218
|
+
const duration = Date.now() - new Date(current.startedAt).getTime();
|
|
219
|
+
const durationMin = Math.round(duration / 60000);
|
|
220
|
+
console.log(`${LOG_PREFIX} ✅ Session stopped`);
|
|
221
|
+
console.log(` WU: ${current.wuId}`);
|
|
222
|
+
console.log(` Agent: ${current.agent}`);
|
|
223
|
+
console.log(` Duration: ${durationMin} minutes`);
|
|
224
|
+
if (args.reason) {
|
|
225
|
+
console.log(` Reason: ${args.reason}`);
|
|
226
|
+
}
|
|
227
|
+
writeSession(null);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Handle status command
|
|
231
|
+
*/
|
|
232
|
+
/* istanbul ignore next -- CLI entry point */
|
|
233
|
+
function handleStatus() {
|
|
234
|
+
const current = readCurrentSession();
|
|
235
|
+
if (!current) {
|
|
236
|
+
console.log(`${LOG_PREFIX} No active session.`);
|
|
237
|
+
console.log(`\n${LOG_PREFIX} Start a session with: session start --wu WU-XXXX`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const duration = Date.now() - new Date(current.startedAt).getTime();
|
|
241
|
+
const durationMin = Math.round(duration / 60000);
|
|
242
|
+
console.log(`${LOG_PREFIX} Active session:`);
|
|
243
|
+
console.log(` WU: ${current.wuId}`);
|
|
244
|
+
console.log(` Agent: ${current.agent}`);
|
|
245
|
+
console.log(` Started: ${current.startedAt}`);
|
|
246
|
+
console.log(` Duration: ${durationMin} minutes`);
|
|
247
|
+
console.log(` Last activity: ${current.lastActivity}`);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Handle handoff command
|
|
251
|
+
*/
|
|
252
|
+
/* istanbul ignore next -- CLI entry point */
|
|
253
|
+
function handleHandoff(args) {
|
|
254
|
+
const current = readCurrentSession();
|
|
255
|
+
if (current) {
|
|
256
|
+
console.log(`${LOG_PREFIX} Stopping current session...`);
|
|
257
|
+
handleStop({ reason: `Handoff to ${args.agent || 'another agent'}` });
|
|
258
|
+
}
|
|
259
|
+
console.log(`\n${LOG_PREFIX} Starting new session for handoff...`);
|
|
260
|
+
handleStart(args);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Main entry point for session-coordinator command
|
|
264
|
+
*/
|
|
265
|
+
/* istanbul ignore next -- CLI entry point */
|
|
266
|
+
async function main() {
|
|
267
|
+
const args = parseSessionArgs(process.argv);
|
|
268
|
+
if (args.help) {
|
|
269
|
+
printHelp();
|
|
270
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
271
|
+
}
|
|
272
|
+
const validation = validateSessionCommand(args);
|
|
273
|
+
if (!validation.valid) {
|
|
274
|
+
console.error(`${LOG_PREFIX} Error: ${validation.error}`);
|
|
275
|
+
printHelp();
|
|
276
|
+
process.exit(EXIT_CODES.ERROR);
|
|
277
|
+
}
|
|
278
|
+
switch (args.command) {
|
|
279
|
+
case SessionCommand.START:
|
|
280
|
+
handleStart(args);
|
|
281
|
+
break;
|
|
282
|
+
case SessionCommand.STOP:
|
|
283
|
+
handleStop(args);
|
|
284
|
+
break;
|
|
285
|
+
case SessionCommand.STATUS:
|
|
286
|
+
handleStatus();
|
|
287
|
+
break;
|
|
288
|
+
case SessionCommand.HANDOFF:
|
|
289
|
+
handleHandoff(args);
|
|
290
|
+
break;
|
|
291
|
+
default:
|
|
292
|
+
console.error(`${LOG_PREFIX} Unknown command: ${args.command}`);
|
|
293
|
+
printHelp();
|
|
294
|
+
process.exit(EXIT_CODES.ERROR);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Run main if executed directly
|
|
298
|
+
if (import.meta.main) {
|
|
299
|
+
runCLI(main);
|
|
300
|
+
}
|