@lumenflow/core 1.3.0 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/arg-parser.d.ts +6 -0
  2. package/dist/arg-parser.js +16 -0
  3. package/dist/core/tool.schemas.d.ts +1 -1
  4. package/dist/coverage-gate.d.ts +3 -0
  5. package/dist/coverage-gate.js +7 -4
  6. package/dist/force-bypass-audit.d.ts +63 -0
  7. package/dist/force-bypass-audit.js +140 -0
  8. package/dist/gates-config.d.ts +132 -0
  9. package/dist/gates-config.js +229 -0
  10. package/dist/git-adapter.d.ts +7 -0
  11. package/dist/git-adapter.js +15 -1
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +6 -0
  14. package/dist/lumenflow-config-schema.d.ts +97 -0
  15. package/dist/lumenflow-config-schema.js +9 -0
  16. package/dist/lumenflow-home.d.ts +130 -0
  17. package/dist/lumenflow-home.js +211 -0
  18. package/dist/manual-test-validator.d.ts +3 -0
  19. package/dist/manual-test-validator.js +7 -4
  20. package/dist/orphan-detector.d.ts +16 -0
  21. package/dist/orphan-detector.js +24 -0
  22. package/dist/prompt-linter.js +2 -1
  23. package/dist/prompt-monitor.js +3 -1
  24. package/dist/spec-branch-helpers.d.ts +118 -0
  25. package/dist/spec-branch-helpers.js +199 -0
  26. package/dist/user-normalizer.d.ts +5 -1
  27. package/dist/user-normalizer.js +6 -1
  28. package/dist/validators/phi-scanner.js +6 -0
  29. package/dist/worktree-symlink.d.ts +4 -0
  30. package/dist/worktree-symlink.js +14 -20
  31. package/dist/wu-consistency-checker.d.ts +2 -0
  32. package/dist/wu-consistency-checker.js +35 -1
  33. package/dist/wu-constants.d.ts +193 -0
  34. package/dist/wu-constants.js +200 -4
  35. package/dist/wu-create-validators.d.ts +57 -2
  36. package/dist/wu-create-validators.js +111 -2
  37. package/dist/wu-done-branch-only.js +9 -0
  38. package/dist/wu-done-docs-generate.d.ts +73 -0
  39. package/dist/wu-done-docs-generate.js +108 -0
  40. package/dist/wu-done-worktree.js +12 -0
  41. package/dist/wu-schema.js +3 -1
  42. package/dist/wu-spawn.js +15 -2
  43. package/dist/wu-yaml-fixer.js +6 -3
  44. package/package.json +12 -11
@@ -21,6 +21,12 @@ interface WUOption {
21
21
  isRepeatable?: boolean;
22
22
  }
23
23
  export declare const WU_OPTIONS: Record<string, WUOption>;
24
+ /**
25
+ * WU-1062: Additional options for wu:create command
26
+ *
27
+ * These options control how wu:create handles external plan storage.
28
+ */
29
+ export declare const WU_CREATE_OPTIONS: Record<string, WUOption>;
24
30
  /**
25
31
  * Create a commander-based CLI parser for a WU script.
26
32
  *
@@ -396,6 +396,22 @@ export const WU_OPTIONS = {
396
396
  description: 'Skip automatic pnpm install in worktree after creation (faster claims when deps already built)',
397
397
  },
398
398
  };
399
+ /**
400
+ * WU-1062: Additional options for wu:create command
401
+ *
402
+ * These options control how wu:create handles external plan storage.
403
+ */
404
+ export const WU_CREATE_OPTIONS = {
405
+ /**
406
+ * Create plan template in $LUMENFLOW_HOME/plans/
407
+ * Stores plans externally for traceability without polluting the repo.
408
+ */
409
+ plan: {
410
+ name: 'plan',
411
+ flags: '--plan',
412
+ description: 'Create plan template in $LUMENFLOW_HOME/plans/ (external plan storage)',
413
+ },
414
+ };
399
415
  /**
400
416
  * Negated options that commander handles specially.
401
417
  * --no-foo creates opts.foo = false. We convert to noFoo = true.
@@ -140,8 +140,8 @@ export declare const ToolExecutionResultSchema: z.ZodObject<{
140
140
  tool: z.ZodString;
141
141
  status: z.ZodEnum<{
142
142
  cancelled: "cancelled";
143
- success: "success";
144
143
  timeout: "timeout";
144
+ success: "success";
145
145
  failed: "failed";
146
146
  pending: "pending";
147
147
  running: "running";
@@ -26,6 +26,9 @@ export declare const COVERAGE_GATE_MODES: Readonly<{
26
26
  * Glob patterns for hex core files that require ≥90% coverage.
27
27
  * These are the critical application layer files.
28
28
  *
29
+ * WU-1068: Changed from @patientpath to @lumenflow for framework reusability.
30
+ * Project-specific patterns should be configured in .lumenflow.config.yaml.
31
+ *
29
32
  * @constant {string[]}
30
33
  */
31
34
  export declare const HEX_CORE_PATTERNS: readonly string[];
@@ -29,11 +29,14 @@ export const COVERAGE_GATE_MODES = Object.freeze({
29
29
  * Glob patterns for hex core files that require ≥90% coverage.
30
30
  * These are the critical application layer files.
31
31
  *
32
+ * WU-1068: Changed from @patientpath to @lumenflow for framework reusability.
33
+ * Project-specific patterns should be configured in .lumenflow.config.yaml.
34
+ *
32
35
  * @constant {string[]}
33
36
  */
34
37
  export const HEX_CORE_PATTERNS = Object.freeze([
35
- 'packages/@patientpath/application/',
36
- 'packages/@patientpath/prompts/',
38
+ 'packages/@lumenflow/core/',
39
+ 'packages/@lumenflow/cli/',
37
40
  ]);
38
41
  /**
39
42
  * Coverage threshold for hex core files (percentage)
@@ -144,7 +147,7 @@ export function formatCoverageDelta(coverageData) {
144
147
  const metrics = metricsValue;
145
148
  const pct = metrics.lines?.pct ?? 0;
146
149
  const status = pct >= COVERAGE_THRESHOLD ? EMOJI.SUCCESS : EMOJI.FAILURE;
147
- const shortFile = file.replace('packages/@patientpath/', '');
150
+ const shortFile = file.replace('packages/@lumenflow/', '');
148
151
  lines.push(` ${status} ${shortFile}: ${pct.toFixed(1)}%`);
149
152
  }
150
153
  }
@@ -178,7 +181,7 @@ export async function runCoverageGate(options = {}) {
178
181
  if (!pass) {
179
182
  logger.log(`\n${EMOJI.FAILURE} Coverage below ${COVERAGE_THRESHOLD}% for hex core files:`);
180
183
  for (const failure of failures) {
181
- const shortFile = failure.file.replace('packages/@patientpath/', '');
184
+ const shortFile = failure.file.replace('packages/@lumenflow/', '');
182
185
  logger.log(` - ${shortFile}: ${failure.actual.toFixed(1)}% (requires ${failure.threshold}%)`);
183
186
  }
184
187
  if (mode === COVERAGE_GATE_MODES.BLOCK) {
@@ -0,0 +1,63 @@
1
+ /**
2
+ * WU-1070: Force Bypass Audit Logging
3
+ *
4
+ * Provides audit logging for LUMENFLOW_FORCE bypass usage.
5
+ * All hooks log bypass events to .beacon/force-bypasses.log for accountability.
6
+ *
7
+ * Key principles:
8
+ * - FAIL-OPEN: Audit logging must never block the bypass itself
9
+ * - WARN ON MISSING REASON: stderr warning if LUMENFLOW_FORCE_REASON not set
10
+ * - GIT-TRACKED: .beacon/force-bypasses.log is version controlled
11
+ *
12
+ * Log format:
13
+ * ISO_TIMESTAMP | HOOK_NAME | GIT_USER | BRANCH | REASON | CWD
14
+ *
15
+ * Example:
16
+ * 2026-01-23T10:30:00.000Z | pre-commit | tom@hellm.ai | lane/ops/wu-1070 | Emergency hotfix | /project
17
+ */
18
+ /**
19
+ * Environment variable names for force bypass
20
+ */
21
+ export declare const FORCE_ENV_VAR = "LUMENFLOW_FORCE";
22
+ export declare const FORCE_REASON_ENV_VAR = "LUMENFLOW_FORCE_REASON";
23
+ /**
24
+ * Represents a parsed audit log entry
25
+ */
26
+ export interface AuditLogEntry {
27
+ timestamp: string;
28
+ hook: string;
29
+ user: string;
30
+ branch: string;
31
+ reason: string;
32
+ cwd: string;
33
+ }
34
+ /**
35
+ * Check if LUMENFLOW_FORCE bypass is active
36
+ *
37
+ * @returns true if LUMENFLOW_FORCE=1
38
+ */
39
+ export declare function shouldBypass(): boolean;
40
+ /**
41
+ * Get the audit log file path
42
+ *
43
+ * @param projectRoot - Project root directory
44
+ * @returns Full path to the audit log file
45
+ */
46
+ export declare function getAuditLogPath(projectRoot: string): string;
47
+ /**
48
+ * Log a force bypass event to the audit log
49
+ *
50
+ * This function is FAIL-OPEN: if logging fails, it warns to stderr
51
+ * but does NOT throw or block the bypass operation.
52
+ *
53
+ * @param hookName - Name of the hook being bypassed (pre-commit, pre-push, etc.)
54
+ * @param projectRoot - Project root directory
55
+ */
56
+ export declare function logForceBypass(hookName: string, projectRoot: string): void;
57
+ /**
58
+ * Parse a single line from the audit log
59
+ *
60
+ * @param line - Raw log line
61
+ * @returns Parsed entry or null if invalid
62
+ */
63
+ export declare function parseAuditLogLine(line: string): AuditLogEntry | null;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * WU-1070: Force Bypass Audit Logging
3
+ *
4
+ * Provides audit logging for LUMENFLOW_FORCE bypass usage.
5
+ * All hooks log bypass events to .beacon/force-bypasses.log for accountability.
6
+ *
7
+ * Key principles:
8
+ * - FAIL-OPEN: Audit logging must never block the bypass itself
9
+ * - WARN ON MISSING REASON: stderr warning if LUMENFLOW_FORCE_REASON not set
10
+ * - GIT-TRACKED: .beacon/force-bypasses.log is version controlled
11
+ *
12
+ * Log format:
13
+ * ISO_TIMESTAMP | HOOK_NAME | GIT_USER | BRANCH | REASON | CWD
14
+ *
15
+ * Example:
16
+ * 2026-01-23T10:30:00.000Z | pre-commit | tom@hellm.ai | lane/ops/wu-1070 | Emergency hotfix | /project
17
+ */
18
+ import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
19
+ import { execSync } from 'node:child_process';
20
+ import { join } from 'node:path';
21
+ /**
22
+ * Environment variable names for force bypass
23
+ */
24
+ export const FORCE_ENV_VAR = 'LUMENFLOW_FORCE';
25
+ export const FORCE_REASON_ENV_VAR = 'LUMENFLOW_FORCE_REASON';
26
+ /**
27
+ * Log file path relative to project root
28
+ */
29
+ const LOG_FILE_NAME = '.beacon/force-bypasses.log';
30
+ /**
31
+ * Check if LUMENFLOW_FORCE bypass is active
32
+ *
33
+ * @returns true if LUMENFLOW_FORCE=1
34
+ */
35
+ export function shouldBypass() {
36
+ return process.env[FORCE_ENV_VAR] === '1';
37
+ }
38
+ /**
39
+ * Get the audit log file path
40
+ *
41
+ * @param projectRoot - Project root directory
42
+ * @returns Full path to the audit log file
43
+ */
44
+ export function getAuditLogPath(projectRoot) {
45
+ return join(projectRoot, LOG_FILE_NAME);
46
+ }
47
+ /**
48
+ * Get git user.name for audit logging
49
+ *
50
+ * @returns Git user name or 'unknown' if not configured
51
+ */
52
+ function getGitUser() {
53
+ try {
54
+ return execSync('git config user.name', { encoding: 'utf8' }).trim();
55
+ }
56
+ catch {
57
+ return 'unknown';
58
+ }
59
+ }
60
+ /**
61
+ * Get current git branch name
62
+ *
63
+ * @returns Branch name or 'unknown' if not in a git repo
64
+ */
65
+ function getGitBranch() {
66
+ try {
67
+ return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
68
+ }
69
+ catch {
70
+ return 'unknown';
71
+ }
72
+ }
73
+ /**
74
+ * Log a force bypass event to the audit log
75
+ *
76
+ * This function is FAIL-OPEN: if logging fails, it warns to stderr
77
+ * but does NOT throw or block the bypass operation.
78
+ *
79
+ * @param hookName - Name of the hook being bypassed (pre-commit, pre-push, etc.)
80
+ * @param projectRoot - Project root directory
81
+ */
82
+ export function logForceBypass(hookName, projectRoot) {
83
+ // Only log when bypass is active
84
+ if (!shouldBypass()) {
85
+ return;
86
+ }
87
+ // Get reason from environment (warn if missing)
88
+ const reason = process.env[FORCE_REASON_ENV_VAR];
89
+ if (!reason) {
90
+ console.warn(`[${hookName}] Warning: ${FORCE_REASON_ENV_VAR} not set. ` +
91
+ 'Consider providing a reason for the bypass: ' +
92
+ `${FORCE_REASON_ENV_VAR}="reason here" LUMENFLOW_FORCE=1 git ...`);
93
+ }
94
+ // Collect audit data
95
+ const timestamp = new Date().toISOString();
96
+ const user = getGitUser();
97
+ const branch = getGitBranch();
98
+ const cwd = projectRoot;
99
+ const reasonText = reason || '(no reason provided)';
100
+ // Format log line: ISO timestamp | hook | user | branch | reason | cwd
101
+ const logLine = `${timestamp} | ${hookName} | ${user} | ${branch} | ${reasonText} | ${cwd}\n`;
102
+ // Write to log file (fail-open)
103
+ try {
104
+ const logPath = getAuditLogPath(projectRoot);
105
+ const beaconDir = join(projectRoot, '.beacon');
106
+ // Ensure .beacon directory exists
107
+ if (!existsSync(beaconDir)) {
108
+ mkdirSync(beaconDir, { recursive: true });
109
+ }
110
+ appendFileSync(logPath, logLine);
111
+ }
112
+ catch (error) {
113
+ // Fail-open: warn but don't block
114
+ console.error(`[${hookName}] Warning: Failed to write to audit log: ${error instanceof Error ? error.message : String(error)}`);
115
+ }
116
+ }
117
+ /**
118
+ * Parse a single line from the audit log
119
+ *
120
+ * @param line - Raw log line
121
+ * @returns Parsed entry or null if invalid
122
+ */
123
+ export function parseAuditLogLine(line) {
124
+ if (!line || line.trim() === '') {
125
+ return null;
126
+ }
127
+ const parts = line.split(' | ');
128
+ if (parts.length !== 6) {
129
+ return null;
130
+ }
131
+ const [timestamp, hook, user, branch, reason, cwd] = parts;
132
+ return {
133
+ timestamp: timestamp.trim(),
134
+ hook: hook.trim(),
135
+ user: user.trim(),
136
+ branch: branch.trim(),
137
+ reason: reason.trim(),
138
+ cwd: cwd.trim(),
139
+ };
140
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Gates Configuration
3
+ *
4
+ * WU-1067: Config-driven gates execution
5
+ *
6
+ * Provides a config-driven gates system that allows users to define
7
+ * custom format, lint, test commands in .lumenflow.config.yaml instead
8
+ * of relying on hardcoded language presets.
9
+ *
10
+ * @module gates-config
11
+ */
12
+ import { z } from 'zod';
13
+ /**
14
+ * Schema for a gate command - either a string or an object with options
15
+ */
16
+ export declare const GateCommandConfigSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
17
+ command: z.ZodString;
18
+ continueOnError: z.ZodOptional<z.ZodBoolean>;
19
+ timeout: z.ZodOptional<z.ZodNumber>;
20
+ }, z.core.$strip>]>;
21
+ /**
22
+ * Type for parsed gate command configuration
23
+ */
24
+ export type GateCommandConfig = z.infer<typeof GateCommandConfigSchema>;
25
+ /**
26
+ * Schema for the gates execution configuration section
27
+ */
28
+ export declare const GatesExecutionConfigSchema: z.ZodObject<{
29
+ preset: z.ZodOptional<z.ZodString>;
30
+ setup: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
31
+ command: z.ZodString;
32
+ continueOnError: z.ZodOptional<z.ZodBoolean>;
33
+ timeout: z.ZodOptional<z.ZodNumber>;
34
+ }, z.core.$strip>]>>;
35
+ format: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
36
+ command: z.ZodString;
37
+ continueOnError: z.ZodOptional<z.ZodBoolean>;
38
+ timeout: z.ZodOptional<z.ZodNumber>;
39
+ }, z.core.$strip>]>>;
40
+ lint: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
41
+ command: z.ZodString;
42
+ continueOnError: z.ZodOptional<z.ZodBoolean>;
43
+ timeout: z.ZodOptional<z.ZodNumber>;
44
+ }, z.core.$strip>]>>;
45
+ typecheck: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
46
+ command: z.ZodString;
47
+ continueOnError: z.ZodOptional<z.ZodBoolean>;
48
+ timeout: z.ZodOptional<z.ZodNumber>;
49
+ }, z.core.$strip>]>>;
50
+ test: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
51
+ command: z.ZodString;
52
+ continueOnError: z.ZodOptional<z.ZodBoolean>;
53
+ timeout: z.ZodOptional<z.ZodNumber>;
54
+ }, z.core.$strip>]>>;
55
+ coverage: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
56
+ command: z.ZodString;
57
+ threshold: z.ZodOptional<z.ZodNumber>;
58
+ }, z.core.$strip>]>>;
59
+ }, z.core.$strip>;
60
+ /**
61
+ * Type for gates execution configuration
62
+ */
63
+ export type GatesExecutionConfig = z.infer<typeof GatesExecutionConfigSchema>;
64
+ /**
65
+ * Parsed gate command ready for execution
66
+ */
67
+ export interface ParsedGateCommand {
68
+ /** The shell command to execute */
69
+ command: string;
70
+ /** Whether to continue if this gate fails */
71
+ continueOnError: boolean;
72
+ /** Timeout in milliseconds */
73
+ timeout: number;
74
+ }
75
+ /**
76
+ * Gate preset definitions
77
+ *
78
+ * These provide sensible defaults for common language ecosystems.
79
+ * Users can override any field via .lumenflow.config.yaml
80
+ */
81
+ export declare const GATE_PRESETS: Record<string, Partial<GatesExecutionConfig>>;
82
+ /**
83
+ * Parse a gate command configuration into executable form
84
+ *
85
+ * @param config - Gate command configuration (string or object)
86
+ * @returns Parsed command with defaults applied, or null if undefined
87
+ */
88
+ export declare function parseGateCommand(config: GateCommandConfig | undefined): ParsedGateCommand | null;
89
+ /**
90
+ * Expand a preset name into its default gate commands
91
+ *
92
+ * @param preset - Preset name (node, python, go, rust, dotnet) or undefined
93
+ * @returns Partial gates config with preset defaults, or empty object if unknown
94
+ */
95
+ export declare function expandPreset(preset: string | undefined): Partial<GatesExecutionConfig>;
96
+ /**
97
+ * Load gates configuration from .lumenflow.config.yaml
98
+ *
99
+ * @param projectRoot - Project root directory
100
+ * @returns Gates execution config, or null if not configured
101
+ */
102
+ export declare function loadGatesConfig(projectRoot: string): GatesExecutionConfig | null;
103
+ /**
104
+ * Get default gates configuration for auto-detection fallback
105
+ *
106
+ * Used when no gates config is present in .lumenflow.config.yaml.
107
+ * These are generic commands that work across common setups.
108
+ *
109
+ * @returns Default gates configuration
110
+ */
111
+ export declare function getDefaultGatesConfig(): GatesExecutionConfig;
112
+ /**
113
+ * Resolve the effective gates configuration
114
+ *
115
+ * Priority order:
116
+ * 1. Explicit config from .lumenflow.config.yaml
117
+ * 2. Preset defaults (if preset specified)
118
+ * 3. Auto-detection defaults
119
+ *
120
+ * @param projectRoot - Project root directory
121
+ * @returns Resolved gates configuration
122
+ */
123
+ export declare function resolveGatesConfig(projectRoot: string): GatesExecutionConfig;
124
+ /**
125
+ * Check if a specific gate should be skipped
126
+ *
127
+ * @param gateName - Name of the gate (format, lint, typecheck, test)
128
+ * @param config - Gates execution configuration
129
+ * @param skipFlags - Map of skip flags from CLI/Action inputs
130
+ * @returns True if the gate should be skipped
131
+ */
132
+ export declare function shouldSkipGate(gateName: keyof Omit<GatesExecutionConfig, 'preset' | 'coverage'>, config: GatesExecutionConfig, skipFlags: Record<string, boolean>): boolean;
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Gates Configuration
3
+ *
4
+ * WU-1067: Config-driven gates execution
5
+ *
6
+ * Provides a config-driven gates system that allows users to define
7
+ * custom format, lint, test commands in .lumenflow.config.yaml instead
8
+ * of relying on hardcoded language presets.
9
+ *
10
+ * @module gates-config
11
+ */
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import * as yaml from 'yaml';
15
+ import { z } from 'zod';
16
+ /**
17
+ * Default timeout for gate commands (2 minutes)
18
+ */
19
+ const DEFAULT_GATE_TIMEOUT = 120000;
20
+ /**
21
+ * Schema for a gate command object with options
22
+ */
23
+ const GateCommandObjectSchema = z.object({
24
+ /** The shell command to execute */
25
+ command: z.string(),
26
+ /** Whether to continue if this gate fails (default: false) */
27
+ continueOnError: z.boolean().optional(),
28
+ /** Timeout in milliseconds (default: 120000) */
29
+ timeout: z.number().int().positive().optional(),
30
+ });
31
+ /**
32
+ * Schema for a gate command - either a string or an object with options
33
+ */
34
+ export const GateCommandConfigSchema = z.union([z.string(), GateCommandObjectSchema]);
35
+ /**
36
+ * Schema for the gates execution configuration section
37
+ */
38
+ export const GatesExecutionConfigSchema = z.object({
39
+ /** Preset to use for default commands (node, python, go, rust, dotnet) */
40
+ preset: z.string().optional(),
41
+ /** Setup command (e.g., install dependencies) */
42
+ setup: GateCommandConfigSchema.optional(),
43
+ /** Format check command */
44
+ format: GateCommandConfigSchema.optional(),
45
+ /** Lint command */
46
+ lint: GateCommandConfigSchema.optional(),
47
+ /** Type check command */
48
+ typecheck: GateCommandConfigSchema.optional(),
49
+ /** Test command */
50
+ test: GateCommandConfigSchema.optional(),
51
+ /** Coverage configuration */
52
+ coverage: z
53
+ .union([
54
+ z.string(),
55
+ z.object({
56
+ command: z.string(),
57
+ threshold: z.number().min(0).max(100).optional(),
58
+ }),
59
+ ])
60
+ .optional(),
61
+ });
62
+ /**
63
+ * Gate preset definitions
64
+ *
65
+ * These provide sensible defaults for common language ecosystems.
66
+ * Users can override any field via .lumenflow.config.yaml
67
+ */
68
+ export const GATE_PRESETS = {
69
+ node: {
70
+ setup: 'npm ci || npm install',
71
+ format: 'npx prettier --check .',
72
+ lint: 'npx eslint .',
73
+ typecheck: 'npx tsc --noEmit',
74
+ test: 'npm test',
75
+ },
76
+ python: {
77
+ setup: 'pip install -e ".[dev]" || pip install -r requirements.txt',
78
+ format: 'ruff format --check .',
79
+ lint: 'ruff check .',
80
+ typecheck: 'mypy .',
81
+ test: 'pytest',
82
+ },
83
+ go: {
84
+ format: 'gofmt -l . | grep -v "^$" && exit 1 || exit 0',
85
+ lint: 'golangci-lint run',
86
+ typecheck: 'go vet ./...',
87
+ test: 'go test ./...',
88
+ },
89
+ rust: {
90
+ format: 'cargo fmt --check',
91
+ lint: 'cargo clippy -- -D warnings',
92
+ typecheck: 'cargo check',
93
+ test: 'cargo test',
94
+ },
95
+ dotnet: {
96
+ setup: 'dotnet restore',
97
+ format: 'dotnet format --verify-no-changes',
98
+ lint: 'dotnet build --no-restore -warnaserror',
99
+ test: 'dotnet test --no-restore',
100
+ },
101
+ };
102
+ /**
103
+ * Parse a gate command configuration into executable form
104
+ *
105
+ * @param config - Gate command configuration (string or object)
106
+ * @returns Parsed command with defaults applied, or null if undefined
107
+ */
108
+ export function parseGateCommand(config) {
109
+ if (config === undefined) {
110
+ return null;
111
+ }
112
+ if (typeof config === 'string') {
113
+ return {
114
+ command: config,
115
+ continueOnError: false,
116
+ timeout: DEFAULT_GATE_TIMEOUT,
117
+ };
118
+ }
119
+ return {
120
+ command: config.command,
121
+ continueOnError: config.continueOnError ?? false,
122
+ timeout: config.timeout ?? DEFAULT_GATE_TIMEOUT,
123
+ };
124
+ }
125
+ /**
126
+ * Expand a preset name into its default gate commands
127
+ *
128
+ * @param preset - Preset name (node, python, go, rust, dotnet) or undefined
129
+ * @returns Partial gates config with preset defaults, or empty object if unknown
130
+ */
131
+ export function expandPreset(preset) {
132
+ if (!preset) {
133
+ return {};
134
+ }
135
+ return GATE_PRESETS[preset] ?? {};
136
+ }
137
+ /**
138
+ * Load gates configuration from .lumenflow.config.yaml
139
+ *
140
+ * @param projectRoot - Project root directory
141
+ * @returns Gates execution config, or null if not configured
142
+ */
143
+ export function loadGatesConfig(projectRoot) {
144
+ const configPath = path.join(projectRoot, '.lumenflow.config.yaml');
145
+ if (!fs.existsSync(configPath)) {
146
+ return null;
147
+ }
148
+ try {
149
+ const content = fs.readFileSync(configPath, 'utf8');
150
+ const data = yaml.parse(content);
151
+ // Check if gates.execution section exists
152
+ const executionConfig = data?.gates?.execution;
153
+ if (!executionConfig) {
154
+ return null;
155
+ }
156
+ // Validate the config
157
+ const result = GatesExecutionConfigSchema.safeParse(executionConfig);
158
+ if (!result.success) {
159
+ console.warn('Warning: Invalid gates.execution config:', result.error.message);
160
+ return null;
161
+ }
162
+ // Expand preset and merge with explicit config (explicit wins)
163
+ const presetDefaults = expandPreset(result.data.preset);
164
+ const merged = {
165
+ ...presetDefaults,
166
+ ...result.data,
167
+ };
168
+ return merged;
169
+ }
170
+ catch (error) {
171
+ console.warn('Warning: Failed to parse .lumenflow.config.yaml:', error instanceof Error ? error.message : String(error));
172
+ return null;
173
+ }
174
+ }
175
+ /**
176
+ * Get default gates configuration for auto-detection fallback
177
+ *
178
+ * Used when no gates config is present in .lumenflow.config.yaml.
179
+ * These are generic commands that work across common setups.
180
+ *
181
+ * @returns Default gates configuration
182
+ */
183
+ export function getDefaultGatesConfig() {
184
+ return {
185
+ format: 'npm run format:check 2>/dev/null || npx prettier --check . 2>/dev/null || echo "No formatter configured"',
186
+ lint: 'npm run lint 2>/dev/null || npx eslint . 2>/dev/null || echo "No linter configured"',
187
+ typecheck: 'npm run typecheck 2>/dev/null || npx tsc --noEmit 2>/dev/null || echo "No type checker configured"',
188
+ test: 'npm test 2>/dev/null || echo "No test command configured"',
189
+ };
190
+ }
191
+ /**
192
+ * Resolve the effective gates configuration
193
+ *
194
+ * Priority order:
195
+ * 1. Explicit config from .lumenflow.config.yaml
196
+ * 2. Preset defaults (if preset specified)
197
+ * 3. Auto-detection defaults
198
+ *
199
+ * @param projectRoot - Project root directory
200
+ * @returns Resolved gates configuration
201
+ */
202
+ export function resolveGatesConfig(projectRoot) {
203
+ const config = loadGatesConfig(projectRoot);
204
+ if (config) {
205
+ return config;
206
+ }
207
+ // Fall back to defaults for auto-detection
208
+ return getDefaultGatesConfig();
209
+ }
210
+ /**
211
+ * Check if a specific gate should be skipped
212
+ *
213
+ * @param gateName - Name of the gate (format, lint, typecheck, test)
214
+ * @param config - Gates execution configuration
215
+ * @param skipFlags - Map of skip flags from CLI/Action inputs
216
+ * @returns True if the gate should be skipped
217
+ */
218
+ export function shouldSkipGate(gateName, config, skipFlags) {
219
+ // Check if skip flag is set
220
+ const skipFlagName = `skip-${gateName}`;
221
+ if (skipFlags[skipFlagName] || skipFlags[gateName]) {
222
+ return true;
223
+ }
224
+ // Check if gate is configured (undefined means skip)
225
+ if (config[gateName] === undefined) {
226
+ return true;
227
+ }
228
+ return false;
229
+ }
@@ -66,6 +66,13 @@ export declare class GitAdapter {
66
66
  * await git.getStatus(); // " M file.txt\n?? untracked.txt"
67
67
  */
68
68
  getStatus(): Promise<string>;
69
+ /**
70
+ * Get unpushed commits (compared to upstream)
71
+ * @returns {Promise<string>} Oneline log output for unpushed commits
72
+ * @example
73
+ * await git.getUnpushedCommits(); // "abc123 fix: ...\n"
74
+ */
75
+ getUnpushedCommits(): Promise<string>;
69
76
  /**
70
77
  * Check if a branch exists
71
78
  * @param {string} branch - Branch name