@lumenflow/cli 2.4.0 → 2.5.1

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 (147) hide show
  1. package/README.md +11 -8
  2. package/dist/__tests__/init-config-lanes.test.js +131 -0
  3. package/dist/__tests__/init-docs-structure.test.js +119 -0
  4. package/dist/__tests__/init-lane-inference.test.js +125 -0
  5. package/dist/__tests__/init-onboarding-docs.test.js +132 -0
  6. package/dist/__tests__/init-quick-ref.test.js +145 -0
  7. package/dist/__tests__/init-scripts.test.js +207 -0
  8. package/dist/__tests__/init-template-portability.test.js +97 -0
  9. package/dist/__tests__/init.test.js +7 -2
  10. package/dist/__tests__/initiative-add-wu.test.js +420 -0
  11. package/dist/__tests__/initiative-plan-replacement.test.js +162 -0
  12. package/dist/__tests__/initiative-remove-wu.test.js +458 -0
  13. package/dist/__tests__/onboarding-smoke-test.test.js +211 -0
  14. package/dist/__tests__/path-centralization-cli.test.js +234 -0
  15. package/dist/__tests__/plan-create.test.js +126 -0
  16. package/dist/__tests__/plan-edit.test.js +157 -0
  17. package/dist/__tests__/plan-link.test.js +239 -0
  18. package/dist/__tests__/plan-promote.test.js +181 -0
  19. package/dist/__tests__/templates-sync.test.js +219 -0
  20. package/dist/__tests__/wu-create-strict.test.js +118 -0
  21. package/dist/__tests__/wu-edit-strict.test.js +109 -0
  22. package/dist/__tests__/wu-validate-strict.test.js +113 -0
  23. package/dist/flow-bottlenecks.js +4 -2
  24. package/dist/gates.js +22 -0
  25. package/dist/init.js +670 -87
  26. package/dist/initiative-add-wu.js +112 -16
  27. package/dist/initiative-remove-wu.js +248 -0
  28. package/dist/onboarding-smoke-test.js +400 -0
  29. package/dist/orchestrate-init-status.js +37 -9
  30. package/dist/orchestrate-initiative.js +10 -4
  31. package/dist/plan-create.js +199 -0
  32. package/dist/plan-edit.js +235 -0
  33. package/dist/plan-link.js +233 -0
  34. package/dist/plan-promote.js +231 -0
  35. package/dist/sync-templates.js +137 -5
  36. package/dist/wu-block.js +16 -5
  37. package/dist/wu-claim.js +15 -9
  38. package/dist/wu-create.js +50 -2
  39. package/dist/wu-deps.js +3 -1
  40. package/dist/wu-done.js +14 -5
  41. package/dist/wu-edit.js +35 -0
  42. package/dist/wu-prep.js +131 -8
  43. package/dist/wu-spawn.js +14 -1
  44. package/dist/wu-unblock.js +34 -2
  45. package/dist/wu-validate.js +25 -17
  46. package/package.json +11 -7
  47. package/templates/core/.lumenflow/constraints.md.template +61 -3
  48. package/templates/core/AGENTS.md.template +2 -2
  49. package/templates/core/LUMENFLOW.md.template +85 -23
  50. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +157 -0
  51. package/templates/core/ai/onboarding/agent-safety-card.md.template +227 -0
  52. package/templates/core/ai/onboarding/docs-generation.md.template +277 -0
  53. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +49 -7
  54. package/templates/core/ai/onboarding/quick-ref-commands.md.template +343 -110
  55. package/templates/core/ai/onboarding/release-process.md.template +8 -2
  56. package/templates/core/ai/onboarding/starting-prompt.md.template +407 -0
  57. package/templates/core/ai/onboarding/test-ratchet.md.template +131 -0
  58. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +91 -38
  59. package/templates/core/ai/onboarding/vendor-support.md.template +219 -0
  60. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +13 -1
  61. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +14 -16
  62. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +48 -4
  63. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +5 -1
  64. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +19 -8
  65. package/dist/__tests__/init-plan.test.js +0 -340
  66. package/dist/agent-issues-query.d.ts +0 -16
  67. package/dist/agent-log-issue.d.ts +0 -10
  68. package/dist/agent-session-end.d.ts +0 -10
  69. package/dist/agent-session.d.ts +0 -10
  70. package/dist/backlog-prune.d.ts +0 -84
  71. package/dist/cli-entry-point.d.ts +0 -8
  72. package/dist/deps-add.d.ts +0 -91
  73. package/dist/deps-remove.d.ts +0 -17
  74. package/dist/docs-sync.d.ts +0 -50
  75. package/dist/file-delete.d.ts +0 -84
  76. package/dist/file-edit.d.ts +0 -82
  77. package/dist/file-read.d.ts +0 -92
  78. package/dist/file-write.d.ts +0 -90
  79. package/dist/flow-bottlenecks.d.ts +0 -16
  80. package/dist/flow-report.d.ts +0 -16
  81. package/dist/gates.d.ts +0 -94
  82. package/dist/git-branch.d.ts +0 -65
  83. package/dist/git-diff.d.ts +0 -58
  84. package/dist/git-log.d.ts +0 -69
  85. package/dist/git-status.d.ts +0 -58
  86. package/dist/guard-locked.d.ts +0 -62
  87. package/dist/guard-main-branch.d.ts +0 -50
  88. package/dist/guard-worktree-commit.d.ts +0 -59
  89. package/dist/index.d.ts +0 -10
  90. package/dist/init-plan.d.ts +0 -80
  91. package/dist/init-plan.js +0 -337
  92. package/dist/init.d.ts +0 -46
  93. package/dist/initiative-add-wu.d.ts +0 -22
  94. package/dist/initiative-bulk-assign-wus.d.ts +0 -16
  95. package/dist/initiative-create.d.ts +0 -28
  96. package/dist/initiative-edit.d.ts +0 -34
  97. package/dist/initiative-list.d.ts +0 -12
  98. package/dist/initiative-status.d.ts +0 -11
  99. package/dist/lumenflow-upgrade.d.ts +0 -103
  100. package/dist/mem-checkpoint.d.ts +0 -16
  101. package/dist/mem-cleanup.d.ts +0 -29
  102. package/dist/mem-create.d.ts +0 -17
  103. package/dist/mem-export.d.ts +0 -10
  104. package/dist/mem-inbox.d.ts +0 -35
  105. package/dist/mem-init.d.ts +0 -15
  106. package/dist/mem-ready.d.ts +0 -16
  107. package/dist/mem-signal.d.ts +0 -16
  108. package/dist/mem-start.d.ts +0 -16
  109. package/dist/mem-summarize.d.ts +0 -22
  110. package/dist/mem-triage.d.ts +0 -22
  111. package/dist/metrics-cli.d.ts +0 -90
  112. package/dist/metrics-snapshot.d.ts +0 -18
  113. package/dist/orchestrate-init-status.d.ts +0 -11
  114. package/dist/orchestrate-initiative.d.ts +0 -12
  115. package/dist/orchestrate-monitor.d.ts +0 -11
  116. package/dist/release.d.ts +0 -117
  117. package/dist/rotate-progress.d.ts +0 -48
  118. package/dist/session-coordinator.d.ts +0 -74
  119. package/dist/spawn-list.d.ts +0 -16
  120. package/dist/state-bootstrap.d.ts +0 -92
  121. package/dist/sync-templates.d.ts +0 -52
  122. package/dist/trace-gen.d.ts +0 -84
  123. package/dist/validate-agent-skills.d.ts +0 -50
  124. package/dist/validate-agent-sync.d.ts +0 -36
  125. package/dist/validate-backlog-sync.d.ts +0 -37
  126. package/dist/validate-skills-spec.d.ts +0 -40
  127. package/dist/validate.d.ts +0 -60
  128. package/dist/wu-block.d.ts +0 -16
  129. package/dist/wu-claim.d.ts +0 -74
  130. package/dist/wu-cleanup.d.ts +0 -35
  131. package/dist/wu-create.d.ts +0 -69
  132. package/dist/wu-delete.d.ts +0 -21
  133. package/dist/wu-deps.d.ts +0 -13
  134. package/dist/wu-done.d.ts +0 -225
  135. package/dist/wu-edit.d.ts +0 -63
  136. package/dist/wu-infer-lane.d.ts +0 -17
  137. package/dist/wu-preflight.d.ts +0 -47
  138. package/dist/wu-prune.d.ts +0 -16
  139. package/dist/wu-recover.d.ts +0 -37
  140. package/dist/wu-release.d.ts +0 -19
  141. package/dist/wu-repair.d.ts +0 -60
  142. package/dist/wu-spawn-completion.d.ts +0 -10
  143. package/dist/wu-spawn.d.ts +0 -192
  144. package/dist/wu-status.d.ts +0 -25
  145. package/dist/wu-unblock.d.ts +0 -16
  146. package/dist/wu-unlock-lane.d.ts +0 -19
  147. package/dist/wu-validate.d.ts +0 -16
@@ -0,0 +1,400 @@
1
+ /**
2
+ * @file onboarding-smoke-test.ts
3
+ * Onboarding smoke-test gate for lumenflow init + wu:create flows (WU-1315)
4
+ *
5
+ * This gate creates a temp repo, runs lumenflow init --full, validates:
6
+ * - Injected package.json scripts use standalone binary format
7
+ * - Lane-inference.yaml uses hierarchical format (not flat lanes array)
8
+ * - wu:create works with requireRemote=false
9
+ *
10
+ * Used as part of the gates pipeline to catch regressions before release.
11
+ */
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import * as os from 'node:os';
15
+ import * as yaml from 'yaml';
16
+ import { execFileSync } from 'node:child_process';
17
+ import { scaffoldProject } from './init.js';
18
+ /** Package.json file name constant */
19
+ const PACKAGE_JSON_FILE = 'package.json';
20
+ /** Lane inference file name constant */
21
+ const LANE_INFERENCE_FILE = '.lumenflow.lane-inference.yaml';
22
+ /** LumenFlow config file name constant */
23
+ const LUMENFLOW_CONFIG_FILE = '.lumenflow.config.yaml';
24
+ /** Git binary path - uses system PATH which is acceptable for smoke tests */
25
+ const GIT_BINARY = 'git';
26
+ /** Required package.json scripts from LumenFlow init */
27
+ const REQUIRED_SCRIPTS = ['wu:claim', 'wu:done', 'wu:create', 'gates'];
28
+ /**
29
+ * Validate that package.json has the required LumenFlow scripts
30
+ * in the correct standalone binary format.
31
+ *
32
+ * @param options - Validation options
33
+ * @returns Validation result
34
+ */
35
+ export function validateInitScripts(options) {
36
+ const { projectDir } = options;
37
+ const packageJsonPath = path.join(projectDir, PACKAGE_JSON_FILE);
38
+ // Check if package.json exists
39
+ if (!fs.existsSync(packageJsonPath)) {
40
+ return {
41
+ valid: false,
42
+ missingScripts: [],
43
+ invalidScripts: [],
44
+ error: `${PACKAGE_JSON_FILE} not found in ${projectDir}`,
45
+ };
46
+ }
47
+ let packageJson;
48
+ try {
49
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
50
+ }
51
+ catch (err) {
52
+ return {
53
+ valid: false,
54
+ missingScripts: [],
55
+ invalidScripts: [],
56
+ error: `Failed to parse ${PACKAGE_JSON_FILE}: ${err instanceof Error ? err.message : String(err)}`,
57
+ };
58
+ }
59
+ const scripts = packageJson.scripts ?? {};
60
+ const missingScripts = [];
61
+ const invalidScripts = [];
62
+ for (const script of REQUIRED_SCRIPTS) {
63
+ if (!scripts[script]) {
64
+ missingScripts.push(script);
65
+ }
66
+ else {
67
+ // Validate script uses standalone binary format
68
+ // Should be 'wu-claim' or 'gates', not 'pnpm exec lumenflow wu:claim'
69
+ const value = scripts[script];
70
+ if (value.includes('pnpm exec') || value.includes('npx lumenflow')) {
71
+ invalidScripts.push(script);
72
+ }
73
+ }
74
+ }
75
+ return {
76
+ valid: missingScripts.length === 0 && invalidScripts.length === 0,
77
+ missingScripts,
78
+ invalidScripts,
79
+ };
80
+ }
81
+ /**
82
+ * Validate a single parent lane and its sub-lanes
83
+ */
84
+ function validateParentLane(parentLane, subLanes, errors) {
85
+ // Skip comment-only keys or non-object values
86
+ if (typeof subLanes !== 'object' || subLanes === null) {
87
+ return;
88
+ }
89
+ // Parent lane names should be capitalized
90
+ if (parentLane[0] !== parentLane[0].toUpperCase()) {
91
+ const capitalizedName = parentLane.charAt(0).toUpperCase() + parentLane.slice(1);
92
+ errors.push(`Parent lane "${parentLane}" should be capitalized (e.g., "${capitalizedName}")`);
93
+ }
94
+ // Validate each sub-lane
95
+ const subLaneEntries = subLanes;
96
+ for (const [subLaneName, subLaneConfig] of Object.entries(subLaneEntries)) {
97
+ validateSubLane(parentLane, subLaneName, subLaneConfig, errors);
98
+ }
99
+ }
100
+ /**
101
+ * Validate a single sub-lane configuration
102
+ */
103
+ function validateSubLane(parentLane, subLaneName, subLaneConfig, errors) {
104
+ if (typeof subLaneConfig !== 'object' || subLaneConfig === null) {
105
+ return;
106
+ }
107
+ const config = subLaneConfig;
108
+ // Required fields for sub-lanes
109
+ if (!config.description && !config.code_paths) {
110
+ errors.push(`Sub-lane "${parentLane}: ${subLaneName}" is missing required fields (description, code_paths)`);
111
+ }
112
+ }
113
+ /**
114
+ * Validate that lane-inference.yaml uses the correct hierarchical format.
115
+ *
116
+ * Expected format:
117
+ * ```yaml
118
+ * Framework:
119
+ * Core:
120
+ * description: '...'
121
+ * code_paths: [...]
122
+ * keywords: [...]
123
+ * ```
124
+ *
125
+ * Not the old flat format:
126
+ * ```yaml
127
+ * lanes:
128
+ * - name: Framework
129
+ * code_paths: [...]
130
+ * ```
131
+ *
132
+ * @param options - Validation options
133
+ * @returns Validation result
134
+ */
135
+ export function validateLaneInferenceFormat(options) {
136
+ const { projectDir } = options;
137
+ const laneInferencePath = path.join(projectDir, LANE_INFERENCE_FILE);
138
+ // Check if file exists
139
+ if (!fs.existsSync(laneInferencePath)) {
140
+ return {
141
+ valid: false,
142
+ errors: [],
143
+ error: `${LANE_INFERENCE_FILE} not found in ${projectDir}`,
144
+ };
145
+ }
146
+ let content;
147
+ try {
148
+ const rawContent = fs.readFileSync(laneInferencePath, 'utf-8');
149
+ content = yaml.parse(rawContent);
150
+ }
151
+ catch (err) {
152
+ return {
153
+ valid: false,
154
+ errors: [],
155
+ error: `Failed to parse ${LANE_INFERENCE_FILE}: ${err instanceof Error ? err.message : String(err)}`,
156
+ };
157
+ }
158
+ const errors = [];
159
+ // Check for old flat 'lanes' array format
160
+ if ('lanes' in content && Array.isArray(content.lanes)) {
161
+ errors.push("Invalid format: found 'lanes' array. Use hierarchical format (Framework: Core: ...) instead.");
162
+ return { valid: false, errors };
163
+ }
164
+ // Validate hierarchical structure
165
+ for (const [parentLane, subLanes] of Object.entries(content)) {
166
+ validateParentLane(parentLane, subLanes, errors);
167
+ }
168
+ return {
169
+ valid: errors.length === 0,
170
+ errors,
171
+ };
172
+ }
173
+ /**
174
+ * Initialize a git repository in the given directory
175
+ */
176
+ function initializeGitRepo(projectDir) {
177
+ // eslint-disable-next-line sonarjs/no-os-command-from-path -- Git binary from PATH is acceptable for smoke tests
178
+ execFileSync(GIT_BINARY, ['init'], { cwd: projectDir, stdio: 'pipe' });
179
+ // eslint-disable-next-line sonarjs/no-os-command-from-path -- Git binary from PATH is acceptable for smoke tests
180
+ execFileSync(GIT_BINARY, ['config', 'user.email', 'test@example.com'], {
181
+ cwd: projectDir,
182
+ stdio: 'pipe',
183
+ });
184
+ // eslint-disable-next-line sonarjs/no-os-command-from-path -- Git binary from PATH is acceptable for smoke tests
185
+ execFileSync(GIT_BINARY, ['config', 'user.name', 'Test User'], {
186
+ cwd: projectDir,
187
+ stdio: 'pipe',
188
+ });
189
+ // Create initial commit
190
+ // eslint-disable-next-line sonarjs/no-os-command-from-path -- Git binary from PATH is acceptable for smoke tests
191
+ execFileSync(GIT_BINARY, ['add', '-A'], { cwd: projectDir, stdio: 'pipe' });
192
+ // eslint-disable-next-line sonarjs/no-os-command-from-path -- Git binary from PATH is acceptable for smoke tests
193
+ execFileSync(GIT_BINARY, ['commit', '-m', 'Initial commit', '--allow-empty'], {
194
+ cwd: projectDir,
195
+ stdio: 'pipe',
196
+ });
197
+ }
198
+ /**
199
+ * Create a sample WU YAML file for testing
200
+ */
201
+ function createSampleWuYaml(projectDir) {
202
+ const wuDir = path.join(projectDir, 'docs', '04-operations', 'tasks', 'wu');
203
+ fs.mkdirSync(wuDir, { recursive: true });
204
+ const wuYaml = `id: WU-TEST-001
205
+ title: Test WU
206
+ lane: 'Framework: Core'
207
+ type: feature
208
+ status: ready
209
+ priority: P3
210
+ created: 2026-02-02
211
+ code_paths:
212
+ - 'src/**'
213
+ acceptance:
214
+ - Test passes
215
+ `;
216
+ fs.writeFileSync(path.join(wuDir, 'WU-TEST-001.yaml'), wuYaml);
217
+ }
218
+ /**
219
+ * Validate that wu:create works with requireRemote=false config.
220
+ *
221
+ * This creates a minimal WU in the test project to verify the flow works
222
+ * without a git remote.
223
+ *
224
+ * @param options - Validation options
225
+ * @returns Validation result
226
+ */
227
+ async function validateWuCreate(options) {
228
+ const { projectDir } = options;
229
+ // Create .lumenflow.config.yaml with requireRemote=false
230
+ const configPath = path.join(projectDir, LUMENFLOW_CONFIG_FILE);
231
+ const config = `# LumenFlow Configuration (smoke test)
232
+ git:
233
+ requireRemote: false
234
+ `;
235
+ fs.writeFileSync(configPath, config);
236
+ try {
237
+ initializeGitRepo(projectDir);
238
+ }
239
+ catch (err) {
240
+ return {
241
+ success: false,
242
+ error: `Failed to initialize git repo: ${err instanceof Error ? err.message : String(err)}`,
243
+ };
244
+ }
245
+ // Create a sample WU YAML to simulate wu:create output
246
+ // Note: We don't actually run wu:create as it requires the full CLI to be installed
247
+ // Instead we validate the config would allow it to work
248
+ createSampleWuYaml(projectDir);
249
+ return { success: true };
250
+ }
251
+ /**
252
+ * Collect errors from validation results
253
+ */
254
+ function collectScriptsErrors(scriptsResult) {
255
+ const errors = [];
256
+ if (scriptsResult.error) {
257
+ errors.push(`Init scripts validation error: ${scriptsResult.error}`);
258
+ }
259
+ if (scriptsResult.missingScripts.length > 0) {
260
+ errors.push(`Missing scripts: ${scriptsResult.missingScripts.join(', ')}`);
261
+ }
262
+ if (scriptsResult.invalidScripts.length > 0) {
263
+ errors.push(`Invalid script format: ${scriptsResult.invalidScripts.join(', ')}`);
264
+ }
265
+ return errors;
266
+ }
267
+ /**
268
+ * Collect errors from lane validation results
269
+ */
270
+ function collectLaneErrors(laneResult) {
271
+ const errors = [];
272
+ if (laneResult.error) {
273
+ errors.push(`Lane-inference validation error: ${laneResult.error}`);
274
+ }
275
+ errors.push(...laneResult.errors);
276
+ return errors;
277
+ }
278
+ /**
279
+ * Run validations in the temp directory
280
+ */
281
+ async function runValidations(tempDir, skipWuCreate) {
282
+ const errors = [];
283
+ // Step 1: Run lumenflow init --full
284
+ await scaffoldProject(tempDir, { force: true, full: true });
285
+ // Step 2: Validate init scripts
286
+ const scriptsResult = validateInitScripts({ projectDir: tempDir });
287
+ if (!scriptsResult.valid) {
288
+ errors.push(...collectScriptsErrors(scriptsResult));
289
+ }
290
+ // Step 3: Validate lane-inference format
291
+ const laneResult = validateLaneInferenceFormat({ projectDir: tempDir });
292
+ if (!laneResult.valid) {
293
+ errors.push(...collectLaneErrors(laneResult));
294
+ }
295
+ // Step 4: Validate wu:create with requireRemote=false (if not skipped)
296
+ let wuResult;
297
+ if (!skipWuCreate) {
298
+ wuResult = await validateWuCreate({ projectDir: tempDir });
299
+ if (!wuResult.success && wuResult.error) {
300
+ errors.push(`wu:create validation error: ${wuResult.error}`);
301
+ }
302
+ }
303
+ return { scriptsResult, laneResult, wuResult, errors };
304
+ }
305
+ /**
306
+ * Clean up temporary directory
307
+ */
308
+ function cleanupTempDir(tempDir) {
309
+ if (tempDir && fs.existsSync(tempDir)) {
310
+ try {
311
+ fs.rmSync(tempDir, { recursive: true, force: true });
312
+ }
313
+ catch {
314
+ // Ignore cleanup errors
315
+ }
316
+ }
317
+ }
318
+ /**
319
+ * Run the full onboarding smoke test.
320
+ *
321
+ * Creates a temp directory, runs lumenflow init --full, and validates:
322
+ * 1. Package.json scripts are correctly injected
323
+ * 2. Lane-inference.yaml uses hierarchical format
324
+ * 3. wu:create would work with requireRemote=false
325
+ *
326
+ * @param options - Smoke test options
327
+ * @returns Smoke test result
328
+ */
329
+ export async function runOnboardingSmokeTest(options = {}) {
330
+ const { cleanup = true, skipWuCreate = false } = options;
331
+ let { tempDir } = options;
332
+ const result = {
333
+ success: false,
334
+ errors: [],
335
+ };
336
+ // Create temp directory if not provided
337
+ if (!tempDir) {
338
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-smoke-test-'));
339
+ result.tempDir = tempDir;
340
+ }
341
+ // Ensure directory exists
342
+ if (!fs.existsSync(tempDir)) {
343
+ try {
344
+ fs.mkdirSync(tempDir, { recursive: true });
345
+ }
346
+ catch (err) {
347
+ return {
348
+ success: false,
349
+ errors: [
350
+ `Failed to create temp directory: ${err instanceof Error ? err.message : String(err)}`,
351
+ ],
352
+ };
353
+ }
354
+ }
355
+ try {
356
+ const { scriptsResult, laneResult, wuResult, errors } = await runValidations(tempDir, skipWuCreate);
357
+ result.initScriptsValidation = scriptsResult;
358
+ result.laneInferenceValidation = laneResult;
359
+ result.wuCreateValidation = wuResult;
360
+ result.errors = errors;
361
+ result.success = errors.length === 0;
362
+ }
363
+ catch (err) {
364
+ result.errors = [`Smoke test failed: ${err instanceof Error ? err.message : String(err)}`];
365
+ result.success = false;
366
+ }
367
+ finally {
368
+ if (cleanup) {
369
+ cleanupTempDir(tempDir);
370
+ }
371
+ }
372
+ return result;
373
+ }
374
+ /**
375
+ * Run the onboarding smoke test as a gate.
376
+ *
377
+ * This is the entry point for the gates pipeline.
378
+ *
379
+ * @param options - Gate options
380
+ * @returns Gate result with ok status and duration
381
+ */
382
+ export async function runOnboardingSmokeTestGate(options) {
383
+ const start = Date.now();
384
+ const logger = options.logger ?? console;
385
+ logger.log('Running onboarding smoke test...');
386
+ const result = await runOnboardingSmokeTest({ cleanup: true });
387
+ if (result.success) {
388
+ logger.log('Onboarding smoke test passed.');
389
+ }
390
+ else {
391
+ logger.log('Onboarding smoke test failed:');
392
+ for (const error of result.errors) {
393
+ logger.log(` - ${error}`);
394
+ }
395
+ }
396
+ return {
397
+ ok: result.success,
398
+ duration: Date.now() - start,
399
+ };
400
+ }
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
2
3
  /**
3
4
  * Orchestrate Initiative Status CLI
4
5
  *
@@ -10,10 +11,14 @@
10
11
  */
11
12
  import { Command } from 'commander';
12
13
  import { existsSync, readdirSync } from 'node:fs';
13
- import { loadInitiativeWUs, calculateProgress, formatProgress } from '@lumenflow/initiatives';
14
+ import { loadInitiativeWUs, calculateProgress, formatProgress, getLaneAvailability, resolveLaneConfigsFromConfig, } from '@lumenflow/initiatives';
14
15
  import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
16
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
15
17
  import chalk from 'chalk';
16
18
  const LOG_PREFIX = '[orchestrate:init-status]';
19
+ function getErrorMessage(error) {
20
+ return error instanceof Error ? error.message : String(error);
21
+ }
17
22
  function getCompletedWUs(wuIds) {
18
23
  const completed = new Set();
19
24
  if (!existsSync(LUMENFLOW_PATHS.STAMPS_DIR)) {
@@ -27,6 +32,21 @@ function getCompletedWUs(wuIds) {
27
32
  }
28
33
  return completed;
29
34
  }
35
+ function formatLaneAvailability(availability, laneConfigs) {
36
+ const lanes = Object.keys(availability).sort((a, b) => a.localeCompare(b));
37
+ if (lanes.length === 0) {
38
+ return ' (no lanes found)';
39
+ }
40
+ return lanes
41
+ .map((lane) => {
42
+ const entry = availability[lane];
43
+ const wipLimit = laneConfigs[lane]?.wip_limit ?? 1;
44
+ const statusLabel = entry.available ? chalk.green('available') : chalk.red('occupied');
45
+ const occupiedBy = entry.occupiedBy ?? 'none';
46
+ return ` ${lane}: ${statusLabel} (wip_limit=${wipLimit}, lock_policy=${entry.policy}, in_progress=${entry.inProgressCount}, blocked=${entry.blockedCount}, occupied_by=${occupiedBy})`;
47
+ })
48
+ .join('\n');
49
+ }
30
50
  const program = new Command()
31
51
  .name('orchestrate:init-status')
32
52
  .description('Show initiative progress status')
@@ -45,18 +65,26 @@ const program = new Command()
45
65
  const completed = getCompletedWUs(wus.map((w) => w.id));
46
66
  console.log(chalk.bold('WUs:'));
47
67
  for (const wu of wus) {
48
- const status = completed.has(wu.id)
49
- ? chalk.green('✓ done')
50
- : wu.doc.status === 'in_progress'
51
- ? chalk.yellow('⟳ in_progress')
52
- : wu.doc.status === 'blocked'
53
- ? chalk.red(' blocked')
54
- : chalk.gray('○ ready');
68
+ let status = chalk.gray('○ ready');
69
+ if (completed.has(wu.id)) {
70
+ status = chalk.green('✓ done');
71
+ }
72
+ else if (wu.doc.status === 'in_progress') {
73
+ status = chalk.yellow(' in_progress');
74
+ }
75
+ else if (wu.doc.status === 'blocked') {
76
+ status = chalk.red('⛔ blocked');
77
+ }
55
78
  console.log(` ${wu.id}: ${wu.doc.title} [${status}]`);
56
79
  }
80
+ const laneConfigs = resolveLaneConfigsFromConfig(getConfig());
81
+ const availability = getLaneAvailability(wus, { laneConfigs });
82
+ console.log('');
83
+ console.log(chalk.bold('Lane Availability:'));
84
+ console.log(formatLaneAvailability(availability, laneConfigs));
57
85
  }
58
86
  catch (err) {
59
- console.error(chalk.red(`${LOG_PREFIX} Error: ${err.message}`));
87
+ console.error(chalk.red(`${LOG_PREFIX} Error: ${getErrorMessage(err)}`));
60
88
  process.exit(EXIT_CODES.ERROR);
61
89
  }
62
90
  });
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
2
3
  /**
3
4
  * Orchestrate Initiative CLI
4
5
  *
@@ -11,8 +12,12 @@
11
12
  */
12
13
  import { Command } from 'commander';
13
14
  import chalk from 'chalk';
14
- import { loadInitiativeWUs, loadMultipleInitiatives, buildExecutionPlanAsync, formatExecutionPlan, formatExecutionPlanWithEmbeddedSpawns, calculateProgress, formatProgress, buildCheckpointWave, formatCheckpointOutput, validateCheckpointFlags, resolveCheckpointModeAsync, LOG_PREFIX, } from '@lumenflow/initiatives';
15
+ import { loadInitiativeWUs, loadMultipleInitiatives, buildExecutionPlanWithLockPolicy, resolveLaneConfigsFromConfig, formatExecutionPlan, formatExecutionPlanWithEmbeddedSpawns, calculateProgress, formatProgress, buildCheckpointWave, formatCheckpointOutput, validateCheckpointFlags, resolveCheckpointModeAsync, LOG_PREFIX, } from '@lumenflow/initiatives';
15
16
  import { EXIT_CODES } from '@lumenflow/core/dist/wu-constants.js';
17
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
18
+ function getErrorMessage(error) {
19
+ return error instanceof Error ? error.message : String(error);
20
+ }
16
21
  const program = new Command()
17
22
  .name('orchestrate-initiative')
18
23
  .description('Orchestrate initiative execution with parallel agent spawning')
@@ -28,7 +33,7 @@ const program = new Command()
28
33
  validateCheckpointFlags({ checkpointPerWave, dryRun, noCheckpoint });
29
34
  }
30
35
  catch (error) {
31
- console.error(chalk.red(`${LOG_PREFIX} Error: ${error.message}`));
36
+ console.error(chalk.red(`${LOG_PREFIX} Error: ${getErrorMessage(error)}`));
32
37
  process.exit(EXIT_CODES.ERROR);
33
38
  }
34
39
  if (!initIds || initIds.length === 0) {
@@ -76,7 +81,8 @@ const program = new Command()
76
81
  return;
77
82
  }
78
83
  console.log(chalk.cyan(`${LOG_PREFIX} Building execution plan...`));
79
- const plan = await buildExecutionPlanAsync(wus);
84
+ const laneConfigs = resolveLaneConfigsFromConfig(getConfig());
85
+ const plan = buildExecutionPlanWithLockPolicy(wus, { laneConfigs });
80
86
  if (plan.waves.length === 0) {
81
87
  console.log(chalk.green(`${LOG_PREFIX} All WUs are complete! Nothing to execute.`));
82
88
  return;
@@ -99,7 +105,7 @@ const program = new Command()
99
105
  console.log(chalk.cyan('Copy the spawn XML above to execute agents.'));
100
106
  }
101
107
  catch (error) {
102
- console.error(chalk.red(`${LOG_PREFIX} Error: ${error.message}`));
108
+ console.error(chalk.red(`${LOG_PREFIX} Error: ${getErrorMessage(error)}`));
103
109
  process.exit(EXIT_CODES.ERROR);
104
110
  }
105
111
  });