@lumenflow/cli 2.8.0 → 2.10.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 +4 -3
- package/dist/__tests__/commands.test.js +75 -0
- package/dist/__tests__/doctor.test.js +510 -0
- package/dist/__tests__/init.test.js +222 -0
- package/dist/commands.js +171 -0
- package/dist/doctor.js +479 -8
- package/dist/hooks/enforcement-generator.js +256 -5
- package/dist/hooks/enforcement-sync.js +52 -6
- package/dist/init.js +299 -8
- package/dist/mem-recover.js +221 -0
- package/package.json +7 -6
- package/templates/core/LUMENFLOW.md.template +24 -0
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +47 -0
- package/templates/core/ai/onboarding/lumenflow-force-usage.md.template +183 -0
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +68 -55
- package/templates/core/ai/onboarding/release-process.md.template +58 -4
- package/templates/core/ai/onboarding/starting-prompt.md.template +67 -3
- package/templates/vendors/claude/.claude/hooks/pre-compact-checkpoint.sh +102 -0
- package/templates/vendors/claude/.claude/hooks/session-start-recovery.sh +74 -0
- package/templates/vendors/claude/.claude/settings.json.template +42 -0
- package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +23 -6
package/dist/init.js
CHANGED
|
@@ -12,13 +12,15 @@ import * as path from 'node:path';
|
|
|
12
12
|
import * as yaml from 'yaml';
|
|
13
13
|
import { execFileSync } from 'node:child_process';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
-
import { getDefaultConfig, createWUParser, WU_OPTIONS } from '@lumenflow/core';
|
|
15
|
+
import { getDefaultConfig, createWUParser, WU_OPTIONS, CLAUDE_HOOKS } from '@lumenflow/core';
|
|
16
16
|
// WU-1067: Import GATE_PRESETS for --preset support
|
|
17
17
|
import { GATE_PRESETS } from '@lumenflow/core/dist/gates-config.js';
|
|
18
18
|
// WU-1171: Import merge block utilities
|
|
19
19
|
import { updateMergeBlock } from './merge-block.js';
|
|
20
20
|
// WU-1362: Import worktree guard utilities for branch checking
|
|
21
21
|
import { isMainBranch, isInWorktree } from '@lumenflow/core/dist/core/worktree-guard.js';
|
|
22
|
+
// WU-1386: Import doctor for auto-run after init
|
|
23
|
+
import { runDoctorForInit } from './doctor.js';
|
|
22
24
|
/**
|
|
23
25
|
* WU-1085: CLI option definitions for init command
|
|
24
26
|
* WU-1171: Added --merge and --client options
|
|
@@ -70,9 +72,12 @@ const INIT_OPTIONS = {
|
|
|
70
72
|
* Provides proper --help, --version, and option parsing
|
|
71
73
|
*/
|
|
72
74
|
export function parseInitOptions() {
|
|
75
|
+
// WU-1378: Description includes subcommand hint
|
|
73
76
|
const opts = createWUParser({
|
|
74
77
|
name: 'lumenflow-init',
|
|
75
|
-
description: 'Initialize LumenFlow in a project'
|
|
78
|
+
description: 'Initialize LumenFlow in a project\n\n' +
|
|
79
|
+
'Subcommands:\n' +
|
|
80
|
+
' lumenflow commands List all available CLI commands',
|
|
76
81
|
options: Object.values(INIT_OPTIONS),
|
|
77
82
|
});
|
|
78
83
|
// WU-1171: --client takes precedence, --vendor is alias
|
|
@@ -321,9 +326,24 @@ const DEFAULT_LANE_DEFINITIONS = [
|
|
|
321
326
|
* WU-1067: Supports --preset option for config-driven gates
|
|
322
327
|
* WU-1307: Includes default lane definitions for onboarding
|
|
323
328
|
* WU-1364: Supports git config overrides (requireRemote)
|
|
329
|
+
* WU-1383: Adds enforcement hooks config for Claude client by default
|
|
324
330
|
*/
|
|
325
|
-
function generateLumenflowConfigYaml(gatePreset, gitConfigOverride) {
|
|
326
|
-
|
|
331
|
+
function generateLumenflowConfigYaml(gatePreset, gitConfigOverride, client) {
|
|
332
|
+
// WU-1382: Add managed file header to prevent manual edits
|
|
333
|
+
const header = `# ============================================================================
|
|
334
|
+
# LUMENFLOW MANAGED FILE - DO NOT EDIT MANUALLY
|
|
335
|
+
# ============================================================================
|
|
336
|
+
# Generated by: lumenflow init
|
|
337
|
+
# Regenerate with: pnpm exec lumenflow init --force
|
|
338
|
+
#
|
|
339
|
+
# This file is managed by LumenFlow tooling. Manual edits may be overwritten.
|
|
340
|
+
# To customize, use the CLI commands or edit the appropriate source templates.
|
|
341
|
+
# ============================================================================
|
|
342
|
+
|
|
343
|
+
# LumenFlow Configuration
|
|
344
|
+
# Customize paths based on your project structure
|
|
345
|
+
|
|
346
|
+
`;
|
|
327
347
|
const config = getDefaultConfig();
|
|
328
348
|
config.directories.agentsDir = LUMENFLOW_AGENTS_DIR;
|
|
329
349
|
// WU-1067: Add gates.execution section with preset if specified
|
|
@@ -344,6 +364,22 @@ function generateLumenflowConfigYaml(gatePreset, gitConfigOverride) {
|
|
|
344
364
|
requireRemote: gitConfigOverride.requireRemote,
|
|
345
365
|
};
|
|
346
366
|
}
|
|
367
|
+
// WU-1383: Add enforcement hooks for Claude client by default
|
|
368
|
+
// This prevents agents from working on main and editing config files manually
|
|
369
|
+
if (client === 'claude') {
|
|
370
|
+
config.agents = {
|
|
371
|
+
clients: {
|
|
372
|
+
'claude-code': {
|
|
373
|
+
enforcement: {
|
|
374
|
+
hooks: true,
|
|
375
|
+
block_outside_worktree: true,
|
|
376
|
+
require_wu_for_edits: true,
|
|
377
|
+
warn_on_stop_without_wu_done: true,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
347
383
|
return header + yaml.stringify(config);
|
|
348
384
|
}
|
|
349
385
|
/**
|
|
@@ -453,7 +489,78 @@ const LUMENFLOW_MD_TEMPLATE = `# LumenFlow Workflow Guide\n\n**Last updated:** {
|
|
|
453
489
|
const CONSTRAINTS_MD_TEMPLATE = `# LumenFlow Constraints Capsule\n\n**Version:** 1.0\n**Last updated:** {{DATE}}\n\n## The 6 Non-Negotiable Constraints\n\n### 1. Worktree Discipline and Git Safety\nWork only in worktrees, treat main as read-only, never run destructive git commands on main.\n\n### 2. WUs Are Specs, Not Code\nRespect code_paths boundaries, no feature creep, no code blocks in WU YAML files.\n\n### 3. Docs-Only vs Code WUs\nDocumentation WUs use \`--docs-only\` gates, code WUs run full gates.\n\n### 4. LLM-First, Zero-Fallback Inference\nUse LLMs for semantic tasks, fall back to safe defaults (never regex/keywords).\n\n### 5. Gates and Skip-Gates\nComplete via \`pnpm wu:done\`; skip-gates only for pre-existing failures with \`--reason\` and \`--fix-wu\`.\n\n### 6. Safety and Governance\nRespect privacy rules, approved sources, security policies; when uncertain, choose safer path.\n\n---\n\n## Mini Audit Checklist\n\nBefore running \`wu:done\`, verify:\n\n- [ ] Working in worktree (not main)\n- [ ] Only modified files in \`code_paths\`\n- [ ] Gates pass\n- [ ] No forbidden git commands used\n- [ ] Acceptance criteria satisfied\n\n---\n\n## Escalation Triggers\n\nStop and ask a human when:\n- Same error repeats 3 times\n- Auth or permissions changes required\n- PII/PHI/safety issues discovered\n- Cloud spend or secrets involved\n`;
|
|
454
490
|
// Template for root CLAUDE.md
|
|
455
491
|
// WU-1309: Use <project-root> placeholder for portability
|
|
456
|
-
|
|
492
|
+
// WU-1382: Expanded with CLI commands table and warning about manual YAML editing
|
|
493
|
+
const CLAUDE_MD_TEMPLATE = `# Claude Code Instructions
|
|
494
|
+
|
|
495
|
+
**Last updated:** {{DATE}}
|
|
496
|
+
|
|
497
|
+
This project uses LumenFlow workflow. For workflow documentation, see [LUMENFLOW.md](LUMENFLOW.md).
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## Quick Start
|
|
502
|
+
|
|
503
|
+
\`\`\`bash
|
|
504
|
+
# 1. Claim a WU
|
|
505
|
+
pnpm wu:claim --id WU-XXXX --lane <Lane>
|
|
506
|
+
cd worktrees/<lane>-wu-xxxx
|
|
507
|
+
|
|
508
|
+
# 2. Work in worktree, run gates
|
|
509
|
+
pnpm gates
|
|
510
|
+
|
|
511
|
+
# 3. Complete (ALWAYS run this!)
|
|
512
|
+
cd <project-root>
|
|
513
|
+
pnpm wu:done --id WU-XXXX
|
|
514
|
+
\`\`\`
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## CLI Commands Reference
|
|
519
|
+
|
|
520
|
+
### WU Lifecycle
|
|
521
|
+
|
|
522
|
+
| Command | Description |
|
|
523
|
+
| ----------------------------------------- | ---------------------------------------- |
|
|
524
|
+
| \`pnpm wu:status --id WU-XXX\` | Show WU status, location, valid commands |
|
|
525
|
+
| \`pnpm wu:claim --id WU-XXX --lane <Lane>\` | Claim WU and create worktree |
|
|
526
|
+
| \`pnpm wu:prep --id WU-XXX\` | Run gates in worktree, prep for wu:done |
|
|
527
|
+
| \`pnpm wu:done --id WU-XXX\` | Complete WU (from main checkout) |
|
|
528
|
+
| \`pnpm wu:block --id WU-XXX --reason "..."\`| Block WU with reason |
|
|
529
|
+
| \`pnpm wu:unblock --id WU-XXX\` | Unblock WU |
|
|
530
|
+
|
|
531
|
+
### Gates & Quality
|
|
532
|
+
|
|
533
|
+
| Command | Description |
|
|
534
|
+
| ------------------------ | -------------------------- |
|
|
535
|
+
| \`pnpm gates\` | Run all quality gates |
|
|
536
|
+
| \`pnpm gates --docs-only\` | Run gates for docs changes |
|
|
537
|
+
| \`pnpm format\` | Format all files |
|
|
538
|
+
| \`pnpm lint\` | Run linter |
|
|
539
|
+
| \`pnpm typecheck\` | Run TypeScript check |
|
|
540
|
+
| \`pnpm test\` | Run tests |
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## Critical: Always wu:done
|
|
545
|
+
|
|
546
|
+
After completing work, ALWAYS run \`pnpm wu:done --id WU-XXXX\` from the main checkout.
|
|
547
|
+
|
|
548
|
+
See [LUMENFLOW.md](LUMENFLOW.md) for full workflow documentation.
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Warning: Do Not Edit WU YAML Files Manually
|
|
553
|
+
|
|
554
|
+
**Never manually edit WU YAML files** in \`docs/.../tasks/wu/WU-XXX.yaml\`.
|
|
555
|
+
|
|
556
|
+
Use CLI commands instead:
|
|
557
|
+
|
|
558
|
+
- \`pnpm wu:create ...\` to create new WUs
|
|
559
|
+
- \`pnpm wu:edit --id WU-XXX ...\` to modify WU fields
|
|
560
|
+
- \`pnpm wu:claim\` / \`wu:block\` / \`wu:done\` for status changes
|
|
561
|
+
|
|
562
|
+
Manual edits bypass validation and can corrupt workflow state.
|
|
563
|
+
`;
|
|
457
564
|
// Template for .claude/settings.json
|
|
458
565
|
const CLAUDE_SETTINGS_TEMPLATE = `{
|
|
459
566
|
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
@@ -1095,9 +1202,19 @@ Choose the safer path:
|
|
|
1095
1202
|
`;
|
|
1096
1203
|
// WU-1307: Lane inference configuration template (hierarchical Parent→Sublane format)
|
|
1097
1204
|
// WU-1364: Added Core and Feature as parent lanes for intuitive naming
|
|
1205
|
+
// WU-1382: Added managed file header to prevent manual edits
|
|
1098
1206
|
// This format is required by lane-inference.ts and lane-checker.ts
|
|
1099
|
-
const LANE_INFERENCE_TEMPLATE = `#
|
|
1207
|
+
const LANE_INFERENCE_TEMPLATE = `# ============================================================================
|
|
1208
|
+
# LUMENFLOW MANAGED FILE - DO NOT EDIT MANUALLY
|
|
1209
|
+
# ============================================================================
|
|
1100
1210
|
# Generated by: lumenflow init
|
|
1211
|
+
# Regenerate with: pnpm exec lumenflow init --force
|
|
1212
|
+
#
|
|
1213
|
+
# This file is managed by LumenFlow tooling. Manual edits may be overwritten.
|
|
1214
|
+
# To customize lanes, use: pnpm lane:suggest --output .lumenflow.lane-inference.yaml
|
|
1215
|
+
# ============================================================================
|
|
1216
|
+
|
|
1217
|
+
# Lane Inference Configuration
|
|
1101
1218
|
#
|
|
1102
1219
|
# Hierarchical format: Parent -> Sublane -> { code_paths, keywords }
|
|
1103
1220
|
# This format is required by lane-inference.ts for proper sub-lane suggestion.
|
|
@@ -1363,6 +1480,7 @@ LumenFlow uses Work Units (WUs) to track all changes:
|
|
|
1363
1480
|
- [quick-ref-commands.md](quick-ref-commands.md) - Complete command reference
|
|
1364
1481
|
- [agent-safety-card.md](agent-safety-card.md) - Safety guidelines
|
|
1365
1482
|
- [wu-create-checklist.md](wu-create-checklist.md) - WU creation guide
|
|
1483
|
+
- [wu-sizing-guide.md](wu-sizing-guide.md) - WU complexity and context management
|
|
1366
1484
|
`;
|
|
1367
1485
|
const WU_CREATE_CHECKLIST_TEMPLATE = `# WU Creation Checklist
|
|
1368
1486
|
|
|
@@ -1751,6 +1869,96 @@ This detects:
|
|
|
1751
1869
|
- Overlapping code paths between lanes
|
|
1752
1870
|
- Code files not covered by any lane
|
|
1753
1871
|
`;
|
|
1872
|
+
// WU-1385: WU sizing guide template for agent onboarding
|
|
1873
|
+
const WU_SIZING_GUIDE_TEMPLATE = `# Work Unit Sizing & Strategy Guide
|
|
1874
|
+
|
|
1875
|
+
**Last updated:** {{DATE}}
|
|
1876
|
+
|
|
1877
|
+
**Purpose:** Decision framework for agents to determine execution strategy based on task complexity.
|
|
1878
|
+
|
|
1879
|
+
**Status:** Active — Thresholds are **mandatory limits**, not guidelines.
|
|
1880
|
+
|
|
1881
|
+
---
|
|
1882
|
+
|
|
1883
|
+
## Complexity Assessment Matrix
|
|
1884
|
+
|
|
1885
|
+
Before claiming a WU, estimate its "weight" using these heuristics.
|
|
1886
|
+
|
|
1887
|
+
| Complexity | Files | Tool Calls | Context Budget | Strategy |
|
|
1888
|
+
| :------------ | :---- | :--------- | :------------- | :------------------------------------------- |
|
|
1889
|
+
| **Simple** | <20 | <50 | <30% | **Single Session** (Tier 2 Context) |
|
|
1890
|
+
| **Medium** | 20-50 | 50-100 | 30-50% | **Checkpoint-Resume** (Standard Handoff) |
|
|
1891
|
+
| **Complex** | 50+ | 100+ | >50% | **Orchestrator-Worker** OR **Decomposition** |
|
|
1892
|
+
| **Oversized** | 100+ | 200+ | — | **MUST Split** (See Patterns below) |
|
|
1893
|
+
|
|
1894
|
+
**These thresholds are mandatory.** Exceeding them leads to context exhaustion and rule loss. Agents operate in context windows and tool calls, not clock time.
|
|
1895
|
+
|
|
1896
|
+
---
|
|
1897
|
+
|
|
1898
|
+
## Context Safety Triggers
|
|
1899
|
+
|
|
1900
|
+
If you hit ANY of these triggers during a session, you MUST checkpoint and spawn fresh:
|
|
1901
|
+
|
|
1902
|
+
- **Token Limit:** Context usage hits **50% (Warning)** or **80% (Critical)**.
|
|
1903
|
+
- **Tool Volume:** **50+ tool calls** in current session.
|
|
1904
|
+
- **File Volume:** **20+ files** modified in \`git status\`.
|
|
1905
|
+
- **Session Staleness:** Repeated redundant queries or forgotten context.
|
|
1906
|
+
|
|
1907
|
+
---
|
|
1908
|
+
|
|
1909
|
+
## Spawn Fresh, Don't Continue
|
|
1910
|
+
|
|
1911
|
+
**When approaching context limits, spawn a fresh agent instead of continuing after compaction.**
|
|
1912
|
+
|
|
1913
|
+
Context compaction causes agents to lose critical rules. The disciplined approach:
|
|
1914
|
+
|
|
1915
|
+
1. Checkpoint your progress: \`pnpm mem:checkpoint --wu WU-XXX\`
|
|
1916
|
+
2. Commit and push work
|
|
1917
|
+
3. Generate fresh agent prompt: \`pnpm wu:spawn --id WU-XXX\`
|
|
1918
|
+
4. EXIT current session (do NOT continue after compaction)
|
|
1919
|
+
|
|
1920
|
+
---
|
|
1921
|
+
|
|
1922
|
+
## Splitting Patterns
|
|
1923
|
+
|
|
1924
|
+
When a WU is Oversized or Complex, split it using approved patterns:
|
|
1925
|
+
|
|
1926
|
+
- **Tracer Bullet**: WU-1 proves skeleton works, WU-2 implements real logic
|
|
1927
|
+
- **Layer Split**: WU-1 for ports/application, WU-2 for infrastructure
|
|
1928
|
+
- **UI/Logic Split**: WU-1 for backend, WU-2 for frontend
|
|
1929
|
+
- **Feature Flag**: WU-1 behind flag, WU-2 removes flag
|
|
1930
|
+
|
|
1931
|
+
---
|
|
1932
|
+
|
|
1933
|
+
## Quick Reference
|
|
1934
|
+
|
|
1935
|
+
| Scenario | Strategy | Action |
|
|
1936
|
+
| :---------------------------------- | :------------------ | :------------------------------------------- |
|
|
1937
|
+
| Bug fix, single file, <20 calls | Simple | Claim, fix, commit, \`wu:done\` |
|
|
1938
|
+
| Feature 50-100 calls, clear phases | Checkpoint-Resume | Phase 1 → checkpoint → Phase 2 → done |
|
|
1939
|
+
| Multi-domain, must land atomically | Orchestrator-Worker | Main agent coordinates, spawns sub-agents |
|
|
1940
|
+
| Large refactor 100+ calls | Feature Flag Split | WU-A: New behind flag → WU-B: Remove flag |
|
|
1941
|
+
|
|
1942
|
+
---
|
|
1943
|
+
|
|
1944
|
+
## Documentation-Only Exception
|
|
1945
|
+
|
|
1946
|
+
Documentation WUs (\`type: documentation\`) have relaxed file count thresholds:
|
|
1947
|
+
|
|
1948
|
+
| Complexity | Files (docs) | Tool Calls | Strategy |
|
|
1949
|
+
| :--------- | :----------- | :--------- | :---------------- |
|
|
1950
|
+
| **Simple** | <40 | <50 | Single Session |
|
|
1951
|
+
| **Medium** | 40-80 | 50-100 | Checkpoint-Resume |
|
|
1952
|
+
|
|
1953
|
+
**Applies when ALL true:**
|
|
1954
|
+
- WU \`type: documentation\`
|
|
1955
|
+
- Only modifies: \`docs/**\`, \`*.md\`
|
|
1956
|
+
- Does NOT touch code paths
|
|
1957
|
+
|
|
1958
|
+
---
|
|
1959
|
+
|
|
1960
|
+
For complete sizing guidance, see the canonical [wu-sizing-guide.md](https://lumenflow.dev/reference/wu-sizing-guide/) documentation.
|
|
1961
|
+
`;
|
|
1754
1962
|
// WU-1083: Claude skills templates
|
|
1755
1963
|
const WU_LIFECYCLE_SKILL_TEMPLATE = `---
|
|
1756
1964
|
name: wu-lifecycle
|
|
@@ -2127,8 +2335,17 @@ export async function scaffoldProject(targetDir, options) {
|
|
|
2127
2335
|
};
|
|
2128
2336
|
// Create .lumenflow.config.yaml (WU-1067: includes gate preset if specified)
|
|
2129
2337
|
// WU-1364: Includes git config overrides (e.g., requireRemote: false for local-only)
|
|
2338
|
+
// WU-1383: Includes enforcement hooks for Claude client
|
|
2130
2339
|
// Note: Config files don't use merge mode (always skip or force)
|
|
2131
|
-
|
|
2340
|
+
const configPath = path.join(targetDir, CONFIG_FILE_NAME);
|
|
2341
|
+
// WU-1383: Warn if config already exists to discourage manual editing
|
|
2342
|
+
if (fs.existsSync(configPath) && !options.force) {
|
|
2343
|
+
result.warnings = result.warnings ?? [];
|
|
2344
|
+
result.warnings.push(`${CONFIG_FILE_NAME} already exists. ` +
|
|
2345
|
+
'To modify configuration, use CLI commands (e.g., pnpm lumenflow:init --force) ' +
|
|
2346
|
+
'instead of manual editing.');
|
|
2347
|
+
}
|
|
2348
|
+
await createFile(configPath, generateLumenflowConfigYaml(options.gatePreset, gitConfigOverride, client), options.force ? 'force' : 'skip', result, targetDir);
|
|
2132
2349
|
// WU-1171: Create AGENTS.md (universal entry point for all agents)
|
|
2133
2350
|
try {
|
|
2134
2351
|
const agentsTemplate = loadTemplate('core/AGENTS.md.template');
|
|
@@ -2382,6 +2599,8 @@ async function scaffoldAgentOnboardingDocs(targetDir, options, result, tokens) {
|
|
|
2382
2599
|
await createFile(path.join(onboardingDir, 'troubleshooting-wu-done.md'), processTemplate(TROUBLESHOOTING_WU_DONE_TEMPLATE, tokens), options.force, result, targetDir);
|
|
2383
2600
|
await createFile(path.join(onboardingDir, 'agent-safety-card.md'), processTemplate(AGENT_SAFETY_CARD_TEMPLATE, tokens), options.force, result, targetDir);
|
|
2384
2601
|
await createFile(path.join(onboardingDir, 'wu-create-checklist.md'), processTemplate(WU_CREATE_CHECKLIST_TEMPLATE, tokens), options.force, result, targetDir);
|
|
2602
|
+
// WU-1385: Add wu-sizing-guide.md to onboarding docs
|
|
2603
|
+
await createFile(path.join(onboardingDir, 'wu-sizing-guide.md'), processTemplate(WU_SIZING_GUIDE_TEMPLATE, tokens), options.force, result, targetDir);
|
|
2385
2604
|
}
|
|
2386
2605
|
/**
|
|
2387
2606
|
* WU-1083: Scaffold Claude skills
|
|
@@ -2429,7 +2648,34 @@ async function scaffoldClientFiles(targetDir, options, result, tokens, client) {
|
|
|
2429
2648
|
await createFile(path.join(targetDir, 'CLAUDE.md'), processTemplate(CLAUDE_MD_TEMPLATE, tokens), fileMode, result, targetDir);
|
|
2430
2649
|
await createDirectory(path.join(targetDir, CLAUDE_AGENTS_DIR), result, targetDir);
|
|
2431
2650
|
await createFile(path.join(targetDir, CLAUDE_AGENTS_DIR, '.gitkeep'), '', options.force ? 'force' : 'skip', result, targetDir);
|
|
2432
|
-
|
|
2651
|
+
// WU-1394: Load settings.json from template (includes PreCompact/SessionStart hooks)
|
|
2652
|
+
let settingsContent;
|
|
2653
|
+
try {
|
|
2654
|
+
settingsContent = loadTemplate(CLAUDE_HOOKS.TEMPLATES.SETTINGS);
|
|
2655
|
+
}
|
|
2656
|
+
catch {
|
|
2657
|
+
settingsContent = CLAUDE_SETTINGS_TEMPLATE;
|
|
2658
|
+
}
|
|
2659
|
+
await createFile(path.join(targetDir, CLAUDE_DIR, 'settings.json'), settingsContent, options.force ? 'force' : 'skip', result, targetDir);
|
|
2660
|
+
// WU-1394: Scaffold recovery hook scripts with executable permissions
|
|
2661
|
+
const hooksDir = path.join(targetDir, CLAUDE_DIR, 'hooks');
|
|
2662
|
+
await createDirectory(hooksDir, result, targetDir);
|
|
2663
|
+
// Load and write pre-compact-checkpoint.sh
|
|
2664
|
+
try {
|
|
2665
|
+
const preCompactScript = loadTemplate(CLAUDE_HOOKS.TEMPLATES.PRE_COMPACT);
|
|
2666
|
+
await createExecutableScript(path.join(hooksDir, CLAUDE_HOOKS.SCRIPTS.PRE_COMPACT_CHECKPOINT), preCompactScript, options.force ? 'force' : 'skip', result, targetDir);
|
|
2667
|
+
}
|
|
2668
|
+
catch {
|
|
2669
|
+
// Template not found - hook won't be scaffolded
|
|
2670
|
+
}
|
|
2671
|
+
// Load and write session-start-recovery.sh
|
|
2672
|
+
try {
|
|
2673
|
+
const sessionStartScript = loadTemplate(CLAUDE_HOOKS.TEMPLATES.SESSION_START);
|
|
2674
|
+
await createExecutableScript(path.join(hooksDir, CLAUDE_HOOKS.SCRIPTS.SESSION_START_RECOVERY), sessionStartScript, options.force ? 'force' : 'skip', result, targetDir);
|
|
2675
|
+
}
|
|
2676
|
+
catch {
|
|
2677
|
+
// Template not found - hook won't be scaffolded
|
|
2678
|
+
}
|
|
2433
2679
|
// WU-1083: Scaffold Claude skills
|
|
2434
2680
|
await scaffoldClaudeSkills(targetDir, options, result, tokens);
|
|
2435
2681
|
// WU-1083: Scaffold agent onboarding docs for Claude vendor (even without --full)
|
|
@@ -2555,12 +2801,45 @@ function writeNewFile(filePath, content, result, relativePath) {
|
|
|
2555
2801
|
fs.writeFileSync(filePath, content);
|
|
2556
2802
|
result.created.push(relativePath);
|
|
2557
2803
|
}
|
|
2804
|
+
/**
|
|
2805
|
+
* WU-1394: Create an executable script file with proper permissions
|
|
2806
|
+
* Similar to createFile but sets 0o755 mode for shell scripts
|
|
2807
|
+
*/
|
|
2808
|
+
async function createExecutableScript(filePath, content, mode, result, targetDir) {
|
|
2809
|
+
const relativePath = getRelativePath(targetDir, filePath);
|
|
2810
|
+
const resolvedMode = resolveBooleanToFileMode(mode);
|
|
2811
|
+
result.merged = result.merged ?? [];
|
|
2812
|
+
result.warnings = result.warnings ?? [];
|
|
2813
|
+
const fileExists = fs.existsSync(filePath);
|
|
2814
|
+
if (fileExists && resolvedMode === 'skip') {
|
|
2815
|
+
result.skipped.push(relativePath);
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
// Write file with executable permissions
|
|
2819
|
+
const parentDir = path.dirname(filePath);
|
|
2820
|
+
if (!fs.existsSync(parentDir)) {
|
|
2821
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
2822
|
+
}
|
|
2823
|
+
fs.writeFileSync(filePath, content, { mode: 0o755 });
|
|
2824
|
+
result.created.push(relativePath);
|
|
2825
|
+
}
|
|
2558
2826
|
/**
|
|
2559
2827
|
* CLI entry point
|
|
2560
2828
|
* WU-1085: Updated to use parseInitOptions for proper --help support
|
|
2561
2829
|
* WU-1171: Added --merge and --client support
|
|
2830
|
+
* WU-1378: Added subcommand routing for 'commands' subcommand
|
|
2562
2831
|
*/
|
|
2563
2832
|
export async function main() {
|
|
2833
|
+
// WU-1378: Check for subcommands before parsing init options
|
|
2834
|
+
const subcommand = process.argv[2];
|
|
2835
|
+
if (subcommand === 'commands') {
|
|
2836
|
+
// Route to commands subcommand
|
|
2837
|
+
const { main: commandsMain } = await import('./commands.js');
|
|
2838
|
+
// Remove 'commands' from argv so the subcommand parser sees clean args
|
|
2839
|
+
process.argv.splice(2, 1);
|
|
2840
|
+
await commandsMain();
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2564
2843
|
const opts = parseInitOptions();
|
|
2565
2844
|
const targetDir = process.cwd();
|
|
2566
2845
|
console.log('[lumenflow init] Scaffolding LumenFlow project...');
|
|
@@ -2603,6 +2882,18 @@ export async function main() {
|
|
|
2603
2882
|
console.log('\nWarnings:');
|
|
2604
2883
|
result.warnings.forEach((w) => console.log(` ⚠ ${w}`));
|
|
2605
2884
|
}
|
|
2885
|
+
// WU-1386: Run doctor auto-check (non-blocking)
|
|
2886
|
+
// This provides feedback on workflow health without failing init
|
|
2887
|
+
try {
|
|
2888
|
+
const doctorResult = await runDoctorForInit(targetDir);
|
|
2889
|
+
if (doctorResult.output) {
|
|
2890
|
+
console.log('');
|
|
2891
|
+
console.log(doctorResult.output);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
catch {
|
|
2895
|
+
// Doctor check is non-blocking - if it fails, continue with init
|
|
2896
|
+
}
|
|
2606
2897
|
// WU-1359: Show complete lifecycle with auto-ID (no --id flag required)
|
|
2607
2898
|
// WU-1364: Added initiative-first guidance for product visions
|
|
2608
2899
|
console.log('\n[lumenflow init] Done! Next steps:');
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Memory Recover CLI (WU-1390)
|
|
4
|
+
*
|
|
5
|
+
* Generate post-compaction recovery context for agents that have lost
|
|
6
|
+
* their LumenFlow instructions due to context compaction.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* pnpm mem:recover --wu WU-XXXX [options]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --max-size <bytes> Maximum output size in bytes (default: 2048)
|
|
13
|
+
* --format <json|human> Output format (default: human)
|
|
14
|
+
* --quiet Suppress header/footer output
|
|
15
|
+
*
|
|
16
|
+
* The recovery context includes:
|
|
17
|
+
* - Last checkpoint for the WU
|
|
18
|
+
* - Compact constraints (7 rules)
|
|
19
|
+
* - Essential CLI commands
|
|
20
|
+
* - Guidance to spawn fresh agent
|
|
21
|
+
*
|
|
22
|
+
* @see {@link packages/@lumenflow/memory/src/mem-recover-core.ts} - Core logic
|
|
23
|
+
* @see {@link packages/@lumenflow/memory/__tests__/mem-recover-core.test.ts} - Tests
|
|
24
|
+
*/
|
|
25
|
+
import fs from 'node:fs/promises';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import { generateRecoveryContext } from '@lumenflow/memory/dist/mem-recover-core.js';
|
|
28
|
+
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
29
|
+
import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
|
|
30
|
+
/**
|
|
31
|
+
* Log prefix for mem:recover output
|
|
32
|
+
*/
|
|
33
|
+
const LOG_PREFIX = '[mem:recover]';
|
|
34
|
+
/**
|
|
35
|
+
* Tool name for audit logging
|
|
36
|
+
*/
|
|
37
|
+
const TOOL_NAME = 'mem:recover';
|
|
38
|
+
/**
|
|
39
|
+
* Valid output formats
|
|
40
|
+
*/
|
|
41
|
+
const VALID_FORMATS = ['json', 'human'];
|
|
42
|
+
/**
|
|
43
|
+
* CLI argument options specific to mem:recover
|
|
44
|
+
*/
|
|
45
|
+
const CLI_OPTIONS = {
|
|
46
|
+
maxSize: {
|
|
47
|
+
name: 'maxSize',
|
|
48
|
+
flags: '-m, --max-size <bytes>',
|
|
49
|
+
description: 'Maximum output size in bytes (default: 2048)',
|
|
50
|
+
},
|
|
51
|
+
format: {
|
|
52
|
+
name: 'format',
|
|
53
|
+
flags: '-f, --format <format>',
|
|
54
|
+
description: 'Output format: json or human (default: human)',
|
|
55
|
+
},
|
|
56
|
+
baseDir: {
|
|
57
|
+
name: 'baseDir',
|
|
58
|
+
flags: '-d, --base-dir <path>',
|
|
59
|
+
description: 'Base directory (defaults to current directory)',
|
|
60
|
+
},
|
|
61
|
+
quiet: {
|
|
62
|
+
name: 'quiet',
|
|
63
|
+
flags: '-q, --quiet',
|
|
64
|
+
description: 'Suppress header/footer output, only show recovery context',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Write audit log entry for tool execution
|
|
69
|
+
*/
|
|
70
|
+
async function writeAuditLog(baseDir, entry) {
|
|
71
|
+
try {
|
|
72
|
+
const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
|
|
73
|
+
const logDir = path.dirname(logPath);
|
|
74
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
75
|
+
const line = `${JSON.stringify(entry)}\n`;
|
|
76
|
+
await fs.appendFile(logPath, line, 'utf-8');
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Audit logging is non-fatal - silently ignore errors
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate and parse a positive integer argument
|
|
84
|
+
*/
|
|
85
|
+
function parsePositiveInt(value, optionName) {
|
|
86
|
+
if (!value) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const parsed = parseInt(value, 10);
|
|
90
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
91
|
+
throw new Error(`Invalid ${optionName} value: "${value}". Must be a positive integer.`);
|
|
92
|
+
}
|
|
93
|
+
return parsed;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Validate format argument
|
|
97
|
+
*/
|
|
98
|
+
function validateFormat(format) {
|
|
99
|
+
if (!format) {
|
|
100
|
+
return 'human';
|
|
101
|
+
}
|
|
102
|
+
if (!VALID_FORMATS.includes(format)) {
|
|
103
|
+
throw new Error(`Invalid --format value: "${format}". Valid formats: ${VALID_FORMATS.join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
return format;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Print output in human-readable format
|
|
109
|
+
*/
|
|
110
|
+
function printHumanFormat(result, wuId, quiet) {
|
|
111
|
+
if (!quiet) {
|
|
112
|
+
console.log(`${LOG_PREFIX} Recovery context for ${wuId}:`);
|
|
113
|
+
console.log('');
|
|
114
|
+
}
|
|
115
|
+
console.log(result.context);
|
|
116
|
+
if (!quiet) {
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log(`${LOG_PREFIX} ${result.size} bytes${result.truncated ? ' (truncated)' : ''}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Print output in JSON format
|
|
123
|
+
*/
|
|
124
|
+
function printJsonFormat(result, wuId) {
|
|
125
|
+
const output = {
|
|
126
|
+
wuId,
|
|
127
|
+
context: result.context,
|
|
128
|
+
size: result.size,
|
|
129
|
+
truncated: result.truncated,
|
|
130
|
+
};
|
|
131
|
+
console.log(JSON.stringify(output, null, 2));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Main CLI entry point
|
|
135
|
+
*/
|
|
136
|
+
async function main() {
|
|
137
|
+
const args = createWUParser({
|
|
138
|
+
name: 'mem-recover',
|
|
139
|
+
description: 'Generate post-compaction recovery context for agents',
|
|
140
|
+
options: [
|
|
141
|
+
WU_OPTIONS.wu,
|
|
142
|
+
CLI_OPTIONS.maxSize,
|
|
143
|
+
CLI_OPTIONS.format,
|
|
144
|
+
CLI_OPTIONS.baseDir,
|
|
145
|
+
CLI_OPTIONS.quiet,
|
|
146
|
+
],
|
|
147
|
+
required: ['wu'],
|
|
148
|
+
});
|
|
149
|
+
const baseDir = args.baseDir || process.cwd();
|
|
150
|
+
const startedAt = new Date().toISOString();
|
|
151
|
+
const startTime = Date.now();
|
|
152
|
+
let maxSize;
|
|
153
|
+
let format;
|
|
154
|
+
// Validate arguments
|
|
155
|
+
try {
|
|
156
|
+
maxSize = parsePositiveInt(args.maxSize, '--max-size');
|
|
157
|
+
format = validateFormat(args.format);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
const error = err;
|
|
161
|
+
console.error(`${LOG_PREFIX} Error: ${error.message}`);
|
|
162
|
+
process.exit(EXIT_CODES.ERROR);
|
|
163
|
+
}
|
|
164
|
+
let result;
|
|
165
|
+
let error = null;
|
|
166
|
+
try {
|
|
167
|
+
result = await generateRecoveryContext({
|
|
168
|
+
wuId: args.wu,
|
|
169
|
+
baseDir,
|
|
170
|
+
maxSize,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
const e = err;
|
|
175
|
+
error = e.message;
|
|
176
|
+
result = {
|
|
177
|
+
success: false,
|
|
178
|
+
context: '',
|
|
179
|
+
size: 0,
|
|
180
|
+
truncated: false,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const durationMs = Date.now() - startTime;
|
|
184
|
+
// Write audit log entry
|
|
185
|
+
await writeAuditLog(baseDir, {
|
|
186
|
+
tool: TOOL_NAME,
|
|
187
|
+
status: error ? 'failed' : 'success',
|
|
188
|
+
startedAt,
|
|
189
|
+
completedAt: new Date().toISOString(),
|
|
190
|
+
durationMs,
|
|
191
|
+
input: {
|
|
192
|
+
baseDir,
|
|
193
|
+
wuId: args.wu,
|
|
194
|
+
maxSize,
|
|
195
|
+
format,
|
|
196
|
+
quiet: args.quiet,
|
|
197
|
+
},
|
|
198
|
+
output: result.success
|
|
199
|
+
? {
|
|
200
|
+
contextSize: result.size,
|
|
201
|
+
truncated: result.truncated,
|
|
202
|
+
}
|
|
203
|
+
: null,
|
|
204
|
+
error: error ? { message: error } : null,
|
|
205
|
+
});
|
|
206
|
+
if (error) {
|
|
207
|
+
console.error(`${LOG_PREFIX} Error: ${error}`);
|
|
208
|
+
process.exit(EXIT_CODES.ERROR);
|
|
209
|
+
}
|
|
210
|
+
// Print output based on format
|
|
211
|
+
if (format === 'json') {
|
|
212
|
+
printJsonFormat(result, args.wu);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
printHumanFormat(result, args.wu, !!args.quiet);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
main().catch((e) => {
|
|
219
|
+
console.error(`${LOG_PREFIX} ${e.message}`);
|
|
220
|
+
process.exit(EXIT_CODES.ERROR);
|
|
221
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Command-line interface for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"lumenflow-init": "./dist/init.js",
|
|
100
100
|
"lumenflow": "./dist/init.js",
|
|
101
101
|
"lumenflow-doctor": "./dist/doctor.js",
|
|
102
|
+
"lumenflow-commands": "./dist/commands.js",
|
|
102
103
|
"lumenflow-release": "./dist/release.js",
|
|
103
104
|
"lumenflow-docs-sync": "./dist/docs-sync.js",
|
|
104
105
|
"lumenflow-sync-templates": "./dist/sync-templates.js",
|
|
@@ -150,11 +151,11 @@
|
|
|
150
151
|
"pretty-ms": "^9.2.0",
|
|
151
152
|
"simple-git": "^3.30.0",
|
|
152
153
|
"yaml": "^2.8.2",
|
|
153
|
-
"@lumenflow/core": "2.
|
|
154
|
-
"@lumenflow/
|
|
155
|
-
"@lumenflow/
|
|
156
|
-
"@lumenflow/agent": "2.
|
|
157
|
-
"@lumenflow/
|
|
154
|
+
"@lumenflow/core": "2.10.0",
|
|
155
|
+
"@lumenflow/metrics": "2.10.0",
|
|
156
|
+
"@lumenflow/initiatives": "2.10.0",
|
|
157
|
+
"@lumenflow/agent": "2.10.0",
|
|
158
|
+
"@lumenflow/memory": "2.10.0"
|
|
158
159
|
},
|
|
159
160
|
"devDependencies": {
|
|
160
161
|
"@vitest/coverage-v8": "^4.0.17",
|