@lumenflow/cli 2.3.2 → 2.5.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 (135) hide show
  1. package/dist/__tests__/init-config-lanes.test.js +131 -0
  2. package/dist/__tests__/init-docs-structure.test.js +119 -0
  3. package/dist/__tests__/init-lane-inference.test.js +125 -0
  4. package/dist/__tests__/init-onboarding-docs.test.js +132 -0
  5. package/dist/__tests__/init-quick-ref.test.js +145 -0
  6. package/dist/__tests__/init-scripts.test.js +96 -0
  7. package/dist/__tests__/init-template-portability.test.js +97 -0
  8. package/dist/__tests__/init.test.js +199 -3
  9. package/dist/__tests__/initiative-add-wu.test.js +420 -0
  10. package/dist/__tests__/initiative-plan-replacement.test.js +162 -0
  11. package/dist/__tests__/initiative-remove-wu.test.js +458 -0
  12. package/dist/__tests__/onboarding-smoke-test.test.js +211 -0
  13. package/dist/__tests__/path-centralization-cli.test.js +234 -0
  14. package/dist/__tests__/plan-create.test.js +126 -0
  15. package/dist/__tests__/plan-edit.test.js +157 -0
  16. package/dist/__tests__/plan-link.test.js +239 -0
  17. package/dist/__tests__/plan-promote.test.js +181 -0
  18. package/dist/__tests__/wu-create-strict.test.js +118 -0
  19. package/dist/__tests__/wu-edit-strict.test.js +109 -0
  20. package/dist/__tests__/wu-validate-strict.test.js +113 -0
  21. package/dist/flow-bottlenecks.js +4 -2
  22. package/dist/flow-report.js +3 -2
  23. package/dist/gates.js +202 -2
  24. package/dist/init.js +720 -40
  25. package/dist/initiative-add-wu.js +112 -16
  26. package/dist/initiative-plan.js +3 -2
  27. package/dist/initiative-remove-wu.js +248 -0
  28. package/dist/mem-context.js +0 -0
  29. package/dist/metrics-snapshot.js +3 -2
  30. package/dist/onboarding-smoke-test.js +400 -0
  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/rotate-progress.js +8 -5
  36. package/dist/spawn-list.js +4 -3
  37. package/dist/state-bootstrap.js +6 -4
  38. package/dist/state-doctor-fix.js +5 -4
  39. package/dist/state-doctor.js +32 -2
  40. package/dist/trace-gen.js +6 -3
  41. package/dist/wu-block.js +16 -5
  42. package/dist/wu-claim.js +15 -9
  43. package/dist/wu-create.js +50 -2
  44. package/dist/wu-deps.js +3 -1
  45. package/dist/wu-done.js +14 -5
  46. package/dist/wu-edit.js +35 -0
  47. package/dist/wu-infer-lane.js +3 -1
  48. package/dist/wu-spawn.js +8 -0
  49. package/dist/wu-unblock.js +34 -2
  50. package/dist/wu-validate.js +25 -17
  51. package/package.json +12 -6
  52. package/templates/core/AGENTS.md.template +2 -2
  53. package/dist/__tests__/init-plan.test.js +0 -340
  54. package/dist/agent-issues-query.d.ts +0 -16
  55. package/dist/agent-log-issue.d.ts +0 -10
  56. package/dist/agent-session-end.d.ts +0 -10
  57. package/dist/agent-session.d.ts +0 -10
  58. package/dist/backlog-prune.d.ts +0 -84
  59. package/dist/cli-entry-point.d.ts +0 -8
  60. package/dist/deps-add.d.ts +0 -91
  61. package/dist/deps-remove.d.ts +0 -17
  62. package/dist/docs-sync.d.ts +0 -50
  63. package/dist/file-delete.d.ts +0 -84
  64. package/dist/file-edit.d.ts +0 -82
  65. package/dist/file-read.d.ts +0 -92
  66. package/dist/file-write.d.ts +0 -90
  67. package/dist/flow-bottlenecks.d.ts +0 -16
  68. package/dist/flow-report.d.ts +0 -16
  69. package/dist/gates.d.ts +0 -94
  70. package/dist/git-branch.d.ts +0 -65
  71. package/dist/git-diff.d.ts +0 -58
  72. package/dist/git-log.d.ts +0 -69
  73. package/dist/git-status.d.ts +0 -58
  74. package/dist/guard-locked.d.ts +0 -62
  75. package/dist/guard-main-branch.d.ts +0 -50
  76. package/dist/guard-worktree-commit.d.ts +0 -59
  77. package/dist/index.d.ts +0 -10
  78. package/dist/init-plan.d.ts +0 -80
  79. package/dist/init-plan.js +0 -337
  80. package/dist/init.d.ts +0 -46
  81. package/dist/initiative-add-wu.d.ts +0 -22
  82. package/dist/initiative-bulk-assign-wus.d.ts +0 -16
  83. package/dist/initiative-create.d.ts +0 -28
  84. package/dist/initiative-edit.d.ts +0 -34
  85. package/dist/initiative-list.d.ts +0 -12
  86. package/dist/initiative-status.d.ts +0 -11
  87. package/dist/lumenflow-upgrade.d.ts +0 -103
  88. package/dist/mem-checkpoint.d.ts +0 -16
  89. package/dist/mem-cleanup.d.ts +0 -29
  90. package/dist/mem-create.d.ts +0 -17
  91. package/dist/mem-export.d.ts +0 -10
  92. package/dist/mem-inbox.d.ts +0 -35
  93. package/dist/mem-init.d.ts +0 -15
  94. package/dist/mem-ready.d.ts +0 -16
  95. package/dist/mem-signal.d.ts +0 -16
  96. package/dist/mem-start.d.ts +0 -16
  97. package/dist/mem-summarize.d.ts +0 -22
  98. package/dist/mem-triage.d.ts +0 -22
  99. package/dist/metrics-cli.d.ts +0 -90
  100. package/dist/metrics-snapshot.d.ts +0 -18
  101. package/dist/orchestrate-init-status.d.ts +0 -11
  102. package/dist/orchestrate-initiative.d.ts +0 -12
  103. package/dist/orchestrate-monitor.d.ts +0 -11
  104. package/dist/release.d.ts +0 -117
  105. package/dist/rotate-progress.d.ts +0 -48
  106. package/dist/session-coordinator.d.ts +0 -74
  107. package/dist/spawn-list.d.ts +0 -16
  108. package/dist/state-bootstrap.d.ts +0 -92
  109. package/dist/sync-templates.d.ts +0 -52
  110. package/dist/trace-gen.d.ts +0 -84
  111. package/dist/validate-agent-skills.d.ts +0 -50
  112. package/dist/validate-agent-sync.d.ts +0 -36
  113. package/dist/validate-backlog-sync.d.ts +0 -37
  114. package/dist/validate-skills-spec.d.ts +0 -40
  115. package/dist/validate.d.ts +0 -60
  116. package/dist/wu-block.d.ts +0 -16
  117. package/dist/wu-claim.d.ts +0 -74
  118. package/dist/wu-cleanup.d.ts +0 -35
  119. package/dist/wu-create.d.ts +0 -69
  120. package/dist/wu-delete.d.ts +0 -21
  121. package/dist/wu-deps.d.ts +0 -13
  122. package/dist/wu-done.d.ts +0 -225
  123. package/dist/wu-edit.d.ts +0 -63
  124. package/dist/wu-infer-lane.d.ts +0 -17
  125. package/dist/wu-preflight.d.ts +0 -47
  126. package/dist/wu-prune.d.ts +0 -16
  127. package/dist/wu-recover.d.ts +0 -37
  128. package/dist/wu-release.d.ts +0 -19
  129. package/dist/wu-repair.d.ts +0 -60
  130. package/dist/wu-spawn-completion.d.ts +0 -10
  131. package/dist/wu-spawn.d.ts +0 -192
  132. package/dist/wu-status.d.ts +0 -25
  133. package/dist/wu-unblock.d.ts +0 -16
  134. package/dist/wu-unlock-lane.d.ts +0 -19
  135. package/dist/wu-validate.d.ts +0 -16
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @file wu-validate-strict.test.ts
3
+ * Test suite for wu:validate strict validation behavior (WU-1329)
4
+ *
5
+ * WU-1329: Make wu:validate treat warnings as errors by default
6
+ *
7
+ * Tests:
8
+ * - Default strict mode behavior (warnings treated as errors)
9
+ * - --no-strict flag restores original behavior (warnings advisory)
10
+ * - Help text documents strict default
11
+ */
12
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
13
+ import { WU_OPTIONS, NEGATED_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
14
+ describe('wu:validate strict validation (WU-1329)', () => {
15
+ describe('WU_OPTIONS.noStrict configuration', () => {
16
+ // WU-1329: Verify the noStrict option is properly configured
17
+ it('should have noStrict option defined in WU_OPTIONS', () => {
18
+ expect(WU_OPTIONS.noStrict).toBeDefined();
19
+ expect(WU_OPTIONS.noStrict.name).toBe('noStrict');
20
+ expect(WU_OPTIONS.noStrict.flags).toBe('--no-strict');
21
+ expect(WU_OPTIONS.noStrict.isNegated).toBe(true);
22
+ });
23
+ it('should include description about bypassing strict validation', () => {
24
+ expect(WU_OPTIONS.noStrict.description).toContain('Bypass strict validation');
25
+ });
26
+ // WU-1329: Verify 'strict' is in NEGATED_OPTIONS array
27
+ it('should include strict in NEGATED_OPTIONS array', () => {
28
+ expect(NEGATED_OPTIONS).toContain('strict');
29
+ });
30
+ });
31
+ describe('strict mode logic', () => {
32
+ // WU-1329: Verify the strict mode conversion pattern
33
+ it('should default to strict=true when noStrict is undefined', () => {
34
+ const args = { noStrict: undefined };
35
+ const strict = !args.noStrict;
36
+ expect(strict).toBe(true);
37
+ });
38
+ it('should set strict=false when noStrict is true (--no-strict flag)', () => {
39
+ const args = { noStrict: true };
40
+ const strict = !args.noStrict;
41
+ expect(strict).toBe(false);
42
+ });
43
+ it('should set strict=true when noStrict is false (explicit)', () => {
44
+ const args = { noStrict: false };
45
+ const strict = !args.noStrict;
46
+ expect(strict).toBe(true);
47
+ });
48
+ });
49
+ describe('--no-strict logging behavior', () => {
50
+ let consoleSpy;
51
+ beforeEach(() => {
52
+ consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
53
+ });
54
+ afterEach(() => {
55
+ consoleSpy.mockRestore();
56
+ });
57
+ // WU-1329: The logging behavior is implemented in main() functions
58
+ // This test documents the expected behavior pattern using the spy
59
+ it('should log when --no-strict bypass is used', () => {
60
+ // Simulate the logging pattern from wu-validate.ts main()
61
+ const noStrict = true;
62
+ const LOG_PREFIX = '[wu:validate]';
63
+ const message = `${LOG_PREFIX} WARNING: strict validation bypassed (--no-strict). Warnings will be advisory only.`;
64
+ if (noStrict) {
65
+ // Use the spy to simulate logging
66
+ consoleSpy(message);
67
+ }
68
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('strict validation bypassed'));
69
+ });
70
+ it('should not log when strict mode is active', () => {
71
+ // When noStrict is false, no logging should occur
72
+ const noStrict = false;
73
+ if (noStrict) {
74
+ consoleSpy('This should not be called');
75
+ }
76
+ expect(consoleSpy).not.toHaveBeenCalled();
77
+ });
78
+ });
79
+ describe('validateSingleWU strict mode behavior', () => {
80
+ // WU-1329: These tests document the expected behavior
81
+ // The actual validation is done by calling the CLI command
82
+ it('should treat warnings as errors by default (strict=true)', () => {
83
+ // In strict mode, any warnings from completeness validation
84
+ // should become errors and cause validation to fail
85
+ const strict = true;
86
+ const warnings = ['Missing recommended field: notes'];
87
+ const errors = [];
88
+ if (strict && warnings.length > 0) {
89
+ errors.push(...warnings.map((w) => `[STRICT] ${w}`));
90
+ }
91
+ expect(errors.length).toBeGreaterThan(0);
92
+ expect(errors[0]).toContain('[STRICT]');
93
+ });
94
+ it('should allow warnings when strict=false (--no-strict)', () => {
95
+ // In non-strict mode, warnings should remain warnings
96
+ // and validation should pass
97
+ const strict = false;
98
+ const warnings = ['Missing recommended field: notes'];
99
+ const errors = [];
100
+ if (strict && warnings.length > 0) {
101
+ errors.push(...warnings.map((w) => `[STRICT] ${w}`));
102
+ }
103
+ expect(errors.length).toBe(0);
104
+ });
105
+ });
106
+ describe('help text documentation', () => {
107
+ // WU-1329: Verify help text documents strict default
108
+ it('should document that --no-strict bypasses strict validation', () => {
109
+ const expectedDescription = 'Bypass strict validation';
110
+ expect(WU_OPTIONS.noStrict.description).toContain(expectedDescription);
111
+ });
112
+ });
113
+ });
@@ -15,8 +15,9 @@
15
15
  */
16
16
  import { Command } from 'commander';
17
17
  import { getBottleneckAnalysis, } from '@lumenflow/metrics';
18
- import { buildDependencyGraphAsync, renderMermaid, } from '@lumenflow/core/dist/dependency-graph.js';
18
+ import { buildDependencyGraphAsync, renderMermaid } from '@lumenflow/core/dist/dependency-graph.js';
19
19
  import { die } from '@lumenflow/core/dist/error-handler.js';
20
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
20
21
  /** Log prefix for console output */
21
22
  const LOG_PREFIX = '[flow:bottlenecks]';
22
23
  /** Default bottleneck limit */
@@ -147,7 +148,8 @@ async function main() {
147
148
  const coreGraph = await buildDependencyGraphAsync();
148
149
  if (coreGraph.size === 0) {
149
150
  console.log(`${LOG_PREFIX} No WUs found in dependency graph.`);
150
- console.log(`${LOG_PREFIX} Ensure WU YAML files exist in docs/04-operations/tasks/wu/ with blocked_by/blocks fields.`);
151
+ // WU-1311: Use config-based WU directory path
152
+ console.log(`${LOG_PREFIX} Ensure WU YAML files exist in ${getConfig().directories.wuDir}/ with blocked_by/blocks fields.`);
151
153
  return;
152
154
  }
153
155
  console.log(`${LOG_PREFIX} Found ${coreGraph.size} WUs in graph`);
@@ -21,6 +21,7 @@ import { parse as parseYaml } from 'yaml';
21
21
  import { Command } from 'commander';
22
22
  import { generateFlowReport, TELEMETRY_PATHS, } from '@lumenflow/metrics';
23
23
  import { die } from '@lumenflow/core/dist/error-handler.js';
24
+ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
24
25
  /** Log prefix for console output */
25
26
  const LOG_PREFIX = '[flow:report]';
26
27
  /** Default report window in days */
@@ -30,8 +31,8 @@ const OUTPUT_FORMATS = {
30
31
  JSON: 'json',
31
32
  TABLE: 'table',
32
33
  };
33
- /** WU directory relative to repo root */
34
- const WU_DIR = 'docs/04-operations/tasks/wu';
34
+ /** WU directory relative to repo root (WU-1301: uses config-based paths) */
35
+ const WU_DIR = WU_PATHS.WU_DIR();
35
36
  /**
36
37
  * Parse command line arguments
37
38
  */
package/dist/gates.js CHANGED
@@ -45,6 +45,9 @@ import { access } from 'node:fs/promises';
45
45
  import path from 'node:path';
46
46
  import { emitGateEvent, getCurrentWU, getCurrentLane } from '@lumenflow/core/dist/telemetry.js';
47
47
  import { die } from '@lumenflow/core/dist/error-handler.js';
48
+ // WU-1299: Import WU YAML reader to get code_paths for docs-only filtering
49
+ import { readWURaw } from '@lumenflow/core/dist/wu-yaml.js';
50
+ import { createWuPaths } from '@lumenflow/core/dist/wu-paths.js';
48
51
  import { getChangedLintableFiles, isLintableFile } from '@lumenflow/core/dist/incremental-lint.js';
49
52
  import { buildVitestChangedArgs, isCodeFilePath } from '@lumenflow/core/dist/incremental-test.js';
50
53
  import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
@@ -65,6 +68,8 @@ import { runSystemMapValidation } from '@lumenflow/core/dist/system-map-validato
65
68
  import { loadLaneHealthConfig, resolveTestPolicy, } from '@lumenflow/core/dist/gates-config.js';
66
69
  // WU-1191: Lane health check
67
70
  import { runLaneHealthCheck } from './lane-health.js';
71
+ // WU-1315: Onboarding smoke test
72
+ import { runOnboardingSmokeTestGate } from './onboarding-smoke-test.js';
68
73
  import { BRANCHES, PACKAGES, PKG_MANAGER, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS, CLI_MODES, EXIT_CODES, FILE_SYSTEM, PRETTIER_ARGS, PRETTIER_FLAGS, } from '@lumenflow/core/dist/wu-constants.js';
69
74
  /**
70
75
  * WU-1087: Gates-specific option definitions for createWUParser.
@@ -250,6 +255,152 @@ export function resolveTestPlan({ isMainBranch, hasUntrackedCode, hasConfigChang
250
255
  }
251
256
  return { mode: 'incremental' };
252
257
  }
258
+ /**
259
+ * WU-1299: Extract package name from a single code path
260
+ *
261
+ * @param codePath - Single code path to parse
262
+ * @returns Package name or null if not a package/app path
263
+ */
264
+ function extractPackageFromPath(codePath) {
265
+ if (!codePath || typeof codePath !== 'string') {
266
+ return null;
267
+ }
268
+ const normalized = codePath.replace(/\\/g, '/');
269
+ // Handle packages/@scope/name/... or packages/name/...
270
+ if (normalized.startsWith('packages/')) {
271
+ const parts = normalized.slice('packages/'.length).split('/');
272
+ // Scoped package (@scope/name)
273
+ if (parts[0]?.startsWith('@') && parts[1]) {
274
+ return `${parts[0]}/${parts[1]}`;
275
+ }
276
+ // Unscoped package
277
+ if (parts[0]) {
278
+ return parts[0];
279
+ }
280
+ }
281
+ // Handle apps/name/...
282
+ if (normalized.startsWith('apps/')) {
283
+ const parts = normalized.slice('apps/'.length).split('/');
284
+ if (parts[0]) {
285
+ return parts[0];
286
+ }
287
+ }
288
+ return null;
289
+ }
290
+ /**
291
+ * WU-1299: Extract package/app names from code_paths
292
+ *
293
+ * Parses paths like:
294
+ * - packages/@lumenflow/cli/src/file.ts -> @lumenflow/cli
295
+ * - apps/web/src/file.ts -> web
296
+ *
297
+ * @param codePaths - Array of code paths from WU YAML
298
+ * @returns Array of unique package/app names
299
+ */
300
+ export function extractPackagesFromCodePaths(codePaths) {
301
+ if (!codePaths || !Array.isArray(codePaths) || codePaths.length === 0) {
302
+ return [];
303
+ }
304
+ const packages = new Set();
305
+ for (const codePath of codePaths) {
306
+ const pkg = extractPackageFromPath(codePath);
307
+ if (pkg) {
308
+ packages.add(pkg);
309
+ }
310
+ }
311
+ return Array.from(packages);
312
+ }
313
+ /**
314
+ * WU-1299: Resolve test plan for docs-only mode
315
+ *
316
+ * When --docs-only is passed, this determines whether to:
317
+ * - Skip tests entirely (no code packages in code_paths)
318
+ * - Run tests only for packages mentioned in code_paths
319
+ *
320
+ * @param options - Options including code_paths from WU YAML
321
+ * @returns DocsOnlyTestPlan indicating how to handle tests
322
+ */
323
+ export function resolveDocsOnlyTestPlan({ codePaths }) {
324
+ const packages = extractPackagesFromCodePaths(codePaths);
325
+ if (packages.length === 0) {
326
+ return {
327
+ mode: 'skip',
328
+ packages: [],
329
+ reason: 'no-code-packages',
330
+ };
331
+ }
332
+ return {
333
+ mode: 'filtered',
334
+ packages,
335
+ };
336
+ }
337
+ /**
338
+ * WU-1299: Format message for docs-only test skipping/filtering
339
+ *
340
+ * Provides clear messaging when tests are skipped or filtered in docs-only mode.
341
+ *
342
+ * @param plan - The docs-only test plan
343
+ * @returns Human-readable message explaining what's happening
344
+ */
345
+ export function formatDocsOnlySkipMessage(plan) {
346
+ if (plan.mode === 'skip') {
347
+ return '📝 docs-only mode: skipping all tests (no code packages in code_paths)';
348
+ }
349
+ const packageList = plan.packages.join(', ');
350
+ return `📝 docs-only mode: running tests only for packages in code_paths: ${packageList}`;
351
+ }
352
+ /**
353
+ * WU-1299: Load code_paths from current WU YAML
354
+ *
355
+ * Attempts to read the WU YAML file for the current WU (detected from git branch)
356
+ * and return its code_paths. Returns empty array if WU cannot be determined or
357
+ * YAML file doesn't exist.
358
+ *
359
+ * @param options - Options including optional cwd
360
+ * @returns Array of code_paths from WU YAML, or empty array if unavailable
361
+ */
362
+ export function loadCurrentWUCodePaths(options = {}) {
363
+ const cwd = options.cwd ?? process.cwd();
364
+ const wuId = getCurrentWU();
365
+ if (!wuId) {
366
+ return [];
367
+ }
368
+ try {
369
+ const wuPaths = createWuPaths({ projectRoot: cwd });
370
+ const wuYamlPath = wuPaths.WU(wuId);
371
+ const wuDoc = readWURaw(wuYamlPath);
372
+ if (wuDoc && Array.isArray(wuDoc.code_paths)) {
373
+ return wuDoc.code_paths.filter((p) => typeof p === 'string');
374
+ }
375
+ }
376
+ catch {
377
+ // WU YAML not found or unreadable - return empty array
378
+ }
379
+ return [];
380
+ }
381
+ /**
382
+ * WU-1299: Run filtered tests for docs-only mode
383
+ *
384
+ * When --docs-only is passed and code_paths contains packages, this runs tests
385
+ * only for those specific packages using turbo's --filter flag.
386
+ *
387
+ * @param options - Options including packages to test and agent log context
388
+ * @returns Result object with ok status and duration
389
+ */
390
+ async function runDocsOnlyFilteredTests({ packages, agentLog, }) {
391
+ const start = Date.now();
392
+ const logLine = makeGateLogger({ agentLog, useAgentMode: !!agentLog });
393
+ if (packages.length === 0) {
394
+ logLine('📝 docs-only mode: no packages to test, skipping');
395
+ return { ok: true, duration: Date.now() - start };
396
+ }
397
+ logLine(`\n> Tests (docs-only filtered: ${packages.join(', ')})\n`);
398
+ // Build turbo filter args for each package
399
+ // turbo supports --filter=@scope/package or --filter=package
400
+ const filterArgs = packages.map((pkg) => `--filter=${pkg}`);
401
+ const result = run(pnpmCmd('turbo', 'run', 'test', ...filterArgs), { agentLog });
402
+ return { ok: result.ok, duration: Date.now() - start };
403
+ }
253
404
  export function parsePrettierListOutput(output) {
254
405
  if (!output)
255
406
  return [];
@@ -935,6 +1086,12 @@ async function executeGates(opts) {
935
1086
  }
936
1087
  // Determine effective docs-only mode (explicit flag OR detected from changed files)
937
1088
  const effectiveDocsOnly = isDocsOnly || (riskTier && riskTier.isDocsOnly);
1089
+ // WU-1299: Load code_paths and compute docs-only test plan
1090
+ let docsOnlyTestPlan = null;
1091
+ if (effectiveDocsOnly) {
1092
+ const codePaths = loadCurrentWUCodePaths({ cwd: process.cwd() });
1093
+ docsOnlyTestPlan = resolveDocsOnlyTestPlan({ codePaths });
1094
+ }
938
1095
  // Determine which gates to run
939
1096
  // WU-2252: Invariants gate runs FIRST and is included in both docs-only and regular modes
940
1097
  const gates = effectiveDocsOnly
@@ -960,6 +1117,30 @@ async function executeGates(opts) {
960
1117
  run: (ctx) => runLaneHealthGate({ ...ctx, mode: laneHealthMode }),
961
1118
  warnOnly: laneHealthMode !== 'error',
962
1119
  },
1120
+ // WU-1315: Onboarding smoke test (init + wu:create validation)
1121
+ {
1122
+ name: GATE_NAMES.ONBOARDING_SMOKE_TEST,
1123
+ cmd: GATE_COMMANDS.ONBOARDING_SMOKE_TEST,
1124
+ },
1125
+ // WU-1299: Filtered tests for packages in code_paths (if any)
1126
+ // When docs-only mode has packages in code_paths, run tests only for those packages
1127
+ // This prevents pre-existing failures in unrelated packages from blocking
1128
+ ...(docsOnlyTestPlan && docsOnlyTestPlan.mode === 'filtered'
1129
+ ? [
1130
+ {
1131
+ name: GATE_NAMES.TEST,
1132
+ run: (ctx) => {
1133
+ // Safe access: docsOnlyTestPlan is guaranteed non-null by the outer conditional
1134
+ const pkgs = docsOnlyTestPlan.packages;
1135
+ return runDocsOnlyFilteredTests({
1136
+ packages: pkgs,
1137
+ agentLog: ctx.agentLog,
1138
+ });
1139
+ },
1140
+ warnOnly: !testsRequired,
1141
+ },
1142
+ ]
1143
+ : []),
963
1144
  ]
964
1145
  : [
965
1146
  // WU-2252: Invariants check runs first (non-bypassable)
@@ -989,6 +1170,11 @@ async function executeGates(opts) {
989
1170
  run: (ctx) => runLaneHealthGate({ ...ctx, mode: laneHealthMode }),
990
1171
  warnOnly: laneHealthMode !== 'error',
991
1172
  },
1173
+ // WU-1315: Onboarding smoke test (init + wu:create validation)
1174
+ {
1175
+ name: GATE_NAMES.ONBOARDING_SMOKE_TEST,
1176
+ cmd: GATE_COMMANDS.ONBOARDING_SMOKE_TEST,
1177
+ },
992
1178
  // WU-2062: Safety-critical tests ALWAYS run
993
1179
  // WU-1280: When tests_required=false (methodology.testing: none), failures only warn
994
1180
  {
@@ -1021,11 +1207,15 @@ async function executeGates(opts) {
1021
1207
  { name: GATE_NAMES.COVERAGE, cmd: GATE_COMMANDS.COVERAGE_GATE },
1022
1208
  ];
1023
1209
  if (effectiveDocsOnly) {
1210
+ // WU-1299: Show clear messaging about what's being skipped/run in docs-only mode
1211
+ const docsOnlyMessage = docsOnlyTestPlan && docsOnlyTestPlan.mode === 'filtered'
1212
+ ? formatDocsOnlySkipMessage(docsOnlyTestPlan)
1213
+ : '📝 Docs-only mode: skipping lint, typecheck, and all tests (no code packages in code_paths)';
1024
1214
  if (!useAgentMode) {
1025
- console.log('📝 Docs-only mode: skipping lint, typecheck, and tests\n');
1215
+ console.log(`${docsOnlyMessage}\n`);
1026
1216
  }
1027
1217
  else {
1028
- writeSync(agentLog.logFd, '📝 Docs-only mode: skipping lint, typecheck, and tests\n');
1218
+ writeSync(agentLog.logFd, `${docsOnlyMessage}\n`);
1029
1219
  }
1030
1220
  }
1031
1221
  // Run all gates sequentially
@@ -1107,6 +1297,16 @@ async function executeGates(opts) {
1107
1297
  : console,
1108
1298
  });
1109
1299
  }
1300
+ else if (gate.cmd === GATE_COMMANDS.ONBOARDING_SMOKE_TEST) {
1301
+ // WU-1315: Onboarding smoke test (init + wu:create validation)
1302
+ const logLine = useAgentMode
1303
+ ? (line) => writeSync(agentLog.logFd, `${line}\n`)
1304
+ : (line) => console.log(line);
1305
+ logLine('\n> Onboarding smoke test\n');
1306
+ result = await runOnboardingSmokeTestGate({
1307
+ logger: { log: logLine },
1308
+ });
1309
+ }
1110
1310
  else {
1111
1311
  result = run(gate.cmd, { agentLog });
1112
1312
  }