@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,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse YAML frontmatter from backlog.md
|
|
3
|
+
* @param {string} backlogPath - Path to backlog.md file
|
|
4
|
+
* @returns {{frontmatter: object|null, markdown: string}} Parsed frontmatter and markdown body
|
|
5
|
+
* @throws {Error} If file not found or YAML parsing fails
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseBacklogFrontmatter(backlogPath: any): {
|
|
8
|
+
frontmatter: {
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
};
|
|
11
|
+
markdown: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Section configuration from frontmatter
|
|
15
|
+
*/
|
|
16
|
+
interface SectionConfig {
|
|
17
|
+
heading?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Backlog frontmatter structure
|
|
21
|
+
*/
|
|
22
|
+
interface BacklogFrontmatter {
|
|
23
|
+
sections?: Record<string, SectionConfig>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Section boundary definition
|
|
27
|
+
*/
|
|
28
|
+
interface SectionBoundary {
|
|
29
|
+
start: number;
|
|
30
|
+
end: number | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract section headings from frontmatter
|
|
34
|
+
* @param {object|null} frontmatter - Parsed frontmatter object
|
|
35
|
+
* @returns {object} Map of section names to heading strings (e.g., {ready: "## 🚀 Ready"})
|
|
36
|
+
*/
|
|
37
|
+
export declare function getSectionHeadings(frontmatter: BacklogFrontmatter | null): Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* Find section boundaries in backlog content
|
|
40
|
+
* @param {string[]} lines - Lines of backlog content
|
|
41
|
+
* @param {object|null} frontmatter - Parsed frontmatter object
|
|
42
|
+
* @returns {object} Map of section names to {start, end} boundary indices
|
|
43
|
+
*/
|
|
44
|
+
export declare function findSectionBoundaries(lines: string[], frontmatter: BacklogFrontmatter | null): Record<string, SectionBoundary | null>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backlog Parser (WU-1065, WU-1211)
|
|
3
|
+
* Shared module for parsing YAML frontmatter in backlog.md
|
|
4
|
+
* Uses gray-matter library for robust frontmatter parsing
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import matter from 'gray-matter';
|
|
8
|
+
import yaml from 'yaml';
|
|
9
|
+
import { createError, ErrorCodes } from './error-handler.js';
|
|
10
|
+
/**
|
|
11
|
+
* Parse YAML frontmatter from backlog.md
|
|
12
|
+
* @param {string} backlogPath - Path to backlog.md file
|
|
13
|
+
* @returns {{frontmatter: object|null, markdown: string}} Parsed frontmatter and markdown body
|
|
14
|
+
* @throws {Error} If file not found or YAML parsing fails
|
|
15
|
+
*/
|
|
16
|
+
export function parseBacklogFrontmatter(backlogPath) {
|
|
17
|
+
if (!existsSync(backlogPath)) {
|
|
18
|
+
throw createError(ErrorCodes.FILE_NOT_FOUND, `Backlog not found: ${backlogPath}`, {
|
|
19
|
+
path: backlogPath,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const content = readFileSync(backlogPath, { encoding: 'utf-8' });
|
|
23
|
+
try {
|
|
24
|
+
// Configure gray-matter to use modern yaml library instead of deprecated js-yaml
|
|
25
|
+
const { data, content: markdown } = matter(content, {
|
|
26
|
+
engines: {
|
|
27
|
+
yaml: {
|
|
28
|
+
parse: yaml.parse.bind(yaml),
|
|
29
|
+
stringify: yaml.stringify.bind(yaml),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
// gray-matter returns {} for no frontmatter; normalize to null for consistency
|
|
34
|
+
const frontmatter = Object.keys(data).length > 0 ? data : null;
|
|
35
|
+
return { frontmatter, markdown };
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
throw createError(ErrorCodes.YAML_PARSE_ERROR, `Failed to parse frontmatter in ${backlogPath}:\n\n${err.message}\n\n` +
|
|
39
|
+
`Ensure frontmatter is valid YAML between --- delimiters.`, { path: backlogPath, originalError: err.message });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extract section headings from frontmatter
|
|
44
|
+
* @param {object|null} frontmatter - Parsed frontmatter object
|
|
45
|
+
* @returns {object} Map of section names to heading strings (e.g., {ready: "## 🚀 Ready"})
|
|
46
|
+
*/
|
|
47
|
+
export function getSectionHeadings(frontmatter) {
|
|
48
|
+
if (!frontmatter || !frontmatter.sections) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
const headings = {};
|
|
52
|
+
for (const [sectionName, sectionConfig] of Object.entries(frontmatter.sections)) {
|
|
53
|
+
if (sectionConfig.heading) {
|
|
54
|
+
headings[sectionName] = sectionConfig.heading;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return headings;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Find section boundaries in backlog content
|
|
61
|
+
* @param {string[]} lines - Lines of backlog content
|
|
62
|
+
* @param {object|null} frontmatter - Parsed frontmatter object
|
|
63
|
+
* @returns {object} Map of section names to {start, end} boundary indices
|
|
64
|
+
*/
|
|
65
|
+
export function findSectionBoundaries(lines, frontmatter) {
|
|
66
|
+
if (!frontmatter || !frontmatter.sections) {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
const headings = getSectionHeadings(frontmatter);
|
|
70
|
+
const boundaries = {};
|
|
71
|
+
// Initialize all sections as null (not found)
|
|
72
|
+
for (const sectionName of Object.keys(headings)) {
|
|
73
|
+
boundaries[sectionName] = null;
|
|
74
|
+
}
|
|
75
|
+
// Find start indices for each section (exact match)
|
|
76
|
+
for (let i = 0; i < lines.length; i++) {
|
|
77
|
+
const line = lines[i];
|
|
78
|
+
for (const [sectionName, heading] of Object.entries(headings)) {
|
|
79
|
+
if (line === heading) {
|
|
80
|
+
boundaries[sectionName] = { start: i, end: null };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Calculate end indices (last line before next section or EOF)
|
|
85
|
+
const sectionStarts = Object.values(boundaries)
|
|
86
|
+
.filter((b) => b !== null)
|
|
87
|
+
.map((b) => b.start)
|
|
88
|
+
.sort((a, b) => a - b);
|
|
89
|
+
for (const boundary of Object.values(boundaries)) {
|
|
90
|
+
if (boundary === null)
|
|
91
|
+
continue;
|
|
92
|
+
const currentStart = boundary.start;
|
|
93
|
+
const nextStart = sectionStarts.find((s) => s > currentStart);
|
|
94
|
+
if (nextStart !== undefined) {
|
|
95
|
+
boundary.end = nextStart - 1;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
boundary.end = lines.length - 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return boundaries;
|
|
102
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export declare function validateBacklogSync(backlogPath: any): {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
errors: any[];
|
|
4
|
+
stats?: undefined;
|
|
5
|
+
} | {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
errors: any[];
|
|
8
|
+
stats: {
|
|
9
|
+
ready: number;
|
|
10
|
+
inProgress: number;
|
|
11
|
+
blocked: number;
|
|
12
|
+
done: number;
|
|
13
|
+
duplicates: number;
|
|
14
|
+
parentOnlyInReady: number;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Options for fixing backlog duplicates
|
|
19
|
+
*/
|
|
20
|
+
export interface FixBacklogDuplicatesOptions {
|
|
21
|
+
/** If true, report changes without writing */
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
/** If true, return fixed content without writing (WU-1506) */
|
|
24
|
+
returnContent?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fix backlog duplicates by removing WUs from non-authoritative sections
|
|
28
|
+
* - If WU in Done AND Ready: remove from Ready (Done is authoritative)
|
|
29
|
+
* - If WU in Done AND InProgress: remove from InProgress (Done is authoritative)
|
|
30
|
+
*
|
|
31
|
+
* Part of WU-1303: Backlog duplicate entries after rebase conflicts
|
|
32
|
+
* WU-1506: Added returnContent option for atomic in-memory validation
|
|
33
|
+
*
|
|
34
|
+
* @param {string} backlogPath - Path to backlog.md file
|
|
35
|
+
* @param {FixBacklogDuplicatesOptions} options - Fix options
|
|
36
|
+
* @returns {{fixed: boolean, removed: Array<{wu: string, section: string}>, backupPath?: string, content?: string}}
|
|
37
|
+
*/
|
|
38
|
+
export declare function fixBacklogDuplicates(backlogPath: any, options?: FixBacklogDuplicatesOptions): {
|
|
39
|
+
fixed: boolean;
|
|
40
|
+
removed: any[];
|
|
41
|
+
error: any;
|
|
42
|
+
message?: undefined;
|
|
43
|
+
content?: undefined;
|
|
44
|
+
dryRun?: undefined;
|
|
45
|
+
backupPath?: undefined;
|
|
46
|
+
} | {
|
|
47
|
+
fixed: boolean;
|
|
48
|
+
removed: any[];
|
|
49
|
+
message: string;
|
|
50
|
+
error?: undefined;
|
|
51
|
+
content?: undefined;
|
|
52
|
+
dryRun?: undefined;
|
|
53
|
+
backupPath?: undefined;
|
|
54
|
+
} | {
|
|
55
|
+
fixed: boolean;
|
|
56
|
+
removed: any[];
|
|
57
|
+
content: string;
|
|
58
|
+
message: string;
|
|
59
|
+
error?: undefined;
|
|
60
|
+
dryRun?: undefined;
|
|
61
|
+
backupPath?: undefined;
|
|
62
|
+
} | {
|
|
63
|
+
fixed: boolean;
|
|
64
|
+
removed: any[];
|
|
65
|
+
dryRun: boolean;
|
|
66
|
+
message: string;
|
|
67
|
+
error?: undefined;
|
|
68
|
+
content?: undefined;
|
|
69
|
+
backupPath?: undefined;
|
|
70
|
+
} | {
|
|
71
|
+
fixed: boolean;
|
|
72
|
+
removed: any[];
|
|
73
|
+
backupPath: string;
|
|
74
|
+
message: string;
|
|
75
|
+
error?: undefined;
|
|
76
|
+
content?: undefined;
|
|
77
|
+
dryRun?: undefined;
|
|
78
|
+
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backlog Sync Validator (WU-672, WU-1065, WU-1137, WU-1303)
|
|
3
|
+
* Detects WUs present in multiple sections (Done+Ready, Done+InProgress, etc.)
|
|
4
|
+
* Uses frontmatter-driven section detection (WU-1065) instead of brittle string matching
|
|
5
|
+
* Flags parent-only WUs in Ready section (WU-1137) - sub-lane format is preferred
|
|
6
|
+
* Supports --fix mode to automatically remove duplicates (WU-1303)
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import yaml from 'js-yaml';
|
|
11
|
+
import { parseBacklogFrontmatter, getSectionHeadings } from './backlog-parser.js';
|
|
12
|
+
import { extractParent } from './lane-checker.js';
|
|
13
|
+
import { CONFIG_FILES, STRING_LITERALS, getProjectRoot, } from './wu-constants.js';
|
|
14
|
+
/**
|
|
15
|
+
* Check if parent lane has sub-lane taxonomy in .lumenflow.lane-inference.yaml
|
|
16
|
+
* @param {string} parent - Parent lane name
|
|
17
|
+
* @param {string} projectRoot - Path to project root
|
|
18
|
+
* @returns {boolean} True if parent has sub-lanes defined
|
|
19
|
+
*/
|
|
20
|
+
function hasSubLaneTaxonomy(parent, projectRoot) {
|
|
21
|
+
const taxonomyPath = path.join(projectRoot, CONFIG_FILES.LANE_INFERENCE);
|
|
22
|
+
if (!existsSync(taxonomyPath)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const taxonomyContent = readFileSync(taxonomyPath, { encoding: 'utf-8' });
|
|
27
|
+
const taxonomy = yaml.load(taxonomyContent);
|
|
28
|
+
const normalizedParent = parent.trim().toLowerCase();
|
|
29
|
+
return Object.keys(taxonomy).some((key) => key.toLowerCase().trim() === normalizedParent);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// If taxonomy file is malformed, assume no taxonomy (fail safe)
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function validateBacklogSync(backlogPath) {
|
|
37
|
+
// Parse frontmatter to get configured section headings
|
|
38
|
+
let frontmatter, markdown;
|
|
39
|
+
try {
|
|
40
|
+
({ frontmatter, markdown } = parseBacklogFrontmatter(backlogPath));
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return { valid: false, errors: [err.message] };
|
|
44
|
+
}
|
|
45
|
+
// If no frontmatter, fall back to empty sections (backlog without frontmatter is valid)
|
|
46
|
+
const headings = frontmatter ? getSectionHeadings(frontmatter) : {};
|
|
47
|
+
const lines = markdown.split(/\r?\n/);
|
|
48
|
+
// Parse sections using frontmatter headings
|
|
49
|
+
const sections = {
|
|
50
|
+
ready: new Set(),
|
|
51
|
+
in_progress: new Set(),
|
|
52
|
+
blocked: new Set(),
|
|
53
|
+
done: new Set(),
|
|
54
|
+
};
|
|
55
|
+
let currentSection = null;
|
|
56
|
+
// Build heading-to-section map for efficient lookup
|
|
57
|
+
const headingMap = new Map();
|
|
58
|
+
for (const [sectionName, heading] of Object.entries(headings)) {
|
|
59
|
+
headingMap.set(heading, sectionName);
|
|
60
|
+
}
|
|
61
|
+
// WU-1334: Pattern to match WU IDs only in backlog list items
|
|
62
|
+
// Matches lines starting with:
|
|
63
|
+
// - `- WU-123` (bullet list)
|
|
64
|
+
// - `* WU-123` (asterisk list)
|
|
65
|
+
// - `- [ ] WU-123` (unchecked checkbox)
|
|
66
|
+
// - `- [x] WU-123` (checked checkbox)
|
|
67
|
+
// - `- [WU-123 - title](...)` (markdown link)
|
|
68
|
+
// Does NOT match prose like "See WU-123 for details" or "WU-123 → WU-124"
|
|
69
|
+
const BACKLOG_ITEM_PATTERN = /^\s*[-*]\s*(?:\[[ x]\]\s*)?\[?(WU-\d+)/i;
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
// Check if line matches any configured section heading (exact match)
|
|
72
|
+
if (headingMap.has(line)) {
|
|
73
|
+
currentSection = headingMap.get(line);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Reset section when encountering other ## headings (not subsections ###)
|
|
77
|
+
if (line.trim().startsWith('## ') && !line.trim().startsWith('### ')) {
|
|
78
|
+
currentSection = null;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (currentSection) {
|
|
82
|
+
const match = line.match(BACKLOG_ITEM_PATTERN);
|
|
83
|
+
if (match) {
|
|
84
|
+
sections[currentSection].add(match[1].toUpperCase());
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Detect duplicates
|
|
89
|
+
const errors = [];
|
|
90
|
+
// Done + Ready
|
|
91
|
+
const doneAndReady = [...sections.done].filter((wu) => sections.ready.has(wu));
|
|
92
|
+
if (doneAndReady.length > 0) {
|
|
93
|
+
errors.push(`❌ ${doneAndReady.length} WU(s) are in BOTH Done and Ready sections:${STRING_LITERALS.NEWLINE}${doneAndReady
|
|
94
|
+
.map((wu) => ` - ${wu}`)
|
|
95
|
+
.join(STRING_LITERALS.NEWLINE)}${STRING_LITERALS.DOUBLE_NEWLINE}` +
|
|
96
|
+
` Fix: Remove from Ready section (they are already complete)${STRING_LITERALS.NEWLINE}` +
|
|
97
|
+
` Command: Edit docs/04-operations/tasks/backlog.md and remove duplicate entries`);
|
|
98
|
+
}
|
|
99
|
+
// Done + In Progress (should never happen, but check anyway)
|
|
100
|
+
const doneAndInProgress = [...sections.done].filter((wu) => sections.in_progress.has(wu));
|
|
101
|
+
if (doneAndInProgress.length > 0) {
|
|
102
|
+
errors.push(`❌ ${doneAndInProgress.length} WU(s) are in BOTH Done and In Progress sections:${STRING_LITERALS.NEWLINE}${doneAndInProgress
|
|
103
|
+
.map((wu) => ` - ${wu}`)
|
|
104
|
+
.join(STRING_LITERALS.NEWLINE)}${STRING_LITERALS.DOUBLE_NEWLINE}` +
|
|
105
|
+
` Fix: Remove from In Progress section (they are already complete)${STRING_LITERALS.NEWLINE}` +
|
|
106
|
+
` Or: If reopened, remove from Done and update WU YAML status to in_progress`);
|
|
107
|
+
}
|
|
108
|
+
// Ready + In Progress (legitimate during claim, but flag for awareness)
|
|
109
|
+
const readyAndInProgress = [...sections.ready].filter((wu) => sections.in_progress.has(wu));
|
|
110
|
+
if (readyAndInProgress.length > 0) {
|
|
111
|
+
errors.push(`⚠️ ${readyAndInProgress.length} WU(s) are in BOTH Ready and In Progress sections:${STRING_LITERALS.NEWLINE}${readyAndInProgress
|
|
112
|
+
.map((wu) => ` - ${wu}`)
|
|
113
|
+
.join(STRING_LITERALS.NEWLINE)}${STRING_LITERALS.DOUBLE_NEWLINE}` +
|
|
114
|
+
` This is normal during wu:claim before commit.${STRING_LITERALS.NEWLINE}` +
|
|
115
|
+
` If you see this error after commit, wu:claim did not remove from Ready.${STRING_LITERALS.NEWLINE}` +
|
|
116
|
+
` Fix: Remove from Ready section`);
|
|
117
|
+
}
|
|
118
|
+
// WU-1137: Check for parent-only WUs in Ready section (sub-lane format preferred)
|
|
119
|
+
const projectRoot = getProjectRoot(import.meta.url);
|
|
120
|
+
const wuDir = path.join(path.dirname(backlogPath), 'wu');
|
|
121
|
+
const parentOnlyWUs = [];
|
|
122
|
+
for (const wuId of sections.ready) {
|
|
123
|
+
const wuPath = path.join(wuDir, `${wuId}.yaml`);
|
|
124
|
+
if (!existsSync(wuPath)) {
|
|
125
|
+
continue; // Skip missing WU files (handled by other validators)
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const wuContent = readFileSync(wuPath, { encoding: 'utf-8' });
|
|
129
|
+
const wuDoc = yaml.load(wuContent);
|
|
130
|
+
if (wuDoc && wuDoc.lane) {
|
|
131
|
+
const lane = wuDoc.lane.toString().trim();
|
|
132
|
+
const hasColon = lane.includes(':');
|
|
133
|
+
// If parent-only format (no colon) and parent has sub-lane taxonomy, flag it
|
|
134
|
+
if (!hasColon) {
|
|
135
|
+
const parent = extractParent(lane);
|
|
136
|
+
if (hasSubLaneTaxonomy(parent, projectRoot)) {
|
|
137
|
+
parentOnlyWUs.push({ wuId, lane, parent });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Skip WU files that can't be parsed (handled by other validators)
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (parentOnlyWUs.length > 0) {
|
|
148
|
+
errors.push(`⚠️ ${parentOnlyWUs.length} WU(s) in Ready section use parent-only lane format (sub-lane format preferred):${STRING_LITERALS.NEWLINE}${parentOnlyWUs
|
|
149
|
+
.map((wu) => ` - ${wu.wuId}: "${wu.lane}" (parent has sub-lanes)`)
|
|
150
|
+
.join(STRING_LITERALS.NEWLINE)}${STRING_LITERALS.DOUBLE_NEWLINE}` +
|
|
151
|
+
` Fix: Migrate to sub-lane format using:${STRING_LITERALS.NEWLINE}` +
|
|
152
|
+
` pnpm wu:infer-lane --id WU-123 # Suggest a sub-lane${STRING_LITERALS.NEWLINE}` +
|
|
153
|
+
` pnpm wu:edit --id WU-123 --lane "Parent: Sub"${STRING_LITERALS.NEWLINE}` +
|
|
154
|
+
` See: docs/04-operations/_frameworks/lumenflow/sub-lanes.md`);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
valid: errors.length === 0,
|
|
158
|
+
errors,
|
|
159
|
+
stats: {
|
|
160
|
+
ready: sections.ready.size,
|
|
161
|
+
inProgress: sections.in_progress.size,
|
|
162
|
+
blocked: sections.blocked.size,
|
|
163
|
+
done: sections.done.size,
|
|
164
|
+
duplicates: doneAndReady.length + doneAndInProgress.length + readyAndInProgress.length,
|
|
165
|
+
parentOnlyInReady: parentOnlyWUs.length,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Fix backlog duplicates by removing WUs from non-authoritative sections
|
|
171
|
+
* - If WU in Done AND Ready: remove from Ready (Done is authoritative)
|
|
172
|
+
* - If WU in Done AND InProgress: remove from InProgress (Done is authoritative)
|
|
173
|
+
*
|
|
174
|
+
* Part of WU-1303: Backlog duplicate entries after rebase conflicts
|
|
175
|
+
* WU-1506: Added returnContent option for atomic in-memory validation
|
|
176
|
+
*
|
|
177
|
+
* @param {string} backlogPath - Path to backlog.md file
|
|
178
|
+
* @param {FixBacklogDuplicatesOptions} options - Fix options
|
|
179
|
+
* @returns {{fixed: boolean, removed: Array<{wu: string, section: string}>, backupPath?: string, content?: string}}
|
|
180
|
+
*/
|
|
181
|
+
export function fixBacklogDuplicates(backlogPath, options = {}) {
|
|
182
|
+
const { dryRun = false, returnContent = false } = options;
|
|
183
|
+
// Parse frontmatter to get configured section headings
|
|
184
|
+
let frontmatter, markdown;
|
|
185
|
+
try {
|
|
186
|
+
({ frontmatter, markdown } = parseBacklogFrontmatter(backlogPath));
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
return { fixed: false, removed: [], error: err.message };
|
|
190
|
+
}
|
|
191
|
+
const headings = frontmatter ? getSectionHeadings(frontmatter) : {};
|
|
192
|
+
const lines = markdown.split(/\r?\n/);
|
|
193
|
+
// Track section boundaries and WU locations
|
|
194
|
+
const sections = {
|
|
195
|
+
ready: { wus: new Set(), lineNumbers: new Map() },
|
|
196
|
+
in_progress: { wus: new Set(), lineNumbers: new Map() },
|
|
197
|
+
blocked: { wus: new Set(), lineNumbers: new Map() },
|
|
198
|
+
done: { wus: new Set(), lineNumbers: new Map() },
|
|
199
|
+
};
|
|
200
|
+
let currentSection = null;
|
|
201
|
+
// Build heading-to-section map
|
|
202
|
+
const headingMap = new Map();
|
|
203
|
+
for (const [sectionName, heading] of Object.entries(headings)) {
|
|
204
|
+
headingMap.set(heading, sectionName);
|
|
205
|
+
}
|
|
206
|
+
// WU-1334: Same pattern as validateBacklogSync - only match list items
|
|
207
|
+
const BACKLOG_ITEM_PATTERN = /^\s*[-*]\s*(?:\[[ x]\]\s*)?\[?(WU-\d+)/i;
|
|
208
|
+
// Parse sections and track line numbers for each WU
|
|
209
|
+
for (let i = 0; i < lines.length; i++) {
|
|
210
|
+
const line = lines[i];
|
|
211
|
+
if (headingMap.has(line)) {
|
|
212
|
+
currentSection = headingMap.get(line);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
// Reset section on other ## headings
|
|
216
|
+
if (line.trim().startsWith('## ') && !line.trim().startsWith('### ')) {
|
|
217
|
+
currentSection = null;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (currentSection && sections[currentSection]) {
|
|
221
|
+
const match = line.match(BACKLOG_ITEM_PATTERN);
|
|
222
|
+
if (match) {
|
|
223
|
+
const wuId = match[1].toUpperCase();
|
|
224
|
+
sections[currentSection].wus.add(wuId);
|
|
225
|
+
// Store line number (can have multiple lines per WU, but we just need one to identify the entry)
|
|
226
|
+
if (!sections[currentSection].lineNumbers.has(wuId)) {
|
|
227
|
+
sections[currentSection].lineNumbers.set(wuId, i);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Find duplicates that need removal
|
|
233
|
+
const linesToRemove = new Set();
|
|
234
|
+
const removed = [];
|
|
235
|
+
// Done + Ready: remove from Ready
|
|
236
|
+
for (const wu of sections.done.wus) {
|
|
237
|
+
if (sections.ready.wus.has(wu)) {
|
|
238
|
+
const lineNum = sections.ready.lineNumbers.get(wu);
|
|
239
|
+
if (lineNum !== undefined) {
|
|
240
|
+
linesToRemove.add(lineNum);
|
|
241
|
+
removed.push({ wu, section: 'ready' });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Done + InProgress: remove from InProgress
|
|
246
|
+
for (const wu of sections.done.wus) {
|
|
247
|
+
if (sections.in_progress.wus.has(wu)) {
|
|
248
|
+
const lineNum = sections.in_progress.lineNumbers.get(wu);
|
|
249
|
+
if (lineNum !== undefined) {
|
|
250
|
+
linesToRemove.add(lineNum);
|
|
251
|
+
removed.push({ wu, section: 'in_progress' });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// No duplicates to fix
|
|
256
|
+
if (removed.length === 0) {
|
|
257
|
+
return { fixed: false, removed: [], message: 'No duplicates found' };
|
|
258
|
+
}
|
|
259
|
+
// Remove duplicate lines (filter out the lines to remove)
|
|
260
|
+
const newLines = lines.filter((_, index) => !linesToRemove.has(index));
|
|
261
|
+
// Reconstruct file with frontmatter
|
|
262
|
+
const originalContent = readFileSync(backlogPath, { encoding: 'utf-8' });
|
|
263
|
+
const frontmatterMatch = originalContent.match(/^---\n[\s\S]*?\n---\n/);
|
|
264
|
+
const frontmatterContent = frontmatterMatch ? frontmatterMatch[0] : STRING_LITERALS.EMPTY;
|
|
265
|
+
const newContent = frontmatterContent + newLines.join(STRING_LITERALS.NEWLINE);
|
|
266
|
+
// WU-1506: Return content without writing when returnContent is true
|
|
267
|
+
if (returnContent) {
|
|
268
|
+
return {
|
|
269
|
+
fixed: true,
|
|
270
|
+
removed,
|
|
271
|
+
content: newContent,
|
|
272
|
+
message: `Would remove ${removed.length} duplicate(s)`,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
// Dry run - report what would change
|
|
276
|
+
if (dryRun) {
|
|
277
|
+
return {
|
|
278
|
+
fixed: false,
|
|
279
|
+
removed,
|
|
280
|
+
dryRun: true,
|
|
281
|
+
message: `Would remove ${removed.length} duplicate(s)`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
// Create backup before modifying
|
|
285
|
+
const backupPath = `${backlogPath}.bak`;
|
|
286
|
+
copyFileSync(backlogPath, backupPath);
|
|
287
|
+
writeFileSync(backlogPath, newContent, { encoding: 'utf-8' });
|
|
288
|
+
return {
|
|
289
|
+
fixed: true,
|
|
290
|
+
removed,
|
|
291
|
+
backupPath,
|
|
292
|
+
message: `Removed ${removed.length} duplicate(s). Backup at ${backupPath}`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch drift detection and graduated warning system
|
|
3
|
+
*
|
|
4
|
+
* WU-1370: Graduated warnings for branch drift
|
|
5
|
+
* - < 10 commits behind: OK (no message)
|
|
6
|
+
* - 10-14 commits behind: INFO ("Consider rebasing")
|
|
7
|
+
* - 15-19 commits behind: WARNING ("Rebase recommended")
|
|
8
|
+
* - >= 20 commits behind: ERROR (hard block, "Must rebase")
|
|
9
|
+
*
|
|
10
|
+
* @see {@link THRESHOLDS} for configurable threshold values
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Drift level constants
|
|
14
|
+
* Used as return values from getDriftLevel()
|
|
15
|
+
*/
|
|
16
|
+
export declare const DRIFT_LEVELS: {
|
|
17
|
+
OK: string;
|
|
18
|
+
INFO: string;
|
|
19
|
+
WARNING: string;
|
|
20
|
+
ERROR: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Calculate drift level based on number of commits behind main
|
|
24
|
+
*
|
|
25
|
+
* @param {number} commitsBehind - Number of commits behind main (from git rev-list --count)
|
|
26
|
+
* @returns {'ok' | 'info' | 'warning' | 'error'} Drift severity level
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* getDriftLevel(5) // 'ok' - no warning needed
|
|
30
|
+
* getDriftLevel(10) // 'info' - suggest rebasing
|
|
31
|
+
* getDriftLevel(15) // 'warning' - recommend rebasing
|
|
32
|
+
* getDriftLevel(20) // 'error' - hard block, must rebase
|
|
33
|
+
*/
|
|
34
|
+
export declare function getDriftLevel(commitsBehind: any): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch drift detection and graduated warning system
|
|
3
|
+
*
|
|
4
|
+
* WU-1370: Graduated warnings for branch drift
|
|
5
|
+
* - < 10 commits behind: OK (no message)
|
|
6
|
+
* - 10-14 commits behind: INFO ("Consider rebasing")
|
|
7
|
+
* - 15-19 commits behind: WARNING ("Rebase recommended")
|
|
8
|
+
* - >= 20 commits behind: ERROR (hard block, "Must rebase")
|
|
9
|
+
*
|
|
10
|
+
* @see {@link THRESHOLDS} for configurable threshold values
|
|
11
|
+
*/
|
|
12
|
+
import { THRESHOLDS } from './wu-constants.js';
|
|
13
|
+
/**
|
|
14
|
+
* Drift level constants
|
|
15
|
+
* Used as return values from getDriftLevel()
|
|
16
|
+
*/
|
|
17
|
+
export const DRIFT_LEVELS = {
|
|
18
|
+
OK: 'ok',
|
|
19
|
+
INFO: 'info',
|
|
20
|
+
WARNING: 'warning',
|
|
21
|
+
ERROR: 'error',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Calculate drift level based on number of commits behind main
|
|
25
|
+
*
|
|
26
|
+
* @param {number} commitsBehind - Number of commits behind main (from git rev-list --count)
|
|
27
|
+
* @returns {'ok' | 'info' | 'warning' | 'error'} Drift severity level
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* getDriftLevel(5) // 'ok' - no warning needed
|
|
31
|
+
* getDriftLevel(10) // 'info' - suggest rebasing
|
|
32
|
+
* getDriftLevel(15) // 'warning' - recommend rebasing
|
|
33
|
+
* getDriftLevel(20) // 'error' - hard block, must rebase
|
|
34
|
+
*/
|
|
35
|
+
export function getDriftLevel(commitsBehind) {
|
|
36
|
+
// Handle edge cases: negative or non-integer values
|
|
37
|
+
const commits = Math.floor(commitsBehind);
|
|
38
|
+
if (commits < 0) {
|
|
39
|
+
return DRIFT_LEVELS.OK;
|
|
40
|
+
}
|
|
41
|
+
if (commits >= THRESHOLDS.BRANCH_DRIFT_MAX) {
|
|
42
|
+
return DRIFT_LEVELS.ERROR;
|
|
43
|
+
}
|
|
44
|
+
if (commits >= THRESHOLDS.BRANCH_DRIFT_WARNING) {
|
|
45
|
+
return DRIFT_LEVELS.WARNING;
|
|
46
|
+
}
|
|
47
|
+
if (commits >= THRESHOLDS.BRANCH_DRIFT_INFO) {
|
|
48
|
+
return DRIFT_LEVELS.INFO;
|
|
49
|
+
}
|
|
50
|
+
return DRIFT_LEVELS.OK;
|
|
51
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU-2278: Cleanup Install Configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides configuration for pnpm install during wu:done cleanup.
|
|
5
|
+
* - 60 second timeout to prevent indefinite hangs
|
|
6
|
+
* - CI=true for non-interactive mode
|
|
7
|
+
* - frozen-lockfile to prevent lockfile mutations
|
|
8
|
+
*
|
|
9
|
+
* @module cleanup-install-config
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Timeout for cleanup install operation (ms)
|
|
13
|
+
* @constant {number}
|
|
14
|
+
*/
|
|
15
|
+
export declare const CLEANUP_INSTALL_TIMEOUT_MS = 60000;
|
|
16
|
+
/**
|
|
17
|
+
* Get configuration for cleanup pnpm install
|
|
18
|
+
*
|
|
19
|
+
* Returns command and options suitable for execAsync:
|
|
20
|
+
* - CI=true environment variable for non-interactive mode
|
|
21
|
+
* - 60 second timeout to prevent hangs
|
|
22
|
+
* - frozen-lockfile flag to prevent mutations
|
|
23
|
+
*
|
|
24
|
+
* @returns {{ command: string, timeout: number, env: object }}
|
|
25
|
+
*/
|
|
26
|
+
export declare function getCleanupInstallConfig(): {
|
|
27
|
+
command: string;
|
|
28
|
+
timeout: number;
|
|
29
|
+
env: {
|
|
30
|
+
CI: string;
|
|
31
|
+
TZ?: string | undefined;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU-2278: Cleanup Install Configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides configuration for pnpm install during wu:done cleanup.
|
|
5
|
+
* - 60 second timeout to prevent indefinite hangs
|
|
6
|
+
* - CI=true for non-interactive mode
|
|
7
|
+
* - frozen-lockfile to prevent lockfile mutations
|
|
8
|
+
*
|
|
9
|
+
* @module cleanup-install-config
|
|
10
|
+
*/
|
|
11
|
+
import { PKG_MANAGER, PKG_COMMANDS, PKG_FLAGS } from './wu-constants.js';
|
|
12
|
+
/**
|
|
13
|
+
* Timeout for cleanup install operation (ms)
|
|
14
|
+
* @constant {number}
|
|
15
|
+
*/
|
|
16
|
+
export const CLEANUP_INSTALL_TIMEOUT_MS = 60000; // 60 seconds
|
|
17
|
+
/**
|
|
18
|
+
* Get configuration for cleanup pnpm install
|
|
19
|
+
*
|
|
20
|
+
* Returns command and options suitable for execAsync:
|
|
21
|
+
* - CI=true environment variable for non-interactive mode
|
|
22
|
+
* - 60 second timeout to prevent hangs
|
|
23
|
+
* - frozen-lockfile flag to prevent mutations
|
|
24
|
+
*
|
|
25
|
+
* @returns {{ command: string, timeout: number, env: object }}
|
|
26
|
+
*/
|
|
27
|
+
export function getCleanupInstallConfig() {
|
|
28
|
+
const command = `${PKG_MANAGER} ${PKG_COMMANDS.INSTALL} ${PKG_FLAGS.FROZEN_LOCKFILE}`;
|
|
29
|
+
return {
|
|
30
|
+
command,
|
|
31
|
+
timeout: CLEANUP_INSTALL_TIMEOUT_MS,
|
|
32
|
+
env: {
|
|
33
|
+
...process.env,
|
|
34
|
+
CI: 'true', // Non-interactive mode
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|