@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.
Files changed (120) 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__/release.test.js +61 -0
  10. package/dist/__tests__/safe-git.test.js +191 -0
  11. package/dist/__tests__/state-doctor.test.js +274 -0
  12. package/dist/__tests__/wu-done.test.js +36 -0
  13. package/dist/__tests__/wu-edit.test.js +119 -0
  14. package/dist/__tests__/wu-prep.test.js +108 -0
  15. package/dist/agent-issues-query.js +4 -3
  16. package/dist/agent-log-issue.js +25 -4
  17. package/dist/backlog-prune.js +5 -4
  18. package/dist/cli-entry-point.js +11 -1
  19. package/dist/doctor.js +368 -0
  20. package/dist/flow-bottlenecks.js +6 -5
  21. package/dist/flow-report.js +4 -3
  22. package/dist/gates.js +356 -101
  23. package/dist/guard-locked.js +4 -3
  24. package/dist/guard-worktree-commit.js +4 -3
  25. package/dist/init.js +517 -86
  26. package/dist/initiative-add-wu.js +4 -3
  27. package/dist/initiative-bulk-assign-wus.js +8 -5
  28. package/dist/initiative-create.js +73 -37
  29. package/dist/initiative-edit.js +37 -21
  30. package/dist/initiative-list.js +4 -3
  31. package/dist/initiative-plan.js +337 -0
  32. package/dist/initiative-status.js +4 -3
  33. package/dist/lane-health.js +377 -0
  34. package/dist/lane-suggest.js +382 -0
  35. package/dist/mem-checkpoint.js +2 -2
  36. package/dist/mem-cleanup.js +2 -2
  37. package/dist/mem-context.js +306 -0
  38. package/dist/mem-create.js +2 -2
  39. package/dist/mem-delete.js +293 -0
  40. package/dist/mem-inbox.js +2 -2
  41. package/dist/mem-index.js +211 -0
  42. package/dist/mem-init.js +1 -1
  43. package/dist/mem-profile.js +207 -0
  44. package/dist/mem-promote.js +254 -0
  45. package/dist/mem-ready.js +2 -2
  46. package/dist/mem-signal.js +2 -2
  47. package/dist/mem-start.js +2 -2
  48. package/dist/mem-summarize.js +2 -2
  49. package/dist/mem-triage.js +2 -2
  50. package/dist/merge-block.js +222 -0
  51. package/dist/metrics-cli.js +7 -4
  52. package/dist/metrics-snapshot.js +4 -3
  53. package/dist/orchestrate-initiative.js +10 -4
  54. package/dist/orchestrate-monitor.js +379 -31
  55. package/dist/release.js +69 -29
  56. package/dist/signal-cleanup.js +296 -0
  57. package/dist/spawn-list.js +6 -5
  58. package/dist/state-bootstrap.js +5 -4
  59. package/dist/state-cleanup.js +360 -0
  60. package/dist/state-doctor-fix.js +196 -0
  61. package/dist/state-doctor.js +501 -0
  62. package/dist/validate-agent-skills.js +4 -3
  63. package/dist/validate-agent-sync.js +4 -3
  64. package/dist/validate-backlog-sync.js +4 -3
  65. package/dist/validate-skills-spec.js +4 -3
  66. package/dist/validate.js +4 -3
  67. package/dist/wu-block.js +3 -3
  68. package/dist/wu-claim.js +208 -98
  69. package/dist/wu-cleanup.js +5 -4
  70. package/dist/wu-create.js +71 -46
  71. package/dist/wu-delete.js +88 -60
  72. package/dist/wu-deps.js +6 -5
  73. package/dist/wu-done-check.js +34 -0
  74. package/dist/wu-done.js +39 -12
  75. package/dist/wu-edit.js +63 -28
  76. package/dist/wu-infer-lane.js +7 -6
  77. package/dist/wu-preflight.js +23 -81
  78. package/dist/wu-prep.js +125 -0
  79. package/dist/wu-prune.js +4 -3
  80. package/dist/wu-recover.js +88 -22
  81. package/dist/wu-repair.js +7 -6
  82. package/dist/wu-spawn.js +226 -270
  83. package/dist/wu-status.js +4 -3
  84. package/dist/wu-unblock.js +5 -5
  85. package/dist/wu-unlock-lane.js +4 -3
  86. package/dist/wu-validate.js +5 -4
  87. package/package.json +16 -7
  88. package/templates/core/.lumenflow/constraints.md.template +192 -0
  89. package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
  90. package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
  91. package/templates/core/AGENTS.md.template +60 -0
  92. package/templates/core/LUMENFLOW.md.template +255 -0
  93. package/templates/core/UPGRADING.md.template +121 -0
  94. package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
  95. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
  96. package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
  97. package/templates/core/ai/onboarding/release-process.md.template +362 -0
  98. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
  99. package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
  100. package/templates/vendors/aider/.aider.conf.yml.template +27 -0
  101. package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
  102. package/templates/vendors/claude/.claude/settings.json.template +49 -0
  103. package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
  104. package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
  105. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
  106. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
  107. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
  108. package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
  109. package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
  110. package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
  111. package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
  112. package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
  113. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
  114. package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
  115. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
  116. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
  117. package/templates/vendors/cline/.clinerules.template +53 -0
  118. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
  119. package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
  120. package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +34 -0
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
3
+ /**
4
+ * Memory Context CLI (WU-1234, WU-1292)
5
+ *
6
+ * Generate deterministic, formatted context injection blocks for wu:spawn prompts.
7
+ * Outputs structured markdown with sections for project profile, summaries,
8
+ * WU context, and discoveries.
9
+ *
10
+ * Usage:
11
+ * pnpm mem:context --wu WU-XXXX [options]
12
+ *
13
+ * Options:
14
+ * --max-size <bytes> Maximum context size in bytes (default: 4096)
15
+ * --spawn-context-max-size <bytes> Alias for --max-size (for config parity)
16
+ * --lane <lane> Filter project memories by lane (WU-1292)
17
+ * --max-recent-summaries <n> Limit recent summaries included (WU-1292)
18
+ * --max-project-nodes <n> Limit project nodes included (WU-1292)
19
+ * --format <json|human> Output format (default: human)
20
+ * --quiet Suppress header/footer output
21
+ *
22
+ * Includes audit logging to .lumenflow/telemetry/tools.ndjson.
23
+ *
24
+ * @see {@link packages/@lumenflow/memory/src/mem-context-core.ts} - Core logic
25
+ * @see {@link packages/@lumenflow/cli/__tests__/mem-context.test.ts} - Tests
26
+ */
27
+ import fs from 'node:fs/promises';
28
+ import path from 'node:path';
29
+ import { generateContext } from '@lumenflow/memory/dist/mem-context-core.js';
30
+ import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
31
+ import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
32
+ /**
33
+ * Log prefix for mem:context output
34
+ */
35
+ const LOG_PREFIX = '[mem:context]';
36
+ /**
37
+ * Tool name for audit logging
38
+ */
39
+ const TOOL_NAME = 'mem:context';
40
+ /**
41
+ * Valid output formats
42
+ */
43
+ const VALID_FORMATS = ['json', 'human'];
44
+ /**
45
+ * CLI argument options specific to mem:context
46
+ */
47
+ const CLI_OPTIONS = {
48
+ maxSize: {
49
+ name: 'maxSize',
50
+ flags: '-m, --max-size <bytes>',
51
+ description: 'Maximum context size in bytes (default: 4096)',
52
+ },
53
+ spawnContextMaxSize: {
54
+ name: 'spawnContextMaxSize',
55
+ flags: '--spawn-context-max-size <bytes>',
56
+ description: 'Alias for --max-size (for config parity with spawn_context_max_size)',
57
+ },
58
+ lane: {
59
+ name: 'lane',
60
+ flags: '-l, --lane <lane>',
61
+ description: 'Filter project memories by lane (e.g., "Framework: CLI")',
62
+ },
63
+ maxRecentSummaries: {
64
+ name: 'maxRecentSummaries',
65
+ flags: '--max-recent-summaries <count>',
66
+ description: 'Maximum number of recent summaries to include (default: 5)',
67
+ },
68
+ maxProjectNodes: {
69
+ name: 'maxProjectNodes',
70
+ flags: '--max-project-nodes <count>',
71
+ description: 'Maximum number of project nodes to include (default: 10)',
72
+ },
73
+ format: {
74
+ name: 'format',
75
+ flags: '-f, --format <format>',
76
+ description: 'Output format: json or human (default: human)',
77
+ },
78
+ baseDir: {
79
+ name: 'baseDir',
80
+ flags: '-d, --base-dir <path>',
81
+ description: 'Base directory (defaults to current directory)',
82
+ },
83
+ quiet: {
84
+ name: 'quiet',
85
+ flags: '-q, --quiet',
86
+ description: 'Suppress header/footer output, only show context block',
87
+ },
88
+ };
89
+ /**
90
+ * Write audit log entry for tool execution
91
+ *
92
+ * @param baseDir - Base directory
93
+ * @param entry - Audit log entry
94
+ */
95
+ async function writeAuditLog(baseDir, entry) {
96
+ try {
97
+ const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
98
+ const logDir = path.dirname(logPath);
99
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool creates known directory
100
+ await fs.mkdir(logDir, { recursive: true });
101
+ const line = `${JSON.stringify(entry)}\n`;
102
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes audit log
103
+ await fs.appendFile(logPath, line, 'utf-8');
104
+ }
105
+ catch {
106
+ // Audit logging is non-fatal - silently ignore errors
107
+ }
108
+ }
109
+ /**
110
+ * Validate and parse a positive integer argument
111
+ *
112
+ * @param value - Raw argument value
113
+ * @param optionName - Name of the option for error messages
114
+ * @returns Parsed integer value
115
+ * @throws If value is invalid
116
+ */
117
+ function parsePositiveInt(value, optionName) {
118
+ if (!value) {
119
+ return undefined;
120
+ }
121
+ const parsed = parseInt(value, 10);
122
+ if (isNaN(parsed) || parsed <= 0) {
123
+ throw new Error(`Invalid ${optionName} value: "${value}". Must be a positive integer.`);
124
+ }
125
+ return parsed;
126
+ }
127
+ /**
128
+ * WU-1292: Validate lane argument
129
+ *
130
+ * @param lane - Lane argument
131
+ * @returns Validated lane
132
+ * @throws If lane is empty string
133
+ */
134
+ function validateLane(lane) {
135
+ if (lane === undefined) {
136
+ return undefined;
137
+ }
138
+ if (lane === '') {
139
+ throw new Error('Invalid --lane value: lane cannot be empty.');
140
+ }
141
+ return lane;
142
+ }
143
+ /**
144
+ * Validate format argument
145
+ *
146
+ * @param format - Format argument
147
+ * @returns Validated format
148
+ * @throws If format is invalid
149
+ */
150
+ function validateFormat(format) {
151
+ if (!format) {
152
+ return 'human';
153
+ }
154
+ if (!VALID_FORMATS.includes(format)) {
155
+ throw new Error(`Invalid --format value: "${format}". Valid formats: ${VALID_FORMATS.join(', ')}`);
156
+ }
157
+ return format;
158
+ }
159
+ /**
160
+ * Print output in human-readable format
161
+ *
162
+ * @param result - Generate context result
163
+ * @param wuId - WU ID
164
+ * @param quiet - Whether to suppress headers
165
+ */
166
+ function printHumanFormat(result, wuId, quiet) {
167
+ if (!quiet) {
168
+ console.log(`${LOG_PREFIX} Context for ${wuId}:`);
169
+ console.log('');
170
+ }
171
+ if (result.contextBlock === '') {
172
+ if (!quiet) {
173
+ console.log(' (no memories found - empty context block)');
174
+ console.log('');
175
+ }
176
+ return;
177
+ }
178
+ console.log(result.contextBlock);
179
+ if (!quiet) {
180
+ console.log('');
181
+ console.log(`${LOG_PREFIX} ${result.stats.totalNodes} node(s) included${result.stats.truncated ? ' (truncated)' : ''}`);
182
+ }
183
+ }
184
+ /**
185
+ * Print output in JSON format
186
+ *
187
+ * @param result - Generate context result
188
+ * @param wuId - WU ID
189
+ */
190
+ function printJsonFormat(result, wuId) {
191
+ const output = {
192
+ wuId,
193
+ contextBlock: result.contextBlock,
194
+ stats: result.stats,
195
+ };
196
+ console.log(JSON.stringify(output, null, 2));
197
+ }
198
+ /**
199
+ * Main CLI entry point
200
+ */
201
+ async function main() {
202
+ const args = createWUParser({
203
+ name: 'mem-context',
204
+ description: 'Generate context injection block for wu:spawn prompts',
205
+ options: [
206
+ WU_OPTIONS.wu,
207
+ CLI_OPTIONS.maxSize,
208
+ CLI_OPTIONS.spawnContextMaxSize,
209
+ CLI_OPTIONS.lane,
210
+ CLI_OPTIONS.maxRecentSummaries,
211
+ CLI_OPTIONS.maxProjectNodes,
212
+ CLI_OPTIONS.format,
213
+ CLI_OPTIONS.baseDir,
214
+ CLI_OPTIONS.quiet,
215
+ ],
216
+ required: ['wu'],
217
+ });
218
+ const baseDir = args.baseDir || process.cwd();
219
+ const startedAt = new Date().toISOString();
220
+ const startTime = Date.now();
221
+ let maxSize;
222
+ let lane;
223
+ let maxRecentSummaries;
224
+ let maxProjectNodes;
225
+ let format;
226
+ // Validate arguments
227
+ try {
228
+ // WU-1292: --spawn-context-max-size is an alias for --max-size (for config parity)
229
+ // If both are provided, --spawn-context-max-size takes precedence
230
+ const maxSizeArg = args.spawnContextMaxSize || args.maxSize;
231
+ maxSize = parsePositiveInt(maxSizeArg, '--max-size');
232
+ lane = validateLane(args.lane);
233
+ maxRecentSummaries = parsePositiveInt(args.maxRecentSummaries, '--max-recent-summaries');
234
+ maxProjectNodes = parsePositiveInt(args.maxProjectNodes, '--max-project-nodes');
235
+ format = validateFormat(args.format);
236
+ }
237
+ catch (err) {
238
+ const error = err;
239
+ console.error(`${LOG_PREFIX} Error: ${error.message}`);
240
+ process.exit(EXIT_CODES.ERROR);
241
+ }
242
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Core returns any type due to loose tsconfig
243
+ let result;
244
+ let error = null;
245
+ try {
246
+ result = await generateContext(baseDir, {
247
+ wuId: args.wu,
248
+ maxSize,
249
+ lane,
250
+ maxRecentSummaries,
251
+ maxProjectNodes,
252
+ });
253
+ }
254
+ catch (err) {
255
+ const e = err;
256
+ error = e.message;
257
+ result = {
258
+ success: false,
259
+ contextBlock: '',
260
+ stats: { totalNodes: 0, byType: {}, truncated: false, size: 0 },
261
+ };
262
+ }
263
+ // Type assertion for type safety
264
+ const typedResult = result;
265
+ const durationMs = Date.now() - startTime;
266
+ // Write audit log entry
267
+ await writeAuditLog(baseDir, {
268
+ tool: TOOL_NAME,
269
+ status: error ? 'failed' : 'success',
270
+ startedAt,
271
+ completedAt: new Date().toISOString(),
272
+ durationMs,
273
+ input: {
274
+ baseDir,
275
+ wuId: args.wu,
276
+ maxSize,
277
+ lane,
278
+ maxRecentSummaries,
279
+ maxProjectNodes,
280
+ format,
281
+ quiet: args.quiet,
282
+ },
283
+ output: typedResult.success
284
+ ? {
285
+ contextSize: typedResult.contextBlock.length,
286
+ stats: typedResult.stats,
287
+ }
288
+ : null,
289
+ error: error ? { message: error } : null,
290
+ });
291
+ if (error) {
292
+ console.error(`${LOG_PREFIX} Error: ${error}`);
293
+ process.exit(EXIT_CODES.ERROR);
294
+ }
295
+ // Print output based on format
296
+ if (format === 'json') {
297
+ printJsonFormat(typedResult, args.wu);
298
+ }
299
+ else {
300
+ printHumanFormat(typedResult, args.wu, !!args.quiet);
301
+ }
302
+ }
303
+ main().catch((e) => {
304
+ console.error(`${LOG_PREFIX} ${e.message}`);
305
+ process.exit(EXIT_CODES.ERROR);
306
+ });
@@ -11,8 +11,8 @@
11
11
  * Usage:
12
12
  * pnpm mem:create 'title' [--type <type>] [--discovered-from <id>] [--wu <id>] [--quiet]
13
13
  *
14
- * @see {@link tools/lib/mem-create-core.mjs} - Core logic
15
- * @see {@link tools/__tests__/mem-create.test.mjs} - Tests
14
+ * @see {@link packages/@lumenflow/cli/src/lib/mem-create-core.ts} - Core logic
15
+ * @see {@link packages/@lumenflow/cli/src/__tests__/mem-create.test.ts} - Tests
16
16
  */
17
17
  import fs from 'node:fs/promises';
18
18
  import path from 'node:path';
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI command uses console for status output */
3
+ /**
4
+ * Memory Delete CLI (WU-1284)
5
+ *
6
+ * Delete or archive memory nodes using soft-delete pattern.
7
+ * Respects append-only pattern by marking nodes with metadata.status=deleted.
8
+ *
9
+ * Features:
10
+ * - Delete by node ID(s)
11
+ * - Bulk delete via --tag filter
12
+ * - Bulk delete via --older-than filter
13
+ * - Dry-run preview mode
14
+ *
15
+ * Usage:
16
+ * pnpm mem:delete <node-id> # Delete single node
17
+ * pnpm mem:delete <id1> <id2> # Delete multiple nodes
18
+ * pnpm mem:delete --tag obsolete # Delete by tag
19
+ * pnpm mem:delete --older-than 30d # Delete old nodes
20
+ * pnpm mem:delete --tag old --older-than 7d # Combined filters
21
+ * pnpm mem:delete <id> --dry-run # Preview only
22
+ *
23
+ * @see {@link packages/@lumenflow/memory/src/mem-delete-core.ts} - Core logic
24
+ * @see {@link packages/@lumenflow/memory/__tests__/mem-delete.test.ts} - Tests
25
+ */
26
+ import fs from 'node:fs/promises';
27
+ import path from 'node:path';
28
+ import { deleteMemoryNodes } from '@lumenflow/memory/dist/mem-delete-core.js';
29
+ import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
30
+ import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
31
+ /**
32
+ * Log prefix for mem:delete output
33
+ */
34
+ const LOG_PREFIX = '[mem:delete]';
35
+ /**
36
+ * Tool name for audit logging
37
+ */
38
+ const TOOL_NAME = 'mem:delete';
39
+ /**
40
+ * CLI argument options specific to mem:delete
41
+ */
42
+ const CLI_OPTIONS = {
43
+ dryRun: {
44
+ name: 'dryRun',
45
+ flags: '--dry-run',
46
+ description: 'Preview deletion without making changes',
47
+ },
48
+ tag: {
49
+ name: 'tag',
50
+ flags: '--tag <tag>',
51
+ description: 'Delete all nodes matching this tag',
52
+ },
53
+ olderThan: {
54
+ name: 'olderThan',
55
+ flags: '--older-than <duration>',
56
+ description: 'Delete nodes older than duration (e.g., 30d, 7d, 24h, 2w)',
57
+ },
58
+ json: {
59
+ name: 'json',
60
+ flags: '--json',
61
+ description: 'Output as JSON',
62
+ },
63
+ quiet: {
64
+ name: 'quiet',
65
+ flags: '-q, --quiet',
66
+ description: 'Suppress output except errors',
67
+ },
68
+ baseDir: {
69
+ name: 'baseDir',
70
+ flags: '-b, --base-dir <path>',
71
+ description: 'Base directory (defaults to current directory)',
72
+ },
73
+ };
74
+ /**
75
+ * Write audit log entry for tool execution
76
+ *
77
+ * @param baseDir - Base directory
78
+ * @param entry - Audit log entry
79
+ */
80
+ async function writeAuditLog(baseDir, entry) {
81
+ try {
82
+ const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
83
+ const logDir = path.dirname(logPath);
84
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool creates known directory
85
+ await fs.mkdir(logDir, { recursive: true });
86
+ const line = `${JSON.stringify(entry)}\n`;
87
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes audit log
88
+ await fs.appendFile(logPath, line, 'utf-8');
89
+ }
90
+ catch {
91
+ // Audit logging is non-fatal - silently ignore errors
92
+ }
93
+ }
94
+ /**
95
+ * Parse CLI arguments and extract node IDs from positional args
96
+ *
97
+ * @returns Parsed args and node IDs
98
+ */
99
+ function parseArguments() {
100
+ const args = createWUParser({
101
+ name: 'mem-delete',
102
+ description: 'Delete memory nodes (soft delete via metadata.status=deleted)',
103
+ options: [
104
+ CLI_OPTIONS.dryRun,
105
+ CLI_OPTIONS.tag,
106
+ CLI_OPTIONS.olderThan,
107
+ CLI_OPTIONS.json,
108
+ CLI_OPTIONS.quiet,
109
+ CLI_OPTIONS.baseDir,
110
+ ],
111
+ required: [],
112
+ allowPositionalId: true,
113
+ });
114
+ // Extract positional arguments as node IDs
115
+ const nodeIds = [];
116
+ const argv = process.argv.slice(2).filter((arg) => arg !== '--');
117
+ for (const arg of argv) {
118
+ // Skip flags and their values
119
+ if (arg.startsWith('-')) {
120
+ continue;
121
+ }
122
+ // Check if this arg is a value for a flag (by checking previous arg)
123
+ const prevArg = argv[argv.indexOf(arg) - 1];
124
+ if (prevArg &&
125
+ prevArg.startsWith('-') &&
126
+ !prevArg.startsWith('--dry-run') &&
127
+ !prevArg.startsWith('--json') &&
128
+ !prevArg.startsWith('-q')) {
129
+ continue;
130
+ }
131
+ // Node IDs start with 'mem-'
132
+ if (arg.startsWith('mem-')) {
133
+ nodeIds.push(arg);
134
+ }
135
+ }
136
+ return { args, nodeIds };
137
+ }
138
+ /**
139
+ * Display limit for ID lists
140
+ */
141
+ const DISPLAY_LIMITS = {
142
+ DELETED_IDS: 10,
143
+ SKIPPED_IDS: 5,
144
+ };
145
+ /**
146
+ * Print a list of IDs with limit
147
+ */
148
+ function printIdList(ids, limit, label) {
149
+ console.log('');
150
+ console.log(label);
151
+ const displayIds = ids.slice(0, limit);
152
+ for (const id of displayIds) {
153
+ console.log(` - ${id}`);
154
+ }
155
+ if (ids.length > limit) {
156
+ console.log(` ... and ${ids.length - limit} more`);
157
+ }
158
+ }
159
+ /**
160
+ * Print errors list
161
+ */
162
+ function printErrors(errors) {
163
+ if (errors.length === 0)
164
+ return;
165
+ console.log('');
166
+ console.log('Errors:');
167
+ for (const error of errors) {
168
+ console.log(` - ${error}`);
169
+ }
170
+ }
171
+ /**
172
+ * Print deletion result to console
173
+ *
174
+ * @param result - Deletion result
175
+ * @param quiet - Suppress verbose output
176
+ */
177
+ function printResult(result, quiet) {
178
+ const statusMsg = result.dryRun
179
+ ? `${LOG_PREFIX} Dry-run: Would delete ${result.deletedCount} node(s)`
180
+ : `${LOG_PREFIX} Deletion complete`;
181
+ console.log(statusMsg);
182
+ if (quiet) {
183
+ console.log(`${result.deletedCount} deleted, ${result.skippedIds.length} skipped`);
184
+ return;
185
+ }
186
+ console.log('');
187
+ console.log('Summary:');
188
+ console.log(` Deleted: ${result.deletedCount} node(s)`);
189
+ console.log(` Skipped: ${result.skippedIds.length} node(s)`);
190
+ if (result.deletedIds.length > 0) {
191
+ const label = result.dryRun ? 'Would Delete:' : 'Deleted:';
192
+ printIdList(result.deletedIds, DISPLAY_LIMITS.DELETED_IDS, label);
193
+ }
194
+ if (result.skippedIds.length > 0) {
195
+ printIdList(result.skippedIds, DISPLAY_LIMITS.SKIPPED_IDS, 'Skipped (already deleted):');
196
+ }
197
+ printErrors(result.errors);
198
+ if (result.dryRun) {
199
+ console.log('');
200
+ console.log('To execute, run without --dry-run');
201
+ }
202
+ }
203
+ /**
204
+ * Build delete options from CLI arguments
205
+ */
206
+ function buildDeleteOptions(args, nodeIds) {
207
+ const options = {
208
+ dryRun: args.dryRun,
209
+ };
210
+ if (nodeIds.length > 0) {
211
+ options.nodeIds = nodeIds;
212
+ }
213
+ if (args.tag) {
214
+ options.tag = args.tag;
215
+ }
216
+ if (args.olderThan) {
217
+ options.olderThan = args.olderThan;
218
+ }
219
+ return options;
220
+ }
221
+ /**
222
+ * Print usage help and exit with error
223
+ */
224
+ function printUsageAndExit() {
225
+ console.error(`${LOG_PREFIX} Error: At least one filter is required`);
226
+ console.error('');
227
+ console.error('Usage:');
228
+ console.error(' pnpm mem:delete <node-id> Delete single node by ID');
229
+ console.error(' pnpm mem:delete <id1> <id2> Delete multiple nodes');
230
+ console.error(' pnpm mem:delete --tag <tag> Delete all nodes with tag');
231
+ console.error(' pnpm mem:delete --older-than 30d Delete nodes older than 30 days');
232
+ console.error(' pnpm mem:delete --dry-run Preview deletion');
233
+ process.exit(EXIT_CODES.ERROR);
234
+ }
235
+ /**
236
+ * Main CLI entry point
237
+ */
238
+ async function main() {
239
+ const { args, nodeIds } = parseArguments();
240
+ const baseDir = args.baseDir || process.cwd();
241
+ const startedAt = new Date().toISOString();
242
+ const startTime = Date.now();
243
+ const deleteOptions = buildDeleteOptions(args, nodeIds);
244
+ // Check if any filter is provided
245
+ if (!deleteOptions.nodeIds && !deleteOptions.tag && !deleteOptions.olderThan) {
246
+ printUsageAndExit();
247
+ }
248
+ let result = null;
249
+ let error = null;
250
+ try {
251
+ result = await deleteMemoryNodes(baseDir, deleteOptions);
252
+ }
253
+ catch (err) {
254
+ error = err instanceof Error ? err.message : String(err);
255
+ }
256
+ const durationMs = Date.now() - startTime;
257
+ await writeAuditLog(baseDir, {
258
+ tool: TOOL_NAME,
259
+ status: error ? 'failed' : 'success',
260
+ startedAt,
261
+ completedAt: new Date().toISOString(),
262
+ durationMs,
263
+ input: { baseDir, nodeIds, tag: args.tag, olderThan: args.olderThan, dryRun: args.dryRun },
264
+ output: result
265
+ ? {
266
+ success: result.success,
267
+ deletedCount: result.deletedCount,
268
+ deletedIds: result.deletedIds,
269
+ skippedCount: result.skippedIds.length,
270
+ dryRun: result.dryRun,
271
+ }
272
+ : null,
273
+ error: error ? { message: error } : null,
274
+ });
275
+ if (error) {
276
+ console.error(`${LOG_PREFIX} Error: ${error}`);
277
+ process.exit(EXIT_CODES.ERROR);
278
+ }
279
+ if (!result) {
280
+ console.error(`${LOG_PREFIX} Error: No result from delete operation`);
281
+ process.exit(EXIT_CODES.ERROR);
282
+ }
283
+ if (args.json) {
284
+ console.log(JSON.stringify(result, null, 2));
285
+ process.exit(result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
286
+ }
287
+ printResult(result, args.quiet);
288
+ process.exit(result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
289
+ }
290
+ main().catch((e) => {
291
+ console.error(`${LOG_PREFIX} ${e instanceof Error ? e.message : String(e)}`);
292
+ process.exit(EXIT_CODES.ERROR);
293
+ });
package/dist/mem-inbox.js CHANGED
@@ -14,8 +14,8 @@
14
14
  * Usage:
15
15
  * pnpm mem:inbox [--lane <name>] [--wu <id>] [--since <time>] [--watch]
16
16
  *
17
- * @see {@link tools/lib/mem-signal-core.mjs} - Core logic
18
- * @see {@link tools/__tests__/mem-inbox.test.mjs} - Tests
17
+ * @see {@link packages/@lumenflow/cli/src/lib/mem-signal-core.ts} - Core logic
18
+ * @see {@link packages/@lumenflow/cli/src/__tests__/mem-inbox.test.ts} - Tests
19
19
  */
20
20
  import fs from 'node:fs/promises';
21
21
  import path from 'node:path';