@lumenflow/core 2.5.0 → 2.6.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.
@@ -204,3 +204,75 @@ export interface TestPolicy extends CoverageConfig {
204
204
  * @returns Resolved test policy including tests_required
205
205
  */
206
206
  export declare function resolveTestPolicy(projectRoot: string): TestPolicy;
207
+ /**
208
+ * WU-1356: Supported package managers type
209
+ * Re-exported from lumenflow-config-schema to avoid circular import
210
+ */
211
+ type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
212
+ /**
213
+ * WU-1356: Supported test runners type
214
+ * Re-exported from lumenflow-config-schema to avoid circular import
215
+ */
216
+ type TestRunner = 'vitest' | 'jest' | 'mocha';
217
+ /**
218
+ * WU-1356: Gates commands configuration type
219
+ */
220
+ export interface GatesCommands {
221
+ test_full: string;
222
+ test_docs_only: string;
223
+ test_incremental: string;
224
+ lint?: string;
225
+ typecheck?: string;
226
+ format?: string;
227
+ }
228
+ /**
229
+ * WU-1356: Resolve package manager from configuration
230
+ *
231
+ * Reads the package_manager field from .lumenflow.config.yaml.
232
+ * Returns 'pnpm' as default if not configured.
233
+ *
234
+ * @param projectRoot - Project root directory
235
+ * @returns Resolved package manager ('pnpm', 'npm', 'yarn', or 'bun')
236
+ */
237
+ export declare function resolvePackageManager(projectRoot: string): PackageManager;
238
+ /**
239
+ * WU-1356: Resolve test runner from configuration
240
+ *
241
+ * Reads the test_runner field from .lumenflow.config.yaml.
242
+ * Returns 'vitest' as default if not configured.
243
+ *
244
+ * @param projectRoot - Project root directory
245
+ * @returns Resolved test runner ('vitest', 'jest', or 'mocha')
246
+ */
247
+ export declare function resolveTestRunner(projectRoot: string): TestRunner;
248
+ /**
249
+ * WU-1356: Resolve build command from configuration
250
+ *
251
+ * Reads the build_command field from .lumenflow.config.yaml.
252
+ * If not configured, uses default based on package_manager.
253
+ *
254
+ * @param projectRoot - Project root directory
255
+ * @returns Resolved build command
256
+ */
257
+ export declare function resolveBuildCommand(projectRoot: string): string;
258
+ /**
259
+ * WU-1356: Resolve gates commands from configuration
260
+ *
261
+ * Reads gates.commands from .lumenflow.config.yaml.
262
+ * Merges with defaults based on package_manager if not fully specified.
263
+ *
264
+ * @param projectRoot - Project root directory
265
+ * @returns Resolved gates commands configuration
266
+ */
267
+ export declare function resolveGatesCommands(projectRoot: string): GatesCommands;
268
+ /**
269
+ * WU-1356: Get ignore patterns for test runner
270
+ *
271
+ * Returns patterns to ignore when detecting changed tests,
272
+ * based on the test runner configuration.
273
+ *
274
+ * @param testRunner - Test runner type (vitest, jest, mocha)
275
+ * @returns Array of ignore patterns
276
+ */
277
+ export declare function getIgnorePatterns(testRunner: TestRunner): string[];
278
+ export {};
@@ -453,3 +453,193 @@ export function resolveTestPolicy(projectRoot) {
453
453
  tests_required: policy.tests_required,
454
454
  };
455
455
  }
456
+ /**
457
+ * WU-1356: Default gates commands by package manager
458
+ *
459
+ * Provides sensible defaults for different package manager and test runner combinations.
460
+ */
461
+ const DEFAULT_GATES_COMMANDS = {
462
+ pnpm: {
463
+ test_full: 'pnpm turbo run test',
464
+ test_docs_only: '',
465
+ test_incremental: 'pnpm vitest run --changed origin/main',
466
+ lint: 'pnpm lint',
467
+ typecheck: 'pnpm typecheck',
468
+ format: 'pnpm format:check',
469
+ },
470
+ npm: {
471
+ test_full: 'npm test',
472
+ test_docs_only: '',
473
+ test_incremental: 'npm test -- --onlyChanged',
474
+ lint: 'npm run lint',
475
+ typecheck: 'npm run typecheck',
476
+ format: 'npm run format:check',
477
+ },
478
+ yarn: {
479
+ test_full: 'yarn test',
480
+ test_docs_only: '',
481
+ test_incremental: 'yarn test --onlyChanged',
482
+ lint: 'yarn lint',
483
+ typecheck: 'yarn typecheck',
484
+ format: 'yarn format:check',
485
+ },
486
+ bun: {
487
+ test_full: 'bun test',
488
+ test_docs_only: '',
489
+ test_incremental: 'bun test --changed',
490
+ lint: 'bun run lint',
491
+ typecheck: 'bun run typecheck',
492
+ format: 'bun run format:check',
493
+ },
494
+ };
495
+ /**
496
+ * WU-1356: Default build commands by package manager
497
+ */
498
+ const DEFAULT_BUILD_COMMANDS = {
499
+ pnpm: 'pnpm --filter @lumenflow/cli build',
500
+ npm: 'npm run build --workspace @lumenflow/cli',
501
+ yarn: 'yarn workspace @lumenflow/cli build',
502
+ bun: 'bun run --filter @lumenflow/cli build',
503
+ };
504
+ /**
505
+ * WU-1356: Ignore patterns by test runner
506
+ *
507
+ * Different test runners use different cache directories that should be ignored.
508
+ */
509
+ const IGNORE_PATTERNS_BY_RUNNER = {
510
+ vitest: ['.turbo'],
511
+ jest: ['coverage', '.jest-cache'],
512
+ mocha: ['coverage', '.nyc_output'],
513
+ };
514
+ /**
515
+ * WU-1356: Resolve package manager from configuration
516
+ *
517
+ * Reads the package_manager field from .lumenflow.config.yaml.
518
+ * Returns 'pnpm' as default if not configured.
519
+ *
520
+ * @param projectRoot - Project root directory
521
+ * @returns Resolved package manager ('pnpm', 'npm', 'yarn', or 'bun')
522
+ */
523
+ export function resolvePackageManager(projectRoot) {
524
+ const configPath = path.join(projectRoot, CONFIG_FILE_NAME);
525
+ if (!fs.existsSync(configPath)) {
526
+ return 'pnpm';
527
+ }
528
+ try {
529
+ const content = fs.readFileSync(configPath, 'utf8');
530
+ const data = yaml.parse(content);
531
+ const pm = data?.package_manager;
532
+ if (pm && ['pnpm', 'npm', 'yarn', 'bun'].includes(pm)) {
533
+ return pm;
534
+ }
535
+ return 'pnpm';
536
+ }
537
+ catch {
538
+ return 'pnpm';
539
+ }
540
+ }
541
+ /**
542
+ * WU-1356: Resolve test runner from configuration
543
+ *
544
+ * Reads the test_runner field from .lumenflow.config.yaml.
545
+ * Returns 'vitest' as default if not configured.
546
+ *
547
+ * @param projectRoot - Project root directory
548
+ * @returns Resolved test runner ('vitest', 'jest', or 'mocha')
549
+ */
550
+ export function resolveTestRunner(projectRoot) {
551
+ const configPath = path.join(projectRoot, CONFIG_FILE_NAME);
552
+ if (!fs.existsSync(configPath)) {
553
+ return 'vitest';
554
+ }
555
+ try {
556
+ const content = fs.readFileSync(configPath, 'utf8');
557
+ const data = yaml.parse(content);
558
+ const runner = data?.test_runner;
559
+ if (runner && ['vitest', 'jest', 'mocha'].includes(runner)) {
560
+ return runner;
561
+ }
562
+ return 'vitest';
563
+ }
564
+ catch {
565
+ return 'vitest';
566
+ }
567
+ }
568
+ /**
569
+ * WU-1356: Resolve build command from configuration
570
+ *
571
+ * Reads the build_command field from .lumenflow.config.yaml.
572
+ * If not configured, uses default based on package_manager.
573
+ *
574
+ * @param projectRoot - Project root directory
575
+ * @returns Resolved build command
576
+ */
577
+ export function resolveBuildCommand(projectRoot) {
578
+ const configPath = path.join(projectRoot, CONFIG_FILE_NAME);
579
+ const defaultPm = resolvePackageManager(projectRoot);
580
+ if (!fs.existsSync(configPath)) {
581
+ return DEFAULT_BUILD_COMMANDS[defaultPm];
582
+ }
583
+ try {
584
+ const content = fs.readFileSync(configPath, 'utf8');
585
+ const data = yaml.parse(content);
586
+ // If explicit build_command is set, use it
587
+ if (data?.build_command && typeof data.build_command === 'string') {
588
+ return data.build_command;
589
+ }
590
+ // Otherwise, use default for the configured package manager
591
+ return DEFAULT_BUILD_COMMANDS[defaultPm];
592
+ }
593
+ catch {
594
+ return DEFAULT_BUILD_COMMANDS[defaultPm];
595
+ }
596
+ }
597
+ /**
598
+ * WU-1356: Resolve gates commands from configuration
599
+ *
600
+ * Reads gates.commands from .lumenflow.config.yaml.
601
+ * Merges with defaults based on package_manager if not fully specified.
602
+ *
603
+ * @param projectRoot - Project root directory
604
+ * @returns Resolved gates commands configuration
605
+ */
606
+ export function resolveGatesCommands(projectRoot) {
607
+ const configPath = path.join(projectRoot, CONFIG_FILE_NAME);
608
+ const pm = resolvePackageManager(projectRoot);
609
+ const defaults = DEFAULT_GATES_COMMANDS[pm];
610
+ if (!fs.existsSync(configPath)) {
611
+ return defaults;
612
+ }
613
+ try {
614
+ const content = fs.readFileSync(configPath, 'utf8');
615
+ const data = yaml.parse(content);
616
+ const commands = data?.gates?.commands;
617
+ if (!commands) {
618
+ return defaults;
619
+ }
620
+ // Merge user config with defaults (user config wins)
621
+ return {
622
+ test_full: commands.test_full ?? defaults.test_full,
623
+ test_docs_only: commands.test_docs_only ?? defaults.test_docs_only,
624
+ test_incremental: commands.test_incremental ?? defaults.test_incremental,
625
+ lint: commands.lint ?? defaults.lint,
626
+ typecheck: commands.typecheck ?? defaults.typecheck,
627
+ format: commands.format ?? defaults.format,
628
+ };
629
+ }
630
+ catch {
631
+ return defaults;
632
+ }
633
+ }
634
+ /**
635
+ * WU-1356: Get ignore patterns for test runner
636
+ *
637
+ * Returns patterns to ignore when detecting changed tests,
638
+ * based on the test runner configuration.
639
+ *
640
+ * @param testRunner - Test runner type (vitest, jest, mocha)
641
+ * @returns Array of ignore patterns
642
+ */
643
+ export function getIgnorePatterns(testRunner) {
644
+ return IGNORE_PATTERNS_BY_RUNNER[testRunner] ?? ['.turbo'];
645
+ }
@@ -7,6 +7,68 @@
7
7
  * @module lumenflow-config-schema
8
8
  */
9
9
  import { z } from 'zod';
10
+ /**
11
+ * WU-1356: Package manager options
12
+ *
13
+ * Supported package managers for LumenFlow CLI operations.
14
+ * Used for build commands, dependency installation, and script execution.
15
+ *
16
+ * @example
17
+ * ```yaml
18
+ * package_manager: npm
19
+ * ```
20
+ */
21
+ export declare const PackageManagerSchema: z.ZodDefault<z.ZodEnum<{
22
+ pnpm: "pnpm";
23
+ npm: "npm";
24
+ yarn: "yarn";
25
+ bun: "bun";
26
+ }>>;
27
+ /** WU-1356: TypeScript type for package manager */
28
+ export type PackageManager = z.infer<typeof PackageManagerSchema>;
29
+ /**
30
+ * WU-1356: Test runner options
31
+ *
32
+ * Supported test runners for incremental test detection and execution.
33
+ * Determines how changed tests are detected and ignore patterns are derived.
34
+ *
35
+ * @example
36
+ * ```yaml
37
+ * test_runner: jest
38
+ * ```
39
+ */
40
+ export declare const TestRunnerSchema: z.ZodDefault<z.ZodEnum<{
41
+ vitest: "vitest";
42
+ jest: "jest";
43
+ mocha: "mocha";
44
+ }>>;
45
+ /** WU-1356: TypeScript type for test runner */
46
+ export type TestRunner = z.infer<typeof TestRunnerSchema>;
47
+ /**
48
+ * WU-1356: Gates commands configuration
49
+ *
50
+ * Configurable test commands for gates execution.
51
+ * Replaces hard-coded turbo/vitest commands with user-configurable alternatives.
52
+ *
53
+ * @example
54
+ * ```yaml
55
+ * gates:
56
+ * commands:
57
+ * test_full: 'npm test'
58
+ * test_docs_only: 'npm test -- --testPathPattern=docs'
59
+ * test_incremental: 'npm test -- --onlyChanged'
60
+ * ```
61
+ */
62
+ export declare const GatesCommandsConfigSchema: z.ZodObject<{
63
+ test_full: z.ZodDefault<z.ZodString>;
64
+ test_docs_only: z.ZodDefault<z.ZodString>;
65
+ test_incremental: z.ZodDefault<z.ZodString>;
66
+ lint: z.ZodOptional<z.ZodString>;
67
+ typecheck: z.ZodOptional<z.ZodString>;
68
+ format: z.ZodOptional<z.ZodString>;
69
+ }, z.core.$strip>;
70
+ /** WU-1356: TypeScript type for gates commands config */
71
+ export type GatesCommandsConfig = z.infer<typeof GatesCommandsConfigSchema>;
10
72
  /**
11
73
  * WU-1325: Lock policy for lane-level WIP enforcement
12
74
  *
@@ -172,6 +234,20 @@ export declare const GatesConfigSchema: z.ZodObject<{
172
234
  threshold: z.ZodOptional<z.ZodNumber>;
173
235
  }, z.core.$strip>]>>;
174
236
  }, z.core.$strip>>;
237
+ commands: z.ZodDefault<z.ZodObject<{
238
+ test_full: z.ZodDefault<z.ZodString>;
239
+ test_docs_only: z.ZodDefault<z.ZodString>;
240
+ test_incremental: z.ZodDefault<z.ZodString>;
241
+ lint: z.ZodOptional<z.ZodString>;
242
+ typecheck: z.ZodOptional<z.ZodString>;
243
+ format: z.ZodOptional<z.ZodString>;
244
+ }, z.core.$strip>>;
245
+ ignore_patterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
246
+ lane_health: z.ZodDefault<z.ZodEnum<{
247
+ error: "error";
248
+ off: "off";
249
+ warn: "warn";
250
+ }>>;
175
251
  }, z.core.$strip>;
176
252
  /**
177
253
  * WU-1203: Progress signals configuration for sub-agent coordination
@@ -357,6 +433,15 @@ export declare const TelemetryConfigSchema: z.ZodObject<{
357
433
  enabled: z.ZodDefault<z.ZodBoolean>;
358
434
  }, z.core.$strip>>;
359
435
  }, z.core.$strip>;
436
+ /**
437
+ * WU-1345: Lane enforcement configuration schema
438
+ *
439
+ * Controls how lane format validation behaves.
440
+ */
441
+ export declare const LanesEnforcementSchema: z.ZodObject<{
442
+ require_parent: z.ZodDefault<z.ZodBoolean>;
443
+ allow_custom: z.ZodDefault<z.ZodBoolean>;
444
+ }, z.core.$strip>;
360
445
  /**
361
446
  * WU-1322: Lane definition schema for .lumenflow.config.yaml
362
447
  *
@@ -374,6 +459,66 @@ export declare const LaneDefinitionSchema: z.ZodObject<{
374
459
  }>>>;
375
460
  code_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
376
461
  }, z.core.$strip>;
462
+ /**
463
+ * WU-1345: Complete lanes configuration schema
464
+ *
465
+ * Supports three formats:
466
+ * 1. definitions array (recommended)
467
+ * 2. engineering + business arrays (legacy/alternate)
468
+ * 3. flat array (simple format - parsed as definitions)
469
+ *
470
+ * @example
471
+ * ```yaml
472
+ * lanes:
473
+ * enforcement:
474
+ * require_parent: true
475
+ * allow_custom: false
476
+ * definitions:
477
+ * - name: 'Framework: Core'
478
+ * wip_limit: 1
479
+ * code_paths:
480
+ * - 'packages/@lumenflow/core/**'
481
+ * ```
482
+ */
483
+ export declare const LanesConfigSchema: z.ZodObject<{
484
+ enforcement: z.ZodOptional<z.ZodObject<{
485
+ require_parent: z.ZodDefault<z.ZodBoolean>;
486
+ allow_custom: z.ZodDefault<z.ZodBoolean>;
487
+ }, z.core.$strip>>;
488
+ definitions: z.ZodOptional<z.ZodArray<z.ZodObject<{
489
+ name: z.ZodString;
490
+ wip_limit: z.ZodOptional<z.ZodNumber>;
491
+ wip_justification: z.ZodOptional<z.ZodString>;
492
+ lock_policy: z.ZodDefault<z.ZodDefault<z.ZodEnum<{
493
+ none: "none";
494
+ all: "all";
495
+ active: "active";
496
+ }>>>;
497
+ code_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
498
+ }, z.core.$strip>>>;
499
+ engineering: z.ZodOptional<z.ZodArray<z.ZodObject<{
500
+ name: z.ZodString;
501
+ wip_limit: z.ZodOptional<z.ZodNumber>;
502
+ wip_justification: z.ZodOptional<z.ZodString>;
503
+ lock_policy: z.ZodDefault<z.ZodDefault<z.ZodEnum<{
504
+ none: "none";
505
+ all: "all";
506
+ active: "active";
507
+ }>>>;
508
+ code_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
509
+ }, z.core.$strip>>>;
510
+ business: z.ZodOptional<z.ZodArray<z.ZodObject<{
511
+ name: z.ZodString;
512
+ wip_limit: z.ZodOptional<z.ZodNumber>;
513
+ wip_justification: z.ZodOptional<z.ZodString>;
514
+ lock_policy: z.ZodDefault<z.ZodDefault<z.ZodEnum<{
515
+ none: "none";
516
+ all: "all";
517
+ active: "active";
518
+ }>>>;
519
+ code_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
520
+ }, z.core.$strip>>>;
521
+ }, z.core.$strip>;
377
522
  /**
378
523
  * Complete LumenFlow configuration schema
379
524
  */
@@ -481,6 +626,20 @@ export declare const LumenFlowConfigSchema: z.ZodObject<{
481
626
  threshold: z.ZodOptional<z.ZodNumber>;
482
627
  }, z.core.$strip>]>>;
483
628
  }, z.core.$strip>>;
629
+ commands: z.ZodDefault<z.ZodObject<{
630
+ test_full: z.ZodDefault<z.ZodString>;
631
+ test_docs_only: z.ZodDefault<z.ZodString>;
632
+ test_incremental: z.ZodDefault<z.ZodString>;
633
+ lint: z.ZodOptional<z.ZodString>;
634
+ typecheck: z.ZodOptional<z.ZodString>;
635
+ format: z.ZodOptional<z.ZodString>;
636
+ }, z.core.$strip>>;
637
+ ignore_patterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
638
+ lane_health: z.ZodDefault<z.ZodEnum<{
639
+ error: "error";
640
+ off: "off";
641
+ warn: "warn";
642
+ }>>;
484
643
  }, z.core.$strip>>;
485
644
  memory: z.ZodDefault<z.ZodObject<{
486
645
  directory: z.ZodDefault<z.ZodString>;
@@ -571,6 +730,57 @@ export declare const LumenFlowConfigSchema: z.ZodObject<{
571
730
  }>>;
572
731
  }, z.core.$strip>>;
573
732
  }, z.core.$strip>>;
733
+ lanes: z.ZodOptional<z.ZodObject<{
734
+ enforcement: z.ZodOptional<z.ZodObject<{
735
+ require_parent: z.ZodDefault<z.ZodBoolean>;
736
+ allow_custom: z.ZodDefault<z.ZodBoolean>;
737
+ }, z.core.$strip>>;
738
+ definitions: z.ZodOptional<z.ZodArray<z.ZodObject<{
739
+ name: z.ZodString;
740
+ wip_limit: z.ZodOptional<z.ZodNumber>;
741
+ wip_justification: z.ZodOptional<z.ZodString>;
742
+ lock_policy: z.ZodDefault<z.ZodDefault<z.ZodEnum<{
743
+ none: "none";
744
+ all: "all";
745
+ active: "active";
746
+ }>>>;
747
+ code_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
748
+ }, z.core.$strip>>>;
749
+ engineering: z.ZodOptional<z.ZodArray<z.ZodObject<{
750
+ name: z.ZodString;
751
+ wip_limit: z.ZodOptional<z.ZodNumber>;
752
+ wip_justification: z.ZodOptional<z.ZodString>;
753
+ lock_policy: z.ZodDefault<z.ZodDefault<z.ZodEnum<{
754
+ none: "none";
755
+ all: "all";
756
+ active: "active";
757
+ }>>>;
758
+ code_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
759
+ }, z.core.$strip>>>;
760
+ business: z.ZodOptional<z.ZodArray<z.ZodObject<{
761
+ name: z.ZodString;
762
+ wip_limit: z.ZodOptional<z.ZodNumber>;
763
+ wip_justification: z.ZodOptional<z.ZodString>;
764
+ lock_policy: z.ZodDefault<z.ZodDefault<z.ZodEnum<{
765
+ none: "none";
766
+ all: "all";
767
+ active: "active";
768
+ }>>>;
769
+ code_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
770
+ }, z.core.$strip>>>;
771
+ }, z.core.$strip>>;
772
+ package_manager: z.ZodDefault<z.ZodEnum<{
773
+ pnpm: "pnpm";
774
+ npm: "npm";
775
+ yarn: "yarn";
776
+ bun: "bun";
777
+ }>>;
778
+ test_runner: z.ZodDefault<z.ZodEnum<{
779
+ vitest: "vitest";
780
+ jest: "jest";
781
+ mocha: "mocha";
782
+ }>>;
783
+ build_command: z.ZodDefault<z.ZodString>;
574
784
  }, z.core.$strip>;
575
785
  /**
576
786
  * TypeScript types inferred from schemas
@@ -598,6 +808,8 @@ export type MethodologyTelemetryConfig = z.infer<typeof MethodologyTelemetryConf
598
808
  export type TelemetryConfig = z.infer<typeof TelemetryConfigSchema>;
599
809
  export type LumenFlowConfig = z.infer<typeof LumenFlowConfigSchema>;
600
810
  export type LaneDefinition = z.infer<typeof LaneDefinitionSchema>;
811
+ export type LanesEnforcement = z.infer<typeof LanesEnforcementSchema>;
812
+ export type LanesConfig = z.infer<typeof LanesConfigSchema>;
601
813
  export type { MethodologyConfig, MethodologyOverrides } from './resolve-policy.js';
602
814
  /**
603
815
  * Validate configuration data
@@ -677,6 +889,15 @@ export declare function validateConfig(data: unknown): z.ZodSafeParseResult<{
677
889
  minCoverage: number;
678
890
  enableSafetyCriticalTests: boolean;
679
891
  enableInvariants: boolean;
892
+ commands: {
893
+ test_full: string;
894
+ test_docs_only: string;
895
+ test_incremental: string;
896
+ lint?: string;
897
+ typecheck?: string;
898
+ format?: string;
899
+ };
900
+ lane_health: "error" | "off" | "warn";
680
901
  execution?: {
681
902
  preset?: string;
682
903
  setup?: string | {
@@ -709,6 +930,7 @@ export declare function validateConfig(data: unknown): z.ZodSafeParseResult<{
709
930
  threshold?: number;
710
931
  };
711
932
  };
933
+ ignore_patterns?: string[];
712
934
  };
713
935
  memory: {
714
936
  directory: string;
@@ -772,6 +994,9 @@ export declare function validateConfig(data: unknown): z.ZodSafeParseResult<{
772
994
  enabled: boolean;
773
995
  };
774
996
  };
997
+ package_manager: "pnpm" | "npm" | "yarn" | "bun";
998
+ test_runner: "vitest" | "jest" | "mocha";
999
+ build_command: string;
775
1000
  methodology?: {
776
1001
  testing: "tdd" | "test-after" | "none";
777
1002
  architecture: "none" | "hexagonal" | "layered";
@@ -780,6 +1005,33 @@ export declare function validateConfig(data: unknown): z.ZodSafeParseResult<{
780
1005
  coverage_mode?: "off" | "warn" | "block";
781
1006
  };
782
1007
  };
1008
+ lanes?: {
1009
+ enforcement?: {
1010
+ require_parent: boolean;
1011
+ allow_custom: boolean;
1012
+ };
1013
+ definitions?: {
1014
+ name: string;
1015
+ lock_policy: "none" | "all" | "active";
1016
+ wip_limit?: number;
1017
+ wip_justification?: string;
1018
+ code_paths?: string[];
1019
+ }[];
1020
+ engineering?: {
1021
+ name: string;
1022
+ lock_policy: "none" | "all" | "active";
1023
+ wip_limit?: number;
1024
+ wip_justification?: string;
1025
+ code_paths?: string[];
1026
+ }[];
1027
+ business?: {
1028
+ name: string;
1029
+ lock_policy: "none" | "all" | "active";
1030
+ wip_limit?: number;
1031
+ wip_justification?: string;
1032
+ code_paths?: string[];
1033
+ }[];
1034
+ };
783
1035
  }>;
784
1036
  /**
785
1037
  * Parse configuration with defaults
@@ -11,6 +11,77 @@ import { z } from 'zod';
11
11
  import { GatesExecutionConfigSchema } from './gates-config.js';
12
12
  // WU-1259: Import methodology config schema for resolvePolicy()
13
13
  import { MethodologyConfigSchema } from './resolve-policy.js';
14
+ /**
15
+ * WU-1356: Package manager options
16
+ *
17
+ * Supported package managers for LumenFlow CLI operations.
18
+ * Used for build commands, dependency installation, and script execution.
19
+ *
20
+ * @example
21
+ * ```yaml
22
+ * package_manager: npm
23
+ * ```
24
+ */
25
+ export const PackageManagerSchema = z.enum(['pnpm', 'npm', 'yarn', 'bun']).default('pnpm');
26
+ /**
27
+ * WU-1356: Test runner options
28
+ *
29
+ * Supported test runners for incremental test detection and execution.
30
+ * Determines how changed tests are detected and ignore patterns are derived.
31
+ *
32
+ * @example
33
+ * ```yaml
34
+ * test_runner: jest
35
+ * ```
36
+ */
37
+ export const TestRunnerSchema = z.enum(['vitest', 'jest', 'mocha']).default('vitest');
38
+ /**
39
+ * WU-1356: Gates commands configuration
40
+ *
41
+ * Configurable test commands for gates execution.
42
+ * Replaces hard-coded turbo/vitest commands with user-configurable alternatives.
43
+ *
44
+ * @example
45
+ * ```yaml
46
+ * gates:
47
+ * commands:
48
+ * test_full: 'npm test'
49
+ * test_docs_only: 'npm test -- --testPathPattern=docs'
50
+ * test_incremental: 'npm test -- --onlyChanged'
51
+ * ```
52
+ */
53
+ export const GatesCommandsConfigSchema = z.object({
54
+ /**
55
+ * Command to run full test suite.
56
+ * Default: 'pnpm turbo run test'
57
+ */
58
+ test_full: z.string().default('pnpm turbo run test'),
59
+ /**
60
+ * Command to run tests in docs-only mode.
61
+ * Default: empty (skip tests in docs-only mode)
62
+ */
63
+ test_docs_only: z.string().default(''),
64
+ /**
65
+ * Command to run incremental tests (changed files only).
66
+ * Default: 'pnpm vitest run --changed origin/main'
67
+ */
68
+ test_incremental: z.string().default('pnpm vitest run --changed origin/main'),
69
+ /**
70
+ * Command to run lint checks.
71
+ * Default: 'pnpm lint'
72
+ */
73
+ lint: z.string().optional(),
74
+ /**
75
+ * Command to run type checks.
76
+ * Default: 'pnpm typecheck'
77
+ */
78
+ typecheck: z.string().optional(),
79
+ /**
80
+ * Command to run format checks.
81
+ * Default: 'pnpm format:check'
82
+ */
83
+ format: z.string().optional(),
84
+ });
14
85
  /**
15
86
  * WU-1325: Lock policy for lane-level WIP enforcement
16
87
  *
@@ -306,6 +377,26 @@ export const GatesConfigSchema = z.object({
306
377
  * When set, gates runner uses these instead of hardcoded commands.
307
378
  */
308
379
  execution: GatesExecutionConfigSchema.optional(),
380
+ /**
381
+ * WU-1356: Configurable gate commands
382
+ * Replaces hard-coded turbo/vitest commands with user-configurable alternatives.
383
+ * Enables LumenFlow to work with npm/yarn/bun, Nx/plain scripts, Jest/Mocha, etc.
384
+ */
385
+ commands: GatesCommandsConfigSchema.default(() => GatesCommandsConfigSchema.parse({})),
386
+ /**
387
+ * WU-1356: Ignore patterns for test runners
388
+ * Patterns to ignore when detecting changed tests.
389
+ * Default: ['.turbo'] for vitest (derived from test_runner if not specified)
390
+ */
391
+ ignore_patterns: z.array(z.string()).optional(),
392
+ /**
393
+ * WU-1191: Lane health gate mode
394
+ * Controls how lane health check behaves during gates.
395
+ * - 'warn': Log warning if issues found (default)
396
+ * - 'error': Fail gates if issues found
397
+ * - 'off': Skip lane health check
398
+ */
399
+ lane_health: z.enum(['warn', 'error', 'off']).default('warn'),
309
400
  });
310
401
  /**
311
402
  * WU-1203: Progress signals configuration for sub-agent coordination
@@ -582,6 +673,24 @@ export const TelemetryConfigSchema = z.object({
582
673
  */
583
674
  methodology: MethodologyTelemetryConfigSchema.default(() => MethodologyTelemetryConfigSchema.parse({})),
584
675
  });
676
+ /**
677
+ * WU-1345: Lane enforcement configuration schema
678
+ *
679
+ * Controls how lane format validation behaves.
680
+ */
681
+ export const LanesEnforcementSchema = z.object({
682
+ /**
683
+ * When true, lanes MUST use "Parent: Sublane" format if parent has taxonomy.
684
+ * @default true
685
+ */
686
+ require_parent: z.boolean().default(true),
687
+ /**
688
+ * When false, only lanes in the taxonomy are allowed.
689
+ * When true, custom lanes can be used.
690
+ * @default false
691
+ */
692
+ allow_custom: z.boolean().default(false),
693
+ });
585
694
  /**
586
695
  * WU-1322: Lane definition schema for .lumenflow.config.yaml
587
696
  *
@@ -616,6 +725,37 @@ export const LaneDefinitionSchema = z.object({
616
725
  /** Code paths associated with this lane (glob patterns) */
617
726
  code_paths: z.array(z.string()).optional(),
618
727
  });
728
+ /**
729
+ * WU-1345: Complete lanes configuration schema
730
+ *
731
+ * Supports three formats:
732
+ * 1. definitions array (recommended)
733
+ * 2. engineering + business arrays (legacy/alternate)
734
+ * 3. flat array (simple format - parsed as definitions)
735
+ *
736
+ * @example
737
+ * ```yaml
738
+ * lanes:
739
+ * enforcement:
740
+ * require_parent: true
741
+ * allow_custom: false
742
+ * definitions:
743
+ * - name: 'Framework: Core'
744
+ * wip_limit: 1
745
+ * code_paths:
746
+ * - 'packages/@lumenflow/core/**'
747
+ * ```
748
+ */
749
+ export const LanesConfigSchema = z.object({
750
+ /** Lane enforcement configuration (validation rules) */
751
+ enforcement: LanesEnforcementSchema.optional(),
752
+ /** Primary lane definitions array (recommended format) */
753
+ definitions: z.array(LaneDefinitionSchema).optional(),
754
+ /** Engineering lanes (alternate format) */
755
+ engineering: z.array(LaneDefinitionSchema).optional(),
756
+ /** Business lanes (alternate format) */
757
+ business: z.array(LaneDefinitionSchema).optional(),
758
+ });
619
759
  /**
620
760
  * Complete LumenFlow configuration schema
621
761
  */
@@ -663,6 +803,62 @@ export const LumenFlowConfigSchema = z.object({
663
803
  * ```
664
804
  */
665
805
  methodology: MethodologyConfigSchema.optional(),
806
+ /**
807
+ * WU-1345: Lanes configuration
808
+ * Defines delivery lanes with WIP limits, code paths, and lock policies.
809
+ * Required for resolveLaneConfigsFromConfig() to work with getConfig().
810
+ *
811
+ * @example
812
+ * ```yaml
813
+ * lanes:
814
+ * enforcement:
815
+ * require_parent: true
816
+ * allow_custom: false
817
+ * definitions:
818
+ * - name: 'Framework: Core'
819
+ * wip_limit: 1
820
+ * code_paths:
821
+ * - 'packages/@lumenflow/core/**'
822
+ * ```
823
+ */
824
+ lanes: LanesConfigSchema.optional(),
825
+ /**
826
+ * WU-1356: Package manager for CLI operations
827
+ * Determines which package manager is used for build commands,
828
+ * dependency installation, and script execution.
829
+ *
830
+ * @default 'pnpm'
831
+ *
832
+ * @example
833
+ * ```yaml
834
+ * package_manager: npm
835
+ * ```
836
+ */
837
+ package_manager: PackageManagerSchema,
838
+ /**
839
+ * WU-1356: Test runner for incremental test detection
840
+ * Determines how changed tests are detected and which ignore patterns to use.
841
+ *
842
+ * @default 'vitest'
843
+ *
844
+ * @example
845
+ * ```yaml
846
+ * test_runner: jest
847
+ * ```
848
+ */
849
+ test_runner: TestRunnerSchema,
850
+ /**
851
+ * WU-1356: Custom build command for CLI bootstrap
852
+ * Overrides the default build command used in cli-entry.mjs.
853
+ *
854
+ * @default 'pnpm --filter @lumenflow/cli build'
855
+ *
856
+ * @example
857
+ * ```yaml
858
+ * build_command: 'npm run build'
859
+ * ```
860
+ */
861
+ build_command: z.string().default('pnpm --filter @lumenflow/cli build'),
666
862
  });
667
863
  /**
668
864
  * Validate configuration data
@@ -306,11 +306,16 @@ export declare function mergeWithRetry(tempBranchName: string, microWorktreePath
306
306
  * Push to origin/main with retry logic for race conditions
307
307
  *
308
308
  * WU-1179: When push fails because origin/main advanced (race condition with
309
- * parallel agents), this function rolls back local main to origin/main and
310
- * retries the full sequence: fetch -> rebase temp branch -> re-merge -> push.
309
+ * parallel agents), this function retries with fetch and rebase.
311
310
  *
312
- * This prevents the scenario where local main is left diverged from origin
313
- * after a push failure.
311
+ * WU-1348: The retry logic no longer resets the main checkout. Instead, it:
312
+ * 1. Fetches origin/main to get latest remote state
313
+ * 2. Rebases the temp branch onto origin/main (in the micro-worktree)
314
+ * 3. Re-merges the rebased temp branch to local main (ff-only)
315
+ * 4. Retries the push
316
+ *
317
+ * This preserves micro-worktree isolation - the main checkout files are never
318
+ * hard-reset, preventing file flash and preserving any uncommitted work.
314
319
  *
315
320
  * @param {Object} mainGit - GitAdapter instance for main checkout
316
321
  * @param {Object} worktreeGit - GitAdapter instance for micro-worktree
@@ -328,6 +333,15 @@ export declare function pushWithRetry(mainGit: GitAdapter, worktreeGit: GitAdapt
328
333
  * and supports configuration via PushRetryConfig. When push fails due to
329
334
  * non-fast-forward (origin moved), automatically rebases and retries.
330
335
  *
336
+ * WU-1348: The retry logic no longer resets the main checkout. Instead, it:
337
+ * 1. Fetches origin/main to get latest remote state
338
+ * 2. Rebases the temp branch onto origin/main (in the micro-worktree)
339
+ * 3. Re-merges the rebased temp branch to local main (ff-only)
340
+ * 4. Retries the push
341
+ *
342
+ * This preserves micro-worktree isolation - the main checkout files are never
343
+ * hard-reset, preventing file flash and preserving any uncommitted work.
344
+ *
331
345
  * @param {Object} mainGit - GitAdapter instance for main checkout
332
346
  * @param {Object} worktreeGit - GitAdapter instance for micro-worktree
333
347
  * @param {string} remote - Remote name (e.g., 'origin')
@@ -518,11 +518,16 @@ export async function mergeWithRetry(tempBranchName, microWorktreePath, logPrefi
518
518
  * Push to origin/main with retry logic for race conditions
519
519
  *
520
520
  * WU-1179: When push fails because origin/main advanced (race condition with
521
- * parallel agents), this function rolls back local main to origin/main and
522
- * retries the full sequence: fetch -> rebase temp branch -> re-merge -> push.
521
+ * parallel agents), this function retries with fetch and rebase.
523
522
  *
524
- * This prevents the scenario where local main is left diverged from origin
525
- * after a push failure.
523
+ * WU-1348: The retry logic no longer resets the main checkout. Instead, it:
524
+ * 1. Fetches origin/main to get latest remote state
525
+ * 2. Rebases the temp branch onto origin/main (in the micro-worktree)
526
+ * 3. Re-merges the rebased temp branch to local main (ff-only)
527
+ * 4. Retries the push
528
+ *
529
+ * This preserves micro-worktree isolation - the main checkout files are never
530
+ * hard-reset, preventing file flash and preserving any uncommitted work.
526
531
  *
527
532
  * @param {Object} mainGit - GitAdapter instance for main checkout
528
533
  * @param {Object} worktreeGit - GitAdapter instance for micro-worktree
@@ -544,27 +549,27 @@ export async function pushWithRetry(mainGit, worktreeGit, remote, branch, tempBr
544
549
  }
545
550
  catch (pushErr) {
546
551
  if (attempt < maxRetries) {
547
- console.log(`${logPrefix} ⚠️ Push failed (origin moved). Rolling back and retrying...`);
548
- // Step 1: Rollback local main to origin/main
549
- console.log(`${logPrefix} Rolling back local ${branch} to ${remote}/${branch}...`);
550
- await mainGit.reset(`${remote}/${branch}`, { hard: true });
551
- // Step 2: Fetch latest origin/main
552
+ console.log(`${logPrefix} ⚠️ Push failed (origin moved). Fetching and rebasing before retry...`);
553
+ // WU-1348: Do NOT reset main checkout - preserve micro-worktree isolation
554
+ // Instead, fetch latest remote state and rebase the temp branch
555
+ // Step 1: Fetch latest origin/main
552
556
  console.log(`${logPrefix} Fetching ${remote}/${branch}...`);
553
557
  await mainGit.fetch(remote, branch);
554
- // Step 3: Update local main to match origin/main (ff-only)
555
- console.log(`${logPrefix} Updating local ${branch}...`);
556
- await mainGit.merge(`${remote}/${branch}`, { ffOnly: true });
557
- // Step 4: Rebase temp branch onto updated main
558
- console.log(`${logPrefix} Rebasing temp branch onto ${branch}...`);
559
- await worktreeGit.rebase(branch);
560
- // Step 5: Re-merge temp branch to local main
558
+ // Step 2: Rebase temp branch onto updated origin/main
559
+ console.log(`${logPrefix} Rebasing temp branch onto ${remote}/${branch}...`);
560
+ await worktreeGit.rebase(`${remote}/${branch}`);
561
+ // Step 3: Re-merge temp branch to local main (ff-only)
562
+ // This updates local main to include the rebased commits
561
563
  console.log(`${logPrefix} Re-merging temp branch to ${branch}...`);
562
564
  await mainGit.merge(tempBranchName, { ffOnly: true });
563
565
  }
564
566
  else {
565
567
  const errMsg = pushErr instanceof Error ? pushErr.message : String(pushErr);
566
568
  throw new Error(`Push failed after ${maxRetries} attempts. ` +
567
- `Origin ${branch} may have significant traffic.\n` +
569
+ `Origin ${branch} may have significant traffic.\n\n` +
570
+ `Suggestions:\n` +
571
+ ` - Wait a few seconds and retry the operation\n` +
572
+ ` - Check if another agent is rapidly pushing changes\n` +
568
573
  `Error: ${errMsg}`);
569
574
  }
570
575
  }
@@ -577,6 +582,15 @@ export async function pushWithRetry(mainGit, worktreeGit, remote, branch, tempBr
577
582
  * and supports configuration via PushRetryConfig. When push fails due to
578
583
  * non-fast-forward (origin moved), automatically rebases and retries.
579
584
  *
585
+ * WU-1348: The retry logic no longer resets the main checkout. Instead, it:
586
+ * 1. Fetches origin/main to get latest remote state
587
+ * 2. Rebases the temp branch onto origin/main (in the micro-worktree)
588
+ * 3. Re-merges the rebased temp branch to local main (ff-only)
589
+ * 4. Retries the push
590
+ *
591
+ * This preserves micro-worktree isolation - the main checkout files are never
592
+ * hard-reset, preventing file flash and preserving any uncommitted work.
593
+ *
580
594
  * @param {Object} mainGit - GitAdapter instance for main checkout
581
595
  * @param {Object} worktreeGit - GitAdapter instance for micro-worktree
582
596
  * @param {string} remote - Remote name (e.g., 'origin')
@@ -603,20 +617,17 @@ export async function pushWithRetryConfig(mainGit, worktreeGit, remote, branch,
603
617
  console.log(`${logPrefix} ✅ Pushed to ${remote}/${branch}`);
604
618
  }
605
619
  catch (pushErr) {
606
- console.log(`${logPrefix} ⚠️ Push failed (origin moved). Rolling back and retrying...`);
607
- // Rollback local main to origin/main
608
- console.log(`${logPrefix} Rolling back local ${branch} to ${remote}/${branch}...`);
609
- await mainGit.reset(`${remote}/${branch}`, { hard: true });
620
+ console.log(`${logPrefix} ⚠️ Push failed (origin moved). Fetching and rebasing before retry...`);
621
+ // WU-1348: Do NOT reset main checkout - preserve micro-worktree isolation
622
+ // Instead, fetch latest remote state and rebase the temp branch
610
623
  // Fetch latest origin/main
611
624
  console.log(`${logPrefix} Fetching ${remote}/${branch}...`);
612
625
  await mainGit.fetch(remote, branch);
613
- // Update local main to match origin/main (ff-only)
614
- console.log(`${logPrefix} Updating local ${branch}...`);
615
- await mainGit.merge(`${remote}/${branch}`, { ffOnly: true });
616
- // Rebase temp branch onto updated main
617
- console.log(`${logPrefix} Rebasing temp branch onto ${branch}...`);
618
- await worktreeGit.rebase(branch);
619
- // Re-merge temp branch to local main
626
+ // Rebase temp branch onto updated origin/main
627
+ console.log(`${logPrefix} Rebasing temp branch onto ${remote}/${branch}...`);
628
+ await worktreeGit.rebase(`${remote}/${branch}`);
629
+ // Re-merge temp branch to local main (ff-only)
630
+ // This updates local main to include the rebased commits
620
631
  console.log(`${logPrefix} Re-merging temp branch to ${branch}...`);
621
632
  await mainGit.merge(tempBranchName, { ffOnly: true });
622
633
  // Re-throw to trigger p-retry
@@ -627,11 +638,8 @@ export async function pushWithRetryConfig(mainGit, worktreeGit, remote, branch,
627
638
  minTimeout: config.min_delay_ms,
628
639
  maxTimeout: config.max_delay_ms,
629
640
  randomize: config.jitter,
630
- onFailedAttempt: (error) => {
631
- // Log is handled in the try/catch above
632
- if (error.retriesLeft === 0) {
633
- // This will be the final failure
634
- }
641
+ onFailedAttempt: () => {
642
+ // Logging is handled in the try/catch above
635
643
  },
636
644
  }).catch(() => {
637
645
  // p-retry exhausted all retries, throw descriptive error
@@ -1718,6 +1718,75 @@ export declare const CONTEXT_VALIDATION: {
1718
1718
  readonly GATES: "gates";
1719
1719
  };
1720
1720
  };
1721
+ /**
1722
+ * Git hook error messages (WU-1357)
1723
+ *
1724
+ * Educational, structured messages for git hook blocks.
1725
+ * Follows the "message bag" pattern: TITLE, WHY, ACTIONS, HELP, BYPASS.
1726
+ *
1727
+ * Design principles:
1728
+ * - Explain WHY before showing WHAT to do
1729
+ * - Provide multiple paths forward (not just one command)
1730
+ * - Put emergency bypass LAST with clear warnings
1731
+ * - Include help resources for learning
1732
+ *
1733
+ * @see .husky/hooks/pre-commit.mjs - Primary consumer
1734
+ */
1735
+ export declare const HOOK_MESSAGES: {
1736
+ /**
1737
+ * Main branch protection block message components
1738
+ */
1739
+ readonly MAIN_BRANCH_BLOCK: {
1740
+ /** Box drawing for visual structure */
1741
+ readonly BOX: {
1742
+ readonly TOP: "══════════════════════════════════════════════════════════════";
1743
+ readonly DIVIDER: "──────────────────────────────────────────────────────────────";
1744
+ };
1745
+ /** Title shown at the top */
1746
+ readonly TITLE: (branch: string) => string;
1747
+ /** Educational explanation of WHY the block exists */
1748
+ readonly WHY: {
1749
+ readonly HEADER: "WHY THIS HAPPENS";
1750
+ readonly LINES: readonly ["LumenFlow protects main from direct commits to ensure:", " • All work is tracked in Work Units (WUs)", " • Changes can be reviewed and coordinated", " • Parallel work across lanes stays isolated"];
1751
+ };
1752
+ /** Action paths - multiple ways forward */
1753
+ readonly ACTIONS: {
1754
+ readonly HEADER: "WHAT TO DO";
1755
+ readonly HAVE_WU: {
1756
+ readonly HEADER: "1. If you have a Work Unit to implement:";
1757
+ readonly COMMANDS: readonly ["pnpm wu:claim --id WU-XXXX --lane \"<Lane>\"", "cd worktrees/<lane>-wu-xxxx"];
1758
+ readonly NOTE: "Then make your commits there";
1759
+ };
1760
+ readonly NEED_WU: {
1761
+ readonly HEADER: "2. If you need to create a new Work Unit:";
1762
+ readonly COMMANDS: readonly ["pnpm wu:create --lane \"<Lane>\" --title \"Your task\""];
1763
+ readonly NOTE: "This generates a WU ID, then claim it as above";
1764
+ };
1765
+ readonly LIST_LANES: {
1766
+ readonly HEADER: "3. Not sure what lane to use?";
1767
+ readonly COMMANDS: readonly ["pnpm wu:list-lanes"];
1768
+ };
1769
+ };
1770
+ /** Help resources */
1771
+ readonly HELP: {
1772
+ readonly HEADER: "NEED HELP?";
1773
+ readonly RESOURCES: readonly ["• Read: LUMENFLOW.md (workflow overview)", "• Read: docs/04-operations/_frameworks/lumenflow/agent/onboarding/", "• Run: pnpm wu:help"];
1774
+ };
1775
+ /** Emergency bypass (shown last, with warnings) */
1776
+ readonly BYPASS: {
1777
+ readonly HEADER: "EMERGENCY BYPASS (logged, use sparingly)";
1778
+ readonly WARNING: "Bypasses are audit-logged. Only use for genuine emergencies.";
1779
+ readonly COMMAND: "LUMENFLOW_FORCE=1 LUMENFLOW_FORCE_REASON=\"<reason>\" git commit ...";
1780
+ };
1781
+ };
1782
+ /**
1783
+ * Worktree discipline block message components
1784
+ */
1785
+ readonly WORKTREE_BLOCK: {
1786
+ readonly TITLE: "LANE BRANCH WORK SHOULD BE IN WORKTREE";
1787
+ readonly WHY: "Worktrees provide isolation for parallel work. Working on a lane branch from the main checkout bypasses this isolation.";
1788
+ };
1789
+ };
1721
1790
  /** Type for location types */
1722
1791
  export type LocationType = (typeof CONTEXT_VALIDATION.LOCATION_TYPES)[keyof typeof CONTEXT_VALIDATION.LOCATION_TYPES];
1723
1792
  /** Type for validation error codes */
@@ -1789,3 +1789,81 @@ export const CONTEXT_VALIDATION = {
1789
1789
  GATES: 'gates',
1790
1790
  },
1791
1791
  };
1792
+ /**
1793
+ * Git hook error messages (WU-1357)
1794
+ *
1795
+ * Educational, structured messages for git hook blocks.
1796
+ * Follows the "message bag" pattern: TITLE, WHY, ACTIONS, HELP, BYPASS.
1797
+ *
1798
+ * Design principles:
1799
+ * - Explain WHY before showing WHAT to do
1800
+ * - Provide multiple paths forward (not just one command)
1801
+ * - Put emergency bypass LAST with clear warnings
1802
+ * - Include help resources for learning
1803
+ *
1804
+ * @see .husky/hooks/pre-commit.mjs - Primary consumer
1805
+ */
1806
+ export const HOOK_MESSAGES = {
1807
+ /**
1808
+ * Main branch protection block message components
1809
+ */
1810
+ MAIN_BRANCH_BLOCK: {
1811
+ /** Box drawing for visual structure */
1812
+ BOX: {
1813
+ TOP: '══════════════════════════════════════════════════════════════',
1814
+ DIVIDER: '──────────────────────────────────────────────────────────────',
1815
+ },
1816
+ /** Title shown at the top */
1817
+ TITLE: (branch) => `DIRECT COMMIT TO ${branch.toUpperCase()} BLOCKED`,
1818
+ /** Educational explanation of WHY the block exists */
1819
+ WHY: {
1820
+ HEADER: 'WHY THIS HAPPENS',
1821
+ LINES: [
1822
+ 'LumenFlow protects main from direct commits to ensure:',
1823
+ ' • All work is tracked in Work Units (WUs)',
1824
+ ' • Changes can be reviewed and coordinated',
1825
+ ' • Parallel work across lanes stays isolated',
1826
+ ],
1827
+ },
1828
+ /** Action paths - multiple ways forward */
1829
+ ACTIONS: {
1830
+ HEADER: 'WHAT TO DO',
1831
+ HAVE_WU: {
1832
+ HEADER: '1. If you have a Work Unit to implement:',
1833
+ COMMANDS: ['pnpm wu:claim --id WU-XXXX --lane "<Lane>"', 'cd worktrees/<lane>-wu-xxxx'],
1834
+ NOTE: 'Then make your commits there',
1835
+ },
1836
+ NEED_WU: {
1837
+ HEADER: '2. If you need to create a new Work Unit:',
1838
+ COMMANDS: ['pnpm wu:create --lane "<Lane>" --title "Your task"'],
1839
+ NOTE: 'This generates a WU ID, then claim it as above',
1840
+ },
1841
+ LIST_LANES: {
1842
+ HEADER: '3. Not sure what lane to use?',
1843
+ COMMANDS: ['pnpm wu:list-lanes'],
1844
+ },
1845
+ },
1846
+ /** Help resources */
1847
+ HELP: {
1848
+ HEADER: 'NEED HELP?',
1849
+ RESOURCES: [
1850
+ '• Read: LUMENFLOW.md (workflow overview)',
1851
+ '• Read: docs/04-operations/_frameworks/lumenflow/agent/onboarding/',
1852
+ '• Run: pnpm wu:help',
1853
+ ],
1854
+ },
1855
+ /** Emergency bypass (shown last, with warnings) */
1856
+ BYPASS: {
1857
+ HEADER: 'EMERGENCY BYPASS (logged, use sparingly)',
1858
+ WARNING: 'Bypasses are audit-logged. Only use for genuine emergencies.',
1859
+ COMMAND: 'LUMENFLOW_FORCE=1 LUMENFLOW_FORCE_REASON="<reason>" git commit ...',
1860
+ },
1861
+ },
1862
+ /**
1863
+ * Worktree discipline block message components
1864
+ */
1865
+ WORKTREE_BLOCK: {
1866
+ TITLE: 'LANE BRANCH WORK SHOULD BE IN WORKTREE',
1867
+ WHY: 'Worktrees provide isolation for parallel work. Working on a lane branch from the main checkout bypasses this isolation.',
1868
+ },
1869
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/core",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Core WU lifecycle tools for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -99,7 +99,7 @@
99
99
  "vitest": "^4.0.17"
100
100
  },
101
101
  "peerDependencies": {
102
- "@lumenflow/memory": "2.5.0"
102
+ "@lumenflow/memory": "2.6.0"
103
103
  },
104
104
  "peerDependenciesMeta": {
105
105
  "@lumenflow/memory": {