@stackmemoryai/stackmemory 0.5.64 → 0.5.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -345
- package/bin/claude-sm +1 -1
- package/bin/claude-smd +1 -1
- package/bin/codex-smd +1 -1
- package/dist/src/cli/commands/skills.js +104 -0
- package/dist/src/cli/commands/skills.js.map +2 -2
- package/dist/src/cli/index.js +304 -1
- package/dist/src/cli/index.js.map +2 -2
- package/dist/src/integrations/mcp/pending-utils.js +33 -0
- package/dist/src/integrations/mcp/pending-utils.js.map +7 -0
- package/dist/src/integrations/mcp/server.js +565 -1
- package/dist/src/integrations/mcp/server.js.map +2 -2
- package/dist/src/orchestrators/multimodal/constants.js +17 -0
- package/dist/src/orchestrators/multimodal/constants.js.map +7 -0
- package/dist/src/orchestrators/multimodal/harness.js +293 -0
- package/dist/src/orchestrators/multimodal/harness.js.map +7 -0
- package/dist/src/orchestrators/multimodal/providers.js +85 -0
- package/dist/src/orchestrators/multimodal/providers.js.map +7 -0
- package/dist/src/orchestrators/multimodal/types.js +5 -0
- package/dist/src/orchestrators/multimodal/types.js.map +7 -0
- package/dist/src/orchestrators/multimodal/utils.js +25 -0
- package/dist/src/orchestrators/multimodal/utils.js.map +7 -0
- package/package.json +12 -9
- package/scripts/claude-code-wrapper.sh +18 -30
- package/scripts/demos/ralph-integration-demo.ts +14 -13
- package/scripts/demos/trace-demo.ts +7 -21
- package/scripts/demos/trace-test.ts +20 -8
- package/scripts/verify-dist.cjs +83 -0
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
const DEFAULT_PLANNER_MODEL = process.env.STACKMEMORY_MM_PLANNER_MODEL || "claude-3-5-sonnet-latest";
|
|
6
|
+
const DEFAULT_REVIEWER_MODEL = process.env.STACKMEMORY_MM_REVIEWER_MODEL || DEFAULT_PLANNER_MODEL;
|
|
7
|
+
const DEFAULT_IMPLEMENTER = process.env.STACKMEMORY_MM_IMPLEMENTER || "codex";
|
|
8
|
+
const DEFAULT_MAX_ITERS = Number(
|
|
9
|
+
process.env.STACKMEMORY_MM_MAX_ITERS || 2
|
|
10
|
+
);
|
|
11
|
+
export {
|
|
12
|
+
DEFAULT_IMPLEMENTER,
|
|
13
|
+
DEFAULT_MAX_ITERS,
|
|
14
|
+
DEFAULT_PLANNER_MODEL,
|
|
15
|
+
DEFAULT_REVIEWER_MODEL
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/orchestrators/multimodal/constants.ts"],
|
|
4
|
+
"sourcesContent": ["export const DEFAULT_PLANNER_MODEL =\n process.env.STACKMEMORY_MM_PLANNER_MODEL || 'claude-3-5-sonnet-latest';\nexport const DEFAULT_REVIEWER_MODEL =\n process.env.STACKMEMORY_MM_REVIEWER_MODEL || DEFAULT_PLANNER_MODEL;\nexport const DEFAULT_IMPLEMENTER = (process.env.STACKMEMORY_MM_IMPLEMENTER ||\n 'codex') as 'codex' | 'claude';\nexport const DEFAULT_MAX_ITERS = Number(\n process.env.STACKMEMORY_MM_MAX_ITERS || 2\n);\n"],
|
|
5
|
+
"mappings": ";;;;AAAO,MAAM,wBACX,QAAQ,IAAI,gCAAgC;AACvC,MAAM,yBACX,QAAQ,IAAI,iCAAiC;AACxC,MAAM,sBAAuB,QAAQ,IAAI,8BAC9C;AACK,MAAM,oBAAoB;AAAA,EAC/B,QAAQ,IAAI,4BAA4B;AAC1C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
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 { callClaude, callCodexCLI, implementWithClaude } from "./providers.js";
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import Database from "better-sqlite3";
|
|
9
|
+
import { FrameManager } from "../../core/context/index.js";
|
|
10
|
+
import { deriveProjectId } from "./utils.js";
|
|
11
|
+
function heuristicPlan(input) {
|
|
12
|
+
return {
|
|
13
|
+
summary: `Plan for: ${input.task}`,
|
|
14
|
+
steps: [
|
|
15
|
+
{
|
|
16
|
+
id: "step-1",
|
|
17
|
+
title: "Analyze requirements",
|
|
18
|
+
rationale: "Understand the task scope and constraints",
|
|
19
|
+
acceptanceCriteria: [
|
|
20
|
+
"Requirements clearly defined",
|
|
21
|
+
"Edge cases identified"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "step-2",
|
|
26
|
+
title: "Implement core changes",
|
|
27
|
+
rationale: "Make the minimal changes needed to complete the task",
|
|
28
|
+
acceptanceCriteria: [
|
|
29
|
+
"Code compiles without errors",
|
|
30
|
+
"Core functionality works"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "step-3",
|
|
35
|
+
title: "Verify and test",
|
|
36
|
+
rationale: "Ensure changes work correctly",
|
|
37
|
+
acceptanceCriteria: ["Tests pass", "No regressions introduced"]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
risks: [
|
|
41
|
+
"API key not configured - using heuristic plan",
|
|
42
|
+
"May need manual review of generated code"
|
|
43
|
+
]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function runSpike(input, options = {}) {
|
|
47
|
+
const plannerSystem = `You write concise, actionable implementation plans with numbered steps, acceptance criteria, and explicit risks. Output JSON only.`;
|
|
48
|
+
const contextSummary = getLocalContextSummary(input.repoPath);
|
|
49
|
+
const plannerPrompt = `Task: ${input.task}
|
|
50
|
+
Repo: ${input.repoPath}
|
|
51
|
+
Notes: ${input.contextNotes || "(none)"}
|
|
52
|
+
${contextSummary}
|
|
53
|
+
Constraints: Keep the plan minimal and implementable in a single PR.`;
|
|
54
|
+
let plan;
|
|
55
|
+
try {
|
|
56
|
+
const raw = await callClaude(plannerPrompt, {
|
|
57
|
+
model: options.plannerModel,
|
|
58
|
+
system: plannerSystem
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
plan = JSON.parse(raw);
|
|
62
|
+
} catch {
|
|
63
|
+
plan = heuristicPlan(input);
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
plan = heuristicPlan(input);
|
|
67
|
+
}
|
|
68
|
+
const implementer = options.implementer || "codex";
|
|
69
|
+
const maxIters = Math.max(1, options.maxIters ?? 2);
|
|
70
|
+
const iterations = [];
|
|
71
|
+
let approved = false;
|
|
72
|
+
let lastCommand = "";
|
|
73
|
+
let lastOutput = "";
|
|
74
|
+
let lastCritique = {
|
|
75
|
+
approved: true,
|
|
76
|
+
issues: [],
|
|
77
|
+
suggestions: []
|
|
78
|
+
};
|
|
79
|
+
for (let i = 0; i < maxIters; i++) {
|
|
80
|
+
const stepsList = plan.steps.map((s, idx) => `${idx + 1}. ${s.title}`).join("\n");
|
|
81
|
+
const basePrompt = `Implement the following plan:
|
|
82
|
+
${stepsList}
|
|
83
|
+
|
|
84
|
+
Keep changes minimal and focused. Avoid unrelated edits.`;
|
|
85
|
+
const refine = i === 0 ? "" : `
|
|
86
|
+
Incorporate reviewer suggestions: ${lastCritique.suggestions.join("; ")}`;
|
|
87
|
+
const implPrompt = basePrompt + refine;
|
|
88
|
+
let ok = false;
|
|
89
|
+
if (implementer === "codex") {
|
|
90
|
+
const impl = callCodexCLI(
|
|
91
|
+
implPrompt,
|
|
92
|
+
["--no-trace"],
|
|
93
|
+
options.dryRun !== false
|
|
94
|
+
);
|
|
95
|
+
ok = impl.ok;
|
|
96
|
+
lastCommand = impl.command;
|
|
97
|
+
lastOutput = impl.output;
|
|
98
|
+
} else {
|
|
99
|
+
const impl = await implementWithClaude(implPrompt, {
|
|
100
|
+
model: options.plannerModel
|
|
101
|
+
});
|
|
102
|
+
ok = impl.ok;
|
|
103
|
+
lastCommand = `claude:${options.plannerModel || "sonnet"} prompt`;
|
|
104
|
+
lastOutput = impl.output;
|
|
105
|
+
}
|
|
106
|
+
const criticSystem = `You are a strict code reviewer. Return a JSON object: { approved: boolean, issues: string[], suggestions: string[] }`;
|
|
107
|
+
const criticPrompt = `Plan: ${plan.summary}
|
|
108
|
+
Attempt ${i + 1}/${maxIters}
|
|
109
|
+
Command: ${lastCommand}
|
|
110
|
+
Output: ${lastOutput.slice(0, 2e3)}`;
|
|
111
|
+
try {
|
|
112
|
+
const raw = await callClaude(criticPrompt, {
|
|
113
|
+
model: options.reviewerModel,
|
|
114
|
+
system: criticSystem
|
|
115
|
+
});
|
|
116
|
+
lastCritique = JSON.parse(raw);
|
|
117
|
+
} catch {
|
|
118
|
+
lastCritique = {
|
|
119
|
+
approved: ok,
|
|
120
|
+
issues: ok ? [] : ["Critique failed"],
|
|
121
|
+
suggestions: []
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
iterations.push({
|
|
125
|
+
command: lastCommand,
|
|
126
|
+
ok,
|
|
127
|
+
outputPreview: lastOutput.slice(0, 400),
|
|
128
|
+
critique: lastCritique
|
|
129
|
+
});
|
|
130
|
+
if (lastCritique.approved) {
|
|
131
|
+
approved = true;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const dir = options.auditDir || path.join(input.repoPath, ".stackmemory", "build");
|
|
137
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
138
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
139
|
+
const file = path.join(dir, `spike-${stamp}.json`);
|
|
140
|
+
fs.writeFileSync(
|
|
141
|
+
file,
|
|
142
|
+
JSON.stringify(
|
|
143
|
+
{
|
|
144
|
+
input,
|
|
145
|
+
options: { ...options, auditDir: void 0 },
|
|
146
|
+
plan,
|
|
147
|
+
iterations
|
|
148
|
+
},
|
|
149
|
+
null,
|
|
150
|
+
2
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
if (options.record) {
|
|
156
|
+
recordContext(input.repoPath, "decision", `Plan: ${plan.summary}`, 0.8);
|
|
157
|
+
recordContext(
|
|
158
|
+
input.repoPath,
|
|
159
|
+
"decision",
|
|
160
|
+
`Critique: ${lastCritique.approved ? "approved" : "needs_changes"}`,
|
|
161
|
+
0.6
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (options.recordFrame) {
|
|
165
|
+
recordAsFrame(input.repoPath, input.task, plan, lastCritique, iterations);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
plan,
|
|
169
|
+
implementation: {
|
|
170
|
+
success: approved,
|
|
171
|
+
summary: approved ? "Implementation approved by critic" : "Implementation not approved",
|
|
172
|
+
commands: iterations.map((it) => it.command)
|
|
173
|
+
},
|
|
174
|
+
critique: lastCritique,
|
|
175
|
+
iterations
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const runPlanAndCode = runSpike;
|
|
179
|
+
function recordContext(repoPath, type, content, importance = 0.6) {
|
|
180
|
+
try {
|
|
181
|
+
const dbPath = path.join(repoPath, ".stackmemory", "context.db");
|
|
182
|
+
if (!fs.existsSync(path.dirname(dbPath)))
|
|
183
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
184
|
+
const db = new Database(dbPath);
|
|
185
|
+
db.exec(`
|
|
186
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
187
|
+
id TEXT PRIMARY KEY,
|
|
188
|
+
type TEXT NOT NULL,
|
|
189
|
+
content TEXT NOT NULL,
|
|
190
|
+
importance REAL DEFAULT 0.5,
|
|
191
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
192
|
+
last_accessed INTEGER DEFAULT (unixepoch()),
|
|
193
|
+
access_count INTEGER DEFAULT 1
|
|
194
|
+
);
|
|
195
|
+
`);
|
|
196
|
+
const id = `ctx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
197
|
+
const stmt = db.prepare(
|
|
198
|
+
"INSERT OR REPLACE INTO contexts (id, type, content, importance) VALUES (?, ?, ?, ?)"
|
|
199
|
+
);
|
|
200
|
+
stmt.run(id, type, content, importance);
|
|
201
|
+
db.close();
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function recordAsFrame(repoPath, task, plan, critique, iterations) {
|
|
206
|
+
try {
|
|
207
|
+
const dbPath = path.join(repoPath, ".stackmemory", "context.db");
|
|
208
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
209
|
+
const db = new Database(dbPath);
|
|
210
|
+
const projectId = deriveProjectId(repoPath);
|
|
211
|
+
const fm = new FrameManager(db, projectId);
|
|
212
|
+
const frameId = fm.createFrame({
|
|
213
|
+
type: "task",
|
|
214
|
+
name: `Plan & Code: ${task}`,
|
|
215
|
+
inputs: { plan }
|
|
216
|
+
});
|
|
217
|
+
fm.addAnchor("DECISION", plan.summary, 8, { source: "build" }, frameId);
|
|
218
|
+
const commands = iterations.map((it) => it.command).filter(Boolean);
|
|
219
|
+
if (commands.length) {
|
|
220
|
+
fm.addAnchor(
|
|
221
|
+
"FACT",
|
|
222
|
+
`Commands: ${commands.join(" | ")}`,
|
|
223
|
+
5,
|
|
224
|
+
{ commands },
|
|
225
|
+
frameId
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (critique.issues?.length) {
|
|
229
|
+
critique.issues.slice(0, 5).forEach((issue) => fm.addAnchor("RISK", issue, 6, {}, frameId));
|
|
230
|
+
}
|
|
231
|
+
if (critique.suggestions?.length) {
|
|
232
|
+
critique.suggestions.slice(0, 5).forEach(
|
|
233
|
+
(s) => fm.addAnchor("TODO", s, 5, { from: "critic" }, frameId)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
fm.closeFrame(frameId, { approved: critique.approved });
|
|
237
|
+
db.close();
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function runPlanOnly(input, options = {}) {
|
|
242
|
+
const plannerSystem = `You write concise, actionable implementation plans with numbered steps, acceptance criteria, and explicit risks. Output JSON only.`;
|
|
243
|
+
const contextSummary = getLocalContextSummary(input.repoPath);
|
|
244
|
+
const plannerPrompt = `Task: ${input.task}
|
|
245
|
+
Repo: ${input.repoPath}
|
|
246
|
+
Notes: ${input.contextNotes || "(none)"}
|
|
247
|
+
${contextSummary}
|
|
248
|
+
Constraints: Keep the plan minimal and implementable in a single PR.`;
|
|
249
|
+
try {
|
|
250
|
+
const raw = await callClaude(plannerPrompt, {
|
|
251
|
+
model: options.plannerModel,
|
|
252
|
+
system: plannerSystem
|
|
253
|
+
});
|
|
254
|
+
try {
|
|
255
|
+
return JSON.parse(raw);
|
|
256
|
+
} catch {
|
|
257
|
+
return heuristicPlan(input);
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
return heuristicPlan(input);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function getLocalContextSummary(repoPath) {
|
|
264
|
+
try {
|
|
265
|
+
const dbPath = path.join(repoPath, ".stackmemory", "context.db");
|
|
266
|
+
if (!fs.existsSync(dbPath)) return "Project context: (no local DB found)";
|
|
267
|
+
const db = new Database(dbPath);
|
|
268
|
+
const frames = db.prepare(
|
|
269
|
+
"SELECT name,type,state,digest_text,created_at FROM frames ORDER BY created_at DESC LIMIT 5"
|
|
270
|
+
).all();
|
|
271
|
+
const anchors = db.prepare(
|
|
272
|
+
"SELECT type,text,priority,created_at FROM anchors ORDER BY created_at DESC LIMIT 5"
|
|
273
|
+
).all();
|
|
274
|
+
db.close();
|
|
275
|
+
const fStr = frames.map(
|
|
276
|
+
(f) => `- [${f.type}/${f.state}] ${f.name} ${f.digest_text ? `\u2014 ${f.digest_text}` : ""}`
|
|
277
|
+
).join("\n");
|
|
278
|
+
const aStr = anchors.map((a) => `- (${a.priority}) [${a.type}] ${a.text}`).join("\n");
|
|
279
|
+
return `Project context:
|
|
280
|
+
Recent frames:
|
|
281
|
+
${fStr || "(none)"}
|
|
282
|
+
Recent anchors:
|
|
283
|
+
${aStr || "(none)"}`;
|
|
284
|
+
} catch {
|
|
285
|
+
return "Project context: (unavailable \u2014 local DB not ready)";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
export {
|
|
289
|
+
runPlanAndCode,
|
|
290
|
+
runPlanOnly,
|
|
291
|
+
runSpike
|
|
292
|
+
};
|
|
293
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/orchestrators/multimodal/harness.ts"],
|
|
4
|
+
"sourcesContent": ["import { callClaude, callCodexCLI, implementWithClaude } from './providers.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport Database from 'better-sqlite3';\nimport { FrameManager } from '../../core/context/index.js';\nimport { deriveProjectId } from './utils.js';\nimport type {\n PlanningInput,\n ImplementationPlan,\n CritiqueResult,\n HarnessOptions,\n HarnessResult,\n} from './types.js';\n\nfunction heuristicPlan(input: PlanningInput): ImplementationPlan {\n // Generic fallback plan when Claude API is unavailable\n return {\n summary: `Plan for: ${input.task}`,\n steps: [\n {\n id: 'step-1',\n title: 'Analyze requirements',\n rationale: 'Understand the task scope and constraints',\n acceptanceCriteria: [\n 'Requirements clearly defined',\n 'Edge cases identified',\n ],\n },\n {\n id: 'step-2',\n title: 'Implement core changes',\n rationale: 'Make the minimal changes needed to complete the task',\n acceptanceCriteria: [\n 'Code compiles without errors',\n 'Core functionality works',\n ],\n },\n {\n id: 'step-3',\n title: 'Verify and test',\n rationale: 'Ensure changes work correctly',\n acceptanceCriteria: ['Tests pass', 'No regressions introduced'],\n },\n ],\n risks: [\n 'API key not configured - using heuristic plan',\n 'May need manual review of generated code',\n ],\n };\n}\n\nexport async function runSpike(\n input: PlanningInput,\n options: HarnessOptions = {}\n): Promise<HarnessResult> {\n const plannerSystem = `You write concise, actionable implementation plans with numbered steps, acceptance criteria, and explicit risks. Output JSON only.`;\n\n // Attempt to enrich planner prompt with local StackMemory context (best-effort)\n const contextSummary = getLocalContextSummary(input.repoPath);\n const plannerPrompt = `Task: ${input.task}\\nRepo: ${input.repoPath}\\nNotes: ${input.contextNotes || '(none)'}\\n${contextSummary}\\nConstraints: Keep the plan minimal and implementable in a single PR.`;\n\n let plan: ImplementationPlan;\n try {\n const raw = await callClaude(plannerPrompt, {\n model: options.plannerModel,\n system: plannerSystem,\n });\n try {\n plan = JSON.parse(raw);\n } catch {\n // Fall back to heuristic if model returned text\n plan = heuristicPlan(input);\n }\n } catch {\n plan = heuristicPlan(input);\n }\n\n // Implementer (Codex by default) with retry loop driven by critique suggestions\n const implementer = (options.implementer || 'codex') as 'codex' | 'claude';\n const maxIters = Math.max(1, options.maxIters ?? 2);\n const iterations: HarnessResult['iterations'] = [];\n\n let approved = false;\n let lastCommand = '';\n let lastOutput = '';\n let lastCritique: CritiqueResult = {\n approved: true,\n issues: [],\n suggestions: [],\n };\n\n for (let i = 0; i < maxIters; i++) {\n // Build implementation prompt from all plan steps\n const stepsList = plan.steps\n .map((s, idx) => `${idx + 1}. ${s.title}`)\n .join('\\n');\n const basePrompt = `Implement the following plan:\\n${stepsList}\\n\\nKeep changes minimal and focused. Avoid unrelated edits.`;\n const refine =\n i === 0\n ? ''\n : `\\nIncorporate reviewer suggestions: ${lastCritique.suggestions.join('; ')}`;\n const implPrompt = basePrompt + refine;\n\n let ok = false;\n if (implementer === 'codex') {\n const impl = callCodexCLI(\n implPrompt,\n ['--no-trace'],\n options.dryRun !== false\n );\n ok = impl.ok;\n lastCommand = impl.command;\n lastOutput = impl.output;\n } else {\n const impl = await implementWithClaude(implPrompt, {\n model: options.plannerModel,\n });\n ok = impl.ok;\n lastCommand = `claude:${options.plannerModel || 'sonnet'} prompt`; // logical label\n lastOutput = impl.output;\n }\n\n // Critic\n const criticSystem = `You are a strict code reviewer. Return a JSON object: { approved: boolean, issues: string[], suggestions: string[] }`;\n const criticPrompt = `Plan: ${plan.summary}\\nAttempt ${i + 1}/${maxIters}\\nCommand: ${lastCommand}\\nOutput: ${lastOutput.slice(0, 2000)}`;\n try {\n const raw = await callClaude(criticPrompt, {\n model: options.reviewerModel,\n system: criticSystem,\n });\n lastCritique = JSON.parse(raw);\n } catch {\n lastCritique = {\n approved: ok,\n issues: ok ? [] : ['Critique failed'],\n suggestions: [],\n };\n }\n\n iterations.push({\n command: lastCommand,\n ok,\n outputPreview: lastOutput.slice(0, 400),\n critique: lastCritique,\n });\n\n if (lastCritique.approved) {\n approved = true;\n break;\n }\n }\n\n // Persist audit\n try {\n const dir =\n options.auditDir || path.join(input.repoPath, '.stackmemory', 'build');\n fs.mkdirSync(dir, { recursive: true });\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const file = path.join(dir, `spike-${stamp}.json`);\n fs.writeFileSync(\n file,\n JSON.stringify(\n {\n input,\n options: { ...options, auditDir: undefined },\n plan,\n iterations,\n },\n null,\n 2\n )\n );\n } catch {\n // best-effort only\n }\n\n // Optionally record to local context DB\n if (options.record) {\n recordContext(input.repoPath, 'decision', `Plan: ${plan.summary}`, 0.8);\n recordContext(\n input.repoPath,\n 'decision',\n `Critique: ${lastCritique.approved ? 'approved' : 'needs_changes'}`,\n 0.6\n );\n }\n\n // Optionally record as a real frame with anchors\n if (options.recordFrame) {\n recordAsFrame(input.repoPath, input.task, plan, lastCritique, iterations);\n }\n\n return {\n plan,\n implementation: {\n success: approved,\n summary: approved\n ? 'Implementation approved by critic'\n : 'Implementation not approved',\n commands: iterations.map((it) => it.command),\n },\n critique: lastCritique,\n iterations,\n };\n}\n\n// Programmatic API alias, intended for chat UIs to call directly\nexport const runPlanAndCode = runSpike;\n\n// Best-effort context recorder compatible with MCP server's contexts table\nfunction recordContext(\n repoPath: string,\n type: string,\n content: string,\n importance = 0.6\n) {\n try {\n const dbPath = path.join(repoPath, '.stackmemory', 'context.db');\n if (!fs.existsSync(path.dirname(dbPath)))\n fs.mkdirSync(path.dirname(dbPath), { recursive: true });\n const db = new Database(dbPath);\n db.exec(`\n CREATE TABLE IF NOT EXISTS contexts (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n content TEXT NOT NULL,\n importance REAL DEFAULT 0.5,\n created_at INTEGER DEFAULT (unixepoch()),\n last_accessed INTEGER DEFAULT (unixepoch()),\n access_count INTEGER DEFAULT 1\n );\n `);\n const id = `ctx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n const stmt = db.prepare(\n 'INSERT OR REPLACE INTO contexts (id, type, content, importance) VALUES (?, ?, ?, ?)'\n );\n stmt.run(id, type, content, importance);\n db.close();\n } catch {\n // ignore\n }\n}\n\nfunction recordAsFrame(\n repoPath: string,\n task: string,\n plan: ImplementationPlan,\n critique: CritiqueResult,\n iterations: NonNullable<HarnessResult['iterations']>\n) {\n try {\n const dbPath = path.join(repoPath, '.stackmemory', 'context.db');\n fs.mkdirSync(path.dirname(dbPath), { recursive: true });\n const db = new Database(dbPath);\n const projectId = deriveProjectId(repoPath);\n const fm = new FrameManager(db, projectId);\n const frameId = fm.createFrame({\n type: 'task',\n name: `Plan & Code: ${task}`,\n inputs: { plan },\n });\n\n // Anchors: decision (plan summary), fact (implementer commands), risk/todo (critique)\n fm.addAnchor('DECISION', plan.summary, 8, { source: 'build' }, frameId);\n const commands = iterations.map((it) => it.command).filter(Boolean);\n if (commands.length) {\n fm.addAnchor(\n 'FACT',\n `Commands: ${commands.join(' | ')}`,\n 5,\n { commands },\n frameId\n );\n }\n if (critique.issues?.length) {\n critique.issues\n .slice(0, 5)\n .forEach((issue) => fm.addAnchor('RISK', issue, 6, {}, frameId));\n }\n if (critique.suggestions?.length) {\n critique.suggestions\n .slice(0, 5)\n .forEach((s) =>\n fm.addAnchor('TODO', s, 5, { from: 'critic' }, frameId)\n );\n }\n\n fm.closeFrame(frameId, { approved: critique.approved });\n db.close();\n } catch {\n // best-effort\n }\n}\n\n// Lightweight planner: returns only the plan without implementation/critique\nexport async function runPlanOnly(\n input: PlanningInput,\n options: { plannerModel?: string } = {}\n): Promise<ImplementationPlan> {\n const plannerSystem = `You write concise, actionable implementation plans with numbered steps, acceptance criteria, and explicit risks. Output JSON only.`;\n const contextSummary = getLocalContextSummary(input.repoPath);\n const plannerPrompt = `Task: ${input.task}\\nRepo: ${input.repoPath}\\nNotes: ${input.contextNotes || '(none)'}\\n${contextSummary}\\nConstraints: Keep the plan minimal and implementable in a single PR.`;\n\n try {\n const raw = await callClaude(plannerPrompt, {\n model: options.plannerModel,\n system: plannerSystem,\n });\n try {\n return JSON.parse(raw);\n } catch {\n return heuristicPlan(input);\n }\n } catch {\n return heuristicPlan(input);\n }\n}\n\nfunction getLocalContextSummary(repoPath: string): string {\n try {\n const dbPath = path.join(repoPath, '.stackmemory', 'context.db');\n if (!fs.existsSync(dbPath)) return 'Project context: (no local DB found)';\n const db = new Database(dbPath);\n // recent frames\n const frames = db\n .prepare(\n 'SELECT name,type,state,digest_text,created_at FROM frames ORDER BY created_at DESC LIMIT 5'\n )\n .all() as Array<{\n name: string;\n type: string;\n state: string;\n digest_text: string | null;\n created_at: number;\n }>;\n // recent anchors\n const anchors = db\n .prepare(\n 'SELECT type,text,priority,created_at FROM anchors ORDER BY created_at DESC LIMIT 5'\n )\n .all() as Array<{\n type: string;\n text: string;\n priority: number;\n created_at: number;\n }>;\n db.close();\n\n const fStr = frames\n .map(\n (f) =>\n `- [${f.type}/${f.state}] ${f.name} ${f.digest_text ? `\u2014 ${f.digest_text}` : ''}`\n )\n .join('\\n');\n const aStr = anchors\n .map((a) => `- (${a.priority}) [${a.type}] ${a.text}`)\n .join('\\n');\n return `Project context:\\nRecent frames:\\n${fStr || '(none)'}\\nRecent anchors:\\n${aStr || '(none)'}`;\n } catch {\n return 'Project context: (unavailable \u2014 local DB not ready)';\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { spawnSync } from "child_process";
|
|
6
|
+
async function callClaude(prompt, options) {
|
|
7
|
+
const apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
const sys = (options.system || "").toLowerCase();
|
|
10
|
+
if (sys.includes("strict code reviewer") || sys.includes("return a json object") || sys.includes("approved")) {
|
|
11
|
+
return JSON.stringify({ approved: true, issues: [], suggestions: [] });
|
|
12
|
+
}
|
|
13
|
+
return `STUB: No ANTHROPIC_API_KEY set. Returning heuristic plan for prompt: ${prompt.slice(0, 80).trim()}...`;
|
|
14
|
+
}
|
|
15
|
+
const { Anthropic } = await import("@anthropic-ai/sdk");
|
|
16
|
+
const client = new Anthropic({ apiKey });
|
|
17
|
+
const model = options.model || "claude-3-5-sonnet-latest";
|
|
18
|
+
const system = options.system || "You are a precise software planning assistant.";
|
|
19
|
+
const msg = await client.messages.create({
|
|
20
|
+
model,
|
|
21
|
+
max_tokens: 4096,
|
|
22
|
+
system,
|
|
23
|
+
messages: [{ role: "user", content: prompt }]
|
|
24
|
+
});
|
|
25
|
+
const block = msg?.content?.[0];
|
|
26
|
+
const text = block && "text" in block ? block.text : JSON.stringify(msg);
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
function callCodexCLI(prompt, args = [], dryRun = true) {
|
|
30
|
+
const cmd = "codex-sm";
|
|
31
|
+
const fullArgs = ["-p", prompt, ...args];
|
|
32
|
+
const printable = `${cmd} ${fullArgs.map((a) => a.includes(" ") ? `'${a}'` : a).join(" ")}`;
|
|
33
|
+
if (dryRun) {
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
output: "[DRY RUN] Skipped execution",
|
|
37
|
+
command: printable
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const whichSm = spawnSync("which", ["codex-sm"], { encoding: "utf8" });
|
|
42
|
+
const whichCodex = spawnSync("which", ["codex"], { encoding: "utf8" });
|
|
43
|
+
const whichCli = spawnSync("which", ["codex-cli"], { encoding: "utf8" });
|
|
44
|
+
const availableCmd = whichSm.status === 0 ? "codex-sm" : whichCodex.status === 0 ? "codex" : whichCli.status === 0 ? "codex-cli" : null;
|
|
45
|
+
if (!availableCmd) {
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
output: "[OFFLINE] Codex CLI not found; skipping execution",
|
|
49
|
+
command: printable
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const res = spawnSync(availableCmd, fullArgs, { encoding: "utf8" });
|
|
53
|
+
if (res.status !== 0) {
|
|
54
|
+
return {
|
|
55
|
+
ok: true,
|
|
56
|
+
output: "[OFFLINE] Codex run failed or unavailable; treating as no-op for offline run",
|
|
57
|
+
command: printable
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
output: (res.stdout || "") + (res.stderr || ""),
|
|
63
|
+
command: printable
|
|
64
|
+
};
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return { ok: false, output: e?.message || String(e), command: printable };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function implementWithClaude(prompt, options) {
|
|
70
|
+
try {
|
|
71
|
+
const out = await callClaude(prompt, {
|
|
72
|
+
model: options.model || "claude-3-5-sonnet-latest",
|
|
73
|
+
system: options.system || "You generate minimal diffs/patches for the described change, focusing on one file at a time."
|
|
74
|
+
});
|
|
75
|
+
return { ok: true, output: out };
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return { ok: false, output: e?.message || String(e) };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
callClaude,
|
|
82
|
+
callCodexCLI,
|
|
83
|
+
implementWithClaude
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/orchestrators/multimodal/providers.ts"],
|
|
4
|
+
"sourcesContent": ["import { spawnSync } from 'child_process';\n\n// Lightweight provider wrappers with safe fallbacks for a spike.\n\nexport async function callClaude(\n prompt: string,\n options: { model?: string; system?: string }\n): Promise<string> {\n // Use Anthropic SDK only if key is present; otherwise return a stubbed plan\n const apiKey = process.env['ANTHROPIC_API_KEY'];\n if (!apiKey) {\n // If this is used as the critic, return a valid JSON approval to allow offline runs\n const sys = (options.system || '').toLowerCase();\n if (\n sys.includes('strict code reviewer') ||\n sys.includes('return a json object') ||\n sys.includes('approved')\n ) {\n return JSON.stringify({ approved: true, issues: [], suggestions: [] });\n }\n // Otherwise, return a heuristic hint (planner will fall back to heuristicPlan)\n return `STUB: No ANTHROPIC_API_KEY set. Returning heuristic plan for prompt: ${prompt\n .slice(0, 80)\n .trim()}...`;\n }\n\n // Dynamic import to avoid bundling the SDK when not needed\n const { Anthropic } = await import('@anthropic-ai/sdk');\n const client = new Anthropic({ apiKey });\n const model = options.model || 'claude-3-5-sonnet-latest';\n const system =\n options.system || 'You are a precise software planning assistant.';\n\n const msg = await client.messages.create({\n model,\n max_tokens: 4096,\n system,\n messages: [{ role: 'user', content: prompt }],\n });\n const block = msg?.content?.[0];\n const text = block && 'text' in block ? block.text : JSON.stringify(msg);\n return text;\n}\n\nexport function callCodexCLI(\n prompt: string,\n args: string[] = [],\n dryRun = true\n): {\n ok: boolean;\n output: string;\n command: string;\n} {\n // Prefer our codex-sm wrapper which sets tracing/context\n const cmd = 'codex-sm';\n const fullArgs = ['-p', prompt, ...args];\n const printable = `${cmd} ${fullArgs.map((a) => (a.includes(' ') ? `'${a}'` : a)).join(' ')}`;\n\n if (dryRun) {\n return {\n ok: true,\n output: '[DRY RUN] Skipped execution',\n command: printable,\n };\n }\n\n try {\n // Check if any codex binary is available (codex-sm preferred, fallback to codex/codex-cli)\n const whichSm = spawnSync('which', ['codex-sm'], { encoding: 'utf8' });\n const whichCodex = spawnSync('which', ['codex'], { encoding: 'utf8' });\n const whichCli = spawnSync('which', ['codex-cli'], { encoding: 'utf8' });\n const availableCmd =\n whichSm.status === 0\n ? 'codex-sm'\n : whichCodex.status === 0\n ? 'codex'\n : whichCli.status === 0\n ? 'codex-cli'\n : null;\n\n if (!availableCmd) {\n return {\n ok: true,\n output: '[OFFLINE] Codex CLI not found; skipping execution',\n command: printable,\n };\n }\n const res = spawnSync(availableCmd, fullArgs, { encoding: 'utf8' });\n if (res.status !== 0) {\n return {\n ok: true,\n output:\n '[OFFLINE] Codex run failed or unavailable; treating as no-op for offline run',\n command: printable,\n };\n }\n return {\n ok: true,\n output: (res.stdout || '') + (res.stderr || ''),\n command: printable,\n };\n } catch (e: any) {\n return { ok: false, output: e?.message || String(e), command: printable };\n }\n}\n\nexport async function implementWithClaude(\n prompt: string,\n options: { model?: string; system?: string }\n): Promise<{ ok: boolean; output: string }> {\n try {\n const out = await callClaude(prompt, {\n model: options.model || 'claude-3-5-sonnet-latest',\n system:\n options.system ||\n 'You generate minimal diffs/patches for the described change, focusing on one file at a time.',\n });\n return { ok: true, output: out };\n } catch (e: any) {\n return { ok: false, output: e?.message || String(e) };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
function deriveProjectId(repoPath) {
|
|
6
|
+
const id = repoPath.replace(/\/+$/, "").split("/").pop() || "project";
|
|
7
|
+
return id.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(-50) || "project";
|
|
8
|
+
}
|
|
9
|
+
function compactPlan(plan) {
|
|
10
|
+
try {
|
|
11
|
+
const steps = Array.isArray(plan?.steps) ? plan.steps.map((s) => ({
|
|
12
|
+
id: s.id,
|
|
13
|
+
title: s.title,
|
|
14
|
+
acceptanceCriteria: s.acceptanceCriteria
|
|
15
|
+
})) : [];
|
|
16
|
+
return { summary: plan?.summary, steps, risks: plan?.risks };
|
|
17
|
+
} catch {
|
|
18
|
+
return plan;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
compactPlan,
|
|
23
|
+
deriveProjectId
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/orchestrators/multimodal/utils.ts"],
|
|
4
|
+
"sourcesContent": ["export function deriveProjectId(repoPath: string): string {\n const id = repoPath.replace(/\\/+$/, '').split('/').pop() || 'project';\n return (\n id\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .slice(-50) || 'project'\n );\n}\n\nexport function compactPlan(plan: any) {\n try {\n const steps = Array.isArray(plan?.steps)\n ? plan.steps.map((s: any) => ({\n id: s.id,\n title: s.title,\n acceptanceCriteria: s.acceptanceCriteria,\n }))\n : [];\n return { summary: plan?.summary, steps, risks: plan?.risks };\n } catch {\n return plan;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;AAAO,SAAS,gBAAgB,UAA0B;AACxD,QAAM,KAAK,SAAS,QAAQ,QAAQ,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK;AAC5D,SACE,GACG,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,MAAM,GAAG,KAAK;AAErB;AAEO,SAAS,YAAY,MAAW;AACrC,MAAI;AACF,UAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IACnC,KAAK,MAAM,IAAI,CAAC,OAAY;AAAA,MAC1B,IAAI,EAAE;AAAA,MACN,OAAO,EAAE;AAAA,MACT,oBAAoB,EAAE;AAAA,IACxB,EAAE,IACF,CAAC;AACL,WAAO,EAAE,SAAS,MAAM,SAAS,OAAO,OAAO,MAAM,MAAM;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackmemoryai/stackmemory",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.66",
|
|
4
|
+
"description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, and smart retrieval.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"model-routing"
|
|
50
50
|
],
|
|
51
51
|
"scripts": {
|
|
52
|
-
"start": "node dist/integrations/mcp/server.js",
|
|
53
|
-
"start:full": "node dist/integrations/mcp/server.js",
|
|
52
|
+
"start": "node dist/src/integrations/mcp/server.js",
|
|
53
|
+
"start:full": "node dist/src/integrations/mcp/server.js",
|
|
54
54
|
"setup": "npm install && npm run build && npm run init",
|
|
55
55
|
"postinstall": "node scripts/install-claude-hooks-auto.js || true",
|
|
56
56
|
"init": "node dist/scripts/initialize.js",
|
|
@@ -65,11 +65,11 @@
|
|
|
65
65
|
"test:run": "vitest run",
|
|
66
66
|
"test:pre-publish": "./scripts/test-pre-publish-quick.sh",
|
|
67
67
|
"test:pre-commit": "vitest related --run --reporter=dot --silent --bail=1",
|
|
68
|
-
"prepublishOnly": "npm run build && npm run test:pre-publish",
|
|
68
|
+
"prepublishOnly": "npm run build && npm run verify:dist && npm run test:pre-publish",
|
|
69
69
|
"quality": "npm run lint && npm run test:run && npm run build",
|
|
70
|
-
"dev": "tsx watch src/mcp/
|
|
71
|
-
"mcp:start": "node dist/integrations/mcp/server.js",
|
|
72
|
-
"mcp:dev": "tsx src/mcp/
|
|
70
|
+
"dev": "tsx watch src/integrations/mcp/server.ts",
|
|
71
|
+
"mcp:start": "node dist/src/integrations/mcp/server.js",
|
|
72
|
+
"mcp:dev": "tsx src/integrations/mcp/server.ts",
|
|
73
73
|
"status": "node dist/scripts/status.js",
|
|
74
74
|
"linear:sync": "node scripts/sync-linear-graphql.js",
|
|
75
75
|
"linear:mirror": "node scripts/sync-linear-graphql.js --mirror",
|
|
@@ -86,7 +86,10 @@
|
|
|
86
86
|
"daemon:status": "node dist/cli/index.js daemon status",
|
|
87
87
|
"sync:start": "node scripts/background-sync-manager.js",
|
|
88
88
|
"sync:setup": "./scripts/setup-background-sync.sh",
|
|
89
|
-
"prepare": "echo 'Prepare step completed'"
|
|
89
|
+
"prepare": "echo 'Prepare step completed'",
|
|
90
|
+
"verify:dist": "node scripts/verify-dist.cjs",
|
|
91
|
+
"rebuild:native": "npm rebuild better-sqlite3 || true",
|
|
92
|
+
"deps:reset": "rm -rf node_modules package-lock.json && npm ci"
|
|
90
93
|
},
|
|
91
94
|
"dependencies": {
|
|
92
95
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
@@ -3,62 +3,50 @@
|
|
|
3
3
|
# Claude Code wrapper with StackMemory integration
|
|
4
4
|
# Usage: Add alias to ~/.zshrc: alias claude='~/Dev/stackmemory/scripts/claude-code-wrapper.sh'
|
|
5
5
|
|
|
6
|
-
# Check for auto-sync flag
|
|
6
|
+
# Check for auto-sync flag and filter wrapper-specific args
|
|
7
7
|
AUTO_SYNC=false
|
|
8
8
|
SYNC_INTERVAL=5
|
|
9
|
+
CLAUDE_ARGS=()
|
|
10
|
+
|
|
9
11
|
for arg in "$@"; do
|
|
10
12
|
case $arg in
|
|
11
13
|
--auto-sync)
|
|
12
14
|
AUTO_SYNC=true
|
|
13
|
-
shift
|
|
14
15
|
;;
|
|
15
16
|
--sync-interval=*)
|
|
16
17
|
SYNC_INTERVAL="${arg#*=}"
|
|
17
|
-
|
|
18
|
+
;;
|
|
19
|
+
*)
|
|
20
|
+
CLAUDE_ARGS+=("$arg")
|
|
18
21
|
;;
|
|
19
22
|
esac
|
|
20
23
|
done
|
|
21
24
|
|
|
22
|
-
# Start Linear auto-sync in background if requested
|
|
23
|
-
SYNC_PID=""
|
|
25
|
+
# Start Linear auto-sync in background if requested (survives exec)
|
|
24
26
|
if [ "$AUTO_SYNC" = true ] && [ -n "$LINEAR_API_KEY" ]; then
|
|
25
27
|
echo "🔄 Starting Linear auto-sync (${SYNC_INTERVAL}min intervals)..."
|
|
26
|
-
|
|
28
|
+
nohup bash -c "
|
|
27
29
|
while true; do
|
|
28
30
|
sleep $((SYNC_INTERVAL * 60))
|
|
29
|
-
if [ -d "
|
|
31
|
+
if [ -d \"$PWD/.stackmemory\" ]; then
|
|
30
32
|
stackmemory linear sync --quiet 2>/dev/null || true
|
|
31
33
|
fi
|
|
32
34
|
done
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
" > /dev/null 2>&1 &
|
|
36
|
+
disown
|
|
35
37
|
fi
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# Kill auto-sync if running
|
|
41
|
-
if [ -n "$SYNC_PID" ] && kill -0 $SYNC_PID 2>/dev/null; then
|
|
42
|
-
echo "🛑 Stopping auto-sync..."
|
|
43
|
-
kill $SYNC_PID 2>/dev/null || true
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
# Check if in a git repo with stackmemory
|
|
47
|
-
if [ -d ".stackmemory" ] && [ -f "stackmemory.json" ]; then
|
|
48
|
-
# Save current context (without sync)
|
|
49
|
-
stackmemory status 2>/dev/null || true
|
|
50
|
-
echo "✅ StackMemory context saved"
|
|
51
|
-
fi
|
|
52
|
-
}
|
|
39
|
+
# Note: Cleanup is now handled by Claude hooks instead of this wrapper
|
|
40
|
+
# See: stackmemory setup-hooks --cleanup
|
|
53
41
|
|
|
54
|
-
#
|
|
55
|
-
|
|
42
|
+
# Run Claude Code with exec for full TTY control (interactive mode)
|
|
43
|
+
# This replaces the shell process, ensuring stdin works properly
|
|
44
|
+
# Note: cleanup trap won't run with exec - use Claude hooks for session cleanup instead
|
|
56
45
|
|
|
57
|
-
# Run Claude Code (try multiple possible command names)
|
|
58
46
|
if command -v claude-code &> /dev/null; then
|
|
59
|
-
claude-code "
|
|
47
|
+
exec claude-code "${CLAUDE_ARGS[@]}"
|
|
60
48
|
elif command -v claude &> /dev/null; then
|
|
61
|
-
claude "
|
|
49
|
+
exec claude "${CLAUDE_ARGS[@]}"
|
|
62
50
|
else
|
|
63
51
|
echo "❌ Claude Code not found. Please install it first."
|
|
64
52
|
echo " Visit: https://github.com/anthropics/claude-code"
|