@stackmemoryai/stackmemory 1.6.2 → 1.8.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 (86) hide show
  1. package/dist/src/agents/core/agent-task-manager.js +3 -3
  2. package/dist/src/agents/verifiers/formatter-verifier.js +1 -1
  3. package/dist/src/agents/verifiers/llm-judge.js +2 -2
  4. package/dist/src/cli/commands/conductor-traces.js +314 -0
  5. package/dist/src/cli/commands/context-rehydrate.js +2 -2
  6. package/dist/src/cli/commands/log.js +1 -1
  7. package/dist/src/cli/commands/onboard.js +4 -4
  8. package/dist/src/cli/commands/orchestrate.js +328 -27
  9. package/dist/src/cli/commands/orchestrator.js +50 -3
  10. package/dist/src/cli/commands/search.js +3 -3
  11. package/dist/src/cli/commands/setup.js +1 -1
  12. package/dist/src/cli/commands/signup.js +1 -1
  13. package/dist/src/cli/commands/skills.js +2 -2
  14. package/dist/src/cli/commands/worktree.js +1 -1
  15. package/dist/src/core/config/config-manager.js +3 -1
  16. package/dist/src/core/config/types.js +7 -0
  17. package/dist/src/core/context/dual-stack-manager.js +2 -2
  18. package/dist/src/core/context/frame-database.js +38 -0
  19. package/dist/src/core/context/frame-enrichment.js +93 -0
  20. package/dist/src/core/context/frame-handoff-manager.js +1 -1
  21. package/dist/src/core/context/frame-manager.js +4 -4
  22. package/dist/src/core/context/rehydration.js +6 -6
  23. package/dist/src/core/database/sqlite-adapter.js +136 -15
  24. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  25. package/dist/src/core/digest/hybrid-digest-generator.js +1 -1
  26. package/dist/src/core/digest/hybrid-digest.js +3 -3
  27. package/dist/src/core/errors/recovery.js +1 -1
  28. package/dist/src/core/extensions/loader.js +10 -10
  29. package/dist/src/core/extensions/plugin-system.js +1 -1
  30. package/dist/src/core/monitoring/logger.js +2 -2
  31. package/dist/src/core/monitoring/metrics.js +2 -2
  32. package/dist/src/core/monitoring/progress-tracker.js +2 -2
  33. package/dist/src/core/performance/lazy-context-loader.js +1 -1
  34. package/dist/src/core/performance/performance-benchmark.js +8 -8
  35. package/dist/src/core/performance/performance-profiler.js +2 -2
  36. package/dist/src/core/performance/streaming-jsonl-parser.js +1 -1
  37. package/dist/src/core/projects/project-isolation.js +1 -1
  38. package/dist/src/core/projects/project-manager.js +5 -5
  39. package/dist/src/core/session/clear-survival.js +3 -3
  40. package/dist/src/core/session/handoff-generator.js +1 -1
  41. package/dist/src/core/session/session-manager.js +4 -4
  42. package/dist/src/core/skills/skill-storage.js +2 -2
  43. package/dist/src/core/storage/remote-storage.js +2 -2
  44. package/dist/src/core/trace/cli-trace-wrapper.js +3 -3
  45. package/dist/src/core/trace/debug-trace.js +2 -2
  46. package/dist/src/core/trace/trace-detector.js +1 -1
  47. package/dist/src/core/worktree/worktree-manager.js +2 -2
  48. package/dist/src/features/analytics/api/analytics-api.js +1 -1
  49. package/dist/src/features/analytics/core/analytics-service.js +2 -2
  50. package/dist/src/features/analytics/queries/metrics-queries.js +1 -1
  51. package/dist/src/features/web/server/index.js +8 -8
  52. package/dist/src/integrations/anthropic/client.js +4 -4
  53. package/dist/src/integrations/claude-code/post-task-hooks.js +3 -3
  54. package/dist/src/integrations/claude-code/subagent-client.js +2 -2
  55. package/dist/src/integrations/diffmem/client.js +1 -1
  56. package/dist/src/integrations/linear/client.js +1 -1
  57. package/dist/src/integrations/linear/migration.js +1 -1
  58. package/dist/src/integrations/linear/oauth-server.js +2 -2
  59. package/dist/src/integrations/linear/sync.js +1 -1
  60. package/dist/src/integrations/linear/webhook-handler.js +2 -2
  61. package/dist/src/integrations/linear/webhook-server.js +2 -2
  62. package/dist/src/integrations/linear/webhook.js +2 -2
  63. package/dist/src/integrations/mcp/handlers/discovery-handlers.js +3 -3
  64. package/dist/src/integrations/mcp/handlers/linear-handlers.js +1 -1
  65. package/dist/src/integrations/mcp/handlers/skill-handlers.js +2 -2
  66. package/dist/src/integrations/mcp/handlers/task-handlers.js +2 -2
  67. package/dist/src/integrations/mcp/handlers/trace-handlers.js +1 -1
  68. package/dist/src/integrations/mcp/refactored-server.js +2 -2
  69. package/dist/src/integrations/mcp/remote-server.js +1 -1
  70. package/dist/src/integrations/mcp/server.js +4 -4
  71. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +13 -13
  72. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +1 -1
  73. package/dist/src/integrations/ralph/learning/pattern-learner.js +3 -3
  74. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +1 -1
  75. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +2 -2
  76. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +19 -19
  77. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +5 -5
  78. package/dist/src/integrations/ralph/performance/performance-optimizer.js +1 -1
  79. package/dist/src/integrations/ralph/recovery/crash-recovery.js +1 -1
  80. package/dist/src/integrations/ralph/state/state-reconciler.js +1 -1
  81. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +4 -4
  82. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +8 -8
  83. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +3 -3
  84. package/dist/src/orchestrators/multimodal/harness.js +3 -3
  85. package/dist/src/servers/production/auth-middleware.js +2 -2
  86. package/package.json +1 -1
@@ -161,7 +161,7 @@ class AgentTaskManager {
161
161
  /**
162
162
  * Run a single verifier
163
163
  */
164
- async runVerifier(verifierId, action, context) {
164
+ async runVerifier(verifierId, _action, _context) {
165
165
  const mockResults = {
166
166
  formatter: () => ({
167
167
  verifierId: "formatter",
@@ -317,7 +317,7 @@ class AgentTaskManager {
317
317
  * Generate feedback from verification results
318
318
  */
319
319
  generateFeedback(results) {
320
- const failed = results.filter((r) => !r.passed);
320
+ const _failed = results.filter((r) => !r.passed);
321
321
  const warnings = results.filter(
322
322
  (r) => !r.passed && r.severity === "warning"
323
323
  );
@@ -363,7 +363,7 @@ ${warningMessages}`;
363
363
  taskId: session.taskId,
364
364
  turnCount: session.turnCount
365
365
  });
366
- const task = this.taskStore.getTask(session.taskId);
366
+ const _task = this.taskStore.getTask(session.taskId);
367
367
  const isComplete = this.assessTaskCompletion(session);
368
368
  if (isComplete) {
369
369
  await this.completeSession(session);
@@ -95,7 +95,7 @@ class FormatterVerifier extends BaseVerifier {
95
95
  async runFormatter(formatter, context) {
96
96
  const command = `${formatter.checkCommand} "${context.filePath}"`;
97
97
  try {
98
- const { stdout, stderr } = await execAsync(command, {
98
+ const { _stdout, _stderr } = await execAsync(command, {
99
99
  cwd: process.cwd(),
100
100
  timeout: this.config.timeout
101
101
  });
@@ -28,7 +28,7 @@ class LLMJudge extends BaseVerifier {
28
28
  ...config
29
29
  });
30
30
  }
31
- shouldActivate(context) {
31
+ shouldActivate(_context) {
32
32
  return true;
33
33
  }
34
34
  async verify(input, context) {
@@ -185,7 +185,7 @@ class LLMJudge extends BaseVerifier {
185
185
  }
186
186
  );
187
187
  }
188
- generateJudgementFeedback(criteria, overallScore, context) {
188
+ generateJudgementFeedback(criteria, overallScore, _context) {
189
189
  if (overallScore >= this.VETO_THRESHOLD) {
190
190
  return `Semantic validation PASSED (${(overallScore * 100).toFixed(1)}% confidence). Changes adequately address the requirements.`;
191
191
  }
@@ -0,0 +1,314 @@
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 Database from "better-sqlite3";
6
+ import { mkdirSync } from "fs";
7
+ import { join } from "path";
8
+ import { homedir } from "os";
9
+ import { randomUUID } from "crypto";
10
+ function classifyErrorText(text) {
11
+ const lower = text.toLowerCase();
12
+ if (lower.includes("lint") || lower.includes("eslint") || lower.includes("prettier"))
13
+ return "lint_failure";
14
+ if (lower.includes("test") && (lower.includes("fail") || lower.includes("error")))
15
+ return "test_failure";
16
+ if (lower.includes("timeout") || lower.includes("timed out"))
17
+ return "timeout";
18
+ if (lower.includes("conflict") || lower.includes("merge"))
19
+ return "git_conflict";
20
+ if (lower.includes("429") || lower.includes("rate limit"))
21
+ return "rate_limit";
22
+ if (lower.includes("permission") || lower.includes("EACCES") || lower.includes("not found"))
23
+ return "permission_or_missing";
24
+ if (lower.includes("build") && lower.includes("error"))
25
+ return "build_failure";
26
+ return null;
27
+ }
28
+ function getTracesDbPath() {
29
+ return join(homedir(), ".stackmemory", "conductor", "traces.db");
30
+ }
31
+ function openTracesDb(dbPath) {
32
+ const path = dbPath ?? getTracesDbPath();
33
+ mkdirSync(join(path, ".."), { recursive: true });
34
+ const db = new Database(path);
35
+ db.pragma("journal_mode = WAL");
36
+ db.pragma("foreign_keys = ON");
37
+ db.exec(`
38
+ CREATE TABLE IF NOT EXISTS conductor_traces (
39
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ issue_id TEXT NOT NULL,
41
+ session_id TEXT NOT NULL,
42
+ attempt INTEGER NOT NULL,
43
+ turn_number INTEGER NOT NULL,
44
+ timestamp INTEGER NOT NULL,
45
+ phase TEXT,
46
+ tool_names TEXT,
47
+ tool_count INTEGER DEFAULT 0,
48
+ files_modified INTEGER DEFAULT 0,
49
+ input_tokens INTEGER DEFAULT 0,
50
+ output_tokens INTEGER DEFAULT 0,
51
+ cache_creation_tokens INTEGER DEFAULT 0,
52
+ cache_read_tokens INTEGER DEFAULT 0,
53
+ message_preview TEXT,
54
+ event_json TEXT NOT NULL,
55
+ UNIQUE(session_id, turn_number)
56
+ );
57
+
58
+ CREATE INDEX IF NOT EXISTS idx_traces_issue
59
+ ON conductor_traces(issue_id, attempt);
60
+ CREATE INDEX IF NOT EXISTS idx_traces_session
61
+ ON conductor_traces(session_id);
62
+ CREATE INDEX IF NOT EXISTS idx_traces_phase
63
+ ON conductor_traces(phase);
64
+ CREATE INDEX IF NOT EXISTS idx_traces_timestamp
65
+ ON conductor_traces(timestamp DESC);
66
+ `);
67
+ return db;
68
+ }
69
+ function withDb(db, fn) {
70
+ const ownDb = db ?? openTracesDb();
71
+ try {
72
+ return fn(ownDb);
73
+ } finally {
74
+ if (!db) ownDb.close();
75
+ }
76
+ }
77
+ class TraceCollector {
78
+ db;
79
+ ownsDb;
80
+ sessionId;
81
+ issueId;
82
+ attempt;
83
+ turnCounter = 0;
84
+ insertStmt;
85
+ constructor(opts) {
86
+ this.issueId = opts.issueId;
87
+ this.attempt = opts.attempt;
88
+ this.sessionId = `${opts.issueId}-${opts.attempt}-${randomUUID().slice(0, 8)}`;
89
+ this.ownsDb = !opts.db;
90
+ this.db = opts.db ?? openTracesDb();
91
+ this.insertStmt = this.db.prepare(`
92
+ INSERT INTO conductor_traces (
93
+ issue_id, session_id, attempt, turn_number, timestamp,
94
+ phase, tool_names, tool_count, files_modified,
95
+ input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens,
96
+ message_preview, event_json
97
+ ) VALUES (
98
+ @issue_id, @session_id, @attempt, @turn_number, @timestamp,
99
+ @phase, @tool_names, @tool_count, @files_modified,
100
+ @input_tokens, @output_tokens, @cache_creation_tokens, @cache_read_tokens,
101
+ @message_preview, @event_json
102
+ )
103
+ `);
104
+ }
105
+ get session() {
106
+ return this.sessionId;
107
+ }
108
+ /**
109
+ * Record a turn using pre-extracted data from the orchestrator's stream parser.
110
+ * Avoids re-iterating content blocks — the caller already did that work.
111
+ */
112
+ recordTurn(turnData, phase, eventJson) {
113
+ this.insertStmt.run({
114
+ issue_id: this.issueId,
115
+ session_id: this.sessionId,
116
+ attempt: this.attempt,
117
+ turn_number: this.turnCounter++,
118
+ timestamp: Date.now(),
119
+ phase: phase ?? null,
120
+ tool_names: turnData.toolNames.length > 0 ? JSON.stringify(turnData.toolNames) : null,
121
+ tool_count: turnData.toolCount,
122
+ files_modified: turnData.filesModified,
123
+ input_tokens: turnData.inputTokens,
124
+ output_tokens: turnData.outputTokens,
125
+ cache_creation_tokens: turnData.cacheCreationTokens,
126
+ cache_read_tokens: turnData.cacheReadTokens,
127
+ message_preview: turnData.textPreview,
128
+ event_json: eventJson
129
+ });
130
+ }
131
+ /** Record a result event (final output) */
132
+ recordResult(event) {
133
+ const resultText = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
134
+ this.insertStmt.run({
135
+ issue_id: this.issueId,
136
+ session_id: this.sessionId,
137
+ attempt: this.attempt,
138
+ turn_number: this.turnCounter++,
139
+ timestamp: Date.now(),
140
+ phase: "result",
141
+ tool_names: null,
142
+ tool_count: 0,
143
+ files_modified: 0,
144
+ input_tokens: 0,
145
+ output_tokens: 0,
146
+ cache_creation_tokens: 0,
147
+ cache_read_tokens: 0,
148
+ message_preview: (resultText || "").slice(0, 500),
149
+ event_json: JSON.stringify(event).slice(0, 5e4)
150
+ });
151
+ }
152
+ /** Close the DB connection only if we own it */
153
+ close() {
154
+ if (!this.ownsDb) return;
155
+ try {
156
+ this.db.close();
157
+ } catch {
158
+ }
159
+ }
160
+ }
161
+ function stringifyEventTruncated(event) {
162
+ return JSON.stringify(event, (_key, value) => {
163
+ if (value && typeof value === "object" && !Array.isArray(value)) {
164
+ const obj = value;
165
+ if (obj.type === "tool_use" && obj.input) {
166
+ const inputStr = JSON.stringify(obj.input);
167
+ if (inputStr.length > 2e3) {
168
+ return {
169
+ ...obj,
170
+ input: { _truncated: true, length: inputStr.length }
171
+ };
172
+ }
173
+ }
174
+ if (obj.type === "tool_result" && obj.content) {
175
+ const contentStr = typeof obj.content === "string" ? obj.content : JSON.stringify(obj.content);
176
+ if (contentStr.length > 2e3) {
177
+ return { ...obj, content: `[truncated: ${contentStr.length} chars]` };
178
+ }
179
+ }
180
+ }
181
+ return value;
182
+ });
183
+ }
184
+ function listSessions(issueId, db) {
185
+ return withDb(db, (d) => {
186
+ const rows = d.prepare(
187
+ `
188
+ SELECT
189
+ issue_id,
190
+ session_id,
191
+ attempt,
192
+ COUNT(*) as total_turns,
193
+ SUM(tool_count) as total_tool_calls,
194
+ SUM(files_modified) as total_files_modified,
195
+ SUM(input_tokens) as total_input_tokens,
196
+ SUM(output_tokens) as total_output_tokens,
197
+ GROUP_CONCAT(DISTINCT phase) as phases,
198
+ MIN(timestamp) as started_at,
199
+ MAX(timestamp) as ended_at
200
+ FROM conductor_traces
201
+ WHERE issue_id = ?
202
+ GROUP BY session_id
203
+ ORDER BY started_at DESC
204
+ `
205
+ ).all(issueId);
206
+ return rows.map((r) => ({
207
+ issue_id: r.issue_id,
208
+ session_id: r.session_id,
209
+ attempt: r.attempt,
210
+ total_turns: r.total_turns,
211
+ total_tool_calls: r.total_tool_calls || 0,
212
+ total_files_modified: r.total_files_modified || 0,
213
+ total_input_tokens: r.total_input_tokens || 0,
214
+ total_output_tokens: r.total_output_tokens || 0,
215
+ phases: (r.phases || "").split(",").filter(Boolean),
216
+ started_at: r.started_at,
217
+ ended_at: r.ended_at,
218
+ duration_ms: r.ended_at - r.started_at
219
+ }));
220
+ });
221
+ }
222
+ function getSessionTurns(sessionId, db) {
223
+ return withDb(
224
+ db,
225
+ (d) => d.prepare(
226
+ `
227
+ SELECT * FROM conductor_traces
228
+ WHERE session_id = ?
229
+ ORDER BY turn_number ASC
230
+ `
231
+ ).all(sessionId)
232
+ );
233
+ }
234
+ function getPhaseBreakdown(sessionId, db) {
235
+ return withDb(
236
+ db,
237
+ (d) => d.prepare(
238
+ `
239
+ SELECT
240
+ phase,
241
+ COUNT(*) as turns,
242
+ SUM(tool_count) as tool_calls,
243
+ SUM(input_tokens) as input_tokens,
244
+ SUM(output_tokens) as output_tokens
245
+ FROM conductor_traces
246
+ WHERE session_id = ? AND phase IS NOT NULL
247
+ GROUP BY phase
248
+ ORDER BY MIN(turn_number) ASC
249
+ `
250
+ ).all(sessionId)
251
+ );
252
+ }
253
+ function getToolFrequencies(issueId, db) {
254
+ return withDb(
255
+ db,
256
+ (d) => d.prepare(
257
+ `
258
+ SELECT j.value as tool_name, COUNT(*) as count
259
+ FROM conductor_traces, json_each(tool_names) j
260
+ WHERE issue_id = ? AND tool_names IS NOT NULL
261
+ GROUP BY j.value
262
+ ORDER BY count DESC
263
+ `
264
+ ).all(issueId)
265
+ );
266
+ }
267
+ function getFailureTurns(issueId, tailCount = 5, db) {
268
+ return withDb(
269
+ db,
270
+ (d) => d.prepare(
271
+ `
272
+ SELECT t.* FROM conductor_traces t
273
+ INNER JOIN (
274
+ SELECT session_id, MAX(turn_number) as max_turn
275
+ FROM conductor_traces
276
+ WHERE issue_id = ?
277
+ GROUP BY session_id
278
+ ) latest ON t.session_id = latest.session_id
279
+ AND t.turn_number > latest.max_turn - ?
280
+ WHERE t.issue_id = ?
281
+ ORDER BY t.session_id, t.turn_number ASC
282
+ `
283
+ ).all(issueId, tailCount, issueId)
284
+ );
285
+ }
286
+ function getTraceStats(db) {
287
+ return withDb(
288
+ db,
289
+ (d) => d.prepare(
290
+ `
291
+ SELECT
292
+ COUNT(DISTINCT session_id) as total_sessions,
293
+ COUNT(*) as total_turns,
294
+ SUM(input_tokens) as total_input_tokens,
295
+ SUM(output_tokens) as total_output_tokens,
296
+ COUNT(DISTINCT issue_id) as issues_traced
297
+ FROM conductor_traces
298
+ `
299
+ ).get()
300
+ );
301
+ }
302
+ export {
303
+ TraceCollector,
304
+ classifyErrorText,
305
+ getFailureTurns,
306
+ getPhaseBreakdown,
307
+ getSessionTurns,
308
+ getToolFrequencies,
309
+ getTraceStats,
310
+ getTracesDbPath,
311
+ listSessions,
312
+ openTracesDb,
313
+ stringifyEventTruncated
314
+ };
@@ -289,7 +289,7 @@ async function listCheckpoints() {
289
289
  console.log(
290
290
  "\u{1F4A1} Verify: stackmemory context rehydrate --verify -c <checkpoint-id>"
291
291
  );
292
- } catch (err) {
292
+ } catch {
293
293
  console.log("\u{1F4ED} No rehydration checkpoints directory found");
294
294
  console.log(
295
295
  "\u{1F4A1} Create first checkpoint with: stackmemory context rehydrate --create"
@@ -382,7 +382,7 @@ async function captureStackTraces() {
382
382
  try {
383
383
  const traces = [];
384
384
  const fs = await import("fs/promises");
385
- const { execSync } = await import("child_process");
385
+ const { _execSync } = await import("child_process");
386
386
  const stackMemoryLogs = [
387
387
  ".stackmemory/error.log",
388
388
  ".stackmemory/compaction.log",
@@ -10,7 +10,7 @@ function createLogCommand() {
10
10
  const log = new Command("log").alias("history").description("View recent activity log").option("-n, --lines <n>", "Number of entries to show", "20").option("-t, --type <type>", "Filter by type (task, frame, event, sync)").option("-f, --follow", "Follow log in real-time").action(async (options) => {
11
11
  const projectRoot = process.cwd();
12
12
  const dbPath = join(projectRoot, ".stackmemory", "context.db");
13
- const tasksPath = join(projectRoot, ".stackmemory", "tasks.jsonl");
13
+ const _tasksPath = join(projectRoot, ".stackmemory", "tasks.jsonl");
14
14
  if (!existsSync(dbPath)) {
15
15
  console.log(
16
16
  '\u274C StackMemory not initialized. Run "stackmemory init" first.'
@@ -99,7 +99,7 @@ async function runOnboarding() {
99
99
  console.log(chalk.gray(`Opening signup page: ${signupUrl}`));
100
100
  const mod = await import("open");
101
101
  await mod.default(signupUrl);
102
- } catch (e) {
102
+ } catch {
103
103
  console.log(
104
104
  chalk.yellow(
105
105
  "Could not open browser automatically. Please sign up and obtain your DATABASE_URL."
@@ -379,7 +379,7 @@ DATABASE_URL=${config.databaseUrl}
379
379
  " Tip: export DATABASE_URL from this file in your shell profile."
380
380
  )
381
381
  );
382
- } catch (e) {
382
+ } catch {
383
383
  console.log(chalk.yellow(" \u26A0 Could not write hosted DB env file"));
384
384
  }
385
385
  }
@@ -410,7 +410,7 @@ exec stackmemory "$@"
410
410
  execFileSync("ln", ["-s", sourcePath, binPath]);
411
411
  console.log(chalk.green(" \u2713 Created claude-sm command"));
412
412
  }
413
- } catch (error) {
413
+ } catch {
414
414
  console.log(
415
415
  chalk.yellow(" \u26A0 Could not create claude-sm symlink (may need sudo)")
416
416
  );
@@ -506,7 +506,7 @@ fi
506
506
  execFileSync("ln", ["-s", codexSourcePath, codexBinPath]);
507
507
  console.log(chalk.green(" \u2713 Created codex-sm command"));
508
508
  }
509
- } catch (error) {
509
+ } catch {
510
510
  console.log(
511
511
  chalk.yellow(" \u26A0 Could not create codex-sm symlink (may need sudo)")
512
512
  );