@lumenflow/cli 2.10.0 → 2.12.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 (255) hide show
  1. package/README.md +28 -18
  2. package/dist/__tests__/commands.test.js +198 -2
  3. package/dist/__tests__/gates-integration-tests.test.js +112 -0
  4. package/dist/__tests__/init-docs-structure.test.js +33 -0
  5. package/dist/__tests__/init.test.js +225 -0
  6. package/dist/__tests__/initiative-add-wu.test.js +71 -1
  7. package/dist/__tests__/no-beacon-references-docs.test.js +30 -0
  8. package/dist/__tests__/no-beacon-references.test.js +39 -0
  9. package/dist/__tests__/safe-git.test.js +4 -4
  10. package/dist/__tests__/wu-create-required-fields.test.js +22 -0
  11. package/dist/__tests__/wu-create.test.js +121 -0
  12. package/dist/__tests__/wu-done-docs-only-policy.test.js +20 -0
  13. package/dist/__tests__/wu-prep-default-exec.test.js +35 -0
  14. package/dist/__tests__/wu-prep.test.js +32 -0
  15. package/dist/__tests__/wu-validate.test.js +36 -0
  16. package/dist/agent-issues-query.js +1 -0
  17. package/dist/agent-issues-query.js.map +1 -0
  18. package/dist/agent-log-issue.js +1 -0
  19. package/dist/agent-log-issue.js.map +1 -0
  20. package/dist/agent-session-end.js +1 -0
  21. package/dist/agent-session-end.js.map +1 -0
  22. package/dist/agent-session.js +1 -0
  23. package/dist/agent-session.js.map +1 -0
  24. package/dist/backlog-prune.js +2 -0
  25. package/dist/backlog-prune.js.map +1 -0
  26. package/dist/cli-entry-point.js +1 -0
  27. package/dist/cli-entry-point.js.map +1 -0
  28. package/dist/commands/integrate.js +1 -0
  29. package/dist/commands/integrate.js.map +1 -0
  30. package/dist/commands.js +56 -77
  31. package/dist/commands.js.map +1 -0
  32. package/dist/deps-add.js +1 -0
  33. package/dist/deps-add.js.map +1 -0
  34. package/dist/deps-remove.js +1 -0
  35. package/dist/deps-remove.js.map +1 -0
  36. package/dist/docs-sync.js +1 -0
  37. package/dist/docs-sync.js.map +1 -0
  38. package/dist/doctor.js +1 -0
  39. package/dist/doctor.js.map +1 -0
  40. package/dist/file-delete.js +1 -0
  41. package/dist/file-delete.js.map +1 -0
  42. package/dist/file-edit.js +1 -0
  43. package/dist/file-edit.js.map +1 -0
  44. package/dist/file-read.js +1 -0
  45. package/dist/file-read.js.map +1 -0
  46. package/dist/file-write.js +1 -0
  47. package/dist/file-write.js.map +1 -0
  48. package/dist/flow-bottlenecks.js +1 -0
  49. package/dist/flow-bottlenecks.js.map +1 -0
  50. package/dist/flow-report.js +1 -0
  51. package/dist/flow-report.js.map +1 -0
  52. package/dist/gates.js +32 -20
  53. package/dist/gates.js.map +1 -0
  54. package/dist/git-branch.js +1 -0
  55. package/dist/git-branch.js.map +1 -0
  56. package/dist/git-diff.js +1 -0
  57. package/dist/git-diff.js.map +1 -0
  58. package/dist/git-log.js +1 -0
  59. package/dist/git-log.js.map +1 -0
  60. package/dist/git-status.js +1 -0
  61. package/dist/git-status.js.map +1 -0
  62. package/dist/guard-locked.js +1 -0
  63. package/dist/guard-locked.js.map +1 -0
  64. package/dist/guard-main-branch.js +1 -0
  65. package/dist/guard-main-branch.js.map +1 -0
  66. package/dist/guard-worktree-commit.js +1 -0
  67. package/dist/guard-worktree-commit.js.map +1 -0
  68. package/dist/hooks/auto-checkpoint-utils.js +52 -0
  69. package/dist/hooks/auto-checkpoint-utils.js.map +1 -0
  70. package/dist/hooks/enforcement-checks.js +1 -0
  71. package/dist/hooks/enforcement-checks.js.map +1 -0
  72. package/dist/hooks/enforcement-generator.js +185 -1
  73. package/dist/hooks/enforcement-generator.js.map +1 -0
  74. package/dist/hooks/enforcement-sync.js +91 -1
  75. package/dist/hooks/enforcement-sync.js.map +1 -0
  76. package/dist/hooks/index.js +1 -0
  77. package/dist/hooks/index.js.map +1 -0
  78. package/dist/index.js +1 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/init.js +176 -36
  81. package/dist/init.js.map +1 -0
  82. package/dist/initiative-add-wu.js +180 -59
  83. package/dist/initiative-add-wu.js.map +1 -0
  84. package/dist/initiative-bulk-assign-wus.js +3 -1
  85. package/dist/initiative-bulk-assign-wus.js.map +1 -0
  86. package/dist/initiative-create.js +1 -0
  87. package/dist/initiative-create.js.map +1 -0
  88. package/dist/initiative-edit.js +67 -32
  89. package/dist/initiative-edit.js.map +1 -0
  90. package/dist/initiative-list.js +1 -0
  91. package/dist/initiative-list.js.map +1 -0
  92. package/dist/initiative-plan.js +1 -0
  93. package/dist/initiative-plan.js.map +1 -0
  94. package/dist/initiative-remove-wu.js +1 -0
  95. package/dist/initiative-remove-wu.js.map +1 -0
  96. package/dist/initiative-status.js +1 -0
  97. package/dist/initiative-status.js.map +1 -0
  98. package/dist/lane-health.js +1 -0
  99. package/dist/lane-health.js.map +1 -0
  100. package/dist/lane-suggest.js +1 -0
  101. package/dist/lane-suggest.js.map +1 -0
  102. package/dist/lumenflow-upgrade.js +1 -0
  103. package/dist/lumenflow-upgrade.js.map +1 -0
  104. package/dist/mem-checkpoint.js +1 -0
  105. package/dist/mem-checkpoint.js.map +1 -0
  106. package/dist/mem-cleanup.js +114 -1
  107. package/dist/mem-cleanup.js.map +1 -0
  108. package/dist/mem-context.js +1 -0
  109. package/dist/mem-context.js.map +1 -0
  110. package/dist/mem-create.js +1 -0
  111. package/dist/mem-create.js.map +1 -0
  112. package/dist/mem-delete.js +1 -0
  113. package/dist/mem-delete.js.map +1 -0
  114. package/dist/mem-export.js +1 -0
  115. package/dist/mem-export.js.map +1 -0
  116. package/dist/mem-inbox.js +1 -0
  117. package/dist/mem-inbox.js.map +1 -0
  118. package/dist/mem-index.js +1 -0
  119. package/dist/mem-index.js.map +1 -0
  120. package/dist/mem-init.js +1 -0
  121. package/dist/mem-init.js.map +1 -0
  122. package/dist/mem-profile.js +1 -0
  123. package/dist/mem-profile.js.map +1 -0
  124. package/dist/mem-promote.js +1 -0
  125. package/dist/mem-promote.js.map +1 -0
  126. package/dist/mem-ready.js +1 -0
  127. package/dist/mem-ready.js.map +1 -0
  128. package/dist/mem-recover.js +1 -0
  129. package/dist/mem-recover.js.map +1 -0
  130. package/dist/mem-signal.js +12 -1
  131. package/dist/mem-signal.js.map +1 -0
  132. package/dist/mem-start.js +1 -0
  133. package/dist/mem-start.js.map +1 -0
  134. package/dist/mem-summarize.js +1 -0
  135. package/dist/mem-summarize.js.map +1 -0
  136. package/dist/mem-triage.js +2 -1
  137. package/dist/mem-triage.js.map +1 -0
  138. package/dist/merge-block.js +1 -0
  139. package/dist/merge-block.js.map +1 -0
  140. package/dist/metrics-cli.js +1 -0
  141. package/dist/metrics-cli.js.map +1 -0
  142. package/dist/metrics-snapshot.js +1 -0
  143. package/dist/metrics-snapshot.js.map +1 -0
  144. package/dist/onboarding-smoke-test.js +1 -0
  145. package/dist/onboarding-smoke-test.js.map +1 -0
  146. package/dist/orchestrate-init-status.js +1 -0
  147. package/dist/orchestrate-init-status.js.map +1 -0
  148. package/dist/orchestrate-initiative.js +20 -1
  149. package/dist/orchestrate-initiative.js.map +1 -0
  150. package/dist/orchestrate-monitor.js +1 -0
  151. package/dist/orchestrate-monitor.js.map +1 -0
  152. package/dist/plan-create.js +1 -0
  153. package/dist/plan-create.js.map +1 -0
  154. package/dist/plan-edit.js +1 -0
  155. package/dist/plan-edit.js.map +1 -0
  156. package/dist/plan-link.js +1 -0
  157. package/dist/plan-link.js.map +1 -0
  158. package/dist/plan-promote.js +1 -0
  159. package/dist/plan-promote.js.map +1 -0
  160. package/dist/public-manifest.js +773 -0
  161. package/dist/public-manifest.js.map +1 -0
  162. package/dist/release.js +3 -2
  163. package/dist/release.js.map +1 -0
  164. package/dist/rotate-progress.js +2 -1
  165. package/dist/rotate-progress.js.map +1 -0
  166. package/dist/session-coordinator.js +1 -0
  167. package/dist/session-coordinator.js.map +1 -0
  168. package/dist/shared-validators.js +78 -0
  169. package/dist/shared-validators.js.map +1 -0
  170. package/dist/signal-cleanup.js +1 -0
  171. package/dist/signal-cleanup.js.map +1 -0
  172. package/dist/spawn-list.js +1 -0
  173. package/dist/spawn-list.js.map +1 -0
  174. package/dist/state-bootstrap.js +1 -0
  175. package/dist/state-bootstrap.js.map +1 -0
  176. package/dist/state-cleanup.js +1 -0
  177. package/dist/state-cleanup.js.map +1 -0
  178. package/dist/state-doctor-fix.js +37 -1
  179. package/dist/state-doctor-fix.js.map +1 -0
  180. package/dist/state-doctor.js +11 -6
  181. package/dist/state-doctor.js.map +1 -0
  182. package/dist/sync-templates.js +1 -0
  183. package/dist/sync-templates.js.map +1 -0
  184. package/dist/trace-gen.js +1 -0
  185. package/dist/trace-gen.js.map +1 -0
  186. package/dist/validate-agent-skills.js +1 -0
  187. package/dist/validate-agent-skills.js.map +1 -0
  188. package/dist/validate-agent-sync.js +1 -0
  189. package/dist/validate-agent-sync.js.map +1 -0
  190. package/dist/validate-backlog-sync.js +1 -0
  191. package/dist/validate-backlog-sync.js.map +1 -0
  192. package/dist/validate-skills-spec.js +1 -0
  193. package/dist/validate-skills-spec.js.map +1 -0
  194. package/dist/validate.js +1 -0
  195. package/dist/validate.js.map +1 -0
  196. package/dist/wu-block.js +1 -0
  197. package/dist/wu-block.js.map +1 -0
  198. package/dist/wu-claim-repair-guidance.js +10 -0
  199. package/dist/wu-claim-repair-guidance.js.map +1 -0
  200. package/dist/wu-claim.js +40 -0
  201. package/dist/wu-claim.js.map +1 -0
  202. package/dist/wu-cleanup.js +2 -1
  203. package/dist/wu-cleanup.js.map +1 -0
  204. package/dist/wu-create.js +91 -25
  205. package/dist/wu-create.js.map +1 -0
  206. package/dist/wu-delete.js +3 -2
  207. package/dist/wu-delete.js.map +1 -0
  208. package/dist/wu-deps.js +2 -1
  209. package/dist/wu-deps.js.map +1 -0
  210. package/dist/wu-done-auto-cleanup.js +1 -0
  211. package/dist/wu-done-auto-cleanup.js.map +1 -0
  212. package/dist/wu-done-check.js +1 -0
  213. package/dist/wu-done-check.js.map +1 -0
  214. package/dist/wu-done-decay.js +88 -0
  215. package/dist/wu-done-decay.js.map +1 -0
  216. package/dist/wu-done.js +75 -18
  217. package/dist/wu-done.js.map +1 -0
  218. package/dist/wu-edit.js +2 -1
  219. package/dist/wu-edit.js.map +1 -0
  220. package/dist/wu-infer-lane.js +1 -0
  221. package/dist/wu-infer-lane.js.map +1 -0
  222. package/dist/wu-preflight.js +1 -0
  223. package/dist/wu-preflight.js.map +1 -0
  224. package/dist/wu-prep.js +105 -9
  225. package/dist/wu-prep.js.map +1 -0
  226. package/dist/wu-proto.js +12 -9
  227. package/dist/wu-proto.js.map +1 -0
  228. package/dist/wu-prune.js +1 -0
  229. package/dist/wu-prune.js.map +1 -0
  230. package/dist/wu-recover.js +54 -2
  231. package/dist/wu-recover.js.map +1 -0
  232. package/dist/wu-release.js +2 -1
  233. package/dist/wu-release.js.map +1 -0
  234. package/dist/wu-repair.js +1 -0
  235. package/dist/wu-repair.js.map +1 -0
  236. package/dist/wu-spawn-completion.js +1 -0
  237. package/dist/wu-spawn-completion.js.map +1 -0
  238. package/dist/wu-spawn.js +3 -2
  239. package/dist/wu-spawn.js.map +1 -0
  240. package/dist/wu-status.js +1 -0
  241. package/dist/wu-status.js.map +1 -0
  242. package/dist/wu-unblock.js +1 -0
  243. package/dist/wu-unblock.js.map +1 -0
  244. package/dist/wu-unlock-lane.js +1 -0
  245. package/dist/wu-unlock-lane.js.map +1 -0
  246. package/dist/wu-validate.js +58 -9
  247. package/dist/wu-validate.js.map +1 -0
  248. package/package.json +11 -21
  249. package/templates/core/.husky/pre-commit.template +5 -5
  250. package/templates/core/.mcp.json.template +8 -0
  251. package/templates/core/LUMENFLOW.md.template +2 -2
  252. package/templates/core/ai/onboarding/agent-safety-card.md.template +6 -6
  253. package/templates/core/ai/onboarding/lumenflow-force-usage.md.template +4 -4
  254. package/templates/core/ai/onboarding/vendor-support.md.template +73 -0
  255. package/templates/core/scripts/safe-git.template +29 -0
package/README.md CHANGED
@@ -101,7 +101,7 @@ This package provides CLI commands for the LumenFlow workflow framework, includi
101
101
 
102
102
  | Command | Description |
103
103
  | ---------------------------- | ------------------------------------------------------------------ |
104
- | `initiative-add-wu` | Link a WU to an initiative bidirectionally |
104
+ | `initiative-add-wu` | Link one or more WUs to an initiative bidirectionally |
105
105
  | `initiative-bulk-assign-wus` | Bulk-assign orphaned WUs to initiatives based on lane prefix rules |
106
106
  | `initiative-create` | Create a new Initiative with micro-worktree isolation (race-safe) |
107
107
  | `initiative-edit` | Edit Initiative YAML files with micro-worktree isolation |
@@ -131,21 +131,15 @@ This package provides CLI commands for the LumenFlow workflow framework, includi
131
131
 
132
132
  ### Verification & Gates
133
133
 
134
- | Command | Description |
135
- | ----------------- | ------------------------------------------------------------------------------------------ |
136
- | `gates` | Run quality gates with support for docs-only mode, incremental linting, and tiered testing |
137
- | `lumenflow-gates` | Alias for `gates` - run quality gates with support for docs-only mode and tiered testing |
134
+ | Command | Description |
135
+ | ------- | ------------------------------------------------------------------------------------------ |
136
+ | `gates` | Run quality gates with support for docs-only mode, incremental linting, and tiered testing |
138
137
 
139
138
  ### System & Setup
140
139
 
141
140
  | Command | Description |
142
141
  | -------------------------- | --------------------------------------------------------------------------------- |
143
142
  | `backlog-prune` | Backlog Prune Command |
144
- | `deps-add` | Deps Add CLI Command |
145
- | `deps-remove` | Deps Remove CLI Command |
146
- | `guard-locked` | |
147
- | `guard-main-branch` | Guard Main Branch CLI Tool |
148
- | `guard-worktree-commit` | |
149
143
  | `init-plan` | Link a plan file to an initiative |
150
144
  | `lumenflow` | Initialize LumenFlow in a project\n\n |
151
145
  | `lumenflow-commands` | List all available LumenFlow CLI commands |
@@ -162,19 +156,12 @@ This package provides CLI commands for the LumenFlow workflow framework, includi
162
156
  | `plan-edit` | Edit a section in a plan file |
163
157
  | `plan-link` | Link a plan file to a WU or initiative |
164
158
  | `plan-promote` | Promote a plan to approved status |
165
- | `rotate-progress` | Rotate Progress CLI Command |
166
- | `session-coordinator` | Session Coordinator CLI Command |
167
159
  | `signal-cleanup` | Prune old signals based on TTL policy to prevent unbounded growth |
168
160
  | `state-bootstrap` | State Bootstrap Command |
169
161
  | `state-cleanup` | Orchestrate all state cleanup: signals, memory, events |
170
162
  | `state-doctor` | Check state integrity and optionally repair issues |
171
163
  | `sync-templates` | Sync internal docs to CLI templates for release-cycle maintenance |
172
- | `trace-gen` | Trace Generator CLI Command |
173
164
  | `validate` | |
174
- | `validate-agent-skills` | |
175
- | `validate-agent-sync` | |
176
- | `validate-backlog-sync` | |
177
- | `validate-skills-spec` | |
178
165
 
179
166
  ### File & Git Operations
180
167
 
@@ -202,7 +189,7 @@ pnpm wu:done --id WU-123
202
189
 
203
190
  # Memory operations
204
191
  pnpm mem:checkpoint "Completed port definitions" --wu WU-123
205
- pnpm mem:inbox --unread
192
+ pnpm mem:inbox --since 10m
206
193
 
207
194
  # Initiative management
208
195
  pnpm initiative:status INIT-007
@@ -245,6 +232,27 @@ The CLI integrates with other LumenFlow packages:
245
232
  - `@lumenflow/agent` - Agent session management
246
233
  - `@lumenflow/initiatives` - Initiative tracking
247
234
 
235
+ ## MCP Server Setup (Claude Code)
236
+
237
+ LumenFlow provides an MCP (Model Context Protocol) server for deep integration with Claude Code.
238
+
239
+ When you run `lumenflow init --client claude`, a `.mcp.json` is automatically created:
240
+
241
+ ```json
242
+ {
243
+ "mcpServers": {
244
+ "lumenflow": {
245
+ "command": "npx",
246
+ "args": ["@lumenflow/mcp"]
247
+ }
248
+ }
249
+ }
250
+ ```
251
+
252
+ The `@lumenflow/mcp` server provides tools for WU lifecycle, memory coordination, and lane management directly within Claude Code.
253
+
254
+ See [AI Integrations](https://lumenflow.dev/guides/ai-integrations) for full MCP documentation.
255
+
248
256
  ## Documentation
249
257
 
250
258
  For complete documentation, see [lumenflow.dev](https://lumenflow.dev/reference/cli).
@@ -271,3 +279,5 @@ For detailed upgrade instructions, migration guides, and troubleshooting, see [U
271
279
  ## License
272
280
 
273
281
  Apache-2.0
282
+
283
+ <!-- MODIFIED -->
@@ -1,12 +1,23 @@
1
1
  /**
2
2
  * @file commands.test.ts
3
3
  * Tests for lumenflow commands discovery feature (WU-1378)
4
+ * Extended for public CLI manifest alignment (WU-1432)
4
5
  *
5
- * Tests the new commands subcommand that lists all available CLI commands
6
- * grouped by category with brief descriptions.
6
+ * Tests the commands subcommand that lists all available CLI commands
7
+ * grouped by category with brief descriptions. Also verifies alignment
8
+ * between public-manifest.ts, commands.ts, and package.json bin entries.
7
9
  */
8
10
  import { describe, it, expect } from 'vitest';
9
11
  import { getCommandsRegistry, formatCommandsOutput } from '../commands.js';
12
+ import { getPublicManifest, getPublicCommandNames, getPublicBinNames, isPublicCommand, } from '../public-manifest.js';
13
+ import { readFileSync } from 'node:fs';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { dirname, join } from 'node:path';
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const packageJsonPath = join(__dirname, '../../package.json');
18
+ // Script commands that are NOT CLI binaries (pnpm scripts only)
19
+ // These appear in the registry but should NOT be in the manifest
20
+ const SCRIPT_COMMANDS = new Set(['format', 'lint', 'typecheck', 'test', 'setup']);
10
21
  describe('lumenflow commands', () => {
11
22
  describe('getCommandsRegistry', () => {
12
23
  it('should return command categories', () => {
@@ -73,3 +84,188 @@ describe('lumenflow commands', () => {
73
84
  });
74
85
  });
75
86
  });
87
+ // ============================================================================
88
+ // WU-1432: Public CLI Manifest Tests
89
+ // ============================================================================
90
+ describe('public CLI manifest (WU-1432)', () => {
91
+ describe('getPublicManifest', () => {
92
+ it('should return an array of public commands', () => {
93
+ const manifest = getPublicManifest();
94
+ expect(manifest).toBeDefined();
95
+ expect(Array.isArray(manifest)).toBe(true);
96
+ expect(manifest.length).toBeGreaterThan(0);
97
+ });
98
+ it('should have required fields for each command', () => {
99
+ const manifest = getPublicManifest();
100
+ for (const cmd of manifest) {
101
+ expect(cmd.name).toBeDefined();
102
+ expect(typeof cmd.name).toBe('string');
103
+ expect(cmd.name.length).toBeGreaterThan(0);
104
+ expect(cmd.binName).toBeDefined();
105
+ expect(typeof cmd.binName).toBe('string');
106
+ expect(cmd.binName.length).toBeGreaterThan(0);
107
+ expect(cmd.description).toBeDefined();
108
+ expect(typeof cmd.description).toBe('string');
109
+ expect(cmd.description.length).toBeGreaterThan(0);
110
+ expect(cmd.category).toBeDefined();
111
+ expect(typeof cmd.category).toBe('string');
112
+ expect(cmd.category.length).toBeGreaterThan(0);
113
+ }
114
+ });
115
+ it('should include core public commands', () => {
116
+ const names = getPublicCommandNames();
117
+ // WU lifecycle - must be public
118
+ expect(names).toContain('wu:create');
119
+ expect(names).toContain('wu:claim');
120
+ expect(names).toContain('wu:done');
121
+ expect(names).toContain('wu:prep');
122
+ expect(names).toContain('wu:status');
123
+ // Gates - must be public
124
+ expect(names).toContain('gates');
125
+ expect(names).toContain('lumenflow');
126
+ // Memory - must be public
127
+ expect(names).toContain('mem:checkpoint');
128
+ expect(names).toContain('mem:inbox');
129
+ // Initiatives - must be public
130
+ expect(names).toContain('initiative:create');
131
+ expect(names).toContain('initiative:status');
132
+ });
133
+ it('should NOT include internal/maintainer commands', () => {
134
+ const names = getPublicCommandNames();
135
+ // Guards are internal
136
+ expect(names).not.toContain('guard-worktree-commit');
137
+ expect(names).not.toContain('guard-locked');
138
+ expect(names).not.toContain('guard-main-branch');
139
+ // Validation internals
140
+ expect(names).not.toContain('validate-agent-skills');
141
+ expect(names).not.toContain('validate-agent-sync');
142
+ expect(names).not.toContain('validate-backlog-sync');
143
+ expect(names).not.toContain('validate-skills-spec');
144
+ // Session internals
145
+ expect(names).not.toContain('session-coordinator');
146
+ expect(names).not.toContain('rotate-progress');
147
+ // Trace/debug internals
148
+ expect(names).not.toContain('trace-gen');
149
+ });
150
+ });
151
+ describe('isPublicCommand', () => {
152
+ it('should return true for public commands', () => {
153
+ expect(isPublicCommand('wu:create')).toBe(true);
154
+ expect(isPublicCommand('wu:claim')).toBe(true);
155
+ expect(isPublicCommand('gates')).toBe(true);
156
+ expect(isPublicCommand('lumenflow')).toBe(true);
157
+ });
158
+ it('should return false for internal commands', () => {
159
+ expect(isPublicCommand('guard-worktree-commit')).toBe(false);
160
+ expect(isPublicCommand('validate-agent-skills')).toBe(false);
161
+ expect(isPublicCommand('session-coordinator')).toBe(false);
162
+ });
163
+ });
164
+ });
165
+ describe('manifest alignment (WU-1432)', () => {
166
+ describe('commands.ts derives from manifest', () => {
167
+ it('should have all CLI registry commands in the public manifest (excluding script commands)', () => {
168
+ const registry = getCommandsRegistry();
169
+ const publicNames = new Set(getPublicCommandNames());
170
+ const registryNames = [];
171
+ for (const category of registry) {
172
+ for (const cmd of category.commands) {
173
+ // Skip script commands - they're not CLI binaries
174
+ if (!SCRIPT_COMMANDS.has(cmd.name)) {
175
+ registryNames.push(cmd.name);
176
+ }
177
+ }
178
+ }
179
+ // Every CLI command in the registry must be a public command
180
+ for (const name of registryNames) {
181
+ expect(publicNames.has(name), `Command "${name}" in registry but not in public manifest`).toBe(true);
182
+ }
183
+ });
184
+ it('should have matching descriptions between manifest and registry', () => {
185
+ const manifest = getPublicManifest();
186
+ const registry = getCommandsRegistry();
187
+ // Build a map of registry descriptions
188
+ const registryDescriptions = new Map();
189
+ for (const category of registry) {
190
+ for (const cmd of category.commands) {
191
+ registryDescriptions.set(cmd.name, cmd.description);
192
+ }
193
+ }
194
+ // For commands that appear in both, descriptions should match
195
+ for (const cmd of manifest) {
196
+ const registryDesc = registryDescriptions.get(cmd.name);
197
+ if (registryDesc) {
198
+ expect(cmd.description, `Description mismatch for "${cmd.name}"`).toBe(registryDesc);
199
+ }
200
+ }
201
+ });
202
+ it('should have matching categories between manifest and registry', () => {
203
+ const manifest = getPublicManifest();
204
+ const registry = getCommandsRegistry();
205
+ // Build a map of registry categories
206
+ const registryCategories = new Map();
207
+ for (const category of registry) {
208
+ for (const cmd of category.commands) {
209
+ registryCategories.set(cmd.name, category.name);
210
+ }
211
+ }
212
+ // For commands that appear in both, categories should match
213
+ for (const cmd of manifest) {
214
+ const registryCategory = registryCategories.get(cmd.name);
215
+ if (registryCategory) {
216
+ expect(cmd.category, `Category mismatch for "${cmd.name}"`).toBe(registryCategory);
217
+ }
218
+ }
219
+ });
220
+ });
221
+ describe('package.json bin alignment', () => {
222
+ it('should only include public commands in bin', () => {
223
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
224
+ const binEntries = Object.keys(packageJson.bin || {});
225
+ const publicBinNames = new Set(getPublicBinNames());
226
+ // Every bin entry should be in the public manifest
227
+ for (const binName of binEntries) {
228
+ expect(publicBinNames.has(binName), `Bin "${binName}" is in package.json but not in public manifest - should it be internal?`).toBe(true);
229
+ }
230
+ });
231
+ it('should have all public commands in bin', () => {
232
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
233
+ const binEntries = new Set(Object.keys(packageJson.bin || {}));
234
+ const publicManifest = getPublicManifest();
235
+ // Every public manifest command should have a bin entry
236
+ for (const cmd of publicManifest) {
237
+ expect(binEntries.has(cmd.binName), `Public command "${cmd.name}" (bin: ${cmd.binName}) missing from package.json bin`).toBe(true);
238
+ }
239
+ });
240
+ it('should have correct file paths in bin', () => {
241
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
242
+ const publicManifest = getPublicManifest();
243
+ for (const cmd of publicManifest) {
244
+ const binPath = packageJson.bin?.[cmd.binName];
245
+ expect(binPath, `Missing bin path for ${cmd.binName}`).toBeDefined();
246
+ expect(binPath, `Bin path for ${cmd.binName} should start with ./dist/`).toMatch(/^\.\/dist\//);
247
+ expect(binPath, `Bin path for ${cmd.binName} should end with .js`).toMatch(/\.js$/);
248
+ }
249
+ });
250
+ it('should not include internal commands in bin', () => {
251
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
252
+ const binEntries = Object.keys(packageJson.bin || {});
253
+ // These internal commands should NOT be in bin
254
+ const internalCommands = [
255
+ 'guard-worktree-commit',
256
+ 'guard-locked',
257
+ 'guard-main-branch',
258
+ 'validate-agent-skills',
259
+ 'validate-agent-sync',
260
+ 'validate-backlog-sync',
261
+ 'validate-skills-spec',
262
+ 'session-coordinator',
263
+ 'rotate-progress',
264
+ 'trace-gen',
265
+ ];
266
+ for (const internalCmd of internalCommands) {
267
+ expect(binEntries.includes(internalCmd), `Internal command "${internalCmd}" should NOT be in package.json bin`).toBe(false);
268
+ }
269
+ });
270
+ });
271
+ });
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @file gates-integration-tests.test.ts
3
+ * @description Tests for gates infrastructure fixes (WU-1415)
4
+ *
5
+ * Bug 1: vitest --include is not a valid CLI option
6
+ * Bug 2: docs-only turbo filter uses directory names instead of package names
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { extractPackagesFromCodePaths, resolveDocsOnlyTestPlan } from '../gates.js';
10
+ describe('WU-1415: Gates infrastructure fixes', () => {
11
+ describe('Bug 1: vitest integration test command', () => {
12
+ it('should NOT use --include flag (vitest does not support it)', async () => {
13
+ // Import the module to inspect the command construction
14
+ // We need to verify that runIntegrationTests uses valid vitest syntax
15
+ //
16
+ // vitest run accepts positional glob patterns, NOT --include flags:
17
+ // WRONG: vitest run --include='**/*.integration.*'
18
+ // RIGHT: vitest run '**/*.integration.*'
19
+ //
20
+ // This test ensures we're using the correct vitest CLI syntax
21
+ const gatesModule = await import('../gates.js');
22
+ // The command construction happens in runIntegrationTests
23
+ // We can't directly test the internal function, but we can verify
24
+ // via the module's exported constants or by checking the implementation
25
+ // doesn't contain --include
26
+ // Read the source to verify no --include in vitest commands
27
+ const fs = await import('fs');
28
+ const path = await import('path');
29
+ const gatesPath = path.join(import.meta.dirname, '..', 'gates.ts');
30
+ const source = fs.readFileSync(gatesPath, 'utf-8');
31
+ // Find the runIntegrationTests function and check it doesn't use --include
32
+ const integrationTestMatch = source.match(/function runIntegrationTests[\s\S]*?^}/m);
33
+ if (integrationTestMatch) {
34
+ const functionBody = integrationTestMatch[0];
35
+ // vitest run should NOT have --include flags
36
+ expect(functionBody).not.toMatch(/vitest.*--include/);
37
+ // Instead, glob patterns should be positional args or via proper config
38
+ // The fix should pass patterns directly: vitest run 'pattern1' 'pattern2'
39
+ }
40
+ });
41
+ });
42
+ describe('Bug 2: docs-only turbo filter', () => {
43
+ describe('extractPackagesFromCodePaths', () => {
44
+ it('should extract scoped package names from packages/ paths', () => {
45
+ const codePaths = [
46
+ 'packages/@lumenflow/cli/src/gates.ts',
47
+ 'packages/@lumenflow/core/src/index.ts',
48
+ ];
49
+ const packages = extractPackagesFromCodePaths(codePaths);
50
+ expect(packages).toContain('@lumenflow/cli');
51
+ expect(packages).toContain('@lumenflow/core');
52
+ });
53
+ it('should return empty array for apps/ paths that are not real turbo packages', () => {
54
+ // apps/docs/ directory name is 'docs' but the turbo package might be
55
+ // named differently (e.g., '@lumenflow/docs' or not exist at all)
56
+ //
57
+ // The current implementation returns 'docs' which causes turbo to fail:
58
+ // "No package found with name 'docs' in workspace"
59
+ //
60
+ // Fix: Either lookup actual package.json name or skip apps
61
+ const codePaths = ['apps/docs/src/content/docs/', 'apps/github-app/'];
62
+ const packages = extractPackagesFromCodePaths(codePaths);
63
+ // Current buggy behavior returns ['docs', 'github-app']
64
+ // Fixed behavior should either:
65
+ // - Return actual package names from package.json
66
+ // - Or return empty array (apps don't have turbo test tasks)
67
+ //
68
+ // For now, the fix should skip apps that aren't valid turbo packages
69
+ // because apps/docs has no test script and apps/github-app was deleted
70
+ expect(packages).not.toContain('docs');
71
+ expect(packages).not.toContain('github-app');
72
+ });
73
+ it('should handle mixed code_paths (packages + apps + docs)', () => {
74
+ const codePaths = [
75
+ 'packages/@lumenflow/cli/src/file.ts',
76
+ 'apps/docs/astro.config.mjs',
77
+ 'docs/DISTRIBUTION.md',
78
+ ];
79
+ const packages = extractPackagesFromCodePaths(codePaths);
80
+ // Should include the real package
81
+ expect(packages).toContain('@lumenflow/cli');
82
+ // Should NOT include apps (no valid turbo package)
83
+ expect(packages).not.toContain('docs');
84
+ // Should NOT include docs/ (not a package)
85
+ expect(packages.length).toBe(1);
86
+ });
87
+ it('should return empty array for pure docs paths', () => {
88
+ const codePaths = ['docs/01-product/product-lines.md', 'docs/DISTRIBUTION.md'];
89
+ const packages = extractPackagesFromCodePaths(codePaths);
90
+ expect(packages).toEqual([]);
91
+ });
92
+ });
93
+ describe('resolveDocsOnlyTestPlan', () => {
94
+ it('should return skip mode for pure documentation WUs', () => {
95
+ const plan = resolveDocsOnlyTestPlan({
96
+ codePaths: ['docs/README.md', 'apps/docs/content/'],
97
+ });
98
+ expect(plan.mode).toBe('skip');
99
+ expect(plan.packages).toEqual([]);
100
+ });
101
+ it('should return filtered mode only for valid package paths', () => {
102
+ const plan = resolveDocsOnlyTestPlan({
103
+ codePaths: ['packages/@lumenflow/cli/src/gates.ts', 'apps/docs/content/'],
104
+ });
105
+ expect(plan.mode).toBe('filtered');
106
+ expect(plan.packages).toContain('@lumenflow/cli');
107
+ // apps/docs should not be included
108
+ expect(plan.packages).not.toContain('docs');
109
+ });
110
+ });
111
+ });
112
+ });
@@ -67,6 +67,24 @@ describe('docs-structure', () => {
67
67
  expect(fs.existsSync(path.join(tempDir, 'docs', 'tasks'))).toBe(true);
68
68
  expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS))).toBe(false);
69
69
  });
70
+ it('should scaffold WU template with resilient defaults (simple)', async () => {
71
+ const options = {
72
+ force: false,
73
+ full: true,
74
+ docsStructure: SIMPLE_DOCS_STRUCTURE,
75
+ };
76
+ await scaffoldProject(tempDir, options);
77
+ const templatePath = path.join(tempDir, 'docs', 'tasks', 'templates', 'wu-template.yaml');
78
+ expect(fs.existsSync(templatePath)).toBe(true);
79
+ const content = fs.readFileSync(templatePath, 'utf-8');
80
+ // Feature WUs should reference plan protocol by default (plan-less friendly).
81
+ expect(content).toContain('lumenflow://plans/WU-XXX-plan.md');
82
+ // Ensure non-empty notes to avoid strict spec-linter failures out of the box.
83
+ expect(content).not.toContain("notes: ''");
84
+ expect(content).toContain('notes:');
85
+ // Ensure manual test stub exists to prevent empty tests failures.
86
+ expect(content).toContain('Manual check:');
87
+ });
70
88
  it('should scaffold arc42 structure with --docs-structure arc42', async () => {
71
89
  const options = {
72
90
  force: false,
@@ -77,6 +95,21 @@ describe('docs-structure', () => {
77
95
  // Arc42 structure: docs/04-operations/tasks
78
96
  expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS, 'tasks'))).toBe(true);
79
97
  });
98
+ it('should scaffold WU template with resilient defaults (arc42)', async () => {
99
+ const options = {
100
+ force: false,
101
+ full: true,
102
+ docsStructure: ARC42_DOCS_STRUCTURE,
103
+ };
104
+ await scaffoldProject(tempDir, options);
105
+ const templatePath = path.join(tempDir, 'docs', DOCS_04_OPERATIONS, 'tasks', 'templates', 'wu-template.yaml');
106
+ expect(fs.existsSync(templatePath)).toBe(true);
107
+ const content = fs.readFileSync(templatePath, 'utf-8');
108
+ expect(content).toContain('lumenflow://plans/WU-XXX-plan.md');
109
+ expect(content).not.toContain("notes: ''");
110
+ expect(content).toContain('notes:');
111
+ expect(content).toContain('Manual check:');
112
+ });
80
113
  it('should auto-detect arc42 when docs/04-operations exists', async () => {
81
114
  // Create existing arc42 structure
82
115
  fs.mkdirSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS), { recursive: true });