@stackmemoryai/stackmemory 1.10.4 → 1.12.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.
Files changed (95) hide show
  1. package/README.md +104 -23
  2. package/dist/src/cli/claude-sm.js +266 -84
  3. package/dist/src/cli/codex-sm.js +185 -33
  4. package/dist/src/cli/commands/bench.js +209 -2
  5. package/dist/src/cli/commands/cache.js +126 -0
  6. package/dist/src/cli/commands/daemon.js +41 -0
  7. package/dist/src/cli/commands/handoff.js +40 -9
  8. package/dist/src/cli/commands/onboard.js +70 -3
  9. package/dist/src/cli/commands/optimize.js +117 -0
  10. package/dist/src/cli/commands/orchestrate.js +230 -5
  11. package/dist/src/cli/commands/orchestrator.js +312 -24
  12. package/dist/src/cli/commands/pack.js +322 -0
  13. package/dist/src/cli/commands/search.js +40 -1
  14. package/dist/src/cli/commands/setup.js +177 -7
  15. package/dist/src/cli/commands/skills.js +10 -1
  16. package/dist/src/cli/commands/state.js +265 -0
  17. package/dist/src/cli/commands/wiki.js +33 -0
  18. package/dist/src/cli/gemini-sm.js +19 -29
  19. package/dist/src/cli/index.js +90 -29
  20. package/dist/src/cli/opencode-sm.js +38 -21
  21. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  22. package/dist/src/cli/utils/real-cli-bin.js +44 -0
  23. package/dist/src/core/cache/content-cache.js +238 -0
  24. package/dist/src/core/cache/index.js +11 -0
  25. package/dist/src/core/cache/token-estimator.js +16 -0
  26. package/dist/src/core/context/frame-database.js +38 -30
  27. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  28. package/dist/src/core/{merge → cross-search}/index.js +6 -4
  29. package/dist/src/core/database/sqlite-adapter.js +0 -83
  30. package/dist/src/core/extensions/provider-adapter.js +5 -0
  31. package/dist/src/core/models/model-router.js +22 -2
  32. package/dist/src/core/monitoring/logger.js +2 -1
  33. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  34. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  35. package/dist/src/core/provenance/index.js +40 -0
  36. package/dist/src/core/provenance/provenance-store.js +194 -0
  37. package/dist/src/core/provenance/types.js +82 -0
  38. package/dist/src/core/session/project-handoff.js +64 -0
  39. package/dist/src/core/session/session-manager.js +28 -0
  40. package/dist/src/core/shared-state/canonical-store.js +564 -0
  41. package/dist/src/core/skill-packs/index.js +18 -0
  42. package/dist/src/core/skill-packs/parser.js +42 -0
  43. package/dist/src/core/skill-packs/registry.js +224 -0
  44. package/dist/src/core/skill-packs/types.js +66 -0
  45. package/dist/src/core/trace/trace-event-store.js +282 -0
  46. package/dist/src/core/trace/trace-event.js +4 -0
  47. package/dist/src/core/wiki/wiki-compiler.js +219 -0
  48. package/dist/src/daemon/daemon-config.js +7 -0
  49. package/dist/src/daemon/services/github-service.js +126 -0
  50. package/dist/src/daemon/unified-daemon.js +30 -0
  51. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  52. package/dist/src/hooks/schemas.js +2 -0
  53. package/dist/src/integrations/claude-code/subagent-client.js +89 -0
  54. package/dist/src/integrations/github/pr-state.js +158 -0
  55. package/dist/src/integrations/linear/client.js +4 -1
  56. package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
  57. package/dist/src/integrations/mcp/handlers/index.js +40 -59
  58. package/dist/src/integrations/mcp/server.js +425 -311
  59. package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
  60. package/dist/src/integrations/mcp/tool-definitions.js +98 -229
  61. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
  62. package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
  63. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
  64. package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
  65. package/dist/src/orchestrators/multimodal/determinism.js +243 -0
  66. package/dist/src/orchestrators/multimodal/harness.js +147 -77
  67. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  68. package/dist/src/utils/hook-installer.js +8 -8
  69. package/package.json +10 -1
  70. package/packs/coding/python-fastapi/instructions.md +60 -0
  71. package/packs/coding/python-fastapi/pack.yaml +28 -0
  72. package/packs/coding/typescript-react/instructions.md +47 -0
  73. package/packs/coding/typescript-react/pack.yaml +28 -0
  74. package/packs/core/commands/capture.md +32 -0
  75. package/packs/core/commands/learn.md +73 -0
  76. package/packs/core/commands/next.md +36 -0
  77. package/packs/core/commands/restart.md +58 -0
  78. package/packs/core/commands/restore.md +29 -0
  79. package/packs/core/commands/start.md +57 -0
  80. package/packs/core/commands/stop.md +65 -0
  81. package/packs/core/commands/summary.md +40 -0
  82. package/packs/core/manifest.json +24 -0
  83. package/packs/ops/decision-recovery/instructions.md +65 -0
  84. package/packs/ops/decision-recovery/pack.yaml +89 -0
  85. package/templates/claude-hooks/doc-ingest.js +76 -0
  86. package/dist/src/cli/commands/team.js +0 -168
  87. package/dist/src/core/context/shared-context-layer.js +0 -620
  88. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  89. package/dist/src/core/merge/conflict-detector.js +0 -430
  90. package/dist/src/core/merge/resolution-engine.js +0 -557
  91. package/dist/src/core/merge/stack-diff.js +0 -531
  92. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  93. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  94. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  95. /package/dist/src/core/{merge → cache}/types.js +0 -0
@@ -0,0 +1,265 @@
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 chalk from "chalk";
7
+ import {
8
+ canonicalStateStore
9
+ } from "../../core/shared-state/canonical-store.js";
10
+ import {
11
+ getCurrentRepoGitHubInfo,
12
+ refreshCurrentRepoPullRequestState
13
+ } from "../../integrations/github/pr-state.js";
14
+ function parseJsonObject(input) {
15
+ if (!input) {
16
+ return {};
17
+ }
18
+ const parsed = JSON.parse(input);
19
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
20
+ throw new Error("Expected a JSON object");
21
+ }
22
+ return parsed;
23
+ }
24
+ function createStateCommand() {
25
+ const cmd = new Command("state").description(
26
+ "Manage canonical user-scoped shared state across instances and sessions"
27
+ );
28
+ const instance = cmd.command("instance").description("Manage instance state");
29
+ instance.command("upsert").requiredOption("--id <id>", "Instance identifier").requiredOption(
30
+ "--tool <tool>",
31
+ "Tool name (claude|codex|opencode|stackmemory)"
32
+ ).option("--session <id>", "Session identifier").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--worktree-path <path>", "Worktree path").option("--pid <pid>", "Process id").option("--status <status>", "Status", "active").option("--metadata <json>", "Metadata JSON object").action(async (options) => {
33
+ const record = await canonicalStateStore.upsertInstance({
34
+ instanceId: options.id,
35
+ tool: options.tool,
36
+ sessionId: options.session,
37
+ projectId: options.project,
38
+ projectPath: options.projectPath,
39
+ branch: options.branch,
40
+ worktreePath: options.worktreePath,
41
+ pid: options.pid ? Number(options.pid) : void 0,
42
+ status: options.status,
43
+ metadata: parseJsonObject(options.metadata)
44
+ });
45
+ console.log(JSON.stringify(record, null, 2));
46
+ });
47
+ instance.command("end").requiredOption("--id <id>", "Instance identifier").action(async (options) => {
48
+ await canonicalStateStore.endInstance(options.id);
49
+ console.log(chalk.green(`Ended instance ${options.id}`));
50
+ });
51
+ const session = cmd.command("session").description("Manage session state");
52
+ session.command("upsert").requiredOption("--id <id>", "Session identifier").requiredOption(
53
+ "--tool <tool>",
54
+ "Tool name (claude|codex|opencode|stackmemory)"
55
+ ).option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--instance <id>", "Associated instance identifier").option("--status <status>", "Status", "active").option("--metadata <json>", "Metadata JSON object").action(async (options) => {
56
+ const record = await canonicalStateStore.upsertSession({
57
+ sessionId: options.id,
58
+ tool: options.tool,
59
+ projectId: options.project,
60
+ projectPath: options.projectPath,
61
+ branch: options.branch,
62
+ instanceId: options.instance,
63
+ status: options.status,
64
+ metadata: parseJsonObject(options.metadata)
65
+ });
66
+ console.log(JSON.stringify(record, null, 2));
67
+ });
68
+ session.command("end").requiredOption("--id <id>", "Session identifier").option("--status <status>", "Status", "closed").action(async (options) => {
69
+ await canonicalStateStore.endSession(options.id, options.status);
70
+ console.log(chalk.green(`Ended session ${options.id}`));
71
+ });
72
+ cmd.command("event").description("Append a shared-state event").requiredOption("--type <type>", "Event type").option("--tool <tool>", "Tool name (claude|codex|opencode|stackmemory)").option("--instance <id>", "Instance identifier").option("--session <id>", "Session identifier").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--payload <json>", "Payload JSON object").action(async (options) => {
73
+ const event = await canonicalStateStore.appendEvent({
74
+ type: options.type,
75
+ tool: options.tool,
76
+ instanceId: options.instance,
77
+ sessionId: options.session,
78
+ projectId: options.project,
79
+ projectPath: options.projectPath,
80
+ branch: options.branch,
81
+ payload: parseJsonObject(options.payload)
82
+ });
83
+ console.log(JSON.stringify(event, null, 2));
84
+ });
85
+ cmd.command("show").description("Show canonical shared state for a project").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--limit <count>", "Recent event limit", "10").option("--json", "Emit JSON output").action(async (options) => {
86
+ const summary = await canonicalStateStore.getProjectSummary({
87
+ projectId: options.project,
88
+ projectPath: options.projectPath,
89
+ eventLimit: Number(options.limit)
90
+ });
91
+ if (options.json) {
92
+ console.log(JSON.stringify(summary, null, 2));
93
+ return;
94
+ }
95
+ console.log(chalk.bold("Canonical Shared State"));
96
+ console.log(
97
+ ` Active sessions: ${summary.activeSessions.length} | Active instances: ${summary.activeInstances.length} | Active claims: ${summary.activeClaims.length}`
98
+ );
99
+ if (summary.projectId) {
100
+ console.log(` Project: ${summary.projectId}`);
101
+ }
102
+ if (summary.activeSessions.length > 0) {
103
+ console.log(chalk.bold("\nSessions"));
104
+ for (const record of summary.activeSessions) {
105
+ console.log(
106
+ ` ${record.sessionId.slice(0, 8)} ${record.tool} ${record.branch || ""}`.trim()
107
+ );
108
+ }
109
+ }
110
+ if (summary.activeInstances.length > 0) {
111
+ console.log(chalk.bold("\nInstances"));
112
+ for (const record of summary.activeInstances) {
113
+ console.log(
114
+ ` ${record.instanceId} ${record.tool} ${record.branch || ""}`.trim()
115
+ );
116
+ }
117
+ }
118
+ if (summary.activeClaims.length > 0) {
119
+ console.log(chalk.bold("\nClaims"));
120
+ for (const claim of summary.activeClaims) {
121
+ const scopes = [
122
+ claim.branch ? `branch:${claim.branch}` : "",
123
+ ...claim.paths.map((item) => `path:${item}`)
124
+ ].filter(Boolean).join(", ");
125
+ console.log(
126
+ ` ${claim.claimId.slice(0, 8)} ${claim.tool} ${scopes || "(no scope)"}`.trim()
127
+ );
128
+ }
129
+ }
130
+ if (summary.recentEvents.length > 0) {
131
+ console.log(chalk.bold("\nRecent events"));
132
+ for (const event of summary.recentEvents) {
133
+ console.log(
134
+ ` ${event.type} ${new Date(event.timestamp).toISOString()}`
135
+ );
136
+ }
137
+ }
138
+ });
139
+ const claims = cmd.command("claims").description("Manage shared ownership claims");
140
+ claims.command("claim").requiredOption(
141
+ "--tool <tool>",
142
+ "Tool name (claude|codex|opencode|stackmemory)"
143
+ ).option("--session <id>", "Session identifier").option("--instance <id>", "Instance identifier").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--path <path...>", "Claimed file or directory path(s)").option("--ttl-ms <ttl>", "Time to live in milliseconds", "86400000").option("--metadata <json>", "Metadata JSON object").option("--json", "Emit JSON output").action(async (options) => {
144
+ const result = await canonicalStateStore.claimPaths({
145
+ tool: options.tool,
146
+ sessionId: options.session,
147
+ instanceId: options.instance,
148
+ projectId: options.project,
149
+ projectPath: options.projectPath,
150
+ branch: options.branch,
151
+ paths: options.path || [],
152
+ ttlMs: Number(options.ttlMs),
153
+ metadata: parseJsonObject(options.metadata)
154
+ });
155
+ if (options.json) {
156
+ console.log(JSON.stringify(result, null, 2));
157
+ return;
158
+ }
159
+ console.log(chalk.green(`Claimed ${result.record.claimId.slice(0, 8)}`));
160
+ if (result.conflicts.length > 0) {
161
+ console.log(chalk.yellow(`Conflicts: ${result.conflicts.length}`));
162
+ for (const conflict of result.conflicts) {
163
+ console.log(
164
+ ` ${conflict.claimId.slice(0, 8)} ${conflict.branch || ""} ${conflict.paths.join(", ")}`.trim()
165
+ );
166
+ }
167
+ }
168
+ });
169
+ claims.command("release").option("--claim <id>", "Claim identifier").option("--session <id>", "Session identifier").option("--instance <id>", "Instance identifier").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--reason <reason>", "Release reason").action(async (options) => {
170
+ const released = await canonicalStateStore.releaseClaims({
171
+ claimId: options.claim,
172
+ sessionId: options.session,
173
+ instanceId: options.instance,
174
+ projectId: options.project,
175
+ projectPath: options.projectPath,
176
+ branch: options.branch,
177
+ reason: options.reason
178
+ });
179
+ console.log(chalk.green(`Released ${released} claim(s)`));
180
+ });
181
+ claims.command("show").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--all", "Show released and expired claims too").option("--json", "Emit JSON output").action(async (options) => {
182
+ const records = await canonicalStateStore.listPathClaims({
183
+ projectId: options.project,
184
+ projectPath: options.projectPath,
185
+ activeOnly: !options.all
186
+ });
187
+ if (options.json) {
188
+ console.log(JSON.stringify(records, null, 2));
189
+ return;
190
+ }
191
+ if (records.length === 0) {
192
+ console.log(chalk.yellow("No claims found"));
193
+ return;
194
+ }
195
+ for (const claim of records) {
196
+ const scopes = [
197
+ claim.branch ? `branch:${claim.branch}` : "",
198
+ ...claim.paths.map((item) => `path:${item}`)
199
+ ].filter(Boolean).join(", ");
200
+ console.log(
201
+ `${claim.claimId.slice(0, 8)} ${claim.status} ${claim.tool} ${scopes || "(no scope)"}`
202
+ );
203
+ }
204
+ });
205
+ const github = cmd.command("github").description("GitHub projection state");
206
+ github.command("refresh").description("Refresh current repo branch PR state from GitHub CLI").option("--json", "Emit JSON output").action(async (options) => {
207
+ const projection = await refreshCurrentRepoPullRequestState();
208
+ if (!projection) {
209
+ console.log(
210
+ chalk.yellow(
211
+ "No GitHub PR projection available for current repo/branch"
212
+ )
213
+ );
214
+ return;
215
+ }
216
+ if (options.json) {
217
+ console.log(JSON.stringify(projection, null, 2));
218
+ return;
219
+ }
220
+ console.log(chalk.green(`Refreshed PR #${projection.prNumber}`));
221
+ console.log(`${projection.state} ${projection.title}`);
222
+ console.log(projection.url);
223
+ });
224
+ github.command("show").description("Show cached current repo branch PR projection").option("--json", "Emit JSON output").action(async (options) => {
225
+ const info = getCurrentRepoGitHubInfo();
226
+ if (!info) {
227
+ console.log(chalk.yellow("Not in a GitHub repository"));
228
+ return;
229
+ }
230
+ const projection = await canonicalStateStore.getGitHubPullRequest({
231
+ repo: info.repo,
232
+ branch: info.branch
233
+ });
234
+ if (!projection) {
235
+ console.log(
236
+ chalk.yellow("No cached GitHub PR projection for current branch")
237
+ );
238
+ return;
239
+ }
240
+ if (options.json) {
241
+ console.log(JSON.stringify(projection, null, 2));
242
+ return;
243
+ }
244
+ console.log(chalk.bold(`PR #${projection.prNumber}`));
245
+ console.log(`${projection.state} ${projection.title}`);
246
+ console.log(`Repo: ${projection.repo}`);
247
+ console.log(
248
+ `Branch: ${projection.headRefName} -> ${projection.baseRefName}`
249
+ );
250
+ if (projection.reviewDecision) {
251
+ console.log(`Review: ${projection.reviewDecision}`);
252
+ }
253
+ if (projection.statusCheckRollup) {
254
+ console.log(`Checks: ${projection.statusCheckRollup}`);
255
+ }
256
+ console.log(`Synced: ${new Date(projection.lastSyncedAt).toISOString()}`);
257
+ console.log(projection.url);
258
+ });
259
+ return cmd;
260
+ }
261
+ var state_default = createStateCommand;
262
+ export {
263
+ createStateCommand,
264
+ state_default as default
265
+ };
@@ -173,6 +173,39 @@ function createWikiCommand() {
173
173
  console.log(chalk.gray(` Articles updated: ${result.updated.length}`));
174
174
  console.log(chalk.gray(` Total articles: ${result.totalArticles}`));
175
175
  });
176
+ cmd.command("ingest <source>").description("Ingest a URL or local path into the wiki").option("--wiki-dir <path>", "Override wiki directory").option("-n, --max-pages <n>", "Max pages to crawl for URLs", "20").option("--json", "Output as JSON").action(async (source, options) => {
177
+ const compiler = getCompiler(options.wikiDir);
178
+ await compiler.initialize();
179
+ const isUrl = source.startsWith("http://") || source.startsWith("https://");
180
+ let result;
181
+ if (isUrl) {
182
+ console.log(
183
+ chalk.cyan(`Crawling ${source} (max ${options.maxPages} pages)...`)
184
+ );
185
+ result = await compiler.ingestUrl(source, {
186
+ maxPages: parseInt(options.maxPages)
187
+ });
188
+ } else {
189
+ console.log(chalk.cyan(`Ingesting ${source}...`));
190
+ result = await compiler.ingestPath(source);
191
+ }
192
+ if (options.json) {
193
+ console.log(JSON.stringify(result, null, 2));
194
+ return;
195
+ }
196
+ console.log(chalk.green("\nIngested."));
197
+ console.log(chalk.gray(` Articles created: ${result.created.length}`));
198
+ console.log(chalk.gray(` Total articles: ${result.totalArticles}`));
199
+ if (result.created.length > 0) {
200
+ console.log(chalk.gray("\n Created:"));
201
+ result.created.slice(0, 10).forEach((p) => console.log(chalk.gray(` - ${p}`)));
202
+ if (result.created.length > 10) {
203
+ console.log(
204
+ chalk.gray(` ...and ${result.created.length - 10} more`)
205
+ );
206
+ }
207
+ }
208
+ });
176
209
  cmd.command("lint").description("Health check the wiki for issues").option("--wiki-dir <path>", "Override wiki directory").option("--json", "Output as JSON").action(async (options) => {
177
210
  const compiler = getCompiler(options.wikiDir);
178
211
  await compiler.initialize();
@@ -13,6 +13,7 @@ import { program } from "commander";
13
13
  import { v4 as uuidv4 } from "uuid";
14
14
  import chalk from "chalk";
15
15
  import { initializeTracing, trace } from "../core/trace/index.js";
16
+ import { resolveRealCliBin } from "./utils/real-cli-bin.js";
16
17
  const DEFAULT_SM_CONFIG = {
17
18
  defaultWorktree: false,
18
19
  defaultTracing: true
@@ -125,35 +126,24 @@ class GeminiSM {
125
126
  }
126
127
  }
127
128
  resolveGeminiBin() {
128
- if (this.config.geminiBin && this.config.geminiBin.trim()) {
129
- return this.config.geminiBin.trim();
130
- }
131
- const envBin = process.env["GEMINI_BIN"];
132
- if (envBin && envBin.trim()) return envBin.trim();
133
- const possiblePaths = [
134
- path.join(
135
- os.homedir(),
136
- ".nvm",
137
- "versions",
138
- "node",
139
- "v22.22.0",
140
- "bin",
141
- "gemini"
142
- ),
143
- "/usr/local/bin/gemini",
144
- "/opt/homebrew/bin/gemini"
145
- ];
146
- for (const binPath of possiblePaths) {
147
- if (fs.existsSync(binPath)) {
148
- return binPath;
149
- }
150
- }
151
- try {
152
- execSync("which gemini", { stdio: "ignore" });
153
- return "gemini";
154
- } catch {
155
- }
156
- return null;
129
+ return resolveRealCliBin({
130
+ explicitBin: this.config.geminiBin,
131
+ envBin: process.env["GEMINI_BIN"],
132
+ preferredPaths: [
133
+ path.join(
134
+ os.homedir(),
135
+ ".nvm",
136
+ "versions",
137
+ "node",
138
+ "v22.22.0",
139
+ "bin",
140
+ "gemini"
141
+ ),
142
+ "/usr/local/bin/gemini",
143
+ "/opt/homebrew/bin/gemini"
144
+ ],
145
+ pathCommands: ["gemini"]
146
+ });
157
147
  }
158
148
  setupWorktree() {
159
149
  if (!this.config.useWorktree || !this.isGitRepo()) {
@@ -4,6 +4,9 @@ import { dirname as __pathDirname } from 'path';
4
4
  const __filename = __fileURLToPath(import.meta.url);
5
5
  const __dirname = __pathDirname(__filename);
6
6
  process.env["STACKMEMORY_CLI"] = "true";
7
+ if (!process.env["STACKMEMORY_LOG_LEVEL"] && process.argv.includes("--json")) {
8
+ process.env["STACKMEMORY_LOG_LEVEL"] = "ERROR";
9
+ }
7
10
  import { config as loadDotenv } from "dotenv";
8
11
  loadDotenv({ quiet: true });
9
12
  import { initializeTracing, trace } from "../core/trace/index.js";
@@ -12,7 +15,6 @@ import { program } from "commander";
12
15
  import { logger } from "../core/monitoring/logger.js";
13
16
  import { FrameManager } from "../core/context/index.js";
14
17
  import { sessionManager, FrameQueryMode } from "../core/session/index.js";
15
- import { sharedContextLayer } from "../core/context/shared-context-layer.js";
16
18
  import { UpdateChecker } from "../core/utils/update-checker.js";
17
19
  import { ProgressTracker } from "../core/monitoring/progress-tracker.js";
18
20
  import { registerProjectCommands } from "./commands/projects.js";
@@ -54,8 +56,9 @@ import { createPingCommand } from "./commands/ping.js";
54
56
  import { createAuditCommand } from "./commands/audit.js";
55
57
  import { createStatsCommand } from "./commands/stats.js";
56
58
  import { createBenchCommand } from "./commands/bench.js";
59
+ import { createOptimizeCommand } from "./commands/optimize.js";
60
+ import { createStateCommand } from "./commands/state.js";
57
61
  import { createDigestCommands } from "./commands/digest.js";
58
- import { createTeamCommands } from "./commands/team.js";
59
62
  import { createDesiresCommands } from "./commands/desires.js";
60
63
  import { createConductorCommands } from "./commands/orchestrate.js";
61
64
  import { createPreflightCommand } from "./commands/preflight.js";
@@ -64,11 +67,18 @@ import { createSnapshotCommand } from "./commands/snapshot.js";
64
67
  import { createWikiCommand } from "./commands/wiki.js";
65
68
  import { createLoopCommand } from "./commands/loop.js";
66
69
  import { createSkillCommand } from "./commands/skill.js";
70
+ import { createPackCommand } from "./commands/pack.js";
71
+ import { createCacheCommand } from "./commands/cache.js";
67
72
  import chalk from "chalk";
68
73
  import * as fs from "fs";
69
74
  import * as path from "path";
70
75
  import { filterPending } from "../integrations/mcp/pending-utils.js";
76
+ import {
77
+ getCurrentRepoGitHubInfo,
78
+ refreshCurrentRepoPullRequestState
79
+ } from "../integrations/github/pr-state.js";
71
80
  import { ProjectManager } from "../core/projects/project-manager.js";
81
+ import { canonicalStateStore } from "../core/shared-state/canonical-store.js";
72
82
  import { join } from "path";
73
83
  import { existsSync, mkdirSync } from "fs";
74
84
  import { createRequire } from "module";
@@ -105,10 +115,13 @@ async function openDatabase(dbPath) {
105
115
  function isTestEnv() {
106
116
  return process.env["VITEST"] === "true" || process.env["NODE_ENV"] === "test" || process.env["STACKMEMORY_TEST_SKIP_DB"] === "1";
107
117
  }
118
+ function collectRepeatedOption(value, previous = []) {
119
+ return [...previous, value];
120
+ }
108
121
  UpdateChecker.checkForUpdates(VERSION, true).catch(() => {
109
122
  });
110
123
  program.name("stackmemory").description(
111
- "Lossless memory runtime for AI coding tools - organizes context as a call stack instead of linear chat logs, with team collaboration and infinite retention"
124
+ "Lossless memory runtime for AI coding tools - organizes context as a call stack instead of linear chat logs, with infinite retention"
112
125
  ).version(VERSION);
113
126
  program.command("init").description(
114
127
  "Initialize StackMemory in current project (zero-config by default)"
@@ -198,33 +211,24 @@ program.command("status").description("Show current StackMemory status").option(
198
211
  }
199
212
  await UpdateChecker.checkForUpdates(VERSION);
200
213
  await sessionManager.initialize();
201
- await sharedContextLayer.initialize();
202
214
  const { initObsidianVault } = await import("../core/storage/obsidian-vault-adapter.js");
203
215
  await initObsidianVault();
204
216
  const session = await sessionManager.getOrCreateSession({
205
217
  projectPath: projectRoot,
206
218
  sessionId: options.session
207
219
  });
208
- const contextDiscovery = await sharedContextLayer.autoDiscoverContext();
209
- if (contextDiscovery.hasSharedContext && contextDiscovery.sessionCount > 1) {
210
- console.log(`
211
- \u{1F4A1} Shared Context Available:`);
212
- console.log(
213
- ` ${contextDiscovery.sessionCount} sessions with shared context`
214
- );
215
- if (contextDiscovery.recentPatterns.length > 0) {
216
- console.log(` Recent patterns:`);
217
- contextDiscovery.recentPatterns.slice(0, 3).forEach((p) => {
218
- console.log(
219
- ` \u2022 ${p.type}: ${p.pattern.slice(0, 50)} (${p.frequency}x)`
220
- );
221
- });
222
- }
223
- if (contextDiscovery.lastDecisions.length > 0) {
224
- console.log(
225
- ` Last decision: ${contextDiscovery.lastDecisions[0].decision.slice(0, 60)}`
226
- );
227
- }
220
+ const sharedProjectState = await canonicalStateStore.getProjectSummary({
221
+ projectId: session.projectId,
222
+ projectPath: projectRoot,
223
+ eventLimit: 5
224
+ });
225
+ const githubInfo = getCurrentRepoGitHubInfo(projectRoot);
226
+ let githubProjection = githubInfo && await canonicalStateStore.getGitHubPullRequest({
227
+ repo: githubInfo.repo,
228
+ branch: githubInfo.branch
229
+ });
230
+ if (githubInfo && (!githubProjection || Date.now() - githubProjection.lastSyncedAt > 2 * 60 * 1e3)) {
231
+ githubProjection = await refreshCurrentRepoPullRequestState(projectRoot) || githubProjection;
228
232
  }
229
233
  const db = await openDatabase(dbPath);
230
234
  const frameManager = new FrameManager(db, session.projectId);
@@ -276,6 +280,23 @@ program.command("status").description("Show current StackMemory status").option(
276
280
  console.log(
277
281
  ` Cached contexts: ${contextCount.count || 0} (global)`
278
282
  );
283
+ console.log(
284
+ ` Shared sessions: ${sharedProjectState.activeSessions.length}`
285
+ );
286
+ console.log(
287
+ ` Shared instances: ${sharedProjectState.activeInstances.length}`
288
+ );
289
+ console.log(
290
+ ` Shared claims: ${sharedProjectState.activeClaims.length}`
291
+ );
292
+ const branchClaim = sharedProjectState.activeClaims.find(
293
+ (claim) => claim.branch && claim.branch === session.branch
294
+ );
295
+ if (branchClaim) {
296
+ console.log(
297
+ ` Branch owner: ${branchClaim.tool} ${branchClaim.instanceId || branchClaim.sessionId || branchClaim.claimId.slice(0, 8)}`
298
+ );
299
+ }
279
300
  const recentFrames = db.prepare(
280
301
  `
281
302
  SELECT name, type, state, datetime(created_at, 'unixepoch') as created
@@ -295,6 +316,22 @@ program.command("status").description("Show current StackMemory status").option(
295
316
  );
296
317
  });
297
318
  }
319
+ if (githubProjection) {
320
+ console.log(`
321
+ GitHub PR:`);
322
+ console.log(
323
+ ` #${githubProjection.prNumber} ${githubProjection.state} ${githubProjection.title}`
324
+ );
325
+ console.log(
326
+ ` ${githubProjection.headRefName} -> ${githubProjection.baseRefName}`
327
+ );
328
+ if (githubProjection.reviewDecision) {
329
+ console.log(` Review: ${githubProjection.reviewDecision}`);
330
+ }
331
+ if (githubProjection.statusCheckRollup) {
332
+ console.log(` Checks: ${githubProjection.statusCheckRollup}`);
333
+ }
334
+ }
298
335
  console.log(`
299
336
  Current Session:`);
300
337
  console.log(` Stack depth: ${stackDepth}`);
@@ -571,8 +608,9 @@ program.addCommand(createModelCommand());
571
608
  program.addCommand(createAuditCommand());
572
609
  program.addCommand(createStatsCommand());
573
610
  program.addCommand(createBenchCommand());
611
+ program.addCommand(createOptimizeCommand());
612
+ program.addCommand(createStateCommand());
574
613
  program.addCommand(createDigestCommands());
575
- program.addCommand(createTeamCommands());
576
614
  program.addCommand(createDesiresCommands());
577
615
  program.addCommand(createConductorCommands());
578
616
  program.addCommand(createPreflightCommand());
@@ -581,6 +619,8 @@ program.addCommand(createWikiCommand());
581
619
  program.addCommand(createLoopCommand());
582
620
  program.addCommand(createRulesCommand());
583
621
  program.addCommand(createSkillCommand());
622
+ program.addCommand(createPackCommand());
623
+ program.addCommand(createCacheCommand());
584
624
  registerSetupCommands(program);
585
625
  program.command("mm-spike").description(
586
626
  "Run multi-agent planning/implementation spike (planner/implementer/critic)"
@@ -600,7 +640,16 @@ program.command("mm-spike").description(
600
640
  "--execute",
601
641
  "Execute implementer (codex-sm) instead of dry-run",
602
642
  false
603
- ).option("--implementer <name>", "codex|claude", "codex").option("--max-iters <n>", "Retry loop iterations", "2").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).option(
643
+ ).option("--implementer <name>", "codex|claude", "codex").option("--max-iters <n>", "Retry loop iterations", "2").option("--audit-dir <path>", "Persist spike results to directory").option(
644
+ "--verify <cmd>",
645
+ "Verification command to run after each implementation attempt; repeatable",
646
+ collectRepeatedOption,
647
+ []
648
+ ).option("--record-frame", "Record as real frame with anchors", false).option("--record", "Record plan & critique into StackMemory context", false).option(
649
+ "--deterministic-fixture",
650
+ "Use deterministic fixture planner/critic for replayable smoke runs",
651
+ false
652
+ ).option("--json", "Emit single JSON result (UI-friendly)", false).option("--quiet", "Minimal output (default)", true).option("--verbose", "Verbose sectioned output", false).option(
604
653
  "--log",
605
654
  "Pretty print interaction log (planner/implementer/critic)",
606
655
  false
@@ -619,8 +668,10 @@ program.command("mm-spike").description(
619
668
  maxIters: parseInt(opts.maxIters),
620
669
  dryRun: !opts.execute,
621
670
  auditDir: opts.auditDir,
671
+ verificationCommands: opts.verify,
622
672
  recordFrame: Boolean(opts.recordFrame),
623
- record: Boolean(opts.record)
673
+ record: Boolean(opts.record),
674
+ deterministicFixture: Boolean(opts.deterministicFixture)
624
675
  }
625
676
  );
626
677
  if (opts.log) {
@@ -677,7 +728,15 @@ program.command("build").description(
677
728
  "--reviewer-model <name>",
678
729
  "Claude model for review",
679
730
  "claude-sonnet-4-20250514"
680
- ).option("--execute", "Execute implementer (default: true)", true).option("--dry-run", "Skip execution, show commands only").option("--implementer <name>", "codex|claude", "codex").option("--max-iters <n>", "Retry loop iterations", "2").option("--audit-dir <path>", "Persist spike results to directory").option("--record-frame", "Record as real frame with anchors").option("--record", "Record plan & critique into StackMemory context").option("--json", "Emit single JSON result (UI-friendly)").option("--quiet", "Minimal output").option("--verbose", "Verbose sectioned output").option("--log", "Pretty print interaction log (default: true)", true).option("-C, --cwd <path>", "Working directory for implementation").action(async (taskArg, opts) => {
731
+ ).option("--execute", "Execute implementer (default: true)", true).option("--dry-run", "Skip execution, show commands only").option("--implementer <name>", "codex|claude", "codex").option("--max-iters <n>", "Retry loop iterations", "2").option("--audit-dir <path>", "Persist spike results to directory").option(
732
+ "--verify <cmd>",
733
+ "Verification command to run after each implementation attempt; repeatable",
734
+ collectRepeatedOption,
735
+ []
736
+ ).option("--record-frame", "Record as real frame with anchors").option("--record", "Record plan & critique into StackMemory context").option(
737
+ "--deterministic-fixture",
738
+ "Use deterministic fixture planner/critic for replayable smoke runs"
739
+ ).option("--json", "Emit single JSON result (UI-friendly)").option("--quiet", "Minimal output").option("--verbose", "Verbose sectioned output").option("--log", "Pretty print interaction log (default: true)", true).option("-C, --cwd <path>", "Working directory for implementation").action(async (taskArg, opts) => {
681
740
  try {
682
741
  const task = typeof taskArg === "string" && taskArg.length > 0 ? taskArg : opts.task;
683
742
  if (!task) {
@@ -713,8 +772,10 @@ program.command("build").description(
713
772
  maxIters: parseInt(opts.maxIters),
714
773
  dryRun,
715
774
  auditDir: opts.auditDir,
775
+ verificationCommands: opts.verify,
716
776
  recordFrame: Boolean(opts.recordFrame),
717
- record: Boolean(opts.record)
777
+ record: Boolean(opts.record),
778
+ deterministicFixture: Boolean(opts.deterministicFixture)
718
779
  }
719
780
  );
720
781
  if (opts.log) {