@lumenflow/core 2.9.0 → 2.11.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.
Files changed (49) hide show
  1. package/dist/agent-patterns-registry.js +4 -2
  2. package/dist/arg-parser.js +5 -0
  3. package/dist/constants/linter-constants.d.ts +1 -1
  4. package/dist/constants/linter-constants.js +1 -1
  5. package/dist/docs-path-validator.js +3 -3
  6. package/dist/domain/context.schemas.d.ts +3 -3
  7. package/dist/domain/orchestration.constants.d.ts +1 -1
  8. package/dist/domain/orchestration.constants.js +1 -1
  9. package/dist/domain/orchestration.types.d.ts +2 -2
  10. package/dist/domain/validation.schemas.d.ts +4 -4
  11. package/dist/hardcoded-strings.js +1 -1
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.js +5 -3
  14. package/dist/invariants/check-automated-tests.js +3 -1
  15. package/dist/lumenflow-config-schema.d.ts +5 -5
  16. package/dist/lumenflow-config-schema.js +5 -5
  17. package/dist/lumenflow-config.d.ts +1 -1
  18. package/dist/lumenflow-config.js +7 -7
  19. package/dist/micro-worktree.d.ts +2 -2
  20. package/dist/micro-worktree.js +112 -77
  21. package/dist/orchestration-rules.js +1 -1
  22. package/dist/prompt-linter.js +5 -5
  23. package/dist/spawn-escalation.d.ts +2 -2
  24. package/dist/spawn-escalation.js +7 -3
  25. package/dist/spawn-monitor.js +3 -1
  26. package/dist/state-doctor-core.d.ts +29 -1
  27. package/dist/state-doctor-core.js +142 -2
  28. package/dist/telemetry.js +3 -3
  29. package/dist/template-loader.js +6 -3
  30. package/dist/test-baseline.d.ts +2 -2
  31. package/dist/test-baseline.js +3 -2
  32. package/dist/wu-constants.d.ts +69 -57
  33. package/dist/wu-constants.js +70 -8
  34. package/dist/wu-create-validators.d.ts +7 -0
  35. package/dist/wu-create-validators.js +12 -2
  36. package/dist/wu-done-concurrent-merge.js +2 -2
  37. package/dist/wu-done-metadata.js +2 -2
  38. package/dist/wu-done-validation.js +2 -2
  39. package/dist/wu-done-worktree.js +6 -5
  40. package/dist/wu-events-cleanup.js +2 -5
  41. package/dist/wu-list.d.ts +92 -0
  42. package/dist/wu-list.js +177 -0
  43. package/dist/wu-paths.js +4 -4
  44. package/dist/wu-spawn-context.js +3 -2
  45. package/dist/wu-spawn-skills.js +8 -5
  46. package/dist/wu-spawn.js +4 -5
  47. package/package.json +2 -2
  48. package/dist/beacon-migration.d.ts +0 -56
  49. package/dist/beacon-migration.js +0 -101
@@ -1066,6 +1066,7 @@ export const DIRECTORIES = {
1066
1066
  WORKTREES: 'worktrees/',
1067
1067
  AI: 'ai/',
1068
1068
  CLAUDE: '.claude/',
1069
+ CLAUDE_HOOKS: '.claude/hooks/',
1069
1070
  DOCS: 'docs/',
1070
1071
  PACKAGES: 'packages/',
1071
1072
  TOOLS: 'tools/',
@@ -1076,6 +1077,43 @@ export const DIRECTORIES = {
1076
1077
  BACKLOG_PATH: 'docs/04-operations/tasks/backlog.md',
1077
1078
  STATUS_PATH: 'docs/04-operations/tasks/status.md',
1078
1079
  };
1080
+ /**
1081
+ * Claude Code hook script constants (WU-1394)
1082
+ *
1083
+ * Centralized constants for Claude Code enforcement and recovery hooks.
1084
+ * Used by enforcement-generator.ts, enforcement-sync.ts, and init.ts.
1085
+ *
1086
+ * @see packages/@lumenflow/cli/src/hooks/enforcement-generator.ts
1087
+ * @see packages/@lumenflow/cli/src/hooks/enforcement-sync.ts
1088
+ */
1089
+ export const CLAUDE_HOOKS = {
1090
+ /** Hook script filenames */
1091
+ SCRIPTS: {
1092
+ ENFORCE_WORKTREE: 'enforce-worktree.sh',
1093
+ REQUIRE_WU: 'require-wu.sh',
1094
+ WARN_INCOMPLETE: 'warn-incomplete.sh',
1095
+ PRE_COMPACT_CHECKPOINT: 'pre-compact-checkpoint.sh',
1096
+ SESSION_START_RECOVERY: 'session-start-recovery.sh',
1097
+ },
1098
+ /** Hook command path prefix (uses Claude Code's $CLAUDE_PROJECT_DIR variable) */
1099
+ PATH_PREFIX: '$CLAUDE_PROJECT_DIR/.claude/hooks',
1100
+ /** Hook matchers for settings.json */
1101
+ MATCHERS: {
1102
+ ALL: '.*',
1103
+ WRITE_EDIT: 'Write|Edit',
1104
+ COMPACT: 'compact',
1105
+ RESUME: 'resume',
1106
+ CLEAR: 'clear',
1107
+ },
1108
+ /** Template paths (relative to templates directory) */
1109
+ TEMPLATES: {
1110
+ SETTINGS: 'vendors/claude/.claude/settings.json.template',
1111
+ PRE_COMPACT: 'vendors/claude/.claude/hooks/pre-compact-checkpoint.sh',
1112
+ SESSION_START: 'vendors/claude/.claude/hooks/session-start-recovery.sh',
1113
+ },
1114
+ };
1115
+ /** Build full hook command path from script name */
1116
+ export const getHookCommand = (scriptName) => `${CLAUDE_HOOKS.PATH_PREFIX}/${scriptName}`;
1079
1117
  /**
1080
1118
  * ESLint cache strategy values
1081
1119
  */
@@ -1171,8 +1209,8 @@ export const GIT_COMMAND_STRINGS = {
1171
1209
  export const PATH_PATTERNS = {
1172
1210
  /** Matches WU YAML paths in both legacy and current locations */
1173
1211
  WU_YAML: /(?:memory-bank|docs\/04-operations)\/tasks\/wu\/(WU-\d+)\.ya?ml$/i,
1174
- /** Matches stamp file paths (supports both .beacon and .lumenflow for migration) */
1175
- STAMP: /\.(?:beacon|lumenflow)\/stamps\/(WU-\d+)\.done$/i,
1212
+ /** Matches stamp file paths */
1213
+ STAMP: /\.lumenflow\/stamps\/(WU-\d+)\.done$/i,
1176
1214
  };
1177
1215
  /**
1178
1216
  * Common shell commands
@@ -1286,8 +1324,6 @@ export const LOCK_DIR_NAME = '.lumenflow-locks';
1286
1324
  *
1287
1325
  * Centralized paths for .lumenflow directory structure to eliminate hardcoded strings.
1288
1326
  * Used by telemetry, agent-session, agent-incidents, memory, and commands-logger modules.
1289
- *
1290
- * @since 1.4.0 Renamed from BEACON_PATHS (WU-1075)
1291
1327
  */
1292
1328
  export const LUMENFLOW_PATHS = {
1293
1329
  /** Base directory for all LumenFlow runtime data */
@@ -1296,6 +1332,8 @@ export const LUMENFLOW_PATHS = {
1296
1332
  STATE_DIR: '.lumenflow/state',
1297
1333
  /** Stamp directory (WU completion markers) */
1298
1334
  STAMPS_DIR: '.lumenflow/stamps',
1335
+ /** Archive directory for old WU events (WU-1430) */
1336
+ ARCHIVE_DIR: '.lumenflow/archive',
1299
1337
  /** Merge lock file (runtime coordination, WU-1747) */
1300
1338
  MERGE_LOCK: '.lumenflow/merge.lock',
1301
1339
  /** Base telemetry directory */
@@ -1326,6 +1364,30 @@ export const LUMENFLOW_PATHS = {
1326
1364
  LOCKS_DIR: '.lumenflow/locks',
1327
1365
  /** Force bypass audit log */
1328
1366
  FORCE_BYPASSES: '.lumenflow/force-bypasses.log',
1367
+ /** Test baseline file for ratchet pattern (WU-1430) */
1368
+ TEST_BASELINE: '.lumenflow/test-baseline.json',
1369
+ /** Templates directory (WU-1430) */
1370
+ TEMPLATES_DIR: '.lumenflow/templates',
1371
+ /** Spawn prompt templates (WU-1430) */
1372
+ SPAWN_PROMPT_DIR: '.lumenflow/templates/spawn-prompt',
1373
+ /** Template manifest file (WU-1430) */
1374
+ TEMPLATE_MANIFEST: '.lumenflow/templates/manifest.yaml',
1375
+ /** Skills directory for agent skills (WU-1430) */
1376
+ SKILLS_DIR: '.lumenflow/skills',
1377
+ /** Agents directory for agent definitions (WU-1430) */
1378
+ AGENTS_DIR: '.lumenflow/agents',
1379
+ /** Methodology log for spawn telemetry (WU-1430) */
1380
+ METHODOLOGY_LOG: '.lumenflow/telemetry/methodology.ndjson',
1381
+ /** Prompt metrics cache (WU-1430) */
1382
+ PROMPT_METRICS: '.lumenflow/telemetry/prompt-metrics.json',
1383
+ /** Prompt lint results (WU-1430) */
1384
+ PROMPT_LINT: '.lumenflow/telemetry/prompt-lint.ndjson',
1385
+ /** Recovery markers directory (WU-1430) */
1386
+ RECOVERY_DIR: '.lumenflow/recovery',
1387
+ /** Checkpoints directory (WU-1430) */
1388
+ CHECKPOINTS_DIR: '.lumenflow/checkpoints',
1389
+ /** Cache directory under user home (WU-1430) */
1390
+ HOME_CACHE: 'cache',
1329
1391
  /**
1330
1392
  * WU-1174: Runtime lock directory for merge/cleanup locks
1331
1393
  *
@@ -1340,10 +1402,6 @@ export const LUMENFLOW_PATHS = {
1340
1402
  */
1341
1403
  LOCK_DIR: path.join(tmpdir(), LOCK_DIR_NAME),
1342
1404
  };
1343
- /**
1344
- * @deprecated Use LUMENFLOW_PATHS instead. Will be removed in v2.0.
1345
- */
1346
- export const BEACON_PATHS = LUMENFLOW_PATHS;
1347
1405
  /**
1348
1406
  * File extensions
1349
1407
  *
@@ -1401,6 +1459,10 @@ export const PATH_LITERALS = {
1401
1459
  PLAN_FILE_SUFFIX: '-plan.md',
1402
1460
  /** Trailing slash regex pattern */
1403
1461
  TRAILING_SLASH_REGEX: /\/+$/,
1462
+ /** .lumenflow path prefix for internal path detection (WU-1430) */
1463
+ LUMENFLOW_PREFIX: '.lumenflow/',
1464
+ /** Current directory prefix for repo-internal paths (WU-1430) */
1465
+ CURRENT_DIR_PREFIX: './',
1404
1466
  };
1405
1467
  /**
1406
1468
  * Slice lengths for path operations
@@ -86,6 +86,13 @@ export declare function validateSpecRefs(specRefs: string[]): {
86
86
  * @returns {boolean} True if any spec_ref is an external path
87
87
  */
88
88
  export declare function hasExternalSpecRefs(specRefs: string[]): boolean;
89
+ /**
90
+ * WU-1429: Check if spec_refs is non-empty
91
+ *
92
+ * @param {string[]|undefined} specRefs - Array of spec reference paths
93
+ * @returns {boolean} True if spec_refs contains at least one entry
94
+ */
95
+ export declare function hasSpecRefs(specRefs: string[] | undefined): boolean;
89
96
  /**
90
97
  * WU-1062: Normalize all spec_refs paths
91
98
  *
@@ -15,10 +15,11 @@
15
15
  * No external library exists for LumenFlow lane inference validation.
16
16
  */
17
17
  import { isExternalPath, normalizeSpecRef } from './lumenflow-home.js';
18
+ import { PATH_LITERALS } from './wu-constants.js';
18
19
  /** Confidence threshold for showing suggestion (percentage) */
19
20
  const CONFIDENCE_THRESHOLD_LOW = 30;
20
- /** Prefixes that indicate repo-internal paths (WU-1069) */
21
- const REPO_INTERNAL_PREFIXES = ['./', '.lumenflow/'];
21
+ /** Prefixes that indicate repo-internal paths (WU-1069, WU-1430: Use centralized constants) */
22
+ const REPO_INTERNAL_PREFIXES = [PATH_LITERALS.CURRENT_DIR_PREFIX, PATH_LITERALS.LUMENFLOW_PREFIX];
22
23
  /**
23
24
  * WU-1069: Check if a path is a repo-internal path that should be rejected
24
25
  *
@@ -186,6 +187,15 @@ export function hasExternalSpecRefs(specRefs) {
186
187
  }
187
188
  return specRefs.some((ref) => isExternalPath(ref));
188
189
  }
190
+ /**
191
+ * WU-1429: Check if spec_refs is non-empty
192
+ *
193
+ * @param {string[]|undefined} specRefs - Array of spec reference paths
194
+ * @returns {boolean} True if spec_refs contains at least one entry
195
+ */
196
+ export function hasSpecRefs(specRefs) {
197
+ return Array.isArray(specRefs) && specRefs.length > 0;
198
+ }
189
199
  /**
190
200
  * WU-1062: Normalize all spec_refs paths
191
201
  *
@@ -18,7 +18,7 @@ import { validateWUEvent } from './wu-state-schema.js';
18
18
  import { generateBacklog, generateStatus } from './backlog-generator.js';
19
19
  import { getStateStoreDirFromBacklog } from './wu-paths.js';
20
20
  import { getGitForCwd } from './git-adapter.js';
21
- import { REMOTES, BRANCHES, BEACON_PATHS } from './wu-constants.js';
21
+ import { REMOTES, BRANCHES, LUMENFLOW_PATHS } from './wu-constants.js';
22
22
  /**
23
23
  * Creates a unique key for an event to detect duplicates.
24
24
  * Events are considered identical if they have the same type, wuId, and timestamp.
@@ -46,7 +46,7 @@ export async function fetchMainEventsContent() {
46
46
  console.warn('[wu-done] Warning: Could not fetch latest main, using cached version');
47
47
  }
48
48
  // Try to read wu-events.jsonl from origin/main
49
- const eventsPath = `${BEACON_PATHS.STATE_DIR}/${WU_EVENTS_FILE_NAME}`;
49
+ const eventsPath = `${LUMENFLOW_PATHS.STATE_DIR}/${WU_EVENTS_FILE_NAME}`;
50
50
  const content = await git.raw(['show', `${REMOTES.ORIGIN}/${BRANCHES.MAIN}:${eventsPath}`]);
51
51
  return content;
52
52
  }
@@ -11,7 +11,7 @@ import { moveWUToDoneBacklog } from './wu-backlog-updater.js';
11
11
  import { createStamp } from './stamp-utils.js';
12
12
  import { WU_EVENTS_FILE_NAME } from './wu-state-store.js';
13
13
  import { computeWUYAMLContent, computeStatusContentFromMergedState, computeBacklogContent, computeWUEventsContentAfterComplete, computeStampContent, } from './wu-transaction-collectors.js';
14
- import { DEFAULTS, LOG_PREFIX, EMOJI, PKG_MANAGER, SCRIPTS, PRETTIER_FLAGS, BEACON_PATHS, } from './wu-constants.js';
14
+ import { DEFAULTS, LOG_PREFIX, EMOJI, PKG_MANAGER, SCRIPTS, PRETTIER_FLAGS, LUMENFLOW_PATHS, } from './wu-constants.js';
15
15
  import { applyExposureDefaults } from './wu-done-validation.js';
16
16
  import { createFileNotFoundError, createValidationError } from './wu-done-errors.js';
17
17
  import { writeWU } from './wu-yaml.js';
@@ -170,7 +170,7 @@ export async function stageAndFormatMetadata({ id, wuPath, statusPath, backlogPa
170
170
  // The singleton git adapter captures cwd at import time, which is wrong after process.chdir()
171
171
  const gitCwd = getGitForCwd();
172
172
  // Stage files
173
- const wuEventsPath = path.join(BEACON_PATHS.STATE_DIR, WU_EVENTS_FILE_NAME);
173
+ const wuEventsPath = path.join(LUMENFLOW_PATHS.STATE_DIR, WU_EVENTS_FILE_NAME);
174
174
  const filesToStage = [wuPath, statusPath, backlogPath, stampsDir];
175
175
  if (existsSync(wuEventsPath)) {
176
176
  filesToStage.push(wuEventsPath);
@@ -261,7 +261,7 @@ const DOCS_ONLY_ALLOWED_PATTERNS = [
261
261
  /^memory-bank\//i,
262
262
  /^docs\//i,
263
263
  /\.md$/i,
264
- /^\.(?:beacon|lumenflow)\/stamps\//i, // Support both legacy .beacon and .lumenflow
264
+ /^\.lumenflow\/stamps\//i,
265
265
  /^\.claude\//i,
266
266
  /^ai\//i,
267
267
  /^README\.md$/i,
@@ -333,7 +333,7 @@ Allowed paths for documentation WUs:
333
333
  - ai/
334
334
  - .claude/
335
335
  - memory-bank/
336
- - .lumenflow/stamps/ (or legacy .beacon/stamps/)
336
+ - .lumenflow/stamps/
337
337
  - *.md files
338
338
 
339
339
  After fixing, retry: pnpm wu:done --id ${id}
@@ -24,7 +24,7 @@ import { generateCommitMessage, collectMetadataToTransaction, stageAndFormatMeta
24
24
  import { getGitForCwd } from './git-adapter.js';
25
25
  import { readWU, writeWU } from './wu-yaml.js';
26
26
  import { WU_PATHS } from './wu-paths.js';
27
- import { BRANCHES, REMOTES, THRESHOLDS, LOG_PREFIX, EMOJI, COMMIT_FORMATS, BOX, STRING_LITERALS, WU_STATUS, GIT_COMMANDS, GIT_FLAGS, } from './wu-constants.js';
27
+ import { BRANCHES, REMOTES, THRESHOLDS, LOG_PREFIX, EMOJI, COMMIT_FORMATS, BOX, STRING_LITERALS, WU_STATUS, GIT_COMMANDS, GIT_FLAGS, LUMENFLOW_PATHS, } from './wu-constants.js';
28
28
  import { RECOVERY, REBASE, PREFLIGHT, MERGE } from './wu-done-messages.js';
29
29
  import { getDriftLevel, DRIFT_LEVELS } from './branch-drift.js';
30
30
  import { createError, ErrorCodes } from './error-handler.js';
@@ -242,7 +242,7 @@ export async function executeWorktreeCompletion(context) {
242
242
  // WU-2310: Capture file state before transaction commit
243
243
  // This allows rollback if git commit fails AFTER files are written
244
244
  // Note: We use the relative paths since we're already chdir'd into the worktree
245
- const workingEventsPath = path.join('.lumenflow', 'state', WU_EVENTS_FILE_NAME);
245
+ const workingEventsPath = path.join(LUMENFLOW_PATHS.STATE_DIR, WU_EVENTS_FILE_NAME);
246
246
  const pathsToSnapshot = [
247
247
  workingWUPath,
248
248
  workingStatusPath,
@@ -580,13 +580,14 @@ export async function checkBranchDrift(branch) {
580
580
  * that would break if paths are rearranged.
581
581
  */
582
582
  const APPEND_ONLY_FILES = [
583
- // State store events file (append-only by design)
584
- path.join('.lumenflow', 'state', WU_EVENTS_FILE_NAME),
583
+ // State store events file (append-only by design) - WU-1430: Use centralized constant
584
+ path.join(LUMENFLOW_PATHS.STATE_DIR, WU_EVENTS_FILE_NAME),
585
585
  // Status and backlog are generated from state store but may conflict during rebase
586
586
  WU_PATHS.STATUS(),
587
587
  WU_PATHS.BACKLOG(),
588
588
  ];
589
- const WU_EVENTS_PATH = path.join('.lumenflow', 'state', WU_EVENTS_FILE_NAME);
589
+ // WU-1430: Use centralized constant
590
+ const WU_EVENTS_PATH = path.join(LUMENFLOW_PATHS.STATE_DIR, WU_EVENTS_FILE_NAME);
590
591
  function normalizeEventForKey(event) {
591
592
  const normalized = {};
592
593
  for (const key of Object.keys(event).sort()) {
@@ -29,12 +29,9 @@ import { WUStateStore, WU_EVENTS_FILE_NAME } from './wu-state-store.js';
29
29
  */
30
30
  const ONE_DAY_MS = 24 * 60 * 60 * 1000;
31
31
  /**
32
- * LumenFlow paths for state and archive
32
+ * Import centralized path constants (WU-1430)
33
33
  */
34
- const LUMENFLOW_PATHS = {
35
- STATE_DIR: '.lumenflow/state',
36
- ARCHIVE_DIR: '.lumenflow/archive',
37
- };
34
+ import { LUMENFLOW_PATHS } from './wu-constants.js';
38
35
  /**
39
36
  * Default event archival configuration
40
37
  */
@@ -0,0 +1,92 @@
1
+ /**
2
+ * WU List Helper (WU-1411)
3
+ *
4
+ * Provides a consistent list of WUs by merging WUStateStore with YAML metadata.
5
+ * MCP server and other tools can use this instead of duplicating listing logic.
6
+ *
7
+ * Key behaviors:
8
+ * - Status from state store takes precedence over YAML status (more current)
9
+ * - Falls back to YAML status when WU is not in state store
10
+ * - Supports filtering by status and lane
11
+ * - Gracefully handles errors (missing files, invalid YAML)
12
+ *
13
+ * @module wu-list
14
+ * @see {@link ./wu-state-store.ts} - State store for runtime status
15
+ * @see {@link ./wu-yaml.ts} - YAML operations
16
+ * @see {@link ./wu-paths.ts} - Path utilities
17
+ */
18
+ /**
19
+ * WU list entry returned by listWUs.
20
+ * Contains essential fields for display and filtering.
21
+ */
22
+ export interface WUListEntry {
23
+ /** WU identifier (e.g., 'WU-100') */
24
+ id: string;
25
+ /** Short title describing the work */
26
+ title: string;
27
+ /** Lane assignment (e.g., 'Framework: Core') */
28
+ lane: string;
29
+ /** Current status (from state store or YAML) */
30
+ status: string;
31
+ /** Work type (feature, bug, documentation, etc.) */
32
+ type: string;
33
+ /** Priority level (P0-P3) */
34
+ priority: string;
35
+ /** Parent initiative reference (optional) */
36
+ initiative?: string;
37
+ /** Phase number within parent initiative (optional) */
38
+ phase?: number;
39
+ /** Creation date (YYYY-MM-DD) */
40
+ created?: string;
41
+ }
42
+ /**
43
+ * Options for listWUs function.
44
+ */
45
+ export interface ListWUsOptions {
46
+ /** Project root directory (default: cwd) */
47
+ projectRoot?: string;
48
+ /** Filter by status (e.g., 'in_progress', 'blocked', 'done') */
49
+ status?: string;
50
+ /** Filter by lane (e.g., 'Framework: Core') */
51
+ lane?: string;
52
+ /**
53
+ * Direct path to WU directory (overrides config-based path).
54
+ * Useful for testing with virtual filesystems.
55
+ */
56
+ wuDir?: string;
57
+ /**
58
+ * Direct path to state directory (overrides config-based path).
59
+ * Useful for testing with virtual filesystems.
60
+ */
61
+ stateDir?: string;
62
+ }
63
+ /**
64
+ * Lists WUs by merging state store status with YAML metadata.
65
+ *
66
+ * The state store contains the most current status (claim, block, complete events),
67
+ * while YAML files contain the full metadata (title, lane, type, etc.).
68
+ *
69
+ * Status precedence:
70
+ * 1. State store status (if WU has events)
71
+ * 2. YAML status (fallback if not in state store)
72
+ *
73
+ * @param options - Listing options
74
+ * @returns Array of WU list entries
75
+ *
76
+ * @example
77
+ * // List all WUs
78
+ * const allWUs = await listWUs();
79
+ *
80
+ * @example
81
+ * // Filter by status
82
+ * const inProgress = await listWUs({ status: 'in_progress' });
83
+ *
84
+ * @example
85
+ * // Filter by lane
86
+ * const coreWUs = await listWUs({ lane: 'Framework: Core' });
87
+ *
88
+ * @example
89
+ * // Filter by both
90
+ * const blockedCore = await listWUs({ status: 'blocked', lane: 'Framework: Core' });
91
+ */
92
+ export declare function listWUs(options?: ListWUsOptions): Promise<WUListEntry[]>;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * WU List Helper (WU-1411)
3
+ *
4
+ * Provides a consistent list of WUs by merging WUStateStore with YAML metadata.
5
+ * MCP server and other tools can use this instead of duplicating listing logic.
6
+ *
7
+ * Key behaviors:
8
+ * - Status from state store takes precedence over YAML status (more current)
9
+ * - Falls back to YAML status when WU is not in state store
10
+ * - Supports filtering by status and lane
11
+ * - Gracefully handles errors (missing files, invalid YAML)
12
+ *
13
+ * @module wu-list
14
+ * @see {@link ./wu-state-store.ts} - State store for runtime status
15
+ * @see {@link ./wu-yaml.ts} - YAML operations
16
+ * @see {@link ./wu-paths.ts} - Path utilities
17
+ */
18
+ import { readdir } from 'node:fs/promises';
19
+ import { join } from 'node:path';
20
+ import { parse as parseYaml } from 'yaml';
21
+ import { readFile } from 'node:fs/promises';
22
+ import { WUStateStore } from './wu-state-store.js';
23
+ import { getConfig } from './lumenflow-config.js';
24
+ /**
25
+ * Lists WUs by merging state store status with YAML metadata.
26
+ *
27
+ * The state store contains the most current status (claim, block, complete events),
28
+ * while YAML files contain the full metadata (title, lane, type, etc.).
29
+ *
30
+ * Status precedence:
31
+ * 1. State store status (if WU has events)
32
+ * 2. YAML status (fallback if not in state store)
33
+ *
34
+ * @param options - Listing options
35
+ * @returns Array of WU list entries
36
+ *
37
+ * @example
38
+ * // List all WUs
39
+ * const allWUs = await listWUs();
40
+ *
41
+ * @example
42
+ * // Filter by status
43
+ * const inProgress = await listWUs({ status: 'in_progress' });
44
+ *
45
+ * @example
46
+ * // Filter by lane
47
+ * const coreWUs = await listWUs({ lane: 'Framework: Core' });
48
+ *
49
+ * @example
50
+ * // Filter by both
51
+ * const blockedCore = await listWUs({ status: 'blocked', lane: 'Framework: Core' });
52
+ */
53
+ export async function listWUs(options = {}) {
54
+ const { projectRoot = process.cwd(), status: filterStatus, lane: filterLane, wuDir: wuDirOverride, stateDir: stateDirOverride, } = options;
55
+ // Determine paths: use overrides if provided, otherwise use config
56
+ let wuDir;
57
+ let stateDir;
58
+ if (wuDirOverride && stateDirOverride) {
59
+ // Direct paths provided (e.g., for testing)
60
+ wuDir = wuDirOverride;
61
+ stateDir = stateDirOverride;
62
+ }
63
+ else {
64
+ // Get configuration for paths
65
+ const config = getConfig({ projectRoot });
66
+ wuDir = wuDirOverride ?? join(projectRoot, config.directories.wuDir);
67
+ stateDir = stateDirOverride ?? join(projectRoot, config.state.stateDir);
68
+ }
69
+ // Load state store for runtime statuses
70
+ const stateMap = await loadStateStore(stateDir);
71
+ // Read all WU YAML files
72
+ const wuEntries = await readWUYamlFiles(wuDir);
73
+ // Merge state store status with YAML data
74
+ const entries = [];
75
+ for (const yamlData of wuEntries) {
76
+ // Skip invalid entries
77
+ if (!yamlData.id || typeof yamlData.id !== 'string') {
78
+ continue;
79
+ }
80
+ const wuId = yamlData.id;
81
+ // Get status: state store takes precedence
82
+ const stateEntry = stateMap.get(wuId);
83
+ const yamlStatus = typeof yamlData.status === 'string' ? yamlData.status : 'ready';
84
+ const status = stateEntry?.status ?? yamlStatus;
85
+ // Get lane: prefer state store (more current), fall back to YAML
86
+ const yamlLane = typeof yamlData.lane === 'string' ? yamlData.lane : '';
87
+ const lane = stateEntry?.lane ?? yamlLane;
88
+ // Extract string fields with type guards
89
+ const yamlTitle = typeof yamlData.title === 'string' ? yamlData.title : '';
90
+ const yamlType = typeof yamlData.type === 'string' ? yamlData.type : 'feature';
91
+ const yamlPriority = typeof yamlData.priority === 'string' ? yamlData.priority : 'P2';
92
+ // Build entry
93
+ const entry = {
94
+ id: wuId,
95
+ title: stateEntry?.title ?? yamlTitle,
96
+ lane,
97
+ status,
98
+ type: yamlType,
99
+ priority: yamlPriority,
100
+ };
101
+ // Add optional fields if present with type guards
102
+ if (typeof yamlData.initiative === 'string') {
103
+ entry.initiative = yamlData.initiative;
104
+ }
105
+ if (typeof yamlData.phase === 'number') {
106
+ entry.phase = yamlData.phase;
107
+ }
108
+ if (typeof yamlData.created === 'string') {
109
+ entry.created = yamlData.created;
110
+ }
111
+ // Apply filters
112
+ if (filterStatus && status !== filterStatus) {
113
+ continue;
114
+ }
115
+ if (filterLane && lane !== filterLane) {
116
+ continue;
117
+ }
118
+ entries.push(entry);
119
+ }
120
+ return entries;
121
+ }
122
+ /**
123
+ * Loads state store and returns a map of WU ID to state entry.
124
+ * Returns empty map on errors (graceful degradation).
125
+ */
126
+ async function loadStateStore(stateDir) {
127
+ const map = new Map();
128
+ try {
129
+ const store = new WUStateStore(stateDir);
130
+ await store.load();
131
+ // Collect all known statuses
132
+ const statuses = ['in_progress', 'blocked', 'done', 'ready'];
133
+ for (const status of statuses) {
134
+ const wuIds = store.getByStatus(status);
135
+ for (const wuId of wuIds) {
136
+ const state = store.getWUState(wuId);
137
+ if (state) {
138
+ map.set(wuId, state);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ catch {
144
+ // Graceful degradation: return empty map on errors
145
+ }
146
+ return map;
147
+ }
148
+ /**
149
+ * Reads all WU YAML files from the WU directory.
150
+ * Skips invalid files (graceful degradation).
151
+ */
152
+ async function readWUYamlFiles(wuDir) {
153
+ const entries = [];
154
+ try {
155
+ const files = await readdir(wuDir);
156
+ for (const file of files) {
157
+ // Only process WU-*.yaml files
158
+ if (!file.startsWith('WU-') || !file.endsWith('.yaml')) {
159
+ continue;
160
+ }
161
+ try {
162
+ const content = await readFile(join(wuDir, file), 'utf-8');
163
+ const parsed = parseYaml(content);
164
+ if (parsed && typeof parsed === 'object') {
165
+ entries.push(parsed);
166
+ }
167
+ }
168
+ catch {
169
+ // Skip invalid YAML files
170
+ }
171
+ }
172
+ }
173
+ catch {
174
+ // Directory doesn't exist or can't be read
175
+ }
176
+ return entries;
177
+ }
package/dist/wu-paths.js CHANGED
@@ -45,7 +45,7 @@ export function resolveRepoRoot(absolutePath, depth) {
45
45
  export function getStateStoreDirFromBacklog(backlogPath) {
46
46
  const config = getConfig();
47
47
  const repoRoot = resolveRepoRoot(backlogPath, PATH_DEPTHS.BACKLOG);
48
- return path.join(repoRoot, config.beacon.stateDir);
48
+ return path.join(repoRoot, config.state.stateDir);
49
49
  }
50
50
  /**
51
51
  * Create WU paths object with configurable base paths
@@ -82,18 +82,18 @@ export function createWuPaths(options = {}) {
82
82
  * Get path to stamps directory
83
83
  * @returns Path to stamps directory
84
84
  */
85
- STAMPS_DIR: () => config.beacon.stampsDir,
85
+ STAMPS_DIR: () => config.state.stampsDir,
86
86
  /**
87
87
  * Get path to WU done stamp file
88
88
  * @param id - WU ID (e.g., 'WU-123')
89
89
  * @returns Path to stamp file
90
90
  */
91
- STAMP: (id) => path.join(config.beacon.stampsDir, `${id}.done`),
91
+ STAMP: (id) => path.join(config.state.stampsDir, `${id}.done`),
92
92
  /**
93
93
  * Get path to state directory
94
94
  * @returns Path to state directory
95
95
  */
96
- STATE_DIR: () => config.beacon.stateDir,
96
+ STATE_DIR: () => config.state.stateDir,
97
97
  /**
98
98
  * Get path to initiatives directory
99
99
  * @returns Path to initiatives directory
@@ -22,15 +22,16 @@
22
22
  */
23
23
  import { existsSync, statSync } from 'node:fs';
24
24
  import path from 'node:path';
25
+ import { LUMENFLOW_PATHS } from './wu-constants.js';
25
26
  /**
26
27
  * Default maximum context size in bytes (4KB)
27
28
  */
28
29
  const DEFAULT_MAX_SIZE = 4096;
29
30
  /**
30
- * Memory layer paths
31
+ * Memory layer paths (WU-1430: Use centralized constants)
31
32
  */
32
33
  const MEMORY_PATHS = {
33
- MEMORY_DIR: '.lumenflow/memory',
34
+ MEMORY_DIR: LUMENFLOW_PATHS.MEMORY_DIR,
34
35
  MEMORY_FILE: 'memory.jsonl',
35
36
  };
36
37
  /**
@@ -1,12 +1,15 @@
1
1
  import { existsSync } from 'node:fs';
2
+ import { LUMENFLOW_PATHS } from './wu-constants.js';
3
+ /** WU-1430: Compose known skills directories from centralized constants */
2
4
  const KNOWN_SKILLS_DIRS = [
3
- '.lumenflow/skills',
5
+ LUMENFLOW_PATHS.SKILLS_DIR,
4
6
  '.claude/skills',
5
7
  '.codex/skills',
6
8
  '.gemini/skills',
7
9
  ];
10
+ /** WU-1430: Compose known agents directories from centralized constants */
8
11
  const KNOWN_AGENTS_DIRS = [
9
- '.lumenflow/agents',
12
+ LUMENFLOW_PATHS.AGENTS_DIR,
10
13
  '.claude/agents',
11
14
  '.codex/agents',
12
15
  '.gemini/agents',
@@ -30,7 +33,7 @@ const CONTEXT_HINTS = {
30
33
  tddWorkflow: '- `tdd-workflow` — TDD is mandatory for feature/enhancement WUs',
31
34
  bugClassification: '- `bug-classification` — Bug severity assessment',
32
35
  lumenflowGates: '- `lumenflow-gates` — Tooling often affects gates',
33
- beaconCompliance: '- `beacon-compliance` — Intelligence lane requires Beacon validation',
36
+ llmCompliance: '- `llm-compliance` — Intelligence lane requires LLM validation',
34
37
  promptManagement: '- `prompt-management` — For prompt template work',
35
38
  frontendDesign: '- `frontend-design` — For UI component work',
36
39
  };
@@ -38,7 +41,7 @@ const ADDITIONAL_SKILLS_TABLE = `| Skill | Use When |
38
41
  |-------|----------|
39
42
  | lumenflow-gates | Gates fail, debugging format/lint/typecheck errors |
40
43
  | bug-classification | Bug discovered mid-WU, need priority classification |
41
- | beacon-compliance | Code touches LLM, prompts, classification |
44
+ | llm-compliance | Code touches LLM, prompts, classification |
42
45
  | prompt-management | Working with prompt templates, golden datasets |
43
46
  | frontend-design | Building UI components, pages |
44
47
  | initiative-management | Multi-phase projects, INIT-XXX coordination |
@@ -159,7 +162,7 @@ export function generateSkillsSelectionSection(doc, config, clientName) {
159
162
  contextHints.push(CONTEXT_HINTS.lumenflowGates);
160
163
  }
161
164
  if (laneParent === 'Intelligence') {
162
- contextHints.push(CONTEXT_HINTS.beaconCompliance);
165
+ contextHints.push(CONTEXT_HINTS.llmCompliance);
163
166
  contextHints.push(CONTEXT_HINTS.promptManagement);
164
167
  }
165
168
  if (laneParent === 'Experience') {