@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
770 lines (765 loc) • 29.4 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { Command } from "commander";
import chalk from "chalk";
import ora from "ora";
import {
initializeUnifiedOrchestrator
} from "../../skills/unified-rlm-orchestrator.js";
import { DualStackManager } from "../../core/context/dual-stack-manager.js";
import { FrameHandoffManager } from "../../core/context/frame-handoff-manager.js";
import { FrameManager } from "../../core/context/index.js";
import { ContextRetriever } from "../../core/retrieval/context-retriever.js";
import { SQLiteAdapter } from "../../core/database/sqlite-adapter.js";
import { LinearTaskManager } from "../../features/tasks/linear-task-manager.js";
import { ConfigManager } from "../../core/config/config-manager.js";
import * as path from "path";
import * as os from "os";
import {
SystemError,
DatabaseError,
ErrorCode
} from "../../core/errors/index.js";
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
let _version;
function getVersion() {
if (_version) return _version;
try {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
let dir = __dirname;
for (let i = 0; i < 6; i++) {
const candidate = path.join(dir, "package.json");
try {
_version = JSON.parse(readFileSync(candidate, "utf-8")).version;
return _version;
} catch {
dir = path.dirname(dir);
}
}
} catch {
}
_version = "0.0.0";
return _version;
}
function getEnv(key, defaultValue) {
const value = process.env[key];
if (value === void 0) {
if (defaultValue !== void 0) return defaultValue;
throw new SystemError(
`Environment variable ${key} is required`,
ErrorCode.CONFIGURATION_ERROR,
{ variable: key }
);
}
return value;
}
function getOptionalEnv(key) {
return process.env[key];
}
async function initializeSkillContext() {
const config = ConfigManager.getInstance();
const projectId = config.get("project.id") || "default-project";
const userId = config.get("user.id") || process.env["USER"] || "default";
const dbPath = path.join(
os.homedir(),
".stackmemory",
"data",
projectId,
"stackmemory.db"
);
const database = new SQLiteAdapter(projectId, { dbPath });
await database.connect();
const rawDatabase = database.getRawDatabase();
if (!rawDatabase) {
throw new DatabaseError(
"Failed to get raw database connection",
ErrorCode.DB_CONNECTION_FAILED,
{ projectId, operation: "initializeSkillContext" }
);
}
if (typeof rawDatabase.exec !== "function") {
throw new DatabaseError(
`Invalid database instance: missing exec() method. Got: ${typeof rawDatabase.exec}`,
ErrorCode.DB_CONNECTION_FAILED,
{ projectId, operation: "initializeSkillContext" }
);
}
try {
rawDatabase.exec("SELECT 1");
} catch (err) {
throw new DatabaseError(
`Database connection test failed: ${err.message}`,
ErrorCode.DB_CONNECTION_FAILED,
{ projectId, operation: "initializeSkillContext" },
err
);
}
const dualStackManager = new DualStackManager(database, projectId, userId);
const handoffManager = new FrameHandoffManager(dualStackManager);
const contextRetriever = new ContextRetriever(database);
const frameManager = new FrameManager(rawDatabase, projectId);
const taskStore = new LinearTaskManager();
const context = {
projectId,
userId,
dualStackManager,
handoffManager,
contextRetriever,
database,
frameManager
};
const unifiedOrchestrator = initializeUnifiedOrchestrator(
frameManager,
dualStackManager,
contextRetriever,
taskStore,
context
);
return { context, unifiedOrchestrator };
}
function createSkillsCommand() {
const skillsCmd = new Command("skills").description(
"Execute Claude skills for enhanced workflow"
);
skillsCmd.command("handoff <targetUser> <message>").description("Streamline frame handoffs between team members").option(
"-p, --priority <level>",
"Set priority (low, medium, high, critical)",
"medium"
).option("-f, --frames <frames...>", "Specific frames to handoff").option("--no-auto-detect", "Disable auto-detection of frames").action(async (targetUser, message, options) => {
const spinner = ora("Initiating handoff...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"handoff",
[targetUser, message],
{
priority: options.priority,
frames: options.frames,
autoDetect: options.autoDetect !== false
}
);
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
if (result.data) {
console.log(chalk.cyan("\nHandoff Details:"));
console.log(` ID: ${result.data.handoffId}`);
console.log(` Frames: ${result.data.frameCount}`);
console.log(` Priority: ${result.data.priority}`);
if (result.data.actionItems?.length > 0) {
console.log(chalk.yellow("\n Action Items:"));
result.data.actionItems.forEach((item) => {
console.log(` \u2022 ${item}`);
});
}
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
const checkpointCmd = skillsCmd.command("checkpoint").description("Create and manage recovery points");
checkpointCmd.command("create <description>").description("Create a new checkpoint").option("--files <files...>", "Include specific files in checkpoint").option("--auto-detect-risky", "Auto-detect risky operations").action(async (description, options) => {
const spinner = ora("Creating checkpoint...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"checkpoint",
["create", description],
{
includeFiles: options.files,
autoDetectRisky: options.autoDetectRisky
}
);
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
if (result.data) {
console.log(chalk.cyan("\nCheckpoint Info:"));
console.log(` ID: ${result.data.checkpointId}`);
console.log(` Time: ${result.data.timestamp}`);
console.log(` Frames: ${result.data.frameCount}`);
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
checkpointCmd.command("restore <checkpointId>").description("Restore from a checkpoint").action(async (checkpointId) => {
const spinner = ora("Restoring checkpoint...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill("checkpoint", [
"restore",
checkpointId
]);
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
if (result.data) {
console.log(chalk.cyan("\nRestored:"));
console.log(` Frames: ${result.data.frameCount}`);
console.log(` Files: ${result.data.filesRestored}`);
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
checkpointCmd.command("list").description("List available checkpoints").option("-l, --limit <number>", "Limit number of results", "10").option("-s, --since <date>", "Show checkpoints since date").action(async (options) => {
const spinner = ora("Loading checkpoints...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"checkpoint",
["list"],
{
limit: parseInt(options.limit),
since: options.since ? new Date(options.since) : void 0
}
);
spinner.stop();
if (result.success) {
console.log(chalk.cyan("Available Checkpoints:\n"));
if (result.data && result.data.length > 0) {
result.data.forEach((cp) => {
const riskIndicator = cp.risky ? chalk.yellow(" [RISKY]") : "";
console.log(`${chalk.bold(cp.id)}${riskIndicator}`);
console.log(` ${cp.description}`);
console.log(
chalk.gray(` ${cp.timestamp} (${cp.frameCount} frames)
`)
);
});
} else {
console.log(chalk.gray("No checkpoints found"));
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
checkpointCmd.command("diff <checkpoint1> <checkpoint2>").description("Show differences between two checkpoints").action(async (checkpoint1, checkpoint2) => {
const spinner = ora("Comparing checkpoints...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill("checkpoint", [
"diff",
checkpoint1,
checkpoint2
]);
spinner.stop();
if (result.success) {
console.log(chalk.cyan("Checkpoint Diff:\n"));
if (result.data) {
console.log(` Time difference: ${result.data.timeDiff}`);
console.log(` Frame difference: ${result.data.framesDiff}`);
console.log(` New frames: ${result.data.newFrames}`);
console.log(` Removed frames: ${result.data.removedFrames}`);
console.log(` Modified frames: ${result.data.modifiedFrames}`);
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
skillsCmd.command("spike").description(
"Run multi-agent spike (planner: Claude, implementer: Codex/Claude, critic: Claude)"
).option("-t, --task <desc>", "Task description", "Spike harness").option(
"--planner-model <name>",
"Claude model for planning",
"claude-sonnet-4-20250514"
).option(
"--reviewer-model <name>",
"Claude model for review",
"claude-sonnet-4-20250514"
).option("--implementer <name>", "codex|claude", "codex").option("--max-iters <n>", "Retry loop iterations", "2").option(
"--execute",
"Execute implementer (codex-sm) instead of dry-run",
false
).option("--audit-dir <path>", "Persist spike results to directory").option("--record-frame", "Record as real frame with anchors", false).option(
"--record",
"Record plan & critique into StackMemory context",
false
).option("--json", "Emit single JSON result (UI-friendly)", false).option("--quiet", "Minimal output (default)", true).option("--verbose", "Verbose sectioned output", false).action(async (options) => {
const spinner = ora("Planning with Claude...").start();
try {
const { runSpike } = await import("../../orchestrators/multimodal/harness.js");
const result = await runSpike(
{
task: options.task,
repoPath: process.cwd()
},
{
plannerModel: options.plannerModel,
reviewerModel: options.reviewerModel,
implementer: options.implementer,
maxIters: parseInt(options.maxIters),
dryRun: !options.execute,
auditDir: options.auditDir,
recordFrame: Boolean(options.recordFrame),
record: Boolean(options.record)
}
);
spinner.stop();
if (options.json) {
console.log(JSON.stringify(result));
} else if (options.verbose) {
console.log(chalk.gray(`StackMemory v${getVersion()}`));
console.log(chalk.cyan("\n=== Plan ==="));
console.log(JSON.stringify(result.plan, null, 2));
console.log(chalk.cyan("\n=== Iterations ==="));
(result.iterations || []).forEach((it, idx) => {
console.log(chalk.gray(`
-- Attempt ${idx + 1} --`));
console.log(`Command: ${it.command}`);
console.log(`OK: ${it.ok}`);
console.log("Critique:", JSON.stringify(it.critique));
});
console.log(chalk.cyan("\n=== Final ==="));
console.log(JSON.stringify(result.implementation, null, 2));
console.log(chalk.cyan("\n=== Critique ==="));
console.log(JSON.stringify(result.critique, null, 2));
} else if (!options.quiet) {
console.log(
`Plan steps: ${result.plan.steps.length}, Approved: ${result.critique.approved}`
);
}
if (!result.implementation.success) process.exitCode = 1;
} catch (error) {
spinner.stop();
console.error(chalk.red("Spike failed:"), error?.message || error);
process.exit(1);
}
});
skillsCmd.command("plan <task>").description("Generate an implementation plan (no code execution)").option(
"--planner-model <name>",
"Claude model for planning",
"claude-sonnet-4-20250514"
).option("--json", "Emit JSON (default)", true).option("--pretty", "Pretty-print JSON", false).option(
"--compact",
"Compact output (summary + step titles + criteria)",
false
).action(async (task, options) => {
const spinner = ora("Planning with Claude...").start();
try {
const { runPlanOnly } = await import("../../orchestrators/multimodal/harness.js");
const plan = await runPlanOnly(
{ task, repoPath: process.cwd() },
{ plannerModel: options.plannerModel }
);
spinner.stop();
const compacted = options.compact ? {
summary: plan?.summary,
steps: Array.isArray(plan?.steps) ? plan.steps.map((s) => ({
id: s.id,
title: s.title,
acceptanceCriteria: s.acceptanceCriteria
})) : [],
risks: plan?.risks
} : plan;
const payload = JSON.stringify(compacted, null, options.pretty ? 2 : 0);
console.log(payload);
} catch (error) {
spinner.stop();
console.error(chalk.red("Plan failed:"), error?.message || error);
process.exit(1);
}
});
skillsCmd.command("dig <query>").description("Deep historical context retrieval").option(
"-d, --depth <depth>",
"Search depth (e.g., 30days, 6months, all)",
"30days"
).option("--patterns", "Extract patterns from results").option("--decisions", "Extract key decisions").option("--timeline", "Generate activity timeline").action(async (query, options) => {
const spinner = ora("Digging through context...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill("dig", [query], {
depth: options.depth,
patterns: options.patterns,
decisions: options.decisions,
timeline: options.timeline
});
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
if (result.data) {
console.log(
chalk.cyan(
`
Searched ${result.data.timeRange.from} to ${result.data.timeRange.to}`
)
);
if (result.data.summary) {
console.log("\n" + result.data.summary);
} else {
if (result.data.topResults?.length > 0) {
console.log(chalk.cyan("\nTop Results:"));
result.data.topResults.forEach((r) => {
console.log(
` ${chalk.yellow(`[${r.score.toFixed(2)}]`)} ${r.summary}`
);
});
}
if (result.data.patterns?.length > 0) {
console.log(chalk.cyan("\nDetected Patterns:"));
result.data.patterns.forEach((p) => {
console.log(` ${p.name}: ${p.count} occurrences`);
});
}
if (result.data.decisions?.length > 0) {
console.log(chalk.cyan("\nKey Decisions:"));
result.data.decisions.slice(0, 5).forEach((d) => {
console.log(
` ${chalk.gray(new Date(d.timestamp).toLocaleDateString())}: ${d.decision}`
);
});
}
if (result.data.timeline?.length > 0) {
console.log(chalk.cyan("\nActivity Timeline:"));
result.data.timeline.slice(0, 5).forEach((t) => {
console.log(` ${t.date}: ${t.itemCount} activities`);
});
}
}
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
skillsCmd.command("rlm <task>").description("Execute complex tasks with recursive agent orchestration").option("--max-parallel <number>", "Maximum concurrent subagents", "5").option("--max-recursion <number>", "Maximum recursion depth", "4").option(
"--max-tokens-per-agent <number>",
"Token budget per subagent",
"30000"
).option("--review-stages <number>", "Number of review iterations", "3").option(
"--quality-threshold <number>",
"Target quality score (0-1)",
"0.85"
).option(
"--test-mode <mode>",
"Test generation mode (unit/integration/e2e/all)",
"all"
).option("--verbose", "Show all recursive operations", false).option(
"--share-context-realtime",
"Share discoveries between agents",
true
).option("--retry-failed-agents", "Retry on failure", true).option("--timeout-per-agent <number>", "Timeout in seconds", "300").action(async (task, options) => {
const spinner = ora("Initializing RLM orchestrator...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
spinner.text = "Decomposing task...";
const result = await unifiedOrchestrator.executeSkill("rlm", [task], {
maxParallel: parseInt(options.maxParallel),
maxRecursionDepth: parseInt(options.maxRecursion),
maxTokensPerAgent: parseInt(options.maxTokensPerAgent),
reviewStages: parseInt(options.reviewStages),
qualityThreshold: parseFloat(options.qualityThreshold),
testGenerationMode: options.testMode,
verboseLogging: options.verbose,
shareContextRealtime: options.shareContextRealtime,
retryFailedAgents: options.retryFailedAgents,
timeoutPerAgent: parseInt(options.timeoutPerAgent) * 1e3
});
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), "RLM execution completed");
if (result.data) {
console.log(chalk.cyan("\nExecution Summary:"));
console.log(` Total tokens: ${result.data.totalTokens}`);
console.log(
` Estimated cost: $${result.data.totalCost.toFixed(2)}`
);
console.log(` Duration: ${result.data.duration}ms`);
console.log(` Tests generated: ${result.data.testsGenerated}`);
console.log(` Issues found: ${result.data.issuesFound}`);
console.log(` Issues fixed: ${result.data.issuesFixed}`);
if (result.data.improvements?.length > 0) {
console.log(chalk.cyan("\nImprovements:"));
result.data.improvements.forEach((imp) => {
console.log(` \u2022 ${imp}`);
});
}
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
const specCmd = skillsCmd.command("spec").description("Generate iterative spec documents");
specCmd.command("generate <type> <title>").description(
"Generate a spec document (one-pager, dev-spec, prompt-plan, agents)"
).action(async (type, title) => {
const spinner = ora(`Generating ${type}...`).start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"spec",
["generate", type, title],
{}
);
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
specCmd.command("list").description("List existing spec documents").action(async () => {
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"spec",
["list"],
{}
);
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
if (result.data) {
console.log(JSON.stringify(result.data, null, 2));
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
specCmd.command("validate <path>").description("Validate spec document completeness").action(async (filePath) => {
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"spec",
["validate", filePath],
{}
);
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
const linearRunCmd = skillsCmd.command("linear-run").description("Execute Linear tasks via RLM orchestrator");
linearRunCmd.command("next").description("Execute the next highest-priority Linear task").option("--priority <level>", "Filter by priority").option("--tag <tag>", "Filter by tag").option("--dry-run", "Preview without executing").action(async (options) => {
const spinner = ora("Fetching next task...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"linear-run",
["next"],
{
priority: options.priority,
tag: options.tag,
dryRun: options.dryRun
}
);
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
if (result.data) {
console.log(JSON.stringify(result.data, null, 2));
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
linearRunCmd.command("all").description("Execute all active Linear tasks iteratively").option("--max-concurrent <n>", "Max concurrent tasks", "1").option("--dry-run", "Preview without executing").action(async (options) => {
const spinner = ora("Running all tasks...").start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"linear-run",
["all"],
{
maxConcurrent: parseInt(options.maxConcurrent),
dryRun: options.dryRun
}
);
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
if (result.data) {
console.log(JSON.stringify(result.data, null, 2));
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
linearRunCmd.command("task <taskId>").description("Execute a specific Linear task by ID").action(async (taskId) => {
const spinner = ora(`Executing task ${taskId}...`).start();
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"linear-run",
["task", taskId],
{}
);
spinner.stop();
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
spinner.stop();
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
linearRunCmd.command("preview [taskId]").description("Show execution plan without running").action(async (taskId) => {
try {
const { context, unifiedOrchestrator } = await initializeSkillContext();
const result = await unifiedOrchestrator.executeSkill(
"linear-run",
["preview", taskId || ""],
{}
);
if (result.success) {
console.log(chalk.green("\u2713"), result.message);
if (result.data) {
console.log(JSON.stringify(result.data, null, 2));
}
} else {
console.log(chalk.red("\u2717"), result.message);
}
await context.database.disconnect();
} catch (error) {
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
skillsCmd.command("help [skill]").description("Show help for a specific skill").action(async (skill) => {
if (skill) {
switch (skill) {
case "lint":
console.log(`
lint (RLM-Orchestrated)
Primary Agent: linting
Secondary Agents: improve
Comprehensive linting of code: Check syntax, types, formatting, security, performance, and dead code. Provide fixes.
This skill is executed through RLM orchestration for:
- Automatic task decomposition
- Parallel agent execution
- Multi-stage quality review
- Comprehensive result aggregation
Usage:
stackmemory skills lint # Lint current directory
stackmemory skills lint src/ # Lint specific directory
stackmemory skills lint src/file.ts # Lint specific file
Options:
--fix Automatically fix issues where possible
--format Focus on formatting issues
--security Focus on security vulnerabilities
--performance Focus on performance issues
--verbose Show detailed output
`);
break;
default:
console.log(
`Unknown skill: ${skill}. Use "stackmemory skills help" to see all available skills.`
);
}
} else {
console.log(
chalk.cyan("Available Claude Skills (RLM-Orchestrated):\n")
);
console.log(
" handoff - Streamline frame handoffs between team members"
);
console.log(" checkpoint - Create and manage recovery points");
console.log(" dig - Deep historical context retrieval");
console.log(
" lint - Comprehensive code linting and quality checks"
);
console.log(" test - Generate comprehensive test suites");
console.log(" review - Multi-stage code review and improvements");
console.log(" refactor - Refactor code for better architecture");
console.log(" publish - Prepare and execute releases");
console.log(" rlm - Direct recursive agent orchestration");
console.log(
" spec - Generate iterative spec docs (one-pager, dev-spec, prompt-plan, agents)"
);
console.log(
" linear-run - Execute Linear tasks via RLM orchestrator\n"
);
console.log(
chalk.yellow(
"\nAll skills now use RLM orchestration for intelligent task decomposition"
)
);
console.log(
'Use "stackmemory skills help <skill>" for detailed help on each skill'
);
}
});
return skillsCmd;
}
export {
createSkillsCommand
};
//# sourceMappingURL=skills.js.map