@lumenflow/cli 2.2.2 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +147 -57
  2. package/dist/__tests__/agent-log-issue.test.js +56 -0
  3. package/dist/__tests__/cli-entry-point.test.js +66 -17
  4. package/dist/__tests__/cli-subprocess.test.js +25 -0
  5. package/dist/__tests__/init.test.js +298 -0
  6. package/dist/__tests__/initiative-plan.test.js +340 -0
  7. package/dist/__tests__/mem-cleanup-execution.test.js +19 -0
  8. package/dist/__tests__/merge-block.test.js +220 -0
  9. package/dist/__tests__/safe-git.test.js +191 -0
  10. package/dist/__tests__/state-doctor.test.js +274 -0
  11. package/dist/__tests__/wu-done.test.js +36 -0
  12. package/dist/__tests__/wu-edit.test.js +119 -0
  13. package/dist/__tests__/wu-prep.test.js +108 -0
  14. package/dist/agent-issues-query.js +4 -3
  15. package/dist/agent-log-issue.js +25 -4
  16. package/dist/backlog-prune.js +5 -4
  17. package/dist/cli-entry-point.js +11 -1
  18. package/dist/doctor.js +368 -0
  19. package/dist/flow-bottlenecks.js +6 -5
  20. package/dist/flow-report.js +4 -3
  21. package/dist/gates.js +356 -101
  22. package/dist/guard-locked.js +4 -3
  23. package/dist/guard-worktree-commit.js +4 -3
  24. package/dist/init.js +508 -86
  25. package/dist/initiative-add-wu.js +4 -3
  26. package/dist/initiative-bulk-assign-wus.js +8 -5
  27. package/dist/initiative-create.js +73 -37
  28. package/dist/initiative-edit.js +37 -21
  29. package/dist/initiative-list.js +4 -3
  30. package/dist/initiative-plan.js +337 -0
  31. package/dist/initiative-status.js +4 -3
  32. package/dist/lane-health.js +377 -0
  33. package/dist/lane-suggest.js +382 -0
  34. package/dist/mem-checkpoint.js +2 -2
  35. package/dist/mem-cleanup.js +2 -2
  36. package/dist/mem-context.js +306 -0
  37. package/dist/mem-create.js +2 -2
  38. package/dist/mem-delete.js +293 -0
  39. package/dist/mem-inbox.js +2 -2
  40. package/dist/mem-index.js +211 -0
  41. package/dist/mem-init.js +1 -1
  42. package/dist/mem-profile.js +207 -0
  43. package/dist/mem-promote.js +254 -0
  44. package/dist/mem-ready.js +2 -2
  45. package/dist/mem-signal.js +2 -2
  46. package/dist/mem-start.js +2 -2
  47. package/dist/mem-summarize.js +2 -2
  48. package/dist/mem-triage.js +2 -2
  49. package/dist/merge-block.js +222 -0
  50. package/dist/metrics-cli.js +7 -4
  51. package/dist/metrics-snapshot.js +4 -3
  52. package/dist/orchestrate-initiative.js +10 -4
  53. package/dist/orchestrate-monitor.js +379 -31
  54. package/dist/signal-cleanup.js +296 -0
  55. package/dist/spawn-list.js +6 -5
  56. package/dist/state-bootstrap.js +5 -4
  57. package/dist/state-cleanup.js +360 -0
  58. package/dist/state-doctor-fix.js +196 -0
  59. package/dist/state-doctor.js +501 -0
  60. package/dist/validate-agent-skills.js +4 -3
  61. package/dist/validate-agent-sync.js +4 -3
  62. package/dist/validate-backlog-sync.js +4 -3
  63. package/dist/validate-skills-spec.js +4 -3
  64. package/dist/validate.js +4 -3
  65. package/dist/wu-block.js +3 -3
  66. package/dist/wu-claim.js +208 -98
  67. package/dist/wu-cleanup.js +5 -4
  68. package/dist/wu-create.js +71 -46
  69. package/dist/wu-delete.js +88 -60
  70. package/dist/wu-deps.js +6 -5
  71. package/dist/wu-done-check.js +34 -0
  72. package/dist/wu-done.js +39 -12
  73. package/dist/wu-edit.js +63 -28
  74. package/dist/wu-infer-lane.js +7 -6
  75. package/dist/wu-preflight.js +23 -81
  76. package/dist/wu-prep.js +125 -0
  77. package/dist/wu-prune.js +4 -3
  78. package/dist/wu-recover.js +88 -22
  79. package/dist/wu-repair.js +7 -6
  80. package/dist/wu-spawn.js +226 -270
  81. package/dist/wu-status.js +4 -3
  82. package/dist/wu-unblock.js +5 -5
  83. package/dist/wu-unlock-lane.js +4 -3
  84. package/dist/wu-validate.js +5 -4
  85. package/package.json +16 -7
  86. package/templates/core/.lumenflow/constraints.md.template +192 -0
  87. package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
  88. package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
  89. package/templates/core/AGENTS.md.template +60 -0
  90. package/templates/core/LUMENFLOW.md.template +255 -0
  91. package/templates/core/UPGRADING.md.template +121 -0
  92. package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
  93. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
  94. package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
  95. package/templates/core/ai/onboarding/release-process.md.template +362 -0
  96. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
  97. package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
  98. package/templates/vendors/aider/.aider.conf.yml.template +27 -0
  99. package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
  100. package/templates/vendors/claude/.claude/settings.json.template +49 -0
  101. package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
  102. package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
  103. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
  104. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
  105. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
  106. package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
  107. package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
  108. package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
  109. package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
  110. package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
  111. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
  112. package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
  113. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
  114. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
  115. package/templates/vendors/cline/.clinerules.template +53 -0
  116. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
  117. package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
  118. package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +34 -0
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
3
+ /**
4
+ * Memory Index CLI (WU-1235)
5
+ *
6
+ * Scans project conventions and creates project-lifecycle summary nodes.
7
+ * Indexes README.md, LUMENFLOW.md, package.json, .lumenflow.config.yaml,
8
+ * and .lumenflow/constraints.md to provide agent context awareness.
9
+ *
10
+ * Includes audit logging to .lumenflow/telemetry/tools.ndjson.
11
+ *
12
+ * Usage:
13
+ * pnpm mem:index [--dry-run] [--quiet] [--json] [--base-dir <path>]
14
+ *
15
+ * @see {@link packages/@lumenflow/memory/src/mem-index-core.ts} - Core logic
16
+ * @see {@link packages/@lumenflow/cli/__tests__/mem-index.test.ts} - Tests
17
+ */
18
+ import fs from 'node:fs/promises';
19
+ import path from 'node:path';
20
+ import { indexProject } from '@lumenflow/memory/dist/mem-index-core.js';
21
+ import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
22
+ import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
23
+ /**
24
+ * Log prefix for mem:index output
25
+ */
26
+ const LOG_PREFIX = '[mem:index]';
27
+ /**
28
+ * Tool name for audit logging
29
+ */
30
+ const TOOL_NAME = 'mem:index';
31
+ /**
32
+ * CLI argument options specific to mem:index
33
+ */
34
+ const CLI_OPTIONS = {
35
+ dryRun: {
36
+ name: 'dryRun',
37
+ flags: '-n, --dry-run',
38
+ description: 'Show what would be indexed without writing',
39
+ },
40
+ quiet: {
41
+ name: 'quiet',
42
+ flags: '-q, --quiet',
43
+ description: 'Suppress output except summary',
44
+ },
45
+ json: {
46
+ name: 'json',
47
+ flags: '--json',
48
+ description: 'Output result as JSON',
49
+ },
50
+ baseDir: {
51
+ name: 'baseDir',
52
+ flags: '-b, --base-dir <path>',
53
+ description: 'Base directory (defaults to current directory)',
54
+ },
55
+ };
56
+ /**
57
+ * Write audit log entry for tool execution
58
+ *
59
+ * @param baseDir - Base directory
60
+ * @param entry - Audit log entry
61
+ */
62
+ async function writeAuditLog(baseDir, entry) {
63
+ try {
64
+ const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
65
+ const logDir = path.dirname(logPath);
66
+ await fs.mkdir(logDir, { recursive: true });
67
+ const line = `${JSON.stringify(entry)}\n`;
68
+ await fs.appendFile(logPath, line, 'utf-8');
69
+ }
70
+ catch {
71
+ // Audit logging is non-fatal - silently ignore errors
72
+ }
73
+ }
74
+ /**
75
+ * Parse CLI arguments
76
+ */
77
+ function parseArguments() {
78
+ return createWUParser({
79
+ name: 'mem-index',
80
+ description: 'Index project conventions for agent context awareness',
81
+ options: [CLI_OPTIONS.dryRun, CLI_OPTIONS.quiet, CLI_OPTIONS.json, CLI_OPTIONS.baseDir],
82
+ required: [],
83
+ allowPositionalId: false,
84
+ });
85
+ }
86
+ /**
87
+ * Print human-readable result
88
+ *
89
+ * @param result - Index result
90
+ * @param dryRun - Whether this was a dry-run
91
+ * @param quiet - Whether to use quiet mode
92
+ */
93
+ function printResult(result, dryRun, quiet) {
94
+ if (dryRun) {
95
+ console.log(`${LOG_PREFIX} Dry-run mode - no changes written`);
96
+ console.log('');
97
+ }
98
+ if (quiet) {
99
+ // Minimal output
100
+ const action = dryRun ? 'Would index' : 'Indexed';
101
+ console.log(`${LOG_PREFIX} ${action}: ${result.nodesCreated} created, ${result.nodesUpdated} updated, ${result.nodesSkipped} skipped`);
102
+ return;
103
+ }
104
+ console.log(`${LOG_PREFIX} Project indexing complete`);
105
+ console.log('');
106
+ if (result.sourcesScanned.length > 0) {
107
+ console.log('Sources scanned:');
108
+ for (const source of result.sourcesScanned) {
109
+ console.log(` - ${source}`);
110
+ }
111
+ console.log('');
112
+ }
113
+ if (result.sourcesMissing.length > 0) {
114
+ console.log('Sources not found:');
115
+ for (const source of result.sourcesMissing) {
116
+ console.log(` - ${source}`);
117
+ }
118
+ console.log('');
119
+ }
120
+ console.log('Summary:');
121
+ const createLabel = dryRun ? 'Would create' : 'Created';
122
+ const updateLabel = dryRun ? 'Would update' : 'Updated';
123
+ console.log(` ${createLabel}: ${result.nodesCreated} nodes`);
124
+ console.log(` ${updateLabel}: ${result.nodesUpdated} nodes`);
125
+ console.log(` Skipped: ${result.nodesSkipped} nodes (unchanged)`);
126
+ console.log('');
127
+ }
128
+ /**
129
+ * Print JSON result
130
+ *
131
+ * @param result - Index result
132
+ */
133
+ function printJsonResult(result) {
134
+ console.log(JSON.stringify(result, null, 2));
135
+ }
136
+ /**
137
+ * Main CLI entry point
138
+ */
139
+ async function main() {
140
+ const args = parseArguments();
141
+ const baseDir = args.baseDir || process.cwd();
142
+ const dryRun = Boolean(args.dryRun);
143
+ const quiet = Boolean(args.quiet);
144
+ const json = Boolean(args.json);
145
+ // Validate base directory exists
146
+ try {
147
+ const stat = await fs.stat(baseDir);
148
+ if (!stat.isDirectory()) {
149
+ console.error(`${LOG_PREFIX} Error: ${baseDir} is not a directory`);
150
+ process.exit(EXIT_CODES.ERROR);
151
+ }
152
+ }
153
+ catch {
154
+ console.error(`${LOG_PREFIX} Error: Directory not found: ${baseDir}`);
155
+ process.exit(EXIT_CODES.ERROR);
156
+ }
157
+ const startedAt = new Date().toISOString();
158
+ const startTime = Date.now();
159
+ let result;
160
+ let error = null;
161
+ try {
162
+ result = await indexProject(baseDir, { dryRun });
163
+ }
164
+ catch (err) {
165
+ error = err instanceof Error ? err.message : String(err);
166
+ result = {
167
+ success: false,
168
+ nodesCreated: 0,
169
+ nodesUpdated: 0,
170
+ nodesSkipped: 0,
171
+ sourcesScanned: [],
172
+ sourcesMissing: [],
173
+ error,
174
+ };
175
+ }
176
+ const durationMs = Date.now() - startTime;
177
+ // Write audit log
178
+ await writeAuditLog(baseDir, {
179
+ tool: TOOL_NAME,
180
+ status: error ? 'failed' : 'success',
181
+ startedAt,
182
+ completedAt: new Date().toISOString(),
183
+ durationMs,
184
+ input: {
185
+ baseDir,
186
+ dryRun,
187
+ },
188
+ output: {
189
+ success: result.success,
190
+ nodesCreated: result.nodesCreated,
191
+ nodesUpdated: result.nodesUpdated,
192
+ nodesSkipped: result.nodesSkipped,
193
+ sourcesScanned: result.sourcesScanned.length,
194
+ },
195
+ error: error ? { message: error } : null,
196
+ });
197
+ if (error) {
198
+ console.error(`${LOG_PREFIX} Error: ${error}`);
199
+ process.exit(EXIT_CODES.ERROR);
200
+ }
201
+ if (json) {
202
+ printJsonResult(result);
203
+ }
204
+ else {
205
+ printResult(result, dryRun, quiet);
206
+ }
207
+ }
208
+ main().catch((e) => {
209
+ console.error(`${LOG_PREFIX} ${e.message}`);
210
+ process.exit(EXIT_CODES.ERROR);
211
+ });
package/dist/mem-init.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * Usage:
11
11
  * pnpm mem:init [--base-dir <path>] [--quiet]
12
12
  *
13
- * @see {@link tools/lib/mem-init-core.mjs} - Core logic
13
+ * @see {@link packages/@lumenflow/cli/src/lib/mem-init-core.ts} - Core logic
14
14
  */
15
15
  import fs from 'node:fs/promises';
16
16
  import path from 'node:path';
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
3
+ /**
4
+ * Memory Profile CLI (WU-1237)
5
+ *
6
+ * Renders top N project-level memories for context injection.
7
+ *
8
+ * Features:
9
+ * - Outputs project-level knowledge profile
10
+ * - Configurable limit (default N=20)
11
+ * - Tag-based filtering
12
+ * - Output format compatible with mem:context
13
+ *
14
+ * Usage:
15
+ * pnpm mem:profile # Top 20 project memories
16
+ * pnpm mem:profile --limit 10 # Top 10 project memories
17
+ * pnpm mem:profile --tag decision # Filter by tag
18
+ * pnpm mem:profile --json # Output as JSON
19
+ *
20
+ * @see {@link packages/@lumenflow/memory/src/mem-profile-core.ts} - Core logic
21
+ */
22
+ import fs from 'node:fs/promises';
23
+ import path from 'node:path';
24
+ import { generateProfile, DEFAULT_PROFILE_LIMIT } from '@lumenflow/memory/dist/mem-profile-core.js';
25
+ import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
26
+ import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
27
+ /**
28
+ * Log prefix for mem:profile output
29
+ */
30
+ const LOG_PREFIX = '[mem:profile]';
31
+ /**
32
+ * Tool name for audit logging
33
+ */
34
+ const TOOL_NAME = 'mem:profile';
35
+ /**
36
+ * CLI argument options specific to mem:profile
37
+ */
38
+ const CLI_OPTIONS = {
39
+ limit: {
40
+ name: 'limit',
41
+ flags: '-l, --limit <n>',
42
+ description: `Maximum nodes to include (default: ${DEFAULT_PROFILE_LIMIT})`,
43
+ },
44
+ tag: {
45
+ name: 'tag',
46
+ flags: '-t, --tag <tag>',
47
+ description: 'Filter by tag category (e.g., decision, pattern)',
48
+ },
49
+ json: {
50
+ name: 'json',
51
+ flags: '--json',
52
+ description: 'Output as JSON',
53
+ },
54
+ raw: {
55
+ name: 'raw',
56
+ flags: '--raw',
57
+ description: 'Output raw profile block (for piping to mem:context)',
58
+ },
59
+ quiet: {
60
+ name: 'quiet',
61
+ flags: '-q, --quiet',
62
+ description: 'Suppress output except the profile block',
63
+ },
64
+ };
65
+ /**
66
+ * Write audit log entry for tool execution
67
+ */
68
+ async function writeAuditLog(baseDir, entry) {
69
+ try {
70
+ const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
71
+ const logDir = path.dirname(logPath);
72
+ await fs.mkdir(logDir, { recursive: true });
73
+ const line = `${JSON.stringify(entry)}\n`;
74
+ await fs.appendFile(logPath, line, 'utf-8');
75
+ }
76
+ catch {
77
+ // Audit logging is non-fatal - silently ignore errors
78
+ }
79
+ }
80
+ /**
81
+ * Parse CLI arguments
82
+ */
83
+ function parseArguments() {
84
+ return createWUParser({
85
+ name: 'mem-profile',
86
+ description: 'Generate project knowledge profile for context injection',
87
+ options: [
88
+ CLI_OPTIONS.limit,
89
+ CLI_OPTIONS.tag,
90
+ CLI_OPTIONS.json,
91
+ CLI_OPTIONS.raw,
92
+ CLI_OPTIONS.quiet,
93
+ ],
94
+ required: [],
95
+ allowPositionalId: false,
96
+ });
97
+ }
98
+ /**
99
+ * Output profile in JSON format
100
+ */
101
+ function outputJson(result) {
102
+ console.log(JSON.stringify(result, null, 2));
103
+ }
104
+ /**
105
+ * Output profile in raw format (for piping)
106
+ */
107
+ function outputRaw(result) {
108
+ if (result.profileBlock) {
109
+ process.stdout.write(result.profileBlock);
110
+ }
111
+ }
112
+ /**
113
+ * Output profile in human-readable format
114
+ */
115
+ function outputHumanReadable(result, args) {
116
+ if (result.nodes.length === 0) {
117
+ if (!args.quiet) {
118
+ console.log(`${LOG_PREFIX} No project-level memories found.`);
119
+ if (args.tag) {
120
+ console.log(` Filter: --tag ${args.tag}`);
121
+ }
122
+ console.log('');
123
+ console.log('To promote session learnings to project level, use:');
124
+ console.log(' pnpm mem:promote --node mem-xxxx --tag pattern');
125
+ }
126
+ return;
127
+ }
128
+ if (!args.quiet) {
129
+ console.log(`${LOG_PREFIX} Project Profile (${result.stats.includedNodes}/${result.stats.totalProjectNodes} nodes)`);
130
+ if (args.tag) {
131
+ console.log(` Filter: --tag ${args.tag}`);
132
+ }
133
+ console.log('');
134
+ }
135
+ // Output the profile block
136
+ console.log(result.profileBlock);
137
+ if (!args.quiet) {
138
+ // Stats summary
139
+ console.log('Tag breakdown:');
140
+ for (const [tag, count] of Object.entries(result.stats.byTag)) {
141
+ console.log(` ${tag}: ${count}`);
142
+ }
143
+ }
144
+ }
145
+ /**
146
+ * Main CLI entry point
147
+ */
148
+ async function main() {
149
+ const args = parseArguments();
150
+ const baseDir = process.cwd();
151
+ const startedAt = new Date().toISOString();
152
+ const startTime = Date.now();
153
+ let result = null;
154
+ let error = null;
155
+ try {
156
+ const limit = args.limit ? parseInt(args.limit, 10) : DEFAULT_PROFILE_LIMIT;
157
+ if (isNaN(limit) || limit < 1) {
158
+ console.error(`${LOG_PREFIX} Error: --limit must be a positive integer`);
159
+ process.exit(EXIT_CODES.ERROR);
160
+ }
161
+ result = await generateProfile(baseDir, {
162
+ limit,
163
+ tag: args.tag,
164
+ });
165
+ // Output format based on flags
166
+ if (args.json) {
167
+ outputJson(result);
168
+ }
169
+ else if (args.raw) {
170
+ outputRaw(result);
171
+ }
172
+ else {
173
+ outputHumanReadable(result, args);
174
+ }
175
+ }
176
+ catch (err) {
177
+ error = err.message;
178
+ console.error(`${LOG_PREFIX} Error: ${error}`);
179
+ process.exit(EXIT_CODES.ERROR);
180
+ }
181
+ const durationMs = Date.now() - startTime;
182
+ await writeAuditLog(baseDir, {
183
+ tool: TOOL_NAME,
184
+ action: 'generate',
185
+ status: error ? 'failed' : 'success',
186
+ startedAt,
187
+ completedAt: new Date().toISOString(),
188
+ durationMs,
189
+ input: {
190
+ baseDir,
191
+ limit: args.limit,
192
+ tag: args.tag,
193
+ },
194
+ output: result
195
+ ? {
196
+ includedNodes: result.stats.includedNodes,
197
+ totalProjectNodes: result.stats.totalProjectNodes,
198
+ byTag: result.stats.byTag,
199
+ }
200
+ : null,
201
+ error: error ? { message: error } : null,
202
+ });
203
+ }
204
+ main().catch((e) => {
205
+ console.error(`${LOG_PREFIX} ${e.message}`);
206
+ process.exit(EXIT_CODES.ERROR);
207
+ });
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
3
+ /**
4
+ * Memory Promote CLI (WU-1237)
5
+ *
6
+ * Promotes session/WU learnings into project-level knowledge nodes.
7
+ *
8
+ * Features:
9
+ * - Promote individual nodes to project lifecycle
10
+ * - Promote all summaries from a WU
11
+ * - Enforced taxonomy tags
12
+ * - Creates discovered_from relationships for provenance
13
+ * - Dry-run mode for preview
14
+ *
15
+ * Usage:
16
+ * pnpm mem:promote --node mem-xxxx --tag pattern # Promote single node
17
+ * pnpm mem:promote --wu WU-1234 --tag decision # Promote all WU summaries
18
+ * pnpm mem:promote --node mem-xxxx --tag pattern --dry-run # Preview
19
+ *
20
+ * @see {@link packages/@lumenflow/memory/src/mem-promote-core.ts} - Core logic
21
+ */
22
+ import fs from 'node:fs/promises';
23
+ import path from 'node:path';
24
+ import { promoteNode, promoteFromWu, ALLOWED_PROMOTION_TAGS, } from '@lumenflow/memory/dist/mem-promote-core.js';
25
+ import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
26
+ import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
27
+ /**
28
+ * Log prefix for mem:promote output
29
+ */
30
+ const LOG_PREFIX = '[mem:promote]';
31
+ /**
32
+ * Tool name for audit logging
33
+ */
34
+ const TOOL_NAME = 'mem:promote';
35
+ /**
36
+ * CLI argument options specific to mem:promote
37
+ */
38
+ const CLI_OPTIONS = {
39
+ node: {
40
+ name: 'node',
41
+ flags: '-n, --node <nodeId>',
42
+ description: 'Memory node ID to promote (mem-xxxx format)',
43
+ },
44
+ wu: {
45
+ name: 'wu',
46
+ flags: '-w, --wu <wuId>',
47
+ description: 'WU ID to promote all summaries from (WU-XXXX format)',
48
+ },
49
+ tag: {
50
+ name: 'tag',
51
+ flags: '-t, --tag <tag>',
52
+ description: `Tag from taxonomy: ${ALLOWED_PROMOTION_TAGS.join(', ')}`,
53
+ },
54
+ dryRun: {
55
+ name: 'dryRun',
56
+ flags: '--dry-run',
57
+ description: 'Preview what would be promoted without writing',
58
+ },
59
+ json: {
60
+ name: 'json',
61
+ flags: '--json',
62
+ description: 'Output as JSON',
63
+ },
64
+ quiet: {
65
+ name: 'quiet',
66
+ flags: '-q, --quiet',
67
+ description: 'Suppress output except errors',
68
+ },
69
+ };
70
+ /**
71
+ * Write audit log entry for tool execution
72
+ */
73
+ async function writeAuditLog(baseDir, entry) {
74
+ try {
75
+ const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
76
+ const logDir = path.dirname(logPath);
77
+ await fs.mkdir(logDir, { recursive: true });
78
+ const line = `${JSON.stringify(entry)}\n`;
79
+ await fs.appendFile(logPath, line, 'utf-8');
80
+ }
81
+ catch {
82
+ // Audit logging is non-fatal - silently ignore errors
83
+ }
84
+ }
85
+ /**
86
+ * Handle promoting a single node
87
+ */
88
+ async function handlePromoteNode(baseDir, args) {
89
+ if (!args.tag) {
90
+ console.error(`${LOG_PREFIX} Error: --tag is required`);
91
+ console.error('');
92
+ console.error(`Usage: pnpm mem:promote --node mem-xxxx --tag <tag>`);
93
+ console.error(`Tags: ${ALLOWED_PROMOTION_TAGS.join(', ')}`);
94
+ process.exit(EXIT_CODES.ERROR);
95
+ }
96
+ // args.node is guaranteed to be defined when this function is called
97
+ const nodeId = args.node;
98
+ const result = await promoteNode(baseDir, {
99
+ nodeId,
100
+ tag: args.tag,
101
+ dryRun: args.dryRun,
102
+ });
103
+ if (args.json) {
104
+ console.log(JSON.stringify(result, null, 2));
105
+ return result;
106
+ }
107
+ if (args.dryRun) {
108
+ console.log(`${LOG_PREFIX} Dry-run: Would promote to:`);
109
+ console.log('');
110
+ console.log(` ID: ${result.promotedNode.id}`);
111
+ console.log(` Lifecycle: ${result.promotedNode.lifecycle}`);
112
+ console.log(` Tags: ${result.promotedNode.tags?.join(', ')}`);
113
+ console.log(` Content: ${result.promotedNode.content.substring(0, 80)}...`);
114
+ console.log('');
115
+ console.log('To execute, run without --dry-run');
116
+ return result;
117
+ }
118
+ if (!args.quiet) {
119
+ console.log(`${LOG_PREFIX} ✅ Promoted to project level`);
120
+ console.log('');
121
+ console.log(` Source: ${args.node}`);
122
+ console.log(` New ID: ${result.promotedNode.id}`);
123
+ console.log(` Lifecycle: project`);
124
+ console.log(` Tag: ${args.tag}`);
125
+ }
126
+ return result;
127
+ }
128
+ /**
129
+ * Handle promoting all summaries from a WU
130
+ */
131
+ async function handlePromoteFromWu(baseDir, args) {
132
+ if (!args.tag) {
133
+ console.error(`${LOG_PREFIX} Error: --tag is required`);
134
+ console.error('');
135
+ console.error(`Usage: pnpm mem:promote --wu WU-XXXX --tag <tag>`);
136
+ console.error(`Tags: ${ALLOWED_PROMOTION_TAGS.join(', ')}`);
137
+ process.exit(EXIT_CODES.ERROR);
138
+ }
139
+ // args.wu is guaranteed to be defined when this function is called
140
+ const wuId = args.wu;
141
+ const result = await promoteFromWu(baseDir, {
142
+ wuId,
143
+ tag: args.tag,
144
+ dryRun: args.dryRun,
145
+ });
146
+ if (args.json) {
147
+ console.log(JSON.stringify(result, null, 2));
148
+ return result;
149
+ }
150
+ if (result.promotedNodes.length === 0) {
151
+ if (!args.quiet) {
152
+ console.log(`${LOG_PREFIX} No summaries found for ${args.wu}`);
153
+ }
154
+ return result;
155
+ }
156
+ if (args.dryRun) {
157
+ console.log(`${LOG_PREFIX} Dry-run: Would promote ${result.promotedNodes.length} summary(ies):`);
158
+ console.log('');
159
+ for (const node of result.promotedNodes) {
160
+ console.log(` - ${node.id}: ${node.content.substring(0, 60)}...`);
161
+ }
162
+ console.log('');
163
+ console.log('To execute, run without --dry-run');
164
+ return result;
165
+ }
166
+ if (!args.quiet) {
167
+ console.log(`${LOG_PREFIX} ✅ Promoted ${result.promotedNodes.length} summary(ies) from ${args.wu}`);
168
+ console.log('');
169
+ for (const node of result.promotedNodes) {
170
+ console.log(` - ${node.id}: ${node.content.substring(0, 60)}...`);
171
+ }
172
+ }
173
+ return result;
174
+ }
175
+ /**
176
+ * Parse CLI arguments
177
+ */
178
+ function parseArguments() {
179
+ return createWUParser({
180
+ name: 'mem-promote',
181
+ description: 'Promote session/WU learnings to project-level knowledge',
182
+ options: [
183
+ CLI_OPTIONS.node,
184
+ CLI_OPTIONS.wu,
185
+ CLI_OPTIONS.tag,
186
+ CLI_OPTIONS.dryRun,
187
+ CLI_OPTIONS.json,
188
+ CLI_OPTIONS.quiet,
189
+ ],
190
+ required: [],
191
+ allowPositionalId: false,
192
+ });
193
+ }
194
+ /**
195
+ * Main CLI entry point
196
+ */
197
+ async function main() {
198
+ const args = parseArguments();
199
+ const baseDir = process.cwd();
200
+ const startedAt = new Date().toISOString();
201
+ const startTime = Date.now();
202
+ // Validate: one of --node or --wu must be provided
203
+ if (!args.node && !args.wu) {
204
+ console.error(`${LOG_PREFIX} Error: Either --node or --wu is required`);
205
+ console.error('');
206
+ console.error('Usage:');
207
+ console.error(' pnpm mem:promote --node mem-xxxx --tag pattern');
208
+ console.error(' pnpm mem:promote --wu WU-1234 --tag decision');
209
+ process.exit(EXIT_CODES.ERROR);
210
+ }
211
+ if (args.node && args.wu) {
212
+ console.error(`${LOG_PREFIX} Error: Cannot use both --node and --wu`);
213
+ process.exit(EXIT_CODES.ERROR);
214
+ }
215
+ let result = null;
216
+ let error = null;
217
+ const action = args.node ? 'promote-node' : 'promote-wu';
218
+ try {
219
+ if (args.node) {
220
+ result = await handlePromoteNode(baseDir, args);
221
+ }
222
+ else {
223
+ result = await handlePromoteFromWu(baseDir, args);
224
+ }
225
+ }
226
+ catch (err) {
227
+ error = err.message;
228
+ console.error(`${LOG_PREFIX} Error: ${error}`);
229
+ process.exit(EXIT_CODES.ERROR);
230
+ }
231
+ const durationMs = Date.now() - startTime;
232
+ await writeAuditLog(baseDir, {
233
+ tool: TOOL_NAME,
234
+ action,
235
+ status: error ? 'failed' : 'success',
236
+ startedAt,
237
+ completedAt: new Date().toISOString(),
238
+ durationMs,
239
+ input: {
240
+ baseDir,
241
+ action,
242
+ node: args.node,
243
+ wu: args.wu,
244
+ tag: args.tag,
245
+ dryRun: args.dryRun,
246
+ },
247
+ output: result,
248
+ error: error ? { message: error } : null,
249
+ });
250
+ }
251
+ main().catch((e) => {
252
+ console.error(`${LOG_PREFIX} ${e.message}`);
253
+ process.exit(EXIT_CODES.ERROR);
254
+ });
package/dist/mem-ready.js CHANGED
@@ -10,8 +10,8 @@
10
10
  *
11
11
  * Includes audit logging to .lumenflow/telemetry/tools.ndjson.
12
12
  *
13
- * @see {@link tools/lib/mem-ready-core.mjs} - Core logic
14
- * @see {@link tools/__tests__/mem-ready.test.mjs} - Tests
13
+ * @see {@link packages/@lumenflow/cli/src/lib/mem-ready-core.ts} - Core logic
14
+ * @see {@link packages/@lumenflow/cli/src/__tests__/mem-ready.test.ts} - Tests
15
15
  */
16
16
  import fs from 'node:fs/promises';
17
17
  import path from 'node:path';
@@ -10,8 +10,8 @@
10
10
  * Usage:
11
11
  * pnpm mem:signal 'message' [--wu <id>] [--lane <name>] [--quiet]
12
12
  *
13
- * @see {@link tools/lib/mem-signal-core.mjs} - Core logic
14
- * @see {@link tools/__tests__/mem-signal.test.mjs} - Tests
13
+ * @see {@link packages/@lumenflow/cli/src/lib/mem-signal-core.ts} - Core logic
14
+ * @see {@link packages/@lumenflow/cli/src/__tests__/mem-signal.test.ts} - Tests
15
15
  */
16
16
  import fs from 'node:fs/promises';
17
17
  import path from 'node:path';