@lumenflow/cli 2.2.1 → 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.
- 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 +28 -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 +468 -116
- package/dist/guard-locked.js +4 -3
- package/dist/guard-worktree-commit.js +4 -3
- package/dist/init.js +508 -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/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 +7 -84
- package/dist/validate-skills-spec.js +4 -3
- package/dist/validate.js +7 -107
- 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 +60 -24
- 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,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
|
|
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
|
|
14
|
-
* @see {@link
|
|
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';
|
package/dist/mem-signal.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* Usage:
|
|
11
11
|
* pnpm mem:signal 'message' [--wu <id>] [--lane <name>] [--quiet]
|
|
12
12
|
*
|
|
13
|
-
* @see {@link
|
|
14
|
-
* @see {@link
|
|
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';
|