@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.
Files changed (225) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +107 -24
  3. package/bin/claude-sm +16 -1
  4. package/bin/claude-smd +16 -1
  5. package/bin/codex-smd +16 -1
  6. package/bin/gemini-sm +16 -1
  7. package/bin/hermes-sm +21 -0
  8. package/bin/hermes-smd +21 -0
  9. package/bin/opencode-sm +16 -1
  10. package/dist/src/cli/claude-sm.js +266 -84
  11. package/dist/src/cli/codex-sm.js +225 -33
  12. package/dist/src/cli/commands/bench.js +209 -2
  13. package/dist/src/cli/commands/brain.js +206 -0
  14. package/dist/src/cli/commands/cache.js +126 -0
  15. package/dist/src/cli/commands/company-os.js +184 -0
  16. package/dist/src/cli/commands/context.js +5 -0
  17. package/dist/src/cli/commands/daemon.js +41 -0
  18. package/dist/src/cli/commands/handoff.js +40 -9
  19. package/dist/src/cli/commands/onboard.js +70 -3
  20. package/dist/src/cli/commands/operator.js +127 -0
  21. package/dist/src/cli/commands/optimize.js +117 -0
  22. package/dist/src/cli/commands/orchestrate.js +232 -5
  23. package/dist/src/cli/commands/orchestrator.js +315 -26
  24. package/dist/src/cli/commands/pack.js +322 -0
  25. package/dist/src/cli/commands/patterns.js +254 -0
  26. package/dist/src/cli/commands/portal.js +161 -0
  27. package/dist/src/cli/commands/scaffold.js +92 -0
  28. package/dist/src/cli/commands/search.js +40 -1
  29. package/dist/src/cli/commands/setup.js +178 -11
  30. package/dist/src/cli/commands/skills.js +10 -1
  31. package/dist/src/cli/commands/state.js +265 -0
  32. package/dist/src/cli/commands/sync.js +253 -0
  33. package/dist/src/cli/commands/tasks.js +130 -1
  34. package/dist/src/cli/commands/vision.js +221 -0
  35. package/dist/src/cli/gemini-sm.js +19 -29
  36. package/dist/src/cli/hermes-sm.js +224 -0
  37. package/dist/src/cli/index.js +105 -39
  38. package/dist/src/cli/opencode-sm.js +38 -21
  39. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  40. package/dist/src/cli/utils/real-cli-bin.js +116 -0
  41. package/dist/src/core/brain/brain-store.js +187 -0
  42. package/dist/src/core/brain/brain-sync.js +193 -0
  43. package/dist/src/core/brain/index.js +78 -0
  44. package/dist/src/core/brain/types.js +10 -0
  45. package/dist/src/core/cache/content-cache.js +238 -0
  46. package/dist/src/{integrations/diffmem → core/cache}/index.js +5 -5
  47. package/dist/src/core/cache/token-estimator.js +39 -0
  48. package/dist/src/core/config/feature-flags.js +2 -6
  49. package/dist/src/core/context/frame-database.js +79 -27
  50. package/dist/src/core/context/recursive-context-manager.js +1 -1
  51. package/dist/src/core/context/rehydration.js +2 -1
  52. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  53. package/dist/src/core/cross-search/index.js +10 -0
  54. package/dist/src/core/database/sqlite-adapter.js +14 -84
  55. package/dist/src/core/extensions/provider-adapter.js +5 -0
  56. package/dist/src/core/models/model-router.js +54 -2
  57. package/dist/src/core/models/provider-pricing.js +58 -4
  58. package/dist/src/core/monitoring/logger.js +2 -1
  59. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  60. package/dist/src/core/patterns/index.js +22 -0
  61. package/dist/src/core/patterns/pattern-applier.js +39 -0
  62. package/dist/src/core/patterns/pattern-observer.js +157 -0
  63. package/dist/src/core/patterns/pattern-store.js +259 -0
  64. package/dist/src/core/patterns/types.js +19 -0
  65. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  66. package/dist/src/core/provenance/index.js +40 -0
  67. package/dist/src/core/provenance/provenance-store.js +194 -0
  68. package/dist/src/core/provenance/types.js +82 -0
  69. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  70. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  71. package/dist/src/core/session/project-handoff.js +64 -0
  72. package/dist/src/core/session/session-manager.js +28 -0
  73. package/dist/src/core/shared-state/canonical-store.js +564 -0
  74. package/dist/src/core/skill-packs/index.js +18 -0
  75. package/dist/src/core/skill-packs/parser.js +42 -0
  76. package/dist/src/core/skill-packs/registry.js +224 -0
  77. package/dist/src/core/skill-packs/types.js +79 -0
  78. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  79. package/dist/src/core/storage/cloud-sync.js +574 -0
  80. package/dist/src/core/storage/two-tier-storage.js +5 -1
  81. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  82. package/dist/src/core/tasks/md-task-parser.js +138 -0
  83. package/dist/src/core/trace/trace-event-store.js +282 -0
  84. package/dist/src/core/vision/index.js +27 -0
  85. package/dist/src/core/vision/signals.js +79 -0
  86. package/dist/src/core/vision/types.js +22 -0
  87. package/dist/src/core/vision/vision-file.js +146 -0
  88. package/dist/src/core/vision/vision-loop.js +220 -0
  89. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  90. package/dist/src/daemon/daemon-config.js +52 -0
  91. package/dist/src/daemon/services/desire-path-service.js +566 -0
  92. package/dist/src/daemon/services/github-service.js +126 -0
  93. package/dist/src/daemon/services/research-stream-service.js +320 -0
  94. package/dist/src/daemon/services/telemetry-service.js +192 -0
  95. package/dist/src/daemon/unified-daemon.js +58 -1
  96. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  97. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  98. package/dist/src/features/operator/adapter-factory.js +62 -0
  99. package/dist/src/features/operator/browser-adapter.js +109 -0
  100. package/dist/src/features/operator/desktop-adapter.js +125 -0
  101. package/dist/src/features/operator/index.js +39 -0
  102. package/dist/src/features/operator/llm-decision.js +137 -0
  103. package/dist/src/features/operator/operator-logger.js +92 -0
  104. package/dist/src/features/operator/overnight-runner.js +327 -0
  105. package/dist/src/features/operator/screen-adapter.js +91 -0
  106. package/dist/src/features/operator/session-manager.js +127 -0
  107. package/dist/src/features/operator/state-machine.js +227 -0
  108. package/dist/src/features/operator/task-queue.js +81 -0
  109. package/dist/src/features/portal/index.js +26 -0
  110. package/dist/src/features/portal/server.js +240 -0
  111. package/dist/src/features/portal/types.js +14 -0
  112. package/dist/src/features/portal/ui.js +195 -0
  113. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  114. package/dist/src/features/tasks/task-aware-context.js +2 -1
  115. package/dist/src/features/tui/simple-monitor.js +0 -23
  116. package/dist/src/features/tui/swarm-monitor.js +8 -66
  117. package/dist/src/features/web/client/hooks/use-socket.js +12 -0
  118. package/dist/src/{core/merge/index.js → features/web/client/lib/utils.js} +8 -4
  119. package/dist/src/features/web/client/next-env.d.js +4 -0
  120. package/dist/src/features/web/client/stores/session-store.js +12 -0
  121. package/dist/src/features/web/server/gcp-billing.js +76 -0
  122. package/dist/src/features/web/server/index.js +10 -0
  123. package/dist/src/features/web/server/spend-calculator.js +228 -0
  124. package/dist/src/hooks/schemas.js +6 -1
  125. package/dist/src/integrations/anthropic/client.js +3 -2
  126. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  127. package/dist/src/integrations/claude-code/subagent-client.js +307 -11
  128. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  129. package/dist/src/integrations/github/pr-state.js +158 -0
  130. package/dist/src/integrations/linear/client.js +4 -1
  131. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  132. package/dist/src/integrations/linear/webhook-server.js +18 -22
  133. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  134. package/dist/src/integrations/mcp/handlers/index.js +40 -84
  135. package/dist/src/integrations/mcp/server.js +542 -641
  136. package/dist/src/integrations/mcp/tool-alias-registry.js +297 -0
  137. package/dist/src/integrations/mcp/tool-definitions.js +152 -682
  138. package/dist/src/mcp/stackmemory-mcp-server.js +571 -231
  139. package/dist/src/orchestrators/multimodal/determinism.js +244 -0
  140. package/dist/src/orchestrators/multimodal/harness.js +149 -78
  141. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  142. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  143. package/dist/src/utils/hook-installer.js +0 -8
  144. package/dist/src/utils/process-cleanup.js +1 -7
  145. package/docs/README.md +42 -0
  146. package/docs/guides/README_INSTALL.md +208 -0
  147. package/package.json +27 -9
  148. package/packs/coding/python-fastapi/instructions.md +60 -0
  149. package/packs/coding/python-fastapi/pack.yaml +28 -0
  150. package/packs/coding/typescript-react/instructions.md +47 -0
  151. package/packs/coding/typescript-react/pack.yaml +28 -0
  152. package/packs/core/commands/capture.md +32 -0
  153. package/packs/core/commands/learn.md +73 -0
  154. package/packs/core/commands/next.md +36 -0
  155. package/packs/core/commands/restart.md +58 -0
  156. package/packs/core/commands/restore.md +29 -0
  157. package/packs/core/commands/start.md +57 -0
  158. package/packs/core/commands/stop.md +65 -0
  159. package/packs/core/commands/summary.md +40 -0
  160. package/packs/core/manifest.json +24 -0
  161. package/packs/ops/decision-recovery/instructions.md +65 -0
  162. package/packs/ops/decision-recovery/pack.yaml +89 -0
  163. package/scripts/claude-code-wrapper.sh +11 -0
  164. package/scripts/claude-sm-setup.sh +12 -1
  165. package/scripts/codex-wrapper.sh +11 -0
  166. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  167. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  168. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  169. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  170. package/scripts/hooks/cleanup-shell.sh +12 -1
  171. package/scripts/hooks/task-complete.sh +12 -1
  172. package/scripts/install-code-execution-hooks.sh +12 -1
  173. package/scripts/install-sweep-hook.sh +12 -0
  174. package/scripts/install.sh +11 -0
  175. package/scripts/opencode-wrapper.sh +11 -0
  176. package/scripts/portal/cloud-init.yaml +69 -0
  177. package/scripts/portal/setup.sh +69 -0
  178. package/scripts/portal/stackmemory-portal.service +34 -0
  179. package/scripts/setup-claude-integration.sh +12 -1
  180. package/scripts/smoke-init-db.sh +23 -0
  181. package/scripts/stackmemory-daemon.sh +11 -0
  182. package/scripts/verify-dist.cjs +11 -4
  183. package/dist/src/cli/commands/ralph.js +0 -1053
  184. package/dist/src/cli/commands/team.js +0 -168
  185. package/dist/src/core/context/shared-context-layer.js +0 -620
  186. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  187. package/dist/src/core/merge/conflict-detector.js +0 -430
  188. package/dist/src/core/merge/resolution-engine.js +0 -557
  189. package/dist/src/core/merge/stack-diff.js +0 -531
  190. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  191. package/dist/src/hooks/diffmem-hooks.js +0 -376
  192. package/dist/src/integrations/diffmem/client.js +0 -208
  193. package/dist/src/integrations/diffmem/config.js +0 -14
  194. package/dist/src/integrations/greptile/client.js +0 -101
  195. package/dist/src/integrations/greptile/config.js +0 -14
  196. package/dist/src/integrations/greptile/index.js +0 -11
  197. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  198. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  199. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  200. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  201. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  202. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  203. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  204. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -391
  205. package/dist/src/integrations/ralph/index.js +0 -17
  206. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -435
  207. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  208. package/dist/src/integrations/ralph/loopmax.js +0 -488
  209. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  210. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  211. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  212. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  213. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  214. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  215. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  216. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  217. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  218. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  219. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1007
  220. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  221. package/scripts/ralph-loop-implementation.js +0 -404
  222. /package/dist/src/core/{merge → cache}/types.js +0 -0
  223. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  224. /package/dist/src/{integrations/greptile/types.js → core/trace/trace-event.js} +0 -0
  225. /package/dist/src/{integrations/ralph → features/operator}/types.js +0 -0
@@ -11,9 +11,26 @@ import { program } from "commander";
11
11
  import { v4 as uuidv4 } from "uuid";
12
12
  import chalk from "chalk";
13
13
  import { initializeTracing, trace } from "../core/trace/index.js";
14
+ import {
15
+ resolveRealCliBin,
16
+ resolveNativeCodexBin,
17
+ resolveNvmBin
18
+ } from "./utils/real-cli-bin.js";
19
+ import {
20
+ startDeterminismWatcher,
21
+ stopDeterminismWatcher
22
+ } from "./utils/determinism-watcher.js";
23
+ import {
24
+ canonicalStateStore,
25
+ projectIdFromIdentifier
26
+ } from "../core/shared-state/canonical-store.js";
14
27
  class CodexSM {
15
28
  config;
16
29
  stackmemoryPath;
30
+ sessionId;
31
+ ownsSession;
32
+ sessionEnded;
33
+ determinismWatcher;
17
34
  constructor() {
18
35
  this.config = {
19
36
  instanceId: this.generateInstanceId(),
@@ -23,6 +40,10 @@ class CodexSM {
23
40
  verboseTracing: false
24
41
  };
25
42
  this.stackmemoryPath = this.findStackMemory();
43
+ this.sessionId = process.env["STACKMEMORY_SESSION"] || uuidv4();
44
+ this.ownsSession = !process.env["STACKMEMORY_SESSION"];
45
+ this.sessionEnded = false;
46
+ this.determinismWatcher = null;
26
47
  }
27
48
  getRepoRoot() {
28
49
  try {
@@ -87,6 +108,20 @@ class CodexSM {
87
108
  return "main";
88
109
  }
89
110
  }
111
+ getProjectId() {
112
+ const root = this.getRepoRoot() || process.cwd();
113
+ try {
114
+ const remote = execSync("git config --get remote.origin.url", {
115
+ cwd: root,
116
+ encoding: "utf8"
117
+ }).trim();
118
+ if (remote) {
119
+ return projectIdFromIdentifier(remote);
120
+ }
121
+ } catch {
122
+ }
123
+ return projectIdFromIdentifier(root);
124
+ }
90
125
  hasUncommittedChanges() {
91
126
  try {
92
127
  const status = execSync("git status --porcelain", { encoding: "utf8" });
@@ -96,24 +131,21 @@ class CodexSM {
96
131
  }
97
132
  }
98
133
  resolveCodexBin() {
99
- if (this.config.codexBin && this.config.codexBin.trim()) {
100
- return this.config.codexBin.trim();
101
- }
102
- const envBin = process.env["CODEX_BIN"];
103
- if (envBin && envBin.trim()) {
104
- return envBin.trim();
105
- }
106
- try {
107
- execSync("which codex", { stdio: "ignore" });
108
- return "codex";
109
- } catch {
110
- }
111
- try {
112
- execSync("which codex-cli", { stdio: "ignore" });
113
- return "codex-cli";
114
- } catch {
115
- }
116
- return null;
134
+ return resolveRealCliBin({
135
+ explicitBin: this.config.codexBin,
136
+ envBin: process.env["CODEX_BIN"],
137
+ preferredPaths: [
138
+ ...resolveNativeCodexBin(),
139
+ // native binary first (no wrapper overhead)
140
+ resolveNvmBin("codex"),
141
+ // dynamic nvm/fnm/volta path
142
+ "/usr/local/bin/codex",
143
+ "/opt/homebrew/bin/codex",
144
+ "/usr/local/bin/codex-cli",
145
+ "/opt/homebrew/bin/codex-cli"
146
+ ].filter((p) => !!p),
147
+ pathCommands: ["codex", "codex-cli"]
148
+ });
117
149
  }
118
150
  setupWorktree() {
119
151
  if (!this.config.useWorktree || !this.isGitRepo()) return null;
@@ -169,22 +201,58 @@ class CodexSM {
169
201
  } catch {
170
202
  }
171
203
  }
204
+ /**
205
+ * Emit context budget advice based on tool-call count from checkpoint state.
206
+ * Mirrors the Claude Code context-budget.js hook.
207
+ */
208
+ emitContextBudgetAdvice() {
209
+ const COMPACT_SUGGEST = 50;
210
+ const COMPACT_STRONG = 65;
211
+ const RESTART_RECOMMEND = 80;
212
+ try {
213
+ const stateFile = path.join(
214
+ os.homedir(),
215
+ ".stackmemory",
216
+ `checkpoint-state-${this.config.instanceId}.json`
217
+ );
218
+ if (!fs.existsSync(stateFile)) return;
219
+ const state = JSON.parse(fs.readFileSync(stateFile, "utf-8"));
220
+ const cwd = process.cwd();
221
+ const toolCount = state.projects?.[cwd]?.toolCount || 0;
222
+ if (toolCount >= RESTART_RECOMMEND) {
223
+ console.log(
224
+ chalk.yellow(
225
+ `[CONTEXT_BUDGET] ${toolCount} tool calls (~150K+ tokens). Recommend: save context then start fresh session.`
226
+ )
227
+ );
228
+ } else if (toolCount >= COMPACT_STRONG) {
229
+ console.log(
230
+ chalk.yellow(
231
+ `[CONTEXT_BUDGET] ${toolCount} tool calls (~100-130K tokens). Context heavy \u2014 consider compacting or restarting.`
232
+ )
233
+ );
234
+ } else if (toolCount >= COMPACT_SUGGEST) {
235
+ console.log(
236
+ chalk.gray(
237
+ `[CONTEXT_BUDGET] ${toolCount} tool calls (~80-100K tokens). Context getting heavy.`
238
+ )
239
+ );
240
+ }
241
+ } catch {
242
+ }
243
+ }
172
244
  loadContext() {
173
245
  if (!this.config.contextEnabled) return;
174
246
  try {
175
247
  console.log(chalk.blue("\u{1F4DA} Loading previous context..."));
176
- const cmd = `${this.stackmemoryPath} context list --limit 5 --format json`;
177
- const output = execSync(cmd, { encoding: "utf8" });
178
- const contexts = JSON.parse(output);
179
- if (Array.isArray(contexts) && contexts.length > 0) {
180
- console.log(chalk.gray("Recent context loaded:"));
181
- contexts.forEach(
182
- (ctx) => {
183
- console.log(
184
- chalk.gray(` - ${ctx.message} (${ctx.metadata?.timestamp})`)
185
- );
186
- }
187
- );
248
+ const cmd = `${this.stackmemoryPath} context show`;
249
+ const output = execSync(cmd, {
250
+ encoding: "utf8",
251
+ stdio: ["pipe", "pipe", "pipe"]
252
+ });
253
+ const lines = output.trim().split("\n").filter((line) => line.trim());
254
+ if (lines.length > 3) {
255
+ console.log(chalk.gray("Context stack loaded"));
188
256
  }
189
257
  } catch {
190
258
  }
@@ -197,6 +265,117 @@ class CodexSM {
197
265
  );
198
266
  }
199
267
  }
268
+ async publishSessionStart() {
269
+ const projectPath = process.cwd();
270
+ const projectId = this.getProjectId();
271
+ const branch = this.isGitRepo() ? this.getCurrentBranch() : void 0;
272
+ await canonicalStateStore.upsertSession({
273
+ sessionId: this.sessionId,
274
+ tool: "codex",
275
+ projectId,
276
+ projectPath,
277
+ branch,
278
+ instanceId: this.config.instanceId,
279
+ metadata: {
280
+ task: this.config.task
281
+ }
282
+ });
283
+ await canonicalStateStore.upsertInstance({
284
+ instanceId: this.config.instanceId,
285
+ tool: "codex",
286
+ sessionId: this.sessionId,
287
+ projectId,
288
+ projectPath,
289
+ branch,
290
+ worktreePath: this.config.worktreePath,
291
+ pid: process.pid,
292
+ status: "active",
293
+ metadata: {
294
+ task: this.config.task
295
+ }
296
+ });
297
+ await canonicalStateStore.appendEvent({
298
+ type: "session_start",
299
+ tool: "codex",
300
+ sessionId: this.sessionId,
301
+ instanceId: this.config.instanceId,
302
+ projectId,
303
+ projectPath,
304
+ branch,
305
+ payload: {
306
+ task: this.config.task,
307
+ worktreePath: this.config.worktreePath
308
+ }
309
+ });
310
+ const claimResult = await canonicalStateStore.claimPaths({
311
+ tool: "codex",
312
+ sessionId: this.sessionId,
313
+ instanceId: this.config.instanceId,
314
+ projectId,
315
+ projectPath,
316
+ branch,
317
+ paths: [],
318
+ metadata: {
319
+ task: this.config.task,
320
+ scope: "branch"
321
+ }
322
+ });
323
+ if (claimResult.conflicts.length > 0) {
324
+ console.log(chalk.yellow("\u26A0\uFE0F Shared state conflict detected"));
325
+ for (const conflict of claimResult.conflicts.slice(0, 3)) {
326
+ console.log(
327
+ chalk.gray(
328
+ ` Claim ${conflict.claimId.slice(0, 8)} already owns ${conflict.branch || "overlapping work"}`
329
+ )
330
+ );
331
+ }
332
+ }
333
+ }
334
+ async publishSessionEnd(eventType, payload = {}) {
335
+ if (this.sessionEnded) {
336
+ return;
337
+ }
338
+ this.sessionEnded = true;
339
+ const projectPath = process.cwd();
340
+ const projectId = this.getProjectId();
341
+ const branch = this.isGitRepo() ? this.getCurrentBranch() : void 0;
342
+ await canonicalStateStore.appendEvent({
343
+ type: eventType,
344
+ tool: "codex",
345
+ sessionId: this.sessionId,
346
+ instanceId: this.config.instanceId,
347
+ projectId,
348
+ projectPath,
349
+ branch,
350
+ payload
351
+ });
352
+ await canonicalStateStore.releaseClaims({
353
+ instanceId: this.config.instanceId,
354
+ reason: eventType
355
+ });
356
+ await canonicalStateStore.endInstance(this.config.instanceId);
357
+ if (this.ownsSession) {
358
+ await canonicalStateStore.endSession(this.sessionId);
359
+ }
360
+ }
361
+ startDeterminismWatcher() {
362
+ this.determinismWatcher = startDeterminismWatcher({
363
+ stackmemoryBin: this.stackmemoryPath,
364
+ cwd: process.cwd(),
365
+ task: this.config.task,
366
+ instanceId: this.config.instanceId,
367
+ sessionId: this.sessionId,
368
+ tool: "codex"
369
+ });
370
+ if (this.determinismWatcher) {
371
+ const modeLabel = this.determinismWatcher.mode === "targeted" ? "targeted" : "repo-root fallback";
372
+ console.log(chalk.gray(`\u{1F9EA} Determinism: ${modeLabel}`));
373
+ }
374
+ }
375
+ stopDeterminismWatcher() {
376
+ stopDeterminismWatcher(this.determinismWatcher);
377
+ this.determinismWatcher = null;
378
+ }
200
379
  async run(args) {
201
380
  const codexArgs = [];
202
381
  let i = 0;
@@ -293,9 +472,13 @@ class CodexSM {
293
472
  }
294
473
  this.loadContext();
295
474
  process.env["CODEX_INSTANCE_ID"] = this.config.instanceId;
475
+ process.env["STACKMEMORY_SESSION"] = this.sessionId;
296
476
  if (this.config.worktreePath)
297
477
  process.env["CODEX_WORKTREE_PATH"] = this.config.worktreePath;
478
+ await this.publishSessionStart();
479
+ this.startDeterminismWatcher();
298
480
  console.log(chalk.gray(`\u{1F916} Instance ID: ${this.config.instanceId}`));
481
+ console.log(chalk.gray(`\u{1F9E0} Session ID: ${this.sessionId.slice(0, 8)}`));
299
482
  console.log(chalk.gray(`\u{1F4C1} Working in: ${process.cwd()}`));
300
483
  console.log();
301
484
  console.log(chalk.gray("Starting Codex..."));
@@ -334,11 +517,15 @@ class CodexSM {
334
517
  }
335
518
  process.exit(1);
336
519
  });
337
- child.on("exit", (code) => {
520
+ child.on("exit", async (code) => {
521
+ this.stopDeterminismWatcher();
338
522
  this.saveContext("Codex session ended", {
339
523
  action: "session_end",
340
524
  exitCode: code
341
525
  });
526
+ await this.publishSessionEnd("session_end", {
527
+ exitCode: code
528
+ });
342
529
  try {
343
530
  execSync("stackmemory linear sync", {
344
531
  stdio: "ignore",
@@ -346,6 +533,7 @@ class CodexSM {
346
533
  });
347
534
  } catch {
348
535
  }
536
+ this.emitContextBudgetAdvice();
349
537
  if (this.config.tracingEnabled) {
350
538
  const summary = trace.getExecutionSummary();
351
539
  console.log();
@@ -361,16 +549,20 @@ class CodexSM {
361
549
  }
362
550
  process.exit(code || 0);
363
551
  });
364
- process.on("SIGINT", () => {
552
+ process.on("SIGINT", async () => {
553
+ this.stopDeterminismWatcher();
365
554
  this.saveContext("Codex session interrupted", {
366
555
  action: "session_interrupt"
367
556
  });
557
+ await this.publishSessionEnd("session_interrupt");
368
558
  child.kill("SIGINT");
369
559
  });
370
- process.on("SIGTERM", () => {
560
+ process.on("SIGTERM", async () => {
561
+ this.stopDeterminismWatcher();
371
562
  this.saveContext("Codex session terminated", {
372
563
  action: "session_terminate"
373
564
  });
565
+ await this.publishSessionEnd("session_terminate");
374
566
  child.kill("SIGTERM");
375
567
  });
376
568
  }
@@ -3,6 +3,7 @@ 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 chalk from "chalk";
6
7
  import { existsSync, readFileSync, readdirSync } from "fs";
7
8
  import { join } from "path";
8
9
  import {
@@ -13,6 +14,13 @@ import {
13
14
  import {
14
15
  feedbackLoops
15
16
  } from "../../core/monitoring/feedback-loops.js";
17
+ import {
18
+ DETERMINISM_WATCH_IGNORE,
19
+ getDeterminismWatchTargets,
20
+ persistDeterminismReport,
21
+ readLatestDeterminismReport,
22
+ runDeterminismSmoke
23
+ } from "../../orchestrators/multimodal/determinism.js";
16
24
  function loadRunMetrics(projectRoot) {
17
25
  const metricsFile = join(
18
26
  projectRoot,
@@ -45,6 +53,56 @@ function loadSpikeAudits(projectRoot) {
45
53
  }
46
54
  }).filter(Boolean);
47
55
  }
56
+ function printDeterminismReport(task, requestedRuns, report) {
57
+ console.log("\nHarness Determinism Smoke");
58
+ console.log("\u2550".repeat(60));
59
+ console.log(`Task: ${task}`);
60
+ console.log(`Runs: ${report.runs}`);
61
+ console.log(`Determinism score: ${report.score.toFixed(2)}/100`);
62
+ console.log("\nDimension Scores:");
63
+ for (const dimension of report.dimensions) {
64
+ console.log(
65
+ ` ${dimension.name.padEnd(14)} ${dimension.score.toFixed(2).padStart(6)}/100 ${dimension.details}`
66
+ );
67
+ }
68
+ if (report.recommendations.length > 0) {
69
+ console.log("\nRecommended Tightening:");
70
+ for (const recommendation of report.recommendations) {
71
+ console.log(` - ${recommendation}`);
72
+ }
73
+ } else {
74
+ console.log("\nNo drift detected in deterministic fixture mode.");
75
+ }
76
+ const sample = report.snapshots[0];
77
+ if (sample) {
78
+ console.log("\nReference Snapshot:");
79
+ console.log(` resultHash: ${sample.resultHash.slice(0, 16)}`);
80
+ console.log(` planHash: ${sample.planHash.slice(0, 16)}`);
81
+ console.log(` critiqueHash: ${sample.critiqueHash.slice(0, 16)}`);
82
+ console.log(` commandsHash: ${sample.commandsHash.slice(0, 16)}`);
83
+ console.log(` iterations: ${sample.iterations}`);
84
+ console.log(` contextTokens: ${sample.contextTokens}`);
85
+ }
86
+ if (report.runs !== requestedRuns) {
87
+ console.log(
88
+ `
89
+ Note: requested ${requestedRuns} runs, completed ${report.runs}.`
90
+ );
91
+ }
92
+ console.log("");
93
+ }
94
+ function printStoredDeterminismReport(stored) {
95
+ console.log("\nCached Determinism Result");
96
+ console.log("\u2550".repeat(60));
97
+ console.log(`Task: ${stored.task}`);
98
+ console.log(`Trigger: ${stored.trigger}`);
99
+ console.log(`Timestamp: ${stored.timestamp}`);
100
+ console.log(`Determinism score: ${stored.report.score.toFixed(2)}/100`);
101
+ if (stored.changedPaths.length > 0) {
102
+ console.log(`Changed paths: ${stored.changedPaths.join(", ")}`);
103
+ }
104
+ console.log("");
105
+ }
48
106
  function createBenchCommand() {
49
107
  const bench = new Command("bench").description(
50
108
  "Harness benchmarks \u2014 compare local runs against SWE-bench baselines"
@@ -197,11 +255,160 @@ Recent Spike Audits (${audits.length}):`);
197
255
  }
198
256
  console.log("");
199
257
  });
200
- bench.command("loops").description("Show feedback loop configuration, status, and recent events").option("--json", "Output as JSON", false).action((options) => {
258
+ bench.command("determinism").description(
259
+ "Run deterministic fixture smoke checks for the multimodal harness"
260
+ ).option(
261
+ "-t, --task <desc>",
262
+ "Task description to run through the harness",
263
+ "Add a small auth guard"
264
+ ).option("--runs <n>", "Number of repeated runs", "5").option(
265
+ "--planner-model <name>",
266
+ "Planner model label to include in the run config",
267
+ "claude-sonnet-4-20250514"
268
+ ).option(
269
+ "--reviewer-model <name>",
270
+ "Reviewer model label to include in the run config",
271
+ "claude-sonnet-4-20250514"
272
+ ).option("--implementer <name>", "codex|claude", "codex").option("--max-iters <n>", "Retry loop iterations", "2").option(
273
+ "--watch",
274
+ "Watch harness-critical files and rerun on changes",
275
+ false
276
+ ).option(
277
+ "--debounce-ms <n>",
278
+ "Debounce window for write completion in watch mode",
279
+ "3000"
280
+ ).option("--latest", "Show the latest cached determinism result", false).option("--json", "Output as JSON", false).action(async function() {
281
+ const command = this;
282
+ const options = command.opts();
283
+ const json = Boolean(options.json || command.parent?.opts().json);
284
+ const projectRoot = process.cwd();
285
+ const runs = Math.max(1, parseInt(options.runs, 10) || 5);
286
+ const debounceMs = Math.max(
287
+ 250,
288
+ parseInt(options.debounceMs, 10) || 3e3
289
+ );
290
+ if (options.latest) {
291
+ const stored = readLatestDeterminismReport(projectRoot);
292
+ if (!stored) {
293
+ console.error("No cached determinism result found.");
294
+ process.exitCode = 1;
295
+ return;
296
+ }
297
+ if (json) {
298
+ console.log(JSON.stringify(stored, null, 2));
299
+ return;
300
+ }
301
+ printStoredDeterminismReport(stored);
302
+ return;
303
+ }
304
+ const runCheck = async (trigger, changedPaths = []) => {
305
+ const report = await runDeterminismSmoke(
306
+ {
307
+ task: options.task,
308
+ repoPath: projectRoot
309
+ },
310
+ {
311
+ runs,
312
+ plannerModel: options.plannerModel,
313
+ reviewerModel: options.reviewerModel,
314
+ implementer: options.implementer,
315
+ maxIters: parseInt(options.maxIters, 10) || 2
316
+ }
317
+ );
318
+ const stored = persistDeterminismReport(projectRoot, report, {
319
+ task: options.task,
320
+ trigger,
321
+ changedPaths
322
+ });
323
+ if (json) {
324
+ console.log(JSON.stringify(stored, null, 2));
325
+ } else {
326
+ printDeterminismReport(options.task, runs, report);
327
+ }
328
+ return stored;
329
+ };
330
+ if (options.watch) {
331
+ const chokidar = await import("chokidar");
332
+ const watchTargets = getDeterminismWatchTargets(projectRoot);
333
+ const watchPatterns = watchTargets.map(
334
+ (pattern) => join(projectRoot, pattern)
335
+ );
336
+ const watcher = chokidar.watch(watchPatterns, {
337
+ ignoreInitial: true,
338
+ ignored: DETERMINISM_WATCH_IGNORE.map(
339
+ (pattern) => join(projectRoot, pattern)
340
+ ),
341
+ awaitWriteFinish: {
342
+ stabilityThreshold: debounceMs,
343
+ pollInterval: 100
344
+ }
345
+ });
346
+ let running = false;
347
+ let rerunRequested = false;
348
+ const pendingPaths = /* @__PURE__ */ new Set();
349
+ const maybeRun = async (trigger) => {
350
+ if (running) {
351
+ rerunRequested = true;
352
+ return;
353
+ }
354
+ running = true;
355
+ const changedPaths = Array.from(pendingPaths).sort();
356
+ pendingPaths.clear();
357
+ try {
358
+ await runCheck(trigger, changedPaths);
359
+ } finally {
360
+ running = false;
361
+ if (rerunRequested) {
362
+ rerunRequested = false;
363
+ await maybeRun("watch:debounced-rerun");
364
+ }
365
+ }
366
+ };
367
+ const onFileEvent = async (trigger, filePath) => {
368
+ const relativePath = filePath.startsWith(projectRoot) ? filePath.slice(projectRoot.length + 1) : filePath;
369
+ pendingPaths.add(relativePath);
370
+ if (!json) {
371
+ console.log(
372
+ chalk.gray(`determinism watcher: ${trigger} ${relativePath}`)
373
+ );
374
+ }
375
+ await maybeRun(`watch:${trigger}`);
376
+ };
377
+ watcher.on("all", async (eventName, filePath) => {
378
+ if (eventName !== "add" && eventName !== "change") {
379
+ return;
380
+ }
381
+ await onFileEvent(eventName, filePath);
382
+ });
383
+ if (!json) {
384
+ console.log("\nHarness Determinism Watch");
385
+ console.log("\u2550".repeat(60));
386
+ console.log(`Task: ${options.task}`);
387
+ console.log(`Watching: ${watchTargets.join(", ")}`);
388
+ console.log(`Debounce: ${debounceMs}ms`);
389
+ console.log(chalk.gray("Press Ctrl+C to stop.\n"));
390
+ }
391
+ await runCheck("watch:initial");
392
+ await new Promise((resolve) => {
393
+ const stop = () => {
394
+ void watcher.close();
395
+ resolve();
396
+ };
397
+ process.once("SIGINT", stop);
398
+ process.once("SIGTERM", stop);
399
+ });
400
+ return;
401
+ }
402
+ await runCheck("manual");
403
+ });
404
+ bench.command("loops").description("Show feedback loop configuration, status, and recent events").option("--json", "Output as JSON", false).action(function() {
405
+ const command = this;
406
+ const options = command.opts();
407
+ const json = Boolean(options.json || command.parent?.opts().json);
201
408
  const config = feedbackLoops.getConfig();
202
409
  const stats = feedbackLoops.getStats();
203
410
  const history = feedbackLoops.getHistory(void 0, 20);
204
- if (options.json) {
411
+ if (json) {
205
412
  console.log(JSON.stringify({ config, stats, history }, null, 2));
206
413
  return;
207
414
  }