@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
@@ -0,0 +1,244 @@
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 { createHash } from "crypto";
6
+ import { estimateTokens } from "../../core/cache/token-estimator.js";
7
+ import {
8
+ appendFileSync,
9
+ existsSync,
10
+ mkdirSync,
11
+ readFileSync,
12
+ writeFileSync
13
+ } from "fs";
14
+ import { join } from "path";
15
+ import { compactPlan } from "./utils.js";
16
+ import { runSpike } from "./harness.js";
17
+ const DETERMINISM_WATCH_PATTERNS = [
18
+ "src/orchestrators/multimodal",
19
+ "src/cli/commands/bench.ts",
20
+ "src/cli/index.ts",
21
+ "src/core/monitoring/logger.ts"
22
+ ];
23
+ const DETERMINISM_WATCH_IGNORE = [
24
+ ".git/**",
25
+ "node_modules/**",
26
+ "dist/**",
27
+ "build/**",
28
+ ".next/**",
29
+ ".turbo/**",
30
+ "coverage/**",
31
+ ".stackmemory/**"
32
+ ];
33
+ function stableStringify(value) {
34
+ return JSON.stringify(canonicalize(value));
35
+ }
36
+ function canonicalize(value) {
37
+ if (Array.isArray(value)) {
38
+ return value.map((item) => canonicalize(item));
39
+ }
40
+ if (value && typeof value === "object") {
41
+ const entries = Object.entries(value).sort(
42
+ ([a], [b]) => a.localeCompare(b)
43
+ );
44
+ return Object.fromEntries(
45
+ entries.map(([key, entryValue]) => [key, canonicalize(entryValue)])
46
+ );
47
+ }
48
+ return value;
49
+ }
50
+ function hashValue(value) {
51
+ return createHash("sha256").update(stableStringify(value)).digest("hex");
52
+ }
53
+ function modeAgreement(values) {
54
+ if (values.length === 0) return 1;
55
+ const counts = /* @__PURE__ */ new Map();
56
+ for (const value of values) {
57
+ counts.set(value, (counts.get(value) || 0) + 1);
58
+ }
59
+ const maxCount = Math.max(...counts.values());
60
+ return maxCount / values.length;
61
+ }
62
+ function normalizeResult(result) {
63
+ return {
64
+ plan: compactPlan(result.plan),
65
+ critique: canonicalize(result.critique),
66
+ implementation: {
67
+ success: result.implementation.success,
68
+ summary: result.implementation.summary,
69
+ commands: [...result.implementation.commands || []]
70
+ },
71
+ iterations: (result.iterations || []).map((iteration) => ({
72
+ command: iteration.command,
73
+ ok: iteration.ok,
74
+ critique: canonicalize(iteration.critique),
75
+ outputPreviewHash: hashValue(iteration.outputPreview)
76
+ }))
77
+ };
78
+ }
79
+ function estimateContextTokens(result) {
80
+ const normalized = normalizeResult(result);
81
+ return estimateTokens(stableStringify(normalized));
82
+ }
83
+ function toSnapshot(result, index) {
84
+ const normalized = normalizeResult(result);
85
+ return {
86
+ index,
87
+ approved: result.critique.approved,
88
+ iterations: (result.iterations || []).length,
89
+ planHash: hashValue(compactPlan(result.plan)),
90
+ critiqueHash: hashValue(canonicalize(result.critique)),
91
+ commandsHash: hashValue(result.implementation.commands || []),
92
+ resultHash: hashValue(normalized),
93
+ contextTokens: estimateContextTokens(result)
94
+ };
95
+ }
96
+ function computeNumericStability(values) {
97
+ if (values.length <= 1) return 1;
98
+ const min = Math.min(...values);
99
+ const max = Math.max(...values);
100
+ if (max === min) return 1;
101
+ return Math.max(0, 1 - (max - min) / Math.max(max, 1));
102
+ }
103
+ function scoreReport(snapshots) {
104
+ const dimensions = [
105
+ {
106
+ name: "result",
107
+ weight: 40,
108
+ score: modeAgreement(snapshots.map((item) => item.resultHash)) * 100,
109
+ details: "Full normalized result hash agreement"
110
+ },
111
+ {
112
+ name: "plan",
113
+ weight: 20,
114
+ score: modeAgreement(snapshots.map((item) => item.planHash)) * 100,
115
+ details: "Plan structure hash agreement"
116
+ },
117
+ {
118
+ name: "critique",
119
+ weight: 15,
120
+ score: modeAgreement(snapshots.map((item) => item.critiqueHash)) * 100,
121
+ details: "Critique hash agreement"
122
+ },
123
+ {
124
+ name: "commands",
125
+ weight: 10,
126
+ score: modeAgreement(snapshots.map((item) => item.commandsHash)) * 100,
127
+ details: "Implementer command sequence agreement"
128
+ },
129
+ {
130
+ name: "iterations",
131
+ weight: 10,
132
+ score: modeAgreement(snapshots.map((item) => item.iterations)) * 100,
133
+ details: "Retry-count agreement"
134
+ },
135
+ {
136
+ name: "context_tokens",
137
+ weight: 5,
138
+ score: computeNumericStability(snapshots.map((item) => item.contextTokens)) * 100,
139
+ details: "Token-footprint stability"
140
+ }
141
+ ];
142
+ const weightedScore = dimensions.reduce((sum, dimension) => {
143
+ return sum + dimension.score * dimension.weight;
144
+ }, 0);
145
+ const totalWeight = dimensions.reduce(
146
+ (sum, dimension) => sum + dimension.weight,
147
+ 0
148
+ );
149
+ const score = totalWeight > 0 ? weightedScore / totalWeight : 0;
150
+ const recommendations = [];
151
+ if (dimensions[0].score < 100) {
152
+ recommendations.push(
153
+ "Pin planner/critic outputs behind deterministic fixtures or replay traces."
154
+ );
155
+ }
156
+ if (dimensions[1].score < 100) {
157
+ recommendations.push(
158
+ "Canonicalize plan generation further and remove any model-dependent fields from smoke checks."
159
+ );
160
+ }
161
+ if (dimensions[4].score < 100) {
162
+ recommendations.push(
163
+ "Tighten retry rules so the same failure mode produces the same iteration count."
164
+ );
165
+ }
166
+ if (dimensions[5].score < 100) {
167
+ recommendations.push(
168
+ "Reduce context assembly drift by sorting symbols and fixing token accounting."
169
+ );
170
+ }
171
+ return {
172
+ runs: snapshots.length,
173
+ score: Math.round(score * 100) / 100,
174
+ snapshots,
175
+ dimensions,
176
+ recommendations
177
+ };
178
+ }
179
+ async function runDeterminismSmoke(input, options = {}) {
180
+ const runs = Math.max(1, options.runs ?? 5);
181
+ const snapshots = [];
182
+ for (let index = 0; index < runs; index++) {
183
+ const result = await runSpike(input, {
184
+ ...options,
185
+ dryRun: options.dryRun ?? true,
186
+ deterministicFixture: options.deterministicFixture ?? true,
187
+ persistAudit: false,
188
+ record: false,
189
+ recordFrame: false
190
+ });
191
+ snapshots.push(toSnapshot(result, index + 1));
192
+ }
193
+ return scoreReport(snapshots);
194
+ }
195
+ function getDeterminismWatchTargets(repoPath) {
196
+ const existingTargets = DETERMINISM_WATCH_PATTERNS.filter(
197
+ (target) => existsSync(join(repoPath, target))
198
+ );
199
+ if (existingTargets.length > 0) {
200
+ return existingTargets;
201
+ }
202
+ return ["."];
203
+ }
204
+ function getDeterminismDir(repoPath) {
205
+ return join(repoPath, ".stackmemory", "determinism");
206
+ }
207
+ function persistDeterminismReport(repoPath, report, meta) {
208
+ const dir = getDeterminismDir(repoPath);
209
+ mkdirSync(dir, { recursive: true });
210
+ const stored = {
211
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
212
+ task: meta.task,
213
+ trigger: meta.trigger,
214
+ changedPaths: meta.changedPaths || [],
215
+ report
216
+ };
217
+ writeFileSync(
218
+ join(dir, "latest.json"),
219
+ JSON.stringify(stored, null, 2) + "\n"
220
+ );
221
+ appendFileSync(join(dir, "history.jsonl"), JSON.stringify(stored) + "\n");
222
+ return stored;
223
+ }
224
+ function readLatestDeterminismReport(repoPath) {
225
+ const latestPath = join(getDeterminismDir(repoPath), "latest.json");
226
+ if (!existsSync(latestPath)) {
227
+ return null;
228
+ }
229
+ try {
230
+ return JSON.parse(
231
+ readFileSync(latestPath, "utf8")
232
+ );
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+ export {
238
+ DETERMINISM_WATCH_IGNORE,
239
+ DETERMINISM_WATCH_PATTERNS,
240
+ getDeterminismWatchTargets,
241
+ persistDeterminismReport,
242
+ readLatestDeterminismReport,
243
+ runDeterminismSmoke
244
+ };
@@ -2,6 +2,7 @@ import { fileURLToPath as __fileURLToPath } from 'url';
2
2
  import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
+ import { estimateTokens } from "../../core/cache/token-estimator.js";
5
6
  import {
6
7
  callClaude,
7
8
  callCodexCLI,
@@ -51,6 +52,45 @@ function heuristicPlan(input) {
51
52
  ]
52
53
  };
53
54
  }
55
+ function deterministicCritique(args) {
56
+ const issues = [];
57
+ const suggestions = [];
58
+ if (!args.ok) {
59
+ issues.push("Implementer command failed");
60
+ suggestions.push("Fix the command invocation before retrying");
61
+ }
62
+ if (args.diff.includes("<<<<<<<") || args.diff.includes(">>>>>>>")) {
63
+ issues.push("Merge conflict markers detected in diff");
64
+ suggestions.push("Resolve conflict markers before approval");
65
+ }
66
+ if (args.checks && !args.checks.lintOk) {
67
+ issues.push("Lint checks failed");
68
+ suggestions.push("Address lint failures before approval");
69
+ }
70
+ if (args.checks && !args.checks.testsOk) {
71
+ issues.push("Tests failed");
72
+ suggestions.push("Fix failing tests before approval");
73
+ }
74
+ const failedVerifications = args.checks?.verifications.filter((verification) => !verification.ok) || [];
75
+ if (failedVerifications.length > 0) {
76
+ issues.push(
77
+ `Verification command failed: ${failedVerifications[0]?.command}`
78
+ );
79
+ suggestions.push(
80
+ "Use the verification output as the primary repro signal and fix the root cause"
81
+ );
82
+ }
83
+ if (!args.diff || args.diff.startsWith("(no changes detected)")) {
84
+ suggestions.push(
85
+ "No code changes detected; verify the task can be satisfied without edits"
86
+ );
87
+ }
88
+ return {
89
+ approved: issues.length === 0,
90
+ issues,
91
+ suggestions
92
+ };
93
+ }
54
94
  async function runSpike(input, options = {}) {
55
95
  const plannerSystem = `You write concise, actionable implementation plans. Output raw JSON only (no markdown code fences). Schema: { "summary": "string", "steps": [{ "id": "step-1", "title": "string", "rationale": "string", "acceptanceCriteria": ["string"] }], "risks": ["string"] }`;
56
96
  const contextSummary = getLocalContextSummary(input.repoPath);
@@ -61,23 +101,28 @@ ${contextSummary}
61
101
  Constraints: Keep the plan minimal and implementable in a single PR.`;
62
102
  const t0 = Date.now();
63
103
  let plan;
64
- try {
65
- const raw = await callClaude(plannerPrompt, {
66
- model: options.plannerModel,
67
- system: plannerSystem
68
- });
104
+ if (options.deterministicFixture) {
105
+ plan = heuristicPlan(input);
106
+ } else {
69
107
  try {
70
- const cleaned = raw.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
71
- plan = JSON.parse(cleaned);
108
+ const raw = await callClaude(plannerPrompt, {
109
+ model: options.plannerModel,
110
+ system: plannerSystem
111
+ });
112
+ try {
113
+ const cleaned = raw.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
114
+ plan = JSON.parse(cleaned);
115
+ } catch {
116
+ plan = heuristicPlan(input);
117
+ }
72
118
  } catch {
73
119
  plan = heuristicPlan(input);
74
120
  }
75
- } catch {
76
- plan = heuristicPlan(input);
77
121
  }
78
122
  const planLatencyMs = Date.now() - t0;
79
123
  const implementer = options.implementer || "codex";
80
124
  const maxIters = Math.max(1, options.maxIters ?? 2);
125
+ const verificationCommands = options.verificationCommands || [];
81
126
  const iterations = [];
82
127
  let approved = false;
83
128
  let lastCommand = "";
@@ -89,10 +134,14 @@ Constraints: Keep the plan minimal and implementable in a single PR.`;
89
134
  };
90
135
  for (let i = 0; i < maxIters; i++) {
91
136
  const stepsList = plan.steps.map((s, idx) => `${idx + 1}. ${s.title}`).join("\n");
137
+ const verificationPrompt = verificationCommands.length > 0 ? `
138
+
139
+ Verification commands that must pass:
140
+ ${verificationCommands.map((command) => `- ${command}`).join("\n")}` : "\n\nIf this task fixes uncertain behavior, first create or identify a deterministic repro/test/trace that fails for the current behavior, then use it to guide the fix.";
92
141
  const basePrompt = `Implement the following plan:
93
142
  ${stepsList}
94
143
 
95
- Keep changes minimal and focused. Avoid unrelated edits.`;
144
+ Keep changes minimal and focused. Avoid unrelated edits.${verificationPrompt}`;
96
145
  const refine = i === 0 ? "" : `
97
146
  Incorporate reviewer suggestions: ${lastCritique.suggestions.join("; ")}`;
98
147
  const implPrompt = basePrompt + refine;
@@ -116,14 +165,20 @@ Incorporate reviewer suggestions: ${lastCritique.suggestions.join("; ")}`;
116
165
  _lastOutput = impl.output;
117
166
  }
118
167
  const diff = options.dryRun !== false ? "(dry run \u2014 no diff)" : captureGitDiff(input.repoPath);
119
- const checks = options.dryRun !== false ? null : runPostImplChecks(input.repoPath);
168
+ const checks = options.dryRun !== false ? null : runPostImplChecks(input.repoPath, verificationCommands);
169
+ const verificationSection = checks && checks.verifications.length > 0 ? `
170
+ Custom verification:
171
+ ${checks.verifications.map(
172
+ (verification) => ` - ${verification.ok ? "PASS" : "FAIL"} ${verification.command}
173
+ ${verification.output}`
174
+ ).join("\n")}` : "";
120
175
  const checksSection = checks ? `
121
176
 
122
177
  Post-implementation checks:
123
178
  Lint: ${checks.lintOk ? "PASS" : "FAIL"}
124
179
  ${checks.lintOutput}
125
180
  Tests: ${checks.testsOk ? "PASS" : "FAIL"}
126
- ${checks.testOutput}` : "";
181
+ ${checks.testOutput}${verificationSection}` : "";
127
182
  const criticSystem = `You are a strict code reviewer. Review the git diff against the plan. Check for: correctness, missing steps, unrelated changes, bugs, security issues. Also review lint and test results if provided. Return raw JSON only (no markdown fences): { "approved": boolean, "issues": ["string"], "suggestions": ["string"] }`;
128
183
  const criticPrompt = `Plan: ${plan.summary}
129
184
  Acceptance criteria:
@@ -134,25 +189,35 @@ Implementer exit: ${ok ? "success" : "failed"}
134
189
 
135
190
  Git diff:
136
191
  ${diff}${checksSection}`;
137
- try {
138
- const raw = await callClaude(criticPrompt, {
139
- model: options.reviewerModel,
140
- system: criticSystem
192
+ if (options.deterministicFixture) {
193
+ lastCritique = deterministicCritique({
194
+ plan,
195
+ ok,
196
+ diff,
197
+ checks
141
198
  });
142
- const cleaned = raw.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
143
- lastCritique = JSON.parse(cleaned);
144
- } catch {
145
- lastCritique = {
146
- approved: ok,
147
- issues: ok ? [] : ["Critique failed"],
148
- suggestions: []
149
- };
199
+ } else {
200
+ try {
201
+ const raw = await callClaude(criticPrompt, {
202
+ model: options.reviewerModel,
203
+ system: criticSystem
204
+ });
205
+ const cleaned = raw.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
206
+ lastCritique = JSON.parse(cleaned);
207
+ } catch {
208
+ lastCritique = {
209
+ approved: ok,
210
+ issues: ok ? [] : ["Critique failed"],
211
+ suggestions: []
212
+ };
213
+ }
150
214
  }
151
215
  iterations.push({
152
216
  command: lastCommand,
153
217
  ok,
154
218
  outputPreview: diff.slice(0, 2e3),
155
- critique: lastCritique
219
+ critique: lastCritique,
220
+ checks
156
221
  });
157
222
  if (lastCritique.approved) {
158
223
  approved = true;
@@ -175,64 +240,66 @@ ${diff}${checksSection}`;
175
240
  editAttempts: editMetrics.editAttempts,
176
241
  editSuccesses: editMetrics.editSuccesses,
177
242
  editFuzzyFallbacks: editMetrics.editFuzzyFallbacks,
178
- contextTokens: Math.ceil(finalDiff.length / 4)
243
+ contextTokens: estimateTokens(finalDiff)
179
244
  };
180
- try {
181
- const dir = options.auditDir || path.join(input.repoPath, ".stackmemory", "build");
182
- fs.mkdirSync(dir, { recursive: true });
183
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
184
- const file = path.join(dir, `spike-${stamp}.json`);
185
- fs.writeFileSync(
186
- file,
187
- JSON.stringify(
188
- {
189
- input,
190
- options: { ...options, auditDir: void 0 },
191
- plan,
192
- iterations,
193
- metrics: runMetrics
194
- },
195
- null,
196
- 2
197
- )
198
- );
199
- const metricsFile = path.join(dir, "harness-metrics.jsonl");
200
- fs.appendFileSync(metricsFile, JSON.stringify(runMetrics) + "\n");
245
+ if (options.persistAudit !== false) {
201
246
  try {
202
- const lines = fs.readFileSync(metricsFile, "utf-8").split("\n").filter((l) => l.trim());
203
- const recent = lines.slice(-10).map((l) => JSON.parse(l));
204
- if (recent.length >= 3) {
205
- const summary = summarizeRuns(recent);
206
- if (summary.approvalRate < HARNESS_TARGETS.firstPassApprovalRate) {
207
- feedbackLoops.fire(
208
- "harnessRegression",
209
- "metrics_append",
210
- {
211
- metric: "approvalRate",
212
- current: summary.approvalRate,
213
- target: HARNESS_TARGETS.firstPassApprovalRate,
214
- window: recent.length
215
- },
216
- "regression_alert"
217
- );
218
- }
219
- if (summary.p95TotalLatencyMs > HARNESS_TARGETS.totalLatencyP95Ms) {
220
- feedbackLoops.fire(
221
- "harnessRegression",
222
- "metrics_append",
223
- {
224
- metric: "totalLatencyP95",
225
- current: summary.p95TotalLatencyMs,
226
- target: HARNESS_TARGETS.totalLatencyP95Ms,
227
- window: recent.length
228
- },
229
- "regression_alert"
230
- );
247
+ const dir = options.auditDir || path.join(input.repoPath, ".stackmemory", "build");
248
+ fs.mkdirSync(dir, { recursive: true });
249
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
250
+ const file = path.join(dir, `spike-${stamp}.json`);
251
+ fs.writeFileSync(
252
+ file,
253
+ JSON.stringify(
254
+ {
255
+ input,
256
+ options: { ...options, auditDir: void 0 },
257
+ plan,
258
+ iterations,
259
+ metrics: runMetrics
260
+ },
261
+ null,
262
+ 2
263
+ )
264
+ );
265
+ const metricsFile = path.join(dir, "harness-metrics.jsonl");
266
+ fs.appendFileSync(metricsFile, JSON.stringify(runMetrics) + "\n");
267
+ try {
268
+ const lines = fs.readFileSync(metricsFile, "utf-8").split("\n").filter((l) => l.trim());
269
+ const recent = lines.slice(-10).map((l) => JSON.parse(l));
270
+ if (recent.length >= 3) {
271
+ const summary = summarizeRuns(recent);
272
+ if (summary.approvalRate < HARNESS_TARGETS.firstPassApprovalRate) {
273
+ feedbackLoops.fire(
274
+ "harnessRegression",
275
+ "metrics_append",
276
+ {
277
+ metric: "approvalRate",
278
+ current: summary.approvalRate,
279
+ target: HARNESS_TARGETS.firstPassApprovalRate,
280
+ window: recent.length
281
+ },
282
+ "regression_alert"
283
+ );
284
+ }
285
+ if (summary.p95TotalLatencyMs > HARNESS_TARGETS.totalLatencyP95Ms) {
286
+ feedbackLoops.fire(
287
+ "harnessRegression",
288
+ "metrics_append",
289
+ {
290
+ metric: "totalLatencyP95",
291
+ current: summary.p95TotalLatencyMs,
292
+ target: HARNESS_TARGETS.totalLatencyP95Ms,
293
+ window: recent.length
294
+ },
295
+ "regression_alert"
296
+ );
297
+ }
231
298
  }
299
+ } catch {
232
300
  }
233
301
  } catch {
234
302
  }
235
- } catch {
236
303
  }
237
304
  if (options.record) {
238
305
  void recordContext(
@@ -265,7 +332,8 @@ ${diff}${checksSection}`;
265
332
  commands: iterations.map((it) => it.command)
266
333
  },
267
334
  critique: lastCritique,
268
- iterations
335
+ iterations,
336
+ verification: iterations.at(-1)?.checks || null
269
337
  };
270
338
  }
271
339
  const runPlanAndCode = runSpike;
@@ -341,6 +409,9 @@ Repo: ${input.repoPath}
341
409
  Notes: ${input.contextNotes || "(none)"}
342
410
  ${contextSummary}
343
411
  Constraints: Keep the plan minimal and implementable in a single PR.`;
412
+ if (options.deterministicFixture) {
413
+ return heuristicPlan(input);
414
+ }
344
415
  try {
345
416
  const raw = await callClaude(plannerPrompt, {
346
417
  model: options.plannerModel,
@@ -115,7 +115,7 @@ ${newFiles.join("\n")}`;
115
115
  return "(git diff failed)";
116
116
  }
117
117
  }
118
- function runPostImplChecks(cwd) {
118
+ function runPostImplChecks(cwd, verificationCommands = []) {
119
119
  const maxOutput = 2e3;
120
120
  function truncate(s) {
121
121
  if (s.length <= maxOutput) return s;
@@ -152,7 +152,47 @@ function runPostImplChecks(cwd) {
152
152
  } catch (e) {
153
153
  testOutput = truncate(e instanceof Error ? e.message : String(e));
154
154
  }
155
- return { lintOk, lintOutput, testsOk, testOutput };
155
+ return {
156
+ lintOk,
157
+ lintOutput,
158
+ testsOk,
159
+ testOutput,
160
+ verifications: runVerificationCommands(cwd, verificationCommands)
161
+ };
162
+ }
163
+ function runVerificationCommands(cwd, commands, options = {}) {
164
+ const timeout = options.timeoutMs ?? 12e4;
165
+ const maxOutput = options.maxOutput ?? 4e3;
166
+ const truncate = (value) => {
167
+ if (value.length <= maxOutput) return value;
168
+ return value.slice(0, maxOutput) + `
169
+ ... (truncated, ${value.length} total chars)`;
170
+ };
171
+ return commands.map((command) => command.trim()).filter(Boolean).map((command) => {
172
+ try {
173
+ const result = spawnSync(command, {
174
+ cwd,
175
+ encoding: "utf8",
176
+ shell: true,
177
+ timeout,
178
+ maxBuffer: Math.max(maxOutput * 4, 1024 * 1024)
179
+ });
180
+ const output = truncate((result.stdout || "") + (result.stderr || ""));
181
+ return {
182
+ command,
183
+ ok: result.status === 0,
184
+ output: output || (result.status === 0 ? "(command completed with no output)" : `(command failed with exit ${result.status ?? "unknown"})`)
185
+ };
186
+ } catch (error) {
187
+ return {
188
+ command,
189
+ ok: false,
190
+ output: truncate(
191
+ error instanceof Error ? error.message : String(error)
192
+ )
193
+ };
194
+ }
195
+ });
156
196
  }
157
197
  function parseEditMetrics(diff) {
158
198
  if (!diff || diff.startsWith("(")) {
@@ -204,5 +244,6 @@ export {
204
244
  captureGitDiff,
205
245
  implementWithClaude,
206
246
  parseEditMetrics,
207
- runPostImplChecks
247
+ runPostImplChecks,
248
+ runVerificationCommands
208
249
  };
@@ -5,6 +5,7 @@ const __dirname = __pathDirname(__filename);
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import { logger } from "../core/monitoring/logger.js";
8
+ import { estimateTokens } from "../core/cache/token-estimator.js";
8
9
  import { ParallelExecutor } from "../core/execution/parallel-executor.js";
9
10
  import { RecursiveContextManager } from "../core/context/recursive-context-manager.js";
10
11
  import { ClaudeCodeSubagentClient } from "../integrations/claude-code/subagent-client.js";
@@ -345,7 +346,7 @@ Rules:
345
346
  context: agentContext
346
347
  });
347
348
  node.result = response.result;
348
- node.tokens = response.tokens || this.estimateTokens(JSON.stringify(response));
349
+ node.tokens = response.tokens || estimateTokens(JSON.stringify(response));
349
350
  node.cost = this.calculateNodeCost(node.tokens, agentConfig.model);
350
351
  if (options.shareContextRealtime) {
351
352
  await this.shareAgentResults(node);
@@ -527,9 +528,6 @@ ${sections.join("\n\n")}` : "";
527
528
  `- Output structured JSON when possible`
528
529
  ].join("\n");
529
530
  }
530
- estimateTokens(text) {
531
- return Math.ceil(text.length / 4);
532
- }
533
531
  async shareAgentResults(_node) {
534
532
  logger.debug("Sharing agent results", { nodeId: _node.id });
535
533
  }
@@ -32,14 +32,6 @@ const CANONICAL_HOOKS = [
32
32
  commandPrefix: "node",
33
33
  required: true
34
34
  },
35
- {
36
- scriptName: "cord-trace.js",
37
- eventType: "PostToolUse",
38
- matcher: "mcp__.*__cord_(spawn|fork|complete|ask|tree)",
39
- timeout: 2,
40
- commandPrefix: "node",
41
- required: true
42
- },
43
35
  {
44
36
  scriptName: "theory-capture.js",
45
37
  eventType: "PostToolUse",
@@ -15,13 +15,7 @@ function isProcessAlive(pid) {
15
15
  return false;
16
16
  }
17
17
  }
18
- const STACKMEMORY_PROCESS_PATTERNS = [
19
- "stackmemory",
20
- "ralph orchestrate",
21
- "ralph swarm",
22
- "ralph loop",
23
- "hooks start"
24
- ];
18
+ const STACKMEMORY_PROCESS_PATTERNS = ["stackmemory", "hooks start"];
25
19
  function getStackmemoryProcesses() {
26
20
  const processes = [];
27
21
  try {