@lumenflow/cli 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 (31) hide show
  1. package/README.md +23 -2
  2. package/dist/__tests__/gates-integration-tests.test.js +112 -0
  3. package/dist/__tests__/init.test.js +225 -0
  4. package/dist/__tests__/safe-git.test.js +4 -4
  5. package/dist/__tests__/wu-create-required-fields.test.js +22 -0
  6. package/dist/__tests__/wu-create.test.js +72 -0
  7. package/dist/gates.js +6 -8
  8. package/dist/hooks/enforcement-generator.js +256 -5
  9. package/dist/hooks/enforcement-sync.js +52 -6
  10. package/dist/init.js +195 -2
  11. package/dist/mem-recover.js +221 -0
  12. package/dist/orchestrate-initiative.js +19 -1
  13. package/dist/state-doctor-fix.js +36 -1
  14. package/dist/state-doctor.js +10 -6
  15. package/dist/wu-create.js +37 -15
  16. package/dist/wu-recover.js +53 -2
  17. package/dist/wu-spawn.js +2 -2
  18. package/package.json +6 -6
  19. package/templates/core/.mcp.json.template +8 -0
  20. package/templates/core/LUMENFLOW.md.template +24 -0
  21. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +47 -0
  22. package/templates/core/ai/onboarding/lumenflow-force-usage.md.template +183 -0
  23. package/templates/core/ai/onboarding/quick-ref-commands.md.template +68 -55
  24. package/templates/core/ai/onboarding/release-process.md.template +58 -4
  25. package/templates/core/ai/onboarding/starting-prompt.md.template +67 -3
  26. package/templates/core/ai/onboarding/vendor-support.md.template +73 -0
  27. package/templates/core/scripts/safe-git.template +29 -0
  28. package/templates/vendors/claude/.claude/hooks/pre-compact-checkpoint.sh +102 -0
  29. package/templates/vendors/claude/.claude/hooks/session-start-recovery.sh +74 -0
  30. package/templates/vendors/claude/.claude/settings.json.template +42 -0
  31. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +23 -6
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Memory Recover CLI (WU-1390)
4
+ *
5
+ * Generate post-compaction recovery context for agents that have lost
6
+ * their LumenFlow instructions due to context compaction.
7
+ *
8
+ * Usage:
9
+ * pnpm mem:recover --wu WU-XXXX [options]
10
+ *
11
+ * Options:
12
+ * --max-size <bytes> Maximum output size in bytes (default: 2048)
13
+ * --format <json|human> Output format (default: human)
14
+ * --quiet Suppress header/footer output
15
+ *
16
+ * The recovery context includes:
17
+ * - Last checkpoint for the WU
18
+ * - Compact constraints (7 rules)
19
+ * - Essential CLI commands
20
+ * - Guidance to spawn fresh agent
21
+ *
22
+ * @see {@link packages/@lumenflow/memory/src/mem-recover-core.ts} - Core logic
23
+ * @see {@link packages/@lumenflow/memory/__tests__/mem-recover-core.test.ts} - Tests
24
+ */
25
+ import fs from 'node:fs/promises';
26
+ import path from 'node:path';
27
+ import { generateRecoveryContext } from '@lumenflow/memory/dist/mem-recover-core.js';
28
+ import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
29
+ import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
30
+ /**
31
+ * Log prefix for mem:recover output
32
+ */
33
+ const LOG_PREFIX = '[mem:recover]';
34
+ /**
35
+ * Tool name for audit logging
36
+ */
37
+ const TOOL_NAME = 'mem:recover';
38
+ /**
39
+ * Valid output formats
40
+ */
41
+ const VALID_FORMATS = ['json', 'human'];
42
+ /**
43
+ * CLI argument options specific to mem:recover
44
+ */
45
+ const CLI_OPTIONS = {
46
+ maxSize: {
47
+ name: 'maxSize',
48
+ flags: '-m, --max-size <bytes>',
49
+ description: 'Maximum output size in bytes (default: 2048)',
50
+ },
51
+ format: {
52
+ name: 'format',
53
+ flags: '-f, --format <format>',
54
+ description: 'Output format: json or human (default: human)',
55
+ },
56
+ baseDir: {
57
+ name: 'baseDir',
58
+ flags: '-d, --base-dir <path>',
59
+ description: 'Base directory (defaults to current directory)',
60
+ },
61
+ quiet: {
62
+ name: 'quiet',
63
+ flags: '-q, --quiet',
64
+ description: 'Suppress header/footer output, only show recovery context',
65
+ },
66
+ };
67
+ /**
68
+ * Write audit log entry for tool execution
69
+ */
70
+ async function writeAuditLog(baseDir, entry) {
71
+ try {
72
+ const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
73
+ const logDir = path.dirname(logPath);
74
+ await fs.mkdir(logDir, { recursive: true });
75
+ const line = `${JSON.stringify(entry)}\n`;
76
+ await fs.appendFile(logPath, line, 'utf-8');
77
+ }
78
+ catch {
79
+ // Audit logging is non-fatal - silently ignore errors
80
+ }
81
+ }
82
+ /**
83
+ * Validate and parse a positive integer argument
84
+ */
85
+ function parsePositiveInt(value, optionName) {
86
+ if (!value) {
87
+ return undefined;
88
+ }
89
+ const parsed = parseInt(value, 10);
90
+ if (isNaN(parsed) || parsed <= 0) {
91
+ throw new Error(`Invalid ${optionName} value: "${value}". Must be a positive integer.`);
92
+ }
93
+ return parsed;
94
+ }
95
+ /**
96
+ * Validate format argument
97
+ */
98
+ function validateFormat(format) {
99
+ if (!format) {
100
+ return 'human';
101
+ }
102
+ if (!VALID_FORMATS.includes(format)) {
103
+ throw new Error(`Invalid --format value: "${format}". Valid formats: ${VALID_FORMATS.join(', ')}`);
104
+ }
105
+ return format;
106
+ }
107
+ /**
108
+ * Print output in human-readable format
109
+ */
110
+ function printHumanFormat(result, wuId, quiet) {
111
+ if (!quiet) {
112
+ console.log(`${LOG_PREFIX} Recovery context for ${wuId}:`);
113
+ console.log('');
114
+ }
115
+ console.log(result.context);
116
+ if (!quiet) {
117
+ console.log('');
118
+ console.log(`${LOG_PREFIX} ${result.size} bytes${result.truncated ? ' (truncated)' : ''}`);
119
+ }
120
+ }
121
+ /**
122
+ * Print output in JSON format
123
+ */
124
+ function printJsonFormat(result, wuId) {
125
+ const output = {
126
+ wuId,
127
+ context: result.context,
128
+ size: result.size,
129
+ truncated: result.truncated,
130
+ };
131
+ console.log(JSON.stringify(output, null, 2));
132
+ }
133
+ /**
134
+ * Main CLI entry point
135
+ */
136
+ async function main() {
137
+ const args = createWUParser({
138
+ name: 'mem-recover',
139
+ description: 'Generate post-compaction recovery context for agents',
140
+ options: [
141
+ WU_OPTIONS.wu,
142
+ CLI_OPTIONS.maxSize,
143
+ CLI_OPTIONS.format,
144
+ CLI_OPTIONS.baseDir,
145
+ CLI_OPTIONS.quiet,
146
+ ],
147
+ required: ['wu'],
148
+ });
149
+ const baseDir = args.baseDir || process.cwd();
150
+ const startedAt = new Date().toISOString();
151
+ const startTime = Date.now();
152
+ let maxSize;
153
+ let format;
154
+ // Validate arguments
155
+ try {
156
+ maxSize = parsePositiveInt(args.maxSize, '--max-size');
157
+ format = validateFormat(args.format);
158
+ }
159
+ catch (err) {
160
+ const error = err;
161
+ console.error(`${LOG_PREFIX} Error: ${error.message}`);
162
+ process.exit(EXIT_CODES.ERROR);
163
+ }
164
+ let result;
165
+ let error = null;
166
+ try {
167
+ result = await generateRecoveryContext({
168
+ wuId: args.wu,
169
+ baseDir,
170
+ maxSize,
171
+ });
172
+ }
173
+ catch (err) {
174
+ const e = err;
175
+ error = e.message;
176
+ result = {
177
+ success: false,
178
+ context: '',
179
+ size: 0,
180
+ truncated: false,
181
+ };
182
+ }
183
+ const durationMs = Date.now() - startTime;
184
+ // Write audit log entry
185
+ await writeAuditLog(baseDir, {
186
+ tool: TOOL_NAME,
187
+ status: error ? 'failed' : 'success',
188
+ startedAt,
189
+ completedAt: new Date().toISOString(),
190
+ durationMs,
191
+ input: {
192
+ baseDir,
193
+ wuId: args.wu,
194
+ maxSize,
195
+ format,
196
+ quiet: args.quiet,
197
+ },
198
+ output: result.success
199
+ ? {
200
+ contextSize: result.size,
201
+ truncated: result.truncated,
202
+ }
203
+ : null,
204
+ error: error ? { message: error } : null,
205
+ });
206
+ if (error) {
207
+ console.error(`${LOG_PREFIX} Error: ${error}`);
208
+ process.exit(EXIT_CODES.ERROR);
209
+ }
210
+ // Print output based on format
211
+ if (format === 'json') {
212
+ printJsonFormat(result, args.wu);
213
+ }
214
+ else {
215
+ printHumanFormat(result, args.wu, !!args.quiet);
216
+ }
217
+ }
218
+ main().catch((e) => {
219
+ console.error(`${LOG_PREFIX} ${e.message}`);
220
+ process.exit(EXIT_CODES.ERROR);
221
+ });
@@ -91,7 +91,25 @@ const program = new Command()
91
91
  console.log(formatExecutionPlan(initiative, plan));
92
92
  if (dryRun) {
93
93
  console.log(chalk.yellow(`${LOG_PREFIX} Dry run mode - no agents spawned`));
94
- console.log(chalk.cyan('To execute this plan, remove the --dry-run flag.'));
94
+ console.log('');
95
+ console.log(chalk.bold('Next Steps (Recommended Defaults):'));
96
+ console.log('');
97
+ console.log(chalk.cyan(' Option 1 (Recommended): Checkpoint-per-wave mode'));
98
+ console.log(' pnpm orchestrate:initiative -i ' + initIds[0] + ' -c');
99
+ console.log(' Best for: Large initiatives, context management, idempotent resumption');
100
+ console.log('');
101
+ console.log(chalk.cyan(' Option 2: Full execution (polling mode)'));
102
+ console.log(' pnpm orchestrate:initiative -i ' + initIds[0]);
103
+ console.log(' Best for: Small initiatives (<4 WUs), quick execution');
104
+ console.log('');
105
+ console.log(chalk.cyan(' Option 3: Manual spawn per WU'));
106
+ console.log(' pnpm wu:spawn --id <WU-ID> --client claude-code');
107
+ console.log(' Best for: Testing, debugging, single WU execution');
108
+ console.log('');
109
+ console.log(chalk.bold('Monitoring Commands:'));
110
+ console.log(' pnpm mem:inbox --since 10m # Check for signals from agents');
111
+ console.log(' pnpm orchestrate:init-status -i ' + initIds[0] + ' # Check progress');
112
+ console.log(' pnpm orchestrate:monitor # Live agent activity');
95
113
  return;
96
114
  }
97
115
  // WU-1202: Output spawn XML for actual execution (not dry-run)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * State Doctor Fix Operations (WU-1230)
2
+ * State Doctor Fix Operations (WU-1230, WU-1420)
3
3
  *
4
4
  * Provides fix dependencies for state:doctor --fix that use micro-worktree
5
5
  * isolation for all tracked file changes. This ensures:
@@ -8,6 +8,7 @@
8
8
  * 2. Removal of stale WU references from backlog.md and status.md
9
9
  * 3. All changes pushed via merge, not direct file modification
10
10
  * 4. WU-1362: Retry logic for push failures (inherited from withMicroWorktree)
11
+ * 5. WU-1420: Emit corrective events to reconcile YAML vs state store mismatches
11
12
  *
12
13
  * Retry behavior is configured via .lumenflow.config.yaml git.push_retry section.
13
14
  * Default: 3 retries with exponential backoff and jitter.
@@ -72,6 +73,9 @@ async function readFileSafe(filePath) {
72
73
  * WU-1230: All file modifications happen in a micro-worktree and are pushed
73
74
  * to origin/main via merge. This prevents direct modifications to local main.
74
75
  *
76
+ * WU-1420: Includes emitEvent for fixing status mismatches by emitting
77
+ * corrective events (release, complete) to reconcile state store with YAML.
78
+ *
75
79
  * @param baseDir - Project base directory
76
80
  * @returns Partial StateDoctorDeps with fix operations
77
81
  */
@@ -190,5 +194,36 @@ export function createStateDoctorFixDeps(_baseDir) {
190
194
  },
191
195
  });
192
196
  },
197
+ /**
198
+ * Emit a corrective event to fix status mismatch (WU-1420)
199
+ *
200
+ * Appends a release or complete event to wu-events.jsonl to reconcile
201
+ * the state store with the WU YAML status.
202
+ */
203
+ emitEvent: async (event) => {
204
+ await withMicroWorktree({
205
+ operation: OPERATION_NAME,
206
+ id: `emit-event-${event.wuId.toLowerCase()}-${event.type}`,
207
+ logPrefix: LOG_PREFIX,
208
+ pushOnly: true,
209
+ execute: async ({ worktreePath }) => {
210
+ const eventsPath = path.join(worktreePath, WU_EVENTS_FILE);
211
+ // Build the event object
212
+ const eventLine = JSON.stringify({
213
+ wuId: event.wuId,
214
+ type: event.type,
215
+ reason: event.reason,
216
+ timestamp: event.timestamp || new Date().toISOString(),
217
+ });
218
+ // Append to events file (create if needed)
219
+ await fs.mkdir(path.dirname(eventsPath), { recursive: true });
220
+ await fs.appendFile(eventsPath, eventLine + '\n', 'utf-8');
221
+ return {
222
+ commitMessage: `fix(state-doctor): emit ${event.type} event for ${event.wuId} to reconcile state`,
223
+ files: [WU_EVENTS_FILE],
224
+ };
225
+ },
226
+ });
227
+ },
193
228
  };
194
229
  }
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * State Doctor CLI (WU-1209)
3
+ * State Doctor CLI (WU-1209, WU-1420)
4
4
  *
5
5
  * Integrity checker for LumenFlow state that detects:
6
6
  * - Orphaned WUs (done status but no stamp)
7
7
  * - Dangling signals (reference non-existent WUs)
8
8
  * - Broken memory relationships (events for missing WU specs)
9
+ * - Status mismatches between WU YAML and state store (WU-1420)
9
10
  *
10
11
  * Inspired by Beads bd doctor command.
11
12
  *
@@ -171,7 +172,7 @@ async function createDeps(baseDir) {
171
172
  */
172
173
  listStamps: async () => {
173
174
  try {
174
- const stampsDir = path.join(baseDir, config.beacon.stampsDir);
175
+ const stampsDir = path.join(baseDir, config.state.stampsDir);
175
176
  const stampFiles = await fg('WU-*.done', { cwd: stampsDir });
176
177
  return stampFiles.map((file) => file.replace('.done', ''));
177
178
  }
@@ -321,6 +322,8 @@ function getIssueTypeLabel(type) {
321
322
  return 'Dangling Signal';
322
323
  case ISSUE_TYPES.BROKEN_EVENT:
323
324
  return 'Broken Event';
325
+ case ISSUE_TYPES.STATUS_MISMATCH:
326
+ return 'Status Mismatch';
324
327
  default:
325
328
  return type;
326
329
  }
@@ -330,10 +333,11 @@ function getIssueTypeLabel(type) {
330
333
  */
331
334
  function printSummary(result) {
332
335
  console.log('=== Summary ===');
333
- console.log(` Orphaned WUs: ${result.summary.orphanedWUs}`);
334
- console.log(` Dangling Signals: ${result.summary.danglingSignals}`);
335
- console.log(` Broken Events: ${result.summary.brokenEvents}`);
336
- console.log(` Total Issues: ${result.summary.totalIssues}`);
336
+ console.log(` Orphaned WUs: ${result.summary.orphanedWUs}`);
337
+ console.log(` Dangling Signals: ${result.summary.danglingSignals}`);
338
+ console.log(` Broken Events: ${result.summary.brokenEvents}`);
339
+ console.log(` Status Mismatches: ${result.summary.statusMismatches}`);
340
+ console.log(` Total Issues: ${result.summary.totalIssues}`);
337
341
  }
338
342
  /**
339
343
  * Print fixed issues section
package/dist/wu-create.js CHANGED
@@ -43,7 +43,7 @@ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
43
43
  import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
44
44
  import { validateWU } from '@lumenflow/core/dist/wu-schema.js';
45
45
  import { getPlanPath, getPlanProtocolRef, getPlansDir, } from '@lumenflow/core/dist/lumenflow-home.js';
46
- import { validateSpecRefs } from '@lumenflow/core/dist/wu-create-validators.js';
46
+ import { hasSpecRefs, validateSpecRefs } from '@lumenflow/core/dist/wu-create-validators.js';
47
47
  import { COMMIT_FORMATS, FILE_SYSTEM, READINESS_UI, STRING_LITERALS, } from '@lumenflow/core/dist/wu-constants.js';
48
48
  // WU-1593: Use centralized validateWUIDFormat (DRY)
49
49
  import { ensureOnMain, validateWUIDFormat } from '@lumenflow/core/dist/wu-helpers.js';
@@ -114,6 +114,21 @@ export function warnIfBetterLaneExists(providedLane, codePathsArray, title, desc
114
114
  // Non-blocking - if inference fails, continue silently
115
115
  }
116
116
  }
117
+ export function collectInitiativeWarnings({ initiativeId, initiativeDoc, phase, specRefs, }) {
118
+ const warnings = [];
119
+ const phaseCheck = checkInitiativePhases(initiativeDoc);
120
+ if (!phaseCheck.hasPhases && phaseCheck.warning) {
121
+ warnings.push(phaseCheck.warning);
122
+ }
123
+ if (phaseCheck.hasPhases && !phase) {
124
+ warnings.push(`Initiative ${initiativeId} has phases defined. Consider adding --phase to link this WU to a phase.`);
125
+ }
126
+ const relatedPlan = initiativeDoc.related_plan;
127
+ if (relatedPlan && !hasSpecRefs(specRefs)) {
128
+ warnings.push(`Initiative ${initiativeId} has related_plan (${relatedPlan}). Consider adding --spec-refs to link this WU to the plan.`);
129
+ }
130
+ return warnings;
131
+ }
117
132
  /**
118
133
  * Check if WU already exists
119
134
  * @param {string} id - WU ID to check
@@ -211,8 +226,8 @@ function createPlanTemplate(wuId, title) {
211
226
  console.log(`${LOG_PREFIX} ✅ Created plan template: ${planPath}`);
212
227
  return planPath;
213
228
  }
214
- function buildWUContent({ id, lane, title, priority, type, created, opts, }) {
215
- const { description, acceptance, codePaths, testPathsManual, testPathsUnit, testPathsE2e, initiative, phase, blockedBy, blocks, labels, assignedTo, exposure, userJourney, uiPairingWus, specRefs, } = opts;
229
+ export function buildWUContent({ id, lane, title, priority, type, created, opts, }) {
230
+ const { description, acceptance, notes, codePaths, testPathsManual, testPathsUnit, testPathsE2e, initiative, phase, blockedBy, blocks, labels, assignedTo, exposure, userJourney, uiPairingWus, specRefs, } = opts;
216
231
  // Arrays come directly from Commander.js repeatable options - no parsing needed
217
232
  const code_paths = codePaths ?? [];
218
233
  const tests = {
@@ -235,7 +250,7 @@ function buildWUContent({ id, lane, title, priority, type, created, opts, }) {
235
250
  artifacts: [`.lumenflow/stamps/${id}.done`],
236
251
  dependencies: [],
237
252
  risks: [],
238
- notes: '',
253
+ notes: notes ?? '',
239
254
  requires_review: false,
240
255
  ...(initiative && { initiative }),
241
256
  ...(phase && { phase: parseInt(phase, 10) }),
@@ -285,7 +300,7 @@ export function validateCreateSpec({ id, lane, title, priority, type, opts, }) {
285
300
  errors.push('At least one test path flag is required (--test-paths-manual, --test-paths-unit, or --test-paths-e2e)');
286
301
  }
287
302
  }
288
- if (effectiveType === 'feature' && !opts.specRefs) {
303
+ if (effectiveType === 'feature' && !hasSpecRefs(opts.specRefs)) {
289
304
  errors.push('--spec-refs is required for type: feature WUs\n' +
290
305
  ' Tip: Create a plan first with: pnpm plan:create --id <WU-ID> --title "..."\n' +
291
306
  ' Then use --plan flag or --spec-refs lumenflow://plans/<WU-ID>-plan.md');
@@ -511,6 +526,7 @@ async function main() {
511
526
  // WU-1364: Full spec inline options
512
527
  WU_OPTIONS.description,
513
528
  WU_OPTIONS.acceptance,
529
+ WU_OPTIONS.notes,
514
530
  WU_OPTIONS.codePaths,
515
531
  WU_OPTIONS.testPathsManual,
516
532
  WU_OPTIONS.testPathsUnit,
@@ -588,6 +604,7 @@ async function main() {
588
604
  opts: {
589
605
  description: args.description,
590
606
  acceptance: args.acceptance,
607
+ notes: args.notes,
591
608
  codePaths: args.codePaths,
592
609
  testPathsManual: args.testPathsManual,
593
610
  testPathsUnit: args.testPathsUnit,
@@ -613,16 +630,6 @@ async function main() {
613
630
  die(`${LOG_PREFIX} ❌ Spec validation failed:\n\n${errorList}`);
614
631
  }
615
632
  console.log(`${LOG_PREFIX} ✅ Spec validation passed`);
616
- // WU-1211: Warn if linking to initiative with no phases defined
617
- if (args.initiative) {
618
- const initiative = findInitiative(args.initiative);
619
- if (initiative) {
620
- const phaseCheck = checkInitiativePhases(initiative.doc);
621
- if (!phaseCheck.hasPhases && phaseCheck.warning) {
622
- console.warn(`${LOG_PREFIX} ⚠️ ${phaseCheck.warning}`);
623
- }
624
- }
625
- }
626
633
  const specRefsList = mergedSpecRefs;
627
634
  const specRefsValidation = validateSpecRefs(specRefsList);
628
635
  if (!specRefsValidation.valid) {
@@ -636,6 +643,20 @@ async function main() {
636
643
  console.warn(`${LOG_PREFIX} ⚠️ ${warning}`);
637
644
  }
638
645
  }
646
+ if (args.initiative) {
647
+ const initiative = findInitiative(args.initiative);
648
+ if (initiative) {
649
+ const warnings = collectInitiativeWarnings({
650
+ initiativeId: initiative.id,
651
+ initiativeDoc: initiative.doc,
652
+ phase: args.phase,
653
+ specRefs: specRefsList,
654
+ });
655
+ for (const warning of warnings) {
656
+ console.warn(`${LOG_PREFIX} ⚠️ ${warning}`);
657
+ }
658
+ }
659
+ }
639
660
  if (args.plan) {
640
661
  createPlanTemplate(wuId, args.title);
641
662
  }
@@ -664,6 +685,7 @@ async function main() {
664
685
  // WU-1364: Full spec inline options
665
686
  description: args.description,
666
687
  acceptance: args.acceptance,
688
+ notes: args.notes,
667
689
  codePaths: args.codePaths,
668
690
  testPathsManual: args.testPathsManual,
669
691
  testPathsUnit: args.testPathsUnit,
@@ -15,16 +15,19 @@
15
15
  * pnpm wu:recover --id WU-123 --action resume # Apply fix
16
16
  * pnpm wu:recover --id WU-123 --action nuke --force # Destructive
17
17
  */
18
- import { existsSync, rmSync } from 'node:fs';
18
+ import { existsSync, rmSync, writeFileSync } from 'node:fs';
19
19
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
20
20
  import { computeContext } from '@lumenflow/core/dist/context/index.js';
21
21
  import { analyzeRecovery, } from '@lumenflow/core/dist/recovery/recovery-analyzer.js';
22
22
  import { die } from '@lumenflow/core/dist/error-handler.js';
23
23
  import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
24
24
  import { readWU, writeWU } from '@lumenflow/core/dist/wu-yaml.js';
25
- import { CONTEXT_VALIDATION, EMOJI, WU_STATUS, DEFAULTS, toKebab, } from '@lumenflow/core/dist/wu-constants.js';
25
+ import { CONTEXT_VALIDATION, EMOJI, WU_STATUS, DEFAULTS, toKebab, FILE_SYSTEM, } from '@lumenflow/core/dist/wu-constants.js';
26
26
  import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
27
27
  import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
28
+ import { WUStateStore } from '@lumenflow/core/dist/wu-state-store.js';
29
+ import { generateBacklog, generateStatus } from '@lumenflow/core/dist/backlog-generator.js';
30
+ import { releaseLaneLock } from '@lumenflow/core/dist/lane-lock.js';
28
31
  import { join, relative } from 'node:path';
29
32
  const { RECOVERY_ACTIONS } = CONTEXT_VALIDATION;
30
33
  const LOG_PREFIX = '[wu:recover]';
@@ -159,6 +162,7 @@ async function executeResume(wuId) {
159
162
  * Execute reset action - discard worktree and reset to ready
160
163
  *
161
164
  * WU-1226: Uses micro-worktree isolation for WU YAML state changes.
165
+ * WU-1419: Emits release event to state store so WU can be re-claimed.
162
166
  * Worktree removal still happens directly (git operation, not file write).
163
167
  * Changes are pushed via merge, not direct file modification on main.
164
168
  */
@@ -171,6 +175,7 @@ async function executeReset(wuId) {
171
175
  }
172
176
  const doc = readWU(wuPath, wuId);
173
177
  const worktreePath = getWorktreePath(wuId, doc.lane || '');
178
+ const lane = doc.lane || '';
174
179
  // Remove worktree if exists (git operation, safe to do directly)
175
180
  // WU-1097: Use worktreeRemove() instead of deprecated run() with shell strings
176
181
  // This properly handles paths with spaces and special characters
@@ -193,6 +198,7 @@ async function executeReset(wuId) {
193
198
  }
194
199
  }
195
200
  // WU-1226: Use micro-worktree isolation for WU YAML state changes
201
+ // WU-1419: Also emit release event to state store
196
202
  try {
197
203
  await withMicroWorktree({
198
204
  operation: OPERATION_NAME,
@@ -211,12 +217,57 @@ async function executeReset(wuId) {
211
217
  Reflect.deleteProperty(microDoc, 'session_id');
212
218
  Reflect.deleteProperty(microDoc, 'baseline_main_sha');
213
219
  writeWU(microWuPath, microDoc);
220
+ // WU-1419: Emit release event to state store so re-claiming works
221
+ // Without this, state store still thinks WU is in_progress, blocking re-claim
222
+ const stateDir = join(microPath, '.lumenflow', 'state');
223
+ const store = new WUStateStore(stateDir);
224
+ await store.load();
225
+ // Only emit release event if WU is currently in_progress in state store
226
+ const currentState = store.getWUState(wuId);
227
+ if (currentState && currentState.status === 'in_progress') {
228
+ await store.release(wuId, 'Reset via wu:recover --action reset');
229
+ console.log(`${LOG_PREFIX} Emitted release event to state store`);
230
+ // Regenerate backlog.md and status.md from state store
231
+ const microBacklogPath = join(microPath, WU_PATHS.BACKLOG());
232
+ const microStatusPath = join(microPath, WU_PATHS.STATUS());
233
+ const backlogContent = await generateBacklog(store);
234
+ writeFileSync(microBacklogPath, backlogContent, {
235
+ encoding: FILE_SYSTEM.UTF8,
236
+ });
237
+ const statusContent = await generateStatus(store);
238
+ writeFileSync(microStatusPath, statusContent, {
239
+ encoding: FILE_SYSTEM.UTF8,
240
+ });
241
+ return {
242
+ commitMessage: `fix(wu-recover): reset ${wuId} - clear claim and emit release event`,
243
+ files: [
244
+ relative(process.cwd(), wuPath),
245
+ WU_PATHS.STATUS(),
246
+ WU_PATHS.BACKLOG(),
247
+ '.lumenflow/state/wu-events.jsonl',
248
+ ],
249
+ };
250
+ }
251
+ // WU not in state store as in_progress, just update YAML
214
252
  return {
215
253
  commitMessage: `fix(wu-recover): reset ${wuId} - clear claim and set status to ready`,
216
254
  files: [relative(process.cwd(), wuPath)],
217
255
  };
218
256
  },
219
257
  });
258
+ // Release lane lock so another WU can be claimed
259
+ if (lane) {
260
+ try {
261
+ const releaseResult = releaseLaneLock(lane, { wuId });
262
+ if (releaseResult.released && !releaseResult.notFound) {
263
+ console.log(`${LOG_PREFIX} Lane lock released for "${lane}"`);
264
+ }
265
+ }
266
+ catch (err) {
267
+ // Non-blocking: lock release failure should not block the reset operation
268
+ console.warn(`${LOG_PREFIX} Warning: Could not release lane lock: ${err.message}`);
269
+ }
270
+ }
220
271
  console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Reset completed - ${wuId} is now ready for re-claiming`);
221
272
  return true;
222
273
  }
package/dist/wu-spawn.js CHANGED
@@ -630,10 +630,10 @@ When creating \`.lumenflow/\` stamps or other artifacts:
630
630
  # CORRECT: Create stamp in worktree
631
631
  WORKTREE_ROOT=$(git rev-parse --show-toplevel)
632
632
  mkdir -p "$WORKTREE_ROOT/.lumenflow/agent-runs"
633
- touch "$WORKTREE_ROOT/.lumenflow/agent-runs/beacon-guardian.stamp"
633
+ touch "$WORKTREE_ROOT/.lumenflow/agent-runs/code-reviewer.stamp"
634
634
 
635
635
  # WRONG: Hardcoded path to main
636
- # touch /path/to/main/.lumenflow/agent-runs/beacon-guardian.stamp
636
+ # touch /path/to/main/.lumenflow/agent-runs/code-reviewer.stamp
637
637
  \`\`\`
638
638
 
639
639
  ### Why This Matters
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/cli",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "Command-line interface for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -151,11 +151,11 @@
151
151
  "pretty-ms": "^9.2.0",
152
152
  "simple-git": "^3.30.0",
153
153
  "yaml": "^2.8.2",
154
- "@lumenflow/core": "2.9.0",
155
- "@lumenflow/metrics": "2.9.0",
156
- "@lumenflow/memory": "2.9.0",
157
- "@lumenflow/agent": "2.9.0",
158
- "@lumenflow/initiatives": "2.9.0"
154
+ "@lumenflow/core": "2.11.0",
155
+ "@lumenflow/metrics": "2.11.0",
156
+ "@lumenflow/memory": "2.11.0",
157
+ "@lumenflow/initiatives": "2.11.0",
158
+ "@lumenflow/agent": "2.11.0"
159
159
  },
160
160
  "devDependencies": {
161
161
  "@vitest/coverage-v8": "^4.0.17",
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "lumenflow": {
4
+ "command": "npx",
5
+ "args": ["@lumenflow/mcp"]
6
+ }
7
+ }
8
+ }
@@ -53,6 +53,30 @@ cd /path/to/main && pnpm wu:done --id WU-XXXX
53
53
 
54
54
  ---
55
55
 
56
+ ## When to Use Initiatives
57
+
58
+ Use **Initiatives** for multi-phase work spanning multiple WUs:
59
+
60
+ - **Product visions**: "Build a task management app"
61
+ - **Larger features**: Work requiring multiple WUs across lanes
62
+ - **Complex projects**: Anything that needs phased delivery
63
+
64
+ ```bash
65
+ # Create an initiative for multi-phase work
66
+ pnpm initiative:create --id INIT-001 --title "Feature Name" \
67
+ --description "..." --phase "Phase 1: MVP" --phase "Phase 2: Polish"
68
+
69
+ # Add WUs to the initiative
70
+ pnpm initiative:add-wu --initiative INIT-001 --wu WU-XXX --phase 1
71
+
72
+ # Track progress
73
+ pnpm initiative:status --id INIT-001
74
+ ```
75
+
76
+ **Skip initiatives** for: single-file bug fixes, small docs updates, isolated refactoring.
77
+
78
+ ---
79
+
56
80
  ## Setup Notes (Common First-Run Failures)
57
81
 
58
82
  ### Lane inference (sub-lanes)