@lumenflow/cli 2.2.1 → 2.2.2
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__/release.test.js +28 -0
- package/dist/gates.js +113 -16
- package/dist/validate-backlog-sync.js +3 -81
- package/dist/validate.js +3 -104
- package/dist/wu-done.js +21 -12
- package/package.json +6 -6
|
@@ -146,11 +146,39 @@ describe('release command integration', () => {
|
|
|
146
146
|
describe('WU-1077: release script bug fixes', () => {
|
|
147
147
|
describe('hasNpmAuth - ~/.npmrc detection', () => {
|
|
148
148
|
let testDir;
|
|
149
|
+
let originalUserConfig;
|
|
150
|
+
let originalNpmToken;
|
|
151
|
+
let originalNodeAuthToken;
|
|
149
152
|
beforeEach(() => {
|
|
150
153
|
testDir = join(tmpdir(), `release-npmrc-test-${Date.now()}`);
|
|
151
154
|
mkdirSync(testDir, { recursive: true });
|
|
155
|
+
originalUserConfig = process.env.NPM_CONFIG_USERCONFIG;
|
|
156
|
+
originalNpmToken = process.env.NPM_TOKEN;
|
|
157
|
+
originalNodeAuthToken = process.env.NODE_AUTH_TOKEN;
|
|
158
|
+
process.env.NPM_CONFIG_USERCONFIG = join(testDir, 'user.npmrc');
|
|
159
|
+
writeFileSync(process.env.NPM_CONFIG_USERCONFIG, '');
|
|
160
|
+
delete process.env.NPM_TOKEN;
|
|
161
|
+
delete process.env.NODE_AUTH_TOKEN;
|
|
152
162
|
});
|
|
153
163
|
afterEach(() => {
|
|
164
|
+
if (originalUserConfig === undefined) {
|
|
165
|
+
delete process.env.NPM_CONFIG_USERCONFIG;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
process.env.NPM_CONFIG_USERCONFIG = originalUserConfig;
|
|
169
|
+
}
|
|
170
|
+
if (originalNpmToken === undefined) {
|
|
171
|
+
delete process.env.NPM_TOKEN;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
process.env.NPM_TOKEN = originalNpmToken;
|
|
175
|
+
}
|
|
176
|
+
if (originalNodeAuthToken === undefined) {
|
|
177
|
+
delete process.env.NODE_AUTH_TOKEN;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
process.env.NODE_AUTH_TOKEN = originalNodeAuthToken;
|
|
181
|
+
}
|
|
154
182
|
rmSync(testDir, { recursive: true, force: true });
|
|
155
183
|
});
|
|
156
184
|
it('should detect auth from ~/.npmrc authToken line', async () => {
|
package/dist/gates.js
CHANGED
|
@@ -55,7 +55,10 @@ import { detectRiskTier, RISK_TIERS, } from '@lumenflow/core/dist/risk-detector.
|
|
|
55
55
|
// WU-2252: Import invariants runner for first-check validation
|
|
56
56
|
import { runInvariants } from '@lumenflow/core/dist/invariants-runner.js';
|
|
57
57
|
import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
|
|
58
|
-
import {
|
|
58
|
+
import { validateBacklogSync } from '@lumenflow/core/dist/validators/backlog-sync.js';
|
|
59
|
+
import { runSupabaseDocsLinter } from '@lumenflow/core/dist/validators/supabase-docs-linter.js';
|
|
60
|
+
import { runSystemMapValidation } from '@lumenflow/core/dist/system-map-validator.js';
|
|
61
|
+
import { BRANCHES, PACKAGES, PKG_MANAGER, PKG_FLAGS, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS, CLI_MODES, EXIT_CODES, FILE_SYSTEM, PRETTIER_ARGS, PRETTIER_FLAGS, } from '@lumenflow/core/dist/wu-constants.js';
|
|
59
62
|
/**
|
|
60
63
|
* WU-1087: Gates-specific option definitions for createWUParser.
|
|
61
64
|
* Exported for testing and consistency with other CLI commands.
|
|
@@ -274,6 +277,71 @@ function run(cmd, { agentLog } = {}) {
|
|
|
274
277
|
});
|
|
275
278
|
return { ok: result.status === EXIT_CODES.SUCCESS, duration: Date.now() - start };
|
|
276
279
|
}
|
|
280
|
+
function makeGateLogger({ agentLog, useAgentMode }) {
|
|
281
|
+
return (line) => {
|
|
282
|
+
if (!useAgentMode) {
|
|
283
|
+
console.log(line);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (agentLog) {
|
|
287
|
+
writeSync(agentLog.logFd, `${line}\n`);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
async function runBacklogSyncGate({ agentLog, useAgentMode }) {
|
|
292
|
+
const start = Date.now();
|
|
293
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
294
|
+
logLine('\n> Backlog sync\n');
|
|
295
|
+
const result = await validateBacklogSync({ cwd: process.cwd() });
|
|
296
|
+
if (result.errors.length > 0) {
|
|
297
|
+
logLine('❌ Backlog sync errors:');
|
|
298
|
+
result.errors.forEach((error) => logLine(` - ${error}`));
|
|
299
|
+
}
|
|
300
|
+
if (result.warnings.length > 0) {
|
|
301
|
+
logLine('⚠️ Backlog sync warnings:');
|
|
302
|
+
result.warnings.forEach((warning) => logLine(` - ${warning}`));
|
|
303
|
+
}
|
|
304
|
+
logLine(`Backlog sync summary: WU files=${result.wuCount}, Backlog refs=${result.backlogCount}`);
|
|
305
|
+
return { ok: result.valid, duration: Date.now() - start };
|
|
306
|
+
}
|
|
307
|
+
async function runSupabaseDocsGate({ agentLog, useAgentMode }) {
|
|
308
|
+
const start = Date.now();
|
|
309
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
310
|
+
logLine('\n> Supabase docs linter\n');
|
|
311
|
+
const result = await runSupabaseDocsLinter({ cwd: process.cwd(), logger: { log: logLine } });
|
|
312
|
+
if (result.skipped) {
|
|
313
|
+
logLine(`⚠️ ${result.message ?? 'Supabase docs linter skipped.'}`);
|
|
314
|
+
}
|
|
315
|
+
else if (!result.ok) {
|
|
316
|
+
logLine('❌ Supabase docs linter failed.');
|
|
317
|
+
(result.errors ?? []).forEach((error) => logLine(` - ${error}`));
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
logLine(result.message ?? 'Supabase docs linter passed.');
|
|
321
|
+
}
|
|
322
|
+
return { ok: result.ok, duration: Date.now() - start };
|
|
323
|
+
}
|
|
324
|
+
async function runSystemMapGate({ agentLog, useAgentMode }) {
|
|
325
|
+
const start = Date.now();
|
|
326
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
327
|
+
logLine('\n> System map validation\n');
|
|
328
|
+
const result = await runSystemMapValidation({
|
|
329
|
+
cwd: process.cwd(),
|
|
330
|
+
logger: { log: logLine, warn: logLine, error: logLine },
|
|
331
|
+
});
|
|
332
|
+
if (!result.valid) {
|
|
333
|
+
logLine('❌ System map validation failed');
|
|
334
|
+
(result.pathErrors ?? []).forEach((error) => logLine(` - ${error}`));
|
|
335
|
+
(result.orphanDocs ?? []).forEach((error) => logLine(` - ${error}`));
|
|
336
|
+
(result.audienceErrors ?? []).forEach((error) => logLine(` - ${error}`));
|
|
337
|
+
(result.queryErrors ?? []).forEach((error) => logLine(` - ${error}`));
|
|
338
|
+
(result.classificationErrors ?? []).forEach((error) => logLine(` - ${error}`));
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
logLine('System map validation passed.');
|
|
342
|
+
}
|
|
343
|
+
return { ok: result.valid, duration: Date.now() - start };
|
|
344
|
+
}
|
|
277
345
|
/**
|
|
278
346
|
* Run incremental ESLint on changed files only
|
|
279
347
|
* Falls back to full lint if on main branch or if incremental fails
|
|
@@ -576,15 +644,36 @@ async function getAllChangedFiles(options = {}) {
|
|
|
576
644
|
return [];
|
|
577
645
|
}
|
|
578
646
|
}
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
647
|
+
export async function runGates(options = {}) {
|
|
648
|
+
const originalCwd = process.cwd();
|
|
649
|
+
const targetCwd = options.cwd ?? originalCwd;
|
|
650
|
+
if (targetCwd !== originalCwd) {
|
|
651
|
+
process.chdir(targetCwd);
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
return await executeGates({
|
|
655
|
+
...options,
|
|
656
|
+
coverageMode: options.coverageMode ?? COVERAGE_GATE_MODES.BLOCK,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
catch {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
finally {
|
|
663
|
+
if (targetCwd !== originalCwd) {
|
|
664
|
+
process.chdir(originalCwd);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
584
668
|
// Main execution
|
|
585
669
|
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing: main() orchestrates multi-step gate workflow
|
|
586
|
-
async function
|
|
587
|
-
const
|
|
670
|
+
async function executeGates(opts) {
|
|
671
|
+
const argv = opts.argv ?? process.argv.slice(2);
|
|
672
|
+
// Get context for telemetry
|
|
673
|
+
const wu_id = getCurrentWU();
|
|
674
|
+
const lane = getCurrentLane();
|
|
675
|
+
const useAgentMode = shouldUseGatesAgentMode({ argv, env: process.env });
|
|
676
|
+
const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane }) : null;
|
|
588
677
|
// Parse command line arguments (now via Commander)
|
|
589
678
|
const isDocsOnly = opts.docsOnly || false;
|
|
590
679
|
const isFullLint = opts.fullLint || false;
|
|
@@ -637,11 +726,11 @@ async function main() {
|
|
|
637
726
|
name: GATE_NAMES.PROMPTS_LINT,
|
|
638
727
|
cmd: pnpmRun(SCRIPTS.PROMPTS_LINT, CLI_MODES.LOCAL, '--quiet'),
|
|
639
728
|
},
|
|
640
|
-
{ name: GATE_NAMES.BACKLOG_SYNC,
|
|
729
|
+
{ name: GATE_NAMES.BACKLOG_SYNC, run: runBacklogSyncGate },
|
|
641
730
|
// WU-2315: System map validation (warn-only until orphan docs are indexed)
|
|
642
731
|
{
|
|
643
732
|
name: GATE_NAMES.SYSTEM_MAP_VALIDATE,
|
|
644
|
-
|
|
733
|
+
run: runSystemMapGate,
|
|
645
734
|
warnOnly: true,
|
|
646
735
|
},
|
|
647
736
|
]
|
|
@@ -659,12 +748,12 @@ async function main() {
|
|
|
659
748
|
name: GATE_NAMES.PROMPTS_LINT,
|
|
660
749
|
cmd: pnpmRun(SCRIPTS.PROMPTS_LINT, CLI_MODES.LOCAL, '--quiet'),
|
|
661
750
|
},
|
|
662
|
-
{ name: GATE_NAMES.BACKLOG_SYNC,
|
|
663
|
-
{ name: GATE_NAMES.SUPABASE_DOCS_LINTER,
|
|
751
|
+
{ name: GATE_NAMES.BACKLOG_SYNC, run: runBacklogSyncGate },
|
|
752
|
+
{ name: GATE_NAMES.SUPABASE_DOCS_LINTER, run: runSupabaseDocsGate },
|
|
664
753
|
// WU-2315: System map validation (warn-only until orphan docs are indexed)
|
|
665
754
|
{
|
|
666
755
|
name: GATE_NAMES.SYSTEM_MAP_VALIDATE,
|
|
667
|
-
|
|
756
|
+
run: runSystemMapGate,
|
|
668
757
|
warnOnly: true,
|
|
669
758
|
},
|
|
670
759
|
// WU-2062: Safety-critical tests ALWAYS run
|
|
@@ -697,7 +786,10 @@ async function main() {
|
|
|
697
786
|
let lastTestResult = null;
|
|
698
787
|
for (const gate of gates) {
|
|
699
788
|
let result;
|
|
700
|
-
if (gate.
|
|
789
|
+
if (gate.run) {
|
|
790
|
+
result = await gate.run({ agentLog, useAgentMode });
|
|
791
|
+
}
|
|
792
|
+
else if (gate.cmd === GATE_COMMANDS.INVARIANTS) {
|
|
701
793
|
// WU-2252: Invariants check runs first (non-bypassable)
|
|
702
794
|
const logLine = useAgentMode
|
|
703
795
|
? (line) => writeSync(agentLog.logFd, `${line}\n`)
|
|
@@ -807,13 +899,18 @@ async function main() {
|
|
|
807
899
|
else {
|
|
808
900
|
console.log(`✅ All gates passed (agent mode). Log: ${agentLog.logPath}\n`);
|
|
809
901
|
}
|
|
810
|
-
|
|
902
|
+
return true;
|
|
811
903
|
}
|
|
812
904
|
// WU-1071: Use import.meta.main instead of process.argv[1] comparison
|
|
813
905
|
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
814
906
|
// path but import.meta.url resolves to the real path - they never match
|
|
815
907
|
if (import.meta.main) {
|
|
816
|
-
|
|
908
|
+
const opts = parseGatesArgs();
|
|
909
|
+
executeGates({ ...opts, argv: process.argv.slice(2) })
|
|
910
|
+
.then((ok) => {
|
|
911
|
+
process.exit(ok ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
|
|
912
|
+
})
|
|
913
|
+
.catch((error) => {
|
|
817
914
|
console.error('Gates failed:', error);
|
|
818
915
|
process.exit(EXIT_CODES.ERROR);
|
|
819
916
|
});
|
|
@@ -15,89 +15,11 @@
|
|
|
15
15
|
*
|
|
16
16
|
* @see {@link docs/04-operations/tasks/backlog.md} - Backlog file
|
|
17
17
|
*/
|
|
18
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
19
|
-
import path from 'node:path';
|
|
20
18
|
import { fileURLToPath } from 'node:url';
|
|
21
|
-
import {
|
|
19
|
+
import { validateBacklogSync, } from '@lumenflow/core/dist/validators/backlog-sync.js';
|
|
20
|
+
import { EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
22
21
|
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
|
-
}
|
|
22
|
+
export { validateBacklogSync };
|
|
101
23
|
/**
|
|
102
24
|
* Main CLI entry point
|
|
103
25
|
*/
|
package/dist/validate.js
CHANGED
|
@@ -19,113 +19,12 @@
|
|
|
19
19
|
* @see {@link wu-validate.ts} - Detailed WU validation with schema
|
|
20
20
|
* @see {@link docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md} - WU lifecycle
|
|
21
21
|
*/
|
|
22
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
23
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { validateSingleWU, validateAllWUs, } from '@lumenflow/core/dist/validators/wu-tasks.js';
|
|
24
24
|
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
25
|
-
import {
|
|
26
|
-
import { validateWU, validateWUCompleteness } from '@lumenflow/core/dist/wu-schema.js';
|
|
27
|
-
import { FILE_SYSTEM, EMOJI, PATTERNS } from '@lumenflow/core/dist/wu-constants.js';
|
|
25
|
+
import { EMOJI, PATTERNS } from '@lumenflow/core/dist/wu-constants.js';
|
|
28
26
|
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
|
-
}
|
|
27
|
+
export { validateSingleWU, validateAllWUs };
|
|
129
28
|
/**
|
|
130
29
|
* Main CLI entry point
|
|
131
30
|
*/
|
package/dist/wu-done.js
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import { execSync } from 'node:child_process';
|
|
35
35
|
import prettyMs from 'pretty-ms';
|
|
36
|
+
import { runGates } from './gates.js';
|
|
36
37
|
import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
|
|
37
38
|
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
38
39
|
import { existsSync, readFileSync, mkdirSync, appendFileSync, unlinkSync, statSync } from 'node:fs';
|
|
@@ -915,14 +916,13 @@ function checkNodeModulesStaleness(worktreePath) {
|
|
|
915
916
|
* @param {boolean} options.isDocsOnly - Auto-detected docs-only from code_paths
|
|
916
917
|
* @param {boolean} options.docsOnly - Explicit --docs-only flag from CLI
|
|
917
918
|
*/
|
|
918
|
-
function runGatesInWorktree(worktreePath, id, options = {}) {
|
|
919
|
+
async function runGatesInWorktree(worktreePath, id, options = {}) {
|
|
919
920
|
const { isDocsOnly = false, docsOnly = false } = options;
|
|
920
921
|
console.log(`\n${LOG_PREFIX.DONE} Running gates in worktree: ${worktreePath}`);
|
|
921
922
|
// Check for stale node_modules before running gates (prevents confusing failures)
|
|
922
923
|
checkNodeModulesStaleness(worktreePath);
|
|
923
924
|
// WU-1012: Use docs-only gates if explicit --docs-only flag OR auto-detected
|
|
924
925
|
const useDocsOnlyGates = docsOnly || isDocsOnly;
|
|
925
|
-
const gatesCmd = buildGatesCommand({ docsOnly, isDocsOnly });
|
|
926
926
|
if (useDocsOnlyGates) {
|
|
927
927
|
console.log(`${LOG_PREFIX.DONE} Using docs-only gates (skipping lint/typecheck/tests)`);
|
|
928
928
|
if (docsOnly) {
|
|
@@ -931,12 +931,14 @@ function runGatesInWorktree(worktreePath, id, options = {}) {
|
|
|
931
931
|
}
|
|
932
932
|
const startTime = Date.now();
|
|
933
933
|
try {
|
|
934
|
-
|
|
935
|
-
execSync(gatesCmd, {
|
|
934
|
+
const ok = Boolean(await runGates({
|
|
936
935
|
cwd: worktreePath,
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
});
|
|
936
|
+
docsOnly: useDocsOnlyGates,
|
|
937
|
+
coverageMode: undefined,
|
|
938
|
+
}));
|
|
939
|
+
if (!ok) {
|
|
940
|
+
throw new Error('Gates failed');
|
|
941
|
+
}
|
|
940
942
|
const duration = Date.now() - startTime;
|
|
941
943
|
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Gates passed in ${prettyMs(duration)}`);
|
|
942
944
|
emitTelemetry({ script: 'wu-done', wu_id: id, step: 'gates', ok: true, duration_ms: duration });
|
|
@@ -1774,8 +1776,7 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1774
1776
|
// Branch-Only mode: run gates in-place (current directory on lane branch)
|
|
1775
1777
|
console.log(`\n${LOG_PREFIX.DONE} Running gates in Branch-Only mode (in-place on lane branch)`);
|
|
1776
1778
|
// WU-1012: Use docs-only gates if explicit --docs-only flag OR auto-detected
|
|
1777
|
-
const useDocsOnlyGates = args.docsOnly || isDocsOnly;
|
|
1778
|
-
const gatesCmd = buildGatesCommand({ docsOnly: Boolean(args.docsOnly), isDocsOnly });
|
|
1779
|
+
const useDocsOnlyGates = Boolean(args.docsOnly) || Boolean(isDocsOnly);
|
|
1779
1780
|
if (useDocsOnlyGates) {
|
|
1780
1781
|
console.log(`${LOG_PREFIX.DONE} Using docs-only gates (skipping lint/typecheck/tests)`);
|
|
1781
1782
|
if (args.docsOnly) {
|
|
@@ -1784,7 +1785,10 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1784
1785
|
}
|
|
1785
1786
|
const startTime = Date.now();
|
|
1786
1787
|
try {
|
|
1787
|
-
|
|
1788
|
+
const ok = Boolean(await runGates({ docsOnly: useDocsOnlyGates }));
|
|
1789
|
+
if (!ok) {
|
|
1790
|
+
throw new Error('Gates failed');
|
|
1791
|
+
}
|
|
1788
1792
|
const duration = Date.now() - startTime;
|
|
1789
1793
|
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Gates passed in ${prettyMs(duration)}`);
|
|
1790
1794
|
emitTelemetry({
|
|
@@ -1818,7 +1822,10 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1818
1822
|
else if (worktreePath && existsSync(worktreePath)) {
|
|
1819
1823
|
// Worktree mode: run gates in the dedicated worktree
|
|
1820
1824
|
// WU-1012: Pass both auto-detected and explicit docs-only flags
|
|
1821
|
-
runGatesInWorktree(worktreePath, id, {
|
|
1825
|
+
await runGatesInWorktree(worktreePath, id, {
|
|
1826
|
+
isDocsOnly,
|
|
1827
|
+
docsOnly: Boolean(args.docsOnly),
|
|
1828
|
+
});
|
|
1822
1829
|
}
|
|
1823
1830
|
else {
|
|
1824
1831
|
die(`Worktree not found (${worktreePath || 'unknown'}). Gates must run in the lane worktree.\n` +
|
|
@@ -1977,7 +1984,9 @@ async function main() {
|
|
|
1977
1984
|
// WU-2308: Pass worktreePath to run audit from worktree (checks fixed deps, not stale main deps)
|
|
1978
1985
|
// WU-1145: Skip pre-flight when skipGates is true (pre-flight runs gates which was already skipped)
|
|
1979
1986
|
if (!args.skipGates) {
|
|
1980
|
-
const hookResult = validateAllPreCommitHooks(id, worktreePath
|
|
1987
|
+
const hookResult = await validateAllPreCommitHooks(id, worktreePath, {
|
|
1988
|
+
runGates: ({ cwd }) => runGates({ cwd, docsOnly: false }),
|
|
1989
|
+
});
|
|
1981
1990
|
if (!hookResult.valid) {
|
|
1982
1991
|
die('Pre-flight validation failed. Fix hook issues and try again.');
|
|
1983
1992
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/cli",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Command-line interface for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -133,11 +133,11 @@
|
|
|
133
133
|
"pretty-ms": "^9.2.0",
|
|
134
134
|
"simple-git": "^3.30.0",
|
|
135
135
|
"yaml": "^2.8.2",
|
|
136
|
-
"@lumenflow/
|
|
137
|
-
"@lumenflow/
|
|
138
|
-
"@lumenflow/memory": "2.2.
|
|
139
|
-
"@lumenflow/initiatives": "2.2.
|
|
140
|
-
"@lumenflow/agent": "2.2.
|
|
136
|
+
"@lumenflow/core": "2.2.2",
|
|
137
|
+
"@lumenflow/metrics": "2.2.2",
|
|
138
|
+
"@lumenflow/memory": "2.2.2",
|
|
139
|
+
"@lumenflow/initiatives": "2.2.2",
|
|
140
|
+
"@lumenflow/agent": "2.2.2"
|
|
141
141
|
},
|
|
142
142
|
"devDependencies": {
|
|
143
143
|
"@vitest/coverage-v8": "^4.0.17",
|