@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.
Files changed (42) hide show
  1. package/README.md +19 -0
  2. package/dist/__tests__/backlog-prune.test.js +478 -0
  3. package/dist/__tests__/deps-operations.test.js +206 -0
  4. package/dist/__tests__/file-operations.test.js +906 -0
  5. package/dist/__tests__/git-operations.test.js +668 -0
  6. package/dist/__tests__/guards-validation.test.js +416 -0
  7. package/dist/__tests__/init-plan.test.js +340 -0
  8. package/dist/__tests__/lumenflow-upgrade.test.js +107 -0
  9. package/dist/__tests__/metrics-cli.test.js +619 -0
  10. package/dist/__tests__/rotate-progress.test.js +127 -0
  11. package/dist/__tests__/session-coordinator.test.js +109 -0
  12. package/dist/__tests__/state-bootstrap.test.js +432 -0
  13. package/dist/__tests__/trace-gen.test.js +115 -0
  14. package/dist/backlog-prune.js +299 -0
  15. package/dist/deps-add.js +215 -0
  16. package/dist/deps-remove.js +94 -0
  17. package/dist/docs-sync.js +72 -326
  18. package/dist/file-delete.js +236 -0
  19. package/dist/file-edit.js +247 -0
  20. package/dist/file-read.js +197 -0
  21. package/dist/file-write.js +220 -0
  22. package/dist/git-branch.js +187 -0
  23. package/dist/git-diff.js +177 -0
  24. package/dist/git-log.js +230 -0
  25. package/dist/git-status.js +208 -0
  26. package/dist/guard-locked.js +169 -0
  27. package/dist/guard-main-branch.js +202 -0
  28. package/dist/guard-worktree-commit.js +160 -0
  29. package/dist/init-plan.js +337 -0
  30. package/dist/lumenflow-upgrade.js +178 -0
  31. package/dist/metrics-cli.js +433 -0
  32. package/dist/rotate-progress.js +247 -0
  33. package/dist/session-coordinator.js +300 -0
  34. package/dist/state-bootstrap.js +307 -0
  35. package/dist/sync-templates.js +212 -0
  36. package/dist/trace-gen.js +331 -0
  37. package/dist/validate-agent-skills.js +218 -0
  38. package/dist/validate-agent-sync.js +148 -0
  39. package/dist/validate-backlog-sync.js +152 -0
  40. package/dist/validate-skills-spec.js +206 -0
  41. package/dist/validate.js +230 -0
  42. package/package.json +37 -7
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * File Write CLI Tool
4
+ *
5
+ * Provides audited file write operations with:
6
+ * - Scope checking against WU code_paths
7
+ * - PHI scanning (optional)
8
+ * - Directory creation
9
+ * - Audit logging
10
+ *
11
+ * Usage:
12
+ * node file-write.js <path> --content <content> [--encoding utf-8]
13
+ *
14
+ * WU-1108: INIT-003 Phase 4a - Migrate file operations
15
+ */
16
+ import { writeFile, mkdir, stat } from 'node:fs/promises';
17
+ import { resolve, dirname } from 'node:path';
18
+ /**
19
+ * Default configuration for file write operations
20
+ */
21
+ export const FILE_WRITE_DEFAULTS = {
22
+ /** Default encoding */
23
+ encoding: 'utf-8',
24
+ /** Create parent directories if they don't exist */
25
+ createDirectories: true,
26
+ };
27
+ /**
28
+ * Parse command line arguments for file-write
29
+ */
30
+ export function parseFileWriteArgs(argv) {
31
+ const args = {
32
+ encoding: FILE_WRITE_DEFAULTS.encoding,
33
+ createDirectories: FILE_WRITE_DEFAULTS.createDirectories,
34
+ };
35
+ // Skip node and script name
36
+ const cliArgs = argv.slice(2);
37
+ for (let i = 0; i < cliArgs.length; i++) {
38
+ const arg = cliArgs[i];
39
+ if (arg === '--help' || arg === '-h') {
40
+ args.help = true;
41
+ }
42
+ else if (arg === '--path') {
43
+ args.path = cliArgs[++i];
44
+ }
45
+ else if (arg === '--content') {
46
+ args.content = cliArgs[++i];
47
+ }
48
+ else if (arg === '--encoding') {
49
+ args.encoding = cliArgs[++i];
50
+ }
51
+ else if (arg === '--no-create-dirs') {
52
+ args.createDirectories = false;
53
+ }
54
+ else if (arg === '--scan-phi') {
55
+ args.scanPHI = true;
56
+ }
57
+ else if (!arg.startsWith('-') && !args.path) {
58
+ // Positional argument for path
59
+ args.path = arg;
60
+ }
61
+ }
62
+ return args;
63
+ }
64
+ /**
65
+ * Check if directory exists
66
+ */
67
+ async function directoryExists(dirPath) {
68
+ try {
69
+ const stats = await stat(dirPath);
70
+ return stats.isDirectory();
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * Write a file with audit logging and safety checks
78
+ */
79
+ export async function writeFileWithAudit(args) {
80
+ const startTime = Date.now();
81
+ const filePath = args.path ? resolve(args.path) : '';
82
+ const encoding = args.encoding ?? FILE_WRITE_DEFAULTS.encoding;
83
+ const createDirs = args.createDirectories ?? FILE_WRITE_DEFAULTS.createDirectories;
84
+ const content = args.content ?? '';
85
+ const auditLog = {
86
+ operation: 'write',
87
+ path: filePath,
88
+ timestamp: new Date().toISOString(),
89
+ success: false,
90
+ };
91
+ const warnings = [];
92
+ try {
93
+ // Validate path
94
+ if (!filePath) {
95
+ throw new Error('Path is required');
96
+ }
97
+ const parentDir = dirname(filePath);
98
+ let directoriesCreated = false;
99
+ // Check parent directory
100
+ const parentExists = await directoryExists(parentDir);
101
+ if (!parentExists) {
102
+ if (createDirs) {
103
+ await mkdir(parentDir, { recursive: true });
104
+ directoriesCreated = true;
105
+ }
106
+ else {
107
+ throw new Error(`ENOENT: parent directory does not exist: ${parentDir}`);
108
+ }
109
+ }
110
+ // PHI scanning (if enabled and @lumenflow/core is available)
111
+ if (args.scanPHI) {
112
+ try {
113
+ // Dynamic import to avoid hard dependency
114
+ const { scanForPHI } = await import('@lumenflow/core/dist/validators/phi-scanner.js');
115
+ const scanResult = scanForPHI(content);
116
+ if (scanResult.hasPHI) {
117
+ warnings.push(`PHI detected: ${scanResult.matches.length} potential match(es). Review before committing.`);
118
+ }
119
+ }
120
+ catch {
121
+ // PHI scanner not available - skip silently
122
+ }
123
+ }
124
+ // Write file
125
+ await writeFile(filePath, content, { encoding });
126
+ // Calculate bytes written
127
+ const bytesWritten = Buffer.byteLength(content, encoding);
128
+ // Build metadata
129
+ const metadata = {
130
+ bytesWritten,
131
+ directoriesCreated,
132
+ };
133
+ // Success
134
+ auditLog.success = true;
135
+ auditLog.durationMs = Date.now() - startTime;
136
+ return {
137
+ success: true,
138
+ warnings: warnings.length > 0 ? warnings : undefined,
139
+ metadata,
140
+ auditLog,
141
+ };
142
+ }
143
+ catch (error) {
144
+ const errorMessage = error instanceof Error ? error.message : String(error);
145
+ auditLog.error = errorMessage;
146
+ auditLog.durationMs = Date.now() - startTime;
147
+ return {
148
+ success: false,
149
+ error: errorMessage,
150
+ warnings: warnings.length > 0 ? warnings : undefined,
151
+ auditLog,
152
+ };
153
+ }
154
+ }
155
+ /**
156
+ * Print help message
157
+ */
158
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
159
+ function printHelp() {
160
+ console.log(`
161
+ Usage: file-write <path> --content <content> [options]
162
+
163
+ Write content to a file with audit logging.
164
+
165
+ Arguments:
166
+ path Path to file to write
167
+
168
+ Options:
169
+ --path <path> Path to file (alternative to positional)
170
+ --content <content> Content to write (required)
171
+ --encoding <enc> File encoding (default: utf-8)
172
+ --no-create-dirs Don't create parent directories
173
+ --scan-phi Scan content for PHI before writing
174
+ -h, --help Show this help message
175
+
176
+ Examples:
177
+ file-write output.txt --content "Hello, World!"
178
+ file-write --path nested/dir/file.txt --content "Content"
179
+ file-write config.json --content '{"key": "value"}' --encoding utf-8
180
+ `);
181
+ }
182
+ /**
183
+ * Main entry point
184
+ */
185
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
186
+ async function main() {
187
+ const args = parseFileWriteArgs(process.argv);
188
+ if (args.help) {
189
+ printHelp();
190
+ process.exit(0);
191
+ }
192
+ if (!args.path) {
193
+ console.error('Error: path is required');
194
+ printHelp();
195
+ process.exit(1);
196
+ }
197
+ if (args.content === undefined) {
198
+ console.error('Error: --content is required');
199
+ printHelp();
200
+ process.exit(1);
201
+ }
202
+ const result = await writeFileWithAudit(args);
203
+ if (result.success) {
204
+ console.log(`Written ${result.metadata?.bytesWritten} bytes to ${args.path}`);
205
+ if (result.warnings && result.warnings.length > 0) {
206
+ for (const warning of result.warnings) {
207
+ console.warn(`Warning: ${warning}`);
208
+ }
209
+ }
210
+ }
211
+ else {
212
+ console.error(`Error: ${result.error}`);
213
+ process.exit(1);
214
+ }
215
+ }
216
+ // Run main if executed directly
217
+ import { runCLI } from './cli-entry-point.js';
218
+ if (import.meta.main) {
219
+ runCLI(main);
220
+ }
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git Branch CLI Tool
4
+ *
5
+ * Provides WU-aware git branch with:
6
+ * - Branch listing (local, remote, all)
7
+ * - Current branch detection
8
+ * - Contains filtering
9
+ *
10
+ * Usage:
11
+ * node git-branch.js [--list] [-a] [-r] [--show-current]
12
+ *
13
+ * WU-1109: INIT-003 Phase 4b - Migrate git operations
14
+ */
15
+ import { createGitForPath, getGitForCwd } from '@lumenflow/core';
16
+ /**
17
+ * Parse command line arguments for git-branch
18
+ */
19
+ export function parseGitBranchArgs(argv) {
20
+ const args = {};
21
+ // Skip node and script name
22
+ const cliArgs = argv.slice(2);
23
+ for (let i = 0; i < cliArgs.length; i++) {
24
+ const arg = cliArgs[i];
25
+ if (arg === '--help' || arg === '-h') {
26
+ args.help = true;
27
+ }
28
+ else if (arg === '--list' || arg === '-l') {
29
+ args.list = true;
30
+ }
31
+ else if (arg === '--all' || arg === '-a') {
32
+ args.all = true;
33
+ }
34
+ else if (arg === '--remotes' || arg === '-r') {
35
+ args.remotes = true;
36
+ }
37
+ else if (arg === '--show-current') {
38
+ args.showCurrent = true;
39
+ }
40
+ else if (arg === '--contains') {
41
+ args.contains = cliArgs[++i];
42
+ }
43
+ else if (arg === '--base-dir') {
44
+ args.baseDir = cliArgs[++i];
45
+ }
46
+ }
47
+ return args;
48
+ }
49
+ /**
50
+ * Parse branch list output
51
+ */
52
+ function parseBranchOutput(output) {
53
+ const branches = [];
54
+ const lines = output.split('\n').filter((line) => line.trim());
55
+ for (const line of lines) {
56
+ const trimmed = line.trim();
57
+ if (!trimmed)
58
+ continue;
59
+ // Check if current branch (starts with *)
60
+ const isCurrent = trimmed.startsWith('*');
61
+ // Check if remote branch
62
+ const isRemote = trimmed.includes('remotes/') || trimmed.startsWith('origin/');
63
+ // Extract branch name
64
+ let name = trimmed;
65
+ if (isCurrent) {
66
+ name = name.slice(1).trim();
67
+ }
68
+ // Remove remote prefix for display
69
+ if (name.startsWith('remotes/')) {
70
+ name = name.slice(8);
71
+ }
72
+ // Skip HEAD pointer entries
73
+ if (name.includes(' -> ')) {
74
+ continue;
75
+ }
76
+ branches.push({
77
+ name,
78
+ isCurrent,
79
+ isRemote,
80
+ });
81
+ }
82
+ return branches;
83
+ }
84
+ /**
85
+ * Get git branch information
86
+ */
87
+ export async function getGitBranch(args) {
88
+ try {
89
+ const git = args.baseDir ? createGitForPath(args.baseDir) : getGitForCwd();
90
+ // Show current branch only
91
+ if (args.showCurrent) {
92
+ const current = await git.getCurrentBranch();
93
+ return {
94
+ success: true,
95
+ current: current || undefined,
96
+ };
97
+ }
98
+ // Build branch arguments
99
+ const rawArgs = ['branch'];
100
+ if (args.all) {
101
+ rawArgs.push('-a');
102
+ }
103
+ else if (args.remotes) {
104
+ rawArgs.push('-r');
105
+ }
106
+ if (args.contains) {
107
+ rawArgs.push('--contains', args.contains);
108
+ }
109
+ const output = await git.raw(rawArgs);
110
+ const branches = parseBranchOutput(output);
111
+ // Find current branch
112
+ const currentBranch = branches.find((b) => b.isCurrent);
113
+ return {
114
+ success: true,
115
+ current: currentBranch?.name,
116
+ branches,
117
+ };
118
+ }
119
+ catch (error) {
120
+ const errorMessage = error instanceof Error ? error.message : String(error);
121
+ return {
122
+ success: false,
123
+ error: errorMessage,
124
+ };
125
+ }
126
+ }
127
+ /**
128
+ * Print help message
129
+ */
130
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
131
+ function printHelp() {
132
+ console.log(`
133
+ Usage: git-branch [options]
134
+
135
+ List, create, or delete branches.
136
+
137
+ Options:
138
+ --base-dir <dir> Base directory for git operations
139
+ --list, -l List branches
140
+ -a, --all List both local and remote branches
141
+ -r, --remotes List only remote branches
142
+ --show-current Print the name of the current branch
143
+ --contains <commit> Only list branches containing the specified commit
144
+ -h, --help Show this help message
145
+
146
+ Examples:
147
+ git-branch
148
+ git-branch --list
149
+ git-branch -a
150
+ git-branch --show-current
151
+ git-branch --contains abc123
152
+ `);
153
+ }
154
+ /**
155
+ * Main entry point
156
+ */
157
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
158
+ async function main() {
159
+ const args = parseGitBranchArgs(process.argv);
160
+ if (args.help) {
161
+ printHelp();
162
+ process.exit(0);
163
+ }
164
+ const result = await getGitBranch(args);
165
+ if (result.success) {
166
+ if (args.showCurrent) {
167
+ if (result.current) {
168
+ console.log(result.current);
169
+ }
170
+ }
171
+ else if (result.branches) {
172
+ for (const branch of result.branches) {
173
+ const prefix = branch.isCurrent ? '* ' : ' ';
174
+ console.log(`${prefix}${branch.name}`);
175
+ }
176
+ }
177
+ }
178
+ else {
179
+ console.error(`Error: ${result.error}`);
180
+ process.exit(1);
181
+ }
182
+ }
183
+ // Run main if executed directly
184
+ import { runCLI } from './cli-entry-point.js';
185
+ if (import.meta.main) {
186
+ runCLI(main);
187
+ }
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git Diff CLI Tool
4
+ *
5
+ * Provides WU-aware git diff with:
6
+ * - Staged/cached diff support
7
+ * - Name-only and stat output modes
8
+ * - File filtering
9
+ *
10
+ * Usage:
11
+ * node git-diff.js [ref] [--staged] [--name-only] [--stat]
12
+ *
13
+ * WU-1109: INIT-003 Phase 4b - Migrate git operations
14
+ */
15
+ import { createGitForPath, getGitForCwd } from '@lumenflow/core';
16
+ /**
17
+ * Parse command line arguments for git-diff
18
+ */
19
+ export function parseGitDiffArgs(argv) {
20
+ const args = {};
21
+ // Skip node and script name
22
+ const cliArgs = argv.slice(2);
23
+ let afterDoubleDash = false;
24
+ for (let i = 0; i < cliArgs.length; i++) {
25
+ const arg = cliArgs[i];
26
+ if (arg === '--') {
27
+ afterDoubleDash = true;
28
+ continue;
29
+ }
30
+ if (afterDoubleDash) {
31
+ // Everything after -- is a path
32
+ args.path = arg;
33
+ continue;
34
+ }
35
+ if (arg === '--help' || arg === '-h') {
36
+ args.help = true;
37
+ }
38
+ else if (arg === '--staged' || arg === '--cached') {
39
+ args.staged = true;
40
+ }
41
+ else if (arg === '--name-only') {
42
+ args.nameOnly = true;
43
+ }
44
+ else if (arg === '--stat') {
45
+ args.stat = true;
46
+ }
47
+ else if (arg === '--base-dir') {
48
+ args.baseDir = cliArgs[++i];
49
+ }
50
+ else if (!arg.startsWith('-') && !args.ref) {
51
+ // Positional argument for ref
52
+ args.ref = arg;
53
+ }
54
+ }
55
+ return args;
56
+ }
57
+ /**
58
+ * Get git diff with audit logging
59
+ */
60
+ export async function getGitDiff(args) {
61
+ try {
62
+ const git = args.baseDir ? createGitForPath(args.baseDir) : getGitForCwd();
63
+ // Build diff arguments
64
+ const rawArgs = ['diff'];
65
+ if (args.staged) {
66
+ rawArgs.push('--cached');
67
+ }
68
+ if (args.nameOnly) {
69
+ rawArgs.push('--name-only');
70
+ }
71
+ if (args.stat) {
72
+ rawArgs.push('--stat');
73
+ }
74
+ if (args.ref) {
75
+ rawArgs.push(args.ref);
76
+ }
77
+ if (args.path) {
78
+ rawArgs.push('--', args.path);
79
+ }
80
+ const output = await git.raw(rawArgs);
81
+ const trimmedOutput = output.trim();
82
+ const hasDiff = trimmedOutput !== '';
83
+ // Parse output based on mode
84
+ if (args.nameOnly) {
85
+ const files = trimmedOutput ? trimmedOutput.split('\n').filter((f) => f.trim()) : [];
86
+ return {
87
+ success: true,
88
+ hasDiff,
89
+ files,
90
+ };
91
+ }
92
+ if (args.stat) {
93
+ return {
94
+ success: true,
95
+ hasDiff,
96
+ stat: trimmedOutput,
97
+ };
98
+ }
99
+ return {
100
+ success: true,
101
+ hasDiff,
102
+ diff: trimmedOutput,
103
+ };
104
+ }
105
+ catch (error) {
106
+ const errorMessage = error instanceof Error ? error.message : String(error);
107
+ return {
108
+ success: false,
109
+ error: errorMessage,
110
+ };
111
+ }
112
+ }
113
+ /**
114
+ * Print help message
115
+ */
116
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
117
+ function printHelp() {
118
+ console.log(`
119
+ Usage: git-diff [ref] [options] [-- path]
120
+
121
+ Show changes between commits, commit and working tree, etc.
122
+
123
+ Arguments:
124
+ ref Reference to diff against (e.g., HEAD~1, main)
125
+ path Path to filter diff
126
+
127
+ Options:
128
+ --base-dir <dir> Base directory for git operations
129
+ --staged, --cached Show staged changes
130
+ --name-only Show only names of changed files
131
+ --stat Show diffstat
132
+ -h, --help Show this help message
133
+
134
+ Examples:
135
+ git-diff
136
+ git-diff --staged
137
+ git-diff HEAD~1
138
+ git-diff --name-only
139
+ git-diff -- src/index.ts
140
+ `);
141
+ }
142
+ /**
143
+ * Main entry point
144
+ */
145
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
146
+ async function main() {
147
+ const args = parseGitDiffArgs(process.argv);
148
+ if (args.help) {
149
+ printHelp();
150
+ process.exit(0);
151
+ }
152
+ const result = await getGitDiff(args);
153
+ if (result.success) {
154
+ if (result.files) {
155
+ // Name-only mode
156
+ result.files.forEach((f) => console.log(f));
157
+ }
158
+ else if (result.stat) {
159
+ // Stat mode
160
+ console.log(result.stat);
161
+ }
162
+ else if (result.diff) {
163
+ // Full diff
164
+ console.log(result.diff);
165
+ }
166
+ // Empty diff produces no output
167
+ }
168
+ else {
169
+ console.error(`Error: ${result.error}`);
170
+ process.exit(1);
171
+ }
172
+ }
173
+ // Run main if executed directly
174
+ import { runCLI } from './cli-entry-point.js';
175
+ if (import.meta.main) {
176
+ runCLI(main);
177
+ }