@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.
- package/dist/__tests__/backlog-prune.test.js +478 -0
- package/dist/__tests__/deps-operations.test.js +206 -0
- package/dist/__tests__/file-operations.test.js +906 -0
- package/dist/__tests__/git-operations.test.js +668 -0
- package/dist/__tests__/guards-validation.test.js +416 -0
- package/dist/__tests__/init-plan.test.js +340 -0
- package/dist/__tests__/lumenflow-upgrade.test.js +107 -0
- package/dist/__tests__/metrics-cli.test.js +619 -0
- package/dist/__tests__/rotate-progress.test.js +127 -0
- package/dist/__tests__/session-coordinator.test.js +109 -0
- package/dist/__tests__/state-bootstrap.test.js +432 -0
- package/dist/__tests__/trace-gen.test.js +115 -0
- package/dist/backlog-prune.js +299 -0
- package/dist/deps-add.js +215 -0
- package/dist/deps-remove.js +94 -0
- package/dist/file-delete.js +236 -0
- package/dist/file-edit.js +247 -0
- package/dist/file-read.js +197 -0
- package/dist/file-write.js +220 -0
- package/dist/git-branch.js +187 -0
- package/dist/git-diff.js +177 -0
- package/dist/git-log.js +230 -0
- package/dist/git-status.js +208 -0
- package/dist/guard-locked.js +169 -0
- package/dist/guard-main-branch.js +202 -0
- package/dist/guard-worktree-commit.js +160 -0
- package/dist/init-plan.js +337 -0
- package/dist/lumenflow-upgrade.js +178 -0
- package/dist/metrics-cli.js +433 -0
- package/dist/rotate-progress.js +247 -0
- package/dist/session-coordinator.js +300 -0
- package/dist/state-bootstrap.js +307 -0
- package/dist/trace-gen.js +331 -0
- package/dist/validate-agent-skills.js +218 -0
- package/dist/validate-agent-sync.js +148 -0
- package/dist/validate-backlog-sync.js +152 -0
- package/dist/validate-skills-spec.js +206 -0
- package/dist/validate.js +230 -0
- package/package.json +34 -6
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file validate-backlog-sync.ts
|
|
4
|
+
* @description Validates backlog.md is in sync with WU YAML files (WU-1111)
|
|
5
|
+
*
|
|
6
|
+
* Checks that all WU YAML files are referenced in backlog.md and vice versa.
|
|
7
|
+
* This is the TypeScript replacement for tools/validate-backlog-sync.js.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* validate-backlog-sync # Validate sync
|
|
11
|
+
*
|
|
12
|
+
* Exit codes:
|
|
13
|
+
* 0 - Backlog is in sync
|
|
14
|
+
* 1 - Sync issues found
|
|
15
|
+
*
|
|
16
|
+
* @see {@link docs/04-operations/tasks/backlog.md} - Backlog file
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
22
|
+
const LOG_PREFIX = '[validate-backlog-sync]';
|
|
23
|
+
/**
|
|
24
|
+
* Extract WU IDs from backlog.md content
|
|
25
|
+
*
|
|
26
|
+
* @param content - backlog.md content
|
|
27
|
+
* @returns Array of WU IDs found
|
|
28
|
+
*/
|
|
29
|
+
function extractWUIDsFromBacklog(content) {
|
|
30
|
+
const wuIds = [];
|
|
31
|
+
const pattern = /WU-\d+/gi;
|
|
32
|
+
let match;
|
|
33
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
34
|
+
const wuId = match[0].toUpperCase();
|
|
35
|
+
if (!wuIds.includes(wuId)) {
|
|
36
|
+
wuIds.push(wuId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return wuIds;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get all WU IDs from YAML files
|
|
43
|
+
*
|
|
44
|
+
* @param wuDir - Path to WU directory
|
|
45
|
+
* @returns Array of WU IDs
|
|
46
|
+
*/
|
|
47
|
+
function getWUIDsFromFiles(wuDir) {
|
|
48
|
+
if (!existsSync(wuDir)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return readdirSync(wuDir)
|
|
52
|
+
.filter((f) => f.endsWith('.yaml'))
|
|
53
|
+
.map((f) => f.replace('.yaml', '').toUpperCase());
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validate that backlog.md is in sync with WU YAML files
|
|
57
|
+
*
|
|
58
|
+
* @param options - Validation options
|
|
59
|
+
* @param options.cwd - Working directory (default: process.cwd())
|
|
60
|
+
* @returns Validation result
|
|
61
|
+
*/
|
|
62
|
+
export async function validateBacklogSync(options = {}) {
|
|
63
|
+
const { cwd = process.cwd() } = options;
|
|
64
|
+
const errors = [];
|
|
65
|
+
const warnings = [];
|
|
66
|
+
// Paths
|
|
67
|
+
const backlogPath = path.join(cwd, 'docs', '04-operations', 'tasks', 'backlog.md');
|
|
68
|
+
const wuDir = path.join(cwd, 'docs', '04-operations', 'tasks', 'wu');
|
|
69
|
+
// Check backlog.md exists
|
|
70
|
+
if (!existsSync(backlogPath)) {
|
|
71
|
+
errors.push(`Backlog file not found: ${backlogPath}`);
|
|
72
|
+
return { valid: false, errors, warnings, wuCount: 0, backlogCount: 0 };
|
|
73
|
+
}
|
|
74
|
+
// Get WU IDs from files
|
|
75
|
+
const wuIdsFromFiles = getWUIDsFromFiles(wuDir);
|
|
76
|
+
// Get WU IDs from backlog
|
|
77
|
+
const backlogContent = readFileSync(backlogPath, {
|
|
78
|
+
encoding: FILE_SYSTEM.UTF8,
|
|
79
|
+
});
|
|
80
|
+
const wuIdsFromBacklog = extractWUIDsFromBacklog(backlogContent);
|
|
81
|
+
// Check for WUs in files but not in backlog
|
|
82
|
+
for (const wuId of wuIdsFromFiles) {
|
|
83
|
+
if (!wuIdsFromBacklog.includes(wuId)) {
|
|
84
|
+
errors.push(`${wuId} not found in backlog.md (exists as ${wuId}.yaml)`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Check for WUs in backlog but not as files (warning only)
|
|
88
|
+
for (const wuId of wuIdsFromBacklog) {
|
|
89
|
+
if (!wuIdsFromFiles.includes(wuId)) {
|
|
90
|
+
warnings.push(`${wuId} referenced in backlog.md but ${wuId}.yaml not found`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
valid: errors.length === 0,
|
|
95
|
+
errors,
|
|
96
|
+
warnings,
|
|
97
|
+
wuCount: wuIdsFromFiles.length,
|
|
98
|
+
backlogCount: wuIdsFromBacklog.length,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Main CLI entry point
|
|
103
|
+
*/
|
|
104
|
+
async function main() {
|
|
105
|
+
const args = process.argv.slice(2);
|
|
106
|
+
// Parse arguments
|
|
107
|
+
let cwd = process.cwd();
|
|
108
|
+
for (let i = 0; i < args.length; i++) {
|
|
109
|
+
const arg = args[i];
|
|
110
|
+
if (arg === '--cwd' || arg === '-C') {
|
|
111
|
+
cwd = args[++i];
|
|
112
|
+
}
|
|
113
|
+
else if (arg === '--help' || arg === '-h') {
|
|
114
|
+
console.log(`Usage: validate-backlog-sync [options]
|
|
115
|
+
|
|
116
|
+
Validate that backlog.md is in sync with WU YAML files.
|
|
117
|
+
|
|
118
|
+
Options:
|
|
119
|
+
--cwd, -C DIR Working directory (default: current directory)
|
|
120
|
+
-h, --help Show this help message
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
validate-backlog-sync
|
|
124
|
+
`);
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
console.log(`${LOG_PREFIX} Validating backlog sync...`);
|
|
129
|
+
const result = await validateBacklogSync({ cwd });
|
|
130
|
+
if (result.errors.length > 0) {
|
|
131
|
+
console.log(`${EMOJI.FAILURE} Sync errors:`);
|
|
132
|
+
result.errors.forEach((e) => console.log(` ${e}`));
|
|
133
|
+
}
|
|
134
|
+
if (result.warnings.length > 0) {
|
|
135
|
+
console.log(`${EMOJI.WARNING} Warnings:`);
|
|
136
|
+
result.warnings.forEach((w) => console.log(` ${w}`));
|
|
137
|
+
}
|
|
138
|
+
console.log(`${LOG_PREFIX} WU files: ${result.wuCount}, Backlog references: ${result.backlogCount}`);
|
|
139
|
+
if (result.valid) {
|
|
140
|
+
console.log(`${EMOJI.SUCCESS} Backlog is in sync`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Guard main() for testability
|
|
147
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
148
|
+
main().catch((error) => {
|
|
149
|
+
console.error(`${LOG_PREFIX} Unexpected error:`, error);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file validate-skills-spec.ts
|
|
4
|
+
* @description Validates skills spec format (WU-1111)
|
|
5
|
+
*
|
|
6
|
+
* Validates that skill specification files follow the required format
|
|
7
|
+
* with proper sections and structure.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* validate-skills-spec SKILL.md # Validate specific file
|
|
11
|
+
* validate-skills-spec --dir ./skills # Validate all in directory
|
|
12
|
+
*
|
|
13
|
+
* Exit codes:
|
|
14
|
+
* 0 - Validation passed
|
|
15
|
+
* 1 - Validation errors found
|
|
16
|
+
*
|
|
17
|
+
* @see {@link .claude/skills/} - Skill definitions
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
23
|
+
const LOG_PREFIX = '[validate-skills-spec]';
|
|
24
|
+
/**
|
|
25
|
+
* Required sections for a valid skill spec
|
|
26
|
+
*/
|
|
27
|
+
const REQUIRED_SECTIONS = ['When to Use'];
|
|
28
|
+
/**
|
|
29
|
+
* Recommended sections (produce warnings if missing)
|
|
30
|
+
*/
|
|
31
|
+
const RECOMMENDED_SECTIONS = ['Examples'];
|
|
32
|
+
/**
|
|
33
|
+
* Validate a skill specification file
|
|
34
|
+
*
|
|
35
|
+
* @param skillPath - Path to skill spec file
|
|
36
|
+
* @returns Validation result
|
|
37
|
+
*/
|
|
38
|
+
export function validateSkillsSpec(skillPath) {
|
|
39
|
+
const errors = [];
|
|
40
|
+
const warnings = [];
|
|
41
|
+
if (!existsSync(skillPath)) {
|
|
42
|
+
errors.push(`Skill file not found: ${skillPath}`);
|
|
43
|
+
return { valid: false, errors, warnings };
|
|
44
|
+
}
|
|
45
|
+
const content = readFileSync(skillPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
46
|
+
const lines = content.split('\n');
|
|
47
|
+
// Check for title heading (# Title)
|
|
48
|
+
const hasTitle = lines.some((line) => /^#\s+\S/.test(line));
|
|
49
|
+
if (!hasTitle) {
|
|
50
|
+
errors.push('Missing title heading (# Skill Name)');
|
|
51
|
+
}
|
|
52
|
+
// Check for required sections
|
|
53
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
54
|
+
const sectionPattern = new RegExp(`^##\\s+${section}`, 'im');
|
|
55
|
+
if (!sectionPattern.test(content)) {
|
|
56
|
+
errors.push(`Missing required section: "## ${section}"`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Check for recommended sections
|
|
60
|
+
for (const section of RECOMMENDED_SECTIONS) {
|
|
61
|
+
const sectionPattern = new RegExp(`^##\\s+${section}`, 'im');
|
|
62
|
+
if (!sectionPattern.test(content)) {
|
|
63
|
+
warnings.push(`Missing recommended section: "## ${section}"`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Check "When to Use" section has content
|
|
67
|
+
const whenToUseMatch = content.match(/##\s+When to Use\s*\n([\s\S]*?)(?=\n##|$)/i);
|
|
68
|
+
if (whenToUseMatch) {
|
|
69
|
+
const sectionContent = whenToUseMatch[1].trim();
|
|
70
|
+
if (sectionContent.length < 20) {
|
|
71
|
+
warnings.push('"When to Use" section has minimal content');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Check for minimum overall content
|
|
75
|
+
if (content.length < 100) {
|
|
76
|
+
warnings.push('Skill spec is very short (< 100 characters)');
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
valid: errors.length === 0,
|
|
80
|
+
errors,
|
|
81
|
+
warnings,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Validate all skill specs in a directory
|
|
86
|
+
*
|
|
87
|
+
* @param dir - Directory to scan
|
|
88
|
+
* @returns Map of file path to validation result
|
|
89
|
+
*/
|
|
90
|
+
export function validateSkillsSpecDir(dir) {
|
|
91
|
+
const results = new Map();
|
|
92
|
+
if (!existsSync(dir)) {
|
|
93
|
+
results.set(dir, {
|
|
94
|
+
valid: false,
|
|
95
|
+
errors: [`Directory not found: ${dir}`],
|
|
96
|
+
warnings: [],
|
|
97
|
+
});
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
100
|
+
const entries = readdirSync(dir);
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
const entryPath = path.join(dir, entry);
|
|
103
|
+
const stat = statSync(entryPath);
|
|
104
|
+
if (stat.isDirectory()) {
|
|
105
|
+
// Check for SKILL.md in subdirectory
|
|
106
|
+
const skillFile = path.join(entryPath, 'SKILL.md');
|
|
107
|
+
if (existsSync(skillFile)) {
|
|
108
|
+
results.set(entry, validateSkillsSpec(skillFile));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (entry.endsWith('.md')) {
|
|
112
|
+
results.set(entry, validateSkillsSpec(entryPath));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Main CLI entry point
|
|
119
|
+
*/
|
|
120
|
+
async function main() {
|
|
121
|
+
const args = process.argv.slice(2);
|
|
122
|
+
// Parse arguments
|
|
123
|
+
let skillPath;
|
|
124
|
+
let dir;
|
|
125
|
+
for (let i = 0; i < args.length; i++) {
|
|
126
|
+
const arg = args[i];
|
|
127
|
+
if (arg === '--dir' || arg === '-d') {
|
|
128
|
+
dir = args[++i];
|
|
129
|
+
}
|
|
130
|
+
else if (arg === '--help' || arg === '-h') {
|
|
131
|
+
console.log(`Usage: validate-skills-spec [file.md] [options]
|
|
132
|
+
|
|
133
|
+
Validate skill specification format.
|
|
134
|
+
|
|
135
|
+
Options:
|
|
136
|
+
--dir, -d DIR Validate all specs in directory
|
|
137
|
+
-h, --help Show this help message
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
validate-skills-spec SKILL.md
|
|
141
|
+
validate-skills-spec --dir .claude/skills
|
|
142
|
+
`);
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
else if (!skillPath) {
|
|
146
|
+
skillPath = arg;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!skillPath && !dir) {
|
|
150
|
+
console.error(`${LOG_PREFIX} Error: Provide a skill file or --dir option`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
if (skillPath) {
|
|
154
|
+
// Validate single file
|
|
155
|
+
console.log(`${LOG_PREFIX} Validating ${skillPath}...`);
|
|
156
|
+
const result = validateSkillsSpec(skillPath);
|
|
157
|
+
if (result.errors.length > 0) {
|
|
158
|
+
console.log(`${EMOJI.FAILURE} Validation failed:`);
|
|
159
|
+
result.errors.forEach((e) => console.log(` ${e}`));
|
|
160
|
+
}
|
|
161
|
+
if (result.warnings.length > 0) {
|
|
162
|
+
console.log(`${EMOJI.WARNING} Warnings:`);
|
|
163
|
+
result.warnings.forEach((w) => console.log(` ${w}`));
|
|
164
|
+
}
|
|
165
|
+
if (result.valid) {
|
|
166
|
+
console.log(`${EMOJI.SUCCESS} Skill spec is valid`);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (dir) {
|
|
173
|
+
// Validate directory
|
|
174
|
+
console.log(`${LOG_PREFIX} Validating skills in ${dir}...`);
|
|
175
|
+
const results = validateSkillsSpecDir(dir);
|
|
176
|
+
let totalValid = 0;
|
|
177
|
+
let totalInvalid = 0;
|
|
178
|
+
for (const [name, result] of results) {
|
|
179
|
+
if (result.errors.length > 0) {
|
|
180
|
+
console.log(`${EMOJI.FAILURE} ${name}:`);
|
|
181
|
+
result.errors.forEach((e) => console.log(` ${e}`));
|
|
182
|
+
totalInvalid++;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
totalValid++;
|
|
186
|
+
}
|
|
187
|
+
if (result.warnings.length > 0) {
|
|
188
|
+
console.log(`${EMOJI.WARNING} ${name}: ${result.warnings.length} warning(s)`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log(`${LOG_PREFIX} Summary:`);
|
|
193
|
+
console.log(` ${EMOJI.SUCCESS} Valid: ${totalValid}`);
|
|
194
|
+
console.log(` ${EMOJI.FAILURE} Invalid: ${totalInvalid}`);
|
|
195
|
+
if (totalInvalid > 0) {
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Guard main() for testability
|
|
201
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
202
|
+
main().catch((error) => {
|
|
203
|
+
console.error(`${LOG_PREFIX} Unexpected error:`, error);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
});
|
|
206
|
+
}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file validate.ts
|
|
4
|
+
* @description Main WU YAML validator CLI (WU-1111)
|
|
5
|
+
*
|
|
6
|
+
* Validates WU tasks and status consistency. This is the replacement for
|
|
7
|
+
* tools/validate.js that was previously a stub.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* validate # Validate all WUs
|
|
11
|
+
* validate --id WU-123 # Validate specific WU
|
|
12
|
+
* validate --strict # Fail on warnings too
|
|
13
|
+
* validate --done-only # Only validate done WUs
|
|
14
|
+
*
|
|
15
|
+
* Exit codes:
|
|
16
|
+
* 0 - All validations passed
|
|
17
|
+
* 1 - Validation errors found
|
|
18
|
+
*
|
|
19
|
+
* @see {@link wu-validate.ts} - Detailed WU validation with schema
|
|
20
|
+
* @see {@link docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md} - WU lifecycle
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
25
|
+
import { parseYAML } from '@lumenflow/core/dist/wu-yaml.js';
|
|
26
|
+
import { validateWU, validateWUCompleteness } from '@lumenflow/core/dist/wu-schema.js';
|
|
27
|
+
import { FILE_SYSTEM, EMOJI, PATTERNS } from '@lumenflow/core/dist/wu-constants.js';
|
|
28
|
+
const LOG_PREFIX = '[validate]';
|
|
29
|
+
/**
|
|
30
|
+
* Validate a single WU file
|
|
31
|
+
*
|
|
32
|
+
* @param wuPath - Path to WU YAML file
|
|
33
|
+
* @param options - Validation options
|
|
34
|
+
* @returns Validation result
|
|
35
|
+
*/
|
|
36
|
+
export function validateSingleWU(wuPath, options = {}) {
|
|
37
|
+
const { strict = false } = options;
|
|
38
|
+
const errors = [];
|
|
39
|
+
const warnings = [];
|
|
40
|
+
// Check file exists
|
|
41
|
+
if (!existsSync(wuPath)) {
|
|
42
|
+
errors.push(`WU file not found: ${wuPath}`);
|
|
43
|
+
return { valid: false, warnings, errors };
|
|
44
|
+
}
|
|
45
|
+
// Read and parse YAML
|
|
46
|
+
let doc;
|
|
47
|
+
try {
|
|
48
|
+
const text = readFileSync(wuPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
49
|
+
doc = parseYAML(text);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
errors.push(`Failed to parse YAML: ${e.message}`);
|
|
53
|
+
return { valid: false, warnings, errors };
|
|
54
|
+
}
|
|
55
|
+
// Schema validation
|
|
56
|
+
const schemaResult = validateWU(doc);
|
|
57
|
+
if (!schemaResult.success) {
|
|
58
|
+
const schemaErrors = schemaResult.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`);
|
|
59
|
+
errors.push(...schemaErrors);
|
|
60
|
+
return { valid: false, warnings, errors };
|
|
61
|
+
}
|
|
62
|
+
// Completeness validation (soft warnings)
|
|
63
|
+
const completenessResult = validateWUCompleteness(schemaResult.data);
|
|
64
|
+
warnings.push(...completenessResult.warnings);
|
|
65
|
+
// In strict mode, warnings become errors
|
|
66
|
+
if (strict && warnings.length > 0) {
|
|
67
|
+
errors.push(...warnings.map((w) => `[STRICT] ${w}`));
|
|
68
|
+
return { valid: false, warnings: [], errors };
|
|
69
|
+
}
|
|
70
|
+
return { valid: true, warnings, errors };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate all WU files in the WU directory
|
|
74
|
+
*
|
|
75
|
+
* @param options - Validation options
|
|
76
|
+
* @returns Summary of all validations
|
|
77
|
+
*/
|
|
78
|
+
export function validateAllWUs(options = {}) {
|
|
79
|
+
const { strict = false, doneOnly = false } = options;
|
|
80
|
+
const wuDir = WU_PATHS.WU_DIR();
|
|
81
|
+
if (!existsSync(wuDir)) {
|
|
82
|
+
return {
|
|
83
|
+
totalValid: 0,
|
|
84
|
+
totalInvalid: 1,
|
|
85
|
+
totalWarnings: 0,
|
|
86
|
+
results: [
|
|
87
|
+
{
|
|
88
|
+
wuId: 'DIRECTORY',
|
|
89
|
+
valid: false,
|
|
90
|
+
warnings: [],
|
|
91
|
+
errors: [`WU directory not found: ${wuDir}`],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const files = readdirSync(wuDir).filter((f) => f.endsWith('.yaml'));
|
|
97
|
+
const results = [];
|
|
98
|
+
let totalValid = 0;
|
|
99
|
+
let totalInvalid = 0;
|
|
100
|
+
let totalWarnings = 0;
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
const wuPath = `${wuDir}/${file}`;
|
|
103
|
+
const wuId = file.replace('.yaml', '');
|
|
104
|
+
// Skip if only validating done WUs
|
|
105
|
+
if (doneOnly) {
|
|
106
|
+
try {
|
|
107
|
+
const text = readFileSync(wuPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
108
|
+
const doc = parseYAML(text);
|
|
109
|
+
if (doc.status !== 'done') {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// If we can't read, still validate to catch the error
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const result = validateSingleWU(wuPath, { strict });
|
|
118
|
+
results.push({ wuId, ...result });
|
|
119
|
+
if (result.valid) {
|
|
120
|
+
totalValid++;
|
|
121
|
+
totalWarnings += result.warnings.length;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
totalInvalid++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { totalValid, totalInvalid, totalWarnings, results };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Main CLI entry point
|
|
131
|
+
*/
|
|
132
|
+
async function main() {
|
|
133
|
+
const args = process.argv.slice(2);
|
|
134
|
+
// Parse arguments
|
|
135
|
+
let wuId;
|
|
136
|
+
let strict = false;
|
|
137
|
+
let doneOnly = false;
|
|
138
|
+
for (let i = 0; i < args.length; i++) {
|
|
139
|
+
const arg = args[i];
|
|
140
|
+
if (arg === '--id' || arg === '--wu') {
|
|
141
|
+
wuId = args[++i];
|
|
142
|
+
}
|
|
143
|
+
else if (arg === '--strict' || arg === '-s') {
|
|
144
|
+
strict = true;
|
|
145
|
+
}
|
|
146
|
+
else if (arg === '--done-only') {
|
|
147
|
+
doneOnly = true;
|
|
148
|
+
}
|
|
149
|
+
else if (arg === '--help' || arg === '-h') {
|
|
150
|
+
console.log(`Usage: validate [options]
|
|
151
|
+
|
|
152
|
+
Validate WU YAML files for schema and quality.
|
|
153
|
+
|
|
154
|
+
Options:
|
|
155
|
+
--id, --wu WU-XXX Validate specific WU
|
|
156
|
+
--strict, -s Fail on warnings too
|
|
157
|
+
--done-only Only validate done WUs
|
|
158
|
+
-h, --help Show this help message
|
|
159
|
+
|
|
160
|
+
Examples:
|
|
161
|
+
validate # Validate all WUs
|
|
162
|
+
validate --id WU-123 # Validate specific WU
|
|
163
|
+
validate --strict # Strict mode
|
|
164
|
+
`);
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}
|
|
167
|
+
else if (PATTERNS.WU_ID.test(arg.toUpperCase())) {
|
|
168
|
+
wuId = arg.toUpperCase();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (wuId) {
|
|
172
|
+
// Validate single WU
|
|
173
|
+
wuId = wuId.toUpperCase();
|
|
174
|
+
if (!PATTERNS.WU_ID.test(wuId)) {
|
|
175
|
+
console.error(`${LOG_PREFIX} Invalid WU ID: ${wuId}`);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const wuPath = WU_PATHS.WU(wuId);
|
|
179
|
+
console.log(`${LOG_PREFIX} Validating ${wuId}...`);
|
|
180
|
+
const result = validateSingleWU(wuPath, { strict });
|
|
181
|
+
if (result.errors.length > 0) {
|
|
182
|
+
console.log(`${EMOJI.FAILURE} Validation failed:`);
|
|
183
|
+
result.errors.forEach((e) => console.log(` ${e}`));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
if (result.warnings.length > 0) {
|
|
187
|
+
console.log(`${EMOJI.WARNING} Validation passed with warnings:`);
|
|
188
|
+
result.warnings.forEach((w) => console.log(` ${w}`));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(`${EMOJI.SUCCESS} ${wuId} is valid`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Validate all WUs
|
|
196
|
+
console.log(`${LOG_PREFIX} Validating all WUs${doneOnly ? ' (done only)' : ''}...`);
|
|
197
|
+
const { totalValid, totalInvalid, totalWarnings, results } = validateAllWUs({
|
|
198
|
+
strict,
|
|
199
|
+
doneOnly,
|
|
200
|
+
});
|
|
201
|
+
// Print results
|
|
202
|
+
for (const result of results) {
|
|
203
|
+
if (result.errors.length > 0) {
|
|
204
|
+
console.log(`${EMOJI.FAILURE} ${result.wuId}:`);
|
|
205
|
+
result.errors.forEach((e) => console.log(` ${e}`));
|
|
206
|
+
}
|
|
207
|
+
else if (result.warnings.length > 0) {
|
|
208
|
+
console.log(`${EMOJI.WARNING} ${result.wuId}: ${result.warnings.length} warning(s)`);
|
|
209
|
+
if (process.env.VERBOSE) {
|
|
210
|
+
result.warnings.forEach((w) => console.log(` ${w}`));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log(`${LOG_PREFIX} Summary:`);
|
|
216
|
+
console.log(` ${EMOJI.SUCCESS} Valid: ${totalValid}`);
|
|
217
|
+
console.log(` ${EMOJI.FAILURE} Invalid: ${totalInvalid}`);
|
|
218
|
+
console.log(` ${EMOJI.WARNING} Warnings: ${totalWarnings}`);
|
|
219
|
+
if (totalInvalid > 0) {
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Guard main() for testability
|
|
225
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
226
|
+
main().catch((error) => {
|
|
227
|
+
console.error(`${LOG_PREFIX} Unexpected error:`, error);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
});
|
|
230
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Command-line interface for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"initiative-list": "./dist/initiative-list.js",
|
|
69
69
|
"initiative-status": "./dist/initiative-status.js",
|
|
70
70
|
"initiative-add-wu": "./dist/initiative-add-wu.js",
|
|
71
|
+
"init-plan": "./dist/init-plan.js",
|
|
71
72
|
"agent-session": "./dist/agent-session.js",
|
|
72
73
|
"agent-session-end": "./dist/agent-session-end.js",
|
|
73
74
|
"agent-log-issue": "./dist/agent-log-issue.js",
|
|
@@ -78,6 +79,8 @@
|
|
|
78
79
|
"flow-report": "./dist/flow-report.js",
|
|
79
80
|
"flow-bottlenecks": "./dist/flow-bottlenecks.js",
|
|
80
81
|
"metrics-snapshot": "./dist/metrics-snapshot.js",
|
|
82
|
+
"metrics": "./dist/metrics-cli.js",
|
|
83
|
+
"lumenflow-metrics": "./dist/metrics-cli.js",
|
|
81
84
|
"initiative-bulk-assign-wus": "./dist/initiative-bulk-assign-wus.js",
|
|
82
85
|
"agent-issues-query": "./dist/agent-issues-query.js",
|
|
83
86
|
"gates": "./dist/gates.js",
|
|
@@ -85,7 +88,32 @@
|
|
|
85
88
|
"lumenflow-init": "./dist/init.js",
|
|
86
89
|
"lumenflow": "./dist/init.js",
|
|
87
90
|
"lumenflow-release": "./dist/release.js",
|
|
88
|
-
"lumenflow-docs-sync": "./dist/docs-sync.js"
|
|
91
|
+
"lumenflow-docs-sync": "./dist/docs-sync.js",
|
|
92
|
+
"backlog-prune": "./dist/backlog-prune.js",
|
|
93
|
+
"file-read": "./dist/file-read.js",
|
|
94
|
+
"file-write": "./dist/file-write.js",
|
|
95
|
+
"file-edit": "./dist/file-edit.js",
|
|
96
|
+
"file-delete": "./dist/file-delete.js",
|
|
97
|
+
"guard-worktree-commit": "./dist/guard-worktree-commit.js",
|
|
98
|
+
"guard-locked": "./dist/guard-locked.js",
|
|
99
|
+
"validate": "./dist/validate.js",
|
|
100
|
+
"lumenflow-validate": "./dist/validate.js",
|
|
101
|
+
"validate-agent-skills": "./dist/validate-agent-skills.js",
|
|
102
|
+
"validate-agent-sync": "./dist/validate-agent-sync.js",
|
|
103
|
+
"validate-backlog-sync": "./dist/validate-backlog-sync.js",
|
|
104
|
+
"validate-skills-spec": "./dist/validate-skills-spec.js",
|
|
105
|
+
"deps-add": "./dist/deps-add.js",
|
|
106
|
+
"deps-remove": "./dist/deps-remove.js",
|
|
107
|
+
"session-coordinator": "./dist/session-coordinator.js",
|
|
108
|
+
"rotate-progress": "./dist/rotate-progress.js",
|
|
109
|
+
"lumenflow-upgrade": "./dist/lumenflow-upgrade.js",
|
|
110
|
+
"trace-gen": "./dist/trace-gen.js",
|
|
111
|
+
"git-status": "./dist/git-status.js",
|
|
112
|
+
"git-diff": "./dist/git-diff.js",
|
|
113
|
+
"git-log": "./dist/git-log.js",
|
|
114
|
+
"git-branch": "./dist/git-branch.js",
|
|
115
|
+
"guard-main-branch": "./dist/guard-main-branch.js",
|
|
116
|
+
"state-bootstrap": "./dist/state-bootstrap.js"
|
|
89
117
|
},
|
|
90
118
|
"files": [
|
|
91
119
|
"dist",
|
|
@@ -102,11 +130,11 @@
|
|
|
102
130
|
"pretty-ms": "^9.2.0",
|
|
103
131
|
"simple-git": "^3.30.0",
|
|
104
132
|
"yaml": "^2.8.2",
|
|
105
|
-
"@lumenflow/
|
|
106
|
-
"@lumenflow/initiatives": "1.6.0",
|
|
107
|
-
"@lumenflow/agent": "1.6.0",
|
|
133
|
+
"@lumenflow/memory": "2.0.0",
|
|
108
134
|
"@lumenflow/metrics": "1.6.0",
|
|
109
|
-
"@lumenflow/
|
|
135
|
+
"@lumenflow/core": "2.0.0",
|
|
136
|
+
"@lumenflow/initiatives": "2.0.0",
|
|
137
|
+
"@lumenflow/agent": "2.0.0"
|
|
110
138
|
},
|
|
111
139
|
"devDependencies": {
|
|
112
140
|
"@vitest/coverage-v8": "^4.0.17",
|