@lumenflow/core 1.3.0 → 1.3.2
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/dist/arg-parser.d.ts +6 -0
- package/dist/arg-parser.js +26 -0
- package/dist/git-adapter.d.ts +7 -0
- package/dist/git-adapter.js +15 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/lumenflow-home.d.ts +130 -0
- package/dist/lumenflow-home.js +208 -0
- package/dist/manual-test-validator.js +1 -1
- package/dist/orphan-detector.d.ts +16 -0
- package/dist/orphan-detector.js +24 -0
- package/dist/spec-branch-helpers.d.ts +118 -0
- package/dist/spec-branch-helpers.js +192 -0
- package/dist/wu-consistency-checker.d.ts +2 -0
- package/dist/wu-consistency-checker.js +35 -1
- package/dist/wu-constants.d.ts +77 -0
- package/dist/wu-constants.js +80 -0
- package/dist/wu-create-validators.d.ts +40 -2
- package/dist/wu-create-validators.js +76 -2
- package/dist/wu-done-branch-only.js +9 -0
- package/dist/wu-done-docs-generate.d.ts +73 -0
- package/dist/wu-done-docs-generate.js +108 -0
- package/dist/wu-done-worktree.js +12 -0
- package/dist/wu-schema.js +3 -1
- package/dist/wu-spawn.js +14 -1
- package/package.json +4 -3
package/dist/arg-parser.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ interface WUOption {
|
|
|
21
21
|
isRepeatable?: boolean;
|
|
22
22
|
}
|
|
23
23
|
export declare const WU_OPTIONS: Record<string, WUOption>;
|
|
24
|
+
/**
|
|
25
|
+
* WU-1062: Additional options for wu:create command
|
|
26
|
+
*
|
|
27
|
+
* These options control how wu:create handles spec storage and branch creation.
|
|
28
|
+
*/
|
|
29
|
+
export declare const WU_CREATE_OPTIONS: Record<string, WUOption>;
|
|
24
30
|
/**
|
|
25
31
|
* Create a commander-based CLI parser for a WU script.
|
|
26
32
|
*
|
package/dist/arg-parser.js
CHANGED
|
@@ -396,6 +396,32 @@ export const WU_OPTIONS = {
|
|
|
396
396
|
description: 'Skip automatic pnpm install in worktree after creation (faster claims when deps already built)',
|
|
397
397
|
},
|
|
398
398
|
};
|
|
399
|
+
/**
|
|
400
|
+
* WU-1062: Additional options for wu:create command
|
|
401
|
+
*
|
|
402
|
+
* These options control how wu:create handles spec storage and branch creation.
|
|
403
|
+
*/
|
|
404
|
+
export const WU_CREATE_OPTIONS = {
|
|
405
|
+
/**
|
|
406
|
+
* Create plan template in $LUMENFLOW_HOME/plans/
|
|
407
|
+
* Used with spec branch mode to store plans externally.
|
|
408
|
+
*/
|
|
409
|
+
plan: {
|
|
410
|
+
name: 'plan',
|
|
411
|
+
flags: '--plan',
|
|
412
|
+
description: 'Create plan template in $LUMENFLOW_HOME/plans/ (external plan storage for traceability)',
|
|
413
|
+
},
|
|
414
|
+
/**
|
|
415
|
+
* Direct main-write mode (legacy behavior)
|
|
416
|
+
* Writes WU spec directly to main branch instead of spec branch.
|
|
417
|
+
* Use for emergency/hotfix scenarios or when spec branch workflow is not desired.
|
|
418
|
+
*/
|
|
419
|
+
direct: {
|
|
420
|
+
name: 'direct',
|
|
421
|
+
flags: '--direct',
|
|
422
|
+
description: 'Write to main directly (legacy behavior). Default is spec branch mode (spec/wu-XXXX)',
|
|
423
|
+
},
|
|
424
|
+
};
|
|
399
425
|
/**
|
|
400
426
|
* Negated options that commander handles specially.
|
|
401
427
|
* --no-foo creates opts.foo = false. We convert to noFoo = true.
|
package/dist/git-adapter.d.ts
CHANGED
|
@@ -66,6 +66,13 @@ export declare class GitAdapter {
|
|
|
66
66
|
* await git.getStatus(); // " M file.txt\n?? untracked.txt"
|
|
67
67
|
*/
|
|
68
68
|
getStatus(): Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Get unpushed commits (compared to upstream)
|
|
71
|
+
* @returns {Promise<string>} Oneline log output for unpushed commits
|
|
72
|
+
* @example
|
|
73
|
+
* await git.getUnpushedCommits(); // "abc123 fix: ...\n"
|
|
74
|
+
*/
|
|
75
|
+
getUnpushedCommits(): Promise<string>;
|
|
69
76
|
/**
|
|
70
77
|
* Check if a branch exists
|
|
71
78
|
* @param {string} branch - Branch name
|
package/dist/git-adapter.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { simpleGit } from 'simple-git';
|
|
20
20
|
import { existsSync, rmSync } from 'node:fs';
|
|
21
|
-
import { GIT_FLAGS } from './wu-constants.js';
|
|
21
|
+
import { GIT_COMMANDS, GIT_FLAGS, GIT_REFS } from './wu-constants.js';
|
|
22
22
|
// WU-2242: Runtime assertion helpers
|
|
23
23
|
/**
|
|
24
24
|
* Assert that a value is a non-empty string
|
|
@@ -117,6 +117,20 @@ export class GitAdapter {
|
|
|
117
117
|
const result = await this.git.raw(['status', GIT_FLAGS.PORCELAIN]);
|
|
118
118
|
return result.trim();
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Get unpushed commits (compared to upstream)
|
|
122
|
+
* @returns {Promise<string>} Oneline log output for unpushed commits
|
|
123
|
+
* @example
|
|
124
|
+
* await git.getUnpushedCommits(); // "abc123 fix: ...\n"
|
|
125
|
+
*/
|
|
126
|
+
async getUnpushedCommits() {
|
|
127
|
+
const result = await this.git.raw([
|
|
128
|
+
GIT_COMMANDS.LOG,
|
|
129
|
+
GIT_REFS.UPSTREAM_RANGE,
|
|
130
|
+
GIT_FLAGS.ONELINE,
|
|
131
|
+
]);
|
|
132
|
+
return result.trim();
|
|
133
|
+
}
|
|
120
134
|
/**
|
|
121
135
|
* Check if a branch exists
|
|
122
136
|
* @param {string} branch - Branch name
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -61,3 +61,6 @@ export * from './lumenflow-config.js';
|
|
|
61
61
|
export * from './lumenflow-config-schema.js';
|
|
62
62
|
// Branch check utilities
|
|
63
63
|
export * from './branch-check.js';
|
|
64
|
+
// WU-1062: External plan storage and spec branch helpers
|
|
65
|
+
export * from './lumenflow-home.js';
|
|
66
|
+
export * from './spec-branch-helpers.js';
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LumenFlow Home Directory Resolution
|
|
3
|
+
*
|
|
4
|
+
* WU-1062: External plan storage and no-main-write mode
|
|
5
|
+
*
|
|
6
|
+
* Provides helpers for resolving the $LUMENFLOW_HOME directory and related paths.
|
|
7
|
+
* Plans are stored externally in $LUMENFLOW_HOME/plans/ instead of in the repo.
|
|
8
|
+
*
|
|
9
|
+
* Default: ~/.lumenflow/
|
|
10
|
+
* Override: Set $LUMENFLOW_HOME environment variable
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Environment variable name for LumenFlow home directory
|
|
16
|
+
*/
|
|
17
|
+
export declare const LUMENFLOW_HOME_ENV = "LUMENFLOW_HOME";
|
|
18
|
+
/**
|
|
19
|
+
* Default LumenFlow home directory name
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_LUMENFLOW_DIR = ".lumenflow";
|
|
22
|
+
/**
|
|
23
|
+
* Plans subdirectory name
|
|
24
|
+
*/
|
|
25
|
+
export declare const PLANS_SUBDIR = "plans";
|
|
26
|
+
/**
|
|
27
|
+
* Custom protocol for external LumenFlow paths
|
|
28
|
+
*/
|
|
29
|
+
export declare const LUMENFLOW_PROTOCOL = "lumenflow://";
|
|
30
|
+
/**
|
|
31
|
+
* Environment variable prefix for spec_refs
|
|
32
|
+
*/
|
|
33
|
+
export declare const LUMENFLOW_HOME_VAR_PREFIX = "$LUMENFLOW_HOME";
|
|
34
|
+
/**
|
|
35
|
+
* Get the LumenFlow home directory path
|
|
36
|
+
*
|
|
37
|
+
* Resolution order:
|
|
38
|
+
* 1. $LUMENFLOW_HOME environment variable (with ~ expansion)
|
|
39
|
+
* 2. ~/.lumenflow/ default
|
|
40
|
+
*
|
|
41
|
+
* @returns {string} Absolute path to LumenFlow home directory
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // With LUMENFLOW_HOME=/custom/path
|
|
45
|
+
* getLumenflowHome() // '/custom/path'
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // With LUMENFLOW_HOME=~/.custom-lumenflow
|
|
49
|
+
* getLumenflowHome() // '/home/user/.custom-lumenflow'
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Without LUMENFLOW_HOME set
|
|
53
|
+
* getLumenflowHome() // '/home/user/.lumenflow'
|
|
54
|
+
*/
|
|
55
|
+
export declare function getLumenflowHome(): string;
|
|
56
|
+
/**
|
|
57
|
+
* Get the plans directory path
|
|
58
|
+
*
|
|
59
|
+
* Plans are stored in $LUMENFLOW_HOME/plans/
|
|
60
|
+
*
|
|
61
|
+
* @returns {string} Absolute path to plans directory
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* getPlansDir() // '/home/user/.lumenflow/plans'
|
|
65
|
+
*/
|
|
66
|
+
export declare function getPlansDir(): string;
|
|
67
|
+
/**
|
|
68
|
+
* Check if a path is an external (non-repo) path
|
|
69
|
+
*
|
|
70
|
+
* External paths include:
|
|
71
|
+
* - Paths starting with ~/
|
|
72
|
+
* - Paths starting with $LUMENFLOW_HOME
|
|
73
|
+
* - Paths using lumenflow:// protocol
|
|
74
|
+
* - Absolute paths (starting with /)
|
|
75
|
+
*
|
|
76
|
+
* @param {string} path - Path to check
|
|
77
|
+
* @returns {boolean} True if path is external
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* isExternalPath('~/.lumenflow/plans/plan.md') // true
|
|
81
|
+
* isExternalPath('$LUMENFLOW_HOME/plans/plan.md') // true
|
|
82
|
+
* isExternalPath('lumenflow://plans/plan.md') // true
|
|
83
|
+
* isExternalPath('/home/user/.lumenflow/plans/plan.md') // true
|
|
84
|
+
* isExternalPath('docs/04-operations/plans/plan.md') // false
|
|
85
|
+
*/
|
|
86
|
+
export declare function isExternalPath(path: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Normalize a spec_ref path by expanding variables and protocols
|
|
89
|
+
*
|
|
90
|
+
* Expands:
|
|
91
|
+
* - lumenflow://path -> $LUMENFLOW_HOME/path
|
|
92
|
+
* - ~/path -> /home/user/path
|
|
93
|
+
* - $LUMENFLOW_HOME/path -> actual LUMENFLOW_HOME value
|
|
94
|
+
*
|
|
95
|
+
* Repo-relative paths are returned unchanged.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} specRef - Spec reference path
|
|
98
|
+
* @returns {string} Normalized absolute path or unchanged relative path
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* normalizeSpecRef('lumenflow://plans/WU-1062-plan.md')
|
|
102
|
+
* // '/home/user/.lumenflow/plans/WU-1062-plan.md'
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* normalizeSpecRef('docs/04-operations/plans/plan.md')
|
|
106
|
+
* // 'docs/04-operations/plans/plan.md' (unchanged)
|
|
107
|
+
*/
|
|
108
|
+
export declare function normalizeSpecRef(specRef: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* Get the full path for a plan file given a WU ID
|
|
111
|
+
*
|
|
112
|
+
* @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
|
|
113
|
+
* @returns {string} Full path to the plan file
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* getPlanPath('WU-1062')
|
|
117
|
+
* // '/home/user/.lumenflow/plans/WU-1062-plan.md'
|
|
118
|
+
*/
|
|
119
|
+
export declare function getPlanPath(wuId: string): string;
|
|
120
|
+
/**
|
|
121
|
+
* Get the lumenflow:// protocol reference for a plan
|
|
122
|
+
*
|
|
123
|
+
* @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
|
|
124
|
+
* @returns {string} Protocol reference (e.g., 'lumenflow://plans/WU-1062-plan.md')
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* getPlanProtocolRef('WU-1062')
|
|
128
|
+
* // 'lumenflow://plans/WU-1062-plan.md'
|
|
129
|
+
*/
|
|
130
|
+
export declare function getPlanProtocolRef(wuId: string): string;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LumenFlow Home Directory Resolution
|
|
3
|
+
*
|
|
4
|
+
* WU-1062: External plan storage and no-main-write mode
|
|
5
|
+
*
|
|
6
|
+
* Provides helpers for resolving the $LUMENFLOW_HOME directory and related paths.
|
|
7
|
+
* Plans are stored externally in $LUMENFLOW_HOME/plans/ instead of in the repo.
|
|
8
|
+
*
|
|
9
|
+
* Default: ~/.lumenflow/
|
|
10
|
+
* Override: Set $LUMENFLOW_HOME environment variable
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
/**
|
|
17
|
+
* Environment variable name for LumenFlow home directory
|
|
18
|
+
*/
|
|
19
|
+
export const LUMENFLOW_HOME_ENV = 'LUMENFLOW_HOME';
|
|
20
|
+
/**
|
|
21
|
+
* Default LumenFlow home directory name
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_LUMENFLOW_DIR = '.lumenflow';
|
|
24
|
+
/**
|
|
25
|
+
* Plans subdirectory name
|
|
26
|
+
*/
|
|
27
|
+
export const PLANS_SUBDIR = 'plans';
|
|
28
|
+
/**
|
|
29
|
+
* Custom protocol for external LumenFlow paths
|
|
30
|
+
*/
|
|
31
|
+
export const LUMENFLOW_PROTOCOL = 'lumenflow://';
|
|
32
|
+
/**
|
|
33
|
+
* Environment variable prefix for spec_refs
|
|
34
|
+
*/
|
|
35
|
+
export const LUMENFLOW_HOME_VAR_PREFIX = '$LUMENFLOW_HOME';
|
|
36
|
+
/**
|
|
37
|
+
* Expand ~ to user's home directory
|
|
38
|
+
*
|
|
39
|
+
* @param {string} path - Path that may contain ~
|
|
40
|
+
* @returns {string} Expanded path
|
|
41
|
+
*/
|
|
42
|
+
function expandTilde(path) {
|
|
43
|
+
if (path.startsWith('~/')) {
|
|
44
|
+
return join(homedir(), path.slice(2));
|
|
45
|
+
}
|
|
46
|
+
if (path === '~') {
|
|
47
|
+
return homedir();
|
|
48
|
+
}
|
|
49
|
+
return path;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Remove trailing slashes from path
|
|
53
|
+
*
|
|
54
|
+
* @param {string} path - Path that may have trailing slashes
|
|
55
|
+
* @returns {string} Path without trailing slashes
|
|
56
|
+
*/
|
|
57
|
+
function removeTrailingSlash(path) {
|
|
58
|
+
return path.replace(/\/+$/, '');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the LumenFlow home directory path
|
|
62
|
+
*
|
|
63
|
+
* Resolution order:
|
|
64
|
+
* 1. $LUMENFLOW_HOME environment variable (with ~ expansion)
|
|
65
|
+
* 2. ~/.lumenflow/ default
|
|
66
|
+
*
|
|
67
|
+
* @returns {string} Absolute path to LumenFlow home directory
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // With LUMENFLOW_HOME=/custom/path
|
|
71
|
+
* getLumenflowHome() // '/custom/path'
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // With LUMENFLOW_HOME=~/.custom-lumenflow
|
|
75
|
+
* getLumenflowHome() // '/home/user/.custom-lumenflow'
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Without LUMENFLOW_HOME set
|
|
79
|
+
* getLumenflowHome() // '/home/user/.lumenflow'
|
|
80
|
+
*/
|
|
81
|
+
export function getLumenflowHome() {
|
|
82
|
+
const envValue = process.env[LUMENFLOW_HOME_ENV];
|
|
83
|
+
if (envValue) {
|
|
84
|
+
const expanded = expandTilde(envValue);
|
|
85
|
+
return removeTrailingSlash(expanded);
|
|
86
|
+
}
|
|
87
|
+
return join(homedir(), DEFAULT_LUMENFLOW_DIR);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get the plans directory path
|
|
91
|
+
*
|
|
92
|
+
* Plans are stored in $LUMENFLOW_HOME/plans/
|
|
93
|
+
*
|
|
94
|
+
* @returns {string} Absolute path to plans directory
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* getPlansDir() // '/home/user/.lumenflow/plans'
|
|
98
|
+
*/
|
|
99
|
+
export function getPlansDir() {
|
|
100
|
+
return join(getLumenflowHome(), PLANS_SUBDIR);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if a path is an external (non-repo) path
|
|
104
|
+
*
|
|
105
|
+
* External paths include:
|
|
106
|
+
* - Paths starting with ~/
|
|
107
|
+
* - Paths starting with $LUMENFLOW_HOME
|
|
108
|
+
* - Paths using lumenflow:// protocol
|
|
109
|
+
* - Absolute paths (starting with /)
|
|
110
|
+
*
|
|
111
|
+
* @param {string} path - Path to check
|
|
112
|
+
* @returns {boolean} True if path is external
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* isExternalPath('~/.lumenflow/plans/plan.md') // true
|
|
116
|
+
* isExternalPath('$LUMENFLOW_HOME/plans/plan.md') // true
|
|
117
|
+
* isExternalPath('lumenflow://plans/plan.md') // true
|
|
118
|
+
* isExternalPath('/home/user/.lumenflow/plans/plan.md') // true
|
|
119
|
+
* isExternalPath('docs/04-operations/plans/plan.md') // false
|
|
120
|
+
*/
|
|
121
|
+
export function isExternalPath(path) {
|
|
122
|
+
// Check for tilde-prefixed paths
|
|
123
|
+
if (path.startsWith('~/')) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
// Check for environment variable reference
|
|
127
|
+
if (path.startsWith(LUMENFLOW_HOME_VAR_PREFIX)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// Check for lumenflow:// protocol
|
|
131
|
+
if (path.startsWith(LUMENFLOW_PROTOCOL)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
// Check for absolute paths (starting with /)
|
|
135
|
+
if (path.startsWith('/')) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Normalize a spec_ref path by expanding variables and protocols
|
|
142
|
+
*
|
|
143
|
+
* Expands:
|
|
144
|
+
* - lumenflow://path -> $LUMENFLOW_HOME/path
|
|
145
|
+
* - ~/path -> /home/user/path
|
|
146
|
+
* - $LUMENFLOW_HOME/path -> actual LUMENFLOW_HOME value
|
|
147
|
+
*
|
|
148
|
+
* Repo-relative paths are returned unchanged.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} specRef - Spec reference path
|
|
151
|
+
* @returns {string} Normalized absolute path or unchanged relative path
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* normalizeSpecRef('lumenflow://plans/WU-1062-plan.md')
|
|
155
|
+
* // '/home/user/.lumenflow/plans/WU-1062-plan.md'
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* normalizeSpecRef('docs/04-operations/plans/plan.md')
|
|
159
|
+
* // 'docs/04-operations/plans/plan.md' (unchanged)
|
|
160
|
+
*/
|
|
161
|
+
export function normalizeSpecRef(specRef) {
|
|
162
|
+
// Handle lumenflow:// protocol
|
|
163
|
+
if (specRef.startsWith(LUMENFLOW_PROTOCOL)) {
|
|
164
|
+
const relativePath = specRef.slice(LUMENFLOW_PROTOCOL.length);
|
|
165
|
+
return join(getLumenflowHome(), relativePath);
|
|
166
|
+
}
|
|
167
|
+
// Handle $LUMENFLOW_HOME variable
|
|
168
|
+
if (specRef.startsWith(LUMENFLOW_HOME_VAR_PREFIX)) {
|
|
169
|
+
const relativePath = specRef.slice(LUMENFLOW_HOME_VAR_PREFIX.length);
|
|
170
|
+
// Remove leading slash if present
|
|
171
|
+
const cleanPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
|
|
172
|
+
return join(getLumenflowHome(), cleanPath);
|
|
173
|
+
}
|
|
174
|
+
// Handle tilde expansion
|
|
175
|
+
if (specRef.startsWith('~/')) {
|
|
176
|
+
return expandTilde(specRef);
|
|
177
|
+
}
|
|
178
|
+
// Return relative paths unchanged
|
|
179
|
+
return specRef;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the full path for a plan file given a WU ID
|
|
183
|
+
*
|
|
184
|
+
* @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
|
|
185
|
+
* @returns {string} Full path to the plan file
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* getPlanPath('WU-1062')
|
|
189
|
+
* // '/home/user/.lumenflow/plans/WU-1062-plan.md'
|
|
190
|
+
*/
|
|
191
|
+
export function getPlanPath(wuId) {
|
|
192
|
+
const filename = `${wuId}-plan.md`;
|
|
193
|
+
return join(getPlansDir(), filename);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get the lumenflow:// protocol reference for a plan
|
|
197
|
+
*
|
|
198
|
+
* @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
|
|
199
|
+
* @returns {string} Protocol reference (e.g., 'lumenflow://plans/WU-1062-plan.md')
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* getPlanProtocolRef('WU-1062')
|
|
203
|
+
* // 'lumenflow://plans/WU-1062-plan.md'
|
|
204
|
+
*/
|
|
205
|
+
export function getPlanProtocolRef(wuId) {
|
|
206
|
+
const filename = `${wuId}-plan.md`;
|
|
207
|
+
return `${LUMENFLOW_PROTOCOL}${PLANS_SUBDIR}/${filename}`;
|
|
208
|
+
}
|
|
@@ -20,7 +20,7 @@ import { TEST_TYPES, WU_TYPES } from './wu-constants.js';
|
|
|
20
20
|
* Code file extensions that require automated tests.
|
|
21
21
|
* @constant {string[]}
|
|
22
22
|
*/
|
|
23
|
-
const CODE_EXTENSIONS = Object.freeze(['.js', '.ts', '.tsx', '.
|
|
23
|
+
const CODE_EXTENSIONS = Object.freeze(['.js', '.ts', '.tsx', '.mjs']);
|
|
24
24
|
/**
|
|
25
25
|
* Non-code file extensions (documentation, data, config).
|
|
26
26
|
* @constant {string[]}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* @see {@link tools/wu-claim.mjs} - Pre-flight orphan check
|
|
19
19
|
* @see {@link tools/lib/git-adapter.mjs} - worktreeRemove with cleanup
|
|
20
20
|
*/
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
21
22
|
/**
|
|
22
23
|
* Result of orphan detection
|
|
23
24
|
* @typedef {object} OrphanDetectionResult
|
|
@@ -50,6 +51,21 @@ export declare function parseWorktreeList(porcelainOutput: string): WorktreeEntr
|
|
|
50
51
|
* @returns {Promise<Set<string>>} Set of absolute paths tracked by git worktree
|
|
51
52
|
*/
|
|
52
53
|
export declare function getTrackedWorktreePaths(projectRoot: any): Promise<Set<string>>;
|
|
54
|
+
/**
|
|
55
|
+
* Identify tracked worktrees that are missing on disk
|
|
56
|
+
*
|
|
57
|
+
* @param {string[]} trackedPaths - Absolute paths from git worktree list
|
|
58
|
+
* @param {(path: string) => boolean} [existsFn] - Optional fs.existsSync override
|
|
59
|
+
* @returns {string[]} Missing worktree paths
|
|
60
|
+
*/
|
|
61
|
+
export declare function getMissingWorktreesFromTracked(trackedPaths: any, existsFn?: typeof existsSync): any[];
|
|
62
|
+
/**
|
|
63
|
+
* Detect tracked worktrees that are missing on disk
|
|
64
|
+
*
|
|
65
|
+
* @param {string} [projectRoot] - Project root directory (defaults to cwd)
|
|
66
|
+
* @returns {Promise<string[]>} Missing tracked worktree paths
|
|
67
|
+
*/
|
|
68
|
+
export declare function detectMissingTrackedWorktrees(projectRoot: any): Promise<any[]>;
|
|
53
69
|
/**
|
|
54
70
|
* Get list of directories in worktrees/ folder
|
|
55
71
|
*
|
package/dist/orphan-detector.js
CHANGED
|
@@ -75,6 +75,30 @@ export async function getTrackedWorktreePaths(projectRoot) {
|
|
|
75
75
|
const entries = parseWorktreeList(output);
|
|
76
76
|
return new Set(entries.map((e) => e.path));
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Identify tracked worktrees that are missing on disk
|
|
80
|
+
*
|
|
81
|
+
* @param {string[]} trackedPaths - Absolute paths from git worktree list
|
|
82
|
+
* @param {(path: string) => boolean} [existsFn] - Optional fs.existsSync override
|
|
83
|
+
* @returns {string[]} Missing worktree paths
|
|
84
|
+
*/
|
|
85
|
+
export function getMissingWorktreesFromTracked(trackedPaths, existsFn = existsSync) {
|
|
86
|
+
if (!Array.isArray(trackedPaths)) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
return trackedPaths.filter((trackedPath) => !existsFn(trackedPath));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Detect tracked worktrees that are missing on disk
|
|
93
|
+
*
|
|
94
|
+
* @param {string} [projectRoot] - Project root directory (defaults to cwd)
|
|
95
|
+
* @returns {Promise<string[]>} Missing tracked worktree paths
|
|
96
|
+
*/
|
|
97
|
+
export async function detectMissingTrackedWorktrees(projectRoot) {
|
|
98
|
+
const root = projectRoot || process.cwd();
|
|
99
|
+
const tracked = await getTrackedWorktreePaths(root);
|
|
100
|
+
return getMissingWorktreesFromTracked([...tracked]);
|
|
101
|
+
}
|
|
78
102
|
/**
|
|
79
103
|
* Get list of directories in worktrees/ folder
|
|
80
104
|
*
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec Branch Helpers
|
|
3
|
+
*
|
|
4
|
+
* WU-1062: External plan storage and no-main-write mode
|
|
5
|
+
*
|
|
6
|
+
* Provides helpers for working with spec branches (spec/wu-XXXX).
|
|
7
|
+
* wu:create writes to spec branches by default; wu:claim merges them to main.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { SimpleGit } from 'simple-git';
|
|
12
|
+
/**
|
|
13
|
+
* Spec branch prefix
|
|
14
|
+
*/
|
|
15
|
+
export declare const SPEC_BRANCH_PREFIX = "spec/";
|
|
16
|
+
/**
|
|
17
|
+
* WU source location constants
|
|
18
|
+
*/
|
|
19
|
+
export declare const WU_SOURCE: {
|
|
20
|
+
/** WU exists on main branch only */
|
|
21
|
+
readonly MAIN: "main";
|
|
22
|
+
/** WU exists on spec branch only */
|
|
23
|
+
readonly SPEC_BRANCH: "spec_branch";
|
|
24
|
+
/** WU exists on both main and spec branch */
|
|
25
|
+
readonly BOTH: "both";
|
|
26
|
+
/** WU not found anywhere */
|
|
27
|
+
readonly NOT_FOUND: "not_found";
|
|
28
|
+
};
|
|
29
|
+
export type WUSourceType = (typeof WU_SOURCE)[keyof typeof WU_SOURCE];
|
|
30
|
+
/**
|
|
31
|
+
* Get the spec branch name for a WU
|
|
32
|
+
*
|
|
33
|
+
* @param {string} wuId - Work Unit ID (e.g., 'WU-1062')
|
|
34
|
+
* @returns {string} Spec branch name (e.g., 'spec/wu-1062')
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* getSpecBranchName('WU-1062') // 'spec/wu-1062'
|
|
38
|
+
*/
|
|
39
|
+
export declare function getSpecBranchName(wuId: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Get the origin-qualified spec branch name
|
|
42
|
+
*
|
|
43
|
+
* @param {string} wuId - Work Unit ID
|
|
44
|
+
* @returns {string} Origin-qualified branch name (e.g., 'origin/spec/wu-1062')
|
|
45
|
+
*/
|
|
46
|
+
export declare function getOriginSpecBranch(wuId: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a spec branch exists on origin
|
|
49
|
+
*
|
|
50
|
+
* @param {string} wuId - Work Unit ID
|
|
51
|
+
* @param {SimpleGit} git - Git adapter instance
|
|
52
|
+
* @returns {Promise<boolean>} True if spec branch exists
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const exists = await specBranchExists('WU-1062', git);
|
|
56
|
+
*/
|
|
57
|
+
export declare function specBranchExists(wuId: string, git: SimpleGit): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Check if a WU exists on main branch
|
|
60
|
+
*
|
|
61
|
+
* @param {string} wuId - Work Unit ID
|
|
62
|
+
* @param {SimpleGit} git - Git adapter instance
|
|
63
|
+
* @returns {Promise<boolean>} True if WU YAML exists on main
|
|
64
|
+
*/
|
|
65
|
+
export declare function isWUOnMain(wuId: string, git: SimpleGit): Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Merge spec branch to main branch (fast-forward only)
|
|
68
|
+
*
|
|
69
|
+
* This is used by wu:claim when a WU exists only on a spec branch.
|
|
70
|
+
* The spec branch is merged to main before creating the worktree.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} wuId - Work Unit ID
|
|
73
|
+
* @param {SimpleGit} git - Git adapter instance
|
|
74
|
+
* @throws {Error} If merge fails (e.g., due to conflicts)
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* await mergeSpecBranchToMain('WU-1062', git);
|
|
78
|
+
*/
|
|
79
|
+
export declare function mergeSpecBranchToMain(wuId: string, git: SimpleGit): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Delete spec branch after merge
|
|
82
|
+
*
|
|
83
|
+
* @param {string} wuId - Work Unit ID
|
|
84
|
+
* @param {SimpleGit} git - Git adapter instance
|
|
85
|
+
*/
|
|
86
|
+
export declare function deleteSpecBranch(wuId: string, git: SimpleGit): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Determine the source of a WU (main, spec branch, both, or not found)
|
|
89
|
+
*
|
|
90
|
+
* Used by wu:claim to decide whether to merge spec branch before creating worktree.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} wuId - Work Unit ID
|
|
93
|
+
* @param {SimpleGit} git - Git adapter instance
|
|
94
|
+
* @returns {Promise<WUSourceType>} Source location constant
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* const source = await getWUSource('WU-1062', git);
|
|
98
|
+
* if (source === WU_SOURCE.SPEC_BRANCH) {
|
|
99
|
+
* await mergeSpecBranchToMain('WU-1062', git);
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
export declare function getWUSource(wuId: string, git: SimpleGit): Promise<WUSourceType>;
|
|
103
|
+
/**
|
|
104
|
+
* Create a spec branch from current HEAD
|
|
105
|
+
*
|
|
106
|
+
* Used by wu:create in default mode (no --direct flag).
|
|
107
|
+
*
|
|
108
|
+
* @param {string} wuId - Work Unit ID
|
|
109
|
+
* @param {SimpleGit} git - Git adapter instance
|
|
110
|
+
*/
|
|
111
|
+
export declare function createSpecBranch(wuId: string, git: SimpleGit): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Push spec branch to origin
|
|
114
|
+
*
|
|
115
|
+
* @param {string} wuId - Work Unit ID
|
|
116
|
+
* @param {SimpleGit} git - Git adapter instance
|
|
117
|
+
*/
|
|
118
|
+
export declare function pushSpecBranch(wuId: string, git: SimpleGit): Promise<void>;
|