@binarycheater/research-sidecar 0.1.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.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/README.zh.md +244 -0
- package/bin/research-sidecar.mjs +87 -0
- package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/client/assets/index-BpVgCKdz.css +1 -0
- package/dist/client/assets/index-D7VDrQ1Q.js +324 -0
- package/dist/client/index.html +13 -0
- package/dist-server/lib/context.js +70 -0
- package/dist-server/lib/files.js +118 -0
- package/dist-server/lib/graphDiscovery.js +69 -0
- package/dist-server/lib/openaiProvider.js +89 -0
- package/dist-server/lib/prompt.js +30 -0
- package/dist-server/lib/researchGraph.js +144 -0
- package/dist-server/lib/researchGraphManifest.js +221 -0
- package/dist-server/lib/sidebarLayout.js +17 -0
- package/dist-server/lib/store.js +190 -0
- package/dist-server/lib/tools.js +205 -0
- package/dist-server/lib/types.js +1 -0
- package/dist-server/lib/workspaceInstall.js +157 -0
- package/dist-server/lib/workspaceMeta.js +171 -0
- package/dist-server/server/config.js +82 -0
- package/dist-server/server/index.js +365 -0
- package/package.json +83 -0
- package/scripts/codex-sidecar.mjs +325 -0
- package/scripts/prepare-package.mjs +14 -0
- package/skills/research-graph-sop/SKILL.md +183 -0
- package/skills/research-graph-sop/agents/openai.yaml +4 -0
- package/skills/scholar-mode/SKILL.md +34 -0
- package/skills/scholar-mode/agents/openai.yaml +4 -0
- package/skills/sidecar-thinking/SKILL.md +67 -0
- package/skills/sidecar-thinking/agents/openai.yaml +4 -0
- package/skills/writing-explanatory-reports/SKILL.md +134 -0
- package/skills/writing-explanatory-reports/agents/openai.yaml +4 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { access, cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const args = parseArgs(process.argv.slice(2));
|
|
8
|
+
const baseUrl = args.url || "http://localhost:4317";
|
|
9
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const appRoot = resolve(scriptDir, "..");
|
|
11
|
+
const repoRoot = resolve(appRoot, "..");
|
|
12
|
+
const defaultAllowedWriteExtensions = [".md", ".markdown", ".html", ".htm", ".yaml", ".yml"];
|
|
13
|
+
|
|
14
|
+
if (args.help) {
|
|
15
|
+
console.log(`Usage:
|
|
16
|
+
npm run codex:install -- --workspace /path/to/research-repo
|
|
17
|
+
npm run codex:call -- --title "Review" --context "Codex summary" --file SKILL.md --question "What should the sidecar inspect?"
|
|
18
|
+
npm run codex:ask -- --title "Review" --context "Codex summary" --question "What is the weakest assumption?"
|
|
19
|
+
npm run codex:session -- --title "Review" --context "Codex summary" --file SKILL.md --file notes.md
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--url Sidecar server URL. Default: http://localhost:4317
|
|
23
|
+
--workspace Workspace directory to initialize for install mode.
|
|
24
|
+
--graph Graph manifest path inside the workspace. Default: research/graph.yaml
|
|
25
|
+
--no-graph Do not create a starter graph manifest.
|
|
26
|
+
--no-skills Do not copy bundled skills into the workspace.
|
|
27
|
+
--force Overwrite install-managed files when possible.
|
|
28
|
+
--title Session title.
|
|
29
|
+
--context Manual context packet notes.
|
|
30
|
+
--file Workspace-relative file path. Repeatable.
|
|
31
|
+
--question Optional user question for call mode; required prompt for ask mode.
|
|
32
|
+
--model Model string. Default server model is used when omitted.
|
|
33
|
+
--api responses or chat.
|
|
34
|
+
`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (args.command === "install") {
|
|
39
|
+
await installWorkspace(args);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (args.command !== "call" && args.command !== "session" && args.command !== "ask") {
|
|
44
|
+
throw new Error(`Unknown command: ${args.command}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const session = await request(`${baseUrl}/api/sessions`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
title: args.title || "Codex handoff",
|
|
51
|
+
model: args.model,
|
|
52
|
+
apiMode: args.api
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (args.context) {
|
|
57
|
+
await request(`${baseUrl}/api/sessions/${session.id}`, {
|
|
58
|
+
method: "PATCH",
|
|
59
|
+
body: JSON.stringify({ manualContext: args.context })
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const file of args.files) {
|
|
64
|
+
await request(`${baseUrl}/api/sessions/${session.id}/files`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
body: JSON.stringify({ path: file })
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (args.command !== "ask" && args.question) {
|
|
71
|
+
await request(`${baseUrl}/api/sessions/${session.id}/messages`, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
body: JSON.stringify({ role: "user", content: args.question })
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log("Sidecar session ready");
|
|
78
|
+
console.log(`${baseUrl}`);
|
|
79
|
+
console.log(`Session: ${session.id}`);
|
|
80
|
+
console.log(`Open: ${baseUrl}`);
|
|
81
|
+
|
|
82
|
+
if (args.command === "ask") {
|
|
83
|
+
if (!args.question) {
|
|
84
|
+
throw new Error("--question is required for ask mode.");
|
|
85
|
+
}
|
|
86
|
+
console.log("");
|
|
87
|
+
console.log("Sidecar answer:");
|
|
88
|
+
await streamAnswer(`${baseUrl}/api/sessions/${session.id}/stream`, {
|
|
89
|
+
message: args.question,
|
|
90
|
+
enableTools: true,
|
|
91
|
+
includeInstructionFiles: false
|
|
92
|
+
});
|
|
93
|
+
console.log("");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseArgs(raw) {
|
|
97
|
+
const parsed = { command: "call", files: [] };
|
|
98
|
+
const first = raw[0];
|
|
99
|
+
const start = first && !first.startsWith("-") ? 1 : 0;
|
|
100
|
+
if (start) {
|
|
101
|
+
parsed.command = first;
|
|
102
|
+
}
|
|
103
|
+
for (let i = start; i < raw.length; i += 1) {
|
|
104
|
+
const key = raw[i];
|
|
105
|
+
const value = raw[i + 1];
|
|
106
|
+
if (key === "--help" || key === "-h") parsed.help = true;
|
|
107
|
+
if (key === "--url") parsed.url = value, i += 1;
|
|
108
|
+
if (key === "--workspace") parsed.workspace = value, i += 1;
|
|
109
|
+
if (key === "--graph") parsed.graph = value, i += 1;
|
|
110
|
+
if (key === "--no-graph") parsed.noGraph = true;
|
|
111
|
+
if (key === "--no-skills") parsed.noSkills = true;
|
|
112
|
+
if (key === "--force") parsed.force = true;
|
|
113
|
+
if (key === "--title") parsed.title = value, i += 1;
|
|
114
|
+
if (key === "--context") parsed.context = value, i += 1;
|
|
115
|
+
if (key === "--file") parsed.files.push(value), i += 1;
|
|
116
|
+
if (key === "--question") parsed.question = value, i += 1;
|
|
117
|
+
if (key === "--model") parsed.model = value, i += 1;
|
|
118
|
+
if (key === "--api") parsed.api = value, i += 1;
|
|
119
|
+
}
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function installWorkspace(args) {
|
|
124
|
+
const workspaceRoot = resolve(args.workspace || process.cwd());
|
|
125
|
+
const graphManifestPath = normalizeWorkspacePath(args.graph || "research/graph.yaml");
|
|
126
|
+
const sideDir = join(workspaceRoot, ".side");
|
|
127
|
+
const sessionsDir = join(sideDir, "sessions");
|
|
128
|
+
|
|
129
|
+
await mkdir(workspaceRoot, { recursive: true });
|
|
130
|
+
await mkdir(sessionsDir, { recursive: true });
|
|
131
|
+
await writeSidecarConfig(join(sideDir, "config.json"), graphManifestPath);
|
|
132
|
+
await writeFileIfMissing(join(sessionsDir, "index.json"), `${JSON.stringify({ sessions: [] }, null, 2)}\n`);
|
|
133
|
+
await ensureGitignore(workspaceRoot);
|
|
134
|
+
|
|
135
|
+
if (!args.noSkills) {
|
|
136
|
+
await installBundledSkills(workspaceRoot, Boolean(args.force));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!args.noGraph) {
|
|
140
|
+
await installStarterGraph(workspaceRoot, graphManifestPath, Boolean(args.force));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log("Research Sidecar workspace installed");
|
|
144
|
+
console.log(`Workspace: ${workspaceRoot}`);
|
|
145
|
+
console.log(`Config: ${join(sideDir, "config.json")}`);
|
|
146
|
+
console.log(`Graph: ${graphManifestPath}${args.noGraph ? " (not created)" : ""}`);
|
|
147
|
+
console.log("");
|
|
148
|
+
console.log("Run from a home/user-level Sidecar install:");
|
|
149
|
+
console.log(`SIDECAR_WORKSPACE_ROOT="${workspaceRoot}" npm run dev`);
|
|
150
|
+
console.log("");
|
|
151
|
+
console.log("Or, if the Sidecar app itself is copied into this workspace:");
|
|
152
|
+
console.log("cd sidecar && npm run dev");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function writeSidecarConfig(configPath, graphManifestPath) {
|
|
156
|
+
const existing = await readJsonFile(configPath);
|
|
157
|
+
const config = {
|
|
158
|
+
...existing,
|
|
159
|
+
graph: {
|
|
160
|
+
...(existing.graph || {}),
|
|
161
|
+
manifestPath: graphManifestPath
|
|
162
|
+
},
|
|
163
|
+
tools: {
|
|
164
|
+
allowedWriteExtensions: defaultAllowedWriteExtensions,
|
|
165
|
+
...(existing.tools || {})
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
169
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function installBundledSkills(workspaceRoot, force) {
|
|
173
|
+
const sourceRoot = await findBundledSkillsRoot();
|
|
174
|
+
if (!sourceRoot) return;
|
|
175
|
+
const names = await readdir(sourceRoot, { withFileTypes: true });
|
|
176
|
+
await mkdir(join(workspaceRoot, "skills"), { recursive: true });
|
|
177
|
+
for (const entry of names) {
|
|
178
|
+
if (!entry.isDirectory()) continue;
|
|
179
|
+
const source = join(sourceRoot, entry.name);
|
|
180
|
+
const target = join(workspaceRoot, "skills", entry.name);
|
|
181
|
+
if (!force && (await exists(target))) continue;
|
|
182
|
+
await cp(source, target, { recursive: true, force, errorOnExist: false });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function findBundledSkillsRoot() {
|
|
187
|
+
for (const candidate of [join(repoRoot, "skills"), join(appRoot, "skills")]) {
|
|
188
|
+
if (await exists(candidate)) return candidate;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function installStarterGraph(workspaceRoot, graphManifestPath, force) {
|
|
194
|
+
const graphPath = join(workspaceRoot, graphManifestPath);
|
|
195
|
+
const graphDir = dirname(graphPath);
|
|
196
|
+
const notePath = join(graphDir, "rq.main.md");
|
|
197
|
+
const graphBody = `root: rq.main
|
|
198
|
+
|
|
199
|
+
ui:
|
|
200
|
+
layout: LR
|
|
201
|
+
expanded: [rq.main]
|
|
202
|
+
|
|
203
|
+
nodes:
|
|
204
|
+
- id: rq.main
|
|
205
|
+
title: Core research question
|
|
206
|
+
type: question
|
|
207
|
+
file: ./rq.main.md
|
|
208
|
+
status: active
|
|
209
|
+
tags: [framing]
|
|
210
|
+
|
|
211
|
+
edges: []
|
|
212
|
+
`;
|
|
213
|
+
const noteBody = `# Core research question
|
|
214
|
+
|
|
215
|
+
Use this note as the first graph node, or replace it with your existing research Markdown.
|
|
216
|
+
`;
|
|
217
|
+
await mkdir(graphDir, { recursive: true });
|
|
218
|
+
await writeFileMaybe(graphPath, graphBody, force);
|
|
219
|
+
await writeFileMaybe(notePath, noteBody, force);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function ensureGitignore(workspaceRoot) {
|
|
223
|
+
const path = join(workspaceRoot, ".gitignore");
|
|
224
|
+
const current = await readTextFile(path);
|
|
225
|
+
if (current === null) {
|
|
226
|
+
await writeFile(path, ".side/\n", "utf8");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (!current.split(/\r?\n/).some((line) => line.trim() === ".side/" || line.trim() === ".side")) {
|
|
230
|
+
const suffix = current.endsWith("\n") || current.length === 0 ? "" : "\n";
|
|
231
|
+
await writeFile(path, `${current}${suffix}.side/\n`, "utf8");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function writeFileIfMissing(path, body) {
|
|
236
|
+
if (await exists(path)) return;
|
|
237
|
+
await mkdir(dirname(path), { recursive: true });
|
|
238
|
+
await writeFile(path, body, "utf8");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function writeFileMaybe(path, body, force) {
|
|
242
|
+
if (!force && (await exists(path))) return;
|
|
243
|
+
await mkdir(dirname(path), { recursive: true });
|
|
244
|
+
await writeFile(path, body, "utf8");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function readJsonFile(path) {
|
|
248
|
+
const raw = await readTextFile(path);
|
|
249
|
+
if (!raw?.trim()) return {};
|
|
250
|
+
return JSON.parse(raw);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function readTextFile(path) {
|
|
254
|
+
try {
|
|
255
|
+
return await readFile(path, "utf8");
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (error.code === "ENOENT") return null;
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function exists(path) {
|
|
263
|
+
try {
|
|
264
|
+
await access(path);
|
|
265
|
+
return true;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error.code === "ENOENT") return false;
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function normalizeWorkspacePath(path) {
|
|
273
|
+
const normalized = path.trim().replace(/^\.\//, "");
|
|
274
|
+
if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized === "..") {
|
|
275
|
+
throw new Error("Workspace paths must stay inside the workspace.");
|
|
276
|
+
}
|
|
277
|
+
return normalized;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function request(url, init) {
|
|
281
|
+
const response = await fetch(url, {
|
|
282
|
+
...init,
|
|
283
|
+
headers: {
|
|
284
|
+
"Content-Type": "application/json",
|
|
285
|
+
...(init.headers || {})
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
const body = await response.json().catch(() => ({}));
|
|
290
|
+
throw new Error(body.error || response.statusText);
|
|
291
|
+
}
|
|
292
|
+
return response.json();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function streamAnswer(url, body) {
|
|
296
|
+
const response = await fetch(url, {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: { "Content-Type": "application/json" },
|
|
299
|
+
body: JSON.stringify(body)
|
|
300
|
+
});
|
|
301
|
+
if (!response.ok || !response.body) {
|
|
302
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
303
|
+
throw new Error(errorBody.error || response.statusText);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const decoder = new TextDecoder();
|
|
307
|
+
let buffer = "";
|
|
308
|
+
for await (const chunk of response.body) {
|
|
309
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
310
|
+
const frames = buffer.split("\n\n");
|
|
311
|
+
buffer = frames.pop() || "";
|
|
312
|
+
for (const frame of frames) {
|
|
313
|
+
if (!frame.startsWith("data: ")) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const payload = JSON.parse(frame.slice(6));
|
|
317
|
+
if (payload.type === "delta") {
|
|
318
|
+
process.stdout.write(payload.delta);
|
|
319
|
+
}
|
|
320
|
+
if (payload.type === "error") {
|
|
321
|
+
throw new Error(payload.error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { cp, mkdir, rm } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const appRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const repoRoot = resolve(appRoot, "..");
|
|
9
|
+
const source = resolve(repoRoot, "skills");
|
|
10
|
+
const target = join(appRoot, "skills");
|
|
11
|
+
|
|
12
|
+
await rm(target, { recursive: true, force: true });
|
|
13
|
+
await mkdir(target, { recursive: true });
|
|
14
|
+
await cp(source, target, { recursive: true });
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: research-graph-sop
|
|
3
|
+
description: Use when working in a research repository with graph.yaml, Markdown research notes, hypotheses, evidence, experiments, literature sources, or when Codex is asked to inspect, create, update, or maintain a lightweight research graph.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Research Graph SOP
|
|
7
|
+
|
|
8
|
+
Use the graph as a small, inspectable map of the research state. Put structure and relationships in `graph.yaml`; put reasoning, evidence details, quotes, experiment notes, and drafts in Markdown files.
|
|
9
|
+
|
|
10
|
+
Core principle: **thin graph, thick notes, active agent**. The graph should reduce cognitive load, not become a database the researcher has to maintain.
|
|
11
|
+
|
|
12
|
+
## First Move
|
|
13
|
+
|
|
14
|
+
If `graph.yaml` exists, read it before proposing structure changes. Then read only the linked Markdown/source files needed to understand the active question, claims, evidence, methods, and tasks.
|
|
15
|
+
|
|
16
|
+
If no graph exists, inspect nearby research notes, source files, experiment notes, and task files. Build the smallest useful graph that lets a reader answer:
|
|
17
|
+
|
|
18
|
+
- What is being investigated?
|
|
19
|
+
- What is currently believed or hypothesized?
|
|
20
|
+
- Why believe, doubt, or test it?
|
|
21
|
+
- What should happen next?
|
|
22
|
+
|
|
23
|
+
## Three Entry Points
|
|
24
|
+
|
|
25
|
+
### Explore Current Research
|
|
26
|
+
|
|
27
|
+
Use this when opening an existing research repo or refreshing context.
|
|
28
|
+
|
|
29
|
+
1. Identify the root question or active hypothesis.
|
|
30
|
+
2. List active claims, rival explanations, methods, evidence, sources, and next tasks.
|
|
31
|
+
3. Detect gaps: unsupported claims, missing rivals, unclear concepts, weak methods, stale tasks.
|
|
32
|
+
4. Update the graph only when the update improves navigation or analysis.
|
|
33
|
+
|
|
34
|
+
### Start From A Hypothesis
|
|
35
|
+
|
|
36
|
+
Use this when the user gives a hypothesis before a graph exists or before the project is well framed.
|
|
37
|
+
|
|
38
|
+
Create a minimal validation graph:
|
|
39
|
+
|
|
40
|
+
- a `claim` node for the hypothesis
|
|
41
|
+
- a `question` node it answers
|
|
42
|
+
- one or more rival `claim` nodes when plausible
|
|
43
|
+
- `concept` nodes only for necessary definitions or boundaries
|
|
44
|
+
- `method`, `evidence`, `source`, or `task` nodes only when they need tracking
|
|
45
|
+
|
|
46
|
+
Do not force every prediction, assumption, or falsifier into the graph. Put local details in the claim or method note unless they need separate links.
|
|
47
|
+
|
|
48
|
+
### Maintain During Research
|
|
49
|
+
|
|
50
|
+
Use this after experiments, readings, observations, or analysis updates.
|
|
51
|
+
|
|
52
|
+
1. Add or update the relevant Markdown note first when there is substantive content.
|
|
53
|
+
2. Link evidence to the exact claim it bears on.
|
|
54
|
+
3. Use `supports` only for genuine support, `contradicts` for negative evidence or rival pressure, and `depends_on` for assumptions.
|
|
55
|
+
4. If evidence changes the research direction, revise the question, boundary, method, or task instead of forcing the old hypothesis to survive.
|
|
56
|
+
|
|
57
|
+
## Minimal Graph Vocabulary
|
|
58
|
+
|
|
59
|
+
Prefer the existing Sidecar vocabulary:
|
|
60
|
+
|
|
61
|
+
| Node type | Use for |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `question` | research questions and decomposed subquestions |
|
|
64
|
+
| `claim` | hypotheses, mechanism claims, rival explanations |
|
|
65
|
+
| `concept` | definitions, variables, scope, boundary conditions |
|
|
66
|
+
| `method` | experiments, operationalization, identification strategy |
|
|
67
|
+
| `evidence` | observations, results, patterns, counterexamples |
|
|
68
|
+
| `source` | papers, datasets, documents, external materials |
|
|
69
|
+
| `task` | next research action |
|
|
70
|
+
| `output` | draft conclusions, reports, paper sections |
|
|
71
|
+
|
|
72
|
+
| Edge kind | Use for |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `decomposes` | breaks a question into subquestions |
|
|
75
|
+
| `answers` | a claim answers a question |
|
|
76
|
+
| `operationalizes` | a method makes a claim observable |
|
|
77
|
+
| `supports` | evidence/source supports a claim |
|
|
78
|
+
| `contradicts` | evidence/source/rival weakens a claim |
|
|
79
|
+
| `depends_on` | a claim or method relies on a premise |
|
|
80
|
+
| `cites` | a node uses a source |
|
|
81
|
+
| `leads_to` | a result creates a task or next step |
|
|
82
|
+
|
|
83
|
+
## YAML Contract
|
|
84
|
+
|
|
85
|
+
When creating or editing `graph.yaml`, keep it valid, boring YAML. Use two-space indentation, arrays with `-`, no tabs, and quote strings only when they contain characters that could confuse YAML (`:`, `#`, `{}`, `[]`, leading `*`, or multiline text).
|
|
86
|
+
|
|
87
|
+
Top-level shape:
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
root: rq.main
|
|
91
|
+
|
|
92
|
+
ui:
|
|
93
|
+
layout: LR
|
|
94
|
+
expanded: [rq.main]
|
|
95
|
+
|
|
96
|
+
nodes:
|
|
97
|
+
- id: rq.main
|
|
98
|
+
title: Core research question
|
|
99
|
+
type: question
|
|
100
|
+
summary: One sentence is enough when no note exists yet.
|
|
101
|
+
status: active
|
|
102
|
+
tags: [framing]
|
|
103
|
+
|
|
104
|
+
edges:
|
|
105
|
+
- from: rq.main
|
|
106
|
+
to: claim.001
|
|
107
|
+
kind: answers
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Required:
|
|
111
|
+
|
|
112
|
+
- `root`: id of an existing node.
|
|
113
|
+
- `nodes`: array of node objects.
|
|
114
|
+
- each node: `id`, `title`, `type`.
|
|
115
|
+
- `edges`: array, empty is fine.
|
|
116
|
+
- each edge: `from`, `to`, `kind`; both ids must exist in `nodes`.
|
|
117
|
+
|
|
118
|
+
Optional node fields:
|
|
119
|
+
|
|
120
|
+
- `summary`: use for short, one-sentence nodes that do not need a document yet.
|
|
121
|
+
- `file`: one linked Markdown/HTML/text document.
|
|
122
|
+
- `files`: multiple linked documents. Use either strings or `{ path, title }` objects.
|
|
123
|
+
- `status`: `active`, `draft`, `blocked`, or `done`.
|
|
124
|
+
- `tags`: array of short labels.
|
|
125
|
+
|
|
126
|
+
Valid document-link patterns:
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
nodes:
|
|
130
|
+
- id: claim.short
|
|
131
|
+
title: Short claim
|
|
132
|
+
type: claim
|
|
133
|
+
summary: This node is intentionally just one sentence for now.
|
|
134
|
+
|
|
135
|
+
- id: evidence.single
|
|
136
|
+
title: Single note
|
|
137
|
+
type: evidence
|
|
138
|
+
file: ./evidence.md
|
|
139
|
+
|
|
140
|
+
- id: source.bundle
|
|
141
|
+
title: Source bundle
|
|
142
|
+
type: source
|
|
143
|
+
files:
|
|
144
|
+
- ./paper.md
|
|
145
|
+
- path: ./appendix.html
|
|
146
|
+
title: Appendix preview
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Do not force every node to link to a document. Add `file` or `files` only when the longer note exists or when creating the link improves navigation. Missing linked files are allowed while drafting, but they should be intentional and called out in the change report.
|
|
150
|
+
|
|
151
|
+
## Agent Discipline
|
|
152
|
+
|
|
153
|
+
Act primarily as an active research assistant: propose subquestions, hypotheses, rival explanations, operationalizations, and next tasks.
|
|
154
|
+
|
|
155
|
+
Also apply two constraints:
|
|
156
|
+
|
|
157
|
+
- **Reviewer:** attack weak claims, hidden assumptions, unclear concepts, non-falsifiable hypotheses, and methods that build in their conclusion.
|
|
158
|
+
- **Gatekeeper:** do not present a claim as settled unless it has relevant evidence and plausible rival explanations have been considered.
|
|
159
|
+
|
|
160
|
+
New agent-created nodes should usually be `status: draft`. Use `active` when the researcher is actually pursuing the node, `blocked` when progress depends on missing material, and `done` only for completed tasks or stable source nodes.
|
|
161
|
+
|
|
162
|
+
## Keep It Lightweight
|
|
163
|
+
|
|
164
|
+
Add a graph node only when the item needs to be navigated, connected, reused, challenged, or tracked over time.
|
|
165
|
+
|
|
166
|
+
Avoid:
|
|
167
|
+
|
|
168
|
+
- duplicating long Markdown content in `summary`
|
|
169
|
+
- making every sentence a node
|
|
170
|
+
- adding schema fields the current repo does not already use
|
|
171
|
+
- treating the graph as proof by itself
|
|
172
|
+
- hiding uncertainty behind tidy structure
|
|
173
|
+
|
|
174
|
+
Good graph changes make the current research easier to inspect in under a minute.
|
|
175
|
+
|
|
176
|
+
## Change Report
|
|
177
|
+
|
|
178
|
+
When updating the graph, summarize:
|
|
179
|
+
|
|
180
|
+
- nodes added, changed, or removed
|
|
181
|
+
- important edges added or changed
|
|
182
|
+
- Markdown notes created or updated
|
|
183
|
+
- remaining methodological gaps or next tasks
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scholar-mode
|
|
3
|
+
description: Use when a task is explicitly framed as research, scholarship, literature analysis, theory building, methodology critique, paper reading, academic writing, research notes, or evaluating a research intuition.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Scholar Mode
|
|
7
|
+
|
|
8
|
+
Act as a research collaborator, not a coding agent. Optimize for conceptual precision, evidence quality, argument strength, and readable synthesis.
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Use for research thinking, papers, notes, claims, hypotheses, theory, methods, scholarly writing, literature reading, and judging research intuitions.
|
|
13
|
+
|
|
14
|
+
Do not apply when the user's primary request is implementation, debugging, tests, repository operations, app design, software architecture, or ordinary factual help. If a turn mixes research and implementation, apply this skill only to the research-analysis portion.
|
|
15
|
+
|
|
16
|
+
When this skill applies, do not propose code, architecture, tests, apps, agents, implementation plans, roadmaps, specs, or project structures unless explicitly requested. Do not turn research questions into software tasks or project-management framing. Do not scan repositories, run commands, install dependencies, start services, use git, or edit files unless directly useful for the research task or explicitly requested.
|
|
17
|
+
|
|
18
|
+
## Judgment Protocol
|
|
19
|
+
|
|
20
|
+
Before answering, internally check: What is being judged? What evidence or material is available? What assumptions are doing the work? What would weaken or falsify the claim?
|
|
21
|
+
|
|
22
|
+
- Lead with the substantive conclusion, then give the basis and caveats.
|
|
23
|
+
- Distinguish facts, inferences, assumptions, value judgments, and open questions.
|
|
24
|
+
- Say weak evidence is weak; evaluate weak ideas instead of praising them by default.
|
|
25
|
+
- Do not invent citations, paper titles, authors, findings, or consensus.
|
|
26
|
+
- If relying on memory rather than checked sources, label it as memory or inference.
|
|
27
|
+
|
|
28
|
+
## Research Graph Use
|
|
29
|
+
|
|
30
|
+
When a workspace includes `graph.yaml`, treat it as the structural map of the research project: nodes, relationships, and file pointers. Treat Markdown files as the content layer. If the user asks to update research structure, prefer editing the graph manifest and the affected Markdown notes together.
|
|
31
|
+
|
|
32
|
+
## Output
|
|
33
|
+
|
|
34
|
+
Be brief, direct, and information-dense. For longer answers, start with a short TL;DR, then give the judgment, main reasons, caveats, and next move. Use tables only when comparison helps.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sidecar-thinking
|
|
3
|
+
description: Use when Codex should call the local Research Sidecar app, create or inspect a Sidecar session, hand off explicit context, maintain a research graph, or use the Sidecar API/CLI for a second-opinion review.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sidecar Thinking
|
|
7
|
+
|
|
8
|
+
Use this skill to connect Codex with the local Research Sidecar app. The app is a workspace-aware web UI with chat sessions, a manifest-backed research graph, Markdown/HTML previews, and restricted workspace tools.
|
|
9
|
+
|
|
10
|
+
## Start Or Find The App
|
|
11
|
+
|
|
12
|
+
If the server is not already running, start it from the workspace root:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
research-sidecar
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Default URL: `http://localhost:4317`.
|
|
19
|
+
|
|
20
|
+
The directory where `research-sidecar` is launched becomes the workspace root. The workspace config lives at `.side/config.json`.
|
|
21
|
+
|
|
22
|
+
## Default Handoff
|
|
23
|
+
|
|
24
|
+
Prefer staging a session for the user:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd sidecar
|
|
28
|
+
npm run codex:call -- --title "Review" --context "Codex summary..." --file research/graph.yaml --question "What is the weakest assumption?"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use automatic ask mode only when the user explicitly wants Codex to relay the answer:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd sidecar
|
|
35
|
+
npm run codex:ask -- --title "Review" --context "Codex summary..." --question "What should Codex do next?"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Research Graph
|
|
39
|
+
|
|
40
|
+
The graph is manifest-first:
|
|
41
|
+
|
|
42
|
+
- `graph.yaml` is the authority for nodes, edges, UI defaults, and file pointers.
|
|
43
|
+
- Markdown and HTML files hold node content.
|
|
44
|
+
- Markdown frontmatter can supply local metadata, but `graph.yaml` wins on conflicts.
|
|
45
|
+
- Graph file links are relative to the graph manifest directory by default. Use a leading `/` for an explicit workspace-root-relative link.
|
|
46
|
+
|
|
47
|
+
Default manifest path: `research/graph.yaml`. Override with `SIDECAR_GRAPH_MANIFEST` or `.side/config.json` at `graph.manifestPath`. The UI can discover graph files across the workspace and save the selected graph to `.side/config.json`.
|
|
48
|
+
|
|
49
|
+
## API Quick Reference
|
|
50
|
+
|
|
51
|
+
- `GET /api/config`
|
|
52
|
+
- `PATCH /api/config`
|
|
53
|
+
- `GET /api/graphs`
|
|
54
|
+
- `GET /api/workspace`
|
|
55
|
+
- `POST /api/workspace/skills/install`
|
|
56
|
+
- `GET /api/workspace/file?path=<workspace-relative-path>`
|
|
57
|
+
- `GET /api/workspace/raw?path=<workspace-relative-path>`
|
|
58
|
+
- `GET /api/research-graph`
|
|
59
|
+
- `GET /api/sessions`
|
|
60
|
+
- `POST /api/sessions`
|
|
61
|
+
- `GET /api/sessions/:id`
|
|
62
|
+
- `PATCH /api/sessions/:id`
|
|
63
|
+
- `POST /api/sessions/:id/files`
|
|
64
|
+
- `POST /api/sessions/:id/messages`
|
|
65
|
+
- `POST /api/sessions/:id/stream`
|
|
66
|
+
|
|
67
|
+
All workspace paths are constrained to the configured workspace root.
|