@lumenflow/cli 2.0.0 → 2.1.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.
- package/README.md +19 -0
- package/dist/__tests__/lumenflow-upgrade.test.js +165 -12
- package/dist/docs-sync.js +72 -326
- package/dist/lumenflow-upgrade.js +124 -34
- package/dist/sync-templates.js +212 -0
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -140,6 +140,25 @@ The CLI integrates with other LumenFlow packages:
|
|
|
140
140
|
|
|
141
141
|
For complete documentation, see [lumenflow.dev](https://lumenflow.dev/reference/cli).
|
|
142
142
|
|
|
143
|
+
## Upgrading
|
|
144
|
+
|
|
145
|
+
To upgrade LumenFlow packages:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Check for available updates
|
|
149
|
+
pnpm outdated @lumenflow/*
|
|
150
|
+
|
|
151
|
+
# Update all LumenFlow packages
|
|
152
|
+
pnpm update @lumenflow/cli @lumenflow/core @lumenflow/memory @lumenflow/agent @lumenflow/initiatives
|
|
153
|
+
|
|
154
|
+
# Sync documentation and templates
|
|
155
|
+
pnpm exec lumenflow docs:sync
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Important**: Always run `docs:sync` after upgrading to update agent onboarding documentation, workflow rules, and vendor-specific configurations.
|
|
159
|
+
|
|
160
|
+
For detailed upgrade instructions, migration guides, and troubleshooting, see [UPGRADING.md](https://lumenflow.dev/upgrading).
|
|
161
|
+
|
|
143
162
|
## License
|
|
144
163
|
|
|
145
164
|
Apache-2.0
|
|
@@ -3,18 +3,50 @@
|
|
|
3
3
|
* Tests for lumenflow-upgrade CLI command
|
|
4
4
|
*
|
|
5
5
|
* WU-1112: INIT-003 Phase 6 - Migrate remaining Tier 1 tools
|
|
6
|
+
* WU-1127: Add micro-worktree isolation pattern
|
|
6
7
|
*
|
|
7
8
|
* lumenflow-upgrade updates all @lumenflow/* packages to latest versions.
|
|
8
9
|
* Key requirements:
|
|
9
|
-
* - Uses worktree pattern (
|
|
10
|
+
* - Uses micro-worktree pattern (atomic changes to main without requiring user worktree)
|
|
10
11
|
* - Checks all 7 @lumenflow/* packages
|
|
12
|
+
* - Supports --dry-run and --latest flags
|
|
11
13
|
*/
|
|
12
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
// Mock modules with inline factories (no external references)
|
|
17
|
+
vi.mock('@lumenflow/core/dist/micro-worktree.js', () => ({
|
|
18
|
+
withMicroWorktree: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
vi.mock('@lumenflow/core/dist/git-adapter.js', () => ({
|
|
21
|
+
getGitForCwd: vi.fn(),
|
|
22
|
+
}));
|
|
23
|
+
vi.mock('node:child_process', async (importOriginal) => {
|
|
24
|
+
const actual = await importOriginal();
|
|
25
|
+
return {
|
|
26
|
+
...actual,
|
|
27
|
+
execSync: vi.fn(),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
// Import mocked modules to access mock functions
|
|
31
|
+
import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
32
|
+
import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
|
|
33
|
+
// Import functions under test after mocks are set up
|
|
34
|
+
import { parseUpgradeArgs, LUMENFLOW_PACKAGES, buildUpgradeCommands, executeUpgradeInMicroWorktree, validateMainCheckout, } from '../lumenflow-upgrade.js';
|
|
35
|
+
// Cast mocks for TypeScript
|
|
36
|
+
const mockWithMicroWorktree = withMicroWorktree;
|
|
37
|
+
const mockGetGitForCwd = getGitForCwd;
|
|
38
|
+
const mockExecSync = execSync;
|
|
15
39
|
describe('lumenflow-upgrade', () => {
|
|
16
40
|
beforeEach(() => {
|
|
17
41
|
vi.clearAllMocks();
|
|
42
|
+
// Default mock behavior for git adapter
|
|
43
|
+
mockGetGitForCwd.mockReturnValue({
|
|
44
|
+
raw: vi.fn().mockResolvedValue('main'),
|
|
45
|
+
getStatus: vi.fn().mockResolvedValue(''),
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
vi.restoreAllMocks();
|
|
18
50
|
});
|
|
19
51
|
describe('LUMENFLOW_PACKAGES constant', () => {
|
|
20
52
|
it('should include all 7 @lumenflow/* packages', () => {
|
|
@@ -94,14 +126,135 @@ describe('lumenflow-upgrade', () => {
|
|
|
94
126
|
expect(packageCount).toBe(7);
|
|
95
127
|
});
|
|
96
128
|
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
129
|
+
// WU-1127: Tests for micro-worktree isolation pattern
|
|
130
|
+
describe('validateMainCheckout', () => {
|
|
131
|
+
let originalCwd;
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
originalCwd = process.cwd;
|
|
134
|
+
});
|
|
135
|
+
afterEach(() => {
|
|
136
|
+
process.cwd = originalCwd;
|
|
137
|
+
});
|
|
138
|
+
it('should return valid when on main branch and not in worktree', async () => {
|
|
139
|
+
// Mock process.cwd to be on main checkout (not worktree)
|
|
140
|
+
process.cwd = vi.fn().mockReturnValue('/path/to/repo');
|
|
141
|
+
mockGetGitForCwd.mockReturnValue({
|
|
142
|
+
raw: vi.fn().mockResolvedValue('main'),
|
|
143
|
+
getStatus: vi.fn().mockResolvedValue(''),
|
|
144
|
+
});
|
|
145
|
+
const result = await validateMainCheckout();
|
|
146
|
+
expect(result.valid).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
it('should return invalid when not on main branch', async () => {
|
|
149
|
+
// Mock process.cwd to be on main checkout (not worktree)
|
|
150
|
+
process.cwd = vi.fn().mockReturnValue('/path/to/repo');
|
|
151
|
+
mockGetGitForCwd.mockReturnValue({
|
|
152
|
+
raw: vi.fn().mockResolvedValue('lane/framework-cli/wu-123'),
|
|
153
|
+
getStatus: vi.fn().mockResolvedValue(''),
|
|
154
|
+
});
|
|
155
|
+
const result = await validateMainCheckout();
|
|
156
|
+
expect(result.valid).toBe(false);
|
|
157
|
+
expect(result.error).toContain('must be run from main checkout');
|
|
158
|
+
});
|
|
159
|
+
it('should return invalid when in a worktree directory', async () => {
|
|
160
|
+
// Mock process.cwd() to be in a worktree
|
|
161
|
+
process.cwd = vi
|
|
162
|
+
.fn()
|
|
163
|
+
.mockReturnValue('/path/to/repo/worktrees/some-wu');
|
|
164
|
+
mockGetGitForCwd.mockReturnValue({
|
|
165
|
+
raw: vi.fn().mockResolvedValue('main'),
|
|
166
|
+
getStatus: vi.fn().mockResolvedValue(''),
|
|
167
|
+
});
|
|
168
|
+
const result = await validateMainCheckout();
|
|
169
|
+
expect(result.valid).toBe(false);
|
|
170
|
+
expect(result.error).toContain('worktree');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('executeUpgradeInMicroWorktree', () => {
|
|
174
|
+
it('should call withMicroWorktree with correct operation name', async () => {
|
|
175
|
+
mockWithMicroWorktree.mockResolvedValue({});
|
|
176
|
+
mockExecSync.mockReturnValue('');
|
|
177
|
+
const args = { version: '2.1.0' };
|
|
178
|
+
await executeUpgradeInMicroWorktree(args);
|
|
179
|
+
expect(mockWithMicroWorktree).toHaveBeenCalledTimes(1);
|
|
180
|
+
expect(mockWithMicroWorktree).toHaveBeenCalledWith(expect.objectContaining({
|
|
181
|
+
operation: 'lumenflow-upgrade',
|
|
182
|
+
}));
|
|
183
|
+
});
|
|
184
|
+
it('should use a unique ID for micro-worktree based on timestamp', async () => {
|
|
185
|
+
mockWithMicroWorktree.mockResolvedValue({});
|
|
186
|
+
mockExecSync.mockReturnValue('');
|
|
187
|
+
const args = { latest: true };
|
|
188
|
+
await executeUpgradeInMicroWorktree(args);
|
|
189
|
+
// ID should be a timestamp-like string
|
|
190
|
+
expect(mockWithMicroWorktree).toHaveBeenCalledWith(expect.objectContaining({
|
|
191
|
+
id: expect.stringMatching(/^upgrade-\d+$/),
|
|
192
|
+
}));
|
|
193
|
+
});
|
|
194
|
+
it('should execute pnpm add in the micro-worktree', async () => {
|
|
195
|
+
mockWithMicroWorktree.mockImplementation(async (options) => {
|
|
196
|
+
// Simulate calling the execute function with a worktree path
|
|
197
|
+
return options.execute({ worktreePath: '/tmp/test-worktree' });
|
|
198
|
+
});
|
|
199
|
+
const args = { version: '2.1.0' };
|
|
200
|
+
await executeUpgradeInMicroWorktree(args);
|
|
201
|
+
// Verify pnpm add was executed with correct cwd
|
|
202
|
+
expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('pnpm add'), expect.objectContaining({
|
|
203
|
+
cwd: '/tmp/test-worktree',
|
|
204
|
+
}));
|
|
205
|
+
});
|
|
206
|
+
it('should include all 7 packages in the pnpm add command', async () => {
|
|
207
|
+
mockWithMicroWorktree.mockImplementation(async (options) => {
|
|
208
|
+
return options.execute({ worktreePath: '/tmp/test-worktree' });
|
|
209
|
+
});
|
|
210
|
+
const args = { version: '2.1.0' };
|
|
211
|
+
await executeUpgradeInMicroWorktree(args);
|
|
212
|
+
// Get the command that was executed
|
|
213
|
+
const execCall = mockExecSync.mock.calls[0][0];
|
|
214
|
+
expect(typeof execCall).toBe('string');
|
|
215
|
+
// Verify all 7 packages are included
|
|
216
|
+
for (const pkg of LUMENFLOW_PACKAGES) {
|
|
217
|
+
expect(execCall).toContain(`${pkg}@2.1.0`);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
it('should return appropriate commit message and files', async () => {
|
|
221
|
+
let executeResult;
|
|
222
|
+
mockWithMicroWorktree.mockImplementation(async (options) => {
|
|
223
|
+
executeResult = await options.execute({ worktreePath: '/tmp/test-worktree' });
|
|
224
|
+
return executeResult;
|
|
225
|
+
});
|
|
226
|
+
const args = { version: '2.1.0' };
|
|
227
|
+
await executeUpgradeInMicroWorktree(args);
|
|
228
|
+
expect(executeResult).toBeDefined();
|
|
229
|
+
expect(executeResult.commitMessage).toContain('upgrade @lumenflow packages');
|
|
230
|
+
expect(executeResult.files).toContain('package.json');
|
|
231
|
+
expect(executeResult.files).toContain('pnpm-lock.yaml');
|
|
232
|
+
});
|
|
233
|
+
it('should use --latest version specifier when latest flag is set', async () => {
|
|
234
|
+
mockWithMicroWorktree.mockImplementation(async (options) => {
|
|
235
|
+
return options.execute({ worktreePath: '/tmp/test-worktree' });
|
|
236
|
+
});
|
|
237
|
+
const args = { latest: true };
|
|
238
|
+
await executeUpgradeInMicroWorktree(args);
|
|
239
|
+
const execCall = mockExecSync.mock.calls[0][0];
|
|
240
|
+
expect(execCall).toContain('@lumenflow/core@latest');
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
describe('dry-run mode', () => {
|
|
244
|
+
it('should not call withMicroWorktree when dryRun is true', async () => {
|
|
245
|
+
const args = { version: '2.1.0', dryRun: true };
|
|
246
|
+
// In dry-run mode, executeUpgradeInMicroWorktree should not be called
|
|
247
|
+
// This is handled by the main() function checking dryRun before calling execute
|
|
248
|
+
// We just verify the function exists and can be called
|
|
249
|
+
expect(typeof executeUpgradeInMicroWorktree).toBe('function');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
describe('legacy worktree validation removal', () => {
|
|
253
|
+
it('should not require user to be in a worktree', () => {
|
|
254
|
+
// The old implementation required users to be inside a worktree
|
|
255
|
+
// The new implementation uses micro-worktree and runs from main checkout
|
|
256
|
+
// This test verifies the old validateWorktreeContext is no longer used
|
|
257
|
+
expect(typeof validateMainCheckout).toBe('function');
|
|
105
258
|
});
|
|
106
259
|
});
|
|
107
260
|
});
|
package/dist/docs-sync.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* @file docs-sync.ts
|
|
3
3
|
* LumenFlow docs:sync command for syncing agent docs to existing projects (WU-1083)
|
|
4
4
|
* WU-1085: Added createWUParser for proper --help support
|
|
5
|
+
* WU-1124: Refactored to read templates from bundled files (INIT-004 Phase 2)
|
|
5
6
|
*/
|
|
6
7
|
import * as fs from 'node:fs';
|
|
7
8
|
import * as path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
8
10
|
import { createWUParser, WU_OPTIONS } from '@lumenflow/core';
|
|
9
11
|
/**
|
|
10
12
|
* WU-1085: CLI option definitions for docs-sync command
|
|
@@ -33,6 +35,40 @@ export function parseDocsSyncOptions() {
|
|
|
33
35
|
vendor: opts.vendor ?? 'claude',
|
|
34
36
|
};
|
|
35
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* WU-1124: Get the templates directory path
|
|
40
|
+
* Templates are bundled with the CLI package at dist/templates/
|
|
41
|
+
* Falls back to src/templates/ for development
|
|
42
|
+
*/
|
|
43
|
+
export function getTemplatesDir() {
|
|
44
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
45
|
+
const __dirname = path.dirname(__filename);
|
|
46
|
+
// In production: dist/docs-sync.js -> templates/
|
|
47
|
+
// In development: src/docs-sync.ts -> ../templates/
|
|
48
|
+
const distTemplates = path.join(__dirname, '..', 'templates');
|
|
49
|
+
if (fs.existsSync(distTemplates)) {
|
|
50
|
+
return distTemplates;
|
|
51
|
+
}
|
|
52
|
+
// Fallback for tests running from src
|
|
53
|
+
const srcTemplates = path.join(__dirname, '..', 'templates');
|
|
54
|
+
if (fs.existsSync(srcTemplates)) {
|
|
55
|
+
return srcTemplates;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Templates directory not found at ${distTemplates}`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* WU-1124: Load a template file from the bundled templates directory
|
|
61
|
+
* @param templatePath - Relative path from templates directory (e.g., 'core/ai/onboarding/quick-ref-commands.md.template')
|
|
62
|
+
* @returns Template content as string
|
|
63
|
+
*/
|
|
64
|
+
export function loadTemplate(templatePath) {
|
|
65
|
+
const templatesDir = getTemplatesDir();
|
|
66
|
+
const fullPath = path.join(templatesDir, templatePath);
|
|
67
|
+
if (!fs.existsSync(fullPath)) {
|
|
68
|
+
throw new Error(`Template not found: ${templatePath} (looked at ${fullPath})`);
|
|
69
|
+
}
|
|
70
|
+
return fs.readFileSync(fullPath, 'utf-8');
|
|
71
|
+
}
|
|
36
72
|
/**
|
|
37
73
|
* Get current date in YYYY-MM-DD format
|
|
38
74
|
*/
|
|
@@ -77,317 +113,29 @@ async function createFile(filePath, content, force, result, targetDir) {
|
|
|
77
113
|
fs.writeFileSync(filePath, content);
|
|
78
114
|
result.created.push(relativePath);
|
|
79
115
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| Command | Description |
|
|
101
|
-
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
102
|
-
| \`pnpm wu:create --id WU-XXX --lane <Lane> --title "Title" --description "..." --acceptance "..." --code-paths "path" --test-paths-unit "path" --exposure backend-only --spec-refs "~/.lumenflow/plans/WU-XXX.md"\` | Create new WU |
|
|
103
|
-
| \`pnpm wu:claim --id WU-XXX --lane <Lane>\` | Claim WU (creates worktree) |
|
|
104
|
-
| \`pnpm wu:done --id WU-XXX\` | Complete WU (merge, stamp, cleanup) |
|
|
105
|
-
| \`pnpm wu:block --id WU-XXX --reason "Reason"\` | Block a WU |
|
|
106
|
-
| \`pnpm wu:unblock --id WU-XXX\` | Unblock a WU |
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## Gates
|
|
111
|
-
|
|
112
|
-
| Command | Description |
|
|
113
|
-
| ------------------------ | -------------------------- |
|
|
114
|
-
| \`pnpm gates\` | Run all quality gates |
|
|
115
|
-
| \`pnpm gates --docs-only\` | Run gates for docs changes |
|
|
116
|
-
| \`pnpm format\` | Format all files |
|
|
117
|
-
| \`pnpm lint\` | Run linter |
|
|
118
|
-
| \`pnpm typecheck\` | Run TypeScript check |
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## File Paths
|
|
123
|
-
|
|
124
|
-
| Path | Description |
|
|
125
|
-
| ----------------------------------------- | -------------------- |
|
|
126
|
-
| \`docs/04-operations/tasks/wu/WU-XXX.yaml\` | WU specification |
|
|
127
|
-
| \`docs/04-operations/tasks/status.md\` | Current status board |
|
|
128
|
-
| \`.lumenflow/stamps/WU-XXX.done\` | Completion stamp |
|
|
129
|
-
| \`worktrees/<lane>-wu-xxx/\` | Worktree directory |
|
|
130
|
-
`;
|
|
131
|
-
const FIRST_WU_MISTAKES_TEMPLATE = `# First WU Mistakes
|
|
132
|
-
|
|
133
|
-
**Last updated:** {{DATE}}
|
|
134
|
-
|
|
135
|
-
Common mistakes agents make on their first WU, and how to avoid them.
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## Mistake 1: Not Using Worktrees
|
|
140
|
-
|
|
141
|
-
### Wrong
|
|
142
|
-
|
|
143
|
-
\`\`\`bash
|
|
144
|
-
# Working directly in main
|
|
145
|
-
vim src/feature.ts
|
|
146
|
-
git commit -m "feat: add feature"
|
|
147
|
-
git push origin main
|
|
148
|
-
\`\`\`
|
|
149
|
-
|
|
150
|
-
### Right
|
|
151
|
-
|
|
152
|
-
\`\`\`bash
|
|
153
|
-
# Claim first, then work in worktree
|
|
154
|
-
pnpm wu:claim --id WU-123 --lane Core
|
|
155
|
-
cd worktrees/core-wu-123
|
|
156
|
-
vim src/feature.ts
|
|
157
|
-
git commit -m "feat: add feature"
|
|
158
|
-
git push origin lane/core/wu-123
|
|
159
|
-
cd /path/to/main
|
|
160
|
-
pnpm wu:done --id WU-123
|
|
161
|
-
\`\`\`
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## Mistake 2: Forgetting to Run wu:done
|
|
166
|
-
|
|
167
|
-
**TL;DR:** After gates pass, ALWAYS run \`pnpm wu:done --id WU-XXX\`.
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## Mistake 3: Working Outside code_paths
|
|
172
|
-
|
|
173
|
-
Only edit files within the specified \`code_paths\`.
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
## Quick Checklist
|
|
178
|
-
|
|
179
|
-
- [ ] Claim the WU with \`pnpm wu:claim\`
|
|
180
|
-
- [ ] cd to the worktree IMMEDIATELY
|
|
181
|
-
- [ ] Work only in the worktree
|
|
182
|
-
- [ ] Run gates before wu:done
|
|
183
|
-
- [ ] ALWAYS run wu:done
|
|
184
|
-
`;
|
|
185
|
-
const TROUBLESHOOTING_WU_DONE_TEMPLATE = `# Troubleshooting: wu:done Not Run
|
|
186
|
-
|
|
187
|
-
**Last updated:** {{DATE}}
|
|
188
|
-
|
|
189
|
-
This is the most common mistake agents make.
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## The Fix
|
|
194
|
-
|
|
195
|
-
### Rule: ALWAYS Run wu:done
|
|
196
|
-
|
|
197
|
-
After gates pass, you MUST run:
|
|
198
|
-
|
|
199
|
-
\`\`\`bash
|
|
200
|
-
cd /path/to/main
|
|
201
|
-
pnpm wu:done --id WU-XXX
|
|
202
|
-
\`\`\`
|
|
203
|
-
|
|
204
|
-
Do NOT:
|
|
205
|
-
|
|
206
|
-
- Ask "Should I run wu:done?"
|
|
207
|
-
- Write "To Complete: pnpm wu:done"
|
|
208
|
-
- Wait for permission
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
## What wu:done Does
|
|
213
|
-
|
|
214
|
-
1. Validates the worktree exists and has commits
|
|
215
|
-
2. Runs gates in the worktree (not main)
|
|
216
|
-
3. Fast-forward merges to main
|
|
217
|
-
4. Creates the done stamp
|
|
218
|
-
5. Updates status and backlog docs
|
|
219
|
-
6. Removes the worktree
|
|
220
|
-
7. Pushes to origin
|
|
221
|
-
`;
|
|
222
|
-
const AGENT_SAFETY_CARD_TEMPLATE = `# Agent Safety Card
|
|
223
|
-
|
|
224
|
-
**Last updated:** {{DATE}}
|
|
225
|
-
|
|
226
|
-
Quick reference for AI agents working in LumenFlow projects.
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## Stop and Ask When
|
|
231
|
-
|
|
232
|
-
- Same error repeats 3 times
|
|
233
|
-
- Auth or permissions changes needed
|
|
234
|
-
- PII/PHI/secrets involved
|
|
235
|
-
- Cloud spend decisions
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## Never Do
|
|
240
|
-
|
|
241
|
-
| Action | Why |
|
|
242
|
-
| ------------------------ | ---------------- |
|
|
243
|
-
| \`git reset --hard\` | Data loss |
|
|
244
|
-
| \`git push --force\` | History rewrite |
|
|
245
|
-
| \`--no-verify\` | Bypasses safety |
|
|
246
|
-
| Work in main after claim | Breaks isolation |
|
|
247
|
-
| Skip wu:done | Incomplete WU |
|
|
248
|
-
|
|
249
|
-
---
|
|
250
|
-
|
|
251
|
-
## Always Do
|
|
252
|
-
|
|
253
|
-
| Action | Why |
|
|
254
|
-
| -------------------------- | ---------------- |
|
|
255
|
-
| Read WU spec first | Understand scope |
|
|
256
|
-
| cd to worktree after claim | Isolation |
|
|
257
|
-
| Write tests before code | TDD |
|
|
258
|
-
| Run gates before wu:done | Quality |
|
|
259
|
-
| Run wu:done | Complete WU |
|
|
260
|
-
`;
|
|
261
|
-
const WU_CREATE_CHECKLIST_TEMPLATE = `# WU Creation Checklist
|
|
262
|
-
|
|
263
|
-
**Last updated:** {{DATE}}
|
|
264
|
-
|
|
265
|
-
Before running \`pnpm wu:create\`, verify these items.
|
|
266
|
-
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
## Step 1: Check Valid Lanes
|
|
270
|
-
|
|
271
|
-
\`\`\`bash
|
|
272
|
-
grep -A 30 "lanes:" .lumenflow.config.yaml
|
|
273
|
-
\`\`\`
|
|
274
|
-
|
|
275
|
-
**Format:** \`"Parent: Sublane"\` (colon + single space)
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
## Step 2: Required Fields
|
|
280
|
-
|
|
281
|
-
| Field | Required For | Example |
|
|
282
|
-
|-------|--------------|---------|
|
|
283
|
-
| \`--id\` | All | \`WU-1234\` |
|
|
284
|
-
| \`--lane\` | All | \`"Experience: Chat"\` |
|
|
285
|
-
| \`--title\` | All | \`"Add feature"\` |
|
|
286
|
-
| \`--description\` | All | \`"Context: ... Problem: ... Solution: ..."\` |
|
|
287
|
-
| \`--acceptance\` | All | \`--acceptance "Works"\` (repeatable) |
|
|
288
|
-
| \`--exposure\` | All | \`ui\`, \`api\`, \`backend-only\`, \`documentation\` |
|
|
289
|
-
| \`--code-paths\` | Code WUs | \`"src/a.ts,src/b.ts"\` |
|
|
290
|
-
| \`--test-paths-unit\` | Code WUs | \`"src/__tests__/a.test.ts"\` |
|
|
291
|
-
| \`--spec-refs\` | Feature WUs | \`"~/.lumenflow/plans/WU-XXX.md"\` |
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
## Step 3: Plan Storage
|
|
296
|
-
|
|
297
|
-
Plans go in \`~/.lumenflow/plans/\` (NOT in project):
|
|
298
|
-
|
|
299
|
-
\`\`\`bash
|
|
300
|
-
mkdir -p ~/.lumenflow/plans
|
|
301
|
-
vim ~/.lumenflow/plans/WU-XXX-plan.md
|
|
302
|
-
\`\`\`
|
|
303
|
-
|
|
304
|
-
Reference in wu:create:
|
|
305
|
-
\`\`\`bash
|
|
306
|
-
--spec-refs "~/.lumenflow/plans/WU-XXX-plan.md"
|
|
307
|
-
\`\`\`
|
|
308
|
-
|
|
309
|
-
---
|
|
310
|
-
|
|
311
|
-
## Step 4: Validate First
|
|
312
|
-
|
|
313
|
-
\`\`\`bash
|
|
314
|
-
pnpm wu:create --id WU-XXX ... --validate
|
|
315
|
-
\`\`\`
|
|
316
|
-
|
|
317
|
-
Fix errors, then remove \`--validate\` to create.
|
|
318
|
-
`;
|
|
319
|
-
// Claude skills templates
|
|
320
|
-
const WU_LIFECYCLE_SKILL_TEMPLATE = `---
|
|
321
|
-
name: wu-lifecycle
|
|
322
|
-
description: Work Unit claim/block/done workflow automation.
|
|
323
|
-
version: 1.0.0
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
# WU Lifecycle Skill
|
|
327
|
-
|
|
328
|
-
## State Machine
|
|
329
|
-
|
|
330
|
-
\`\`\`
|
|
331
|
-
ready -> in_progress -> waiting/blocked -> done
|
|
332
|
-
\`\`\`
|
|
333
|
-
|
|
334
|
-
## Core Commands
|
|
335
|
-
|
|
336
|
-
\`\`\`bash
|
|
337
|
-
# Claim WU
|
|
338
|
-
pnpm wu:claim --id WU-XXX --lane <lane>
|
|
339
|
-
cd worktrees/<lane>-wu-xxx # IMMEDIATELY
|
|
340
|
-
|
|
341
|
-
# Complete WU (from main)
|
|
342
|
-
cd ../..
|
|
343
|
-
pnpm wu:done --id WU-XXX
|
|
344
|
-
\`\`\`
|
|
345
|
-
`;
|
|
346
|
-
const WORKTREE_DISCIPLINE_SKILL_TEMPLATE = `---
|
|
347
|
-
name: worktree-discipline
|
|
348
|
-
description: Prevents the "absolute path trap" in Write/Edit/Read tools.
|
|
349
|
-
version: 1.0.0
|
|
350
|
-
---
|
|
351
|
-
|
|
352
|
-
# Worktree Discipline: Absolute Path Trap Prevention
|
|
353
|
-
|
|
354
|
-
**Purpose**: Prevent AI agents from bypassing worktree isolation via absolute file paths.
|
|
355
|
-
|
|
356
|
-
## The Absolute Path Trap
|
|
357
|
-
|
|
358
|
-
**Problem**: AI agents using Write/Edit/Read tools can bypass worktree isolation by passing absolute paths.
|
|
359
|
-
|
|
360
|
-
## Golden Rules
|
|
361
|
-
|
|
362
|
-
1. **Always verify pwd** before file operations
|
|
363
|
-
2. **Never use absolute paths** in Write/Edit/Read tools
|
|
364
|
-
3. **When in doubt, use relative paths**
|
|
365
|
-
`;
|
|
366
|
-
const LUMENFLOW_GATES_SKILL_TEMPLATE = `---
|
|
367
|
-
name: lumenflow-gates
|
|
368
|
-
description: Quality gates troubleshooting (format, lint, typecheck, tests).
|
|
369
|
-
version: 1.0.0
|
|
370
|
-
---
|
|
371
|
-
|
|
372
|
-
# LumenFlow Gates Skill
|
|
373
|
-
|
|
374
|
-
## Gate Sequence
|
|
375
|
-
|
|
376
|
-
\`\`\`
|
|
377
|
-
pnpm gates = format:check -> lint -> typecheck -> spec:linter -> tests
|
|
378
|
-
\`\`\`
|
|
379
|
-
|
|
380
|
-
## Fix Patterns
|
|
381
|
-
|
|
382
|
-
| Gate | Auto-fix | Manual |
|
|
383
|
-
| --------- | --------------- | ----------------------------------- |
|
|
384
|
-
| Format | \`pnpm format\` | - |
|
|
385
|
-
| Lint | \`pnpm lint:fix\` | Fix reported issues |
|
|
386
|
-
| Typecheck | - | Fix type errors (first error first) |
|
|
387
|
-
| Tests | - | Debug, fix mocks, update snapshots |
|
|
388
|
-
`;
|
|
116
|
+
/**
|
|
117
|
+
* WU-1124: Template paths for agent onboarding docs
|
|
118
|
+
* Maps output file names to template paths
|
|
119
|
+
*/
|
|
120
|
+
const ONBOARDING_TEMPLATE_PATHS = {
|
|
121
|
+
'quick-ref-commands.md': 'core/ai/onboarding/quick-ref-commands.md.template',
|
|
122
|
+
'first-wu-mistakes.md': 'core/ai/onboarding/first-wu-mistakes.md.template',
|
|
123
|
+
'troubleshooting-wu-done.md': 'core/ai/onboarding/troubleshooting-wu-done.md.template',
|
|
124
|
+
'agent-safety-card.md': 'core/ai/onboarding/agent-safety-card.md.template',
|
|
125
|
+
'wu-create-checklist.md': 'core/ai/onboarding/wu-create-checklist.md.template',
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* WU-1124: Template paths for Claude skills
|
|
129
|
+
* Maps skill names to template paths
|
|
130
|
+
*/
|
|
131
|
+
const SKILL_TEMPLATE_PATHS = {
|
|
132
|
+
'wu-lifecycle': 'vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template',
|
|
133
|
+
'worktree-discipline': 'vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template',
|
|
134
|
+
'lumenflow-gates': 'vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template',
|
|
135
|
+
};
|
|
389
136
|
/**
|
|
390
137
|
* Sync agent onboarding docs to an existing project
|
|
138
|
+
* WU-1124: Now reads templates from bundled files instead of hardcoded strings
|
|
391
139
|
*/
|
|
392
140
|
export async function syncAgentDocs(targetDir, options) {
|
|
393
141
|
const result = {
|
|
@@ -399,15 +147,17 @@ export async function syncAgentDocs(targetDir, options) {
|
|
|
399
147
|
};
|
|
400
148
|
const onboardingDir = path.join(targetDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
|
|
401
149
|
await createDirectory(onboardingDir, result, targetDir);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
150
|
+
// WU-1124: Load and process templates from bundled files
|
|
151
|
+
for (const [outputFile, templatePath] of Object.entries(ONBOARDING_TEMPLATE_PATHS)) {
|
|
152
|
+
const templateContent = loadTemplate(templatePath);
|
|
153
|
+
const processedContent = processTemplate(templateContent, tokens);
|
|
154
|
+
await createFile(path.join(onboardingDir, outputFile), processedContent, options.force, result, targetDir);
|
|
155
|
+
}
|
|
407
156
|
return result;
|
|
408
157
|
}
|
|
409
158
|
/**
|
|
410
159
|
* Sync Claude skills to an existing project
|
|
160
|
+
* WU-1124: Now reads templates from bundled files instead of hardcoded strings
|
|
411
161
|
*/
|
|
412
162
|
export async function syncSkills(targetDir, options) {
|
|
413
163
|
const result = {
|
|
@@ -422,18 +172,14 @@ export async function syncSkills(targetDir, options) {
|
|
|
422
172
|
DATE: getCurrentDate(),
|
|
423
173
|
};
|
|
424
174
|
const skillsDir = path.join(targetDir, '.claude', 'skills');
|
|
425
|
-
//
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
// lumenflow-gates skill
|
|
434
|
-
const gatesDir = path.join(skillsDir, 'lumenflow-gates');
|
|
435
|
-
await createDirectory(gatesDir, result, targetDir);
|
|
436
|
-
await createFile(path.join(gatesDir, 'SKILL.md'), processTemplate(LUMENFLOW_GATES_SKILL_TEMPLATE, tokens), options.force, result, targetDir);
|
|
175
|
+
// WU-1124: Load and process skill templates from bundled files
|
|
176
|
+
for (const [skillName, templatePath] of Object.entries(SKILL_TEMPLATE_PATHS)) {
|
|
177
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
178
|
+
await createDirectory(skillDir, result, targetDir);
|
|
179
|
+
const templateContent = loadTemplate(templatePath);
|
|
180
|
+
const processedContent = processTemplate(templateContent, tokens);
|
|
181
|
+
await createFile(path.join(skillDir, 'SKILL.md'), processedContent, options.force, result, targetDir);
|
|
182
|
+
}
|
|
437
183
|
return result;
|
|
438
184
|
}
|
|
439
185
|
/**
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
* LumenFlow Upgrade CLI Command
|
|
4
4
|
*
|
|
5
5
|
* Updates all @lumenflow/* packages to a specified version or latest.
|
|
6
|
-
* Uses worktree pattern
|
|
6
|
+
* Uses micro-worktree pattern for atomic changes to main without requiring
|
|
7
|
+
* users to be in a worktree.
|
|
7
8
|
*
|
|
8
9
|
* WU-1112: INIT-003 Phase 6 - Migrate remaining Tier 1 tools
|
|
10
|
+
* WU-1127: Use micro-worktree isolation pattern (fixes user blocking issue)
|
|
9
11
|
*
|
|
10
|
-
* Key requirements
|
|
11
|
-
* - Uses worktree pattern (
|
|
12
|
-
* -
|
|
12
|
+
* Key requirements:
|
|
13
|
+
* - Uses micro-worktree pattern (atomic changes, no user worktree needed)
|
|
14
|
+
* - Runs from main checkout (not inside a worktree)
|
|
15
|
+
* - Checks all 7 @lumenflow/* packages
|
|
13
16
|
*
|
|
14
17
|
* Usage:
|
|
15
18
|
* pnpm lumenflow:upgrade --version 1.5.0
|
|
@@ -17,11 +20,14 @@
|
|
|
17
20
|
* pnpm lumenflow:upgrade --latest --dry-run
|
|
18
21
|
*/
|
|
19
22
|
import { execSync } from 'node:child_process';
|
|
20
|
-
import { STDIO_MODES, EXIT_CODES, PKG_MANAGER, } from '@lumenflow/core/dist/wu-constants.js';
|
|
23
|
+
import { STDIO_MODES, EXIT_CODES, PKG_MANAGER, DEFAULTS, BRANCHES, } from '@lumenflow/core/dist/wu-constants.js';
|
|
24
|
+
import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
|
|
25
|
+
import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
21
26
|
import { runCLI } from './cli-entry-point.js';
|
|
22
|
-
import { validateWorktreeContext } from './deps-add.js';
|
|
23
27
|
/** Log prefix for console output */
|
|
24
28
|
const LOG_PREFIX = '[lumenflow:upgrade]';
|
|
29
|
+
/** Operation name for micro-worktree */
|
|
30
|
+
const OPERATION_NAME = 'lumenflow-upgrade';
|
|
25
31
|
/**
|
|
26
32
|
* All @lumenflow/* packages that should be upgraded together
|
|
27
33
|
*
|
|
@@ -85,6 +91,98 @@ export function buildUpgradeCommands(args) {
|
|
|
85
91
|
versionSpec,
|
|
86
92
|
};
|
|
87
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* WU-1127: Validate that the command is run from main checkout
|
|
96
|
+
*
|
|
97
|
+
* The micro-worktree pattern requires the command to be run from the main
|
|
98
|
+
* checkout (not inside a worktree). This is the inverse of the old behavior
|
|
99
|
+
* which required users to be IN a worktree.
|
|
100
|
+
*
|
|
101
|
+
* @returns Validation result with error and fix command if invalid
|
|
102
|
+
*/
|
|
103
|
+
export async function validateMainCheckout() {
|
|
104
|
+
const cwd = process.cwd();
|
|
105
|
+
const worktreesDir = `/${DEFAULTS.WORKTREES_DIR}/`;
|
|
106
|
+
// Check if we're inside a worktree directory
|
|
107
|
+
if (cwd.includes(worktreesDir)) {
|
|
108
|
+
return {
|
|
109
|
+
valid: false,
|
|
110
|
+
error: `Cannot run lumenflow:upgrade from inside a worktree.\n\n` +
|
|
111
|
+
`This command must be run from main checkout because it uses\n` +
|
|
112
|
+
`micro-worktree isolation to atomically update package.json and lockfile.`,
|
|
113
|
+
fixCommand: `cd to main checkout and re-run:\n cd <main-checkout>\n pnpm lumenflow:upgrade --latest`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Check if we're on main branch
|
|
117
|
+
try {
|
|
118
|
+
const git = getGitForCwd();
|
|
119
|
+
const currentBranch = await git.raw(['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
120
|
+
const branchName = currentBranch.trim();
|
|
121
|
+
if (branchName !== BRANCHES.MAIN) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
error: `lumenflow:upgrade must be run from main checkout (on main branch).\n\n` +
|
|
125
|
+
`Current branch: ${branchName}\n` +
|
|
126
|
+
`Expected branch: main`,
|
|
127
|
+
fixCommand: `Switch to main branch:\n git checkout main\n pnpm lumenflow:upgrade --latest`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// If git fails, assume we're not in a valid git repo
|
|
133
|
+
return {
|
|
134
|
+
valid: false,
|
|
135
|
+
error: `Failed to detect git branch. Ensure you're in a git repository.`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { valid: true };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* WU-1127: Execute the upgrade in a micro-worktree
|
|
142
|
+
*
|
|
143
|
+
* Uses the shared micro-worktree pattern (like wu:create, wu:edit) to:
|
|
144
|
+
* 1. Create a temporary worktree without switching main checkout
|
|
145
|
+
* 2. Run pnpm add in the temporary worktree
|
|
146
|
+
* 3. Commit the changes
|
|
147
|
+
* 4. FF-only merge to main
|
|
148
|
+
* 5. Push to origin
|
|
149
|
+
* 6. Cleanup
|
|
150
|
+
*
|
|
151
|
+
* @param args - Parsed upgrade arguments
|
|
152
|
+
* @returns Promise resolving when upgrade is complete
|
|
153
|
+
*/
|
|
154
|
+
export async function executeUpgradeInMicroWorktree(args) {
|
|
155
|
+
const { addCommand, versionSpec } = buildUpgradeCommands(args);
|
|
156
|
+
// Generate unique ID for this upgrade operation using timestamp
|
|
157
|
+
const upgradeId = `upgrade-${Date.now()}`;
|
|
158
|
+
console.log(`${LOG_PREFIX} Using micro-worktree isolation (WU-1127)`);
|
|
159
|
+
console.log(`${LOG_PREFIX} Upgrading @lumenflow/* packages to ${versionSpec}`);
|
|
160
|
+
console.log(`${LOG_PREFIX} Packages: ${LUMENFLOW_PACKAGES.length} packages`);
|
|
161
|
+
await withMicroWorktree({
|
|
162
|
+
operation: OPERATION_NAME,
|
|
163
|
+
id: upgradeId,
|
|
164
|
+
logPrefix: LOG_PREFIX,
|
|
165
|
+
execute: async ({ worktreePath }) => {
|
|
166
|
+
console.log(`${LOG_PREFIX} Running: ${addCommand}`);
|
|
167
|
+
// Execute pnpm add in the micro-worktree
|
|
168
|
+
execSync(addCommand, {
|
|
169
|
+
stdio: STDIO_MODES.INHERIT,
|
|
170
|
+
cwd: worktreePath,
|
|
171
|
+
});
|
|
172
|
+
console.log(`${LOG_PREFIX} Package installation complete`);
|
|
173
|
+
// Return files to stage and commit message
|
|
174
|
+
return {
|
|
175
|
+
commitMessage: `chore: upgrade @lumenflow packages to ${versionSpec}`,
|
|
176
|
+
files: ['package.json', 'pnpm-lock.yaml'],
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
console.log(`\n${LOG_PREFIX} Upgrade complete!`);
|
|
181
|
+
console.log(`${LOG_PREFIX} Upgraded to ${versionSpec}`);
|
|
182
|
+
console.log(`\n${LOG_PREFIX} Next steps:`);
|
|
183
|
+
console.log(` 1. Run 'pnpm build' to rebuild with new versions`);
|
|
184
|
+
console.log(` 2. Run 'pnpm gates' to verify everything works`);
|
|
185
|
+
}
|
|
88
186
|
/**
|
|
89
187
|
* Print help message for lumenflow-upgrade
|
|
90
188
|
*/
|
|
@@ -94,7 +192,7 @@ function printHelp() {
|
|
|
94
192
|
Usage: lumenflow-upgrade [options]
|
|
95
193
|
|
|
96
194
|
Upgrade all @lumenflow/* packages to a specified version.
|
|
97
|
-
|
|
195
|
+
Uses micro-worktree isolation to atomically update packages from main checkout.
|
|
98
196
|
|
|
99
197
|
Options:
|
|
100
198
|
-v, --version <ver> Upgrade to specific version (e.g., 1.5.0)
|
|
@@ -110,13 +208,13 @@ Examples:
|
|
|
110
208
|
lumenflow:upgrade --latest # Upgrade to latest
|
|
111
209
|
lumenflow:upgrade --latest --dry-run # Preview upgrade commands
|
|
112
210
|
|
|
113
|
-
Worktree
|
|
114
|
-
This command
|
|
115
|
-
|
|
211
|
+
Micro-Worktree Pattern (WU-1127):
|
|
212
|
+
This command uses micro-worktree isolation to atomically update
|
|
213
|
+
package.json and pnpm-lock.yaml without requiring you to claim a WU.
|
|
116
214
|
|
|
117
|
-
|
|
118
|
-
cd
|
|
119
|
-
lumenflow:upgrade --latest
|
|
215
|
+
Run from your main checkout (NOT from inside a worktree):
|
|
216
|
+
cd /path/to/main
|
|
217
|
+
pnpm lumenflow:upgrade --latest
|
|
120
218
|
`);
|
|
121
219
|
}
|
|
122
220
|
/**
|
|
@@ -135,40 +233,32 @@ async function main() {
|
|
|
135
233
|
printHelp();
|
|
136
234
|
process.exit(EXIT_CODES.ERROR);
|
|
137
235
|
}
|
|
138
|
-
// Validate
|
|
139
|
-
const validation =
|
|
236
|
+
// WU-1127: Validate we're on main checkout (not in a worktree)
|
|
237
|
+
const validation = await validateMainCheckout();
|
|
140
238
|
if (!validation.valid) {
|
|
141
239
|
console.error(`${LOG_PREFIX} ${validation.error}`);
|
|
142
|
-
|
|
240
|
+
if (validation.fixCommand) {
|
|
241
|
+
console.error(`\nTo fix:\n${validation.fixCommand}`);
|
|
242
|
+
}
|
|
143
243
|
process.exit(EXIT_CODES.ERROR);
|
|
144
244
|
}
|
|
145
|
-
// Build upgrade commands
|
|
245
|
+
// Build upgrade commands for dry-run display
|
|
146
246
|
const { addCommand, versionSpec } = buildUpgradeCommands(args);
|
|
147
|
-
console.log(`${LOG_PREFIX} Upgrading @lumenflow/* packages to ${versionSpec}`);
|
|
148
|
-
console.log(`${LOG_PREFIX} Packages: ${LUMENFLOW_PACKAGES.length} packages`);
|
|
149
247
|
if (args.dryRun) {
|
|
150
|
-
console.log(
|
|
248
|
+
console.log(`${LOG_PREFIX} DRY RUN - Commands that would be executed:`);
|
|
151
249
|
console.log(` ${addCommand}`);
|
|
250
|
+
console.log(`\n${LOG_PREFIX} Packages: ${LUMENFLOW_PACKAGES.length}`);
|
|
251
|
+
console.log(`${LOG_PREFIX} Version: ${versionSpec}`);
|
|
152
252
|
console.log(`\n${LOG_PREFIX} No changes made.`);
|
|
153
253
|
process.exit(EXIT_CODES.SUCCESS);
|
|
154
254
|
}
|
|
155
|
-
// Execute upgrade
|
|
156
|
-
console.log(`${LOG_PREFIX} Running: ${addCommand}`);
|
|
255
|
+
// Execute upgrade using micro-worktree
|
|
157
256
|
try {
|
|
158
|
-
|
|
159
|
-
stdio: STDIO_MODES.INHERIT,
|
|
160
|
-
cwd: process.cwd(),
|
|
161
|
-
});
|
|
162
|
-
console.log(`\n${LOG_PREFIX} ✅ Upgrade complete!`);
|
|
163
|
-
console.log(`${LOG_PREFIX} Upgraded to ${versionSpec}`);
|
|
164
|
-
console.log(`\n${LOG_PREFIX} Next steps:`);
|
|
165
|
-
console.log(` 1. Run 'pnpm build' to rebuild with new versions`);
|
|
166
|
-
console.log(` 2. Run 'pnpm gates' to verify everything works`);
|
|
167
|
-
console.log(` 3. Commit the changes`);
|
|
257
|
+
await executeUpgradeInMicroWorktree(args);
|
|
168
258
|
}
|
|
169
259
|
catch (error) {
|
|
170
|
-
console.error(`\n${LOG_PREFIX}
|
|
171
|
-
console.error(`${LOG_PREFIX}
|
|
260
|
+
console.error(`\n${LOG_PREFIX} Upgrade failed`);
|
|
261
|
+
console.error(`${LOG_PREFIX} ${error instanceof Error ? error.message : String(error)}`);
|
|
172
262
|
process.exit(EXIT_CODES.ERROR);
|
|
173
263
|
}
|
|
174
264
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file sync-templates.ts
|
|
3
|
+
* Sync internal docs to CLI templates for release-cycle maintenance (WU-1123)
|
|
4
|
+
*
|
|
5
|
+
* This script syncs source docs from the hellmai/os repo to the templates
|
|
6
|
+
* directory, applying template variable substitutions:
|
|
7
|
+
* - Onboarding docs -> templates/core/ai/onboarding/
|
|
8
|
+
* - Claude skills -> templates/vendors/claude/.claude/skills/
|
|
9
|
+
* - Core docs (LUMENFLOW.md, constraints.md) -> templates/core/
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { createWUParser } from '@lumenflow/core';
|
|
14
|
+
// Template variable patterns
|
|
15
|
+
const DATE_PATTERN = /\d{4}-\d{2}-\d{2}/g;
|
|
16
|
+
/**
|
|
17
|
+
* CLI option definitions for sync-templates command
|
|
18
|
+
*/
|
|
19
|
+
const SYNC_TEMPLATES_OPTIONS = {
|
|
20
|
+
dryRun: {
|
|
21
|
+
name: 'dry-run',
|
|
22
|
+
flags: '--dry-run',
|
|
23
|
+
description: 'Show what would be synced without writing files',
|
|
24
|
+
default: false,
|
|
25
|
+
},
|
|
26
|
+
verbose: {
|
|
27
|
+
name: 'verbose',
|
|
28
|
+
flags: '--verbose',
|
|
29
|
+
description: 'Show detailed output',
|
|
30
|
+
default: false,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Parse sync-templates command options
|
|
35
|
+
*/
|
|
36
|
+
export function parseSyncTemplatesOptions() {
|
|
37
|
+
const opts = createWUParser({
|
|
38
|
+
name: 'sync-templates',
|
|
39
|
+
description: 'Sync internal docs to CLI templates for release-cycle maintenance',
|
|
40
|
+
options: Object.values(SYNC_TEMPLATES_OPTIONS),
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
dryRun: opts['dry-run'] ?? false,
|
|
44
|
+
verbose: opts.verbose ?? false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Convert source content to template format by replacing:
|
|
49
|
+
* - YYYY-MM-DD dates with {{DATE}}
|
|
50
|
+
* - Absolute project paths with {{PROJECT_ROOT}}
|
|
51
|
+
*/
|
|
52
|
+
export function convertToTemplate(content, projectRoot) {
|
|
53
|
+
let output = content;
|
|
54
|
+
// Replace dates with {{DATE}}
|
|
55
|
+
output = output.replace(DATE_PATTERN, '{{DATE}}');
|
|
56
|
+
// Replace absolute project paths with {{PROJECT_ROOT}}
|
|
57
|
+
// Escape special regex characters in the path
|
|
58
|
+
const escapedPath = projectRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
59
|
+
const pathPattern = new RegExp(escapedPath, 'g');
|
|
60
|
+
output = output.replace(pathPattern, '{{PROJECT_ROOT}}');
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get the templates directory path
|
|
65
|
+
*/
|
|
66
|
+
function getTemplatesDir(projectRoot) {
|
|
67
|
+
return path.join(projectRoot, 'packages', '@lumenflow', 'cli', 'templates');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Ensure directory exists
|
|
71
|
+
*/
|
|
72
|
+
function ensureDir(dirPath) {
|
|
73
|
+
if (!fs.existsSync(dirPath)) {
|
|
74
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Sync a single file to templates
|
|
79
|
+
*/
|
|
80
|
+
function syncFile(sourcePath, targetPath, projectRoot, result, dryRun = false) {
|
|
81
|
+
try {
|
|
82
|
+
if (!fs.existsSync(sourcePath)) {
|
|
83
|
+
result.errors.push(`Source not found: ${sourcePath}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
87
|
+
const templateContent = convertToTemplate(content, projectRoot);
|
|
88
|
+
if (!dryRun) {
|
|
89
|
+
ensureDir(path.dirname(targetPath));
|
|
90
|
+
fs.writeFileSync(targetPath, templateContent);
|
|
91
|
+
}
|
|
92
|
+
result.synced.push(path.relative(projectRoot, targetPath));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
result.errors.push(`Error syncing ${sourcePath}: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Sync onboarding docs to templates/core/ai/onboarding/
|
|
100
|
+
*/
|
|
101
|
+
export async function syncOnboardingDocs(projectRoot, dryRun = false) {
|
|
102
|
+
const result = { synced: [], errors: [] };
|
|
103
|
+
const sourceDir = path.join(projectRoot, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
|
|
104
|
+
const targetDir = path.join(getTemplatesDir(projectRoot), 'core', 'ai', 'onboarding');
|
|
105
|
+
if (!fs.existsSync(sourceDir)) {
|
|
106
|
+
result.errors.push(`Onboarding source directory not found: ${sourceDir}`);
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
const files = fs.readdirSync(sourceDir).filter((f) => f.endsWith('.md'));
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
const sourcePath = path.join(sourceDir, file);
|
|
112
|
+
const targetPath = path.join(targetDir, `${file}.template`);
|
|
113
|
+
syncFile(sourcePath, targetPath, projectRoot, result, dryRun);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Sync Claude skills to templates/vendors/claude/.claude/skills/
|
|
119
|
+
*/
|
|
120
|
+
export async function syncSkillsToTemplates(projectRoot, dryRun = false) {
|
|
121
|
+
const result = { synced: [], errors: [] };
|
|
122
|
+
const sourceDir = path.join(projectRoot, '.claude', 'skills');
|
|
123
|
+
const targetDir = path.join(getTemplatesDir(projectRoot), 'vendors', 'claude', '.claude', 'skills');
|
|
124
|
+
if (!fs.existsSync(sourceDir)) {
|
|
125
|
+
result.errors.push(`Skills source directory not found: ${sourceDir}`);
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
// Get all skill directories
|
|
129
|
+
const skillDirs = fs
|
|
130
|
+
.readdirSync(sourceDir, { withFileTypes: true })
|
|
131
|
+
.filter((d) => d.isDirectory())
|
|
132
|
+
.map((d) => d.name);
|
|
133
|
+
for (const skillName of skillDirs) {
|
|
134
|
+
const skillSourceDir = path.join(sourceDir, skillName);
|
|
135
|
+
const skillTargetDir = path.join(targetDir, skillName);
|
|
136
|
+
// Look for SKILL.md file
|
|
137
|
+
const skillFile = path.join(skillSourceDir, 'SKILL.md');
|
|
138
|
+
if (fs.existsSync(skillFile)) {
|
|
139
|
+
const targetPath = path.join(skillTargetDir, 'SKILL.md.template');
|
|
140
|
+
syncFile(skillFile, targetPath, projectRoot, result, dryRun);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Sync core docs (LUMENFLOW.md, constraints.md) to templates/core/
|
|
147
|
+
*/
|
|
148
|
+
export async function syncCoreDocs(projectRoot, dryRun = false) {
|
|
149
|
+
const result = { synced: [], errors: [] };
|
|
150
|
+
const templatesDir = getTemplatesDir(projectRoot);
|
|
151
|
+
// Sync LUMENFLOW.md
|
|
152
|
+
const lumenflowSource = path.join(projectRoot, 'LUMENFLOW.md');
|
|
153
|
+
const lumenflowTarget = path.join(templatesDir, 'core', 'LUMENFLOW.md.template');
|
|
154
|
+
syncFile(lumenflowSource, lumenflowTarget, projectRoot, result, dryRun);
|
|
155
|
+
// Sync constraints.md
|
|
156
|
+
const constraintsSource = path.join(projectRoot, '.lumenflow', 'constraints.md');
|
|
157
|
+
const constraintsTarget = path.join(templatesDir, 'core', '.lumenflow', 'constraints.md.template');
|
|
158
|
+
syncFile(constraintsSource, constraintsTarget, projectRoot, result, dryRun);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Sync all templates
|
|
163
|
+
*/
|
|
164
|
+
export async function syncTemplates(projectRoot, dryRun = false) {
|
|
165
|
+
const onboarding = await syncOnboardingDocs(projectRoot, dryRun);
|
|
166
|
+
const skills = await syncSkillsToTemplates(projectRoot, dryRun);
|
|
167
|
+
const core = await syncCoreDocs(projectRoot, dryRun);
|
|
168
|
+
return { onboarding, skills, core };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* CLI entry point
|
|
172
|
+
*/
|
|
173
|
+
export async function main() {
|
|
174
|
+
const opts = parseSyncTemplatesOptions();
|
|
175
|
+
const projectRoot = process.cwd();
|
|
176
|
+
console.log('[sync-templates] Syncing internal docs to CLI templates...');
|
|
177
|
+
if (opts.dryRun) {
|
|
178
|
+
console.log(' (dry-run mode - no files will be written)');
|
|
179
|
+
}
|
|
180
|
+
const result = await syncTemplates(projectRoot, opts.dryRun);
|
|
181
|
+
// Print results
|
|
182
|
+
const sections = [
|
|
183
|
+
{ name: 'Onboarding docs', data: result.onboarding },
|
|
184
|
+
{ name: 'Claude skills', data: result.skills },
|
|
185
|
+
{ name: 'Core docs', data: result.core },
|
|
186
|
+
];
|
|
187
|
+
let totalSynced = 0;
|
|
188
|
+
let totalErrors = 0;
|
|
189
|
+
for (const section of sections) {
|
|
190
|
+
if (section.data.synced.length > 0 || section.data.errors.length > 0) {
|
|
191
|
+
console.log(`\n${section.name}:`);
|
|
192
|
+
if (section.data.synced.length > 0) {
|
|
193
|
+
section.data.synced.forEach((f) => console.log(` + ${f}`));
|
|
194
|
+
totalSynced += section.data.synced.length;
|
|
195
|
+
}
|
|
196
|
+
if (section.data.errors.length > 0) {
|
|
197
|
+
section.data.errors.forEach((e) => console.log(` ! ${e}`));
|
|
198
|
+
totalErrors += section.data.errors.length;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
console.log(`\n[sync-templates] Done! Synced ${totalSynced} files.`);
|
|
203
|
+
if (totalErrors > 0) {
|
|
204
|
+
console.log(` ${totalErrors} error(s) occurred.`);
|
|
205
|
+
process.exitCode = 1;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// CLI entry point
|
|
209
|
+
import { runCLI } from './cli-entry-point.js';
|
|
210
|
+
if (import.meta.main) {
|
|
211
|
+
runCLI(main);
|
|
212
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Command-line interface for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -89,6 +89,8 @@
|
|
|
89
89
|
"lumenflow": "./dist/init.js",
|
|
90
90
|
"lumenflow-release": "./dist/release.js",
|
|
91
91
|
"lumenflow-docs-sync": "./dist/docs-sync.js",
|
|
92
|
+
"lumenflow-sync-templates": "./dist/sync-templates.js",
|
|
93
|
+
"sync-templates": "./dist/sync-templates.js",
|
|
92
94
|
"backlog-prune": "./dist/backlog-prune.js",
|
|
93
95
|
"file-read": "./dist/file-read.js",
|
|
94
96
|
"file-write": "./dist/file-write.js",
|
|
@@ -130,11 +132,11 @@
|
|
|
130
132
|
"pretty-ms": "^9.2.0",
|
|
131
133
|
"simple-git": "^3.30.0",
|
|
132
134
|
"yaml": "^2.8.2",
|
|
133
|
-
"@lumenflow/
|
|
134
|
-
"@lumenflow/metrics": "1.
|
|
135
|
-
"@lumenflow/
|
|
136
|
-
"@lumenflow/initiatives": "2.
|
|
137
|
-
"@lumenflow/agent": "2.
|
|
135
|
+
"@lumenflow/core": "2.1.1",
|
|
136
|
+
"@lumenflow/metrics": "2.1.1",
|
|
137
|
+
"@lumenflow/memory": "2.1.1",
|
|
138
|
+
"@lumenflow/initiatives": "2.1.1",
|
|
139
|
+
"@lumenflow/agent": "2.1.1"
|
|
138
140
|
},
|
|
139
141
|
"devDependencies": {
|
|
140
142
|
"@vitest/coverage-v8": "^4.0.17",
|