@stackmemoryai/stackmemory 1.10.5 → 1.14.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.
- package/LICENSE +131 -64
- package/README.md +107 -24
- package/bin/claude-sm +16 -1
- package/bin/claude-smd +16 -1
- package/bin/codex-smd +16 -1
- package/bin/gemini-sm +16 -1
- package/bin/hermes-sm +21 -0
- package/bin/hermes-smd +21 -0
- package/bin/opencode-sm +16 -1
- package/dist/src/cli/claude-sm.js +266 -84
- package/dist/src/cli/codex-sm.js +225 -33
- package/dist/src/cli/commands/bench.js +209 -2
- package/dist/src/cli/commands/brain.js +206 -0
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/company-os.js +184 -0
- package/dist/src/cli/commands/context.js +5 -0
- package/dist/src/cli/commands/daemon.js +41 -0
- package/dist/src/cli/commands/handoff.js +40 -9
- package/dist/src/cli/commands/onboard.js +70 -3
- package/dist/src/cli/commands/operator.js +127 -0
- package/dist/src/cli/commands/optimize.js +117 -0
- package/dist/src/cli/commands/orchestrate.js +232 -5
- package/dist/src/cli/commands/orchestrator.js +315 -26
- package/dist/src/cli/commands/pack.js +322 -0
- package/dist/src/cli/commands/patterns.js +254 -0
- package/dist/src/cli/commands/portal.js +161 -0
- package/dist/src/cli/commands/scaffold.js +92 -0
- package/dist/src/cli/commands/search.js +40 -1
- package/dist/src/cli/commands/setup.js +178 -11
- package/dist/src/cli/commands/skills.js +10 -1
- package/dist/src/cli/commands/state.js +265 -0
- package/dist/src/cli/commands/sync.js +253 -0
- package/dist/src/cli/commands/tasks.js +130 -1
- package/dist/src/cli/commands/vision.js +221 -0
- package/dist/src/cli/gemini-sm.js +19 -29
- package/dist/src/cli/hermes-sm.js +224 -0
- package/dist/src/cli/index.js +105 -39
- package/dist/src/cli/opencode-sm.js +38 -21
- package/dist/src/cli/utils/determinism-watcher.js +66 -0
- package/dist/src/cli/utils/real-cli-bin.js +116 -0
- package/dist/src/core/brain/brain-store.js +187 -0
- package/dist/src/core/brain/brain-sync.js +193 -0
- package/dist/src/core/brain/index.js +78 -0
- package/dist/src/core/brain/types.js +10 -0
- package/dist/src/core/cache/content-cache.js +238 -0
- package/dist/src/{integrations/diffmem → core/cache}/index.js +5 -5
- package/dist/src/core/cache/token-estimator.js +39 -0
- package/dist/src/core/config/feature-flags.js +2 -6
- package/dist/src/core/context/frame-database.js +79 -27
- package/dist/src/core/context/recursive-context-manager.js +1 -1
- package/dist/src/core/context/rehydration.js +2 -1
- package/dist/src/core/cross-search/cross-project-search.js +269 -0
- package/dist/src/core/cross-search/index.js +10 -0
- package/dist/src/core/database/sqlite-adapter.js +14 -84
- package/dist/src/core/extensions/provider-adapter.js +5 -0
- package/dist/src/core/models/model-router.js +54 -2
- package/dist/src/core/models/provider-pricing.js +58 -4
- package/dist/src/core/monitoring/logger.js +2 -1
- package/dist/src/core/optimization/trace-optimizer.js +413 -0
- package/dist/src/core/patterns/index.js +22 -0
- package/dist/src/core/patterns/pattern-applier.js +39 -0
- package/dist/src/core/patterns/pattern-observer.js +157 -0
- package/dist/src/core/patterns/pattern-store.js +259 -0
- package/dist/src/core/patterns/types.js +19 -0
- package/dist/src/core/provenance/confidence-scorer.js +128 -0
- package/dist/src/core/provenance/index.js +40 -0
- package/dist/src/core/provenance/provenance-store.js +194 -0
- package/dist/src/core/provenance/types.js +82 -0
- package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
- package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
- package/dist/src/core/session/project-handoff.js +64 -0
- package/dist/src/core/session/session-manager.js +28 -0
- package/dist/src/core/shared-state/canonical-store.js +564 -0
- package/dist/src/core/skill-packs/index.js +18 -0
- package/dist/src/core/skill-packs/parser.js +42 -0
- package/dist/src/core/skill-packs/registry.js +224 -0
- package/dist/src/core/skill-packs/types.js +79 -0
- package/dist/src/core/storage/cloud-sync-manager.js +116 -0
- package/dist/src/core/storage/cloud-sync.js +574 -0
- package/dist/src/core/storage/two-tier-storage.js +5 -1
- package/dist/src/core/tasks/master-tasks-template.js +43 -0
- package/dist/src/core/tasks/md-task-parser.js +138 -0
- package/dist/src/core/trace/trace-event-store.js +282 -0
- package/dist/src/core/vision/index.js +27 -0
- package/dist/src/core/vision/signals.js +79 -0
- package/dist/src/core/vision/types.js +22 -0
- package/dist/src/core/vision/vision-file.js +146 -0
- package/dist/src/core/vision/vision-loop.js +220 -0
- package/dist/src/core/wiki/wiki-compiler.js +103 -1
- package/dist/src/daemon/daemon-config.js +52 -0
- package/dist/src/daemon/services/desire-path-service.js +566 -0
- package/dist/src/daemon/services/github-service.js +126 -0
- package/dist/src/daemon/services/research-stream-service.js +320 -0
- package/dist/src/daemon/services/telemetry-service.js +192 -0
- package/dist/src/daemon/unified-daemon.js +58 -1
- package/dist/src/features/browser/cli-browser-agent.js +417 -0
- package/dist/src/features/browser/stagehand-workflows.js +578 -0
- package/dist/src/features/operator/adapter-factory.js +62 -0
- package/dist/src/features/operator/browser-adapter.js +109 -0
- package/dist/src/features/operator/desktop-adapter.js +125 -0
- package/dist/src/features/operator/index.js +39 -0
- package/dist/src/features/operator/llm-decision.js +137 -0
- package/dist/src/features/operator/operator-logger.js +92 -0
- package/dist/src/features/operator/overnight-runner.js +327 -0
- package/dist/src/features/operator/screen-adapter.js +91 -0
- package/dist/src/features/operator/session-manager.js +127 -0
- package/dist/src/features/operator/state-machine.js +227 -0
- package/dist/src/features/operator/task-queue.js +81 -0
- package/dist/src/features/portal/index.js +26 -0
- package/dist/src/features/portal/server.js +240 -0
- package/dist/src/features/portal/types.js +14 -0
- package/dist/src/features/portal/ui.js +195 -0
- package/dist/src/features/sweep/pty-wrapper.js +13 -5
- package/dist/src/features/tasks/task-aware-context.js +2 -1
- package/dist/src/features/tui/simple-monitor.js +0 -23
- package/dist/src/features/tui/swarm-monitor.js +8 -66
- package/dist/src/features/web/client/hooks/use-socket.js +12 -0
- package/dist/src/{core/merge/index.js → features/web/client/lib/utils.js} +8 -4
- package/dist/src/features/web/client/next-env.d.js +4 -0
- package/dist/src/features/web/client/stores/session-store.js +12 -0
- package/dist/src/features/web/server/gcp-billing.js +76 -0
- package/dist/src/features/web/server/index.js +10 -0
- package/dist/src/features/web/server/spend-calculator.js +228 -0
- package/dist/src/hooks/schemas.js +6 -1
- package/dist/src/integrations/anthropic/client.js +3 -2
- package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
- package/dist/src/integrations/claude-code/subagent-client.js +307 -11
- package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
- package/dist/src/integrations/github/pr-state.js +158 -0
- package/dist/src/integrations/linear/client.js +4 -1
- package/dist/src/integrations/linear/webhook-retry.js +196 -0
- package/dist/src/integrations/linear/webhook-server.js +18 -22
- package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
- package/dist/src/integrations/mcp/handlers/index.js +40 -84
- package/dist/src/integrations/mcp/server.js +542 -641
- package/dist/src/integrations/mcp/tool-alias-registry.js +297 -0
- package/dist/src/integrations/mcp/tool-definitions.js +152 -682
- package/dist/src/mcp/stackmemory-mcp-server.js +571 -231
- package/dist/src/orchestrators/multimodal/determinism.js +244 -0
- package/dist/src/orchestrators/multimodal/harness.js +149 -78
- package/dist/src/orchestrators/multimodal/providers.js +44 -3
- package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
- package/dist/src/utils/hook-installer.js +0 -8
- package/dist/src/utils/process-cleanup.js +1 -7
- package/docs/README.md +42 -0
- package/docs/guides/README_INSTALL.md +208 -0
- package/package.json +27 -9
- package/packs/coding/python-fastapi/instructions.md +60 -0
- package/packs/coding/python-fastapi/pack.yaml +28 -0
- package/packs/coding/typescript-react/instructions.md +47 -0
- package/packs/coding/typescript-react/pack.yaml +28 -0
- package/packs/core/commands/capture.md +32 -0
- package/packs/core/commands/learn.md +73 -0
- package/packs/core/commands/next.md +36 -0
- package/packs/core/commands/restart.md +58 -0
- package/packs/core/commands/restore.md +29 -0
- package/packs/core/commands/start.md +57 -0
- package/packs/core/commands/stop.md +65 -0
- package/packs/core/commands/summary.md +40 -0
- package/packs/core/manifest.json +24 -0
- package/packs/ops/decision-recovery/instructions.md +65 -0
- package/packs/ops/decision-recovery/pack.yaml +89 -0
- package/scripts/claude-code-wrapper.sh +11 -0
- package/scripts/claude-sm-setup.sh +12 -1
- package/scripts/codex-wrapper.sh +11 -0
- package/scripts/git-hooks/branch-context-manager.sh +11 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
- package/scripts/hooks/cleanup-shell.sh +12 -1
- package/scripts/hooks/task-complete.sh +12 -1
- package/scripts/install-code-execution-hooks.sh +12 -1
- package/scripts/install-sweep-hook.sh +12 -0
- package/scripts/install.sh +11 -0
- package/scripts/opencode-wrapper.sh +11 -0
- package/scripts/portal/cloud-init.yaml +69 -0
- package/scripts/portal/setup.sh +69 -0
- package/scripts/portal/stackmemory-portal.service +34 -0
- package/scripts/setup-claude-integration.sh +12 -1
- package/scripts/smoke-init-db.sh +23 -0
- package/scripts/stackmemory-daemon.sh +11 -0
- package/scripts/verify-dist.cjs +11 -4
- package/dist/src/cli/commands/ralph.js +0 -1053
- package/dist/src/cli/commands/team.js +0 -168
- package/dist/src/core/context/shared-context-layer.js +0 -620
- package/dist/src/core/context/stack-merge-resolver.js +0 -748
- package/dist/src/core/merge/conflict-detector.js +0 -430
- package/dist/src/core/merge/resolution-engine.js +0 -557
- package/dist/src/core/merge/stack-diff.js +0 -531
- package/dist/src/core/merge/unified-merge-resolver.js +0 -302
- package/dist/src/hooks/diffmem-hooks.js +0 -376
- package/dist/src/integrations/diffmem/client.js +0 -208
- package/dist/src/integrations/diffmem/config.js +0 -14
- package/dist/src/integrations/greptile/client.js +0 -101
- package/dist/src/integrations/greptile/config.js +0 -14
- package/dist/src/integrations/greptile/index.js +0 -11
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
- package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
- package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
- package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
- package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
- package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
- package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -391
- package/dist/src/integrations/ralph/index.js +0 -17
- package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -435
- package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
- package/dist/src/integrations/ralph/loopmax.js +0 -488
- package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
- package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
- package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
- package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
- package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
- package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
- package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
- package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
- package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1007
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
- package/scripts/ralph-loop-implementation.js +0 -404
- /package/dist/src/core/{merge → cache}/types.js +0 -0
- /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
- /package/dist/src/{integrations/greptile/types.js → core/trace/trace-event.js} +0 -0
- /package/dist/src/{integrations/ralph → features/operator}/types.js +0 -0
|
@@ -20,6 +20,10 @@ import { FrameManager } from "../../core/context/index.js";
|
|
|
20
20
|
import { LinearTaskManager } from "../../features/tasks/linear-task-manager.js";
|
|
21
21
|
import { logger } from "../../core/monitoring/logger.js";
|
|
22
22
|
import { EnhancedHandoffGenerator } from "../../core/session/handoff.js";
|
|
23
|
+
import {
|
|
24
|
+
getProjectHandoffPaths,
|
|
25
|
+
loadProjectHandoff
|
|
26
|
+
} from "../../core/session/project-handoff.js";
|
|
23
27
|
const countTokens = (text) => Math.ceil(text.length / 3.5);
|
|
24
28
|
const MAX_HANDOFF_VERSIONS = 10;
|
|
25
29
|
function saveVersionedHandoff(projectRoot, branch, content) {
|
|
@@ -273,7 +277,7 @@ Generated by stackmemory capture at ${timestamp}
|
|
|
273
277
|
if (!existsSync(stackmemoryDir)) {
|
|
274
278
|
mkdirSync(stackmemoryDir, { recursive: true });
|
|
275
279
|
}
|
|
276
|
-
const handoffPath =
|
|
280
|
+
const { handoffPath, metadataPath } = getProjectHandoffPaths(projectRoot);
|
|
277
281
|
writeFileSync(handoffPath, handoffPrompt);
|
|
278
282
|
let branch = "unknown";
|
|
279
283
|
try {
|
|
@@ -289,6 +293,18 @@ Generated by stackmemory capture at ${timestamp}
|
|
|
289
293
|
branch,
|
|
290
294
|
handoffPrompt
|
|
291
295
|
);
|
|
296
|
+
writeFileSync(
|
|
297
|
+
metadataPath,
|
|
298
|
+
JSON.stringify(
|
|
299
|
+
{
|
|
300
|
+
branch,
|
|
301
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
302
|
+
projectRoot
|
|
303
|
+
},
|
|
304
|
+
null,
|
|
305
|
+
2
|
|
306
|
+
)
|
|
307
|
+
);
|
|
292
308
|
console.log(
|
|
293
309
|
`Versioned: ${versionedPath.split("/").slice(-2).join("/")}`
|
|
294
310
|
);
|
|
@@ -331,26 +347,41 @@ Generated by stackmemory capture at ${timestamp}
|
|
|
331
347
|
}
|
|
332
348
|
function createRestoreCommand() {
|
|
333
349
|
const cmd = new Command("restore");
|
|
334
|
-
cmd.description("Restore context from last handoff").option("--no-copy", "Do not copy prompt to clipboard").action(async (options) => {
|
|
350
|
+
cmd.description("Restore context from last handoff").option("--no-copy", "Do not copy prompt to clipboard").option("--force", "Restore even if the handoff branch does not match").action(async (options) => {
|
|
335
351
|
try {
|
|
336
352
|
const projectRoot = process.cwd();
|
|
337
|
-
const handoffPath = join(
|
|
338
|
-
projectRoot,
|
|
339
|
-
".stackmemory",
|
|
340
|
-
"last-handoff.md"
|
|
341
|
-
);
|
|
342
353
|
const metaPath = join(
|
|
343
354
|
process.env["HOME"] || "~",
|
|
344
355
|
".stackmemory",
|
|
345
356
|
"handoffs",
|
|
346
357
|
"last-handoff-meta.json"
|
|
347
358
|
);
|
|
348
|
-
|
|
359
|
+
const currentBranch = (() => {
|
|
360
|
+
try {
|
|
361
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
362
|
+
encoding: "utf-8",
|
|
363
|
+
cwd: projectRoot,
|
|
364
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
365
|
+
}).trim();
|
|
366
|
+
} catch {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
})();
|
|
370
|
+
const handoff = loadProjectHandoff(projectRoot, currentBranch);
|
|
371
|
+
if (!handoff) {
|
|
349
372
|
console.log("\u274C No handoff found in this project");
|
|
350
373
|
console.log('\u{1F4A1} Run "stackmemory capture" to create one');
|
|
351
374
|
return;
|
|
352
375
|
}
|
|
353
|
-
|
|
376
|
+
if (!handoff.compatible && !options.force) {
|
|
377
|
+
console.log("\u26A0\uFE0F Skipping stale handoff");
|
|
378
|
+
console.log(` ${handoff.mismatchReason}`);
|
|
379
|
+
console.log(
|
|
380
|
+
' Run "stackmemory restore --force" to inspect it anyway'
|
|
381
|
+
);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const handoffPrompt = handoff.content;
|
|
354
385
|
console.log("\n" + "=".repeat(60));
|
|
355
386
|
console.log("\u{1F4CB} RESTORED HANDOFF");
|
|
356
387
|
console.log("=".repeat(60));
|
|
@@ -383,6 +383,67 @@ DATABASE_URL=${config.databaseUrl}
|
|
|
383
383
|
console.log(chalk.yellow(" \u26A0 Could not write hosted DB env file"));
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
|
+
{
|
|
387
|
+
const claudeCommandsDir = join(homedir(), ".claude", "commands");
|
|
388
|
+
const packsDir = join(__dirname, "..", "..", "..", "packs");
|
|
389
|
+
const coreManifest = join(packsDir, "core", "manifest.json");
|
|
390
|
+
if (existsSync(coreManifest)) {
|
|
391
|
+
const { installCommands } = await inquirer.prompt([
|
|
392
|
+
{
|
|
393
|
+
type: "confirm",
|
|
394
|
+
name: "installCommands",
|
|
395
|
+
message: "Install session commands for Claude Code? (/start, /stop, /restart, /next)",
|
|
396
|
+
default: true
|
|
397
|
+
}
|
|
398
|
+
]);
|
|
399
|
+
if (installCommands) {
|
|
400
|
+
console.log(chalk.gray("Installing command pack: core..."));
|
|
401
|
+
try {
|
|
402
|
+
execFileSync(
|
|
403
|
+
process.execPath,
|
|
404
|
+
[process.argv[1], "setup-commands", "--force"],
|
|
405
|
+
{ stdio: "pipe" }
|
|
406
|
+
);
|
|
407
|
+
console.log(
|
|
408
|
+
chalk.green(" \u2713 Installed /start, /stop, /restart, /next")
|
|
409
|
+
);
|
|
410
|
+
} catch {
|
|
411
|
+
try {
|
|
412
|
+
if (!existsSync(claudeCommandsDir)) {
|
|
413
|
+
mkdirSync(claudeCommandsDir, { recursive: true });
|
|
414
|
+
}
|
|
415
|
+
const manifest = JSON.parse(
|
|
416
|
+
(await import("fs")).readFileSync(coreManifest, "utf8")
|
|
417
|
+
);
|
|
418
|
+
const allCmds = [
|
|
419
|
+
...manifest.commands?.public || [],
|
|
420
|
+
...manifest.commands?.internal || []
|
|
421
|
+
];
|
|
422
|
+
let count = 0;
|
|
423
|
+
for (const cmd of allCmds) {
|
|
424
|
+
const src = join(packsDir, "core", cmd.file);
|
|
425
|
+
const dst = join(claudeCommandsDir, `${cmd.name}.md`);
|
|
426
|
+
if (existsSync(src) && !existsSync(dst)) {
|
|
427
|
+
execFileSync("ln", ["-s", src, dst]);
|
|
428
|
+
count++;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (count > 0) {
|
|
432
|
+
console.log(
|
|
433
|
+
chalk.green(` \u2713 Installed ${count} command(s)`)
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
console.log(
|
|
438
|
+
chalk.yellow(
|
|
439
|
+
" \u26A0 Could not install commands. Run: stackmemory setup-commands"
|
|
440
|
+
)
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
386
447
|
const binPath = "/usr/local/bin/claude-sm";
|
|
387
448
|
const sourcePath = join(configPath, "bin", "stackmemory");
|
|
388
449
|
try {
|
|
@@ -531,12 +592,18 @@ function showNextSteps(config) {
|
|
|
531
592
|
)
|
|
532
593
|
);
|
|
533
594
|
}
|
|
534
|
-
console.log("3.
|
|
595
|
+
console.log("3. Install session commands for Claude Code:");
|
|
596
|
+
console.log(
|
|
597
|
+
chalk.gray(
|
|
598
|
+
" stackmemory setup-commands # Installs /start, /stop, /restart, /next\n"
|
|
599
|
+
)
|
|
600
|
+
);
|
|
601
|
+
console.log("4. Use with Claude:");
|
|
535
602
|
console.log(chalk.gray(" claude-sm # Or use stackmemory directly\n"));
|
|
536
|
-
console.log("
|
|
603
|
+
console.log("5. Use with Codex:");
|
|
537
604
|
console.log(chalk.gray(" codex-sm # Codex + StackMemory integration\n"));
|
|
538
605
|
if (config.enableLinear) {
|
|
539
|
-
console.log("
|
|
606
|
+
console.log("6. Sync with Linear:");
|
|
540
607
|
console.log(chalk.gray(" stackmemory linear sync\n"));
|
|
541
608
|
}
|
|
542
609
|
console.log("For more help:");
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { OvernightRunner } from "../../features/operator/overnight-runner.js";
|
|
10
|
+
import { OperatorLogger } from "../../features/operator/operator-logger.js";
|
|
11
|
+
const OPERATOR_DIR = join(homedir(), ".stackmemory", "operator");
|
|
12
|
+
const STOP_SIGNAL_FILE = join(OPERATOR_DIR, "stop-signal");
|
|
13
|
+
const LOG_DIR = join(OPERATOR_DIR, "logs");
|
|
14
|
+
function createOperatorCommands() {
|
|
15
|
+
const operator = new Command("operator").description(
|
|
16
|
+
"Autonomous Claude Code operator \u2014 drives sessions overnight via tmux"
|
|
17
|
+
);
|
|
18
|
+
operator.command("start").description(
|
|
19
|
+
"Start the operator \u2014 drains task queue using Claude Code in tmux"
|
|
20
|
+
).requiredOption("-f, --file <path>", "Path to master-tasks.md").option("--cwd <dir>", "Working directory for Claude", process.cwd()).option("--mode <mode>", "Adapter: tmux | desktop | browser | auto", "auto").option("--poll <ms>", "Poll interval in ms", "2000").option("--stuck-timeout <ms>", "Stuck detection threshold in ms", "300000").option("--session <name>", "tmux session name", "operator").option("--model <model>", "Claude model override (inner agent)").option(
|
|
21
|
+
"--api-key <key>",
|
|
22
|
+
"Anthropic API key for LLM outer loop (or ANTHROPIC_API_KEY env)"
|
|
23
|
+
).option(
|
|
24
|
+
"--llm-model <model>",
|
|
25
|
+
"LLM model for outer loop decisions",
|
|
26
|
+
"claude-haiku-4-5-20251001"
|
|
27
|
+
).option("--no-auto-commit", "Disable auto-commit after each task").option(
|
|
28
|
+
"--max-restarts <n>",
|
|
29
|
+
"Max consecutive restarts before stopping",
|
|
30
|
+
"10"
|
|
31
|
+
).option("--log-dir <dir>", "Log directory", LOG_DIR).action(async (opts) => {
|
|
32
|
+
if (!existsSync(opts.file)) {
|
|
33
|
+
process.stderr.write(`Error: task file not found: ${opts.file}
|
|
34
|
+
`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
38
|
+
if (opts.mode === "desktop" && !apiKey) {
|
|
39
|
+
process.stderr.write(
|
|
40
|
+
"Error: desktop mode requires --api-key or ANTHROPIC_API_KEY for screenshot interpretation\n"
|
|
41
|
+
);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const config = {
|
|
45
|
+
taskFilePath: opts.file,
|
|
46
|
+
cwd: opts.cwd,
|
|
47
|
+
adapterMode: opts.mode,
|
|
48
|
+
pollIntervalMs: parseInt(opts.poll, 10),
|
|
49
|
+
stuckTimeoutMs: parseInt(opts.stuckTimeout, 10),
|
|
50
|
+
rateLimitBackoffMs: 6e4,
|
|
51
|
+
maxRateLimitBackoffMs: 9e5,
|
|
52
|
+
maxConsecutiveRestarts: parseInt(opts.maxRestarts, 10),
|
|
53
|
+
sessionName: opts.session,
|
|
54
|
+
model: opts.model,
|
|
55
|
+
anthropicApiKey: apiKey,
|
|
56
|
+
llmModel: opts.llmModel,
|
|
57
|
+
autoCommit: opts.autoCommit !== false,
|
|
58
|
+
logDir: opts.logDir
|
|
59
|
+
};
|
|
60
|
+
const runner = new OvernightRunner(config);
|
|
61
|
+
try {
|
|
62
|
+
await runner.run();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65
|
+
process.stderr.write(`Operator error: ${msg}
|
|
66
|
+
`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
operator.command("stop").description("Signal the running operator to stop gracefully").option("--session <name>", "tmux session name", "operator").action((_opts) => {
|
|
71
|
+
mkdirSync(OPERATOR_DIR, { recursive: true });
|
|
72
|
+
writeFileSync(STOP_SIGNAL_FILE, String(Date.now()), "utf-8");
|
|
73
|
+
process.stderr.write(
|
|
74
|
+
"Stop signal sent. Operator will shut down after current tick.\n"
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
operator.command("status").description("Show current operator status from checkpoint").option("--json", "Output raw JSON").action((opts) => {
|
|
78
|
+
const checkpoint = OperatorLogger.readCheckpoint(OPERATOR_DIR);
|
|
79
|
+
if (!checkpoint) {
|
|
80
|
+
process.stderr.write(
|
|
81
|
+
"No operator checkpoint found. Is the operator running?\n"
|
|
82
|
+
);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
if (opts.json) {
|
|
86
|
+
process.stdout.write(JSON.stringify(checkpoint, null, 2) + "\n");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const runtime = Date.now() - checkpoint.startedAt;
|
|
90
|
+
const hrs = Math.floor(runtime / 36e5);
|
|
91
|
+
const mins = Math.floor(runtime % 36e5 / 6e4);
|
|
92
|
+
const lines = [
|
|
93
|
+
`State: ${checkpoint.currentState}`,
|
|
94
|
+
`Active task: ${checkpoint.currentTaskId ?? "none"}`,
|
|
95
|
+
`Completed: ${checkpoint.tasksCompleted.length}`,
|
|
96
|
+
`Blocked: ${checkpoint.tasksBlocked.length}`,
|
|
97
|
+
`Runtime: ${hrs}h ${mins}m`,
|
|
98
|
+
`Restarts: ${checkpoint.totalRestarts}`,
|
|
99
|
+
`Approvals: ${checkpoint.totalPermissionApprovals}`,
|
|
100
|
+
`Rate limits: ${checkpoint.totalRateLimitHits}`
|
|
101
|
+
];
|
|
102
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
103
|
+
});
|
|
104
|
+
operator.command("attach").description(
|
|
105
|
+
"Attach to the operator tmux session for live observation (Ctrl-B d to detach)"
|
|
106
|
+
).option("--session <name>", "tmux session name", "operator").action((opts) => {
|
|
107
|
+
try {
|
|
108
|
+
const {
|
|
109
|
+
SessionManager
|
|
110
|
+
} = require("../../features/operator/session-manager.js");
|
|
111
|
+
const mgr = new SessionManager({
|
|
112
|
+
sessionName: opts.session,
|
|
113
|
+
cwd: process.cwd()
|
|
114
|
+
});
|
|
115
|
+
mgr.attach();
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
118
|
+
process.stderr.write(`Attach error: ${msg}
|
|
119
|
+
`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return operator;
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
createOperatorCommands
|
|
127
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import Database from "better-sqlite3";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import { TraceStore } from "../../core/trace/trace-store.js";
|
|
11
|
+
import { TraceOptimizer } from "../../core/optimization/trace-optimizer.js";
|
|
12
|
+
function createOptimizeCommand() {
|
|
13
|
+
const optimize = new Command("optimize").description(
|
|
14
|
+
"Offline optimizers for harnesses, traces, and prompts"
|
|
15
|
+
);
|
|
16
|
+
optimize.command("traces").description(
|
|
17
|
+
"Analyze stored traces and generate HALO-like offline optimizer recommendations"
|
|
18
|
+
).option("-d, --days <n>", "Only analyze traces from the last N days", "30").option(
|
|
19
|
+
"-m, --min-occurrences <n>",
|
|
20
|
+
"Minimum repeated occurrences before surfacing a pattern",
|
|
21
|
+
"2"
|
|
22
|
+
).option("--json", "Print machine-readable JSON").option(
|
|
23
|
+
"--no-write",
|
|
24
|
+
"Do not persist report files under .stackmemory/build"
|
|
25
|
+
).action(async (options) => {
|
|
26
|
+
const projectRoot = process.cwd();
|
|
27
|
+
const dbPath = join(projectRoot, ".stackmemory", "context.db");
|
|
28
|
+
if (!existsSync(dbPath)) {
|
|
29
|
+
console.log(
|
|
30
|
+
chalk.red("StackMemory not initialized in this directory.")
|
|
31
|
+
);
|
|
32
|
+
console.log(chalk.gray('Run "stackmemory init" first.'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const db = new Database(dbPath);
|
|
36
|
+
try {
|
|
37
|
+
const traceStore = new TraceStore(db);
|
|
38
|
+
const optimizer = new TraceOptimizer(traceStore);
|
|
39
|
+
const report = optimizer.analyze({
|
|
40
|
+
lookbackDays: parseInt(options.days, 10) || 30,
|
|
41
|
+
minOccurrences: parseInt(options.minOccurrences, 10) || 2
|
|
42
|
+
});
|
|
43
|
+
const persisted = options.write ? optimizer.persistReport(projectRoot, report) : null;
|
|
44
|
+
if (options.json) {
|
|
45
|
+
console.log(
|
|
46
|
+
JSON.stringify(
|
|
47
|
+
{
|
|
48
|
+
...report,
|
|
49
|
+
persisted
|
|
50
|
+
},
|
|
51
|
+
null,
|
|
52
|
+
2
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
console.log(chalk.blue("\nTrace Optimizer Report\n"));
|
|
58
|
+
console.log(`Lookback window: ${report.lookbackDays} day(s)`);
|
|
59
|
+
console.log(`Traces analyzed: ${report.totalTracesAnalyzed}`);
|
|
60
|
+
console.log(`Traces with errors: ${report.tracesWithErrors}`);
|
|
61
|
+
console.log(`Causal traces: ${report.causalTraces}`);
|
|
62
|
+
console.log(
|
|
63
|
+
`Average tools/trace: ${report.averageToolsPerTrace.toFixed(2)}`
|
|
64
|
+
);
|
|
65
|
+
console.log(
|
|
66
|
+
`Average trace score: ${report.averageTraceScore.toFixed(2)}`
|
|
67
|
+
);
|
|
68
|
+
if (report.recommendations.length === 0) {
|
|
69
|
+
console.log(
|
|
70
|
+
chalk.yellow(
|
|
71
|
+
"\nNo repeated patterns crossed the threshold. Lower --min-occurrences or collect more traces."
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
console.log(chalk.blue("\nRecommendations:\n"));
|
|
76
|
+
for (const recommendation of report.recommendations) {
|
|
77
|
+
const badge = recommendation.priority === "high" ? chalk.red("[high]") : chalk.yellow("[medium]");
|
|
78
|
+
console.log(
|
|
79
|
+
`${badge} ${chalk.white(recommendation.title)} (${recommendation.confidence.toFixed(2)} confidence)`
|
|
80
|
+
);
|
|
81
|
+
console.log(` ${recommendation.summary}`);
|
|
82
|
+
console.log(` Targets: ${recommendation.targetAreas.join(", ")}`);
|
|
83
|
+
console.log(` Actions: ${recommendation.actions.join(" | ")}`);
|
|
84
|
+
console.log(
|
|
85
|
+
` Validate: ${recommendation.validations.join(" | ")}`
|
|
86
|
+
);
|
|
87
|
+
console.log("");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (report.clusters.length > 0) {
|
|
91
|
+
console.log(chalk.blue("Detected clusters:\n"));
|
|
92
|
+
for (const cluster of report.clusters) {
|
|
93
|
+
console.log(
|
|
94
|
+
`- ${cluster.label} (${cluster.occurrences} traces, ${cluster.kind})`
|
|
95
|
+
);
|
|
96
|
+
if (cluster.toolPatterns.length > 0) {
|
|
97
|
+
console.log(` Tools: ${cluster.toolPatterns.join(", ")}`);
|
|
98
|
+
}
|
|
99
|
+
if (cluster.sampleSummaries.length > 0) {
|
|
100
|
+
console.log(` Examples: ${cluster.sampleSummaries.join(" | ")}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log("");
|
|
104
|
+
}
|
|
105
|
+
if (persisted) {
|
|
106
|
+
console.log(chalk.gray(`Saved JSON: ${persisted.jsonPath}`));
|
|
107
|
+
console.log(chalk.gray(`Saved Markdown: ${persisted.markdownPath}`));
|
|
108
|
+
}
|
|
109
|
+
} finally {
|
|
110
|
+
db.close();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return optimize;
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
createOptimizeCommand
|
|
117
|
+
};
|