@lumenflow/cli 1.6.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.
Files changed (39) hide show
  1. package/dist/__tests__/backlog-prune.test.js +478 -0
  2. package/dist/__tests__/deps-operations.test.js +206 -0
  3. package/dist/__tests__/file-operations.test.js +906 -0
  4. package/dist/__tests__/git-operations.test.js +668 -0
  5. package/dist/__tests__/guards-validation.test.js +416 -0
  6. package/dist/__tests__/init-plan.test.js +340 -0
  7. package/dist/__tests__/lumenflow-upgrade.test.js +107 -0
  8. package/dist/__tests__/metrics-cli.test.js +619 -0
  9. package/dist/__tests__/rotate-progress.test.js +127 -0
  10. package/dist/__tests__/session-coordinator.test.js +109 -0
  11. package/dist/__tests__/state-bootstrap.test.js +432 -0
  12. package/dist/__tests__/trace-gen.test.js +115 -0
  13. package/dist/backlog-prune.js +299 -0
  14. package/dist/deps-add.js +215 -0
  15. package/dist/deps-remove.js +94 -0
  16. package/dist/file-delete.js +236 -0
  17. package/dist/file-edit.js +247 -0
  18. package/dist/file-read.js +197 -0
  19. package/dist/file-write.js +220 -0
  20. package/dist/git-branch.js +187 -0
  21. package/dist/git-diff.js +177 -0
  22. package/dist/git-log.js +230 -0
  23. package/dist/git-status.js +208 -0
  24. package/dist/guard-locked.js +169 -0
  25. package/dist/guard-main-branch.js +202 -0
  26. package/dist/guard-worktree-commit.js +160 -0
  27. package/dist/init-plan.js +337 -0
  28. package/dist/lumenflow-upgrade.js +178 -0
  29. package/dist/metrics-cli.js +433 -0
  30. package/dist/rotate-progress.js +247 -0
  31. package/dist/session-coordinator.js +300 -0
  32. package/dist/state-bootstrap.js +307 -0
  33. package/dist/trace-gen.js +331 -0
  34. package/dist/validate-agent-skills.js +218 -0
  35. package/dist/validate-agent-sync.js +148 -0
  36. package/dist/validate-backlog-sync.js +152 -0
  37. package/dist/validate-skills-spec.js +206 -0
  38. package/dist/validate.js +230 -0
  39. package/package.json +34 -6
@@ -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
+ }
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git Log CLI Tool
4
+ *
5
+ * Provides WU-aware git log with:
6
+ * - Oneline and custom format output
7
+ * - Max count limiting
8
+ * - Date and author filtering
9
+ *
10
+ * Usage:
11
+ * node git-log.js [ref] [--oneline] [-n <count>] [--format <format>]
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-log
18
+ */
19
+ export function parseGitLogArgs(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 === '--oneline') {
29
+ args.oneline = true;
30
+ }
31
+ else if (arg === '-n') {
32
+ const val = cliArgs[++i];
33
+ if (val)
34
+ args.maxCount = parseInt(val, 10);
35
+ }
36
+ else if (arg === '--max-count') {
37
+ const val = cliArgs[++i];
38
+ if (val)
39
+ args.maxCount = parseInt(val, 10);
40
+ }
41
+ else if (arg.startsWith('-n') && arg.length > 2) {
42
+ // Handle -n5 format
43
+ args.maxCount = parseInt(arg.slice(2), 10);
44
+ }
45
+ else if (arg === '--format') {
46
+ args.format = cliArgs[++i];
47
+ }
48
+ else if (arg === '--since') {
49
+ args.since = cliArgs[++i];
50
+ }
51
+ else if (arg === '--author') {
52
+ args.author = cliArgs[++i];
53
+ }
54
+ else if (arg === '--base-dir') {
55
+ args.baseDir = cliArgs[++i];
56
+ }
57
+ else if (!arg.startsWith('-') && !args.ref) {
58
+ // Positional argument for ref
59
+ args.ref = arg;
60
+ }
61
+ }
62
+ return args;
63
+ }
64
+ /**
65
+ * Parse structured log output
66
+ */
67
+ function parseLogOutput(output) {
68
+ if (!output.trim()) {
69
+ return [];
70
+ }
71
+ const commits = [];
72
+ // Parse format: hash|message|author|date (separated by |||)
73
+ const lines = output.trim().split('\n');
74
+ for (const line of lines) {
75
+ if (!line.trim())
76
+ continue;
77
+ const parts = line.split('|||');
78
+ if (parts.length >= 2) {
79
+ commits.push({
80
+ hash: parts[0].trim(),
81
+ message: parts[1].trim(),
82
+ author: parts[2]?.trim(),
83
+ date: parts[3]?.trim(),
84
+ });
85
+ }
86
+ else {
87
+ // Fallback for oneline format (hash + message)
88
+ const match = line.match(/^([a-f0-9]+)\s+(.*)$/);
89
+ if (match) {
90
+ commits.push({
91
+ hash: match[1],
92
+ message: match[2],
93
+ });
94
+ }
95
+ }
96
+ }
97
+ return commits;
98
+ }
99
+ /**
100
+ * Get git log with audit logging
101
+ */
102
+ export async function getGitLog(args) {
103
+ try {
104
+ const git = args.baseDir ? createGitForPath(args.baseDir) : getGitForCwd();
105
+ // Build log arguments
106
+ const rawArgs = ['log'];
107
+ if (args.maxCount) {
108
+ rawArgs.push(`-n`, String(args.maxCount));
109
+ }
110
+ if (args.oneline) {
111
+ rawArgs.push('--oneline');
112
+ }
113
+ else if (args.format) {
114
+ rawArgs.push(`--format=${args.format}`);
115
+ }
116
+ else {
117
+ // Use structured format for parsing
118
+ rawArgs.push('--format=%H|||%s|||%an|||%ai');
119
+ }
120
+ if (args.since) {
121
+ rawArgs.push(`--since=${args.since}`);
122
+ }
123
+ if (args.author) {
124
+ rawArgs.push(`--author=${args.author}`);
125
+ }
126
+ if (args.ref) {
127
+ rawArgs.push(args.ref);
128
+ }
129
+ const output = await git.raw(rawArgs);
130
+ const trimmedOutput = output.trim();
131
+ // Parse commits
132
+ const commits = args.oneline || args.format ? [] : parseLogOutput(trimmedOutput);
133
+ return {
134
+ success: true,
135
+ commits,
136
+ output: args.oneline || args.format ? trimmedOutput : undefined,
137
+ };
138
+ }
139
+ catch (error) {
140
+ const errorMessage = error instanceof Error ? error.message : String(error);
141
+ // Handle case of repo with no commits
142
+ if (errorMessage.includes('does not have any commits') ||
143
+ errorMessage.includes('fatal: bad revision') ||
144
+ errorMessage.includes("fatal: your current branch 'main' does not have any commits")) {
145
+ return {
146
+ success: true,
147
+ commits: [],
148
+ };
149
+ }
150
+ return {
151
+ success: false,
152
+ error: errorMessage,
153
+ commits: [],
154
+ };
155
+ }
156
+ }
157
+ /**
158
+ * Print help message
159
+ */
160
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
161
+ function printHelp() {
162
+ console.log(`
163
+ Usage: git-log [ref] [options]
164
+
165
+ Show commit logs.
166
+
167
+ Arguments:
168
+ ref Revision range (e.g., main..feature)
169
+
170
+ Options:
171
+ --base-dir <dir> Base directory for git operations
172
+ --oneline Show each commit on a single line
173
+ -n <number> Limit the number of commits
174
+ --max-count <number> Limit the number of commits
175
+ --format <format> Pretty-print format string
176
+ --since <date> Show commits more recent than a date
177
+ --author <pattern> Limit to commits by author
178
+ -h, --help Show this help message
179
+
180
+ Examples:
181
+ git-log
182
+ git-log --oneline
183
+ git-log -n 10
184
+ git-log main..feature
185
+ git-log --since="2024-01-01"
186
+ git-log --author="test@example.com"
187
+ `);
188
+ }
189
+ /**
190
+ * Main entry point
191
+ */
192
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
193
+ async function main() {
194
+ const args = parseGitLogArgs(process.argv);
195
+ if (args.help) {
196
+ printHelp();
197
+ process.exit(0);
198
+ }
199
+ const result = await getGitLog(args);
200
+ if (result.success) {
201
+ if (result.output !== undefined) {
202
+ // Custom format or oneline mode
203
+ if (result.output) {
204
+ console.log(result.output);
205
+ }
206
+ }
207
+ else {
208
+ // Structured output
209
+ for (const commit of result.commits) {
210
+ console.log(`commit ${commit.hash}`);
211
+ if (commit.author)
212
+ console.log(`Author: ${commit.author}`);
213
+ if (commit.date)
214
+ console.log(`Date: ${commit.date}`);
215
+ console.log('');
216
+ console.log(` ${commit.message}`);
217
+ console.log('');
218
+ }
219
+ }
220
+ }
221
+ else {
222
+ console.error(`Error: ${result.error}`);
223
+ process.exit(1);
224
+ }
225
+ }
226
+ // Run main if executed directly
227
+ import { runCLI } from './cli-entry-point.js';
228
+ if (import.meta.main) {
229
+ runCLI(main);
230
+ }
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git Status CLI Tool
4
+ *
5
+ * Provides WU-aware git status with:
6
+ * - Porcelain and short output formats
7
+ * - Parsed file status (staged, modified, untracked)
8
+ * - Clean/dirty state detection
9
+ *
10
+ * Usage:
11
+ * node git-status.js [path] [--porcelain] [--short]
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-status
18
+ */
19
+ export function parseGitStatusArgs(argv) {
20
+ const args = {
21
+ porcelain: false,
22
+ short: false,
23
+ };
24
+ // Skip node and script name
25
+ const cliArgs = argv.slice(2);
26
+ for (let i = 0; i < cliArgs.length; i++) {
27
+ const arg = cliArgs[i];
28
+ if (arg === '--help' || arg === '-h') {
29
+ args.help = true;
30
+ }
31
+ else if (arg === '--porcelain') {
32
+ args.porcelain = true;
33
+ }
34
+ else if (arg === '--short' || arg === '-s') {
35
+ args.short = true;
36
+ }
37
+ else if (arg === '--base-dir') {
38
+ args.baseDir = cliArgs[++i];
39
+ }
40
+ else if (!arg.startsWith('-')) {
41
+ // Positional argument for path
42
+ args.path = arg;
43
+ }
44
+ }
45
+ return args;
46
+ }
47
+ /**
48
+ * Parse porcelain status output into categorized files
49
+ */
50
+ function parseStatusOutput(output) {
51
+ const staged = [];
52
+ const modified = [];
53
+ const untracked = [];
54
+ const deleted = [];
55
+ // Don't filter based on trim - leading spaces are significant in git status
56
+ const lines = output.split('\n').filter((line) => line.length > 0);
57
+ for (const line of lines) {
58
+ if (line.length < 3)
59
+ continue;
60
+ const indexStatus = line[0];
61
+ const workTreeStatus = line[1];
62
+ const filePath = line.slice(3).trim();
63
+ // Handle renames (e.g., "R old -> new")
64
+ const fileName = filePath.includes(' -> ') ? filePath.split(' -> ')[1] : filePath;
65
+ // Untracked files
66
+ if (indexStatus === '?' && workTreeStatus === '?') {
67
+ untracked.push(fileName);
68
+ continue;
69
+ }
70
+ // Staged changes (index has status)
71
+ if (indexStatus !== ' ' && indexStatus !== '?') {
72
+ staged.push(fileName);
73
+ if (indexStatus === 'D') {
74
+ deleted.push(fileName);
75
+ }
76
+ }
77
+ // Working tree changes (unstaged modifications)
78
+ if (workTreeStatus === 'M') {
79
+ modified.push(fileName);
80
+ }
81
+ else if (workTreeStatus === 'D' && indexStatus === ' ') {
82
+ // Only count as deleted in working tree if not already staged for deletion
83
+ deleted.push(fileName);
84
+ }
85
+ }
86
+ return { staged, modified, untracked, deleted };
87
+ }
88
+ /**
89
+ * Get git status with audit logging
90
+ */
91
+ export async function getGitStatus(args) {
92
+ try {
93
+ const git = args.baseDir ? createGitForPath(args.baseDir) : getGitForCwd();
94
+ // Get porcelain status
95
+ const rawArgs = ['status', '--porcelain'];
96
+ if (args.path) {
97
+ rawArgs.push('--', args.path);
98
+ }
99
+ const output = await git.raw(rawArgs);
100
+ // Don't trim the entire output - leading spaces in lines are significant for git status
101
+ // Only trim trailing newlines
102
+ const trimmedOutput = output.replace(/\n+$/, '');
103
+ const isClean = trimmedOutput === '';
104
+ // If porcelain mode requested, return raw output
105
+ if (args.porcelain) {
106
+ return {
107
+ success: true,
108
+ isClean,
109
+ output: trimmedOutput,
110
+ };
111
+ }
112
+ // Parse the status output
113
+ const { staged, modified, untracked, deleted } = parseStatusOutput(trimmedOutput);
114
+ return {
115
+ success: true,
116
+ isClean,
117
+ staged,
118
+ modified,
119
+ untracked,
120
+ deleted,
121
+ output: args.short ? trimmedOutput : undefined,
122
+ };
123
+ }
124
+ catch (error) {
125
+ const errorMessage = error instanceof Error ? error.message : String(error);
126
+ return {
127
+ success: false,
128
+ error: errorMessage,
129
+ };
130
+ }
131
+ }
132
+ /**
133
+ * Print help message
134
+ */
135
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
136
+ function printHelp() {
137
+ console.log(`
138
+ Usage: git-status [path] [options]
139
+
140
+ Show the working tree status.
141
+
142
+ Arguments:
143
+ path Path to filter status
144
+
145
+ Options:
146
+ --base-dir <dir> Base directory for git operations
147
+ --porcelain Give the output in an easy-to-parse format
148
+ --short, -s Give the output in short format
149
+ -h, --help Show this help message
150
+
151
+ Examples:
152
+ git-status
153
+ git-status src/
154
+ git-status --porcelain
155
+ git-status --short
156
+ `);
157
+ }
158
+ /**
159
+ * Main entry point
160
+ */
161
+ /* istanbul ignore next -- CLI entry point tested via subprocess */
162
+ async function main() {
163
+ const args = parseGitStatusArgs(process.argv);
164
+ if (args.help) {
165
+ printHelp();
166
+ process.exit(0);
167
+ }
168
+ const result = await getGitStatus(args);
169
+ if (result.success) {
170
+ if (result.output !== undefined) {
171
+ // Porcelain or short mode
172
+ if (result.output) {
173
+ console.log(result.output);
174
+ }
175
+ }
176
+ else {
177
+ // Human-readable output
178
+ if (result.isClean) {
179
+ console.log('nothing to commit, working tree clean');
180
+ }
181
+ else {
182
+ if (result.staged && result.staged.length > 0) {
183
+ console.log('Changes to be committed:');
184
+ result.staged.forEach((f) => console.log(` ${f}`));
185
+ console.log('');
186
+ }
187
+ if (result.modified && result.modified.length > 0) {
188
+ console.log('Changes not staged for commit:');
189
+ result.modified.forEach((f) => console.log(` modified: ${f}`));
190
+ console.log('');
191
+ }
192
+ if (result.untracked && result.untracked.length > 0) {
193
+ console.log('Untracked files:');
194
+ result.untracked.forEach((f) => console.log(` ${f}`));
195
+ }
196
+ }
197
+ }
198
+ }
199
+ else {
200
+ console.error(`Error: ${result.error}`);
201
+ process.exit(1);
202
+ }
203
+ }
204
+ // Run main if executed directly
205
+ import { runCLI } from './cli-entry-point.js';
206
+ if (import.meta.main) {
207
+ runCLI(main);
208
+ }