@lumenflow/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +119 -0
- package/dist/active-wu-detector.d.ts +33 -0
- package/dist/active-wu-detector.js +106 -0
- package/dist/adapters/filesystem-metrics.adapter.d.ts +108 -0
- package/dist/adapters/filesystem-metrics.adapter.js +519 -0
- package/dist/adapters/terminal-renderer.adapter.d.ts +106 -0
- package/dist/adapters/terminal-renderer.adapter.js +337 -0
- package/dist/arg-parser.d.ts +63 -0
- package/dist/arg-parser.js +560 -0
- package/dist/backlog-editor.d.ts +98 -0
- package/dist/backlog-editor.js +179 -0
- package/dist/backlog-generator.d.ts +111 -0
- package/dist/backlog-generator.js +381 -0
- package/dist/backlog-parser.d.ts +45 -0
- package/dist/backlog-parser.js +102 -0
- package/dist/backlog-sync-validator.d.ts +78 -0
- package/dist/backlog-sync-validator.js +294 -0
- package/dist/branch-drift.d.ts +34 -0
- package/dist/branch-drift.js +51 -0
- package/dist/cleanup-install-config.d.ts +33 -0
- package/dist/cleanup-install-config.js +37 -0
- package/dist/cleanup-lock.d.ts +139 -0
- package/dist/cleanup-lock.js +313 -0
- package/dist/code-path-validator.d.ts +146 -0
- package/dist/code-path-validator.js +537 -0
- package/dist/code-paths-overlap.d.ts +55 -0
- package/dist/code-paths-overlap.js +245 -0
- package/dist/commands-logger.d.ts +77 -0
- package/dist/commands-logger.js +254 -0
- package/dist/commit-message-utils.d.ts +25 -0
- package/dist/commit-message-utils.js +41 -0
- package/dist/compliance-parser.d.ts +150 -0
- package/dist/compliance-parser.js +507 -0
- package/dist/constants/backlog-patterns.d.ts +20 -0
- package/dist/constants/backlog-patterns.js +23 -0
- package/dist/constants/dora-constants.d.ts +49 -0
- package/dist/constants/dora-constants.js +53 -0
- package/dist/constants/gate-constants.d.ts +15 -0
- package/dist/constants/gate-constants.js +15 -0
- package/dist/constants/linter-constants.d.ts +16 -0
- package/dist/constants/linter-constants.js +16 -0
- package/dist/constants/tokenizer-constants.d.ts +15 -0
- package/dist/constants/tokenizer-constants.js +15 -0
- package/dist/core/scope-checker.d.ts +97 -0
- package/dist/core/scope-checker.js +163 -0
- package/dist/core/tool-runner.d.ts +161 -0
- package/dist/core/tool-runner.js +393 -0
- package/dist/core/tool.constants.d.ts +105 -0
- package/dist/core/tool.constants.js +101 -0
- package/dist/core/tool.schemas.d.ts +226 -0
- package/dist/core/tool.schemas.js +226 -0
- package/dist/core/worktree-guard.d.ts +130 -0
- package/dist/core/worktree-guard.js +242 -0
- package/dist/coverage-gate.d.ts +108 -0
- package/dist/coverage-gate.js +196 -0
- package/dist/date-utils.d.ts +75 -0
- package/dist/date-utils.js +140 -0
- package/dist/dependency-graph.d.ts +142 -0
- package/dist/dependency-graph.js +550 -0
- package/dist/dependency-guard.d.ts +54 -0
- package/dist/dependency-guard.js +142 -0
- package/dist/dependency-validator.d.ts +105 -0
- package/dist/dependency-validator.js +154 -0
- package/dist/docs-path-validator.d.ts +36 -0
- package/dist/docs-path-validator.js +95 -0
- package/dist/domain/orchestration.constants.d.ts +99 -0
- package/dist/domain/orchestration.constants.js +97 -0
- package/dist/domain/orchestration.schemas.d.ts +280 -0
- package/dist/domain/orchestration.schemas.js +211 -0
- package/dist/domain/orchestration.types.d.ts +133 -0
- package/dist/domain/orchestration.types.js +12 -0
- package/dist/error-handler.d.ts +116 -0
- package/dist/error-handler.js +136 -0
- package/dist/file-classifiers.d.ts +62 -0
- package/dist/file-classifiers.js +108 -0
- package/dist/gates-agent-mode.d.ts +81 -0
- package/dist/gates-agent-mode.js +94 -0
- package/dist/generate-traceability.d.ts +107 -0
- package/dist/generate-traceability.js +411 -0
- package/dist/git-adapter.d.ts +395 -0
- package/dist/git-adapter.js +649 -0
- package/dist/git-staged-validator.d.ts +32 -0
- package/dist/git-staged-validator.js +48 -0
- package/dist/hardcoded-strings.d.ts +61 -0
- package/dist/hardcoded-strings.js +270 -0
- package/dist/incremental-lint.d.ts +78 -0
- package/dist/incremental-lint.js +129 -0
- package/dist/incremental-test.d.ts +39 -0
- package/dist/incremental-test.js +61 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +61 -0
- package/dist/invariants/check-automated-tests.d.ts +50 -0
- package/dist/invariants/check-automated-tests.js +166 -0
- package/dist/invariants-runner.d.ts +103 -0
- package/dist/invariants-runner.js +527 -0
- package/dist/lane-checker.d.ts +50 -0
- package/dist/lane-checker.js +319 -0
- package/dist/lane-inference.d.ts +39 -0
- package/dist/lane-inference.js +195 -0
- package/dist/lane-lock.d.ts +211 -0
- package/dist/lane-lock.js +474 -0
- package/dist/lane-validator.d.ts +48 -0
- package/dist/lane-validator.js +114 -0
- package/dist/logs-lib.d.ts +104 -0
- package/dist/logs-lib.js +207 -0
- package/dist/lumenflow-config-schema.d.ts +272 -0
- package/dist/lumenflow-config-schema.js +207 -0
- package/dist/lumenflow-config.d.ts +95 -0
- package/dist/lumenflow-config.js +236 -0
- package/dist/manual-test-validator.d.ts +80 -0
- package/dist/manual-test-validator.js +200 -0
- package/dist/merge-lock.d.ts +115 -0
- package/dist/merge-lock.js +251 -0
- package/dist/micro-worktree.d.ts +159 -0
- package/dist/micro-worktree.js +427 -0
- package/dist/migration-deployer.d.ts +69 -0
- package/dist/migration-deployer.js +151 -0
- package/dist/orchestration-advisory-loader.d.ts +28 -0
- package/dist/orchestration-advisory-loader.js +87 -0
- package/dist/orchestration-advisory.d.ts +58 -0
- package/dist/orchestration-advisory.js +94 -0
- package/dist/orchestration-di.d.ts +48 -0
- package/dist/orchestration-di.js +57 -0
- package/dist/orchestration-rules.d.ts +57 -0
- package/dist/orchestration-rules.js +201 -0
- package/dist/orphan-detector.d.ts +131 -0
- package/dist/orphan-detector.js +226 -0
- package/dist/path-classifiers.d.ts +57 -0
- package/dist/path-classifiers.js +93 -0
- package/dist/piped-command-detector.d.ts +34 -0
- package/dist/piped-command-detector.js +64 -0
- package/dist/ports/dashboard-renderer.port.d.ts +112 -0
- package/dist/ports/dashboard-renderer.port.js +25 -0
- package/dist/ports/metrics-collector.port.d.ts +132 -0
- package/dist/ports/metrics-collector.port.js +26 -0
- package/dist/process-detector.d.ts +84 -0
- package/dist/process-detector.js +172 -0
- package/dist/prompt-linter.d.ts +72 -0
- package/dist/prompt-linter.js +312 -0
- package/dist/prompt-monitor.d.ts +15 -0
- package/dist/prompt-monitor.js +205 -0
- package/dist/rebase-artifact-cleanup.d.ts +145 -0
- package/dist/rebase-artifact-cleanup.js +433 -0
- package/dist/retry-strategy.d.ts +189 -0
- package/dist/retry-strategy.js +283 -0
- package/dist/risk-detector.d.ts +108 -0
- package/dist/risk-detector.js +252 -0
- package/dist/rollback-utils.d.ts +76 -0
- package/dist/rollback-utils.js +104 -0
- package/dist/section-headings.d.ts +43 -0
- package/dist/section-headings.js +49 -0
- package/dist/spawn-escalation.d.ts +90 -0
- package/dist/spawn-escalation.js +253 -0
- package/dist/spawn-monitor.d.ts +229 -0
- package/dist/spawn-monitor.js +672 -0
- package/dist/spawn-recovery.d.ts +82 -0
- package/dist/spawn-recovery.js +298 -0
- package/dist/spawn-registry-schema.d.ts +98 -0
- package/dist/spawn-registry-schema.js +108 -0
- package/dist/spawn-registry-store.d.ts +146 -0
- package/dist/spawn-registry-store.js +273 -0
- package/dist/spawn-tree.d.ts +121 -0
- package/dist/spawn-tree.js +285 -0
- package/dist/stamp-status-validator.d.ts +84 -0
- package/dist/stamp-status-validator.js +134 -0
- package/dist/stamp-utils.d.ts +100 -0
- package/dist/stamp-utils.js +229 -0
- package/dist/state-machine.d.ts +26 -0
- package/dist/state-machine.js +83 -0
- package/dist/system-map-validator.d.ts +80 -0
- package/dist/system-map-validator.js +272 -0
- package/dist/telemetry.d.ts +80 -0
- package/dist/telemetry.js +213 -0
- package/dist/token-counter.d.ts +51 -0
- package/dist/token-counter.js +145 -0
- package/dist/usecases/get-dashboard-data.usecase.d.ts +52 -0
- package/dist/usecases/get-dashboard-data.usecase.js +61 -0
- package/dist/usecases/get-suggestions.usecase.d.ts +100 -0
- package/dist/usecases/get-suggestions.usecase.js +153 -0
- package/dist/user-normalizer.d.ts +41 -0
- package/dist/user-normalizer.js +141 -0
- package/dist/validators/phi-constants.d.ts +97 -0
- package/dist/validators/phi-constants.js +152 -0
- package/dist/validators/phi-scanner.d.ts +58 -0
- package/dist/validators/phi-scanner.js +215 -0
- package/dist/worktree-ownership.d.ts +50 -0
- package/dist/worktree-ownership.js +74 -0
- package/dist/worktree-scanner.d.ts +103 -0
- package/dist/worktree-scanner.js +168 -0
- package/dist/worktree-symlink.d.ts +99 -0
- package/dist/worktree-symlink.js +359 -0
- package/dist/wu-backlog-updater.d.ts +17 -0
- package/dist/wu-backlog-updater.js +37 -0
- package/dist/wu-checkpoint.d.ts +124 -0
- package/dist/wu-checkpoint.js +233 -0
- package/dist/wu-claim-helpers.d.ts +26 -0
- package/dist/wu-claim-helpers.js +63 -0
- package/dist/wu-claim-resume.d.ts +106 -0
- package/dist/wu-claim-resume.js +276 -0
- package/dist/wu-consistency-checker.d.ts +95 -0
- package/dist/wu-consistency-checker.js +567 -0
- package/dist/wu-constants.d.ts +1275 -0
- package/dist/wu-constants.js +1382 -0
- package/dist/wu-create-validators.d.ts +42 -0
- package/dist/wu-create-validators.js +93 -0
- package/dist/wu-done-branch-only.d.ts +63 -0
- package/dist/wu-done-branch-only.js +191 -0
- package/dist/wu-done-messages.d.ts +119 -0
- package/dist/wu-done-messages.js +185 -0
- package/dist/wu-done-pr.d.ts +72 -0
- package/dist/wu-done-pr.js +174 -0
- package/dist/wu-done-retry-helpers.d.ts +85 -0
- package/dist/wu-done-retry-helpers.js +172 -0
- package/dist/wu-done-ui.d.ts +37 -0
- package/dist/wu-done-ui.js +69 -0
- package/dist/wu-done-validators.d.ts +411 -0
- package/dist/wu-done-validators.js +1229 -0
- package/dist/wu-done-worktree.d.ts +182 -0
- package/dist/wu-done-worktree.js +1097 -0
- package/dist/wu-helpers.d.ts +128 -0
- package/dist/wu-helpers.js +248 -0
- package/dist/wu-lint.d.ts +70 -0
- package/dist/wu-lint.js +234 -0
- package/dist/wu-paths.d.ts +171 -0
- package/dist/wu-paths.js +178 -0
- package/dist/wu-preflight-validators.d.ts +86 -0
- package/dist/wu-preflight-validators.js +251 -0
- package/dist/wu-recovery.d.ts +138 -0
- package/dist/wu-recovery.js +341 -0
- package/dist/wu-repair-core.d.ts +131 -0
- package/dist/wu-repair-core.js +669 -0
- package/dist/wu-schema-normalization.d.ts +17 -0
- package/dist/wu-schema-normalization.js +82 -0
- package/dist/wu-schema.d.ts +793 -0
- package/dist/wu-schema.js +881 -0
- package/dist/wu-spawn-helpers.d.ts +121 -0
- package/dist/wu-spawn-helpers.js +271 -0
- package/dist/wu-spawn.d.ts +158 -0
- package/dist/wu-spawn.js +1306 -0
- package/dist/wu-state-schema.d.ts +213 -0
- package/dist/wu-state-schema.js +156 -0
- package/dist/wu-state-store.d.ts +264 -0
- package/dist/wu-state-store.js +691 -0
- package/dist/wu-status-transition.d.ts +63 -0
- package/dist/wu-status-transition.js +382 -0
- package/dist/wu-status-updater.d.ts +25 -0
- package/dist/wu-status-updater.js +116 -0
- package/dist/wu-transaction-collectors.d.ts +116 -0
- package/dist/wu-transaction-collectors.js +272 -0
- package/dist/wu-transaction.d.ts +170 -0
- package/dist/wu-transaction.js +273 -0
- package/dist/wu-validation-constants.d.ts +60 -0
- package/dist/wu-validation-constants.js +66 -0
- package/dist/wu-validation.d.ts +118 -0
- package/dist/wu-validation.js +243 -0
- package/dist/wu-validator.d.ts +62 -0
- package/dist/wu-validator.js +325 -0
- package/dist/wu-yaml-fixer.d.ts +97 -0
- package/dist/wu-yaml-fixer.js +264 -0
- package/dist/wu-yaml.d.ts +86 -0
- package/dist/wu-yaml.js +222 -0
- package/package.json +114 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* WU Helpers - Shared utilities for worktree and WU validation hooks
|
|
4
|
+
*
|
|
5
|
+
* Used by:
|
|
6
|
+
* - .husky/prepare-commit-msg
|
|
7
|
+
* - .husky/pre-commit
|
|
8
|
+
* - .husky/pre-push
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Validate WU ID format
|
|
12
|
+
*
|
|
13
|
+
* WU-1593: Extracted from duplicate implementations in wu-create.mjs and wu-edit.mjs (DRY).
|
|
14
|
+
* Uses centralized PATTERNS.WU_ID regex from wu-constants.mjs.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} id - WU ID to validate (e.g., 'WU-123')
|
|
17
|
+
* @throws {Error} If ID format is invalid
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* validateWUIDFormat('WU-123'); // OK
|
|
21
|
+
* validateWUIDFormat('wu-123'); // throws Error
|
|
22
|
+
* validateWUIDFormat('TICKET-123'); // throws Error
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateWUIDFormat(id: any): void;
|
|
25
|
+
/**
|
|
26
|
+
* Execute a shell command and return the output
|
|
27
|
+
* @param {string} cmd - Command to execute
|
|
28
|
+
* @param {object} opts - Execution options
|
|
29
|
+
* @returns {string} Command output
|
|
30
|
+
*/
|
|
31
|
+
export declare function run(cmd: any, opts?: {}): string;
|
|
32
|
+
/**
|
|
33
|
+
* Get the current Git branch name
|
|
34
|
+
* Uses REAL_GIT to bypass shim and prevent recursion.
|
|
35
|
+
* @returns {string|null} Branch name or null if not in a git repo
|
|
36
|
+
*/
|
|
37
|
+
export declare function getCurrentBranch(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Get the current working directory relative to repo root
|
|
40
|
+
* Uses REAL_GIT to bypass shim and prevent recursion.
|
|
41
|
+
* @returns {string} Current directory path
|
|
42
|
+
*/
|
|
43
|
+
export declare function getCurrentDir(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Check if we're in the main worktree (not a lane worktree)
|
|
46
|
+
* Uses REAL_GIT to bypass shim and prevent recursion.
|
|
47
|
+
* @returns {boolean} True if in main worktree
|
|
48
|
+
*/
|
|
49
|
+
export declare function isMainWorktree(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Extract WU ID from branch name
|
|
52
|
+
* Expected format: lane/<lane-name>/<wu-id>
|
|
53
|
+
* @param {string} branch - Branch name
|
|
54
|
+
* @returns {string|null} WU ID (e.g., 'WU-401') or null
|
|
55
|
+
*/
|
|
56
|
+
export declare function extractWUFromBranch(branch: any): any;
|
|
57
|
+
/**
|
|
58
|
+
* Validate branch name follows lane convention
|
|
59
|
+
* Expected: lane/<lane-name>/<wu-id>
|
|
60
|
+
* @param {string} branch - Branch name to validate
|
|
61
|
+
* @returns {{valid: boolean, lane: string|null, wuid: string|null, error: string|null}}
|
|
62
|
+
*/
|
|
63
|
+
export declare function validateBranchName(branch: any): {
|
|
64
|
+
valid: boolean;
|
|
65
|
+
lane: any;
|
|
66
|
+
wuid: any;
|
|
67
|
+
error: string;
|
|
68
|
+
} | {
|
|
69
|
+
valid: boolean;
|
|
70
|
+
lane: any;
|
|
71
|
+
wuid: any;
|
|
72
|
+
error: any;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Read WU YAML file and return parsed content
|
|
76
|
+
* Uses REAL_GIT to bypass shim and prevent recursion.
|
|
77
|
+
* @param {string} wuid - WU ID (e.g., 'WU-401')
|
|
78
|
+
* @returns {object|null} Parsed WU YAML or null if not found
|
|
79
|
+
*/
|
|
80
|
+
export declare function readWUYaml(wuid: any): any;
|
|
81
|
+
/**
|
|
82
|
+
* Check if WU status allows the operation
|
|
83
|
+
* @param {string} wuid - WU ID
|
|
84
|
+
* @param {string[]} allowedStatuses - List of allowed statuses
|
|
85
|
+
* @returns {{allowed: boolean, status: string|null, error: string|null}}
|
|
86
|
+
*/
|
|
87
|
+
export declare function checkWUStatus(wuid: any, allowedStatuses?: string[]): {
|
|
88
|
+
allowed: boolean;
|
|
89
|
+
status: any;
|
|
90
|
+
error: string;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Format an error message for hook output
|
|
94
|
+
* @param {string} hookName - Name of the hook
|
|
95
|
+
* @param {string} message - Error message
|
|
96
|
+
* @returns {string} Formatted error message
|
|
97
|
+
*/
|
|
98
|
+
export declare function formatHookError(hookName: any, message: any): string;
|
|
99
|
+
/**
|
|
100
|
+
* Extract WU ID from commit message
|
|
101
|
+
* Supports formats:
|
|
102
|
+
* - wu(WU-401): message
|
|
103
|
+
* - chore(wu-401): message
|
|
104
|
+
* - feat(wu-401): message
|
|
105
|
+
* @param {string} message - Commit message
|
|
106
|
+
* @returns {string|null} WU ID or null
|
|
107
|
+
*/
|
|
108
|
+
export declare function extractWUFromCommitMessage(message: any): any;
|
|
109
|
+
/**
|
|
110
|
+
* Ensure current branch is main. Throws if not.
|
|
111
|
+
*
|
|
112
|
+
* Centralized from duplicated ensureOnMain() functions across wu-* scripts (WU-1256).
|
|
113
|
+
* Async version - accepts a git adapter with async getCurrentBranch() method.
|
|
114
|
+
*
|
|
115
|
+
* @param {object} git - Git adapter with async getCurrentBranch() method
|
|
116
|
+
* @throws {Error} If not on main branch
|
|
117
|
+
*/
|
|
118
|
+
export declare function ensureOnMain(git: any): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Ensure main branch is up to date with origin.
|
|
121
|
+
*
|
|
122
|
+
* Centralized from duplicated ensureMainUpToDate() functions across wu-* scripts (WU-1256).
|
|
123
|
+
*
|
|
124
|
+
* @param {object} git - Git adapter with async fetch() and getCommitHash() methods
|
|
125
|
+
* @param {string} [scriptName='wu'] - Script name for logging
|
|
126
|
+
* @throws {Error} If main is out of sync with origin
|
|
127
|
+
*/
|
|
128
|
+
export declare function ensureMainUpToDate(git: any, _scriptName?: string): Promise<void>;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* WU Helpers - Shared utilities for worktree and WU validation hooks
|
|
4
|
+
*
|
|
5
|
+
* Used by:
|
|
6
|
+
* - .husky/prepare-commit-msg
|
|
7
|
+
* - .husky/pre-commit
|
|
8
|
+
* - .husky/pre-push
|
|
9
|
+
*/
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { parse } from 'yaml';
|
|
14
|
+
import { BRANCHES, REMOTES, STDIO, REAL_GIT, PATTERNS } from './wu-constants.js';
|
|
15
|
+
import { die } from './error-handler.js';
|
|
16
|
+
/**
|
|
17
|
+
* Validate WU ID format
|
|
18
|
+
*
|
|
19
|
+
* WU-1593: Extracted from duplicate implementations in wu-create.mjs and wu-edit.mjs (DRY).
|
|
20
|
+
* Uses centralized PATTERNS.WU_ID regex from wu-constants.mjs.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} id - WU ID to validate (e.g., 'WU-123')
|
|
23
|
+
* @throws {Error} If ID format is invalid
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* validateWUIDFormat('WU-123'); // OK
|
|
27
|
+
* validateWUIDFormat('wu-123'); // throws Error
|
|
28
|
+
* validateWUIDFormat('TICKET-123'); // throws Error
|
|
29
|
+
*/
|
|
30
|
+
export function validateWUIDFormat(id) {
|
|
31
|
+
if (!PATTERNS.WU_ID.test(id)) {
|
|
32
|
+
die(`Invalid WU ID format: "${id}"\n\nExpected format: WU-<number> (e.g., WU-706)`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Execute a shell command and return the output
|
|
37
|
+
* @param {string} cmd - Command to execute
|
|
38
|
+
* @param {object} opts - Execution options
|
|
39
|
+
* @returns {string} Command output
|
|
40
|
+
*/
|
|
41
|
+
export function run(cmd, opts = {}) {
|
|
42
|
+
try {
|
|
43
|
+
return execSync(cmd, { stdio: STDIO.PIPE, encoding: 'utf-8', ...opts }).trim();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the current Git branch name
|
|
51
|
+
* Uses REAL_GIT to bypass shim and prevent recursion.
|
|
52
|
+
* @returns {string|null} Branch name or null if not in a git repo
|
|
53
|
+
*/
|
|
54
|
+
export function getCurrentBranch() {
|
|
55
|
+
return run(`${REAL_GIT} rev-parse --abbrev-ref HEAD`) || null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the current working directory relative to repo root
|
|
59
|
+
* Uses REAL_GIT to bypass shim and prevent recursion.
|
|
60
|
+
* @returns {string} Current directory path
|
|
61
|
+
*/
|
|
62
|
+
export function getCurrentDir() {
|
|
63
|
+
const repoRoot = run(`${REAL_GIT} rev-parse --show-toplevel`);
|
|
64
|
+
const cwd = process.cwd();
|
|
65
|
+
if (!repoRoot)
|
|
66
|
+
return cwd;
|
|
67
|
+
return path.relative(repoRoot, cwd) || '.';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if we're in the main worktree (not a lane worktree)
|
|
71
|
+
* Uses REAL_GIT to bypass shim and prevent recursion.
|
|
72
|
+
* @returns {boolean} True if in main worktree
|
|
73
|
+
*/
|
|
74
|
+
export function isMainWorktree() {
|
|
75
|
+
const gitDir = run(`${REAL_GIT} rev-parse --git-dir`);
|
|
76
|
+
if (!gitDir)
|
|
77
|
+
return true;
|
|
78
|
+
const normalized = gitDir.replace(/\\/g, '/');
|
|
79
|
+
// Lane worktrees live under .git/worktrees/<name>
|
|
80
|
+
return !normalized.includes('/worktrees/');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Extract WU ID from branch name
|
|
84
|
+
* Expected format: lane/<lane-name>/<wu-id>
|
|
85
|
+
* @param {string} branch - Branch name
|
|
86
|
+
* @returns {string|null} WU ID (e.g., 'WU-401') or null
|
|
87
|
+
*/
|
|
88
|
+
export function extractWUFromBranch(branch) {
|
|
89
|
+
if (!branch)
|
|
90
|
+
return null;
|
|
91
|
+
// Match lane/<lane>/<wu-id> format
|
|
92
|
+
const match = branch.match(/^lane\/[^/]+\/(wu-\d+)$/i);
|
|
93
|
+
if (match) {
|
|
94
|
+
return match[1].toUpperCase();
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate branch name follows lane convention
|
|
100
|
+
* Expected: lane/<lane-name>/<wu-id>
|
|
101
|
+
* @param {string} branch - Branch name to validate
|
|
102
|
+
* @returns {{valid: boolean, lane: string|null, wuid: string|null, error: string|null}}
|
|
103
|
+
*/
|
|
104
|
+
export function validateBranchName(branch) {
|
|
105
|
+
if (!branch || branch === BRANCHES.MAIN) {
|
|
106
|
+
return { valid: true, lane: null, wuid: null, error: null };
|
|
107
|
+
}
|
|
108
|
+
const match = branch.match(/^lane\/([^/]+)\/(wu-\d+)$/i);
|
|
109
|
+
if (!match) {
|
|
110
|
+
return {
|
|
111
|
+
valid: false,
|
|
112
|
+
lane: null,
|
|
113
|
+
wuid: null,
|
|
114
|
+
error: `Branch '${branch}' doesn't follow lane/<lane>/<wu-id> convention`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
valid: true,
|
|
119
|
+
lane: match[1],
|
|
120
|
+
wuid: match[2].toUpperCase(),
|
|
121
|
+
error: null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Read WU YAML file and return parsed content
|
|
126
|
+
* Uses REAL_GIT to bypass shim and prevent recursion.
|
|
127
|
+
* @param {string} wuid - WU ID (e.g., 'WU-401')
|
|
128
|
+
* @returns {object|null} Parsed WU YAML or null if not found
|
|
129
|
+
*/
|
|
130
|
+
export function readWUYaml(wuid) {
|
|
131
|
+
const repoRoot = run(`${REAL_GIT} rev-parse --show-toplevel`);
|
|
132
|
+
if (!repoRoot)
|
|
133
|
+
return null;
|
|
134
|
+
const wuPath = path.join(repoRoot, 'docs', '04-operations', 'tasks', 'wu', `${wuid}.yaml`);
|
|
135
|
+
if (!existsSync(wuPath))
|
|
136
|
+
return null;
|
|
137
|
+
try {
|
|
138
|
+
const content = readFileSync(wuPath, { encoding: 'utf-8' });
|
|
139
|
+
return parse(content);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if WU status allows the operation
|
|
147
|
+
* @param {string} wuid - WU ID
|
|
148
|
+
* @param {string[]} allowedStatuses - List of allowed statuses
|
|
149
|
+
* @returns {{allowed: boolean, status: string|null, error: string|null}}
|
|
150
|
+
*/
|
|
151
|
+
export function checkWUStatus(wuid, allowedStatuses = ['in_progress', 'waiting']) {
|
|
152
|
+
const wu = readWUYaml(wuid);
|
|
153
|
+
if (!wu) {
|
|
154
|
+
return {
|
|
155
|
+
allowed: false,
|
|
156
|
+
status: null,
|
|
157
|
+
error: `WU ${wuid} not found in docs/04-operations/tasks/wu/`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const status = wu.status;
|
|
161
|
+
if (!status) {
|
|
162
|
+
return {
|
|
163
|
+
allowed: false,
|
|
164
|
+
status: null,
|
|
165
|
+
error: `WU ${wuid} has no status field`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (!allowedStatuses.includes(status)) {
|
|
169
|
+
return {
|
|
170
|
+
allowed: false,
|
|
171
|
+
status,
|
|
172
|
+
error: `WU ${wuid} status is '${status}' (expected one of: ${allowedStatuses.join(', ')})`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return { allowed: true, status, error: null };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Format an error message for hook output
|
|
179
|
+
* @param {string} hookName - Name of the hook
|
|
180
|
+
* @param {string} message - Error message
|
|
181
|
+
* @returns {string} Formatted error message
|
|
182
|
+
*/
|
|
183
|
+
export function formatHookError(hookName, message) {
|
|
184
|
+
return `
|
|
185
|
+
╔═══════════════════════════════════════════════════════════════════╗
|
|
186
|
+
║ ${hookName.toUpperCase()} HOOK ERROR
|
|
187
|
+
╠═══════════════════════════════════════════════════════════════════╣
|
|
188
|
+
║ ${message}
|
|
189
|
+
╚═══════════════════════════════════════════════════════════════════╝
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Extract WU ID from commit message
|
|
194
|
+
* Supports formats:
|
|
195
|
+
* - wu(WU-401): message
|
|
196
|
+
* - chore(wu-401): message
|
|
197
|
+
* - feat(wu-401): message
|
|
198
|
+
* @param {string} message - Commit message
|
|
199
|
+
* @returns {string|null} WU ID or null
|
|
200
|
+
*/
|
|
201
|
+
export function extractWUFromCommitMessage(message) {
|
|
202
|
+
if (!message)
|
|
203
|
+
return null;
|
|
204
|
+
// Match wu(WU-401) or type(wu-401) patterns
|
|
205
|
+
const patterns = [
|
|
206
|
+
/wu\((wu-\d+)\)/i, // wu(WU-401)
|
|
207
|
+
/\w+\((wu-\d+)\)/i, // chore(wu-401), feat(wu-401), etc.
|
|
208
|
+
];
|
|
209
|
+
for (const pattern of patterns) {
|
|
210
|
+
const match = message.match(pattern);
|
|
211
|
+
if (match) {
|
|
212
|
+
return match[1].toUpperCase();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Ensure current branch is main. Throws if not.
|
|
219
|
+
*
|
|
220
|
+
* Centralized from duplicated ensureOnMain() functions across wu-* scripts (WU-1256).
|
|
221
|
+
* Async version - accepts a git adapter with async getCurrentBranch() method.
|
|
222
|
+
*
|
|
223
|
+
* @param {object} git - Git adapter with async getCurrentBranch() method
|
|
224
|
+
* @throws {Error} If not on main branch
|
|
225
|
+
*/
|
|
226
|
+
export async function ensureOnMain(git) {
|
|
227
|
+
const branch = await git.getCurrentBranch();
|
|
228
|
+
if (branch !== BRANCHES.MAIN) {
|
|
229
|
+
throw new Error(`Run from shared checkout on '${BRANCHES.MAIN}' (found '${branch}')`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Ensure main branch is up to date with origin.
|
|
234
|
+
*
|
|
235
|
+
* Centralized from duplicated ensureMainUpToDate() functions across wu-* scripts (WU-1256).
|
|
236
|
+
*
|
|
237
|
+
* @param {object} git - Git adapter with async fetch() and getCommitHash() methods
|
|
238
|
+
* @param {string} [scriptName='wu'] - Script name for logging
|
|
239
|
+
* @throws {Error} If main is out of sync with origin
|
|
240
|
+
*/
|
|
241
|
+
export async function ensureMainUpToDate(git, _scriptName = 'wu') {
|
|
242
|
+
await git.fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
243
|
+
const localMain = await git.getCommitHash(BRANCHES.MAIN);
|
|
244
|
+
const remoteMain = await git.getCommitHash(`${REMOTES.ORIGIN}/${BRANCHES.MAIN}`);
|
|
245
|
+
if (localMain !== remoteMain) {
|
|
246
|
+
throw new Error(`Main branch is out of sync with origin.\n\nRun: git pull ${REMOTES.ORIGIN} ${BRANCHES.MAIN}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU Spec Linter (WU-2252)
|
|
3
|
+
*
|
|
4
|
+
* Validates WU specs against two critical rules:
|
|
5
|
+
* 1. Acceptance criteria cannot reference file paths absent from code_paths
|
|
6
|
+
* 2. Acceptance/code_paths cannot conflict with invariants.yml
|
|
7
|
+
*
|
|
8
|
+
* This prevents specs that create work contradicting prior fixes.
|
|
9
|
+
*
|
|
10
|
+
* @module tools/lib/wu-lint
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Error type constants for WU spec linting
|
|
14
|
+
*/
|
|
15
|
+
export declare const WU_LINT_ERROR_TYPES: {
|
|
16
|
+
ACCEPTANCE_PATH_NOT_IN_CODE_PATHS: string;
|
|
17
|
+
CODE_PATH_CONFLICTS_INVARIANT: string;
|
|
18
|
+
ACCEPTANCE_CONFLICTS_INVARIANT: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Validate that acceptance criteria only reference paths in code_paths
|
|
22
|
+
*
|
|
23
|
+
* @param {object} wu - WU spec object
|
|
24
|
+
* @param {string} wu.id - WU ID
|
|
25
|
+
* @param {string[]} wu.acceptance - Acceptance criteria
|
|
26
|
+
* @param {string[]} wu.code_paths - Code paths
|
|
27
|
+
* @returns {{valid: boolean, errors: Array<object>}} Validation result
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateAcceptanceCodePaths(wu: any): {
|
|
30
|
+
valid: boolean;
|
|
31
|
+
errors: any[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Validate that code_paths and acceptance do not conflict with invariants
|
|
35
|
+
*
|
|
36
|
+
* @param {object} wu - WU spec object
|
|
37
|
+
* @param {Array<object>} invariants - Array of invariant definitions
|
|
38
|
+
* @returns {{valid: boolean, errors: Array<object>}} Validation result
|
|
39
|
+
*/
|
|
40
|
+
export declare function validateInvariantsCompliance(wu: any, invariants: any): {
|
|
41
|
+
valid: boolean;
|
|
42
|
+
errors: any[];
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Options for linting WU spec
|
|
46
|
+
*/
|
|
47
|
+
export interface LintWUSpecOptions {
|
|
48
|
+
/** Pre-loaded invariants */
|
|
49
|
+
invariants?: unknown[];
|
|
50
|
+
/** Path to invariants.yml */
|
|
51
|
+
invariantsPath?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Lint a WU spec against all rules
|
|
55
|
+
*
|
|
56
|
+
* @param {object} wu - WU spec object
|
|
57
|
+
* @param {LintWUSpecOptions} [options={}] - Options
|
|
58
|
+
* @returns {{valid: boolean, errors: Array<object>}} Lint result
|
|
59
|
+
*/
|
|
60
|
+
export declare function lintWUSpec(wu: any, options?: LintWUSpecOptions): {
|
|
61
|
+
valid: boolean;
|
|
62
|
+
errors: any[];
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Format lint errors for display
|
|
66
|
+
*
|
|
67
|
+
* @param {Array<object>} errors - Array of lint errors
|
|
68
|
+
* @returns {string} Formatted error message
|
|
69
|
+
*/
|
|
70
|
+
export declare function formatLintErrors(errors: any): string;
|
package/dist/wu-lint.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU Spec Linter (WU-2252)
|
|
3
|
+
*
|
|
4
|
+
* Validates WU specs against two critical rules:
|
|
5
|
+
* 1. Acceptance criteria cannot reference file paths absent from code_paths
|
|
6
|
+
* 2. Acceptance/code_paths cannot conflict with invariants.yml
|
|
7
|
+
*
|
|
8
|
+
* This prevents specs that create work contradicting prior fixes.
|
|
9
|
+
*
|
|
10
|
+
* @module tools/lib/wu-lint
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { minimatch } from 'minimatch';
|
|
14
|
+
import { loadInvariants, INVARIANT_TYPES } from './invariants-runner.js';
|
|
15
|
+
/**
|
|
16
|
+
* Error type constants for WU spec linting
|
|
17
|
+
*/
|
|
18
|
+
export const WU_LINT_ERROR_TYPES = {
|
|
19
|
+
ACCEPTANCE_PATH_NOT_IN_CODE_PATHS: 'acceptance_path_not_in_code_paths',
|
|
20
|
+
CODE_PATH_CONFLICTS_INVARIANT: 'code_path_conflicts_invariant',
|
|
21
|
+
ACCEPTANCE_CONFLICTS_INVARIANT: 'acceptance_conflicts_invariant',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Regex to detect file paths in acceptance criteria text
|
|
25
|
+
* Matches patterns like: apps/web/src/file.ts, tools/lib/helper.mjs
|
|
26
|
+
* Uses explicit character sets to avoid regex backtracking issues
|
|
27
|
+
*/
|
|
28
|
+
const FILE_PATH_PATTERN = /(?:^|[\s'"`])([a-zA-Z0-9_-]+\/[a-zA-Z0-9_./-]+\.[a-zA-Z0-9]+)/g;
|
|
29
|
+
/**
|
|
30
|
+
* Extract file paths from acceptance criteria text
|
|
31
|
+
*
|
|
32
|
+
* @param {string} text - Acceptance criterion text
|
|
33
|
+
* @returns {string[]} Array of file paths found
|
|
34
|
+
*/
|
|
35
|
+
function extractFilePaths(text) {
|
|
36
|
+
const paths = [];
|
|
37
|
+
let match;
|
|
38
|
+
while ((match = FILE_PATH_PATTERN.exec(text)) !== null) {
|
|
39
|
+
paths.push(match[1]);
|
|
40
|
+
}
|
|
41
|
+
// Reset regex state
|
|
42
|
+
FILE_PATH_PATTERN.lastIndex = 0;
|
|
43
|
+
return paths;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if a file path matches any pattern in code_paths
|
|
47
|
+
* Supports glob patterns (e.g., apps/web/src/**\/*.ts)
|
|
48
|
+
*
|
|
49
|
+
* @param {string} filePath - File path to check
|
|
50
|
+
* @param {string[]} codePaths - Array of code_paths (may include globs)
|
|
51
|
+
* @returns {boolean} True if path matches any code_paths pattern
|
|
52
|
+
*/
|
|
53
|
+
function pathMatchesCodePaths(filePath, codePaths) {
|
|
54
|
+
for (const pattern of codePaths) {
|
|
55
|
+
// Exact match
|
|
56
|
+
if (filePath === pattern) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
// Glob match
|
|
60
|
+
if (minimatch(filePath, pattern)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validate that acceptance criteria only reference paths in code_paths
|
|
68
|
+
*
|
|
69
|
+
* @param {object} wu - WU spec object
|
|
70
|
+
* @param {string} wu.id - WU ID
|
|
71
|
+
* @param {string[]} wu.acceptance - Acceptance criteria
|
|
72
|
+
* @param {string[]} wu.code_paths - Code paths
|
|
73
|
+
* @returns {{valid: boolean, errors: Array<object>}} Validation result
|
|
74
|
+
*/
|
|
75
|
+
export function validateAcceptanceCodePaths(wu) {
|
|
76
|
+
const { id, acceptance = [], code_paths = [] } = wu;
|
|
77
|
+
const errors = [];
|
|
78
|
+
for (const criterion of acceptance) {
|
|
79
|
+
const referencedPaths = extractFilePaths(criterion);
|
|
80
|
+
for (const referencedPath of referencedPaths) {
|
|
81
|
+
if (!pathMatchesCodePaths(referencedPath, code_paths)) {
|
|
82
|
+
errors.push({
|
|
83
|
+
type: WU_LINT_ERROR_TYPES.ACCEPTANCE_PATH_NOT_IN_CODE_PATHS,
|
|
84
|
+
wuId: id,
|
|
85
|
+
path: referencedPath,
|
|
86
|
+
criterion,
|
|
87
|
+
message: `Acceptance criterion references '${referencedPath}' which is not in code_paths`,
|
|
88
|
+
suggestion: `Add '${referencedPath}' to code_paths or remove the reference from acceptance criteria`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
valid: errors.length === 0,
|
|
95
|
+
errors,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Check forbidden-file invariant for conflicts
|
|
100
|
+
*
|
|
101
|
+
* @param {object} invariant - Invariant definition
|
|
102
|
+
* @param {object} wu - WU spec object
|
|
103
|
+
* @returns {Array<object>} Array of errors (empty if no conflicts)
|
|
104
|
+
*/
|
|
105
|
+
function checkForbiddenFileInvariant(invariant, wu) {
|
|
106
|
+
const { id, acceptance = [], code_paths = [] } = wu;
|
|
107
|
+
const errors = [];
|
|
108
|
+
// Check if code_paths includes the forbidden file
|
|
109
|
+
if (code_paths.includes(invariant.path)) {
|
|
110
|
+
errors.push({
|
|
111
|
+
type: WU_LINT_ERROR_TYPES.CODE_PATH_CONFLICTS_INVARIANT,
|
|
112
|
+
wuId: id,
|
|
113
|
+
invariantId: invariant.id,
|
|
114
|
+
path: invariant.path,
|
|
115
|
+
message: `code_paths includes '${invariant.path}' which conflicts with invariant ${invariant.id}: ${invariant.description}`,
|
|
116
|
+
suggestion: invariant.message || `Remove '${invariant.path}' from code_paths`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Check if acceptance mentions creating the forbidden file
|
|
120
|
+
for (const criterion of acceptance) {
|
|
121
|
+
if (criterion.includes(invariant.path)) {
|
|
122
|
+
errors.push({
|
|
123
|
+
type: WU_LINT_ERROR_TYPES.ACCEPTANCE_CONFLICTS_INVARIANT,
|
|
124
|
+
wuId: id,
|
|
125
|
+
invariantId: invariant.id,
|
|
126
|
+
path: invariant.path,
|
|
127
|
+
criterion,
|
|
128
|
+
message: `Acceptance criterion references forbidden file '${invariant.path}' (${invariant.id}: ${invariant.description})`,
|
|
129
|
+
suggestion: invariant.message,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return errors;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check mutual-exclusivity invariant for conflicts
|
|
137
|
+
*
|
|
138
|
+
* @param {object} invariant - Invariant definition
|
|
139
|
+
* @param {object} wu - WU spec object
|
|
140
|
+
* @returns {Array<object>} Array of errors (empty if no conflicts)
|
|
141
|
+
*/
|
|
142
|
+
function checkMutualExclusivityInvariant(invariant, wu) {
|
|
143
|
+
const { id, code_paths = [] } = wu;
|
|
144
|
+
const errors = [];
|
|
145
|
+
// Check if code_paths includes multiple files from the mutual-exclusivity set
|
|
146
|
+
const conflictingPaths = invariant.paths.filter((p) => code_paths.includes(p));
|
|
147
|
+
if (conflictingPaths.length > 1) {
|
|
148
|
+
errors.push({
|
|
149
|
+
type: WU_LINT_ERROR_TYPES.CODE_PATH_CONFLICTS_INVARIANT,
|
|
150
|
+
wuId: id,
|
|
151
|
+
invariantId: invariant.id,
|
|
152
|
+
paths: conflictingPaths,
|
|
153
|
+
message: `code_paths includes multiple mutually exclusive files (${invariant.id}): ${conflictingPaths.join(', ')}`,
|
|
154
|
+
suggestion: invariant.message || `Only one of these files should exist: ${invariant.paths.join(', ')}`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return errors;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Validate that code_paths and acceptance do not conflict with invariants
|
|
161
|
+
*
|
|
162
|
+
* @param {object} wu - WU spec object
|
|
163
|
+
* @param {Array<object>} invariants - Array of invariant definitions
|
|
164
|
+
* @returns {{valid: boolean, errors: Array<object>}} Validation result
|
|
165
|
+
*/
|
|
166
|
+
export function validateInvariantsCompliance(wu, invariants) {
|
|
167
|
+
const errors = [];
|
|
168
|
+
for (const invariant of invariants) {
|
|
169
|
+
if (invariant.type === INVARIANT_TYPES.FORBIDDEN_FILE) {
|
|
170
|
+
errors.push(...checkForbiddenFileInvariant(invariant, wu));
|
|
171
|
+
}
|
|
172
|
+
else if (invariant.type === INVARIANT_TYPES.MUTUAL_EXCLUSIVITY) {
|
|
173
|
+
errors.push(...checkMutualExclusivityInvariant(invariant, wu));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
valid: errors.length === 0,
|
|
178
|
+
errors,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Lint a WU spec against all rules
|
|
183
|
+
*
|
|
184
|
+
* @param {object} wu - WU spec object
|
|
185
|
+
* @param {LintWUSpecOptions} [options={}] - Options
|
|
186
|
+
* @returns {{valid: boolean, errors: Array<object>}} Lint result
|
|
187
|
+
*/
|
|
188
|
+
export function lintWUSpec(wu, options = {}) {
|
|
189
|
+
const allErrors = [];
|
|
190
|
+
// 1. Validate acceptance/code_paths consistency
|
|
191
|
+
const acceptanceResult = validateAcceptanceCodePaths(wu);
|
|
192
|
+
allErrors.push(...acceptanceResult.errors);
|
|
193
|
+
// 2. Load invariants if not provided
|
|
194
|
+
let invariants = options.invariants || [];
|
|
195
|
+
if (!options.invariants && options.invariantsPath) {
|
|
196
|
+
try {
|
|
197
|
+
if (existsSync(options.invariantsPath)) {
|
|
198
|
+
invariants = loadInvariants(options.invariantsPath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// If invariants can't be loaded, continue without them
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// 3. Validate invariants compliance
|
|
206
|
+
if (invariants.length > 0) {
|
|
207
|
+
const invariantsResult = validateInvariantsCompliance(wu, invariants);
|
|
208
|
+
allErrors.push(...invariantsResult.errors);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
valid: allErrors.length === 0,
|
|
212
|
+
errors: allErrors,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Format lint errors for display
|
|
217
|
+
*
|
|
218
|
+
* @param {Array<object>} errors - Array of lint errors
|
|
219
|
+
* @returns {string} Formatted error message
|
|
220
|
+
*/
|
|
221
|
+
export function formatLintErrors(errors) {
|
|
222
|
+
if (errors.length === 0) {
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
const lines = ['WU SPEC LINT ERRORS:', ''];
|
|
226
|
+
for (const error of errors) {
|
|
227
|
+
lines.push(`- ${error.message}`);
|
|
228
|
+
if (error.suggestion) {
|
|
229
|
+
lines.push(` Fix: ${error.suggestion}`);
|
|
230
|
+
}
|
|
231
|
+
lines.push('');
|
|
232
|
+
}
|
|
233
|
+
return lines.join('\n');
|
|
234
|
+
}
|