@lumenflow/cli 2.5.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/__tests__/init-scripts.test.js +111 -0
- package/dist/__tests__/templates-sync.test.js +219 -0
- package/dist/init.js +90 -0
- package/dist/orchestrate-init-status.js +37 -9
- package/dist/orchestrate-initiative.js +10 -4
- package/dist/sync-templates.js +137 -5
- package/dist/wu-prep.js +131 -8
- package/dist/wu-spawn.js +7 -2
- package/package.json +7 -7
- package/templates/core/.lumenflow/constraints.md.template +61 -3
- package/templates/core/LUMENFLOW.md.template +85 -23
- package/templates/core/ai/onboarding/agent-invocation-guide.md.template +157 -0
- package/templates/core/ai/onboarding/agent-safety-card.md.template +227 -0
- package/templates/core/ai/onboarding/docs-generation.md.template +277 -0
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +49 -7
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +343 -110
- package/templates/core/ai/onboarding/release-process.md.template +8 -2
- package/templates/core/ai/onboarding/starting-prompt.md.template +407 -0
- package/templates/core/ai/onboarding/test-ratchet.md.template +131 -0
- package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +91 -38
- package/templates/core/ai/onboarding/vendor-support.md.template +219 -0
- package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +13 -1
- package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +14 -16
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +48 -4
- package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +5 -1
- package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +19 -8
package/dist/sync-templates.js
CHANGED
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
* - Claude skills -> templates/vendors/claude/.claude/skills/
|
|
9
9
|
* - Core docs (LUMENFLOW.md, constraints.md) -> templates/core/
|
|
10
10
|
*/
|
|
11
|
+
/* eslint-disable no-console -- CLI tool requires console output */
|
|
12
|
+
/* eslint-disable security/detect-non-literal-fs-filename -- CLI tool syncs templates from known paths */
|
|
13
|
+
/* eslint-disable security/detect-non-literal-regexp -- Dynamic date pattern for template substitution */
|
|
11
14
|
import * as fs from 'node:fs';
|
|
12
15
|
import * as path from 'node:path';
|
|
13
16
|
import { createWUParser } from '@lumenflow/core';
|
|
17
|
+
// Directory name constants to avoid duplicate strings
|
|
18
|
+
const LUMENFLOW_DIR = '.lumenflow';
|
|
19
|
+
const CLAUDE_DIR = '.claude';
|
|
20
|
+
const SKILLS_DIR = 'skills';
|
|
14
21
|
// Template variable patterns
|
|
15
22
|
const DATE_PATTERN = /\d{4}-\d{2}-\d{2}/g;
|
|
16
23
|
/**
|
|
@@ -29,6 +36,12 @@ const SYNC_TEMPLATES_OPTIONS = {
|
|
|
29
36
|
description: 'Show detailed output',
|
|
30
37
|
default: false,
|
|
31
38
|
},
|
|
39
|
+
checkDrift: {
|
|
40
|
+
name: 'check-drift',
|
|
41
|
+
flags: '--check-drift',
|
|
42
|
+
description: 'Check for template drift without syncing (CI mode)',
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
32
45
|
};
|
|
33
46
|
/**
|
|
34
47
|
* Parse sync-templates command options
|
|
@@ -42,6 +55,7 @@ export function parseSyncTemplatesOptions() {
|
|
|
42
55
|
return {
|
|
43
56
|
dryRun: opts['dry-run'] ?? false,
|
|
44
57
|
verbose: opts.verbose ?? false,
|
|
58
|
+
checkDrift: opts['check-drift'] ?? false,
|
|
45
59
|
};
|
|
46
60
|
}
|
|
47
61
|
/**
|
|
@@ -119,8 +133,8 @@ export async function syncOnboardingDocs(projectRoot, dryRun = false) {
|
|
|
119
133
|
*/
|
|
120
134
|
export async function syncSkillsToTemplates(projectRoot, dryRun = false) {
|
|
121
135
|
const result = { synced: [], errors: [] };
|
|
122
|
-
const sourceDir = path.join(projectRoot,
|
|
123
|
-
const targetDir = path.join(getTemplatesDir(projectRoot), 'vendors', 'claude',
|
|
136
|
+
const sourceDir = path.join(projectRoot, CLAUDE_DIR, SKILLS_DIR);
|
|
137
|
+
const targetDir = path.join(getTemplatesDir(projectRoot), 'vendors', 'claude', CLAUDE_DIR, SKILLS_DIR);
|
|
124
138
|
if (!fs.existsSync(sourceDir)) {
|
|
125
139
|
result.errors.push(`Skills source directory not found: ${sourceDir}`);
|
|
126
140
|
return result;
|
|
@@ -153,8 +167,8 @@ export async function syncCoreDocs(projectRoot, dryRun = false) {
|
|
|
153
167
|
const lumenflowTarget = path.join(templatesDir, 'core', 'LUMENFLOW.md.template');
|
|
154
168
|
syncFile(lumenflowSource, lumenflowTarget, projectRoot, result, dryRun);
|
|
155
169
|
// Sync constraints.md
|
|
156
|
-
const constraintsSource = path.join(projectRoot,
|
|
157
|
-
const constraintsTarget = path.join(templatesDir, 'core',
|
|
170
|
+
const constraintsSource = path.join(projectRoot, LUMENFLOW_DIR, 'constraints.md');
|
|
171
|
+
const constraintsTarget = path.join(templatesDir, 'core', LUMENFLOW_DIR, 'constraints.md.template');
|
|
158
172
|
syncFile(constraintsSource, constraintsTarget, projectRoot, result, dryRun);
|
|
159
173
|
return result;
|
|
160
174
|
}
|
|
@@ -167,12 +181,130 @@ export async function syncTemplates(projectRoot, dryRun = false) {
|
|
|
167
181
|
const core = await syncCoreDocs(projectRoot, dryRun);
|
|
168
182
|
return { onboarding, skills, core };
|
|
169
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Compare source file content with template content (ignoring date placeholders)
|
|
186
|
+
*/
|
|
187
|
+
function compareContent(sourceContent, templateContent, projectRoot) {
|
|
188
|
+
// Convert source to template format for comparison
|
|
189
|
+
const convertedSource = convertToTemplate(sourceContent, projectRoot);
|
|
190
|
+
return convertedSource === templateContent;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check if a single template file is in sync with its source
|
|
194
|
+
*/
|
|
195
|
+
function checkFileDrift(sourcePath, templatePath, projectRoot) {
|
|
196
|
+
const relativePath = path.relative(projectRoot, templatePath);
|
|
197
|
+
if (!fs.existsSync(sourcePath)) {
|
|
198
|
+
return { isDrifting: false, relativePath }; // Source doesn't exist, can't drift
|
|
199
|
+
}
|
|
200
|
+
if (!fs.existsSync(templatePath)) {
|
|
201
|
+
return { isDrifting: true, relativePath }; // Template missing, definitely drifting
|
|
202
|
+
}
|
|
203
|
+
const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
|
|
204
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
205
|
+
const isDrifting = !compareContent(sourceContent, templateContent, projectRoot);
|
|
206
|
+
return { isDrifting, relativePath };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Check for template drift - compares source docs with templates (WU-1353)
|
|
210
|
+
*
|
|
211
|
+
* This function compares source documents with their template counterparts
|
|
212
|
+
* to detect if templates have drifted out of sync. Used by CI to warn
|
|
213
|
+
* when templates need to be re-synced.
|
|
214
|
+
*/
|
|
215
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- Multi-category drift check requires nested iteration
|
|
216
|
+
export async function checkTemplateDrift(projectRoot) {
|
|
217
|
+
const driftingFiles = [];
|
|
218
|
+
const checkedFiles = [];
|
|
219
|
+
const templatesDir = getTemplatesDir(projectRoot);
|
|
220
|
+
// Check core docs
|
|
221
|
+
const coreChecks = [
|
|
222
|
+
{
|
|
223
|
+
source: path.join(projectRoot, 'LUMENFLOW.md'),
|
|
224
|
+
template: path.join(templatesDir, 'core', 'LUMENFLOW.md.template'),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
source: path.join(projectRoot, LUMENFLOW_DIR, 'constraints.md'),
|
|
228
|
+
template: path.join(templatesDir, 'core', LUMENFLOW_DIR, 'constraints.md.template'),
|
|
229
|
+
},
|
|
230
|
+
];
|
|
231
|
+
for (const check of coreChecks) {
|
|
232
|
+
const result = checkFileDrift(check.source, check.template, projectRoot);
|
|
233
|
+
checkedFiles.push(result.relativePath);
|
|
234
|
+
if (result.isDrifting) {
|
|
235
|
+
driftingFiles.push(result.relativePath);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Check onboarding docs
|
|
239
|
+
const onboardingSourceDir = path.join(projectRoot, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
|
|
240
|
+
const onboardingTargetDir = path.join(templatesDir, 'core', 'ai', 'onboarding');
|
|
241
|
+
if (fs.existsSync(onboardingSourceDir)) {
|
|
242
|
+
const files = fs.readdirSync(onboardingSourceDir).filter((f) => f.endsWith('.md'));
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
const sourcePath = path.join(onboardingSourceDir, file);
|
|
245
|
+
const templatePath = path.join(onboardingTargetDir, `${file}.template`);
|
|
246
|
+
const result = checkFileDrift(sourcePath, templatePath, projectRoot);
|
|
247
|
+
checkedFiles.push(result.relativePath);
|
|
248
|
+
if (result.isDrifting) {
|
|
249
|
+
driftingFiles.push(result.relativePath);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Check skills
|
|
254
|
+
const skillsSourceDir = path.join(projectRoot, CLAUDE_DIR, SKILLS_DIR);
|
|
255
|
+
const skillsTargetDir = path.join(templatesDir, 'vendors', 'claude', CLAUDE_DIR, SKILLS_DIR);
|
|
256
|
+
if (fs.existsSync(skillsSourceDir)) {
|
|
257
|
+
const skillDirs = fs
|
|
258
|
+
.readdirSync(skillsSourceDir, { withFileTypes: true })
|
|
259
|
+
.filter((d) => d.isDirectory())
|
|
260
|
+
.map((d) => d.name);
|
|
261
|
+
for (const skillName of skillDirs) {
|
|
262
|
+
const skillFile = path.join(skillsSourceDir, skillName, 'SKILL.md');
|
|
263
|
+
const templatePath = path.join(skillsTargetDir, skillName, 'SKILL.md.template');
|
|
264
|
+
if (fs.existsSync(skillFile)) {
|
|
265
|
+
const result = checkFileDrift(skillFile, templatePath, projectRoot);
|
|
266
|
+
checkedFiles.push(result.relativePath);
|
|
267
|
+
if (result.isDrifting) {
|
|
268
|
+
driftingFiles.push(result.relativePath);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
hasDrift: driftingFiles.length > 0,
|
|
275
|
+
driftingFiles,
|
|
276
|
+
checkedFiles,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
170
279
|
/**
|
|
171
280
|
* CLI entry point
|
|
172
281
|
*/
|
|
282
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- CLI main() handles multiple modes and output formatting
|
|
173
283
|
export async function main() {
|
|
174
284
|
const opts = parseSyncTemplatesOptions();
|
|
175
285
|
const projectRoot = process.cwd();
|
|
286
|
+
// Check-drift mode: verify templates match source without syncing
|
|
287
|
+
if (opts.checkDrift) {
|
|
288
|
+
console.log('[sync-templates] Checking for template drift...');
|
|
289
|
+
const drift = await checkTemplateDrift(projectRoot);
|
|
290
|
+
if (opts.verbose) {
|
|
291
|
+
console.log(` Checked ${drift.checkedFiles.length} files`);
|
|
292
|
+
}
|
|
293
|
+
if (drift.hasDrift) {
|
|
294
|
+
console.log('\n[sync-templates] WARNING: Template drift detected!');
|
|
295
|
+
console.log(' The following templates are out of sync with their source:');
|
|
296
|
+
for (const file of drift.driftingFiles) {
|
|
297
|
+
console.log(` - ${file}`);
|
|
298
|
+
}
|
|
299
|
+
console.log('\n Run `pnpm sync:templates` to update templates.');
|
|
300
|
+
process.exitCode = 1;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
console.log('[sync-templates] All templates are in sync.');
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// Sync mode: update templates from source
|
|
176
308
|
console.log('[sync-templates] Syncing internal docs to CLI templates...');
|
|
177
309
|
if (opts.dryRun) {
|
|
178
310
|
console.log(' (dry-run mode - no files will be written)');
|
|
@@ -208,5 +340,5 @@ export async function main() {
|
|
|
208
340
|
// CLI entry point
|
|
209
341
|
import { runCLI } from './cli-entry-point.js';
|
|
210
342
|
if (import.meta.main) {
|
|
211
|
-
runCLI(main);
|
|
343
|
+
void runCLI(main);
|
|
212
344
|
}
|
package/dist/wu-prep.js
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console -- CLI command uses console for status output */
|
|
2
3
|
/**
|
|
3
4
|
* WU Prep Helper (WU-1223)
|
|
4
5
|
*
|
|
5
6
|
* Prepares a WU for completion by running gates and generating docs in the worktree.
|
|
6
7
|
* After successful prep, prints copy-paste instruction to run wu:done from main.
|
|
7
8
|
*
|
|
9
|
+
* WU-1344: When gates fail on spec:linter due to pre-existing WU validation errors
|
|
10
|
+
* (not caused by the current WU), prints a ready-to-copy wu:done --skip-gates
|
|
11
|
+
* command with reason and fix-wu placeholders.
|
|
12
|
+
*
|
|
8
13
|
* Workflow:
|
|
9
14
|
* 1. Verify we're in a worktree (error if in main checkout)
|
|
10
15
|
* 2. Run gates in the worktree
|
|
11
|
-
* 3.
|
|
12
|
-
* 4.
|
|
16
|
+
* 3. If gates fail, check if failures are pre-existing on main
|
|
17
|
+
* 4. If pre-existing, print skip-gates command; otherwise, print fix guidance
|
|
18
|
+
* 5. On success, print copy-paste instruction for wu:done from main
|
|
13
19
|
*
|
|
14
20
|
* Usage:
|
|
15
21
|
* pnpm wu:prep --id WU-XXX [--docs-only]
|
|
16
22
|
*
|
|
17
23
|
* @module
|
|
18
24
|
*/
|
|
25
|
+
import { spawnSync } from 'node:child_process';
|
|
19
26
|
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
20
27
|
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
21
28
|
import { resolveLocation } from '@lumenflow/core/dist/context/location-resolver.js';
|
|
@@ -39,6 +46,84 @@ const PREP_OPTIONS = {
|
|
|
39
46
|
description: 'Run docs-only gates (format, spec-linter)',
|
|
40
47
|
},
|
|
41
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* WU-1344: Check if a gate name is the spec:linter gate.
|
|
51
|
+
* Used to identify when spec:linter fails so we can check for pre-existing failures.
|
|
52
|
+
*
|
|
53
|
+
* @param gateName - Name of the gate that failed
|
|
54
|
+
* @returns true if this is the spec:linter gate
|
|
55
|
+
*/
|
|
56
|
+
export function isPreExistingSpecLinterFailure(gateName) {
|
|
57
|
+
const normalizedName = gateName.toLowerCase().replace(/[:-]/g, '');
|
|
58
|
+
return normalizedName === 'speclinter';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* WU-1344: Format a skip-gates command for wu:done.
|
|
62
|
+
* Includes --reason and --fix-wu placeholders.
|
|
63
|
+
*
|
|
64
|
+
* @param options - Configuration options
|
|
65
|
+
* @param options.wuId - The WU ID being completed
|
|
66
|
+
* @param options.mainCheckout - Path to main checkout
|
|
67
|
+
* @returns Formatted command string ready to copy-paste
|
|
68
|
+
*/
|
|
69
|
+
export function formatSkipGatesCommand(options) {
|
|
70
|
+
const { wuId, mainCheckout } = options;
|
|
71
|
+
return `cd ${mainCheckout} && pnpm wu:done --id ${wuId} --skip-gates --reason "pre-existing on main" --fix-wu WU-XXXX`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* WU-1344: Default implementation of execOnMain using spawnSync.
|
|
75
|
+
* Uses spawnSync with pnpm executable for safety (no shell injection risk).
|
|
76
|
+
*/
|
|
77
|
+
function defaultExecOnMain(mainCheckout) {
|
|
78
|
+
return async (cmd) => {
|
|
79
|
+
// Parse command to extract pnpm script name and args
|
|
80
|
+
// Expected format: "pnpm spec:linter" or similar
|
|
81
|
+
const parts = cmd.split(/\s+/);
|
|
82
|
+
const executable = parts[0];
|
|
83
|
+
const args = parts.slice(1);
|
|
84
|
+
const result = spawnSync(executable, args, {
|
|
85
|
+
cwd: mainCheckout,
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
exitCode: result.status ?? 1,
|
|
91
|
+
stdout: result.stdout ?? '',
|
|
92
|
+
stderr: result.stderr ?? '',
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* WU-1344: Check if spec:linter failures are pre-existing on main branch.
|
|
98
|
+
*
|
|
99
|
+
* Runs spec:linter on the main checkout to determine if the failures
|
|
100
|
+
* already exist there (i.e., not caused by the current WU).
|
|
101
|
+
*
|
|
102
|
+
* @param options - Configuration options
|
|
103
|
+
* @param options.mainCheckout - Path to main checkout
|
|
104
|
+
* @param options.execOnMain - Optional function to execute commands on main (for testing)
|
|
105
|
+
* @returns Result indicating whether failures are pre-existing
|
|
106
|
+
*/
|
|
107
|
+
export async function checkPreExistingFailures(options) {
|
|
108
|
+
const { mainCheckout, execOnMain = defaultExecOnMain(mainCheckout) } = options;
|
|
109
|
+
try {
|
|
110
|
+
// Run spec:linter on main checkout
|
|
111
|
+
const result = await execOnMain('pnpm spec:linter');
|
|
112
|
+
// If spec:linter fails on main, the failures are pre-existing
|
|
113
|
+
if (result.exitCode !== 0) {
|
|
114
|
+
return { hasPreExisting: true };
|
|
115
|
+
}
|
|
116
|
+
return { hasPreExisting: false };
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// If we can't check main, assume failures are NOT pre-existing
|
|
120
|
+
// (safer to require fixing rather than skipping)
|
|
121
|
+
return {
|
|
122
|
+
hasPreExisting: false,
|
|
123
|
+
error: error.message,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
42
127
|
/**
|
|
43
128
|
* Print success message with copy-paste instruction.
|
|
44
129
|
*/
|
|
@@ -106,6 +191,34 @@ async function main() {
|
|
|
106
191
|
console.log(`${PREP_PREFIX} Location: ${location.cwd}`);
|
|
107
192
|
console.log(`${PREP_PREFIX} Main checkout: ${location.mainCheckout}`);
|
|
108
193
|
console.log('');
|
|
194
|
+
// WU-1344: Check for pre-existing spec:linter failures on main BEFORE running gates.
|
|
195
|
+
// We do this first because runGates() calls die() on failure, which exits the process
|
|
196
|
+
// before we can check. By checking first, we can set up an exit handler to show
|
|
197
|
+
// the skip-gates command if gates fail.
|
|
198
|
+
console.log(`${PREP_PREFIX} Checking for pre-existing spec:linter failures on main...`);
|
|
199
|
+
const preExistingCheck = await checkPreExistingFailures({
|
|
200
|
+
mainCheckout: location.mainCheckout,
|
|
201
|
+
});
|
|
202
|
+
if (preExistingCheck.hasPreExisting) {
|
|
203
|
+
console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Pre-existing failures detected on main.`);
|
|
204
|
+
// Set up an exit handler to print the skip-gates command when gates fail
|
|
205
|
+
// This runs before process.exit() fully terminates the process
|
|
206
|
+
process.on('exit', (code) => {
|
|
207
|
+
if (code !== EXIT_CODES.SUCCESS) {
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Since failures are pre-existing on main, you can skip gates:`);
|
|
210
|
+
console.log('');
|
|
211
|
+
console.log(` ${formatSkipGatesCommand({ wuId: id, mainCheckout: location.mainCheckout })}`);
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(`${PREP_PREFIX} Replace WU-XXXX with the WU that will fix these spec issues.`);
|
|
214
|
+
console.log('');
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log(`${PREP_PREFIX} No pre-existing failures on main.`);
|
|
220
|
+
}
|
|
221
|
+
console.log('');
|
|
109
222
|
// Run gates in the worktree
|
|
110
223
|
console.log(`${PREP_PREFIX} Running gates in worktree...`);
|
|
111
224
|
const gatesResult = await runGates({
|
|
@@ -113,13 +226,23 @@ async function main() {
|
|
|
113
226
|
docsOnly: args.docsOnly,
|
|
114
227
|
});
|
|
115
228
|
if (!gatesResult) {
|
|
116
|
-
|
|
117
|
-
|
|
229
|
+
// Gates failed - if pre-existing check was already done and showed failures,
|
|
230
|
+
// the exit handler above will print the skip-gates command.
|
|
231
|
+
// Otherwise, tell the user to fix the failures.
|
|
232
|
+
if (!preExistingCheck.hasPreExisting) {
|
|
233
|
+
die(`${EMOJI.FAILURE} Gates failed in worktree.\n\n` +
|
|
234
|
+
`Fix the gate failures and run wu:prep again.`);
|
|
235
|
+
}
|
|
236
|
+
// Pre-existing failures - exit with error, handler will print skip-gates command
|
|
237
|
+
process.exit(EXIT_CODES.ERROR);
|
|
118
238
|
}
|
|
119
239
|
// Success - print copy-paste instruction
|
|
120
240
|
printSuccessMessage(id, location.mainCheckout);
|
|
121
241
|
}
|
|
122
|
-
main
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
242
|
+
// WU-1181: Use import.meta.main instead of process.argv[1] comparison
|
|
243
|
+
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
244
|
+
// path but import.meta.url resolves to the real path - they never match
|
|
245
|
+
import { runCLI } from './cli-entry-point.js';
|
|
246
|
+
if (import.meta.main) {
|
|
247
|
+
void runCLI(main);
|
|
248
|
+
}
|
package/dist/wu-spawn.js
CHANGED
|
@@ -38,11 +38,12 @@ import { WU_STATUS, PATTERNS, FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu
|
|
|
38
38
|
// WU-1603: Check lane lock status before spawning
|
|
39
39
|
// WU-1325: Import lock policy getter for lane availability check
|
|
40
40
|
import { checkLaneLock } from '@lumenflow/core/dist/lane-lock.js';
|
|
41
|
-
import { getLockPolicyForLane } from '@lumenflow/core/dist/lane-checker.js';
|
|
41
|
+
import { getLockPolicyForLane, getWipLimitForLane } from '@lumenflow/core/dist/lane-checker.js';
|
|
42
42
|
import { minimatch } from 'minimatch';
|
|
43
43
|
// WU-2252: Import invariants loader for spawn output injection
|
|
44
44
|
import { loadInvariants, INVARIANT_TYPES } from '@lumenflow/core/dist/invariants-runner.js';
|
|
45
45
|
import { validateSpawnArgs, generateExecutionModeSection, generateThinkToolGuidance, recordSpawnToRegistry, formatSpawnRecordedMessage, } from '@lumenflow/core/dist/wu-spawn-helpers.js';
|
|
46
|
+
// eslint-disable-next-line sonarjs/deprecation -- legacy factory used by CLI spawns
|
|
46
47
|
import { SpawnStrategyFactory } from '@lumenflow/core/dist/spawn-strategy.js';
|
|
47
48
|
import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
|
|
48
49
|
import { generateClientSkillsGuidance, generateSkillsSelectionSection, resolveClientConfig, } from '@lumenflow/core/dist/wu-spawn-skills.js';
|
|
@@ -1168,7 +1169,10 @@ export function checkLaneOccupation(lane) {
|
|
|
1168
1169
|
export function generateLaneOccupationWarning(lockMetadata, targetWuId, options = {}) {
|
|
1169
1170
|
const { isStale = false } = options;
|
|
1170
1171
|
let warning = `⚠️ Lane "${lockMetadata.lane}" is occupied by ${lockMetadata.wuId}\n`;
|
|
1171
|
-
|
|
1172
|
+
// WU-1346: Use injected values if provided, otherwise look up from config
|
|
1173
|
+
const lockPolicy = options.lockPolicy ?? getLockPolicyForLane(lockMetadata.lane);
|
|
1174
|
+
const wipLimit = options.wipLimit ?? getWipLimitForLane(lockMetadata.lane);
|
|
1175
|
+
warning += ` This violates WIP=${wipLimit} (lock_policy=${lockPolicy}).\n\n`;
|
|
1172
1176
|
if (isStale) {
|
|
1173
1177
|
warning += ` ⏰ This lock is STALE (>24 hours old) - the WU may be abandoned.\n`;
|
|
1174
1178
|
warning += ` Consider using pnpm wu:block --id ${lockMetadata.wuId} if work is stalled.\n\n`;
|
|
@@ -1327,6 +1331,7 @@ async function main() {
|
|
|
1327
1331
|
}
|
|
1328
1332
|
}
|
|
1329
1333
|
// Create strategy
|
|
1334
|
+
// eslint-disable-next-line sonarjs/deprecation -- legacy factory used by CLI spawns
|
|
1330
1335
|
const strategy = SpawnStrategyFactory.create(clientName);
|
|
1331
1336
|
const clientContext = { name: clientName, config: resolveClientConfig(config, clientName) };
|
|
1332
1337
|
if (clientName === 'codex-cli' || args.codex) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/cli",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "Command-line interface for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"initiative-status": "./dist/initiative-status.js",
|
|
75
75
|
"initiative-add-wu": "./dist/initiative-add-wu.js",
|
|
76
76
|
"initiative-plan": "./dist/initiative-plan.js",
|
|
77
|
-
"init-plan": "./dist/
|
|
77
|
+
"init-plan": "./dist/initiative-plan.js",
|
|
78
78
|
"plan-create": "./dist/plan-create.js",
|
|
79
79
|
"plan-link": "./dist/plan-link.js",
|
|
80
80
|
"plan-edit": "./dist/plan-edit.js",
|
|
@@ -148,11 +148,11 @@
|
|
|
148
148
|
"pretty-ms": "^9.2.0",
|
|
149
149
|
"simple-git": "^3.30.0",
|
|
150
150
|
"yaml": "^2.8.2",
|
|
151
|
-
"@lumenflow/
|
|
152
|
-
"@lumenflow/
|
|
153
|
-
"@lumenflow/
|
|
154
|
-
"@lumenflow/agent": "2.5.
|
|
155
|
-
"@lumenflow/
|
|
151
|
+
"@lumenflow/metrics": "2.5.1",
|
|
152
|
+
"@lumenflow/core": "2.5.1",
|
|
153
|
+
"@lumenflow/initiatives": "2.5.1",
|
|
154
|
+
"@lumenflow/agent": "2.5.1",
|
|
155
|
+
"@lumenflow/memory": "2.5.1"
|
|
156
156
|
},
|
|
157
157
|
"devDependencies": {
|
|
158
158
|
"@vitest/coverage-v8": "^4.0.17",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# LumenFlow Constraints Capsule
|
|
2
2
|
|
|
3
|
-
**Version:** 1.
|
|
3
|
+
**Version:** 1.1
|
|
4
4
|
**Last updated:** {{DATE}}
|
|
5
5
|
|
|
6
|
-
This document contains the
|
|
6
|
+
This document contains the 7 non-negotiable constraints that every agent must keep "in working memory" from first plan through `wu:done`.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## The
|
|
10
|
+
## The 7 Non-Negotiable Constraints
|
|
11
11
|
|
|
12
12
|
### 1. Worktree Discipline and Git Safety
|
|
13
13
|
|
|
@@ -19,8 +19,40 @@ This document contains the 6 non-negotiable constraints that every agent must ke
|
|
|
19
19
|
- Hooks block WU commits from main checkout
|
|
20
20
|
- Forbidden commands on main: `git reset --hard`, `git stash`, `git clean -fd`, `git push --force`
|
|
21
21
|
|
|
22
|
+
**MANDATORY PRE-WRITE CHECK**
|
|
23
|
+
|
|
24
|
+
Before ANY Write/Edit/Read operation:
|
|
25
|
+
|
|
26
|
+
1. **Verify worktree location**:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pwd
|
|
30
|
+
# Must show: .../worktrees/<lane>-wu-xxx
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. **Verify relative paths only**:
|
|
34
|
+
- ✅ `docs/...`, `packages/...`, `apps/...`, `./...`
|
|
35
|
+
- ❌ `/home/...`, `/Users/...`, or full repo paths
|
|
36
|
+
|
|
37
|
+
3. **Docs-only exception (documentation WUs)**:
|
|
38
|
+
- You may run **read-only** commands from main (e.g., `pnpm gates --docs-only`).
|
|
39
|
+
- **All file writes still require a worktree**. If no worktree exists, claim one first.
|
|
40
|
+
|
|
22
41
|
**Why:** Worktree isolation prevents cross-contamination between parallel WUs and protects the main branch.
|
|
23
42
|
|
|
43
|
+
**NEVER "QUICK FIX" ON MAIN**
|
|
44
|
+
|
|
45
|
+
If you see something broken on main (failing gates, format issues, typos, lint errors):
|
|
46
|
+
|
|
47
|
+
- ❌ **Don't** fix it directly, even if it seems small or helpful
|
|
48
|
+
- ❌ **Don't** run `prettier --write`, `pnpm lint --fix`, or any command that modifies files
|
|
49
|
+
- ✅ **Do** report the issue to the user
|
|
50
|
+
- ✅ **Do** create a WU if a fix is needed
|
|
51
|
+
|
|
52
|
+
**Why this matters:** The "helpful fix" instinct causes agents to modify files on main without a worktree. While commits are blocked by hooks, the files are still modified, requiring manual cleanup. Every change—no matter how small—needs a worktree.
|
|
53
|
+
|
|
54
|
+
**If you're on main and want to change something: STOP. Create a WU first.**
|
|
55
|
+
|
|
24
56
|
---
|
|
25
57
|
|
|
26
58
|
### 2. WUs Are Specs, Not Code
|
|
@@ -94,6 +126,32 @@ This document contains the 6 non-negotiable constraints that every agent must ke
|
|
|
94
126
|
|
|
95
127
|
---
|
|
96
128
|
|
|
129
|
+
### 7. Test Ratchet Pattern (WU-1253)
|
|
130
|
+
|
|
131
|
+
**Rule:** NEW test failures block gates; pre-existing failures (in baseline) warn but do not block.
|
|
132
|
+
|
|
133
|
+
**Baseline File:** `.lumenflow/test-baseline.json`
|
|
134
|
+
|
|
135
|
+
**Enforcement:**
|
|
136
|
+
|
|
137
|
+
- Gates compare current test results against baseline
|
|
138
|
+
- NEW failures (not in baseline) = **BLOCK** (must fix or add to baseline)
|
|
139
|
+
- Pre-existing failures (in baseline) = **WARNING** (continue, do not block WU)
|
|
140
|
+
- Fixed tests auto-removed from baseline (ratchet forward)
|
|
141
|
+
|
|
142
|
+
**When gates fail due to tests:**
|
|
143
|
+
|
|
144
|
+
1. Check baseline: `cat .lumenflow/test-baseline.json`
|
|
145
|
+
2. If failure is pre-existing: warning shown, WU proceeds
|
|
146
|
+
3. If failure is NEW: fix the test or add to baseline with:
|
|
147
|
+
```bash
|
|
148
|
+
pnpm baseline:add --test "<test_name>" --reason "<why>" --fix-wu WU-XXXX
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Why:** Agents should not be blocked by unrelated failures. The ratchet ensures quality improves over time (failures can only be removed, never added without justification).
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
97
155
|
## Mini Audit Checklist
|
|
98
156
|
|
|
99
157
|
Before running `wu:done`, verify:
|