@stackmemoryai/stackmemory 1.3.1 → 1.4.0

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.
@@ -3,13 +3,15 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { Command } from "commander";
6
+ import { execSync } from "child_process";
6
7
  import { existsSync, mkdirSync, writeFileSync } from "fs";
7
8
  import { join } from "path";
8
- import { homedir } from "os";
9
+ import { homedir, tmpdir } from "os";
9
10
  import Database from "better-sqlite3";
10
11
  import { logger } from "../../core/monitoring/logger.js";
12
+ import { Conductor } from "./orchestrator.js";
11
13
  function getGlobalStorePath() {
12
- const dir = join(homedir(), ".stackmemory", "symphony");
14
+ const dir = join(homedir(), ".stackmemory", "conductor");
13
15
  if (!existsSync(dir)) {
14
16
  mkdirSync(dir, { recursive: true });
15
17
  }
@@ -41,9 +43,9 @@ function getGlobalDb() {
41
43
  `);
42
44
  return db;
43
45
  }
44
- function createSymphonyCommands() {
45
- const cmd = new Command("symphony");
46
- cmd.description("Symphony orchestrator integration commands");
46
+ function createConductorCommands() {
47
+ const cmd = new Command("conductor");
48
+ cmd.description("Conductor \u2014 autonomous agent orchestration via Linear");
47
49
  cmd.command("capture").description("Capture workspace context after an agent run").requiredOption("--issue <id>", "Issue identifier (e.g., STA-476)").option("--workspace <path>", "Workspace directory", process.cwd()).option("--attempt <n>", "Attempt number", "1").action(async (options) => {
48
50
  const workspace = options.workspace;
49
51
  const issueId = options.issue;
@@ -53,16 +55,20 @@ function createSymphonyCommands() {
53
55
  let framesJson = "[]";
54
56
  let anchorsJson = "[]";
55
57
  let eventsJson = "[]";
58
+ let frameCount = 0;
59
+ let anchorCount = 0;
56
60
  if (existsSync(dbPath)) {
57
61
  try {
58
62
  const db = new Database(dbPath, { readonly: true });
59
63
  const frames = db.prepare(
60
64
  "SELECT frame_id, name, type, digest_text, created_at FROM frames ORDER BY created_at DESC LIMIT 20"
61
65
  ).all();
66
+ frameCount = frames.length;
62
67
  framesJson = JSON.stringify(frames);
63
68
  const anchors = db.prepare(
64
69
  "SELECT anchor_id, type, text, priority FROM anchors WHERE type IN ('DECISION', 'FACT', 'CONSTRAINT', 'RISK') ORDER BY priority DESC LIMIT 30"
65
70
  ).all();
71
+ anchorCount = anchors.length;
66
72
  anchorsJson = JSON.stringify(anchors);
67
73
  const events = db.prepare(
68
74
  "SELECT event_type, payload, ts FROM events ORDER BY ts DESC LIMIT 50"
@@ -79,7 +85,6 @@ function createSymphonyCommands() {
79
85
  }
80
86
  let metadata = { workspace, attempt };
81
87
  try {
82
- const { execSync } = await import("child_process");
83
88
  const branch = execSync("git rev-parse --abbrev-ref HEAD", {
84
89
  cwd: workspace,
85
90
  encoding: "utf8",
@@ -112,8 +117,6 @@ function createSymphonyCommands() {
112
117
  JSON.stringify(metadata)
113
118
  );
114
119
  globalDb.close();
115
- const frameCount = JSON.parse(framesJson).length;
116
- const anchorCount = JSON.parse(anchorsJson).length;
117
120
  console.log(
118
121
  `Captured ${frameCount} frames, ${anchorCount} anchors for ${issueId} (attempt ${attempt})`
119
122
  );
@@ -123,7 +126,7 @@ function createSymphonyCommands() {
123
126
  const issueId = options.issue;
124
127
  const globalDbPath = join(getGlobalStorePath(), "context.db");
125
128
  if (!existsSync(globalDbPath)) {
126
- console.log("No prior Symphony context found");
129
+ console.log("No prior orchestrator context found");
127
130
  return;
128
131
  }
129
132
  const globalDb = getGlobalDb();
@@ -185,7 +188,7 @@ function createSymphonyCommands() {
185
188
  if (!existsSync(restoreDir)) {
186
189
  mkdirSync(restoreDir, { recursive: true });
187
190
  }
188
- const restorePath = join(restoreDir, "symphony-context.md");
191
+ const restorePath = join(restoreDir, "conductor-context.md");
189
192
  writeFileSync(restorePath, lines.join("\n"));
190
193
  globalDb.close();
191
194
  console.log(
@@ -226,10 +229,10 @@ function createSymphonyCommands() {
226
229
  `Archived ${frames.length} frames, ${anchors.length} anchors for ${issueId}`
227
230
  );
228
231
  });
229
- cmd.command("search").description("Search across all Symphony issue contexts").argument("<query>", "Search query").option("--limit <n>", "Max results", "10").action(async (query, options) => {
232
+ cmd.command("search").description("Search across all orchestrator issue contexts").argument("<query>", "Search query").option("--limit <n>", "Max results", "10").action(async (query, options) => {
230
233
  const globalDbPath = join(getGlobalStorePath(), "context.db");
231
234
  if (!existsSync(globalDbPath)) {
232
- console.log("No Symphony context database found");
235
+ console.log("No orchestrator context database found");
233
236
  return;
234
237
  }
235
238
  const limit = parseInt(options.limit, 10);
@@ -260,8 +263,36 @@ function createSymphonyCommands() {
260
263
  }
261
264
  globalDb.close();
262
265
  });
266
+ cmd.command("start").description("Start the orchestrator daemon").option("--team <id>", "Linear team ID").option(
267
+ "--states <states>",
268
+ "Comma-separated issue states to pick up",
269
+ "Todo"
270
+ ).option(
271
+ "--in-progress <state>",
272
+ "State name for in-progress",
273
+ "In Progress"
274
+ ).option(
275
+ "--in-review <state>",
276
+ "State name for completed review",
277
+ "In Review"
278
+ ).option("--poll <ms>", "Polling interval in milliseconds", "30000").option("--concurrency <n>", "Max concurrent agents", "3").option("--workspace-root <path>", "Workspace root directory").option("--repo <path>", "Git repo root for worktrees", process.cwd()).option("--branch <name>", "Base branch for worktrees", "main").option("--retries <n>", "Max retries per issue", "1").option("--turn-timeout <ms>", "Agent turn timeout in ms", "3600000").action(async (options) => {
279
+ const conductor = new Conductor({
280
+ teamId: options.team,
281
+ activeStates: options.states.split(",").map((s) => s.trim()),
282
+ inProgressState: options.inProgress,
283
+ inReviewState: options.inReview,
284
+ pollIntervalMs: parseInt(options.poll, 10),
285
+ maxConcurrent: parseInt(options.concurrency, 10),
286
+ workspaceRoot: options.workspaceRoot || join(tmpdir(), "conductor_workspaces"),
287
+ repoRoot: options.repo,
288
+ baseBranch: options.branch,
289
+ maxRetries: parseInt(options.retries, 10),
290
+ turnTimeoutMs: parseInt(options.turnTimeout, 10)
291
+ });
292
+ await conductor.start();
293
+ });
263
294
  return cmd;
264
295
  }
265
296
  export {
266
- createSymphonyCommands
297
+ createConductorCommands
267
298
  };