@stackmemoryai/stackmemory 1.9.0 → 1.10.3
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/dist/src/cli/commands/orchestrator.js +27 -6
- package/dist/src/cli/commands/ralph.js +43 -0
- package/dist/src/cli/commands/rules.js +250 -0
- package/dist/src/cli/commands/skill.js +406 -0
- package/dist/src/cli/index.js +64 -0
- package/dist/src/core/config/config-manager.js +2 -1
- package/dist/src/core/rules/built-in-rules.js +289 -0
- package/dist/src/core/rules/pr-review-rule.js +87 -0
- package/dist/src/core/rules/rule-engine.js +85 -0
- package/dist/src/core/rules/rule-store.js +99 -0
- package/dist/src/core/rules/types.js +4 -0
- package/dist/src/core/skills/index.js +21 -0
- package/dist/src/core/skills/skill-matcher.js +178 -0
- package/dist/src/core/skills/skill-registry.js +646 -0
- package/dist/src/core/skills/types.js +1 -47
- package/dist/src/core/storage/obsidian-vault-adapter.js +392 -0
- package/dist/src/integrations/mcp/handlers/skill-handlers.js +67 -167
- package/dist/src/integrations/mcp/server.js +2 -6
- package/dist/src/integrations/ralph/loopmax.js +488 -0
- package/package.json +2 -2
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
3
|
+
import { dirname as __pathDirname } from 'path';
|
|
4
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = __pathDirname(__filename);
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import * as os from "os";
|
|
11
|
+
function parseFrontmatter(content) {
|
|
12
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
13
|
+
if (!match) {
|
|
14
|
+
throw new Error("No YAML frontmatter found (expected --- delimiters)");
|
|
15
|
+
}
|
|
16
|
+
const yamlBlock = match[1];
|
|
17
|
+
const body = match[2];
|
|
18
|
+
const fm = {};
|
|
19
|
+
let currentKey = null;
|
|
20
|
+
let currentArray = null;
|
|
21
|
+
for (const line of yamlBlock.split("\n")) {
|
|
22
|
+
const trimmed = line.trim();
|
|
23
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
24
|
+
if (trimmed.startsWith("- ") && currentKey && currentArray !== null) {
|
|
25
|
+
currentArray.push(trimmed.slice(2).trim());
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (currentKey && currentArray !== null) {
|
|
29
|
+
fm[currentKey] = currentArray;
|
|
30
|
+
currentArray = null;
|
|
31
|
+
currentKey = null;
|
|
32
|
+
}
|
|
33
|
+
const kvMatch = trimmed.match(/^(\w[\w-]*):\s*(.*)$/);
|
|
34
|
+
if (!kvMatch) continue;
|
|
35
|
+
const key = kvMatch[1];
|
|
36
|
+
const val = kvMatch[2].trim();
|
|
37
|
+
if (val === "" || val === "[]") {
|
|
38
|
+
currentKey = key;
|
|
39
|
+
currentArray = [];
|
|
40
|
+
} else if (val.startsWith("[") && val.endsWith("]")) {
|
|
41
|
+
fm[key] = val.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
|
|
42
|
+
} else {
|
|
43
|
+
fm[key] = val.replace(/^['"]|['"]$/g, "");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (currentKey && currentArray !== null) {
|
|
47
|
+
fm[currentKey] = currentArray;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
frontmatter: {
|
|
51
|
+
name: fm["name"] || "unknown",
|
|
52
|
+
version: fm["version"] || "0.0.0",
|
|
53
|
+
domain: fm["domain"] || "general",
|
|
54
|
+
expires: fm["expires"] || "",
|
|
55
|
+
activates_on: fm["activates_on"] || [],
|
|
56
|
+
sources: fm["sources"] || [],
|
|
57
|
+
context7: fm["context7"]
|
|
58
|
+
},
|
|
59
|
+
body
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function getSkillDirs() {
|
|
63
|
+
const dirs = [];
|
|
64
|
+
const globalDir = path.join(os.homedir(), ".claude", "skills", "knowledge");
|
|
65
|
+
if (fs.existsSync(globalDir)) {
|
|
66
|
+
dirs.push({ dir: globalDir, scope: "global" });
|
|
67
|
+
}
|
|
68
|
+
const projectDir = path.join(process.cwd(), ".claude", "skills", "knowledge");
|
|
69
|
+
if (fs.existsSync(projectDir)) {
|
|
70
|
+
dirs.push({ dir: projectDir, scope: "project" });
|
|
71
|
+
}
|
|
72
|
+
return dirs;
|
|
73
|
+
}
|
|
74
|
+
function discoverSkills() {
|
|
75
|
+
const skills = [];
|
|
76
|
+
const now = /* @__PURE__ */ new Date();
|
|
77
|
+
for (const { dir, scope } of getSkillDirs()) {
|
|
78
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".skill.md"));
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
const filePath = path.join(dir, file);
|
|
81
|
+
try {
|
|
82
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
83
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
84
|
+
const stale = frontmatter.expires ? new Date(frontmatter.expires) < now : false;
|
|
85
|
+
skills.push({ path: filePath, scope, frontmatter, body, stale });
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return skills;
|
|
91
|
+
}
|
|
92
|
+
function matchSkills(prompt, skills) {
|
|
93
|
+
const words = new Set(prompt.toLowerCase().split(/\s+/));
|
|
94
|
+
const results = [];
|
|
95
|
+
for (const skill of skills) {
|
|
96
|
+
const matched = [];
|
|
97
|
+
for (const keyword of skill.frontmatter.activates_on) {
|
|
98
|
+
const kw = keyword.toLowerCase();
|
|
99
|
+
if (words.has(kw) || prompt.toLowerCase().includes(kw)) {
|
|
100
|
+
matched.push(keyword);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (matched.length > 0) {
|
|
104
|
+
const ratio = matched.length / skill.frontmatter.activates_on.length;
|
|
105
|
+
const scopeBoost = skill.scope === "project" ? 0.1 : 0;
|
|
106
|
+
const stalepenalty = skill.stale ? -0.2 : 0;
|
|
107
|
+
results.push({
|
|
108
|
+
skill,
|
|
109
|
+
score: Math.min(1, ratio + scopeBoost + stalepenalty),
|
|
110
|
+
matchedKeywords: matched
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return results.sort((a, b) => b.score - a.score);
|
|
115
|
+
}
|
|
116
|
+
function createSkillCommand() {
|
|
117
|
+
const cmd = new Command("skill").description(
|
|
118
|
+
"Manage knowledge skills (.skill.md) \u2014 domain expertise for AI agents"
|
|
119
|
+
);
|
|
120
|
+
cmd.command("list").description("List all discovered knowledge skills").option("--stale", "Show only stale (expired) skills").option("--json", "Output as JSON").action((options) => {
|
|
121
|
+
let skills = discoverSkills();
|
|
122
|
+
if (options.stale) {
|
|
123
|
+
skills = skills.filter((s) => s.stale);
|
|
124
|
+
}
|
|
125
|
+
if (options.json) {
|
|
126
|
+
const out = skills.map((s) => ({
|
|
127
|
+
name: s.frontmatter.name,
|
|
128
|
+
domain: s.frontmatter.domain,
|
|
129
|
+
version: s.frontmatter.version,
|
|
130
|
+
scope: s.scope,
|
|
131
|
+
expires: s.frontmatter.expires,
|
|
132
|
+
stale: s.stale,
|
|
133
|
+
keywords: s.frontmatter.activates_on.length,
|
|
134
|
+
path: s.path
|
|
135
|
+
}));
|
|
136
|
+
console.log(JSON.stringify(out, null, 2));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (skills.length === 0) {
|
|
140
|
+
console.log(chalk.yellow("No knowledge skills found."));
|
|
141
|
+
console.log(` Global: ~/.claude/skills/knowledge/*.skill.md`);
|
|
142
|
+
console.log(` Project: .claude/skills/knowledge/*.skill.md`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
console.log(chalk.bold(`
|
|
146
|
+
Knowledge Skills (${skills.length})
|
|
147
|
+
`));
|
|
148
|
+
for (const s of skills) {
|
|
149
|
+
const status = s.stale ? chalk.red("expired") : chalk.green("active");
|
|
150
|
+
const scope = s.scope === "global" ? chalk.dim("global") : chalk.cyan("project");
|
|
151
|
+
const keywords = chalk.dim(
|
|
152
|
+
`[${s.frontmatter.activates_on.slice(0, 5).join(", ")}${s.frontmatter.activates_on.length > 5 ? "..." : ""}]`
|
|
153
|
+
);
|
|
154
|
+
console.log(
|
|
155
|
+
` ${chalk.bold(s.frontmatter.name.padEnd(20))} ${s.frontmatter.domain.padEnd(12)} ${s.frontmatter.version.padEnd(14)} ${scope.padEnd(18)} ${status} ${keywords}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const staleCount = skills.filter((s) => s.stale).length;
|
|
159
|
+
if (staleCount > 0) {
|
|
160
|
+
console.log(
|
|
161
|
+
chalk.yellow(
|
|
162
|
+
`
|
|
163
|
+
${staleCount} skill(s) expired \u2014 run ${chalk.bold("stackmemory skill refresh")} to update`
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
console.log();
|
|
168
|
+
});
|
|
169
|
+
cmd.command("match <prompt>").description("Find skills matching a prompt (keyword match)").option("-n, --limit <n>", "Max results", "5").option("--json", "Output as JSON").action((prompt, options) => {
|
|
170
|
+
const skills = discoverSkills();
|
|
171
|
+
const matches = matchSkills(prompt, skills).slice(
|
|
172
|
+
0,
|
|
173
|
+
parseInt(options.limit, 10)
|
|
174
|
+
);
|
|
175
|
+
if (options.json) {
|
|
176
|
+
const out = matches.map((m) => ({
|
|
177
|
+
name: m.skill.frontmatter.name,
|
|
178
|
+
domain: m.skill.frontmatter.domain,
|
|
179
|
+
score: Math.round(m.score * 100),
|
|
180
|
+
stale: m.skill.stale,
|
|
181
|
+
matchedKeywords: m.matchedKeywords,
|
|
182
|
+
path: m.skill.path
|
|
183
|
+
}));
|
|
184
|
+
console.log(JSON.stringify(out, null, 2));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (matches.length === 0) {
|
|
188
|
+
console.log(chalk.yellow(`No skills match: "${prompt}"`));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
console.log(chalk.bold(`
|
|
192
|
+
Matches for: "${prompt}"
|
|
193
|
+
`));
|
|
194
|
+
for (const m of matches) {
|
|
195
|
+
const pct = Math.round(m.score * 100);
|
|
196
|
+
const bar = pct >= 50 ? chalk.green(`${pct}%`) : chalk.yellow(`${pct}%`);
|
|
197
|
+
const staleTag = m.skill.stale ? chalk.red(" [stale]") : "";
|
|
198
|
+
console.log(
|
|
199
|
+
` ${bar.padEnd(16)} ${chalk.bold(m.skill.frontmatter.name.padEnd(20))} ${chalk.dim(m.matchedKeywords.join(", "))}${staleTag}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
console.log();
|
|
203
|
+
});
|
|
204
|
+
cmd.command("show <name>").description("Display a skill's full content").action((name) => {
|
|
205
|
+
const skills = discoverSkills();
|
|
206
|
+
const skill = skills.find((s) => s.frontmatter.name === name);
|
|
207
|
+
if (!skill) {
|
|
208
|
+
console.error(chalk.red(`Skill not found: ${name}`));
|
|
209
|
+
console.log(
|
|
210
|
+
`Available: ${skills.map((s) => s.frontmatter.name).join(", ")}`
|
|
211
|
+
);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
const status = skill.stale ? chalk.red("EXPIRED") : chalk.green("active");
|
|
215
|
+
console.log(
|
|
216
|
+
chalk.bold(`
|
|
217
|
+
${skill.frontmatter.name}`) + ` (${skill.frontmatter.domain}) ${status}`
|
|
218
|
+
);
|
|
219
|
+
console.log(
|
|
220
|
+
chalk.dim(
|
|
221
|
+
` v${skill.frontmatter.version} | expires ${skill.frontmatter.expires} | ${skill.scope}`
|
|
222
|
+
)
|
|
223
|
+
);
|
|
224
|
+
console.log(chalk.dim(` ${skill.path}`));
|
|
225
|
+
if (skill.frontmatter.context7) {
|
|
226
|
+
console.log(chalk.dim(` context7: ${skill.frontmatter.context7}`));
|
|
227
|
+
}
|
|
228
|
+
console.log(
|
|
229
|
+
chalk.dim(` keywords: ${skill.frontmatter.activates_on.join(", ")}`)
|
|
230
|
+
);
|
|
231
|
+
console.log(chalk.dim(` sources:`));
|
|
232
|
+
for (const src of skill.frontmatter.sources) {
|
|
233
|
+
console.log(chalk.dim(` - ${src}`));
|
|
234
|
+
}
|
|
235
|
+
console.log();
|
|
236
|
+
console.log(skill.body);
|
|
237
|
+
});
|
|
238
|
+
cmd.command("add <source>").description("Add a knowledge skill from a local file or URL").option(
|
|
239
|
+
"--global",
|
|
240
|
+
"Install to ~/.claude/skills/knowledge/ (default: project)"
|
|
241
|
+
).option("--name <name>", "Override skill name").action(
|
|
242
|
+
async (source, options) => {
|
|
243
|
+
let content = "";
|
|
244
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
245
|
+
try {
|
|
246
|
+
const res = await fetch(source);
|
|
247
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
248
|
+
content = await res.text();
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error(chalk.red(`Failed to fetch: ${source}`));
|
|
251
|
+
console.error(err.message);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
const resolved = path.resolve(source);
|
|
257
|
+
if (!fs.existsSync(resolved)) {
|
|
258
|
+
console.error(chalk.red(`File not found: ${resolved}`));
|
|
259
|
+
process.exit(1);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
content = fs.readFileSync(resolved, "utf-8");
|
|
263
|
+
}
|
|
264
|
+
let parsed;
|
|
265
|
+
try {
|
|
266
|
+
parsed = parseFrontmatter(content);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.error(
|
|
269
|
+
chalk.red(`Invalid skill file: ${err.message}`)
|
|
270
|
+
);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (!parsed) return;
|
|
275
|
+
const skillName = options.name ?? parsed.frontmatter.name;
|
|
276
|
+
const targetDir = options.global ? path.join(os.homedir(), ".claude", "skills", "knowledge") : path.join(process.cwd(), ".claude", "skills", "knowledge");
|
|
277
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
278
|
+
const targetPath = path.join(targetDir, `${skillName}.skill.md`);
|
|
279
|
+
const exists = fs.existsSync(targetPath);
|
|
280
|
+
fs.writeFileSync(targetPath, content, "utf-8");
|
|
281
|
+
const action = exists ? "Updated" : "Added";
|
|
282
|
+
const scope = options.global ? "global" : "project";
|
|
283
|
+
console.log(
|
|
284
|
+
chalk.green(`${action} ${chalk.bold(skillName)} (${scope})`)
|
|
285
|
+
);
|
|
286
|
+
console.log(chalk.dim(` ${targetPath}`));
|
|
287
|
+
console.log(
|
|
288
|
+
chalk.dim(
|
|
289
|
+
` domain: ${parsed.frontmatter.domain} | keywords: ${parsed.frontmatter.activates_on.length} | expires: ${parsed.frontmatter.expires}`
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
cmd.command("refresh [name]").description("Check freshness and show stale skills that need updating").option("--fetch", "Attempt to fetch source URLs and show diff summary").action(async (name, options) => {
|
|
295
|
+
let skills = discoverSkills();
|
|
296
|
+
if (name) {
|
|
297
|
+
skills = skills.filter((s) => s.frontmatter.name === name);
|
|
298
|
+
if (skills.length === 0) {
|
|
299
|
+
console.error(chalk.red(`Skill not found: ${name}`));
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const stale = skills.filter((s) => s.stale);
|
|
304
|
+
const fresh = skills.filter((s) => !s.stale);
|
|
305
|
+
console.log(chalk.bold(`
|
|
306
|
+
Skill Freshness Report
|
|
307
|
+
`));
|
|
308
|
+
console.log(
|
|
309
|
+
` ${chalk.green(String(fresh.length))} active ${chalk.red(String(stale.length))} expired ${skills.length} total
|
|
310
|
+
`
|
|
311
|
+
);
|
|
312
|
+
if (stale.length === 0) {
|
|
313
|
+
console.log(chalk.green(" All skills are fresh."));
|
|
314
|
+
console.log();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
for (const s of stale) {
|
|
318
|
+
const daysPast = Math.floor(
|
|
319
|
+
(Date.now() - new Date(s.frontmatter.expires).getTime()) / 864e5
|
|
320
|
+
);
|
|
321
|
+
console.log(
|
|
322
|
+
` ${chalk.red("expired")} ${chalk.bold(s.frontmatter.name.padEnd(20))} ${chalk.dim(`${daysPast}d ago`)} ${s.scope}`
|
|
323
|
+
);
|
|
324
|
+
if (s.frontmatter.context7) {
|
|
325
|
+
console.log(
|
|
326
|
+
chalk.dim(` context7: ${s.frontmatter.context7}`)
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
for (const src of s.frontmatter.sources) {
|
|
330
|
+
console.log(chalk.dim(` source: ${src}`));
|
|
331
|
+
}
|
|
332
|
+
if (options.fetch && s.frontmatter.sources.length > 0) {
|
|
333
|
+
const url = s.frontmatter.sources[0];
|
|
334
|
+
try {
|
|
335
|
+
const res = await fetch(url, { method: "HEAD" });
|
|
336
|
+
console.log(
|
|
337
|
+
res.ok ? chalk.green(` ${url} \u2014 reachable (${res.status})`) : chalk.yellow(` ${url} \u2014 ${res.status}`)
|
|
338
|
+
);
|
|
339
|
+
} catch {
|
|
340
|
+
console.log(chalk.red(` ${url} \u2014 unreachable`));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
console.log(
|
|
345
|
+
chalk.yellow(
|
|
346
|
+
`
|
|
347
|
+
To update: edit the .skill.md file and bump the version + expires date.`
|
|
348
|
+
)
|
|
349
|
+
);
|
|
350
|
+
if (!options.fetch) {
|
|
351
|
+
console.log(
|
|
352
|
+
chalk.dim(` Run with --fetch to check source URL reachability.`)
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
console.log();
|
|
356
|
+
});
|
|
357
|
+
cmd.command("init <name>").description("Scaffold a new .skill.md file").option("--global", "Create in ~/.claude/skills/knowledge/").option("--domain <domain>", "Skill domain", "general").action((name, options) => {
|
|
358
|
+
const targetDir = options.global ? path.join(os.homedir(), ".claude", "skills", "knowledge") : path.join(process.cwd(), ".claude", "skills", "knowledge");
|
|
359
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
360
|
+
const targetPath = path.join(targetDir, `${name}.skill.md`);
|
|
361
|
+
if (fs.existsSync(targetPath)) {
|
|
362
|
+
console.error(chalk.red(`Already exists: ${targetPath}`));
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
const sixMonths = /* @__PURE__ */ new Date();
|
|
366
|
+
sixMonths.setMonth(sixMonths.getMonth() + 6);
|
|
367
|
+
const expiresDate = sixMonths.toISOString().slice(0, 10);
|
|
368
|
+
const template = `---
|
|
369
|
+
name: ${name}
|
|
370
|
+
version: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, ".")}
|
|
371
|
+
domain: ${options.domain}
|
|
372
|
+
expires: ${expiresDate}
|
|
373
|
+
activates_on: [${name}]
|
|
374
|
+
sources:
|
|
375
|
+
- https://docs.example.com
|
|
376
|
+
context7:
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
# ${name.charAt(0).toUpperCase() + name.slice(1).replace(/-/g, " ")}
|
|
380
|
+
|
|
381
|
+
## Current SDK & Versions
|
|
382
|
+
- TODO: add current versions
|
|
383
|
+
|
|
384
|
+
## Preferred Patterns
|
|
385
|
+
- TODO: document the right way to do things
|
|
386
|
+
|
|
387
|
+
## Gotchas
|
|
388
|
+
- TODO: things LLMs consistently get wrong
|
|
389
|
+
|
|
390
|
+
## Entry Points
|
|
391
|
+
- TODO: canonical doc URLs
|
|
392
|
+
`;
|
|
393
|
+
fs.writeFileSync(targetPath, template, "utf-8");
|
|
394
|
+
console.log(chalk.green(`Created ${chalk.bold(name)} skill`));
|
|
395
|
+
console.log(chalk.dim(` ${targetPath}`));
|
|
396
|
+
console.log(
|
|
397
|
+
chalk.dim(
|
|
398
|
+
` Edit the file to add domain knowledge, then update activates_on keywords.`
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
return cmd;
|
|
403
|
+
}
|
|
404
|
+
export {
|
|
405
|
+
createSkillCommand
|
|
406
|
+
};
|
package/dist/src/cli/index.js
CHANGED
|
@@ -59,8 +59,10 @@ import { createTeamCommands } from "./commands/team.js";
|
|
|
59
59
|
import { createDesiresCommands } from "./commands/desires.js";
|
|
60
60
|
import { createConductorCommands } from "./commands/orchestrate.js";
|
|
61
61
|
import { createPreflightCommand } from "./commands/preflight.js";
|
|
62
|
+
import { createRulesCommand } from "./commands/rules.js";
|
|
62
63
|
import { createSnapshotCommand } from "./commands/snapshot.js";
|
|
63
64
|
import { createLoopCommand } from "./commands/loop.js";
|
|
65
|
+
import { createSkillCommand } from "./commands/skill.js";
|
|
64
66
|
import chalk from "chalk";
|
|
65
67
|
import * as fs from "fs";
|
|
66
68
|
import * as path from "path";
|
|
@@ -196,6 +198,8 @@ program.command("status").description("Show current StackMemory status").option(
|
|
|
196
198
|
await UpdateChecker.checkForUpdates(VERSION);
|
|
197
199
|
await sessionManager.initialize();
|
|
198
200
|
await sharedContextLayer.initialize();
|
|
201
|
+
const { initObsidianVault } = await import("../core/storage/obsidian-vault-adapter.js");
|
|
202
|
+
await initObsidianVault();
|
|
199
203
|
const session = await sessionManager.getOrCreateSession({
|
|
200
204
|
projectPath: projectRoot,
|
|
201
205
|
sessionId: options.session
|
|
@@ -478,6 +482,64 @@ program.addCommand(createMemoryCommand());
|
|
|
478
482
|
program.addCommand(clearCommand);
|
|
479
483
|
program.addCommand(serviceCommand);
|
|
480
484
|
program.addCommand(createHooksCommand());
|
|
485
|
+
program.command("board").description("Open the StackMemory Board (agent kanban + diff viewer)").option("-p, --port <port>", "Port to serve on", "3456").option("--no-open", "Do not auto-open browser").action(async (options) => {
|
|
486
|
+
const { spawn: spawnProc } = await import("child_process");
|
|
487
|
+
const { join: join2 } = await import("path");
|
|
488
|
+
const { existsSync: existsSync2 } = await import("fs");
|
|
489
|
+
const { fileURLToPath: toPath } = await import("url");
|
|
490
|
+
const pkgRoot = join2(
|
|
491
|
+
toPath(import.meta.url),
|
|
492
|
+
"..",
|
|
493
|
+
"..",
|
|
494
|
+
"..",
|
|
495
|
+
"..",
|
|
496
|
+
"tools",
|
|
497
|
+
"agent-viewer",
|
|
498
|
+
"server.cjs"
|
|
499
|
+
);
|
|
500
|
+
const candidates = [
|
|
501
|
+
join2(process.cwd(), "tools", "agent-viewer", "server.js"),
|
|
502
|
+
pkgRoot,
|
|
503
|
+
join2(
|
|
504
|
+
process.env.HOME || "",
|
|
505
|
+
"Dev",
|
|
506
|
+
"stackmemory",
|
|
507
|
+
"tools",
|
|
508
|
+
"agent-viewer",
|
|
509
|
+
"server.js"
|
|
510
|
+
)
|
|
511
|
+
];
|
|
512
|
+
const serverPath = candidates.find((c) => existsSync2(c));
|
|
513
|
+
if (!serverPath) {
|
|
514
|
+
console.error(
|
|
515
|
+
"Board server not found. Run from the stackmemory repo or install globally."
|
|
516
|
+
);
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
console.log(`Starting StackMemory Board on port ${options.port}...`);
|
|
520
|
+
const child = spawnProc("node", [serverPath, "--port", options.port], {
|
|
521
|
+
stdio: "inherit",
|
|
522
|
+
env: { ...process.env, FORCE_COLOR: "1" }
|
|
523
|
+
});
|
|
524
|
+
if (options.open !== false) {
|
|
525
|
+
setTimeout(async () => {
|
|
526
|
+
const url = `http://localhost:${options.port}`;
|
|
527
|
+
const cp = await import("child_process");
|
|
528
|
+
try {
|
|
529
|
+
cp.execSync(
|
|
530
|
+
process.platform === "darwin" ? `open ${url}` : `xdg-open ${url}`,
|
|
531
|
+
{ stdio: "ignore" }
|
|
532
|
+
);
|
|
533
|
+
} catch {
|
|
534
|
+
}
|
|
535
|
+
}, 1e3);
|
|
536
|
+
}
|
|
537
|
+
child.on("close", (code) => process.exit(code || 0));
|
|
538
|
+
process.on("SIGINT", () => {
|
|
539
|
+
child.kill("SIGINT");
|
|
540
|
+
process.exit(0);
|
|
541
|
+
});
|
|
542
|
+
});
|
|
481
543
|
const lazyCommands = [];
|
|
482
544
|
if (isFeatureEnabled("skills")) {
|
|
483
545
|
lazyCommands.push(
|
|
@@ -515,6 +577,8 @@ program.addCommand(createConductorCommands());
|
|
|
515
577
|
program.addCommand(createPreflightCommand());
|
|
516
578
|
program.addCommand(createSnapshotCommand());
|
|
517
579
|
program.addCommand(createLoopCommand());
|
|
580
|
+
program.addCommand(createRulesCommand());
|
|
581
|
+
program.addCommand(createSkillCommand());
|
|
518
582
|
registerSetupCommands(program);
|
|
519
583
|
program.command("mm-spike").description(
|
|
520
584
|
"Run multi-agent planning/implementation spike (planner/implementer/critic)"
|
|
@@ -77,7 +77,8 @@ class ConfigManager {
|
|
|
77
77
|
},
|
|
78
78
|
performance: { ...DEFAULT_CONFIG.performance, ...loaded.performance },
|
|
79
79
|
enrichment: { ...DEFAULT_ENRICHMENT, ...loaded.enrichment },
|
|
80
|
-
profiles: { ...PRESET_PROFILES, ...loaded.profiles }
|
|
80
|
+
profiles: { ...PRESET_PROFILES, ...loaded.profiles },
|
|
81
|
+
obsidian: loaded.obsidian
|
|
81
82
|
};
|
|
82
83
|
if (config.profile && config.profiles?.[config.profile]) {
|
|
83
84
|
this.applyProfile(config, config.profiles[config.profile]);
|