@lumenflow/cli 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -105
- package/dist/__tests__/agent-spawn-coordination.test.js +451 -0
- package/dist/__tests__/commands/integrate.test.js +165 -0
- package/dist/__tests__/gates-config.test.js +0 -1
- package/dist/__tests__/hooks/enforcement.test.js +279 -0
- package/dist/__tests__/init-greenfield.test.js +247 -0
- package/dist/__tests__/init-quick-ref.test.js +0 -1
- package/dist/__tests__/init-template-portability.test.js +0 -1
- package/dist/__tests__/init.test.js +27 -0
- package/dist/__tests__/initiative-e2e.test.js +442 -0
- package/dist/__tests__/initiative-plan-replacement.test.js +0 -1
- package/dist/__tests__/memory-integration.test.js +333 -0
- package/dist/__tests__/release.test.js +1 -1
- package/dist/__tests__/safe-git.test.js +0 -1
- package/dist/__tests__/state-doctor.test.js +54 -0
- package/dist/__tests__/sync-templates.test.js +255 -0
- package/dist/__tests__/wu-create-required-fields.test.js +121 -0
- package/dist/__tests__/wu-done-auto-cleanup.test.js +135 -0
- package/dist/__tests__/wu-lifecycle-integration.test.js +388 -0
- package/dist/__tests__/wu-proto.test.js +97 -0
- package/dist/backlog-prune.js +0 -1
- package/dist/cli-entry-point.js +0 -1
- package/dist/commands/integrate.js +229 -0
- package/dist/docs-sync.js +46 -0
- package/dist/doctor.js +0 -2
- package/dist/gates.js +0 -7
- package/dist/hooks/enforcement-checks.js +209 -0
- package/dist/hooks/enforcement-generator.js +365 -0
- package/dist/hooks/enforcement-sync.js +243 -0
- package/dist/hooks/index.js +7 -0
- package/dist/init.js +266 -11
- package/dist/initiative-add-wu.js +0 -2
- package/dist/initiative-create.js +0 -3
- package/dist/initiative-edit.js +0 -5
- package/dist/initiative-plan.js +0 -1
- package/dist/initiative-remove-wu.js +0 -2
- package/dist/lane-health.js +0 -2
- package/dist/lane-suggest.js +0 -1
- package/dist/mem-checkpoint.js +0 -2
- package/dist/mem-cleanup.js +0 -2
- package/dist/mem-context.js +0 -3
- package/dist/mem-create.js +0 -2
- package/dist/mem-delete.js +0 -3
- package/dist/mem-inbox.js +0 -2
- package/dist/mem-index.js +0 -1
- package/dist/mem-init.js +0 -2
- package/dist/mem-profile.js +0 -1
- package/dist/mem-promote.js +0 -1
- package/dist/mem-ready.js +0 -2
- package/dist/mem-signal.js +0 -2
- package/dist/mem-start.js +0 -2
- package/dist/mem-summarize.js +0 -2
- package/dist/metrics-cli.js +1 -1
- package/dist/metrics-snapshot.js +1 -1
- package/dist/onboarding-smoke-test.js +0 -5
- package/dist/orchestrate-init-status.js +0 -1
- package/dist/orchestrate-initiative.js +0 -1
- package/dist/orchestrate-monitor.js +0 -1
- package/dist/plan-create.js +0 -2
- package/dist/plan-edit.js +0 -2
- package/dist/plan-link.js +0 -2
- package/dist/plan-promote.js +0 -2
- package/dist/signal-cleanup.js +0 -4
- package/dist/state-bootstrap.js +0 -1
- package/dist/state-cleanup.js +0 -4
- package/dist/state-doctor-fix.js +5 -8
- package/dist/state-doctor.js +0 -11
- package/dist/sync-templates.js +188 -34
- package/dist/wu-block.js +100 -48
- package/dist/wu-claim.js +1 -22
- package/dist/wu-cleanup.js +0 -1
- package/dist/wu-create.js +0 -2
- package/dist/wu-done-auto-cleanup.js +139 -0
- package/dist/wu-done.js +11 -4
- package/dist/wu-edit.js +0 -12
- package/dist/wu-preflight.js +0 -1
- package/dist/wu-prep.js +0 -1
- package/dist/wu-proto.js +329 -0
- package/dist/wu-spawn.js +0 -3
- package/dist/wu-unblock.js +0 -2
- package/dist/wu-validate.js +0 -1
- package/package.json +9 -7
- package/templates/core/.husky/pre-commit.template +93 -0
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +27 -0
- package/templates/core/ai/onboarding/rapid-prototyping.md +143 -0
- package/templates/core/ai/onboarding/starting-prompt.md.template +3 -3
- package/templates/vendors/claude/.claude/CLAUDE.md.template +25 -0
- package/templates/vendors/claude/.claude/hooks/enforce-worktree.sh +135 -0
package/dist/wu-block.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable no-console -- CLI tool requires console output */
|
|
3
2
|
/**
|
|
4
3
|
* WU Block Helper
|
|
5
4
|
*
|
|
@@ -29,7 +28,7 @@ import { readWU, writeWU, appendNote } from '@lumenflow/core/dist/wu-yaml.js';
|
|
|
29
28
|
import { BRANCHES, REMOTES, WU_STATUS, STATUS_SECTIONS, PATTERNS, LOG_PREFIX, FILE_SYSTEM, EXIT_CODES, STRING_LITERALS, MICRO_WORKTREE_OPERATIONS, } from '@lumenflow/core/dist/wu-constants.js';
|
|
30
29
|
import { ensureOnMain } from '@lumenflow/core/dist/wu-helpers.js';
|
|
31
30
|
import { ensureStaged } from '@lumenflow/core/dist/git-staged-validator.js';
|
|
32
|
-
import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
31
|
+
import { withMicroWorktree, LUMENFLOW_WU_TOOL_ENV } from '@lumenflow/core/dist/micro-worktree.js';
|
|
33
32
|
import { WUStateStore } from '@lumenflow/core/dist/wu-state-store.js';
|
|
34
33
|
// WU-1603: Atomic lane locking - release lock when WU is blocked
|
|
35
34
|
import { releaseLaneLock } from '@lumenflow/core/dist/lane-lock.js';
|
|
@@ -38,6 +37,30 @@ import { getLockPolicyForLane } from '@lumenflow/core/dist/lane-checker.js';
|
|
|
38
37
|
// ensureOnMain() moved to wu-helpers.ts (WU-1256)
|
|
39
38
|
// ensureStaged() moved to git-staged-validator.ts (WU-1341)
|
|
40
39
|
// defaultWorktreeFrom() moved to wu-paths.ts (WU-1341)
|
|
40
|
+
/**
|
|
41
|
+
* WU-1365: Execute a function with LUMENFLOW_WU_TOOL set, restoring afterwards
|
|
42
|
+
*
|
|
43
|
+
* Sets the LUMENFLOW_WU_TOOL env var to allow pre-push hook bypass, then
|
|
44
|
+
* restores the original value (or deletes it) after execution completes.
|
|
45
|
+
*
|
|
46
|
+
* @param toolName - Value to set for LUMENFLOW_WU_TOOL
|
|
47
|
+
* @param fn - Async function to execute
|
|
48
|
+
*/
|
|
49
|
+
async function withWuToolEnv(toolName, fn) {
|
|
50
|
+
const previousWuTool = process.env[LUMENFLOW_WU_TOOL_ENV];
|
|
51
|
+
process.env[LUMENFLOW_WU_TOOL_ENV] = toolName;
|
|
52
|
+
try {
|
|
53
|
+
return await fn();
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
if (previousWuTool === undefined) {
|
|
57
|
+
Reflect.deleteProperty(process.env, LUMENFLOW_WU_TOOL_ENV);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
process.env[LUMENFLOW_WU_TOOL_ENV] = previousWuTool;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
41
64
|
/**
|
|
42
65
|
* Remove WU entry from in-progress section of lines array
|
|
43
66
|
*/
|
|
@@ -47,7 +70,6 @@ function removeFromInProgressSection(lines, inProgIdx, rel, id) {
|
|
|
47
70
|
let endIdx = lines.slice(inProgIdx + 1).findIndex((l) => l.startsWith('## '));
|
|
48
71
|
endIdx = endIdx === -1 ? lines.length : inProgIdx + 1 + endIdx;
|
|
49
72
|
for (let i = inProgIdx + 1; i < endIdx; i++) {
|
|
50
|
-
// eslint-disable-next-line security/detect-object-injection -- array index loop
|
|
51
73
|
if (lines[i] && (lines[i].includes(rel) || lines[i].includes(`[${id}`))) {
|
|
52
74
|
lines.splice(i, 1);
|
|
53
75
|
endIdx--;
|
|
@@ -58,6 +80,31 @@ function removeFromInProgressSection(lines, inProgIdx, rel, id) {
|
|
|
58
80
|
if (section.length === 0)
|
|
59
81
|
lines.splice(endIdx, 0, '', '(No items currently in progress)', '');
|
|
60
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* WU-1365: Create missing blocked section in status.md
|
|
85
|
+
*
|
|
86
|
+
* Extracts this logic to reduce cognitive complexity of moveFromInProgressToBlocked.
|
|
87
|
+
*
|
|
88
|
+
* @param lines - Array of lines from status.md
|
|
89
|
+
* @param inProgIdx - Index of "## In Progress" header (-1 if not found)
|
|
90
|
+
* @returns Index of the blocked section header after creation
|
|
91
|
+
*/
|
|
92
|
+
function createMissingBlockedSection(lines, inProgIdx) {
|
|
93
|
+
console.log(`${LOG_PREFIX.BLOCK} Creating missing "${STATUS_SECTIONS.BLOCKED}" section in status.md`);
|
|
94
|
+
// Find a good insertion point - after in_progress section or at end
|
|
95
|
+
const insertIdx = inProgIdx !== -1 ? inProgIdx + 1 : lines.length;
|
|
96
|
+
// Skip to end of in_progress section content if it exists
|
|
97
|
+
let insertPoint = insertIdx;
|
|
98
|
+
if (inProgIdx !== -1) {
|
|
99
|
+
// Find where the next section starts
|
|
100
|
+
const nextSectionIdx = lines.slice(inProgIdx + 1).findIndex((l) => l.startsWith('## '));
|
|
101
|
+
insertPoint = nextSectionIdx === -1 ? lines.length : inProgIdx + 1 + nextSectionIdx;
|
|
102
|
+
}
|
|
103
|
+
// Insert the blocked section
|
|
104
|
+
lines.splice(insertPoint, 0, '', STATUS_SECTIONS.BLOCKED, '');
|
|
105
|
+
// Return the index of the newly created section header
|
|
106
|
+
return insertPoint + 1;
|
|
107
|
+
}
|
|
61
108
|
async function moveFromInProgressToBlocked(statusPath, id, title, reason) {
|
|
62
109
|
// Check file exists
|
|
63
110
|
const fileExists = await access(statusPath)
|
|
@@ -66,23 +113,25 @@ async function moveFromInProgressToBlocked(statusPath, id, title, reason) {
|
|
|
66
113
|
if (!fileExists)
|
|
67
114
|
die(`Missing ${statusPath}`);
|
|
68
115
|
const rel = `wu/${id}.yaml`;
|
|
69
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates status file
|
|
70
116
|
const content = await readFile(statusPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
71
117
|
const lines = content.split(/\r?\n/);
|
|
72
118
|
const findHeader = (h) => lines.findIndex((l) => l.trim().toLowerCase() === h.toLowerCase());
|
|
73
119
|
const inProgIdx = findHeader(STATUS_SECTIONS.IN_PROGRESS);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
120
|
+
let blockedIdx = findHeader(STATUS_SECTIONS.BLOCKED);
|
|
121
|
+
// WU-1365: Handle missing blocked section gracefully by creating it
|
|
122
|
+
if (blockedIdx === -1) {
|
|
123
|
+
blockedIdx = createMissingBlockedSection(lines, inProgIdx);
|
|
124
|
+
}
|
|
77
125
|
removeFromInProgressSection(lines, inProgIdx, rel, id);
|
|
78
126
|
// Add bullet to blocked
|
|
79
127
|
const reasonSuffix = reason ? ` — ${reason}` : '';
|
|
80
128
|
const bullet = `- [${id} — ${title}](${rel})${reasonSuffix}`;
|
|
129
|
+
// Recalculate blockedIdx after removeFromInProgressSection may have changed line positions
|
|
130
|
+
blockedIdx = findHeader(STATUS_SECTIONS.BLOCKED);
|
|
81
131
|
const sectionStart = blockedIdx + 1;
|
|
82
132
|
if (lines.slice(sectionStart).some((l) => l.includes(rel)))
|
|
83
133
|
return; // already listed
|
|
84
134
|
lines.splice(sectionStart, 0, '', bullet);
|
|
85
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes status file
|
|
86
135
|
await writeFile(statusPath, lines.join(STRING_LITERALS.NEWLINE), {
|
|
87
136
|
encoding: FILE_SYSTEM.UTF8,
|
|
88
137
|
});
|
|
@@ -169,47 +218,50 @@ async function main() {
|
|
|
169
218
|
const baseMsg = `wu(${id.toLowerCase()}): block`;
|
|
170
219
|
const commitMsg = args.reason ? `${baseMsg} — ${args.reason}` : baseMsg;
|
|
171
220
|
if (!args.noAuto) {
|
|
172
|
-
//
|
|
173
|
-
await
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
// WU-1365: Set LUMENFLOW_WU_TOOL to allow pre-push hook bypass for micro-worktree pushes
|
|
222
|
+
await withWuToolEnv(MICRO_WORKTREE_OPERATIONS.WU_BLOCK, async () => {
|
|
223
|
+
// Use micro-worktree pattern to avoid pre-commit hook blocking commits to main
|
|
224
|
+
await withMicroWorktree({
|
|
225
|
+
operation: MICRO_WORKTREE_OPERATIONS.WU_BLOCK,
|
|
226
|
+
id,
|
|
227
|
+
logPrefix: LOG_PREFIX.BLOCK,
|
|
228
|
+
pushOnly: true, // Push directly to origin/main without touching local main
|
|
229
|
+
execute: async ({ worktreePath }) => {
|
|
230
|
+
// Build paths relative to micro-worktree
|
|
231
|
+
const microWUPath = path.join(worktreePath, WU_PATHS.WU(id));
|
|
232
|
+
const microStatusPath = path.join(worktreePath, WU_PATHS.STATUS());
|
|
233
|
+
const microBacklogPath = path.join(worktreePath, WU_PATHS.BACKLOG());
|
|
234
|
+
// Update WU YAML in micro-worktree
|
|
235
|
+
const microDoc = readWU(microWUPath, id);
|
|
236
|
+
microDoc.status = WU_STATUS.BLOCKED;
|
|
237
|
+
const noteLine = args.reason
|
|
238
|
+
? `Blocked (${todayISO()}): ${args.reason}`
|
|
239
|
+
: `Blocked (${todayISO()})`;
|
|
240
|
+
appendNote(microDoc, noteLine);
|
|
241
|
+
writeWU(microWUPath, microDoc);
|
|
242
|
+
// Update status.md in micro-worktree
|
|
243
|
+
await moveFromInProgressToBlocked(microStatusPath, id, title, args.reason);
|
|
244
|
+
// Update backlog.md in micro-worktree (WU-1574: regenerate from state store)
|
|
245
|
+
await regenerateBacklogFromState(microBacklogPath);
|
|
246
|
+
// Append block event to WUStateStore (WU-1573)
|
|
247
|
+
const stateDir = path.join(worktreePath, '.lumenflow', 'state');
|
|
248
|
+
const store = new WUStateStore(stateDir);
|
|
249
|
+
await store.load();
|
|
250
|
+
await store.block(id, args.reason || 'No reason provided');
|
|
251
|
+
return {
|
|
252
|
+
commitMessage: commitMsg,
|
|
253
|
+
files: [
|
|
254
|
+
WU_PATHS.WU(id),
|
|
255
|
+
WU_PATHS.STATUS(),
|
|
256
|
+
WU_PATHS.BACKLOG(),
|
|
257
|
+
'.lumenflow/state/wu-events.jsonl',
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
// Fetch to update local main tracking
|
|
263
|
+
await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
210
264
|
});
|
|
211
|
-
// Fetch to update local main tracking
|
|
212
|
-
await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
213
265
|
}
|
|
214
266
|
else {
|
|
215
267
|
// Manual mode: expect files already staged
|
package/dist/wu-claim.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable no-console -- CLI tool requires console output */
|
|
3
2
|
/**
|
|
4
3
|
* WU Claim Helper
|
|
5
4
|
*
|
|
@@ -27,9 +26,7 @@ import { checkLaneFree, validateLaneFormat, checkWipJustification, } from '@lume
|
|
|
27
26
|
import { acquireLaneLock, releaseLaneLock, checkLaneLock, forceRemoveStaleLock, } from '@lumenflow/core/dist/lane-lock.js';
|
|
28
27
|
// WU-1825: Import from unified code-path-validator (consolidates 3 validators)
|
|
29
28
|
// WU-1213: Using deprecated sync API - async validate() requires larger refactor (separate WU)
|
|
30
|
-
import {
|
|
31
|
-
// eslint-disable-next-line sonarjs/deprecation -- sync API required for current architecture
|
|
32
|
-
validateLaneCodePaths, logLaneValidationWarnings, } from '@lumenflow/core/dist/code-path-validator.js';
|
|
29
|
+
import { validateLaneCodePaths, logLaneValidationWarnings, } from '@lumenflow/core/dist/code-path-validator.js';
|
|
33
30
|
// WU-1574: parseBacklogFrontmatter/getSectionHeadings removed - state store replaces backlog parsing
|
|
34
31
|
import { detectConflicts } from '@lumenflow/core/dist/code-paths-overlap.js';
|
|
35
32
|
import { getGitForCwd, createGitForPath } from '@lumenflow/core/dist/git-adapter.js';
|
|
@@ -86,7 +83,6 @@ const PREFIX = LOG_PREFIX.CLAIM;
|
|
|
86
83
|
*/
|
|
87
84
|
function preflightValidateWU(WU_PATH, id) {
|
|
88
85
|
// Check file exists
|
|
89
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
90
86
|
if (!existsSync(WU_PATH)) {
|
|
91
87
|
die(`WU file not found: ${WU_PATH}\n\n` +
|
|
92
88
|
`Cannot claim a WU that doesn't exist.\n\n` +
|
|
@@ -96,7 +92,6 @@ function preflightValidateWU(WU_PATH, id) {
|
|
|
96
92
|
` 3. Check if the WU file was moved or deleted`);
|
|
97
93
|
}
|
|
98
94
|
// Parse and validate YAML structure
|
|
99
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
100
95
|
const text = readFileSync(WU_PATH, { encoding: FILE_SYSTEM.UTF8 });
|
|
101
96
|
let doc;
|
|
102
97
|
try {
|
|
@@ -189,7 +184,6 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
|
|
|
189
184
|
// Read file
|
|
190
185
|
let text;
|
|
191
186
|
try {
|
|
192
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
193
187
|
text = await readFile(WU_PATH, { encoding: FILE_SYSTEM.UTF8 });
|
|
194
188
|
}
|
|
195
189
|
catch (e) {
|
|
@@ -266,7 +260,6 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
|
|
|
266
260
|
// WU-1352: Use centralized stringify for consistent output
|
|
267
261
|
const out = stringifyYAML(doc);
|
|
268
262
|
// Write file
|
|
269
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes WU files
|
|
270
263
|
await writeFile(WU_PATH, out, { encoding: FILE_SYSTEM.UTF8 });
|
|
271
264
|
// WU-1211: Return both title and initiative for status progression check
|
|
272
265
|
return { title: doc.title || '', initiative: doc.initiative || null };
|
|
@@ -326,7 +319,6 @@ async function addOrReplaceInProgressStatus(statusPath, id, title) {
|
|
|
326
319
|
const rel = `wu/${id}.yaml`;
|
|
327
320
|
const bullet = `- [${id} — ${title}](${rel})`;
|
|
328
321
|
// Read file
|
|
329
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates status file
|
|
330
322
|
const content = await readFile(statusPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
331
323
|
const lines = content.split(STRING_LITERALS.NEWLINE);
|
|
332
324
|
const findHeader = (h) => lines.findIndex((l) => l.trim().toLowerCase() === h.toLowerCase());
|
|
@@ -344,7 +336,6 @@ async function addOrReplaceInProgressStatus(statusPath, id, title) {
|
|
|
344
336
|
return; // already listed
|
|
345
337
|
// Remove "No items" marker if present
|
|
346
338
|
for (let i = startIdx + 1; i < endIdx; i++) {
|
|
347
|
-
// eslint-disable-next-line security/detect-object-injection -- array index loop
|
|
348
339
|
if (lines[i] && lines[i].includes('No items currently in progress')) {
|
|
349
340
|
lines.splice(i, 1);
|
|
350
341
|
endIdx--;
|
|
@@ -354,7 +345,6 @@ async function addOrReplaceInProgressStatus(statusPath, id, title) {
|
|
|
354
345
|
// Insert bullet right after header
|
|
355
346
|
lines.splice(startIdx + 1, 0, '', bullet);
|
|
356
347
|
// Write file
|
|
357
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes status file
|
|
358
348
|
await writeFile(statusPath, lines.join(STRING_LITERALS.NEWLINE), {
|
|
359
349
|
encoding: FILE_SYSTEM.UTF8,
|
|
360
350
|
});
|
|
@@ -444,10 +434,8 @@ async function applyStagedChangesToMicroWorktree(worktreePath, stagedChanges) {
|
|
|
444
434
|
continue;
|
|
445
435
|
}
|
|
446
436
|
const sourcePath = path.join(process.cwd(), filePath);
|
|
447
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool applies staged files
|
|
448
437
|
const contents = await readFile(sourcePath, { encoding: FILE_SYSTEM.UTF8 });
|
|
449
438
|
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
450
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool applies staged files
|
|
451
439
|
await writeFile(targetPath, contents, { encoding: FILE_SYSTEM.UTF8 });
|
|
452
440
|
}
|
|
453
441
|
}
|
|
@@ -520,11 +508,9 @@ async function readWUTitle(id) {
|
|
|
520
508
|
return null;
|
|
521
509
|
}
|
|
522
510
|
// Read file
|
|
523
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
524
511
|
const text = await readFile(p, { encoding: FILE_SYSTEM.UTF8 });
|
|
525
512
|
// Match title field - use RegExp.exec for sonarjs/prefer-regexp-exec compliance
|
|
526
513
|
// Regex is safe: runs on trusted WU YAML files with bounded input
|
|
527
|
-
// eslint-disable-next-line sonarjs/slow-regex -- Bounded input from WU YAML files
|
|
528
514
|
const titlePattern = /^title:\s*"?([^"\n]+)"?$/m;
|
|
529
515
|
const m = titlePattern.exec(text);
|
|
530
516
|
return m ? m[1] : null;
|
|
@@ -546,7 +532,6 @@ async function checkExistingBranchOnlyWU(statusPath, currentWU) {
|
|
|
546
532
|
return { hasBranchOnly: false, existingWU: null };
|
|
547
533
|
}
|
|
548
534
|
// Read file
|
|
549
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates status file
|
|
550
535
|
const content = await readFile(statusPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
551
536
|
const lines = content.split(STRING_LITERALS.NEWLINE);
|
|
552
537
|
// Find "In Progress" section
|
|
@@ -581,7 +566,6 @@ async function checkExistingBranchOnlyWU(statusPath, currentWU) {
|
|
|
581
566
|
}
|
|
582
567
|
try {
|
|
583
568
|
// Read file
|
|
584
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
585
569
|
const text = await readFile(wuPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
586
570
|
const doc = parseYAML(text);
|
|
587
571
|
if (doc && doc.claimed_mode === CLAIMED_MODES.BRANCH_ONLY) {
|
|
@@ -705,10 +689,8 @@ function handleLaneOccupancy(laneCheck, lane, id, force) {
|
|
|
705
689
|
* Handle code path overlap detection (WU-901)
|
|
706
690
|
*/
|
|
707
691
|
function handleCodePathOverlap(WU_PATH, STATUS_PATH, id, args) {
|
|
708
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
709
692
|
if (!existsSync(WU_PATH))
|
|
710
693
|
return;
|
|
711
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
712
694
|
const wuContent = readFileSync(WU_PATH, { encoding: FILE_SYSTEM.UTF8 });
|
|
713
695
|
const wuDoc = parseYAML(wuContent);
|
|
714
696
|
const codePaths = wuDoc.code_paths || [];
|
|
@@ -846,7 +828,6 @@ async function claimBranchOnlyMode(ctx) {
|
|
|
846
828
|
console.log(`\n${PREFIX} For sub-agent execution:`);
|
|
847
829
|
console.log(` /wu-prompt ${id} (generates full context prompt)`);
|
|
848
830
|
// Emit mandatory agent advisory based on code_paths (WU-1324)
|
|
849
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
850
831
|
const wuContent = await readFile(WU_PATH, { encoding: FILE_SYSTEM.UTF8 });
|
|
851
832
|
const wuDoc = parseYAML(wuContent);
|
|
852
833
|
const codePaths = wuDoc.code_paths || [];
|
|
@@ -1008,7 +989,6 @@ async function claimWorktreeMode(ctx) {
|
|
|
1008
989
|
// Emit mandatory agent advisory based on code_paths (WU-1324)
|
|
1009
990
|
// Read from worktree since that's where the updated YAML is
|
|
1010
991
|
const wtWUPathForAdvisory = path.join(worktreePath, WU_PATH);
|
|
1011
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
1012
992
|
const wuContent = await readFile(wtWUPathForAdvisory, {
|
|
1013
993
|
encoding: FILE_SYSTEM.UTF8,
|
|
1014
994
|
});
|
|
@@ -1226,7 +1206,6 @@ async function main() {
|
|
|
1226
1206
|
console.warn(`${PREFIX} ${wipJustificationCheck.warning}`);
|
|
1227
1207
|
}
|
|
1228
1208
|
// WU-1372: Lane-to-code_paths consistency check (advisory only, never blocks)
|
|
1229
|
-
// eslint-disable-next-line sonarjs/deprecation -- sync API required for current architecture
|
|
1230
1209
|
const laneValidation = validateLaneCodePaths(doc, args.lane);
|
|
1231
1210
|
logLaneValidationWarnings(laneValidation, PREFIX);
|
|
1232
1211
|
// WU-1361: YAML schema validation at claim time
|
package/dist/wu-cleanup.js
CHANGED
|
@@ -29,7 +29,6 @@ import { isGhCliAvailable } from '@lumenflow/core/dist/wu-done-pr.js';
|
|
|
29
29
|
import { BOX, CLEANUP_GUARD, EXIT_CODES, FILE_SYSTEM, LOG_PREFIX, REMOTES, STRING_LITERALS, WU_STATUS, } from '@lumenflow/core/dist/wu-constants.js';
|
|
30
30
|
// WU-2278: Import ownership validation for cross-agent protection
|
|
31
31
|
import { validateWorktreeOwnership } from '@lumenflow/core/dist/worktree-ownership.js';
|
|
32
|
-
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
33
32
|
const CLEANUP_OPTIONS = {
|
|
34
33
|
artifacts: {
|
|
35
34
|
name: 'artifacts',
|
package/dist/wu-create.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable no-console -- CLI tool requires console output */
|
|
3
2
|
/**
|
|
4
3
|
* WU Create Helper (WU-1262, WU-1439)
|
|
5
4
|
*
|
|
@@ -491,7 +490,6 @@ async function getDefaultAssignedTo() {
|
|
|
491
490
|
return '';
|
|
492
491
|
}
|
|
493
492
|
}
|
|
494
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity -- main() orchestrates multi-step WU creation workflow
|
|
495
493
|
async function main() {
|
|
496
494
|
const args = createWUParser({
|
|
497
495
|
name: 'wu-create',
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU-1366: Auto cleanup after wu:done success
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to run state cleanup automatically after successful wu:done.
|
|
5
|
+
* Cleanup is non-fatal: errors are logged but do not block completion.
|
|
6
|
+
*
|
|
7
|
+
* The cleanup.trigger config option controls when cleanup runs:
|
|
8
|
+
* - 'on_done': Run after wu:done success (default)
|
|
9
|
+
* - 'on_init': Run during lumenflow init
|
|
10
|
+
* - 'manual': Only run via pnpm state:cleanup
|
|
11
|
+
*
|
|
12
|
+
* @see {@link packages/@lumenflow/core/src/state-cleanup-core.ts} - Core cleanup orchestration
|
|
13
|
+
* @see {@link packages/@lumenflow/core/src/lumenflow-config-schema.ts} - CleanupConfigSchema
|
|
14
|
+
*/
|
|
15
|
+
import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
|
|
16
|
+
import { cleanupState } from '@lumenflow/core/dist/state-cleanup-core.js';
|
|
17
|
+
import { cleanupSignals } from '@lumenflow/memory/dist/signal-cleanup-core.js';
|
|
18
|
+
import { cleanupMemory } from '@lumenflow/memory/dist/mem-cleanup-core.js';
|
|
19
|
+
import { archiveWuEvents } from '@lumenflow/core/dist/wu-events-cleanup.js';
|
|
20
|
+
import fg from 'fast-glob';
|
|
21
|
+
import { readFile } from 'node:fs/promises';
|
|
22
|
+
import { parse as parseYaml } from 'yaml';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { LOG_PREFIX, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
25
|
+
/**
|
|
26
|
+
* Active WU statuses that should protect signals
|
|
27
|
+
*/
|
|
28
|
+
const ACTIVE_WU_STATUSES = ['in_progress', 'blocked'];
|
|
29
|
+
/**
|
|
30
|
+
* Get active WU IDs (in_progress or blocked) by scanning WU YAML files.
|
|
31
|
+
*
|
|
32
|
+
* @param baseDir - Base directory
|
|
33
|
+
* @returns Set of active WU IDs
|
|
34
|
+
*/
|
|
35
|
+
async function getActiveWuIds(baseDir) {
|
|
36
|
+
const activeIds = new Set();
|
|
37
|
+
try {
|
|
38
|
+
const config = getConfig({ projectRoot: baseDir });
|
|
39
|
+
const wuDir = path.join(baseDir, config.directories.wuDir);
|
|
40
|
+
// Find all WU YAML files
|
|
41
|
+
const wuFiles = await fg('WU-*.yaml', { cwd: wuDir });
|
|
42
|
+
for (const file of wuFiles) {
|
|
43
|
+
try {
|
|
44
|
+
const filePath = path.join(wuDir, file);
|
|
45
|
+
const content = await readFile(filePath, 'utf-8');
|
|
46
|
+
const wu = parseYaml(content);
|
|
47
|
+
if (wu.id && wu.status && ACTIVE_WU_STATUSES.includes(wu.status)) {
|
|
48
|
+
activeIds.add(wu.id);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Skip files that fail to parse
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// If we can't read WU files, return empty set (safer to remove nothing)
|
|
59
|
+
}
|
|
60
|
+
return activeIds;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if auto cleanup should run based on config.
|
|
64
|
+
*
|
|
65
|
+
* @returns true if cleanup.trigger is 'on_done' or not set (default)
|
|
66
|
+
*/
|
|
67
|
+
export function shouldRunAutoCleanup() {
|
|
68
|
+
try {
|
|
69
|
+
const config = getConfig();
|
|
70
|
+
const trigger = config.cleanup?.trigger;
|
|
71
|
+
// Default to 'on_done' if not set
|
|
72
|
+
if (!trigger) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return trigger === 'on_done';
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// If config can't be loaded, default to running cleanup
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Run state cleanup automatically after wu:done success.
|
|
84
|
+
*
|
|
85
|
+
* This function is non-fatal: errors are logged as warnings but do not throw.
|
|
86
|
+
* Cleanup respects the config.cleanup.trigger setting.
|
|
87
|
+
*
|
|
88
|
+
* @param baseDir - Base directory for cleanup operations
|
|
89
|
+
* @returns Promise that resolves when cleanup completes (or is skipped)
|
|
90
|
+
*/
|
|
91
|
+
export async function runAutoCleanupAfterDone(baseDir) {
|
|
92
|
+
// Check if cleanup should run
|
|
93
|
+
if (!shouldRunAutoCleanup()) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Running auto state cleanup...`);
|
|
98
|
+
const result = await cleanupState(baseDir, {
|
|
99
|
+
dryRun: false,
|
|
100
|
+
// Inject real cleanup functions
|
|
101
|
+
cleanupSignals: async (dir, opts) => cleanupSignals(dir, {
|
|
102
|
+
dryRun: opts.dryRun,
|
|
103
|
+
getActiveWuIds: () => getActiveWuIds(dir),
|
|
104
|
+
}),
|
|
105
|
+
cleanupMemory: async (dir, opts) => cleanupMemory(dir, {
|
|
106
|
+
dryRun: opts.dryRun,
|
|
107
|
+
}),
|
|
108
|
+
archiveEvents: async (dir, opts) => archiveWuEvents(dir, {
|
|
109
|
+
dryRun: opts.dryRun,
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
if (result.success) {
|
|
113
|
+
const typesStr = result.summary.typesExecuted.join(', ');
|
|
114
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} State cleanup complete: ` +
|
|
115
|
+
`${formatBytes(result.summary.totalBytesFreed)} freed [${typesStr}]`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Partial success - some cleanups failed
|
|
119
|
+
const errorMsgs = result.errors.map((e) => `${e.type}: ${e.message}`).join(', ');
|
|
120
|
+
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} State cleanup partial: ${errorMsgs}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// Non-fatal: log warning but don't throw
|
|
125
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
126
|
+
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not run auto state cleanup: ${message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Format bytes as human-readable string
|
|
131
|
+
*/
|
|
132
|
+
function formatBytes(bytes) {
|
|
133
|
+
const BYTES_PER_KB = 1024;
|
|
134
|
+
if (bytes < BYTES_PER_KB) {
|
|
135
|
+
return `${bytes} B`;
|
|
136
|
+
}
|
|
137
|
+
const kb = (bytes / BYTES_PER_KB).toFixed(1);
|
|
138
|
+
return `${kb} KB`;
|
|
139
|
+
}
|
package/dist/wu-done.js
CHANGED
|
@@ -95,6 +95,8 @@ import { SpawnStatus } from '@lumenflow/core/dist/spawn-registry-schema.js';
|
|
|
95
95
|
// WU-2022: Feature accessibility validation (blocking)
|
|
96
96
|
import { validateExposure, validateFeatureAccessibility, } from '@lumenflow/core/dist/wu-validation.js';
|
|
97
97
|
import { ensureCleanWorktree } from './wu-done-check.js';
|
|
98
|
+
// WU-1366: Auto cleanup after wu:done success
|
|
99
|
+
import { runAutoCleanupAfterDone } from './wu-done-auto-cleanup.js';
|
|
98
100
|
// WU-1588: Memory layer constants
|
|
99
101
|
const MEMORY_SIGNAL_TYPES = {
|
|
100
102
|
WU_COMPLETION: 'wu_completion',
|
|
@@ -1091,7 +1093,6 @@ function recordTransactionState(id, wuPath, stampPath, backlogPath, statusPath)
|
|
|
1091
1093
|
* @param {string} backlogPath - Path to backlog.md (WU-1230)
|
|
1092
1094
|
* @param {string} statusPath - Path to status.md (WU-1230)
|
|
1093
1095
|
*/
|
|
1094
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing complexity, refactor tracked separately
|
|
1095
1096
|
async function rollbackTransaction(txState, wuPath, stampPath, backlogPath, statusPath) {
|
|
1096
1097
|
console.error(`\n${LOG_PREFIX.DONE} ${EMOJI.WARNING} ROLLING BACK TRANSACTION (WU-755 + WU-1230 + WU-1255 + WU-1280)...`);
|
|
1097
1098
|
// WU-1280: ATOMIC ROLLBACK - Clean git state FIRST, then restore files
|
|
@@ -1272,7 +1273,6 @@ function runWUValidator(doc, id, allowTodo = false, worktreePath = null) {
|
|
|
1272
1273
|
* @param {string|null} overrideReason - Reason for override
|
|
1273
1274
|
* @returns {{valid: boolean, error: string|null, auditEntry: object|null}}
|
|
1274
1275
|
*/
|
|
1275
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing complexity, refactor tracked separately
|
|
1276
1276
|
async function checkOwnership(id, doc, worktreePath, overrideOwner = false, overrideReason = null) {
|
|
1277
1277
|
// Missing worktree means WU was not claimed properly (unless escape hatch applies)
|
|
1278
1278
|
if (!worktreePath || !existsSync(worktreePath)) {
|
|
@@ -2085,7 +2085,9 @@ async function main() {
|
|
|
2085
2085
|
if (lane)
|
|
2086
2086
|
releaseLaneLock(lane, { wuId: id });
|
|
2087
2087
|
}
|
|
2088
|
-
catch {
|
|
2088
|
+
catch {
|
|
2089
|
+
// Intentionally ignore lock release errors during cleanup
|
|
2090
|
+
}
|
|
2089
2091
|
process.exit(EXIT_CODES.SUCCESS);
|
|
2090
2092
|
}
|
|
2091
2093
|
}
|
|
@@ -2096,7 +2098,9 @@ async function main() {
|
|
|
2096
2098
|
if (lane)
|
|
2097
2099
|
releaseLaneLock(lane, { wuId: id });
|
|
2098
2100
|
}
|
|
2099
|
-
catch {
|
|
2101
|
+
catch {
|
|
2102
|
+
// Intentionally ignore lock release errors during error handling
|
|
2103
|
+
}
|
|
2100
2104
|
// WU-1811: Check if cleanup is safe before removing worktree
|
|
2101
2105
|
// If cleanupSafe is false (or undefined), preserve worktree for recovery
|
|
2102
2106
|
if (err.cleanupSafe === false) {
|
|
@@ -2180,6 +2184,9 @@ async function main() {
|
|
|
2180
2184
|
// WU-1983: Migration deployment nudge - only if supabase paths in code_paths
|
|
2181
2185
|
const codePaths = docMain.code_paths || [];
|
|
2182
2186
|
await printMigrationDeploymentNudge(codePaths, mainCheckoutPath);
|
|
2187
|
+
// WU-1366: Auto state cleanup after successful completion
|
|
2188
|
+
// Non-fatal: errors are logged but do not block completion
|
|
2189
|
+
await runAutoCleanupAfterDone(mainCheckoutPath);
|
|
2183
2190
|
}
|
|
2184
2191
|
/**
|
|
2185
2192
|
* WU-1983: Print migration deployment nudge when WU includes supabase changes.
|
package/dist/wu-edit.js
CHANGED
|
@@ -59,7 +59,6 @@ import { normalizeWUSchema } from '@lumenflow/core/dist/wu-schema-normalization.
|
|
|
59
59
|
import { lintWUSpec, formatLintErrors } from '@lumenflow/core/dist/wu-lint.js';
|
|
60
60
|
// WU-1329: Import path existence validators for strict validation
|
|
61
61
|
import { validateCodePathsExistence, validateTestPathsExistence, } from '@lumenflow/core/dist/wu-preflight-validators.js';
|
|
62
|
-
/* eslint-disable security/detect-object-injection */
|
|
63
62
|
const PREFIX = LOG_PREFIX.EDIT;
|
|
64
63
|
/**
|
|
65
64
|
* WU-1039: Validate which edits are allowed on done WUs
|
|
@@ -271,13 +270,11 @@ const EDIT_OPTIONS = {
|
|
|
271
270
|
* @param {string} newInitId - New initiative ID
|
|
272
271
|
* @returns {Array<string>} Array of relative file paths that were modified
|
|
273
272
|
*/
|
|
274
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing complexity, refactor tracked separately
|
|
275
273
|
function updateInitiativeWusArrays(worktreePath, wuId, oldInitId, newInitId) {
|
|
276
274
|
const modifiedFiles = [];
|
|
277
275
|
// Remove from old initiative if it exists and is different from new
|
|
278
276
|
if (oldInitId && oldInitId !== newInitId) {
|
|
279
277
|
const oldInitPath = join(worktreePath, INIT_PATHS.INITIATIVE(oldInitId));
|
|
280
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
281
278
|
if (existsSync(oldInitPath)) {
|
|
282
279
|
try {
|
|
283
280
|
const oldInit = readInitiative(oldInitPath, oldInitId);
|
|
@@ -296,7 +293,6 @@ function updateInitiativeWusArrays(worktreePath, wuId, oldInitId, newInitId) {
|
|
|
296
293
|
}
|
|
297
294
|
// Add to new initiative
|
|
298
295
|
const newInitPath = join(worktreePath, INIT_PATHS.INITIATIVE(newInitId));
|
|
299
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
300
296
|
if (existsSync(newInitPath)) {
|
|
301
297
|
try {
|
|
302
298
|
const newInit = readInitiative(newInitPath, newInitId);
|
|
@@ -333,7 +329,6 @@ function validateInitiativeFormat(initId) {
|
|
|
333
329
|
*/
|
|
334
330
|
function validateInitiativeExists(initId) {
|
|
335
331
|
const initPath = INIT_PATHS.INITIATIVE(initId);
|
|
336
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
337
332
|
if (!existsSync(initPath)) {
|
|
338
333
|
die(`Initiative not found: ${initId}\n\nFile does not exist: ${initPath}`);
|
|
339
334
|
}
|
|
@@ -464,11 +459,9 @@ function normalizeWUDates(wu) {
|
|
|
464
459
|
*/
|
|
465
460
|
function validateWUEditable(id) {
|
|
466
461
|
const wuPath = WU_PATHS.WU(id);
|
|
467
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
468
462
|
if (!existsSync(wuPath)) {
|
|
469
463
|
die(`WU ${id} not found at ${wuPath}\n\nEnsure the WU exists and you're in the repo root.`);
|
|
470
464
|
}
|
|
471
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
|
|
472
465
|
const content = readFileSync(wuPath, { encoding: FILE_SYSTEM.ENCODING });
|
|
473
466
|
const wu = parseYAML(content);
|
|
474
467
|
// WU-1929: Done WUs allow initiative/phase edits only (metadata reassignment)
|
|
@@ -505,7 +498,6 @@ function validateWUEditable(id) {
|
|
|
505
498
|
* @param {string} id - WU ID (for error messages)
|
|
506
499
|
*/
|
|
507
500
|
function validateWorktreeExists(worktreePath, id) {
|
|
508
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates worktree paths
|
|
509
501
|
if (!existsSync(worktreePath)) {
|
|
510
502
|
die(`Cannot edit WU ${id}: worktree path missing from disk.\n\n` +
|
|
511
503
|
`Expected worktree at: ${worktreePath}\n\n` +
|
|
@@ -580,7 +572,6 @@ async function applyEditsInWorktree({ worktreePath, id, updatedWU }) {
|
|
|
580
572
|
normalizeWUDates(updatedWU);
|
|
581
573
|
// Emergency fix Session 2: Use centralized stringifyYAML helper
|
|
582
574
|
const yamlContent = stringifyYAML(updatedWU);
|
|
583
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes WU files
|
|
584
575
|
writeFileSync(wuPath, yamlContent, { encoding: FILE_SYSTEM.ENCODING });
|
|
585
576
|
console.log(`${PREFIX} ✅ Updated ${id}.yaml in worktree`);
|
|
586
577
|
// Format the file
|
|
@@ -660,11 +651,9 @@ export function mergeStringField(existing, newValue, shouldReplace) {
|
|
|
660
651
|
*/
|
|
661
652
|
function loadSpecFile(specPath, originalWU) {
|
|
662
653
|
const resolvedPath = resolve(specPath);
|
|
663
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates spec files
|
|
664
654
|
if (!existsSync(resolvedPath)) {
|
|
665
655
|
die(`Spec file not found: ${resolvedPath}`);
|
|
666
656
|
}
|
|
667
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates spec files
|
|
668
657
|
const specContent = readFileSync(resolvedPath, {
|
|
669
658
|
encoding: FILE_SYSTEM.ENCODING,
|
|
670
659
|
});
|
|
@@ -1040,7 +1029,6 @@ async function main() {
|
|
|
1040
1029
|
normalizeWUDates(normalizedWU);
|
|
1041
1030
|
// Emergency fix Session 2: Use centralized stringifyYAML helper
|
|
1042
1031
|
const yamlContent = stringifyYAML(normalizedWU);
|
|
1043
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes WU files
|
|
1044
1032
|
writeFileSync(wuPath, yamlContent, { encoding: FILE_SYSTEM.ENCODING });
|
|
1045
1033
|
console.log(`${PREFIX} ✅ Updated ${id}.yaml in micro-worktree`);
|
|
1046
1034
|
// WU-1929: Handle bidirectional initiative updates
|