@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.
Files changed (27) hide show
  1. package/README.md +11 -8
  2. package/dist/__tests__/init-scripts.test.js +111 -0
  3. package/dist/__tests__/templates-sync.test.js +219 -0
  4. package/dist/init.js +90 -0
  5. package/dist/orchestrate-init-status.js +37 -9
  6. package/dist/orchestrate-initiative.js +10 -4
  7. package/dist/sync-templates.js +137 -5
  8. package/dist/wu-prep.js +131 -8
  9. package/dist/wu-spawn.js +7 -2
  10. package/package.json +7 -7
  11. package/templates/core/.lumenflow/constraints.md.template +61 -3
  12. package/templates/core/LUMENFLOW.md.template +85 -23
  13. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +157 -0
  14. package/templates/core/ai/onboarding/agent-safety-card.md.template +227 -0
  15. package/templates/core/ai/onboarding/docs-generation.md.template +277 -0
  16. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +49 -7
  17. package/templates/core/ai/onboarding/quick-ref-commands.md.template +343 -110
  18. package/templates/core/ai/onboarding/release-process.md.template +8 -2
  19. package/templates/core/ai/onboarding/starting-prompt.md.template +407 -0
  20. package/templates/core/ai/onboarding/test-ratchet.md.template +131 -0
  21. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +91 -38
  22. package/templates/core/ai/onboarding/vendor-support.md.template +219 -0
  23. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +13 -1
  24. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +14 -16
  25. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +48 -4
  26. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +5 -1
  27. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +19 -8
@@ -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, '.claude', 'skills');
123
- const targetDir = path.join(getTemplatesDir(projectRoot), 'vendors', 'claude', '.claude', 'skills');
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, '.lumenflow', 'constraints.md');
157
- const constraintsTarget = path.join(templatesDir, 'core', '.lumenflow', 'constraints.md.template');
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. Generate docs (if applicable)
12
- * 4. Print copy-paste instruction for wu:done from main
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
- die(`${EMOJI.FAILURE} Gates failed in worktree.\n\n` +
117
- `Fix the gate failures and run wu:prep again.`);
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().catch((e) => {
123
- console.error(e.message);
124
- process.exit(EXIT_CODES.ERROR);
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
- warning += ` This violates WIP=1 (Work In Progress limit of 1 per lane).\n\n`;
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.0",
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/init-plan.js",
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/core": "2.5.0",
152
- "@lumenflow/initiatives": "2.5.0",
153
- "@lumenflow/memory": "2.5.0",
154
- "@lumenflow/agent": "2.5.0",
155
- "@lumenflow/metrics": "2.5.0"
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.0
3
+ **Version:** 1.1
4
4
  **Last updated:** {{DATE}}
5
5
 
6
- This document contains the 6 non-negotiable constraints that every agent must keep "in working memory" from first plan through `wu:done`.
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 6 Non-Negotiable Constraints
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: