@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,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHI (Protected Health Information) Scanner
|
|
3
|
+
*
|
|
4
|
+
* Detects potential PHI in content using library-first approach:
|
|
5
|
+
* - NHS numbers validated with nhs-number-validator (Modulus 11 checksum)
|
|
6
|
+
* - UK postcodes parsed with postcode library, flagged only in medical context
|
|
7
|
+
*
|
|
8
|
+
* Part of WU-1404: PHI Scanner Integration
|
|
9
|
+
*
|
|
10
|
+
* @see {@link https://github.com/spikeheap/nhs-number-validator} NHS validation
|
|
11
|
+
* @see {@link https://github.com/ideal-postcodes/postcode} Postcode parsing
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Check if a file path should be excluded from PHI scanning
|
|
15
|
+
*
|
|
16
|
+
* @param {string|null|undefined} filePath - Path to check
|
|
17
|
+
* @returns {boolean} True if path should be excluded
|
|
18
|
+
*/
|
|
19
|
+
export declare function isPathExcluded(filePath: any): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Options for PHI scanning
|
|
22
|
+
*/
|
|
23
|
+
export interface ScanForPHIOptions {
|
|
24
|
+
/** File path for exclusion check */
|
|
25
|
+
filePath?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Scan content for PHI (Protected Health Information)
|
|
29
|
+
*
|
|
30
|
+
* Detects:
|
|
31
|
+
* - Valid NHS numbers (validated with Modulus 11 checksum)
|
|
32
|
+
* - UK postcodes in medical context
|
|
33
|
+
*
|
|
34
|
+
* @param {string|null|undefined} content - Content to scan
|
|
35
|
+
* @param {ScanForPHIOptions} [options] - Scan options
|
|
36
|
+
* @returns {{hasPHI: boolean, matches: Array, warnings: string[]}} Scan result
|
|
37
|
+
*/
|
|
38
|
+
interface PHIMatch {
|
|
39
|
+
type: string;
|
|
40
|
+
value: string;
|
|
41
|
+
startIndex: number;
|
|
42
|
+
endIndex: number;
|
|
43
|
+
medicalKeyword?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function scanForPHI(content: string, options?: ScanForPHIOptions): {
|
|
46
|
+
hasPHI: boolean;
|
|
47
|
+
matches: PHIMatch[];
|
|
48
|
+
warnings: string[];
|
|
49
|
+
filePath?: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Create a human-readable summary of PHI matches
|
|
53
|
+
*
|
|
54
|
+
* @param {Array} matches - PHI matches from scanForPHI
|
|
55
|
+
* @returns {string} Summary message
|
|
56
|
+
*/
|
|
57
|
+
export declare function formatPHISummary(matches: any): string;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHI (Protected Health Information) Scanner
|
|
3
|
+
*
|
|
4
|
+
* Detects potential PHI in content using library-first approach:
|
|
5
|
+
* - NHS numbers validated with nhs-number-validator (Modulus 11 checksum)
|
|
6
|
+
* - UK postcodes parsed with postcode library, flagged only in medical context
|
|
7
|
+
*
|
|
8
|
+
* Part of WU-1404: PHI Scanner Integration
|
|
9
|
+
*
|
|
10
|
+
* @see {@link https://github.com/spikeheap/nhs-number-validator} NHS validation
|
|
11
|
+
* @see {@link https://github.com/ideal-postcodes/postcode} Postcode parsing
|
|
12
|
+
*/
|
|
13
|
+
import nhsValidator from 'nhs-number-validator';
|
|
14
|
+
import { isValid as isValidPostcode, parse as parsePostcode } from 'postcode';
|
|
15
|
+
import { PHI_TYPES, MEDICAL_CONTEXT_KEYWORDS, MEDICAL_CONTEXT_WINDOW_SIZE, TEST_NHS_NUMBERS, NHS_TEST_PREFIX, TEST_POSTCODES, TEST_DATA_MARKERS, EXCLUDED_PATH_PATTERNS, NHS_CANDIDATE_PATTERN, } from './phi-constants.js';
|
|
16
|
+
/**
|
|
17
|
+
* Check if a file path should be excluded from PHI scanning
|
|
18
|
+
*
|
|
19
|
+
* @param {string|null|undefined} filePath - Path to check
|
|
20
|
+
* @returns {boolean} True if path should be excluded
|
|
21
|
+
*/
|
|
22
|
+
export function isPathExcluded(filePath) {
|
|
23
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return EXCLUDED_PATH_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if content contains test data markers
|
|
30
|
+
*
|
|
31
|
+
* @param {string} content - Content to check
|
|
32
|
+
* @returns {boolean} True if test markers are present
|
|
33
|
+
*/
|
|
34
|
+
function hasTestDataMarkers(content) {
|
|
35
|
+
const contentLower = content.toLowerCase();
|
|
36
|
+
return TEST_DATA_MARKERS.some((marker) => contentLower.includes(marker.toLowerCase()));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Normalize NHS number by removing spaces and dashes
|
|
40
|
+
*
|
|
41
|
+
* @param {string} nhsNumber - NHS number with possible formatting
|
|
42
|
+
* @returns {string} Normalized 10-digit NHS number
|
|
43
|
+
*/
|
|
44
|
+
function normalizeNhsNumber(nhsNumber) {
|
|
45
|
+
return nhsNumber.replace(/[\s-]/g, '');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if NHS number is a known test number
|
|
49
|
+
*
|
|
50
|
+
* @param {string} nhsNumber - Normalized NHS number
|
|
51
|
+
* @returns {boolean} True if it's a test number
|
|
52
|
+
*/
|
|
53
|
+
function isTestNhsNumber(nhsNumber) {
|
|
54
|
+
// Check against explicit test numbers
|
|
55
|
+
if (TEST_NHS_NUMBERS.includes(nhsNumber)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
// Check for 999 prefix (NHS Digital test range)
|
|
59
|
+
if (nhsNumber.startsWith(NHS_TEST_PREFIX)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Normalize postcode for comparison (uppercase, no spaces)
|
|
66
|
+
*
|
|
67
|
+
* @param {string} postcode - Postcode to normalize
|
|
68
|
+
* @returns {string} Normalized postcode
|
|
69
|
+
*/
|
|
70
|
+
function normalizePostcode(postcode) {
|
|
71
|
+
return postcode.toUpperCase().replace(/\s/g, '');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if postcode is a known test postcode
|
|
75
|
+
*
|
|
76
|
+
* @param {string} postcode - Postcode to check
|
|
77
|
+
* @returns {boolean} True if it's a test postcode
|
|
78
|
+
*/
|
|
79
|
+
function isTestPostcode(postcode) {
|
|
80
|
+
const normalized = normalizePostcode(postcode);
|
|
81
|
+
return TEST_POSTCODES.some((testPc) => normalizePostcode(testPc) === normalized);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if there's a medical context keyword within the context window
|
|
85
|
+
*
|
|
86
|
+
* @param {string} content - Full content
|
|
87
|
+
* @param {number} postcodeIndex - Index of postcode in content
|
|
88
|
+
* @param {number} postcodeLength - Length of the postcode string
|
|
89
|
+
* @returns {{found: boolean, keyword?: string}} Medical context result
|
|
90
|
+
*/
|
|
91
|
+
function findMedicalContext(content, postcodeIndex, postcodeLength) {
|
|
92
|
+
// Define the window around the postcode
|
|
93
|
+
const windowStart = Math.max(0, postcodeIndex - MEDICAL_CONTEXT_WINDOW_SIZE);
|
|
94
|
+
const windowEnd = Math.min(content.length, postcodeIndex + postcodeLength + MEDICAL_CONTEXT_WINDOW_SIZE);
|
|
95
|
+
const windowContent = content.slice(windowStart, windowEnd).toLowerCase();
|
|
96
|
+
for (const keyword of MEDICAL_CONTEXT_KEYWORDS) {
|
|
97
|
+
if (windowContent.includes(keyword.toLowerCase())) {
|
|
98
|
+
return { found: true, keyword };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { found: false };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Extract potential UK postcodes from content
|
|
105
|
+
*
|
|
106
|
+
* UK postcode format is complex - we use the postcode library for validation
|
|
107
|
+
* but need to extract candidates first. The library's isValid handles edge cases.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} content - Content to scan
|
|
110
|
+
* @returns {Array<{value: string, index: number}>} Postcode candidates with positions
|
|
111
|
+
*/
|
|
112
|
+
function extractPostcodeCandidates(content) {
|
|
113
|
+
const candidates = [];
|
|
114
|
+
// UK postcode pattern (simplified - library validates properly)
|
|
115
|
+
// Format: A(A)N(N) NAA or variations
|
|
116
|
+
const postcodePattern = /\b([A-Z]{1,2}\d[A-Z\d]?\s*\d[A-Z]{2})\b/gi;
|
|
117
|
+
let match;
|
|
118
|
+
while ((match = postcodePattern.exec(content)) !== null) {
|
|
119
|
+
const candidate = match[1];
|
|
120
|
+
// Validate with library
|
|
121
|
+
if (isValidPostcode(candidate)) {
|
|
122
|
+
const parsed = parsePostcode(candidate);
|
|
123
|
+
if (parsed.valid) {
|
|
124
|
+
candidates.push({
|
|
125
|
+
value: parsed.postcode, // Normalized postcode
|
|
126
|
+
index: match.index,
|
|
127
|
+
originalValue: match[1],
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return candidates;
|
|
133
|
+
}
|
|
134
|
+
export function scanForPHI(content, options = {}) {
|
|
135
|
+
const result = {
|
|
136
|
+
hasPHI: false,
|
|
137
|
+
matches: [],
|
|
138
|
+
warnings: [],
|
|
139
|
+
};
|
|
140
|
+
// Handle null/undefined/empty content
|
|
141
|
+
if (!content || typeof content !== 'string' || content.trim() === '') {
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
// Check path exclusions
|
|
145
|
+
if (options.filePath && isPathExcluded(options.filePath)) {
|
|
146
|
+
result.warnings.push(`Path excluded from PHI scanning: ${options.filePath}`);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
// Check for test data markers
|
|
150
|
+
if (hasTestDataMarkers(content)) {
|
|
151
|
+
result.warnings.push('Test data markers detected - PHI scanning skipped');
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
// Scan for NHS numbers
|
|
155
|
+
const nhsCandidates = [...content.matchAll(NHS_CANDIDATE_PATTERN)];
|
|
156
|
+
for (const match of nhsCandidates) {
|
|
157
|
+
const rawNumber = match[1];
|
|
158
|
+
const normalized = normalizeNhsNumber(rawNumber);
|
|
159
|
+
// Skip if it's a test number
|
|
160
|
+
if (isTestNhsNumber(normalized)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Validate with library (Modulus 11 checksum)
|
|
164
|
+
if (nhsValidator.validate(normalized)) {
|
|
165
|
+
result.matches.push({
|
|
166
|
+
type: PHI_TYPES.NHS_NUMBER,
|
|
167
|
+
value: normalized,
|
|
168
|
+
startIndex: match.index,
|
|
169
|
+
endIndex: match.index + rawNumber.length,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Scan for postcodes in medical context
|
|
174
|
+
const postcodeCandidates = extractPostcodeCandidates(content);
|
|
175
|
+
for (const candidate of postcodeCandidates) {
|
|
176
|
+
// Skip test postcodes
|
|
177
|
+
if (isTestPostcode(candidate.value)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// Check for medical context
|
|
181
|
+
const medicalContext = findMedicalContext(content, candidate.index, candidate.value.length);
|
|
182
|
+
if (medicalContext.found) {
|
|
183
|
+
result.matches.push({
|
|
184
|
+
type: PHI_TYPES.POSTCODE_MEDICAL_CONTEXT,
|
|
185
|
+
value: candidate.value,
|
|
186
|
+
startIndex: candidate.index,
|
|
187
|
+
endIndex: candidate.index + candidate.originalValue.length,
|
|
188
|
+
medicalKeyword: medicalContext.keyword,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
result.hasPHI = result.matches.length > 0;
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Create a human-readable summary of PHI matches
|
|
197
|
+
*
|
|
198
|
+
* @param {Array} matches - PHI matches from scanForPHI
|
|
199
|
+
* @returns {string} Summary message
|
|
200
|
+
*/
|
|
201
|
+
export function formatPHISummary(matches) {
|
|
202
|
+
if (matches.length === 0) {
|
|
203
|
+
return 'No PHI detected';
|
|
204
|
+
}
|
|
205
|
+
const nhsCount = matches.filter((m) => m.type === PHI_TYPES.NHS_NUMBER).length;
|
|
206
|
+
const postcodeCount = matches.filter((m) => m.type === PHI_TYPES.POSTCODE_MEDICAL_CONTEXT).length;
|
|
207
|
+
const parts = [];
|
|
208
|
+
if (nhsCount > 0) {
|
|
209
|
+
parts.push(`${nhsCount} NHS number${nhsCount > 1 ? 's' : ''}`);
|
|
210
|
+
}
|
|
211
|
+
if (postcodeCount > 0) {
|
|
212
|
+
parts.push(`${postcodeCount} postcode${postcodeCount > 1 ? 's' : ''} in medical context`);
|
|
213
|
+
}
|
|
214
|
+
return `PHI detected: ${parts.join(', ')}`;
|
|
215
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU-2278: Worktree Ownership Validation
|
|
3
|
+
*
|
|
4
|
+
* Validates that a WU can only clean up its own worktree.
|
|
5
|
+
* Prevents cross-agent worktree deletion during parallel execution.
|
|
6
|
+
*
|
|
7
|
+
* Note: No external library exists for LumenFlow-specific worktree ownership
|
|
8
|
+
* validation - this is internal workflow tooling.
|
|
9
|
+
*
|
|
10
|
+
* @module worktree-ownership
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Extract WU ID from worktree path
|
|
14
|
+
*
|
|
15
|
+
* Worktree paths follow pattern: worktrees/<lane>-wu-<id>
|
|
16
|
+
* Examples:
|
|
17
|
+
* - worktrees/operations-wu-100 -> WU-100
|
|
18
|
+
* - worktrees/operations-tooling-wu-2278 -> WU-2278
|
|
19
|
+
* - worktrees/experience-chat-wu-500 -> WU-500
|
|
20
|
+
*
|
|
21
|
+
* @param {string} worktreePath - Path to worktree
|
|
22
|
+
* @returns {string|null} Extracted WU ID (uppercase) or null if not found
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractWUFromWorktreePath(worktreePath: any): string;
|
|
25
|
+
/**
|
|
26
|
+
* Validate that the WU can safely clean up the given worktree
|
|
27
|
+
*
|
|
28
|
+
* Blocks deletion when:
|
|
29
|
+
* - Worktree path contains a different WU ID
|
|
30
|
+
*
|
|
31
|
+
* Allows deletion when:
|
|
32
|
+
* - Worktree path is null/undefined (nothing to clean up)
|
|
33
|
+
* - Worktree path matches the WU ID
|
|
34
|
+
* - Worktree path doesn't follow WU naming convention (manual worktree)
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} params - Validation parameters
|
|
37
|
+
* @param {string|null|undefined} params.worktreePath - Path to worktree
|
|
38
|
+
* @param {string} params.wuId - WU ID attempting cleanup (e.g., "WU-100")
|
|
39
|
+
* @returns {{ valid: boolean, error?: string }} Validation result
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateWorktreeOwnership({ worktreePath, wuId }: {
|
|
42
|
+
worktreePath: any;
|
|
43
|
+
wuId: any;
|
|
44
|
+
}): {
|
|
45
|
+
valid: boolean;
|
|
46
|
+
error?: undefined;
|
|
47
|
+
} | {
|
|
48
|
+
valid: boolean;
|
|
49
|
+
error: string;
|
|
50
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU-2278: Worktree Ownership Validation
|
|
3
|
+
*
|
|
4
|
+
* Validates that a WU can only clean up its own worktree.
|
|
5
|
+
* Prevents cross-agent worktree deletion during parallel execution.
|
|
6
|
+
*
|
|
7
|
+
* Note: No external library exists for LumenFlow-specific worktree ownership
|
|
8
|
+
* validation - this is internal workflow tooling.
|
|
9
|
+
*
|
|
10
|
+
* @module worktree-ownership
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Extract WU ID from worktree path
|
|
14
|
+
*
|
|
15
|
+
* Worktree paths follow pattern: worktrees/<lane>-wu-<id>
|
|
16
|
+
* Examples:
|
|
17
|
+
* - worktrees/operations-wu-100 -> WU-100
|
|
18
|
+
* - worktrees/operations-tooling-wu-2278 -> WU-2278
|
|
19
|
+
* - worktrees/experience-chat-wu-500 -> WU-500
|
|
20
|
+
*
|
|
21
|
+
* @param {string} worktreePath - Path to worktree
|
|
22
|
+
* @returns {string|null} Extracted WU ID (uppercase) or null if not found
|
|
23
|
+
*/
|
|
24
|
+
export function extractWUFromWorktreePath(worktreePath) {
|
|
25
|
+
if (!worktreePath || typeof worktreePath !== 'string') {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
// Match wu-<id> pattern at end of path (case insensitive)
|
|
29
|
+
const match = worktreePath.match(/wu-(\d+)(?:\/)?$/i);
|
|
30
|
+
if (!match) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return `WU-${match[1]}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validate that the WU can safely clean up the given worktree
|
|
37
|
+
*
|
|
38
|
+
* Blocks deletion when:
|
|
39
|
+
* - Worktree path contains a different WU ID
|
|
40
|
+
*
|
|
41
|
+
* Allows deletion when:
|
|
42
|
+
* - Worktree path is null/undefined (nothing to clean up)
|
|
43
|
+
* - Worktree path matches the WU ID
|
|
44
|
+
* - Worktree path doesn't follow WU naming convention (manual worktree)
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} params - Validation parameters
|
|
47
|
+
* @param {string|null|undefined} params.worktreePath - Path to worktree
|
|
48
|
+
* @param {string} params.wuId - WU ID attempting cleanup (e.g., "WU-100")
|
|
49
|
+
* @returns {{ valid: boolean, error?: string }} Validation result
|
|
50
|
+
*/
|
|
51
|
+
export function validateWorktreeOwnership({ worktreePath, wuId }) {
|
|
52
|
+
// No worktree path = nothing to validate
|
|
53
|
+
if (!worktreePath) {
|
|
54
|
+
return { valid: true };
|
|
55
|
+
}
|
|
56
|
+
const worktreeWuId = extractWUFromWorktreePath(worktreePath);
|
|
57
|
+
// If worktree doesn't follow WU naming, allow cleanup (manual worktree)
|
|
58
|
+
if (!worktreeWuId) {
|
|
59
|
+
return {
|
|
60
|
+
valid: false,
|
|
61
|
+
error: `Worktree ownership mismatch: cannot determine owner of ${worktreePath}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Normalize WU IDs for comparison (case insensitive)
|
|
65
|
+
const normalizedWorktreeId = worktreeWuId.toUpperCase();
|
|
66
|
+
const normalizedWuId = wuId.toUpperCase();
|
|
67
|
+
if (normalizedWorktreeId !== normalizedWuId) {
|
|
68
|
+
return {
|
|
69
|
+
valid: false,
|
|
70
|
+
error: `Worktree ownership mismatch: worktree belongs to ${normalizedWorktreeId}, but ${normalizedWuId} attempted cleanup. This could delete another agent's work.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { valid: true };
|
|
74
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree Scanner (WU-1748)
|
|
3
|
+
*
|
|
4
|
+
* Scans existing git worktrees to detect uncommitted changes.
|
|
5
|
+
* Provides cross-agent visibility for abandoned WU work.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Parses git worktree list output
|
|
9
|
+
* - Detects uncommitted changes per worktree
|
|
10
|
+
* - Reports last activity timestamp
|
|
11
|
+
* - Identifies potentially abandoned WUs
|
|
12
|
+
*
|
|
13
|
+
* @see {@link tools/lib/__tests__/worktree-scanner.test.mjs} - Tests
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} WorktreeInfo
|
|
17
|
+
* @property {string} path - Absolute path to worktree
|
|
18
|
+
* @property {string} sha - Current commit SHA
|
|
19
|
+
* @property {string} branch - Branch name or "(detached HEAD)"
|
|
20
|
+
* @property {boolean} isMain - Whether this is the main worktree
|
|
21
|
+
* @property {string} [wuId] - WU ID if this is a lane worktree
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} WorktreeStatus
|
|
25
|
+
* @property {boolean} hasUncommittedChanges - Whether there are uncommitted changes
|
|
26
|
+
* @property {number} uncommittedFileCount - Number of uncommitted files
|
|
27
|
+
* @property {string[]} uncommittedFiles - List of uncommitted file paths
|
|
28
|
+
* @property {string} lastActivityTimestamp - ISO timestamp of last git activity
|
|
29
|
+
* @property {string} [error] - Error message if git commands failed
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} WorktreeScanResult
|
|
33
|
+
* @property {(WorktreeInfo & WorktreeStatus)[]} worktrees - All WU worktrees with status
|
|
34
|
+
* @property {(WorktreeInfo & WorktreeStatus)[]} worktreesWithUncommittedWork - Worktrees with uncommitted changes
|
|
35
|
+
* @property {object} summary - Summary statistics
|
|
36
|
+
* @property {number} summary.totalWorktrees - Total number of WU worktrees
|
|
37
|
+
* @property {number} summary.withUncommittedChanges - Number with uncommitted changes
|
|
38
|
+
* @property {number} summary.totalUncommittedFiles - Total uncommitted files across all worktrees
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Parses git worktree list output into structured data.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} output - Raw output from `git worktree list`
|
|
44
|
+
* @returns {WorktreeInfo[]} Parsed worktree information
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const info = parseWorktreeList('/home/user/project abc1234 [main]');
|
|
48
|
+
* // Returns: [{ path: '/home/user/project', sha: 'abc1234', branch: 'main', isMain: true }]
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseWorktreeList(output: any): any[];
|
|
51
|
+
/**
|
|
52
|
+
* Gets the status of a single worktree including uncommitted changes.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} worktreePath - Path to the worktree
|
|
55
|
+
* @param {object} [options] - Options
|
|
56
|
+
* @param {WorktreeScannerOptions} [options] - Options
|
|
57
|
+
* @returns {Promise<WorktreeStatus>} Worktree status
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const status = await getWorktreeStatus('/path/to/worktree');
|
|
61
|
+
* if (status.hasUncommittedChanges) {
|
|
62
|
+
* console.log(`Found ${status.uncommittedFileCount} uncommitted files`);
|
|
63
|
+
* }
|
|
64
|
+
*/
|
|
65
|
+
interface WorktreeScannerOptions {
|
|
66
|
+
/** Custom exec function for testing */
|
|
67
|
+
execAsync?: (cmd: string) => Promise<{
|
|
68
|
+
stdout: string;
|
|
69
|
+
stderr: string;
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
72
|
+
export declare function getWorktreeStatus(worktreePath: any, options?: WorktreeScannerOptions): Promise<{
|
|
73
|
+
hasUncommittedChanges: boolean;
|
|
74
|
+
uncommittedFileCount: number;
|
|
75
|
+
uncommittedFiles: string[];
|
|
76
|
+
lastActivityTimestamp: string;
|
|
77
|
+
error?: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Scans all worktrees and returns their status.
|
|
81
|
+
*
|
|
82
|
+
* Excludes the main worktree and focuses on WU worktrees (lane branches).
|
|
83
|
+
*
|
|
84
|
+
* @param {string} basePath - Path to main repository
|
|
85
|
+
* @param {WorktreeScannerOptions} [options] - Options
|
|
86
|
+
* @returns {Promise<WorktreeScanResult>} Scan results with all worktrees and summary
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* const result = await scanWorktrees('/path/to/repo');
|
|
90
|
+
* for (const wt of result.worktreesWithUncommittedWork) {
|
|
91
|
+
* console.log(`${wt.wuId}: ${wt.uncommittedFileCount} uncommitted files`);
|
|
92
|
+
* }
|
|
93
|
+
*/
|
|
94
|
+
export declare function scanWorktrees(basePath: any, options?: WorktreeScannerOptions): Promise<{
|
|
95
|
+
worktrees: any[];
|
|
96
|
+
worktreesWithUncommittedWork: any[];
|
|
97
|
+
summary: {
|
|
98
|
+
totalWorktrees: number;
|
|
99
|
+
withUncommittedChanges: number;
|
|
100
|
+
totalUncommittedFiles: any;
|
|
101
|
+
};
|
|
102
|
+
}>;
|
|
103
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree Scanner (WU-1748)
|
|
3
|
+
*
|
|
4
|
+
* Scans existing git worktrees to detect uncommitted changes.
|
|
5
|
+
* Provides cross-agent visibility for abandoned WU work.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Parses git worktree list output
|
|
9
|
+
* - Detects uncommitted changes per worktree
|
|
10
|
+
* - Reports last activity timestamp
|
|
11
|
+
* - Identifies potentially abandoned WUs
|
|
12
|
+
*
|
|
13
|
+
* @see {@link tools/lib/__tests__/worktree-scanner.test.mjs} - Tests
|
|
14
|
+
*/
|
|
15
|
+
import { exec } from 'node:child_process';
|
|
16
|
+
import { promisify } from 'node:util';
|
|
17
|
+
const execAsync = promisify(exec);
|
|
18
|
+
/**
|
|
19
|
+
* Regex pattern to extract WU ID from lane branch name
|
|
20
|
+
* Matches patterns like: lane/operations/wu-1234, lane/operations-tooling/wu-1234
|
|
21
|
+
*/
|
|
22
|
+
const WU_ID_PATTERN = /wu-(\d+)$/i;
|
|
23
|
+
/**
|
|
24
|
+
* Regex pattern to parse git worktree list output line
|
|
25
|
+
* Format: /path/to/worktree SHA1 [branch] or (detached HEAD)
|
|
26
|
+
*/
|
|
27
|
+
const WORKTREE_LINE_PATTERN = /^(\S+)\s+(\S+)\s+(?:\[([^\]]+)\]|\(([^)]+)\))$/;
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} WorktreeInfo
|
|
30
|
+
* @property {string} path - Absolute path to worktree
|
|
31
|
+
* @property {string} sha - Current commit SHA
|
|
32
|
+
* @property {string} branch - Branch name or "(detached HEAD)"
|
|
33
|
+
* @property {boolean} isMain - Whether this is the main worktree
|
|
34
|
+
* @property {string} [wuId] - WU ID if this is a lane worktree
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} WorktreeStatus
|
|
38
|
+
* @property {boolean} hasUncommittedChanges - Whether there are uncommitted changes
|
|
39
|
+
* @property {number} uncommittedFileCount - Number of uncommitted files
|
|
40
|
+
* @property {string[]} uncommittedFiles - List of uncommitted file paths
|
|
41
|
+
* @property {string} lastActivityTimestamp - ISO timestamp of last git activity
|
|
42
|
+
* @property {string} [error] - Error message if git commands failed
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {object} WorktreeScanResult
|
|
46
|
+
* @property {(WorktreeInfo & WorktreeStatus)[]} worktrees - All WU worktrees with status
|
|
47
|
+
* @property {(WorktreeInfo & WorktreeStatus)[]} worktreesWithUncommittedWork - Worktrees with uncommitted changes
|
|
48
|
+
* @property {object} summary - Summary statistics
|
|
49
|
+
* @property {number} summary.totalWorktrees - Total number of WU worktrees
|
|
50
|
+
* @property {number} summary.withUncommittedChanges - Number with uncommitted changes
|
|
51
|
+
* @property {number} summary.totalUncommittedFiles - Total uncommitted files across all worktrees
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Parses git worktree list output into structured data.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} output - Raw output from `git worktree list`
|
|
57
|
+
* @returns {WorktreeInfo[]} Parsed worktree information
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const info = parseWorktreeList('/home/user/project abc1234 [main]');
|
|
61
|
+
* // Returns: [{ path: '/home/user/project', sha: 'abc1234', branch: 'main', isMain: true }]
|
|
62
|
+
*/
|
|
63
|
+
export function parseWorktreeList(output) {
|
|
64
|
+
if (!output || !output.trim()) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const lines = output.trim().split('\n');
|
|
68
|
+
const worktrees = [];
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (!trimmed)
|
|
72
|
+
continue;
|
|
73
|
+
const match = trimmed.match(WORKTREE_LINE_PATTERN);
|
|
74
|
+
if (!match)
|
|
75
|
+
continue;
|
|
76
|
+
const [, path, sha, bracketBranch, parenBranch] = match;
|
|
77
|
+
const branch = bracketBranch || parenBranch;
|
|
78
|
+
const isMain = branch === 'main' || branch === 'master';
|
|
79
|
+
/** @type {WorktreeInfo} */
|
|
80
|
+
const info = {
|
|
81
|
+
path,
|
|
82
|
+
sha,
|
|
83
|
+
branch,
|
|
84
|
+
isMain,
|
|
85
|
+
};
|
|
86
|
+
// Extract WU ID from lane branch name
|
|
87
|
+
const wuMatch = branch.match(WU_ID_PATTERN);
|
|
88
|
+
if (wuMatch) {
|
|
89
|
+
info.wuId = `WU-${wuMatch[1]}`;
|
|
90
|
+
}
|
|
91
|
+
worktrees.push(info);
|
|
92
|
+
}
|
|
93
|
+
return worktrees;
|
|
94
|
+
}
|
|
95
|
+
export async function getWorktreeStatus(worktreePath, options = {}) {
|
|
96
|
+
const runCmd = options.execAsync || execAsync;
|
|
97
|
+
/** @type {WorktreeStatus} */
|
|
98
|
+
const status = {
|
|
99
|
+
hasUncommittedChanges: false,
|
|
100
|
+
uncommittedFileCount: 0,
|
|
101
|
+
uncommittedFiles: [],
|
|
102
|
+
lastActivityTimestamp: '',
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
// Get uncommitted changes via git status --porcelain
|
|
106
|
+
const statusResult = await runCmd(`git -C "${worktreePath}" status --porcelain`);
|
|
107
|
+
// Note: Don't trim() the whole output as it would remove leading space from first line
|
|
108
|
+
// Git status --porcelain format: XY filename (2 chars + space + path)
|
|
109
|
+
const statusOutput = statusResult.stdout;
|
|
110
|
+
if (statusOutput && statusOutput.trim()) {
|
|
111
|
+
const files = statusOutput
|
|
112
|
+
.split('\n')
|
|
113
|
+
.filter((line) => line.length > 3) // Filter empty lines
|
|
114
|
+
.map((line) => line.slice(3).trim()); // Remove XY prefix and trim path
|
|
115
|
+
status.uncommittedFiles = files;
|
|
116
|
+
status.uncommittedFileCount = files.length;
|
|
117
|
+
status.hasUncommittedChanges = files.length > 0;
|
|
118
|
+
}
|
|
119
|
+
// Get last activity timestamp from git log
|
|
120
|
+
const logResult = await runCmd(`git -C "${worktreePath}" log -1 --format=%aI 2>/dev/null || echo ""`);
|
|
121
|
+
status.lastActivityTimestamp = logResult.stdout.trim();
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
status.error = error instanceof Error ? error.message : String(error);
|
|
125
|
+
}
|
|
126
|
+
return status;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Scans all worktrees and returns their status.
|
|
130
|
+
*
|
|
131
|
+
* Excludes the main worktree and focuses on WU worktrees (lane branches).
|
|
132
|
+
*
|
|
133
|
+
* @param {string} basePath - Path to main repository
|
|
134
|
+
* @param {WorktreeScannerOptions} [options] - Options
|
|
135
|
+
* @returns {Promise<WorktreeScanResult>} Scan results with all worktrees and summary
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const result = await scanWorktrees('/path/to/repo');
|
|
139
|
+
* for (const wt of result.worktreesWithUncommittedWork) {
|
|
140
|
+
* console.log(`${wt.wuId}: ${wt.uncommittedFileCount} uncommitted files`);
|
|
141
|
+
* }
|
|
142
|
+
*/
|
|
143
|
+
export async function scanWorktrees(basePath, options = {}) {
|
|
144
|
+
const runCmd = options.execAsync || execAsync;
|
|
145
|
+
// Get worktree list
|
|
146
|
+
const listResult = await runCmd(`git -C "${basePath}" worktree list`);
|
|
147
|
+
const allWorktrees = parseWorktreeList(listResult.stdout);
|
|
148
|
+
// Filter to WU worktrees only (exclude main)
|
|
149
|
+
const wuWorktrees = allWorktrees.filter((wt) => !wt.isMain && wt.wuId);
|
|
150
|
+
// Get status for each WU worktree
|
|
151
|
+
const worktreesWithStatus = await Promise.all(wuWorktrees.map(async (wt) => {
|
|
152
|
+
const status = await getWorktreeStatus(wt.path, { execAsync: runCmd });
|
|
153
|
+
return { ...wt, ...status };
|
|
154
|
+
}));
|
|
155
|
+
// Filter to those with uncommitted changes
|
|
156
|
+
const worktreesWithUncommittedWork = worktreesWithStatus.filter((wt) => wt.hasUncommittedChanges);
|
|
157
|
+
// Calculate summary
|
|
158
|
+
const summary = {
|
|
159
|
+
totalWorktrees: worktreesWithStatus.length,
|
|
160
|
+
withUncommittedChanges: worktreesWithUncommittedWork.length,
|
|
161
|
+
totalUncommittedFiles: worktreesWithStatus.reduce((sum, wt) => sum + wt.uncommittedFileCount, 0),
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
worktrees: worktreesWithStatus,
|
|
165
|
+
worktreesWithUncommittedWork,
|
|
166
|
+
summary,
|
|
167
|
+
};
|
|
168
|
+
}
|