@lumenflow/cli 2.2.2 → 2.3.2
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 +147 -57
- package/dist/__tests__/agent-log-issue.test.js +56 -0
- package/dist/__tests__/cli-entry-point.test.js +66 -17
- package/dist/__tests__/cli-subprocess.test.js +25 -0
- package/dist/__tests__/init.test.js +298 -0
- package/dist/__tests__/initiative-plan.test.js +340 -0
- package/dist/__tests__/mem-cleanup-execution.test.js +19 -0
- package/dist/__tests__/merge-block.test.js +220 -0
- package/dist/__tests__/release.test.js +61 -0
- package/dist/__tests__/safe-git.test.js +191 -0
- package/dist/__tests__/state-doctor.test.js +274 -0
- package/dist/__tests__/wu-done.test.js +36 -0
- package/dist/__tests__/wu-edit.test.js +119 -0
- package/dist/__tests__/wu-prep.test.js +108 -0
- package/dist/agent-issues-query.js +4 -3
- package/dist/agent-log-issue.js +25 -4
- package/dist/backlog-prune.js +5 -4
- package/dist/cli-entry-point.js +11 -1
- package/dist/doctor.js +368 -0
- package/dist/flow-bottlenecks.js +6 -5
- package/dist/flow-report.js +4 -3
- package/dist/gates.js +356 -101
- package/dist/guard-locked.js +4 -3
- package/dist/guard-worktree-commit.js +4 -3
- package/dist/init.js +517 -86
- package/dist/initiative-add-wu.js +4 -3
- package/dist/initiative-bulk-assign-wus.js +8 -5
- package/dist/initiative-create.js +73 -37
- package/dist/initiative-edit.js +37 -21
- package/dist/initiative-list.js +4 -3
- package/dist/initiative-plan.js +337 -0
- package/dist/initiative-status.js +4 -3
- package/dist/lane-health.js +377 -0
- package/dist/lane-suggest.js +382 -0
- package/dist/mem-checkpoint.js +2 -2
- package/dist/mem-cleanup.js +2 -2
- package/dist/mem-context.js +306 -0
- package/dist/mem-create.js +2 -2
- package/dist/mem-delete.js +293 -0
- package/dist/mem-inbox.js +2 -2
- package/dist/mem-index.js +211 -0
- package/dist/mem-init.js +1 -1
- package/dist/mem-profile.js +207 -0
- package/dist/mem-promote.js +254 -0
- package/dist/mem-ready.js +2 -2
- package/dist/mem-signal.js +2 -2
- package/dist/mem-start.js +2 -2
- package/dist/mem-summarize.js +2 -2
- package/dist/mem-triage.js +2 -2
- package/dist/merge-block.js +222 -0
- package/dist/metrics-cli.js +7 -4
- package/dist/metrics-snapshot.js +4 -3
- package/dist/orchestrate-initiative.js +10 -4
- package/dist/orchestrate-monitor.js +379 -31
- package/dist/release.js +69 -29
- package/dist/signal-cleanup.js +296 -0
- package/dist/spawn-list.js +6 -5
- package/dist/state-bootstrap.js +5 -4
- package/dist/state-cleanup.js +360 -0
- package/dist/state-doctor-fix.js +196 -0
- package/dist/state-doctor.js +501 -0
- package/dist/validate-agent-skills.js +4 -3
- package/dist/validate-agent-sync.js +4 -3
- package/dist/validate-backlog-sync.js +4 -3
- package/dist/validate-skills-spec.js +4 -3
- package/dist/validate.js +4 -3
- package/dist/wu-block.js +3 -3
- package/dist/wu-claim.js +208 -98
- package/dist/wu-cleanup.js +5 -4
- package/dist/wu-create.js +71 -46
- package/dist/wu-delete.js +88 -60
- package/dist/wu-deps.js +6 -5
- package/dist/wu-done-check.js +34 -0
- package/dist/wu-done.js +39 -12
- package/dist/wu-edit.js +63 -28
- package/dist/wu-infer-lane.js +7 -6
- package/dist/wu-preflight.js +23 -81
- package/dist/wu-prep.js +125 -0
- package/dist/wu-prune.js +4 -3
- package/dist/wu-recover.js +88 -22
- package/dist/wu-repair.js +7 -6
- package/dist/wu-spawn.js +226 -270
- package/dist/wu-status.js +4 -3
- package/dist/wu-unblock.js +5 -5
- package/dist/wu-unlock-lane.js +4 -3
- package/dist/wu-validate.js +5 -4
- package/package.json +16 -7
- package/templates/core/.lumenflow/constraints.md.template +192 -0
- package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
- package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
- package/templates/core/AGENTS.md.template +60 -0
- package/templates/core/LUMENFLOW.md.template +255 -0
- package/templates/core/UPGRADING.md.template +121 -0
- package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
- package/templates/core/ai/onboarding/release-process.md.template +362 -0
- package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
- package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
- package/templates/vendors/aider/.aider.conf.yml.template +27 -0
- package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
- package/templates/vendors/claude/.claude/settings.json.template +49 -0
- package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
- package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
- package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
- package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
- package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
- package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
- package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
- package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
- package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
- package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
- package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
- package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
- package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
- package/templates/vendors/cline/.clinerules.template +53 -0
- package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
- package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
- package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +34 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console -- CLI tool requires console output */
|
|
3
|
+
/**
|
|
4
|
+
* State Doctor CLI (WU-1209)
|
|
5
|
+
*
|
|
6
|
+
* Integrity checker for LumenFlow state that detects:
|
|
7
|
+
* - Orphaned WUs (done status but no stamp)
|
|
8
|
+
* - Dangling signals (reference non-existent WUs)
|
|
9
|
+
* - Broken memory relationships (events for missing WU specs)
|
|
10
|
+
*
|
|
11
|
+
* Inspired by Beads bd doctor command.
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Human-readable output with actionable suggestions
|
|
15
|
+
* - --fix flag for safe auto-repair of resolvable issues
|
|
16
|
+
* - --dry-run to preview what would be fixed
|
|
17
|
+
* - --json for machine-readable output
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* pnpm state:doctor # Run integrity checks
|
|
21
|
+
* pnpm state:doctor --fix # Auto-repair safe issues
|
|
22
|
+
* pnpm state:doctor --fix --dry-run # Preview repairs
|
|
23
|
+
* pnpm state:doctor --json # Output as JSON
|
|
24
|
+
*
|
|
25
|
+
* @see {@link packages/@lumenflow/core/src/state-doctor-core.ts} - Core logic
|
|
26
|
+
* @see {@link packages/@lumenflow/core/src/__tests__/state-doctor-core.test.ts} - Tests
|
|
27
|
+
*/
|
|
28
|
+
import fs from 'node:fs/promises';
|
|
29
|
+
import path from 'node:path';
|
|
30
|
+
import fg from 'fast-glob';
|
|
31
|
+
import { parse as parseYaml } from 'yaml';
|
|
32
|
+
import { diagnoseState, ISSUE_TYPES, ISSUE_SEVERITY, } from '@lumenflow/core/dist/state-doctor-core.js';
|
|
33
|
+
import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
|
|
34
|
+
import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
|
|
35
|
+
import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
|
|
36
|
+
import { createStamp } from '@lumenflow/core/dist/stamp-utils.js';
|
|
37
|
+
import { createStateDoctorFixDeps } from './state-doctor-fix.js';
|
|
38
|
+
/**
|
|
39
|
+
* Log prefix for state:doctor output
|
|
40
|
+
*/
|
|
41
|
+
const LOG_PREFIX = '[state:doctor]';
|
|
42
|
+
/**
|
|
43
|
+
* Signals file path (relative to project root)
|
|
44
|
+
* Located in memory subdirectory: .lumenflow/memory/signals.jsonl
|
|
45
|
+
*/
|
|
46
|
+
const SIGNALS_FILE = '.lumenflow/memory/signals.jsonl';
|
|
47
|
+
/**
|
|
48
|
+
* WU events file path (relative to project root)
|
|
49
|
+
* Located in state directory: .lumenflow/state/wu-events.jsonl
|
|
50
|
+
*/
|
|
51
|
+
const WU_EVENTS_FILE = '.lumenflow/state/wu-events.jsonl';
|
|
52
|
+
/**
|
|
53
|
+
* Tool name for audit logging
|
|
54
|
+
*/
|
|
55
|
+
const TOOL_NAME = 'state:doctor';
|
|
56
|
+
/**
|
|
57
|
+
* Emoji constants for output formatting
|
|
58
|
+
*/
|
|
59
|
+
const EMOJI = {
|
|
60
|
+
SUCCESS: '\u2705',
|
|
61
|
+
FAILURE: '\u274C',
|
|
62
|
+
WARNING: '\u26A0\uFE0F',
|
|
63
|
+
FIX: '\u{1F527}',
|
|
64
|
+
INFO: '\u2139\uFE0F',
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* CLI argument options for state:doctor
|
|
68
|
+
*/
|
|
69
|
+
const CLI_OPTIONS = {
|
|
70
|
+
fix: {
|
|
71
|
+
name: 'fix',
|
|
72
|
+
flags: '--fix',
|
|
73
|
+
description: 'Auto-repair safe issues',
|
|
74
|
+
},
|
|
75
|
+
dryRun: {
|
|
76
|
+
name: 'dryRun',
|
|
77
|
+
flags: '--dry-run',
|
|
78
|
+
description: 'Preview repairs without making changes',
|
|
79
|
+
},
|
|
80
|
+
json: {
|
|
81
|
+
name: 'json',
|
|
82
|
+
flags: '--json',
|
|
83
|
+
description: 'Output as JSON',
|
|
84
|
+
},
|
|
85
|
+
quiet: {
|
|
86
|
+
name: 'quiet',
|
|
87
|
+
flags: '-q, --quiet',
|
|
88
|
+
description: 'Suppress output except errors and summary',
|
|
89
|
+
},
|
|
90
|
+
baseDir: {
|
|
91
|
+
name: 'baseDir',
|
|
92
|
+
flags: '-b, --base-dir <path>',
|
|
93
|
+
description: 'Base directory (defaults to current directory)',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Write audit log entry for tool execution
|
|
98
|
+
*/
|
|
99
|
+
async function writeAuditLog(baseDir, entry) {
|
|
100
|
+
try {
|
|
101
|
+
const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
|
|
102
|
+
const logDir = path.dirname(logPath);
|
|
103
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool creates known directory
|
|
104
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
105
|
+
const line = `${JSON.stringify(entry)}\n`;
|
|
106
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes audit log
|
|
107
|
+
await fs.appendFile(logPath, line, 'utf-8');
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Audit logging is non-fatal - silently ignore errors
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Parse CLI arguments
|
|
115
|
+
*/
|
|
116
|
+
function parseArguments() {
|
|
117
|
+
return createWUParser({
|
|
118
|
+
name: 'state-doctor',
|
|
119
|
+
description: 'Check state integrity and optionally repair issues',
|
|
120
|
+
options: [
|
|
121
|
+
CLI_OPTIONS.fix,
|
|
122
|
+
CLI_OPTIONS.dryRun,
|
|
123
|
+
CLI_OPTIONS.json,
|
|
124
|
+
CLI_OPTIONS.quiet,
|
|
125
|
+
CLI_OPTIONS.baseDir,
|
|
126
|
+
],
|
|
127
|
+
required: [],
|
|
128
|
+
allowPositionalId: false,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Create dependencies for state doctor from filesystem
|
|
133
|
+
*/
|
|
134
|
+
async function createDeps(baseDir) {
|
|
135
|
+
const config = getConfig({ projectRoot: baseDir });
|
|
136
|
+
return {
|
|
137
|
+
/**
|
|
138
|
+
* List all WU YAML files
|
|
139
|
+
*/
|
|
140
|
+
listWUs: async () => {
|
|
141
|
+
try {
|
|
142
|
+
const wuDir = path.join(baseDir, config.directories.wuDir);
|
|
143
|
+
const wuFiles = await fg('WU-*.yaml', { cwd: wuDir });
|
|
144
|
+
const wus = [];
|
|
145
|
+
for (const file of wuFiles) {
|
|
146
|
+
try {
|
|
147
|
+
const filePath = path.join(wuDir, file);
|
|
148
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads known path
|
|
149
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
150
|
+
const wu = parseYaml(content);
|
|
151
|
+
if (wu.id && wu.status) {
|
|
152
|
+
wus.push({
|
|
153
|
+
id: wu.id,
|
|
154
|
+
status: wu.status,
|
|
155
|
+
lane: wu.lane,
|
|
156
|
+
title: wu.title,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Skip files that fail to parse
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return wus;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
/**
|
|
172
|
+
* List all stamp file IDs
|
|
173
|
+
*/
|
|
174
|
+
listStamps: async () => {
|
|
175
|
+
try {
|
|
176
|
+
const stampsDir = path.join(baseDir, LUMENFLOW_PATHS.STAMPS_DIR);
|
|
177
|
+
const stampFiles = await fg('WU-*.done', { cwd: stampsDir });
|
|
178
|
+
return stampFiles.map((file) => file.replace('.done', ''));
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
/**
|
|
185
|
+
* List all signals from NDJSON file
|
|
186
|
+
*/
|
|
187
|
+
listSignals: async () => {
|
|
188
|
+
try {
|
|
189
|
+
const signalsPath = path.join(baseDir, SIGNALS_FILE);
|
|
190
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads known path
|
|
191
|
+
const content = await fs.readFile(signalsPath, 'utf-8');
|
|
192
|
+
const signals = [];
|
|
193
|
+
for (const line of content.split('\n')) {
|
|
194
|
+
if (!line.trim())
|
|
195
|
+
continue;
|
|
196
|
+
try {
|
|
197
|
+
const signal = JSON.parse(line);
|
|
198
|
+
if (signal.id) {
|
|
199
|
+
signals.push({
|
|
200
|
+
id: signal.id,
|
|
201
|
+
wuId: signal.wuId,
|
|
202
|
+
timestamp: signal.timestamp,
|
|
203
|
+
message: signal.message,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Skip malformed lines
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return signals;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
/**
|
|
219
|
+
* List all events from NDJSON file
|
|
220
|
+
*/
|
|
221
|
+
listEvents: async () => {
|
|
222
|
+
try {
|
|
223
|
+
const eventsPath = path.join(baseDir, WU_EVENTS_FILE);
|
|
224
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads known path
|
|
225
|
+
const content = await fs.readFile(eventsPath, 'utf-8');
|
|
226
|
+
const events = [];
|
|
227
|
+
for (const line of content.split('\n')) {
|
|
228
|
+
if (!line.trim())
|
|
229
|
+
continue;
|
|
230
|
+
try {
|
|
231
|
+
const event = JSON.parse(line);
|
|
232
|
+
if (event.wuId && event.type) {
|
|
233
|
+
events.push({
|
|
234
|
+
wuId: event.wuId,
|
|
235
|
+
type: event.type,
|
|
236
|
+
timestamp: event.timestamp,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Skip malformed lines
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return events;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
/**
|
|
252
|
+
* Remove a signal by ID (rewrite signals file without the target)
|
|
253
|
+
*/
|
|
254
|
+
removeSignal: async (id) => {
|
|
255
|
+
const signalsPath = path.join(baseDir, SIGNALS_FILE);
|
|
256
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads known path
|
|
257
|
+
const content = await fs.readFile(signalsPath, 'utf-8');
|
|
258
|
+
const lines = content.split('\n').filter((line) => {
|
|
259
|
+
if (!line.trim())
|
|
260
|
+
return false;
|
|
261
|
+
try {
|
|
262
|
+
const signal = JSON.parse(line);
|
|
263
|
+
return signal.id !== id;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return true; // Keep malformed lines
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes known path
|
|
270
|
+
await fs.writeFile(signalsPath, lines.join('\n') + '\n', 'utf-8');
|
|
271
|
+
},
|
|
272
|
+
/**
|
|
273
|
+
* Remove events for a WU (rewrite events file without the target WU)
|
|
274
|
+
*/
|
|
275
|
+
removeEvent: async (wuId) => {
|
|
276
|
+
const eventsPath = path.join(baseDir, WU_EVENTS_FILE);
|
|
277
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads known path
|
|
278
|
+
const content = await fs.readFile(eventsPath, 'utf-8');
|
|
279
|
+
const lines = content.split('\n').filter((line) => {
|
|
280
|
+
if (!line.trim())
|
|
281
|
+
return false;
|
|
282
|
+
try {
|
|
283
|
+
const event = JSON.parse(line);
|
|
284
|
+
return event.wuId !== wuId;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return true; // Keep malformed lines
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes known path
|
|
291
|
+
await fs.writeFile(eventsPath, lines.join('\n') + '\n', 'utf-8');
|
|
292
|
+
},
|
|
293
|
+
/**
|
|
294
|
+
* Create a stamp for a WU
|
|
295
|
+
*/
|
|
296
|
+
createStamp: async (wuId, title) => {
|
|
297
|
+
const stampsDir = path.join(baseDir, LUMENFLOW_PATHS.STAMPS_DIR);
|
|
298
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool creates known directory
|
|
299
|
+
await fs.mkdir(stampsDir, { recursive: true });
|
|
300
|
+
await createStamp({
|
|
301
|
+
id: wuId,
|
|
302
|
+
title,
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get emoji for issue severity
|
|
309
|
+
*/
|
|
310
|
+
function getSeverityEmoji(severity) {
|
|
311
|
+
switch (severity) {
|
|
312
|
+
case ISSUE_SEVERITY.ERROR:
|
|
313
|
+
return EMOJI.FAILURE;
|
|
314
|
+
case ISSUE_SEVERITY.WARNING:
|
|
315
|
+
return EMOJI.WARNING;
|
|
316
|
+
case ISSUE_SEVERITY.INFO:
|
|
317
|
+
return EMOJI.INFO;
|
|
318
|
+
default:
|
|
319
|
+
return EMOJI.WARNING;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get human-readable issue type label
|
|
324
|
+
*/
|
|
325
|
+
function getIssueTypeLabel(type) {
|
|
326
|
+
switch (type) {
|
|
327
|
+
case ISSUE_TYPES.ORPHANED_WU:
|
|
328
|
+
return 'Orphaned WU';
|
|
329
|
+
case ISSUE_TYPES.DANGLING_SIGNAL:
|
|
330
|
+
return 'Dangling Signal';
|
|
331
|
+
case ISSUE_TYPES.BROKEN_EVENT:
|
|
332
|
+
return 'Broken Event';
|
|
333
|
+
default:
|
|
334
|
+
return type;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Print summary section
|
|
339
|
+
*/
|
|
340
|
+
function printSummary(result) {
|
|
341
|
+
console.log('=== Summary ===');
|
|
342
|
+
console.log(` Orphaned WUs: ${result.summary.orphanedWUs}`);
|
|
343
|
+
console.log(` Dangling Signals: ${result.summary.danglingSignals}`);
|
|
344
|
+
console.log(` Broken Events: ${result.summary.brokenEvents}`);
|
|
345
|
+
console.log(` Total Issues: ${result.summary.totalIssues}`);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Print fixed issues section
|
|
349
|
+
*/
|
|
350
|
+
function printFixedSection(result) {
|
|
351
|
+
if (result.fixed.length > 0) {
|
|
352
|
+
console.log('\n=== Fixed ===');
|
|
353
|
+
for (const issue of result.fixed) {
|
|
354
|
+
const id = issue.wuId || issue.signalId;
|
|
355
|
+
console.log(` ${EMOJI.SUCCESS} Fixed: ${getIssueTypeLabel(issue.type)} - ${id}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (result.fixErrors.length > 0) {
|
|
359
|
+
console.log('\n=== Fix Errors ===');
|
|
360
|
+
for (const err of result.fixErrors) {
|
|
361
|
+
const id = err.wuId || err.signalId;
|
|
362
|
+
console.log(` ${EMOJI.FAILURE} Failed to fix: ${id} - ${err.error}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Print dry-run section
|
|
368
|
+
*/
|
|
369
|
+
function printDryRunSection(result) {
|
|
370
|
+
if (!result.dryRun || !result.wouldFix || result.wouldFix.length === 0) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
console.log('\n=== Would Fix (dry-run) ===');
|
|
374
|
+
for (const issue of result.wouldFix) {
|
|
375
|
+
const id = issue.wuId || issue.signalId;
|
|
376
|
+
console.log(` ${EMOJI.FIX} Would fix: ${getIssueTypeLabel(issue.type)} - ${id}`);
|
|
377
|
+
}
|
|
378
|
+
console.log('\n To apply fixes, run without --dry-run');
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Print issues list
|
|
382
|
+
*/
|
|
383
|
+
function printIssues(result) {
|
|
384
|
+
console.log(`${LOG_PREFIX} ${EMOJI.WARNING} Found ${result.summary.totalIssues} issue(s)\n`);
|
|
385
|
+
console.log('=== Issues ===');
|
|
386
|
+
for (const issue of result.issues) {
|
|
387
|
+
const emoji = getSeverityEmoji(issue.severity);
|
|
388
|
+
const label = getIssueTypeLabel(issue.type);
|
|
389
|
+
console.log(` ${emoji} [${label}] ${issue.description}`);
|
|
390
|
+
console.log(` ${EMOJI.FIX} ${issue.suggestion}`);
|
|
391
|
+
if (issue.canAutoFix) {
|
|
392
|
+
console.log(` ${EMOJI.INFO} Can be auto-fixed with --fix`);
|
|
393
|
+
}
|
|
394
|
+
console.log('');
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Print diagnosis result to console
|
|
399
|
+
*/
|
|
400
|
+
function printResult(result, quiet) {
|
|
401
|
+
if (result.healthy) {
|
|
402
|
+
console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} State is healthy - no issues found`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (!quiet) {
|
|
406
|
+
printIssues(result);
|
|
407
|
+
}
|
|
408
|
+
printSummary(result);
|
|
409
|
+
printFixedSection(result);
|
|
410
|
+
printDryRunSection(result);
|
|
411
|
+
if (result.issues.length > 0 && result.fixed.length === 0 && !result.dryRun) {
|
|
412
|
+
console.log(`\n ${EMOJI.INFO} Run with --fix to auto-repair fixable issues`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Determine audit log status from result
|
|
417
|
+
*/
|
|
418
|
+
function getAuditStatus(error, result) {
|
|
419
|
+
if (error)
|
|
420
|
+
return 'failed';
|
|
421
|
+
if (result?.healthy)
|
|
422
|
+
return 'success';
|
|
423
|
+
if (result?.fixErrors.length === 0 && result?.fixed.length > 0)
|
|
424
|
+
return 'success';
|
|
425
|
+
return 'partial';
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Build audit log output entry
|
|
429
|
+
*/
|
|
430
|
+
function buildAuditOutput(result) {
|
|
431
|
+
if (!result)
|
|
432
|
+
return null;
|
|
433
|
+
return {
|
|
434
|
+
healthy: result.healthy,
|
|
435
|
+
totalIssues: result.summary.totalIssues,
|
|
436
|
+
fixedCount: result.fixed.length,
|
|
437
|
+
fixErrorCount: result.fixErrors.length,
|
|
438
|
+
dryRun: result.dryRun,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Main CLI entry point
|
|
443
|
+
*/
|
|
444
|
+
async function main() {
|
|
445
|
+
const args = parseArguments();
|
|
446
|
+
const baseDir = args.baseDir || process.cwd();
|
|
447
|
+
const startedAt = new Date().toISOString();
|
|
448
|
+
const startTime = Date.now();
|
|
449
|
+
let result = null;
|
|
450
|
+
let error = null;
|
|
451
|
+
try {
|
|
452
|
+
// Create base deps for read operations
|
|
453
|
+
const baseDeps = await createDeps(baseDir);
|
|
454
|
+
// WU-1230: When --fix is enabled, use micro-worktree isolation for all
|
|
455
|
+
// tracked file modifications. This prevents direct modifications to main
|
|
456
|
+
// and ensures changes are pushed via merge.
|
|
457
|
+
const deps = args.fix && !args.dryRun
|
|
458
|
+
? {
|
|
459
|
+
...baseDeps,
|
|
460
|
+
...createStateDoctorFixDeps(baseDir),
|
|
461
|
+
}
|
|
462
|
+
: baseDeps;
|
|
463
|
+
result = await diagnoseState(baseDir, deps, {
|
|
464
|
+
fix: args.fix,
|
|
465
|
+
dryRun: args.dryRun,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
error = err.message;
|
|
470
|
+
}
|
|
471
|
+
const durationMs = Date.now() - startTime;
|
|
472
|
+
const auditStatus = getAuditStatus(error, result);
|
|
473
|
+
await writeAuditLog(baseDir, {
|
|
474
|
+
tool: TOOL_NAME,
|
|
475
|
+
status: auditStatus,
|
|
476
|
+
startedAt,
|
|
477
|
+
completedAt: new Date().toISOString(),
|
|
478
|
+
durationMs,
|
|
479
|
+
input: { baseDir, fix: args.fix, dryRun: args.dryRun },
|
|
480
|
+
output: buildAuditOutput(result),
|
|
481
|
+
error: error ? { message: error } : null,
|
|
482
|
+
});
|
|
483
|
+
if (error) {
|
|
484
|
+
console.error(`${LOG_PREFIX} Error: ${error}`);
|
|
485
|
+
process.exit(EXIT_CODES.ERROR);
|
|
486
|
+
}
|
|
487
|
+
if (args.json) {
|
|
488
|
+
console.log(JSON.stringify(result, null, 2));
|
|
489
|
+
process.exit(result?.healthy ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
|
|
490
|
+
}
|
|
491
|
+
if (result) {
|
|
492
|
+
printResult(result, args.quiet ?? false);
|
|
493
|
+
const shouldError = !result.healthy && result.fixed.length === 0;
|
|
494
|
+
if (shouldError)
|
|
495
|
+
process.exit(EXIT_CODES.ERROR);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
main().catch((e) => {
|
|
499
|
+
console.error(`${LOG_PREFIX} ${e.message}`);
|
|
500
|
+
process.exit(EXIT_CODES.ERROR);
|
|
501
|
+
});
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
22
21
|
import { FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
23
22
|
const LOG_PREFIX = '[validate-agent-skills]';
|
|
24
23
|
/**
|
|
@@ -209,8 +208,10 @@ Examples:
|
|
|
209
208
|
}
|
|
210
209
|
}
|
|
211
210
|
}
|
|
212
|
-
//
|
|
213
|
-
|
|
211
|
+
// WU-1181: Use import.meta.main instead of process.argv[1] comparison
|
|
212
|
+
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
213
|
+
// path but import.meta.url resolves to the real path - they never match
|
|
214
|
+
if (import.meta.main) {
|
|
214
215
|
main().catch((error) => {
|
|
215
216
|
console.error(`${LOG_PREFIX} Unexpected error:`, error);
|
|
216
217
|
process.exit(1);
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
|
-
import { fileURLToPath } from 'node:url';
|
|
21
20
|
import { FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
22
21
|
const LOG_PREFIX = '[validate-agent-sync]';
|
|
23
22
|
/**
|
|
@@ -139,8 +138,10 @@ Examples:
|
|
|
139
138
|
process.exit(1);
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
|
-
//
|
|
143
|
-
|
|
141
|
+
// WU-1181: Use import.meta.main instead of process.argv[1] comparison
|
|
142
|
+
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
143
|
+
// path but import.meta.url resolves to the real path - they never match
|
|
144
|
+
if (import.meta.main) {
|
|
144
145
|
main().catch((error) => {
|
|
145
146
|
console.error(`${LOG_PREFIX} Unexpected error:`, error);
|
|
146
147
|
process.exit(1);
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
*
|
|
16
16
|
* @see {@link docs/04-operations/tasks/backlog.md} - Backlog file
|
|
17
17
|
*/
|
|
18
|
-
import { fileURLToPath } from 'node:url';
|
|
19
18
|
import { validateBacklogSync, } from '@lumenflow/core/dist/validators/backlog-sync.js';
|
|
20
19
|
import { EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
21
20
|
const LOG_PREFIX = '[validate-backlog-sync]';
|
|
@@ -65,8 +64,10 @@ Examples:
|
|
|
65
64
|
process.exit(1);
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
|
-
//
|
|
69
|
-
|
|
67
|
+
// WU-1181: Use import.meta.main instead of process.argv[1] comparison
|
|
68
|
+
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
69
|
+
// path but import.meta.url resolves to the real path - they never match
|
|
70
|
+
if (import.meta.main) {
|
|
70
71
|
main().catch((error) => {
|
|
71
72
|
console.error(`${LOG_PREFIX} Unexpected error:`, error);
|
|
72
73
|
process.exit(1);
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
22
21
|
import { FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
23
22
|
const LOG_PREFIX = '[validate-skills-spec]';
|
|
24
23
|
/**
|
|
@@ -197,8 +196,10 @@ Examples:
|
|
|
197
196
|
}
|
|
198
197
|
}
|
|
199
198
|
}
|
|
200
|
-
//
|
|
201
|
-
|
|
199
|
+
// WU-1181: Use import.meta.main instead of process.argv[1] comparison
|
|
200
|
+
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
201
|
+
// path but import.meta.url resolves to the real path - they never match
|
|
202
|
+
if (import.meta.main) {
|
|
202
203
|
main().catch((error) => {
|
|
203
204
|
console.error(`${LOG_PREFIX} Unexpected error:`, error);
|
|
204
205
|
process.exit(1);
|
package/dist/validate.js
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
* @see {@link wu-validate.ts} - Detailed WU validation with schema
|
|
20
20
|
* @see {@link docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md} - WU lifecycle
|
|
21
21
|
*/
|
|
22
|
-
import { fileURLToPath } from 'node:url';
|
|
23
22
|
import { validateSingleWU, validateAllWUs, } from '@lumenflow/core/dist/validators/wu-tasks.js';
|
|
24
23
|
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
25
24
|
import { EMOJI, PATTERNS } from '@lumenflow/core/dist/wu-constants.js';
|
|
@@ -120,8 +119,10 @@ Examples:
|
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
|
-
//
|
|
124
|
-
|
|
122
|
+
// WU-1181: Use import.meta.main instead of process.argv[1] comparison
|
|
123
|
+
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
124
|
+
// path but import.meta.url resolves to the real path - they never match
|
|
125
|
+
if (import.meta.main) {
|
|
125
126
|
main().catch((error) => {
|
|
126
127
|
console.error(`${LOG_PREFIX} Unexpected error:`, error);
|
|
127
128
|
process.exit(1);
|
package/dist/wu-block.js
CHANGED
|
@@ -32,9 +32,9 @@ import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
|
32
32
|
import { WUStateStore } from '@lumenflow/core/dist/wu-state-store.js';
|
|
33
33
|
// WU-1603: Atomic lane locking - release lock when WU is blocked
|
|
34
34
|
import { releaseLaneLock } from '@lumenflow/core/dist/lane-lock.js';
|
|
35
|
-
// ensureOnMain() moved to wu-helpers.
|
|
36
|
-
// ensureStaged() moved to git-staged-validator.
|
|
37
|
-
// defaultWorktreeFrom() moved to wu-paths.
|
|
35
|
+
// ensureOnMain() moved to wu-helpers.ts (WU-1256)
|
|
36
|
+
// ensureStaged() moved to git-staged-validator.ts (WU-1341)
|
|
37
|
+
// defaultWorktreeFrom() moved to wu-paths.ts (WU-1341)
|
|
38
38
|
/**
|
|
39
39
|
* Remove WU entry from in-progress section of lines array
|
|
40
40
|
*/
|