@stackmemoryai/stackmemory 1.8.1 → 1.10.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.
- package/dist/src/cli/commands/orchestrate.js +8 -1
- 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 +43 -0
- 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/integrations/linear/client.js +66 -0
- package/dist/src/integrations/mcp/handlers/index.js +43 -1
- package/dist/src/integrations/mcp/handlers/linear-handlers.js +102 -0
- package/dist/src/integrations/mcp/handlers/provenant-handlers.js +398 -0
- package/dist/src/integrations/mcp/handlers/skill-handlers.js +67 -167
- package/dist/src/integrations/mcp/server.js +117 -6
- package/dist/src/integrations/mcp/tool-definitions.js +152 -1
- 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";
|
|
@@ -478,6 +480,45 @@ program.addCommand(createMemoryCommand());
|
|
|
478
480
|
program.addCommand(clearCommand);
|
|
479
481
|
program.addCommand(serviceCommand);
|
|
480
482
|
program.addCommand(createHooksCommand());
|
|
483
|
+
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) => {
|
|
484
|
+
const { spawn: spawnProc } = await import("child_process");
|
|
485
|
+
const { join: join2 } = await import("path");
|
|
486
|
+
const { existsSync: existsSync2 } = await import("fs");
|
|
487
|
+
const candidates = [
|
|
488
|
+
join2(process.cwd(), "tools", "agent-viewer", "server.js"),
|
|
489
|
+
join2(
|
|
490
|
+
process.env.PROVENANTAI_ROOT || "",
|
|
491
|
+
"tools",
|
|
492
|
+
"agent-viewer",
|
|
493
|
+
"server.js"
|
|
494
|
+
),
|
|
495
|
+
join2(
|
|
496
|
+
process.env.HOME || "",
|
|
497
|
+
"Dev",
|
|
498
|
+
"provenantai",
|
|
499
|
+
"tools",
|
|
500
|
+
"agent-viewer",
|
|
501
|
+
"server.js"
|
|
502
|
+
)
|
|
503
|
+
];
|
|
504
|
+
const serverPath = candidates.find((c) => existsSync2(c));
|
|
505
|
+
if (!serverPath) {
|
|
506
|
+
console.error(
|
|
507
|
+
"Board server not found. Run from a repo with tools/agent-viewer/server.js"
|
|
508
|
+
);
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
console.log(`Starting StackMemory Board on port ${options.port}...`);
|
|
512
|
+
const child = spawnProc("node", [serverPath, "--port", options.port], {
|
|
513
|
+
stdio: "inherit",
|
|
514
|
+
env: { ...process.env, FORCE_COLOR: "1" }
|
|
515
|
+
});
|
|
516
|
+
child.on("close", (code) => process.exit(code || 0));
|
|
517
|
+
process.on("SIGINT", () => {
|
|
518
|
+
child.kill("SIGINT");
|
|
519
|
+
process.exit(0);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
481
522
|
const lazyCommands = [];
|
|
482
523
|
if (isFeatureEnabled("skills")) {
|
|
483
524
|
lazyCommands.push(
|
|
@@ -515,6 +556,8 @@ program.addCommand(createConductorCommands());
|
|
|
515
556
|
program.addCommand(createPreflightCommand());
|
|
516
557
|
program.addCommand(createSnapshotCommand());
|
|
517
558
|
program.addCommand(createLoopCommand());
|
|
559
|
+
program.addCommand(createRulesCommand());
|
|
560
|
+
program.addCommand(createSkillCommand());
|
|
518
561
|
registerSetupCommands(program);
|
|
519
562
|
program.command("mm-spike").description(
|
|
520
563
|
"Run multi-agent planning/implementation spike (planner/implementer/critic)"
|